From 5d42a2ec64357e8ffbca439b50c3a59840640738 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 15 Jul 2021 12:25:56 +0200 Subject: [PATCH 0001/1227] 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 0002/1227] 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 0003/1227] 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 0004/1227] 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 0005/1227] 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 bf39fc72a550ce8ac6fc8d4c07bc2359ec9cdfa8 Mon Sep 17 00:00:00 2001 From: "felix.wang" Date: Wed, 19 Jan 2022 18:31:24 -0800 Subject: [PATCH 0006/1227] Update submodules. --- openpype/modules/default_modules/ftrack/python2_vendor/arrow | 2 +- .../default_modules/ftrack/python2_vendor/ftrack-python-api | 2 +- repos/avalon-core | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/default_modules/ftrack/python2_vendor/arrow b/openpype/modules/default_modules/ftrack/python2_vendor/arrow index b746fedf72..4c4689c6d9 160000 --- a/openpype/modules/default_modules/ftrack/python2_vendor/arrow +++ b/openpype/modules/default_modules/ftrack/python2_vendor/arrow @@ -1 +1 @@ -Subproject commit b746fedf7286c3755a46f07ab72f4c414cd41fc0 +Subproject commit 4c4689c6d97ed2b1f37a67b96c561266c66ee088 diff --git a/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api b/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api index d277f474ab..ddf943a5dc 160000 --- a/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api +++ b/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api @@ -1 +1 @@ -Subproject commit d277f474ab016e7b53479c36af87cb861d0cc53e +Subproject commit ddf943a5dcc44d2cecf29896d2075f5198b699aa diff --git a/repos/avalon-core b/repos/avalon-core index ffe9e910f1..047fe1f5bb 160000 --- a/repos/avalon-core +++ b/repos/avalon-core @@ -1 +1 @@ -Subproject commit ffe9e910f1f382e222d457d8e4a8426c41ed43ae +Subproject commit 047fe1f5bba425b8b41c2197961925c1e2ec5fdf From 48fd45ed1aedfba8ac8afdc1f947970c2d76dc85 Mon Sep 17 00:00:00 2001 From: "felix.wang" Date: Mon, 24 Jan 2022 14:42:58 -0800 Subject: [PATCH 0007/1227] Revert "Update submodules." This reverts commit bf39fc72a550ce8ac6fc8d4c07bc2359ec9cdfa8. --- openpype/modules/default_modules/ftrack/python2_vendor/arrow | 2 +- .../default_modules/ftrack/python2_vendor/ftrack-python-api | 2 +- repos/avalon-core | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/default_modules/ftrack/python2_vendor/arrow b/openpype/modules/default_modules/ftrack/python2_vendor/arrow index 4c4689c6d9..b746fedf72 160000 --- a/openpype/modules/default_modules/ftrack/python2_vendor/arrow +++ b/openpype/modules/default_modules/ftrack/python2_vendor/arrow @@ -1 +1 @@ -Subproject commit 4c4689c6d97ed2b1f37a67b96c561266c66ee088 +Subproject commit b746fedf7286c3755a46f07ab72f4c414cd41fc0 diff --git a/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api b/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api index ddf943a5dc..d277f474ab 160000 --- a/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api +++ b/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api @@ -1 +1 @@ -Subproject commit ddf943a5dcc44d2cecf29896d2075f5198b699aa +Subproject commit d277f474ab016e7b53479c36af87cb861d0cc53e diff --git a/repos/avalon-core b/repos/avalon-core index 047fe1f5bb..ffe9e910f1 160000 --- a/repos/avalon-core +++ b/repos/avalon-core @@ -1 +1 @@ -Subproject commit 047fe1f5bba425b8b41c2197961925c1e2ec5fdf +Subproject commit ffe9e910f1f382e222d457d8e4a8426c41ed43ae From 1b184a09f9d3c9ac656f45a2bacded0125399795 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Fri, 18 Feb 2022 10:31:09 +0100 Subject: [PATCH 0008/1227] add root keys and project keys --- openpype/lib/path_tools.py | 88 ++++++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index c0b78c5724..181417c38c 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -6,11 +6,12 @@ import logging import six from openpype.settings import get_project_settings -from openpype.settings.lib import get_site_local_overrides from .anatomy import Anatomy from .profiles_filtering import filter_profiles +import avalon.api + log = logging.getLogger(__name__) @@ -130,45 +131,75 @@ def get_last_version_from_path(path_dir, filter): return None -def compute_paths(basic_paths_items, project_root): +def concatenate_splitted_paths(split_paths, anatomy): pattern_array = re.compile(r"\[.*\]") - project_root_key = "__project_root__" output = [] - for path_items in basic_paths_items: + for path_items in split_paths: clean_items = [] + if isinstance(path_items, str): + path_items = [path_items] + for path_item in path_items: - matches = re.findall(pattern_array, path_item) - if len(matches) > 0: - path_item = path_item.replace(matches[0], "") - if path_item == project_root_key: - path_item = project_root + if not re.match(r"{.+}", path_item): + path_item = re.sub(pattern_array, "", path_item) clean_items.append(path_item) + + # backward compatibility + if "__project_root__" in path_items: + for root, root_path in anatomy.roots.items(): + if not os.path.exists(str(root_path)): + log.debug("Root {} path path {} not exist on \ + computer!".format(root, root_path)) + continue + clean_items = [f"{{root[{root}]}}", "{project[name]}"] \ + + clean_items[1:] + output.append(os.path.normpath(os.path.sep.join(clean_items))) + continue + output.append(os.path.normpath(os.path.sep.join(clean_items))) + return output +def get_format_data(anatomy): + dbcon = avalon.api.AvalonMongoDB() + dbcon.Session["AVALON_PROJECT"] = anatomy.project_name + project_doc = dbcon.find_one({"type": "project"}) + project_code = project_doc["data"]["code"] + + return { + "root": anatomy.roots, + "project": { + "name": anatomy.project_name, + "code": project_code + }, + } + + +def fill_paths(path_list, anatomy): + format_data = get_format_data(anatomy) + filled_paths = [] + + for path in path_list: + new_path = path.format(**format_data) + filled_paths.append(new_path) + + return filled_paths + + def create_project_folders(basic_paths, project_name): anatomy = Anatomy(project_name) - roots_paths = [] - if isinstance(anatomy.roots, dict): - for root in anatomy.roots.values(): - roots_paths.append(root.value) - else: - roots_paths.append(anatomy.roots.value) - for root_path in roots_paths: - project_root = os.path.join(root_path, project_name) - full_paths = compute_paths(basic_paths, project_root) - # Create folders - for path in full_paths: - full_path = path.format(project_root=project_root) - if os.path.exists(full_path): - log.debug( - "Folder already exists: {}".format(full_path) - ) - else: - log.debug("Creating folder: {}".format(full_path)) - os.makedirs(full_path) + concat_paths = concatenate_splitted_paths(basic_paths, anatomy) + filled_paths = fill_paths(concat_paths, anatomy) + + # Create folders + for path in filled_paths: + if os.path.exists(path): + log.debug("Folder already exists: {}".format(path)) + else: + log.debug("Creating folder: {}".format(path)) + os.makedirs(path) def _list_path_items(folder_structure): @@ -267,6 +298,7 @@ class HostDirmap: on_dirmap_enabled: run host code for enabling dirmap do_dirmap: run host code to do actual remapping """ + def __init__(self, host_name, project_settings, sync_module=None): self.host_name = host_name self.project_settings = project_settings From 075b80563b84e720d1bd18a070b0a194a322a9a8 Mon Sep 17 00:00:00 2001 From: BenoitConnan Date: Mon, 28 Feb 2022 15:09:16 +0100 Subject: [PATCH 0009/1227] add python 2 compatibility to path_tools --- openpype/lib/path_tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index 181417c38c..916e392eb2 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -151,8 +151,8 @@ def concatenate_splitted_paths(split_paths, anatomy): log.debug("Root {} path path {} not exist on \ computer!".format(root, root_path)) continue - clean_items = [f"{{root[{root}]}}", "{project[name]}"] \ - + clean_items[1:] + clean_items = ["{{root[{}]}}".format(root), + r"{project[name]}"] + clean_items[1:] output.append(os.path.normpath(os.path.sep.join(clean_items))) continue From d88ed919e6a4a566b8ff8b289415c471de454b00 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 16 Mar 2022 22:09:23 +0100 Subject: [PATCH 0010/1227] First draft pass of refactoring the Integrator --- openpype/plugins/publish/integrate_new.py | 1076 ++++++++++----------- 1 file changed, 508 insertions(+), 568 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index e8dab089af..e4986e3b3f 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -7,9 +7,8 @@ import clique import errno import six import re -import shutil -from pymongo import DeleteOne, InsertOne +from pymongo import DeleteOne, InsertOne, UpdateOne import pyblish.api from avalon import io from avalon.api import format_template_with_optional_keys @@ -31,6 +30,17 @@ else: log = logging.getLogger(__name__) +def get_frame_padded(frame, padding): + """Return frame number as string with `padding` amount of padded zeros""" + return "{frame:0{padding}d}".format(padding=padding, frame=frame) + + +def get_first_frame_padded(collection): + """Return first frame as padded number from `clique.Collection`""" + start_frame = next(iter(collection.indexes)) + return get_frame_padded(start_frame, padding=collection.padding) + + class IntegrateAssetNew(pyblish.api.InstancePlugin): """Resolve any dependency issues @@ -108,7 +118,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): exclude_families = ["clip"] db_representation_context_keys = [ "project", "asset", "task", "subset", "version", "representation", - "family", "hierarchy", "task", "username" + "family", "hierarchy", "task", "username", "frame", "udim" ] default_template_name = "publish" @@ -116,38 +126,40 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): TMP_FILE_EXT = 'tmp' # file_url : file_size of all published and uploaded files - integrated_file_sizes = {} + destinations = list() # Attributes set by settings template_name_profiles = None subset_grouping_profiles = None def process(self, instance): - self.integrated_file_sizes = {} - if [ef for ef in self.exclude_families - if instance.data["family"] in ef]: + self.destinations = [] + + # Exclude instances that also contain families from exclude families + families = set( + # Consider family and families data + [instance.data["family"]] + instance.data.get("families", []) + ) + if families & set(self.exclude_families): return try: self.register(instance) self.log.info("Integrated Asset in to the database ...") - self.log.info("instance.data: {}".format(instance.data)) - self.handle_destination_files(self.integrated_file_sizes, + self.handle_destination_files(self.destinations, 'finalize') except Exception: # clean destination self.log.critical("Error when registering", exc_info=True) - self.handle_destination_files(self.integrated_file_sizes, 'remove') + self.handle_destination_files(self.destinations, 'remove') six.reraise(*sys.exc_info()) - def register(self, instance): - # Required environment variables - anatomy_data = instance.data["anatomyData"] - - io.install() + def prepare_anatomy(self, instance): + """Prepare anatomy data used to define representation destinations""" context = instance.context + anatomy_data = instance.data["anatomyData"] project_entity = instance.data["projectEntity"] context_asset_name = None @@ -206,8 +218,36 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # Fill family in anatomy data anatomy_data["family"] = instance.data.get("family") - stagingdir = instance.data.get("stagingDir") - if not stagingdir: + intent_value = instance.context.data.get("intent") + if intent_value and isinstance(intent_value, dict): + intent_value = intent_value.get("value") + + if intent_value: + anatomy_data["intent"] = intent_value + + # Get profile + key_values = { + "families": self.main_family_from_instance(instance), + "tasks": task_name, + "hosts": instance.context.data["hostName"], + "task_types": task_type + } + profile = filter_profiles( + self.template_name_profiles, + key_values, + logger=self.log + ) + + template_name = "publish" + if profile: + template_name = profile["template_name"] + + return template_name, anatomy_data + + def register(self, instance): + + instance_stagingdir = instance.data.get("stagingDir") + if not instance_stagingdir: self.log.info(( "{0} is missing reference to staging directory." " Will try to get it from representation." @@ -215,7 +255,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): else: self.log.debug( - "Establishing staging directory @ {0}".format(stagingdir) + "Establishing staging directory " + "@ {0}".format(instance_stagingdir) ) # Ensure at least one file is set up for transfer in staging dir. @@ -227,28 +268,74 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): ) ) - subset = self.get_subset(asset_entity, instance) - instance.data["subsetEntity"] = subset + subset = self.register_subset(instance) + + version = self.register_version(instance, subset) + instance.data["versionEntity"] = version + instance.data['version'] = version['name'] + + existing_repres = list(io.find({ + "parent": version["_id"], + "type": "archived_representation" + })) + + # Find the representations to transfer amongst the files + # Each should be a single representation (as such, a single extension) + template_name, anatomy_data = self.prepare_anatomy(instance) + published_representations = {} + representations = [] + for repre in instance.data["representations"]: + + if "delete" in repre.get("tags", []): + self.log.debug("Skipping representation marked for deletion: " + "{}".format(repre)) + continue + + prepared = self.prepare_representation(repre, + anatomy_data, + template_name, + existing_repres, + version, + instance_stagingdir, + instance) + + # todo: simplify this? + representation = prepared["representation"] + representations.append(representation) + published_representations[representation["_id"]] = prepared + + # Remove old representations if there are any (before insertion of new) + if existing_repres: + repre_ids_to_remove = [repre["_id"] for repre in existing_repres] + io.delete_many({"_id": {"$in": repre_ids_to_remove}}) + + # Write the new representations to the database + io.insert_many(representations) + + instance.data["published_representations"] = published_representations + + self.log.info("Registered {} representations" + "".format(len(representations))) + + def register_version(self, instance, subset): version_number = instance.data["version"] self.log.debug("Next version: v{}".format(version_number)) - version_data = self.create_version_data(context, instance) - + version_data = self.create_version_data(instance) version_data_instance = instance.data.get('versionData') if version_data_instance: version_data.update(version_data_instance) - # TODO rename method from `create_version` to - # `prepare_version` or similar... - version = self.create_version( - subset=subset, - version_number=version_number, - data=version_data - ) - - self.log.debug("Creating version ...") + version = { + "schema": "openpype:version-3.0", + "type": "version", + "parent": subset["_id"], + "name": version_number, + "data": version_data + } + repres = instance.data.get("representations", []) new_repre_names_low = [_repre["name"].lower() for _repre in repres] existing_version = io.find_one({ @@ -258,29 +345,28 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): }) if existing_version is None: + self.log.debug("Creating new version ...") version_id = io.insert_one(version).inserted_id else: + self.log.debug("Updating existing version ...") # Check if instance have set `append` mode which cause that # only replicated representations are set to archive append_repres = instance.data.get("append", False) + bulk_writes = [] # Update version data - # TODO query by _id and - io.update_many({ - 'type': 'version', - 'parent': subset["_id"], - 'name': version_number + version_id = existing_version['_id'] + bulk_writes.append(UpdateOne({ + '_id': version_id }, { '$set': version - }) - version_id = existing_version['_id'] + })) # Find representations of existing version and archive them - current_repres = list(io.find({ + current_repres = io.find({ "type": "representation", "parent": version_id - })) - bulk_writes = [] + }) for repre in current_repres: if append_repres: # archive only duplicated representations @@ -304,346 +390,248 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): ) version = io.find_one({"_id": version_id}) - instance.data["versionEntity"] = version + return version - existing_repres = list(io.find({ - "parent": version_id, - "type": "archived_representation" - })) + def prepare_representation(self, repre, + anatomy_data, + template_name, + existing_repres, + version, + instance_stagingdir, + instance): - instance.data['version'] = version['name'] + # create template data for Anatomy + template_data = copy.deepcopy(anatomy_data) - intent_value = instance.context.data.get("intent") - if intent_value and isinstance(intent_value, dict): - intent_value = intent_value.get("value") + # pre-flight validations + if repre["ext"].startswith("."): + raise ValueError("Extension must not start with a dot '.': " + "{}".format(repre["ext"])) - if intent_value: - anatomy_data["intent"] = intent_value + if repre.get("transfers"): + raise ValueError("Representation is not allowed to have transfers" + "data before integration. " + "Got: {}".format(repre["transfers"])) - anatomy = instance.context.data['anatomy'] + # required representation keys + files = repre['files'] + template_data["representation"] = repre["name"] + template_data["ext"] = repre["ext"] - # Find the representations to transfer amongst the files - # Each should be a single representation (as such, a single extension) - representations = [] - destination_list = [] + # optionals + # retrieve additional anatomy data from representation if exists + for representation_key, anatomy_key in { + # Representation Key: Anatomy data key + "resolutionWidth": "resolution_width", + "resolutionHeight": "resolution_height", + "fps": "fps", + "outputName": "output", + }.items(): + value = repre.get(representation_key) + if value: + template_data[anatomy_key] = value - orig_transfers = [] - if 'transfers' not in instance.data: - instance.data['transfers'] = [] + if repre.get('stagingDir'): + stagingdir = repre['stagingDir'] else: - orig_transfers = list(instance.data['transfers']) + # Fall back to instance staging dir if not explicitly + # set for representation in the instance + self.log.debug("Representation uses instance staging dir: " + "{}".format(instance_stagingdir)) + stagingdir = instance_stagingdir - family = self.main_family_from_instance(instance) + self.log.debug("Anatomy template name: {}".format(template_name)) + anatomy = instance.context.data['anatomy'] + template = os.path.normpath( + anatomy.templates[template_name]["path"]) - key_values = { - "families": family, - "tasks": task_name, - "hosts": instance.context.data["hostName"], - "task_types": task_type - } - profile = filter_profiles( - self.template_name_profiles, - key_values, - logger=self.log - ) + is_sequence_representation = isinstance(files, (list, tuple)) + if is_sequence_representation: + # Collection of files (sequence) + # Get the sequence as a collection. The files must be of a single + # sequence and have no remainder outside of the collections. + collections, remainder = clique.assemble(files, + minimum_items=1) + if not collections: + raise ValueError("No collections found in files: " + "{}".format(files)) + if remainder: + raise ValueError("Files found not detected as part" + " of a sequence: {}".format(remainder)) + if len(collections) > 1: + raise ValueError("Files in sequence are not part of a" + " single sequence collection: " + "{}".format(collections)) + src_collection = collections[0] - template_name = "publish" - if profile: - template_name = profile["template_name"] - - published_representations = {} - for idx, repre in enumerate(instance.data["representations"]): - # reset transfers for next representation - # instance.data['transfers'] is used as a global variable - # in current codebase - instance.data['transfers'] = list(orig_transfers) - - if "delete" in repre.get("tags", []): - continue - - published_files = [] - - # create template data for Anatomy - template_data = copy.deepcopy(anatomy_data) - if intent_value is not None: - template_data["intent"] = intent_value - - resolution_width = repre.get("resolutionWidth") - resolution_height = repre.get("resolutionHeight") - fps = instance.data.get("fps") - - if resolution_width: - template_data["resolution_width"] = resolution_width - if resolution_width: - template_data["resolution_height"] = resolution_height - if resolution_width: - template_data["fps"] = fps - - files = repre['files'] - if repre.get('stagingDir'): - stagingdir = repre['stagingDir'] - - if repre.get("outputName"): - template_data["output"] = repre['outputName'] - - template_data["representation"] = repre["name"] - - ext = repre["ext"] - if ext.startswith("."): - self.log.warning(( - "Implementaion warning: <\"{}\">" - " Representation's extension stored under \"ext\" key " - " started with dot (\"{}\")." - ).format(repre["name"], ext)) - ext = ext[1:] - repre["ext"] = ext - template_data["ext"] = ext - - self.log.info(template_name) - template = os.path.normpath( - anatomy.templates[template_name]["path"]) - - sequence_repre = isinstance(files, list) - repre_context = None - if sequence_repre: - self.log.debug( - "files: {}".format(files)) - src_collections, remainder = clique.assemble(files) - self.log.debug( - "src_tail_collections: {}".format(str(src_collections))) - src_collection = src_collections[0] - - # Assert that each member has identical suffix - src_head = src_collection.format("{head}") - src_tail = src_collection.format("{tail}") - - # fix dst_padding - valid_files = [x for x in files if src_collection.match(x)] - padd_len = len( - valid_files[0].replace(src_head, "").replace(src_tail, "") - ) - src_padding_exp = "%0{}d".format(padd_len) - - test_dest_files = list() - for i in [1, 2]: - template_data["representation"] = repre['ext'] - if not repre.get("udim"): - template_data["frame"] = src_padding_exp % i - else: - template_data["udim"] = src_padding_exp % i - - anatomy_filled = anatomy.format(template_data) - template_filled = anatomy_filled[template_name]["path"] - if repre_context is None: - repre_context = template_filled.used_values - test_dest_files.append( - os.path.normpath(template_filled) - ) - if not repre.get("udim"): - template_data["frame"] = repre_context["frame"] - else: - template_data["udim"] = repre_context["udim"] - - self.log.debug( - "test_dest_files: {}".format(str(test_dest_files))) - - dst_collections, remainder = clique.assemble(test_dest_files) - dst_collection = dst_collections[0] - dst_head = dst_collection.format("{head}") - dst_tail = dst_collection.format("{tail}") - - index_frame_start = None + # If the representation has `frameStart` set it renumbers the + # frame indices of the published collection. It will start from + # that `frameStart` index instead. Thus if that frame start + # differs from the collection we want to shift the destination + # frame indices from the source collection. + destination_indexes = list(src_collection.indexes) + destination_padding = len(get_first_frame_padded(src_collection)) + if repre.get("frameStart") is not None: + index_frame_start = int(repre.get("frameStart")) # TODO use frame padding from right template group - if repre.get("frameStart") is not None: - frame_start_padding = int( - anatomy.templates["render"].get( - "frame_padding", - anatomy.templates["render"].get("padding") - ) + render_template = anatomy.templates["render"] + frame_start_padding = int( + render_template.get( + "frame_padding", + render_template.get("padding") ) - - index_frame_start = int(repre.get("frameStart")) - - # exception for slate workflow - if index_frame_start and "slate" in instance.data["families"]: - index_frame_start -= 1 - - dst_padding_exp = src_padding_exp - dst_start_frame = None - collection_start = list(src_collection.indexes)[0] - for i in src_collection.indexes: - # TODO 1.) do not count padding in each index iteration - # 2.) do not count dst_padding from src_padding before - # index_frame_start check - frame_number = i - collection_start - src_padding = src_padding_exp % i - - src_file_name = "{0}{1}{2}".format( - src_head, src_padding, src_tail) - - dst_padding = src_padding_exp % frame_number - - if index_frame_start is not None: - dst_padding_exp = "%0{}d".format(frame_start_padding) - dst_padding = dst_padding_exp % (index_frame_start + frame_number) # noqa: E501 - elif repre.get("udim"): - dst_padding = int(i) - - dst = "{0}{1}{2}".format( - dst_head, - dst_padding, - dst_tail - ) - - self.log.debug("destination: `{}`".format(dst)) - src = os.path.join(stagingdir, src_file_name) - - self.log.debug("source: {}".format(src)) - instance.data["transfers"].append([src, dst]) - - published_files.append(dst) - - # for adding first frame into db - if not dst_start_frame: - dst_start_frame = dst_padding - - # Store used frame value to template data - if repre.get("frame"): - template_data["frame"] = dst_start_frame - - dst = "{0}{1}{2}".format( - dst_head, - dst_start_frame, - dst_tail - ) - repre['published_path'] = dst - - else: - # Single file - # _______ - # | |\ - # | | - # | | - # | | - # |_______| - # - template_data.pop("frame", None) - fname = files - assert not os.path.isabs(fname), ( - "Given file name is a full path" ) - template_data["representation"] = repre['ext'] - # Store used frame value to template data - if repre.get("udim"): - template_data["udim"] = repre["udim"][0] - src = os.path.join(stagingdir, fname) - anatomy_filled = anatomy.format(template_data) - template_filled = anatomy_filled[template_name]["path"] - repre_context = template_filled.used_values - dst = os.path.normpath(template_filled) - - instance.data["transfers"].append([src, dst]) - - published_files.append(dst) - repre['published_path'] = dst - self.log.debug("__ dst: {}".format(dst)) + # Shift destination sequence to the start frame + src_start_frame = next(iter(src_collection.indexes)) + shift = index_frame_start - src_start_frame + if shift: + destination_indexes = [ + frame + shift for frame in destination_indexes + ] + destination_padding = frame_start_padding + # To construct the destination template with anatomy we require + # a Frame or UDIM tile set for the template data. We use the first + # index of the destination for that because that could've shifted + # from the source indexes, etc. + first_index_padded = get_frame_padded(frame=destination_indexes[0], + padding=destination_padding) if repre.get("udim"): - repre_context["udim"] = repre.get("udim") # store list + # UDIM representations handle ranges in a different manner + template_data["udim"] = first_index_padded + else: + template_data["frame"] = first_index_padded - repre["publishedFiles"] = published_files + # Construct destination collection from template + anatomy_filled = anatomy.format(template_data) + template_filled = anatomy_filled[template_name]["path"] + repre_context = template_filled.used_values + self.log.debug("Template filled: {}".format(str(template_filled))) + dst_collections, _remainder = clique.assemble( + [os.path.normpath(template_filled)], minimum_items=1 + ) + assert not _remainder, "This is a bug" + assert len(dst_collections) == 1, "This is a bug" + dst_collection = dst_collections[0] - for key in self.db_representation_context_keys: - value = template_data.get(key) - if not value: - continue - repre_context[key] = template_data[key] + # Update the destination indexes and padding + dst_collection.indexes = destination_indexes + dst_collection.padding = destination_padding + assert len(src_collection) == len(dst_collection), "This is a bug" - # Use previous representation's id if there are any - repre_id = None - repre_name_low = repre["name"].lower() - for _repre in existing_repres: - # NOTE should we check lowered names? - if repre_name_low == _repre["name"]: - repre_id = _repre["orig_id"] - break + transfers = [] + for src_file_name, dst in zip(src_collection, dst_collection): + src = os.path.join(stagingdir, src_file_name) + self.log.debug("source: {}".format(src)) + self.log.debug("destination: `{}`".format(dst)) + transfers.append(src, dst) - # Create new id if existing representations does not match - if repre_id is None: - repre_id = io.ObjectId() + # Store first frame as published path + # todo: remove `published_path` since it can be retrieved from + # `transfers` by taking the first destination transfers[0][1] + repre['published_path'] = next(iter(dst_collection)) + repre["transfers"].extend(transfers) - data = repre.get("data") or {} - data.update({'path': dst, 'template': template}) - representation = { - "_id": repre_id, - "schema": "openpype:representation-2.0", - "type": "representation", - "parent": version_id, - "name": repre['name'], - "data": data, - "dependencies": instance.data.get("dependencies", "").split(), + else: + # Single file + template_data.pop("frame", None) + fname = files + assert not os.path.isabs(fname), ( + "Given file name is a full path" + ) + # Store used frame value to template data + if repre.get("udim"): + template_data["udim"] = repre["udim"][0] + src = os.path.join(stagingdir, fname) + anatomy_filled = anatomy.format(template_data) + template_filled = anatomy_filled[template_name]["path"] + repre_context = template_filled.used_values + dst = os.path.normpath(template_filled) - # Imprint shortcut to context - # for performance reasons. - "context": repre_context - } + # Single file transfer + self.log.debug("source: {}".format(src)) + self.log.debug("destination: `{}`".format(dst)) + repre["transfers"] = [src, dst] - if repre.get("outputName"): - representation["context"]["output"] = repre['outputName'] + repre['published_path'] = dst - if sequence_repre and repre.get("frameStart") is not None: - representation['context']['frame'] = ( - dst_padding_exp % int(repre.get("frameStart")) - ) + if repre.get("udim"): + repre_context["udim"] = repre.get("udim") # store list - # any file that should be physically copied is expected in - # 'transfers' or 'hardlinks' - if instance.data.get('transfers', False) or \ - instance.data.get('hardlinks', False): - # could throw exception, will be caught in 'process' - # all integration to DB is being done together lower, - # so no rollback needed - self.log.debug("Integrating source files to destination ...") - self.integrated_file_sizes.update(self.integrate(instance)) - self.log.debug("Integrated files {}". - format(self.integrated_file_sizes)) + for key in self.db_representation_context_keys: + value = template_data.get(key) + if not value: + continue + repre_context[key] = template_data[key] - # get 'files' info for representation and all attached resources - self.log.debug("Preparing files information ...") - representation["files"] = self.get_files_info( - instance, - self.integrated_file_sizes) + # Use previous representation's id if there are any + repre_id = None + repre_name_lower = repre["name"].lower() + for _existing_repre in existing_repres: + # NOTE should we check lowered names? + if repre_name_lower == _existing_repre["name"].lower(): + repre_id = _existing_repre["orig_id"] + break - self.log.debug("__ representation: {}".format(representation)) - destination_list.append(dst) - self.log.debug("__ destination_list: {}".format(destination_list)) - instance.data['destination_list'] = destination_list - representations.append(representation) - published_representations[repre_id] = { - "representation": representation, - "anatomy_data": template_data, - "published_files": published_files - } - self.log.debug("__ representations: {}".format(representations)) + # Create new id if existing representations does not match + if repre_id is None: + repre_id = io.ObjectId() - # Remove old representations if there are any (before insertion of new) - if existing_repres: - repre_ids_to_remove = [] - for repre in existing_repres: - repre_ids_to_remove.append(repre["_id"]) - io.delete_many({"_id": {"$in": repre_ids_to_remove}}) + # todo: `repre` is not the actual `representation` entity + # we should simplify/clarify difference between data above + # and the actual representation entity for the database + data = repre.get("data") or {} + data.update({'path': dst, 'template': template}) + representation = { + "_id": repre_id, + "schema": "openpype:representation-2.0", + "type": "representation", + "parent": version["_id"], + "name": repre['name'], + "data": data, + "dependencies": instance.data.get("dependencies", "").split(), - for rep in instance.data["representations"]: - self.log.debug("__ rep: {}".format(rep)) + # Imprint shortcut to context for performance reasons. + "context": repre_context + } - io.insert_many(representations) - instance.data["published_representations"] = ( - published_representations + if repre.get("outputName"): + representation["context"]["output"] = repre['outputName'] + + if is_sequence_representation and repre.get("frameStart") is not None: + representation['context']['frame'] = template_data["frame"] + + # any file that should be physically copied is expected in + # 'transfers' or 'hardlinks' + integrated_files = [] + if instance.data.get('transfers', False) or \ + instance.data.get('hardlinks', False): + # could throw exception, will be caught in 'process' + # all integration to DB is being done together lower, + # so no rollback needed + # todo: separate the actual integrating of the files onto its own + # taking just a list of transfers as inputs (potentially + # with copy mode flag, like hardlink/copy, etc.) + self.log.debug("Integrating source files to destination ...") + integrated_files = self.integrate(instance) + self.log.debug("Integrated files {}".format(integrated_files)) + + # get 'files' info for representation and all attached resources + self.log.debug("Preparing files information ...") + representation["files"] = self.get_files_info( + instance, + integrated_files ) - # self.log.debug("Representation: {}".format(representations)) - self.log.info("Registered {} items".format(len(representations))) + + return { + "representation": representation, + "anatomy_data": template_data, + # todo: avoid the need for 'published_files'? + # backwards compatibility + "published_files": [transfer[1] for transfer in repre["transfers"]] + } def integrate(self, instance): """ Move the files. @@ -653,92 +641,93 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): Args: instance: the instance to integrate Returns: - integrated_file_sizes: dictionary of destination file url and - its size in bytes + list: destination full paths of integrated files """ - # store destination url and size for reporting and rollback - integrated_file_sizes = {} + # store destinations for potential rollback and measuring sizes + destinations = [] transfers = list(instance.data.get("transfers", list())) for src, dest in transfers: - if os.path.normpath(src) != os.path.normpath(dest): + src = os.path.normpath(src) + dest = os.path.normpath(dest) + if src != dest: dest = self.get_dest_temp_url(dest) self.copy_file(src, dest) - # TODO needs to be updated during site implementation - integrated_file_sizes[dest] = os.path.getsize(dest) + destinations.append(dest) # Produce hardlinked copies - # Note: hardlink can only be produced between two files on the same - # server/disk and editing one of the two will edit both files at once. - # As such it is recommended to only make hardlinks between static files - # to ensure publishes remain safe and non-edited. hardlinks = instance.data.get("hardlinks", list()) for src, dest in hardlinks: dest = self.get_dest_temp_url(dest) - self.log.debug("Hardlinking file ... {} -> {}".format(src, dest)) if not os.path.exists(dest): self.hardlink_file(src, dest) - # TODO needs to be updated during site implementation - integrated_file_sizes[dest] = os.path.getsize(dest) + destinations.append(dest) - return integrated_file_sizes + return destinations + + def _create_folder_for_file(self, path): + dirname = os.path.dirname(path) + try: + os.makedirs(dirname) + except OSError as e: + if e.errno == errno.EEXIST: + pass + else: + self.log.critical("An unexpected error occurred.") + six.reraise(*sys.exc_info()) def copy_file(self, src, dst): - """ Copy given source to destination + """Copy source filepath to destination filepath Arguments: src (str): the source file which needs to be copied - dst (str): the destination of the sourc file + dst (str): the destination filepath + + Returns: + None + + """ + self._create_folder_for_file(dst) + self.log.debug("Copying file ... {} -> {}".format(src, dst)) + copyfile(src, dst) + + def hardlink_file(self, src, dst): + """Hardlink source filepath to destination filepath. + + Note: + Hardlink can only be produced between two files on the same + server/disk and editing one of the two will edit both files at + once. As such it is recommended to only make hardlinks between + static files to ensure publishes remain safe and non-edited. + + Arguments: + src (str): the source file which needs to be hardlinked + dst (str): the destination filepath + Returns: None """ - src = os.path.normpath(src) - dst = os.path.normpath(dst) - self.log.debug("Copying file ... {} -> {}".format(src, dst)) - dirname = os.path.dirname(dst) - try: - os.makedirs(dirname) - except OSError as e: - if e.errno == errno.EEXIST: - pass - else: - self.log.critical("An unexpected error occurred.") - six.reraise(*sys.exc_info()) - - # copy file with speedcopy and check if size of files are simetrical - while True: - if not shutil._samefile(src, dst): - copyfile(src, dst) - else: - self.log.critical( - "files are the same {} to {}".format(src, dst) - ) - os.remove(dst) - try: - shutil.copyfile(src, dst) - self.log.debug("Copying files with shutil...") - except OSError as e: - self.log.critical("Cannot copy {} to {}".format(src, dst)) - self.log.critical(e) - six.reraise(*sys.exc_info()) - if str(getsize(src)) in str(getsize(dst)): - break - - def hardlink_file(self, src, dst): - dirname = os.path.dirname(dst) - - try: - os.makedirs(dirname) - except OSError as e: - if e.errno == errno.EEXIST: - pass - else: - self.log.critical("An unexpected error occurred.") - six.reraise(*sys.exc_info()) - + self._create_folder_for_file(dst) + self.log.debug("Hardlinking file ... {} -> {}".format(src, dst)) create_hard_link(src, dst) - def get_subset(self, asset, instance): + def _get_instance_families(self, instance): + """Get all families of the instance""" + # todo: move this to lib? + family = instance.data.get("family") + families = [] + if family: + families.append(family) + + for _family in (instance.data.get("families") or []): + if _family not in families: + families.append(_family) + + return families + + def register_subset(self, instance): + # todo: rely less on self.prepare_anatomy to create this value + asset = instance.data.get("assetEntity") # <- from prepare_anatomy :( subset_name = instance.data["subset"] subset = io.find_one({ "type": "subset", @@ -748,18 +737,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if subset is None: self.log.info("Subset '%s' not found, creating ..." % subset_name) - self.log.debug("families. %s" % instance.data.get('families')) - self.log.debug( - "families. %s" % type(instance.data.get('families'))) - - family = instance.data.get("family") - families = [] - if family: - families.append(family) - - for _family in (instance.data.get("families") or []): - if _family not in families: - families.append(_family) + families = self._get_instance_families(instance) _id = io.insert_one({ "schema": "openpype:subset-3.0", @@ -773,8 +751,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): subset = io.find_one({"_id": _id}) - # QUESTION Why is changing of group and updating it's - # families in 'get_subset'? + # Update subset group self._set_subset_group(instance, subset["_id"]) # Update families on subset. @@ -838,7 +815,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self.subset_grouping_profiles, filtering_criteria ) - # Skip if there is not matchin profile + # Skip if there is not matching profile if not matching_profile: return None @@ -867,41 +844,17 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): return filled_template - def create_version(self, subset, version_number, data=None): - """ Copy given source to destination - - Args: - subset (dict): the registered subset of the asset - version_number (int): the version number - - Returns: - dict: collection of data to create a version - """ - - return {"schema": "openpype:version-3.0", - "type": "version", - "parent": subset["_id"], - "name": version_number, - "data": data} - - def create_version_data(self, context, instance): + def create_version_data(self, instance): """Create the data collection for the version Args: - context: the current context instance: the current instance being published Returns: dict: the required information with instance.data as key """ - families = [] - current_families = instance.data.get("families", list()) - instance_family = instance.data.get("family", None) - - if instance_family is not None: - families.append(instance_family) - families += current_families + context = instance.context # create relative source path for DB if "source" in instance.data: @@ -910,10 +863,10 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): source = context.data["currentFile"] anatomy = instance.context.data["anatomy"] source = self.get_rootless_path(anatomy, source) - self.log.debug("Source: {}".format(source)) + version_data = { - "families": families, + "families": self._get_instance_families(instance), "time": context.data["time"], "author": context.data["user"], "source": source, @@ -924,7 +877,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): ) } - intent_value = instance.context.data.get("intent") + intent_value = context.data.get("intent") if intent_value and isinstance(intent_value, dict): intent_value = intent_value.get("value") @@ -944,10 +897,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): def main_family_from_instance(self, instance): """Returns main family of entered instance.""" - family = instance.data.get("family") - if not family: - family = instance.data["families"][0] - return family + return self._get_instance_families(instance)[0] def get_rootless_path(self, anatomy, path): """ Returns, if possible, path without absolute portion from host @@ -976,7 +926,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): ).format(path)) return path - def get_files_info(self, instance, integrated_file_sizes): + def get_files_info(self, instance): """ Prepare 'files' portion for attached resources and main asset. Combining records from 'transfers' and 'hardlinks' parts from instance. @@ -991,27 +941,18 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): output_resources: array of dictionaries to be added to 'files' key in representation """ + # todo: refactor to use transfers/hardlinks of representations + # currently broken logic resources = list(instance.data.get("transfers", [])) resources.extend(list(instance.data.get("hardlinks", []))) + self.log.debug("get_files_info.resources:{}".format(resources)) - self.log.debug("get_resource_files_info.resources:{}". - format(resources)) + sites = self.compute_resource_sync_sites(instance) output_resources = [] anatomy = instance.context.data["anatomy"] for _src, dest in resources: - path = self.get_rootless_path(anatomy, dest) - dest = self.get_dest_temp_url(dest) - file_hash = openpype.api.source_hash(dest) - if self.TMP_FILE_EXT and \ - ',{}'.format(self.TMP_FILE_EXT) in file_hash: - file_hash = file_hash.replace(',{}'.format(self.TMP_FILE_EXT), - '') - - file_info = self.prepare_file_info(path, - integrated_file_sizes[dest], - file_hash, - instance=instance) + file_info = self.prepare_file_info(dest, anatomy, sites=sites) output_resources.append(file_info) return output_resources @@ -1031,8 +972,11 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): dest += '.{}'.format(self.TMP_FILE_EXT) return dest - def prepare_file_info(self, path, size=None, file_hash=None, - sites=None, instance=None): + def get_dest_final_url(self, temp_file_url): + """Temporary destination file url to final destination file url""" + return re.sub(r'\.{}$'.format(self.TMP_FILE_EXT), '', temp_file_url) + + def prepare_file_info(self, path, anatomy, sites): """ Prepare information for one file (asset or resource) Arguments: @@ -1042,74 +986,78 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): sites(optional): array of published locations, [ {'name':'studio', 'created_dt':date} by default keys expected ['studio', 'site1', 'gdrive1'] - instance(dict, optional): to get collected settings Returns: rec: dictionary with filled info """ + file_hash = openpype.api.source_hash(path) + + # todo: Avoid this logic + # Strip the temporary file extension from the file hash + if self.TMP_FILE_EXT and ',{}'.format(self.TMP_FILE_EXT) in file_hash: + file_hash = file_hash.replace(',{}'.format(self.TMP_FILE_EXT), '') + + return { + "_id": io.ObjectId(), + "path": self.get_rootless_path(anatomy, path), + "size": os.path.getsize(path), + "hash": file_hash, + "sites": sites + } + + def compute_resource_sync_sites(self, instance): + """Get available resource sync sites""" + # Sync server logic + # TODO: Clean up sync settings local_site = 'studio' # default remote_site = None - always_accesible = [] + always_accessible = [] sync_project_presets = None - rec = { - "_id": io.ObjectId(), - "path": path - } - if size: - rec["size"] = size + system_sync_server_presets = ( + instance.context.data["system_settings"] + ["modules"] + ["sync_server"]) + log.debug("system_sett:: {}".format(system_sync_server_presets)) - if file_hash: - rec["hash"] = file_hash - - if sites: - rec["sites"] = sites - else: - system_sync_server_presets = ( - instance.context.data["system_settings"] - ["modules"] + if system_sync_server_presets["enabled"]: + sync_project_presets = ( + instance.context.data["project_settings"] + ["global"] ["sync_server"]) - log.debug("system_sett:: {}".format(system_sync_server_presets)) - if system_sync_server_presets["enabled"]: - sync_project_presets = ( - instance.context.data["project_settings"] - ["global"] - ["sync_server"]) + if sync_project_presets and sync_project_presets["enabled"]: + local_site, remote_site = self._get_sites(sync_project_presets) + always_accessible = sync_project_presets["config"]. \ + get("always_accessible_on", []) - if sync_project_presets and sync_project_presets["enabled"]: - local_site, remote_site = self._get_sites(sync_project_presets) + already_attached_sites = {} + meta = {"name": local_site, "created_dt": datetime.now()} + sites = [meta] + already_attached_sites[meta["name"]] = meta["created_dt"] - always_accesible = sync_project_presets["config"]. \ - get("always_accessible_on", []) + if sync_project_presets and sync_project_presets["enabled"]: + if remote_site and \ + remote_site not in already_attached_sites.keys(): + # add remote + meta = {"name": remote_site.strip()} + sites.append(meta) + already_attached_sites[meta["name"]] = None - already_attached_sites = {} - meta = {"name": local_site, "created_dt": datetime.now()} - rec["sites"] = [meta] - already_attached_sites[meta["name"]] = meta["created_dt"] - - if sync_project_presets and sync_project_presets["enabled"]: - if remote_site and \ - remote_site not in already_attached_sites.keys(): - # add remote - meta = {"name": remote_site.strip()} - rec["sites"].append(meta) + # add skeleton for site where it should be always synced to + for always_on_site in always_accessible: + if always_on_site not in already_attached_sites.keys(): + meta = {"name": always_on_site.strip()} + sites.append(meta) already_attached_sites[meta["name"]] = None - # add skeleton for site where it should be always synced to - for always_on_site in always_accesible: - if always_on_site not in already_attached_sites.keys(): - meta = {"name": always_on_site.strip()} - rec["sites"].append(meta) - already_attached_sites[meta["name"]] = None + # add alternative sites + alt = self._add_alternative_sites(system_sync_server_presets, + already_attached_sites) + sites.extend(alt) - # add alternative sites - rec = self._add_alternative_sites(system_sync_server_presets, - already_attached_sites, - rec) + log.debug("final sites:: {}".format(sites)) - log.debug("final sites:: {}".format(rec["sites"])) - - return rec + return sites def _get_sites(self, sync_project_presets): """Returns tuple (local_site, remote_site)""" @@ -1129,14 +1077,14 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): def _add_alternative_sites(self, system_sync_server_presets, - already_attached_sites, - rec): + already_attached_sites): """Loop through all configured sites and add alternatives. See SyncServerModule.handle_alternate_site """ conf_sites = system_sync_server_presets.get("sites", {}) + alternative_sites = [] for site_name, site_info in conf_sites.items(): alt_sites = set(site_info.get("alternative_sites", [])) already_attached_keys = list(already_attached_sites.keys()) @@ -1149,12 +1097,12 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # alt site inherits state of 'created_dt' if real_created: meta["created_dt"] = real_created - rec["sites"].append(meta) + alternative_sites.append(meta) already_attached_sites[meta["name"]] = real_created - return rec + return alternative_sites - def handle_destination_files(self, integrated_file_sizes, mode): + def handle_destination_files(self, destinations, mode): """ Clean destination files Called when error happened during integrating to DB or to disk OR called to rename uploaded files from temporary name to final to @@ -1162,46 +1110,38 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): Used to clean unwanted files Arguments: - integrated_file_sizes: dictionary, file urls as keys, size as value + destinations (list): file paths mode: 'remove' - clean files, 'finalize' - rename files, remove TMP_FILE_EXT suffix denoting temp file """ - if integrated_file_sizes: - for file_url, _file_size in integrated_file_sizes.items(): - if not os.path.exists(file_url): + if not destinations: + return + + for file_url in destinations: + if not os.path.exists(file_url): + self.log.debug( + "File {} was not found.".format(file_url) + ) + continue + + try: + if mode == 'remove': + self.log.debug("Removing file {}".format(file_url)) + os.remove(file_url) + if mode == 'finalize': + + new_name = self.get_dest_final_url(file_url) + if os.path.exists(new_name): + self.log.debug("Removing existing " + "file: {}".format(new_name)) + os.remove(new_name) + self.log.debug( - "File {} was not found.".format(file_url) + "Renaming file {} to {}".format(file_url, new_name) ) - continue - - try: - if mode == 'remove': - self.log.debug("Removing file {}".format(file_url)) - os.remove(file_url) - if mode == 'finalize': - new_name = re.sub( - r'\.{}$'.format(self.TMP_FILE_EXT), - '', - file_url - ) - - if os.path.exists(new_name): - self.log.debug( - "Overwriting file {} to {}".format( - file_url, new_name - ) - ) - shutil.copy(file_url, new_name) - os.remove(file_url) - else: - self.log.debug( - "Renaming file {} to {}".format( - file_url, new_name - ) - ) - os.rename(file_url, new_name) - except OSError: - self.log.error("Cannot {} file {}".format(mode, file_url), - exc_info=True) - six.reraise(*sys.exc_info()) + os.rename(file_url, new_name) + except OSError: + self.log.error("Cannot {} file {}".format(mode, file_url), + exc_info=True) + six.reraise(*sys.exc_info()) From ae1a9ff4cf996445bd74dcd7641639ed8342592e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 17 Mar 2022 11:49:12 +0100 Subject: [PATCH 0011/1227] More refactoring + draft (untested) implementation for separating File Transaction logic --- openpype/plugins/publish/integrate_new.py | 421 +++++++++++----------- 1 file changed, 215 insertions(+), 206 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index e4986e3b3f..500456eaed 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -1,12 +1,10 @@ import os -from os.path import getsize import logging import sys import copy import clique import errno import six -import re from pymongo import DeleteOne, InsertOne, UpdateOne import pyblish.api @@ -14,7 +12,6 @@ from avalon import io from avalon.api import format_template_with_optional_keys import openpype.api from datetime import datetime -# from pype.modules import ModulesManager from openpype.lib.profiles_filtering import filter_profiles from openpype.lib import ( prepare_template_data, @@ -41,6 +38,160 @@ def get_first_frame_padded(collection): return get_frame_padded(start_frame, padding=collection.padding) +class FileTransaction(object): + """ + + The file transaction is a three step process. + + 1) Rename any existing files to a "temporary backup" during `process()` + 2) Copy the files to final destination during `process()` + 3) Remove any backed up files (*no rollback possible!) during `finalize()` + + Step 3 is done during `finalize()`. If not called the .bak files will + remain on disk. + + These steps try to ensure that we don't overwrite half of any existing + files e.g. if they are currently in use. + + Note: + A regular filesystem is *not* a transactional file system and even + though this implementation tries to produce a 'safe copy' with a + potential rollback do keep in mind that it's inherently unsafe due + to how filesystem works and a myriad of things could happen during + the transaction that break the logic. A file storage could go down, + permissions could be changed, other machines could be moving or writing + files. A lot can happen. + + Warning: + Any folders created during the transfer will not be removed. + + """ + + MODE_COPY = 0 + MODE_HARDLINK = 1 + + def __init__(self, log=None): + + if log is None: + log = logging.getLogger("FileTransaction") + + self.log = log + + # The transfer queue + # todo: make this an actual FIFO queue? + self._transfers = {} + + # Destination file paths that a file was transferred to + self._transferred = [] + + # Backup file location mapping to original locations + self._backup_to_original = {} + + def add(self, src, dst, mode=MODE_COPY): + """Add a new file to transfer queue""" + opts = {"mode": mode} + + src = os.path.normpath(src) + dst = os.path.normpath(dst) + + if dst in self._transfers: + queued_src = self._transfers[dst][0] + if src == queued_src: + self.log.debug("File transfer was already " + "in queue: {} -> {}".format(src, dst)) + return + else: + self.log.warning("File transfer in queue overwritten") + + self._transfers[dst] = (src, opts) + + def process(self): + + # Backup any existing files + for dst in self._transfers.keys(): + if os.path.exists(dst): + # Backup original file + # todo: add timestamp or uuid to ensure unique + backup = dst + ".bak" + self._backup_to_original[backup] = dst + self.log.debug("Backup existing file: " + "{} -> {}".format(dst, backup)) + os.rename(dst, backup) + + # Copy the files to transfer + for dst, (src, opts) in self._transfers.items(): + self._create_folder_for_file(dst) + + if opts["mode"] == self.MODE_COPY: + self.log.debug("Copying file ... {} -> {}".format(src, dst)) + copyfile(src, dst) + elif opts["mode"] == self.MODE_HARDLINK: + self.log.debug("Hardlinking file ... {} -> {}".format(src, dst)) + create_hard_link(src, dst) + + self._transferred.append(dst) + + def finalize(self): + # Delete any backed up files + for backup in self._backup_to_original.keys(): + try: + os.remove(backup) + except OSError: + self.log.error("Failed to remove backup file: " + "{}".format(backup), + exc_info=True) + + def rollback(self): + + errors = 0 + + # Rollback any transferred files + for path in self._transferred: + try: + os.remove(path) + except OSError: + errors += 1 + self.log.error("Failed to rollback created file: " + "{}".format(path), + exc_info=True) + + # Rollback the backups + for backup, original in self._backup_to_original.items(): + try: + os.rename(backup, original) + except OSError: + errors +=1 + self.log.error("Failed to restore original file: " + "{} -> {}".format(backup, original), + exc_info=True) + + if errors: + self.log.error("{} errors occurred during " + "rollback.".format(errors), exc_info=True) + six.reraise(*sys.exc_info()) + + @property + def transferred(self): + """Return the processed transfers destination paths""" + return list(self._transferred) + + @property + def backups(self): + """Return the backup file paths""" + return list(self._backup_to_original.keys()) + + def _create_folder_for_file(self, path): + dirname = os.path.dirname(path) + try: + os.makedirs(dirname) + except OSError as e: + if e.errno == errno.EEXIST: + pass + else: + self.log.critical("An unexpected error occurred.") + six.reraise(*sys.exc_info()) + + class IntegrateAssetNew(pyblish.api.InstancePlugin): """Resolve any dependency issues @@ -122,18 +273,11 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): ] default_template_name = "publish" - # suffix to denote temporary files, use without '.' - TMP_FILE_EXT = 'tmp' - - # file_url : file_size of all published and uploaded files - destinations = list() - # Attributes set by settings template_name_profiles = None subset_grouping_profiles = None def process(self, instance): - self.destinations = [] # Exclude instances that also contain families from exclude families families = set( @@ -143,17 +287,20 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if families & set(self.exclude_families): return + file_transactions = FileTransaction(log=self.log) try: - self.register(instance) - self.log.info("Integrated Asset in to the database ...") - self.handle_destination_files(self.destinations, - 'finalize') + self.register(instance, file_transactions) except Exception: # clean destination + # todo: rollback any registered entities? (or how safe are we?) + file_transactions.rollback() self.log.critical("Error when registering", exc_info=True) - self.handle_destination_files(self.destinations, 'remove') six.reraise(*sys.exc_info()) + # Finalizing can't be rollbacked safely so no use for moving it to + # the try, except. + file_transactions.finalize() + def prepare_anatomy(self, instance): """Prepare anatomy data used to define representation destinations""" @@ -244,7 +391,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): return template_name, anatomy_data - def register(self, instance): + def register(self, instance, file_transactions): instance_stagingdir = instance.data.get("stagingDir") if not instance_stagingdir: @@ -272,9 +419,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): version = self.register_version(instance, subset) instance.data["versionEntity"] = version - instance.data['version'] = version['name'] - existing_repres = list(io.find({ + archived_repres = list(io.find({ "parent": version["_id"], "type": "archived_representation" })) @@ -294,19 +440,47 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): prepared = self.prepare_representation(repre, anatomy_data, template_name, - existing_repres, + archived_repres, version, instance_stagingdir, instance) + representation = prepared["representation"] + + # todo: register the file transfers correctly + for src, dst in representation["transfers"]: + file_transactions.add(src, dst, + mode=file_transactions.MODE_COPY) + for src, dst in representation["hardlinks"]: + file_transactions.add(src, dst, + mode=file_transactions.MODE_HARDLINK) # todo: simplify this? - representation = prepared["representation"] representations.append(representation) published_representations[representation["_id"]] = prepared + # could throw exception, will be caught in 'process' + # all integration to DB is being done together lower, + # so no rollback needed + self.log.debug("Integrating source files to destination ...") + file_transactions.process() + self.log.debug("Backup files " + "{}".format(file_transactions.backups)) + self.log.debug("Integrated files " + "{}".format(file_transactions.transferred)) + + # todo: fix get file info for transferred files per representation + # currently it'd set all files for all representations + # get 'files' info for representation and all attached resources + integrated_files = file_transactions.transferred + self.log.debug("Preparing files information ...") + representation["files"] = self.get_files_info( + instance, + integrated_files + ) + # Remove old representations if there are any (before insertion of new) - if existing_repres: - repre_ids_to_remove = [repre["_id"] for repre in existing_repres] + if archived_repres: + repre_ids_to_remove = [repre["_id"] for repre in archived_repres] io.delete_many({"_id": {"$in": repre_ids_to_remove}}) # Write the new representations to the database @@ -395,7 +569,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): def prepare_representation(self, repre, anatomy_data, template_name, - existing_repres, + archived_repres, version, instance_stagingdir, instance): @@ -439,11 +613,13 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self.log.debug("Representation uses instance staging dir: " "{}".format(instance_stagingdir)) stagingdir = instance_stagingdir + if not stagingdir: + raise ValueError("No staging directory set for representation: " + "{}".format(repre)) self.log.debug("Anatomy template name: {}".format(template_name)) anatomy = instance.context.data['anatomy'] - template = os.path.normpath( - anatomy.templates[template_name]["path"]) + template = os.path.normpath(anatomy.templates[template_name]["path"]) is_sequence_representation = isinstance(files, (list, tuple)) if is_sequence_representation: @@ -566,24 +742,21 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): continue repre_context[key] = template_data[key] - # Use previous representation's id if there are any - repre_id = None - repre_name_lower = repre["name"].lower() - for _existing_repre in existing_repres: - # NOTE should we check lowered names? - if repre_name_lower == _existing_repre["name"].lower(): - repre_id = _existing_repre["orig_id"] - break + # Define representation id + repre_id = io.ObjectId() - # Create new id if existing representations does not match - if repre_id is None: - repre_id = io.ObjectId() + # Use previous representation's id if there is a name match + repre_name_lower = repre["name"].lower() + for _archived_repres in archived_repres: + if repre_name_lower == _archived_repres["name"].lower(): + repre_id = _archived_repres["orig_id"] + break # todo: `repre` is not the actual `representation` entity # we should simplify/clarify difference between data above # and the actual representation entity for the database data = repre.get("data") or {} - data.update({'path': dst, 'template': template}) + data.update({'path': repre["published_path"], 'template': template}) representation = { "_id": repre_id, "schema": "openpype:representation-2.0", @@ -597,34 +770,14 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "context": repre_context } + # todo: simplify/streamline which additional data makes its way into + # the representation context if repre.get("outputName"): representation["context"]["output"] = repre['outputName'] if is_sequence_representation and repre.get("frameStart") is not None: representation['context']['frame'] = template_data["frame"] - # any file that should be physically copied is expected in - # 'transfers' or 'hardlinks' - integrated_files = [] - if instance.data.get('transfers', False) or \ - instance.data.get('hardlinks', False): - # could throw exception, will be caught in 'process' - # all integration to DB is being done together lower, - # so no rollback needed - # todo: separate the actual integrating of the files onto its own - # taking just a list of transfers as inputs (potentially - # with copy mode flag, like hardlink/copy, etc.) - self.log.debug("Integrating source files to destination ...") - integrated_files = self.integrate(instance) - self.log.debug("Integrated files {}".format(integrated_files)) - - # get 'files' info for representation and all attached resources - self.log.debug("Preparing files information ...") - representation["files"] = self.get_files_info( - instance, - integrated_files - ) - return { "representation": representation, "anatomy_data": template_data, @@ -633,84 +786,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "published_files": [transfer[1] for transfer in repre["transfers"]] } - def integrate(self, instance): - """ Move the files. - - Through `instance.data["transfers"]` - - Args: - instance: the instance to integrate - Returns: - list: destination full paths of integrated files - """ - # store destinations for potential rollback and measuring sizes - destinations = [] - transfers = list(instance.data.get("transfers", list())) - for src, dest in transfers: - src = os.path.normpath(src) - dest = os.path.normpath(dest) - if src != dest: - dest = self.get_dest_temp_url(dest) - self.copy_file(src, dest) - destinations.append(dest) - - # Produce hardlinked copies - hardlinks = instance.data.get("hardlinks", list()) - for src, dest in hardlinks: - dest = self.get_dest_temp_url(dest) - if not os.path.exists(dest): - self.hardlink_file(src, dest) - - destinations.append(dest) - - return destinations - - def _create_folder_for_file(self, path): - dirname = os.path.dirname(path) - try: - os.makedirs(dirname) - except OSError as e: - if e.errno == errno.EEXIST: - pass - else: - self.log.critical("An unexpected error occurred.") - six.reraise(*sys.exc_info()) - - def copy_file(self, src, dst): - """Copy source filepath to destination filepath - - Arguments: - src (str): the source file which needs to be copied - dst (str): the destination filepath - - Returns: - None - - """ - self._create_folder_for_file(dst) - self.log.debug("Copying file ... {} -> {}".format(src, dst)) - copyfile(src, dst) - - def hardlink_file(self, src, dst): - """Hardlink source filepath to destination filepath. - - Note: - Hardlink can only be produced between two files on the same - server/disk and editing one of the two will edit both files at - once. As such it is recommended to only make hardlinks between - static files to ensure publishes remain safe and non-edited. - - Arguments: - src (str): the source file which needs to be hardlinked - dst (str): the destination filepath - - Returns: - None - """ - self._create_folder_for_file(dst) - self.log.debug("Hardlinking file ... {} -> {}".format(src, dst)) - create_hard_link(src, dst) - def _get_instance_families(self, instance): """Get all families of the instance""" # todo: move this to lib? @@ -727,7 +802,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): def register_subset(self, instance): # todo: rely less on self.prepare_anatomy to create this value - asset = instance.data.get("assetEntity") # <- from prepare_anatomy :( + asset = instance.data.get("assetEntity") # stored by prepare_anatomy subset_name = instance.data["subset"] subset = io.find_one({ "type": "subset", @@ -957,25 +1032,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): return output_resources - def get_dest_temp_url(self, dest): - """ Enhance destination path with TMP_FILE_EXT to denote temporary - file. - Temporary files will be renamed after successful registration - into DB and full copy to destination - - Arguments: - dest: destination url of published file (absolute) - Returns: - dest: destination path + '.TMP_FILE_EXT' - """ - if self.TMP_FILE_EXT and '.{}'.format(self.TMP_FILE_EXT) not in dest: - dest += '.{}'.format(self.TMP_FILE_EXT) - return dest - - def get_dest_final_url(self, temp_file_url): - """Temporary destination file url to final destination file url""" - return re.sub(r'\.{}$'.format(self.TMP_FILE_EXT), '', temp_file_url) - def prepare_file_info(self, path, anatomy, sites): """ Prepare information for one file (asset or resource) @@ -991,11 +1047,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): """ file_hash = openpype.api.source_hash(path) - # todo: Avoid this logic - # Strip the temporary file extension from the file hash - if self.TMP_FILE_EXT and ',{}'.format(self.TMP_FILE_EXT) in file_hash: - file_hash = file_hash.replace(',{}'.format(self.TMP_FILE_EXT), '') - return { "_id": io.ObjectId(), "path": self.get_rootless_path(anatomy, path), @@ -1004,6 +1055,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "sites": sites } + # region sync sites def compute_resource_sync_sites(self, instance): """Get available resource sync sites""" # Sync server logic @@ -1101,47 +1153,4 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): already_attached_sites[meta["name"]] = real_created return alternative_sites - - def handle_destination_files(self, destinations, mode): - """ Clean destination files - Called when error happened during integrating to DB or to disk - OR called to rename uploaded files from temporary name to final to - highlight publishing in progress/broken - Used to clean unwanted files - - Arguments: - destinations (list): file paths - mode: 'remove' - clean files, - 'finalize' - rename files, - remove TMP_FILE_EXT suffix denoting temp file - """ - if not destinations: - return - - for file_url in destinations: - if not os.path.exists(file_url): - self.log.debug( - "File {} was not found.".format(file_url) - ) - continue - - try: - if mode == 'remove': - self.log.debug("Removing file {}".format(file_url)) - os.remove(file_url) - if mode == 'finalize': - - new_name = self.get_dest_final_url(file_url) - if os.path.exists(new_name): - self.log.debug("Removing existing " - "file: {}".format(new_name)) - os.remove(new_name) - - self.log.debug( - "Renaming file {} to {}".format(file_url, new_name) - ) - os.rename(file_url, new_name) - except OSError: - self.log.error("Cannot {} file {}".format(mode, file_url), - exc_info=True) - six.reraise(*sys.exc_info()) + # endregion From 9f6cc5df3a11031fb18155c97e0a73bb6f3f6108 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 17 Mar 2022 11:51:06 +0100 Subject: [PATCH 0012/1227] Fix hound --- openpype/plugins/publish/integrate_new.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 500456eaed..e74b528ae7 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -126,7 +126,8 @@ class FileTransaction(object): self.log.debug("Copying file ... {} -> {}".format(src, dst)) copyfile(src, dst) elif opts["mode"] == self.MODE_HARDLINK: - self.log.debug("Hardlinking file ... {} -> {}".format(src, dst)) + self.log.debug("Hardlinking file ... {} -> {}".format(src, + dst)) create_hard_link(src, dst) self._transferred.append(dst) @@ -160,7 +161,7 @@ class FileTransaction(object): try: os.rename(backup, original) except OSError: - errors +=1 + errors += 1 self.log.error("Failed to restore original file: " "{} -> {}".format(backup, original), exc_info=True) From 56bcd8cec35f201ead80a18c09f6c070b76209c1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 17 Mar 2022 16:30:49 +0100 Subject: [PATCH 0013/1227] Continue refactor, restore functionality - now can correctly publish as before (rudimentary tested only) --- openpype/plugins/publish/integrate_new.py | 136 +++++++++++----------- 1 file changed, 70 insertions(+), 66 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index e74b528ae7..c550c1011c 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -101,7 +101,10 @@ class FileTransaction(object): "in queue: {} -> {}".format(src, dst)) return else: - self.log.warning("File transfer in queue overwritten") + self.log.warning("File transfer in queue replaced..") + self.log.debug("Removed from queue: " + "{} -> {}".format(queued_src, dst)) + self.log.debug("Added to queue: {} -> {}".format(src, dst)) self._transfers[dst] = (src, opts) @@ -298,7 +301,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self.log.critical("Error when registering", exc_info=True) six.reraise(*sys.exc_info()) - # Finalizing can't be rollbacked safely so no use for moving it to + # Finalizing can't rollback safely so no use for moving it to # the try, except. file_transactions.finalize() @@ -426,11 +429,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "type": "archived_representation" })) - # Find the representations to transfer amongst the files - # Each should be a single representation (as such, a single extension) + # Prepare all representations template_name, anatomy_data = self.prepare_anatomy(instance) - published_representations = {} - representations = [] + prepared_representations = [] for repre in instance.data["representations"]: if "delete" in repre.get("tags", []): @@ -438,6 +439,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "{}".format(repre)) continue + # todo: reduce/simplify what is returned from this function prepared = self.prepare_representation(repre, anatomy_data, template_name, @@ -445,23 +447,23 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): version, instance_stagingdir, instance) - representation = prepared["representation"] - # todo: register the file transfers correctly - for src, dst in representation["transfers"]: - file_transactions.add(src, dst, - mode=file_transactions.MODE_COPY) - for src, dst in representation["hardlinks"]: - file_transactions.add(src, dst, - mode=file_transactions.MODE_HARDLINK) + for src, dst in prepared["transfers"]: + # todo: add support for hardlink transfers + file_transactions.add(src, dst) - # todo: simplify this? - representations.append(representation) - published_representations[representation["_id"]] = prepared + prepared_representations.append(prepared) - # could throw exception, will be caught in 'process' - # all integration to DB is being done together lower, - # so no rollback needed + # Each instance can also have pre-defined transfers not explicitly + # part of a representation - like texture resources used by a + # .ma representation. Those destination paths are pre-defined, etc. + # todo: should we move or simplify this logic? + for src, dst in instance.data.get("transfers", []): + file_transactions.add(src, dst, mode=FileTransaction.MODE_COPY) + for src, dst in instance.data.get("hardlinks", []): + file_transactions.add(src, dst, mode=FileTransaction.MODE_HARDLINK) + + # Process all file transfers of all integrations now self.log.debug("Integrating source files to destination ...") file_transactions.process() self.log.debug("Backup files " @@ -469,17 +471,21 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self.log.debug("Integrated files " "{}".format(file_transactions.transferred)) - # todo: fix get file info for transferred files per representation - # currently it'd set all files for all representations - # get 'files' info for representation and all attached resources - integrated_files = file_transactions.transferred - self.log.debug("Preparing files information ...") - representation["files"] = self.get_files_info( - instance, - integrated_files - ) + # Finalize the representations now the published files are integrated + # Get 'files' info for representations and its attached resources + self.log.debug("Retrieving Representation files information ...") + sites = self.compute_resource_sync_sites(instance) + anatomy = instance.context.data["anatomy"] + representations = [] + for prepared in prepared_representations: + transfers = prepared["transfers"] + representation = prepared["representation"] + representation["files"] = self.get_files_info( + transfers, sites, anatomy + ) + representations.append(representation) - # Remove old representations if there are any (before insertion of new) + # Remove all archived representations if archived_repres: repre_ids_to_remove = [repre["_id"] for repre in archived_repres] io.delete_many({"_id": {"$in": repre_ids_to_remove}}) @@ -487,7 +493,11 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # Write the new representations to the database io.insert_many(representations) - instance.data["published_representations"] = published_representations + # Backwards compatibility + # todo: can we avoid the need to store this? + instance.data["published_representations"] = { + p["representation"]["_id"]: p for p in prepared_representations + } self.log.info("Registered {} representations" "".format(len(representations))) @@ -495,7 +505,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): def register_version(self, instance, subset): version_number = instance.data["version"] - self.log.debug("Next version: v{}".format(version_number)) + self.log.debug("Version: v{0:03d}".format(version_number)) version_data = self.create_version_data(instance) version_data_instance = instance.data.get('versionData') @@ -565,6 +575,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): ) version = io.find_one({"_id": version_id}) + + self.log.info("Registered version: v{0:03d}".format(version["name"])) + return version def prepare_representation(self, repre, @@ -585,7 +598,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if repre.get("transfers"): raise ValueError("Representation is not allowed to have transfers" - "data before integration. " + "data before integration. They are computed in " + "the integrator" "Got: {}".format(repre["transfers"])) # required representation keys @@ -698,18 +712,11 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): dst_collection.padding = destination_padding assert len(src_collection) == len(dst_collection), "This is a bug" + # Multiple file transfers transfers = [] for src_file_name, dst in zip(src_collection, dst_collection): src = os.path.join(stagingdir, src_file_name) - self.log.debug("source: {}".format(src)) - self.log.debug("destination: `{}`".format(dst)) - transfers.append(src, dst) - - # Store first frame as published path - # todo: remove `published_path` since it can be retrieved from - # `transfers` by taking the first destination transfers[0][1] - repre['published_path'] = next(iter(dst_collection)) - repre["transfers"].extend(transfers) + transfers.append((src, dst)) else: # Single file @@ -728,11 +735,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): dst = os.path.normpath(template_filled) # Single file transfer - self.log.debug("source: {}".format(src)) - self.log.debug("destination: `{}`".format(dst)) - repre["transfers"] = [src, dst] - - repre['published_path'] = dst + transfers = [(src, dst)] if repre.get("udim"): repre_context["udim"] = repre.get("udim") # store list @@ -753,11 +756,16 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): repre_id = _archived_repres["orig_id"] break + # Backwards compatibility: + # Store first transferred destination as published path data + # todo: can we remove this? + published_path = transfers[0][1] + # todo: `repre` is not the actual `representation` entity # we should simplify/clarify difference between data above # and the actual representation entity for the database data = repre.get("data") or {} - data.update({'path': repre["published_path"], 'template': template}) + data.update({'path': published_path, 'template': template}) representation = { "_id": repre_id, "schema": "openpype:representation-2.0", @@ -782,9 +790,10 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): return { "representation": representation, "anatomy_data": template_data, - # todo: avoid the need for 'published_files'? + "transfers": transfers, + # todo: avoid the need for 'published_files' used by Integrate Hero # backwards compatibility - "published_files": [transfer[1] for transfer in repre["transfers"]] + "published_files": [transfer[1] for transfer in transfers] } def _get_instance_families(self, instance): @@ -805,6 +814,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # todo: rely less on self.prepare_anatomy to create this value asset = instance.data.get("assetEntity") # stored by prepare_anatomy subset_name = instance.data["subset"] + self.log.debug("Subset: {}".format(subset_name)) + subset = io.find_one({ "type": "subset", "parent": asset["_id"], @@ -838,6 +849,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): {"$set": {"data.families": families}} ) + self.log.info("Registered subset: {}".format(subset_name)) + return subset def _set_subset_group(self, instance, subset_id): @@ -871,9 +884,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if not self.subset_grouping_profiles: return None + # TODO: Resolve below questions # QUESTION - # - is there a chance that task name is not filled in anatomy - # data? + # - is there a chance that task name is not filled in anatomy data? # - should we use context task in that case? anatomy_data = instance.data["anatomyData"] task_name = None @@ -1002,7 +1015,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): ).format(path)) return path - def get_files_info(self, instance): + def get_files_info(self, transfers, sites, anatomy): """ Prepare 'files' portion for attached resources and main asset. Combining records from 'transfers' and 'hardlinks' parts from instance. @@ -1017,21 +1030,12 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): output_resources: array of dictionaries to be added to 'files' key in representation """ - # todo: refactor to use transfers/hardlinks of representations - # currently broken logic - resources = list(instance.data.get("transfers", [])) - resources.extend(list(instance.data.get("hardlinks", []))) - self.log.debug("get_files_info.resources:{}".format(resources)) - - sites = self.compute_resource_sync_sites(instance) - - output_resources = [] - anatomy = instance.context.data["anatomy"] - for _src, dest in resources: + file_infos = [] + for _src, dest in transfers: file_info = self.prepare_file_info(dest, anatomy, sites=sites) - output_resources.append(file_info) + file_infos.append(file_info) - return output_resources + return file_infos def prepare_file_info(self, path, anatomy, sites): """ Prepare information for one file (asset or resource) From 75838df107193f24fab2db52ce66ed04e976bfbd Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Tue, 22 Mar 2022 13:54:45 +0100 Subject: [PATCH 0014/1227] Handle timecode and audio state --- .../plugins/publish/extract_review_slate.py | 122 ++++++++++++++++-- 1 file changed, 110 insertions(+), 12 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 505ae75169..a21751aecc 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -10,7 +10,6 @@ from openpype.lib import ( get_ffmpeg_format_args, ) - class ExtractReviewSlate(openpype.api.Extractor): """ Will add slate frame at the start of the video files @@ -58,6 +57,7 @@ class ExtractReviewSlate(openpype.api.Extractor): pixel_aspect = inst_data.get("pixelAspect", 1) fps = inst_data.get("fps") + self.log.debug("fps {} ".format(fps)) for idx, repre in enumerate(inst_data["representations"]): self.log.debug("repre ({}): `{}`".format(idx + 1, repre)) @@ -82,12 +82,55 @@ class ExtractReviewSlate(openpype.api.Extractor): # - there may be a better way (checking `codec_type`?) input_width = None input_height = None + input_timecode = None + input_frame_rate = None + input_audio = False + audio_channels = None + audio_sample_rate = None + audio_channel_layout = None for stream in video_streams: - if "width" in stream and "height" in stream: - input_width = int(stream["width"]) - input_height = int(stream["height"]) - break - + self.log.debug("__ ffprobe: {}".format(stream)) + if "codec_type" in stream: + if stream["codec_type"] == "video": + if stream["tags"]["timecode"]: + # get timecode of the first frame + input_timecode = stream["tags"]["timecode"] + self.log.debug("__Video Timecode : {}".format(input_timecode)) + if "width" in stream and "height" in stream: + input_width = int(stream["width"]) + input_height = int(stream["height"]) + if "r_frame_rate" in stream: + # get frame rate in a form of x/y, like 24000/1001 for 23.976 + input_frame_rate = str(stream["r_frame_rate"]) + if stream["codec_type"] == "audio": + # get audio details for generating silent audio track for slate + if stream["channels"]: + audio_channels = str(stream["channels"]) + if stream["sample_rate"]: + audio_sample_rate = stream["sample_rate"] + if stream["channel_layout"]: + audio_channel_layout = stream["channel_layout"] + # calculate duration of one frame in seconds + if input_frame_rate: + # it is divided by two to make sure audio will be shorter then video + one_frame_duration = str( float(1.0 / eval(input_frame_rate)) / 2 ) + else: + # same sane default (1 frame @ 25 fps) + one_frame_duration = 0.04 + self.log.debug( + "One frame duration is {} sec".format(one_frame_duration)) + # confirm we gathered all needed audio parameters + if audio_channel_layout: + if audio_sample_rate: + if audio_channels: + input_audio = True + self.log.debug("__Audio : channels {}".format( + audio_channels)) + self.log.debug("__Audio : sample_rate {}".format( + audio_sample_rate)) + self.log.debug("__Audio : channel_layout {}".format( + audio_channel_layout)) + # Raise exception of any stream didn't define input resolution if input_width is None: raise AssertionError(( @@ -144,18 +187,34 @@ class ExtractReviewSlate(openpype.api.Extractor): input_args = [] output_args = [] + # if input has audio, add silent audio to the slate + if input_audio: + input_args.extend( + ["-f lavfi -i anullsrc=r={}:cl={}:d={}".format( + audio_sample_rate, + audio_channel_layout, + one_frame_duration + )] + ) # preset's input data if use_legacy_code: input_args.extend(repre["_profile"].get('input', [])) else: input_args.extend(repre["outputDef"].get('input', [])) - input_args.append("-loop 1 -i {}".format( + # enforce framerate before -i + input_args.append("-framerate {} -i {}".format( + input_frame_rate, path_to_subprocess_arg(slate_path) )) - input_args.extend([ - "-r {}".format(fps), - "-t 0.04" - ]) + # add timecode from source to the slate, substract one frame + if input_timecode: + offset_timecode = self._tc_offset( + str(input_timecode), + framerate=fps, + frame_offset=-1 + ) + self.log.debug("Timecode: `{}`".format(offset_timecode)) + input_args.extend(["-timecode {}".format(offset_timecode)]) if use_legacy_code: codec_args = repre["_profile"].get('codec', []) @@ -213,11 +272,16 @@ class ExtractReviewSlate(openpype.api.Extractor): width_scale, height_scale, to_width, to_height, width_half_pad, height_half_pad ) - + # add output frame rate as a filter, just in case + scaling_arg += ",fps={}".format(input_frame_rate) vf_back = self.add_video_filter_args(output_args, scaling_arg) # add it to output_args output_args.insert(0, vf_back) + # use video duration for silent audio duration + if input_audio: + output_args.append("-shortest") + # overrides output file output_args.append("-y") @@ -371,3 +435,37 @@ class ExtractReviewSlate(openpype.api.Extractor): ) return codec_args + + def _tc_offset(self, timecode='00:00:00:00', framerate=24.0, frame_offset=-1): + """Offsets timecode by frame""" + def _seconds(value, framerate): + if isinstance(value, str): + _zip_ft = zip((3600, 60, 1, 1/framerate), value.split(':')) + _s = sum(f * float(t) for f,t in _zip_ft) + elif isinstance(value, (int, float)): + _s = value / framerate + else: + _s = 0 + return _s + + def _frames(seconds, framerate, frame_offset): + _f = seconds * framerate + frame_offset + if _f < 0: + _f = framerate * 60 * 60 * 24 + _f + return _f + + def _timecode(seconds, framerate): + return '{h:02d}:{m:02d}:{s:02d}:{f:02d}' \ + .format(h=int(seconds/3600), + m=int(seconds/60%60), + s=int(seconds%60), + f=int(round((seconds-int(seconds))*framerate))) + drop = False + if ';' in timecode: + timecode = timecode.replace(';', ':') + drop = True + frames = _frames(_seconds(timecode, framerate), framerate, frame_offset) + tc = _timecode(_seconds(frames, framerate), framerate) + if drop: + tc = ';'.join(tc.rsplit(':', 1)) + return tc \ No newline at end of file From 2dd38c74c04814c33356a05dfda32efeda470ffd Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Tue, 22 Mar 2022 14:13:00 +0100 Subject: [PATCH 0015/1227] fix hound --- .../plugins/publish/extract_review_slate.py | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index a21751aecc..5cfb0997a5 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -95,15 +95,18 @@ class ExtractReviewSlate(openpype.api.Extractor): if stream["tags"]["timecode"]: # get timecode of the first frame input_timecode = stream["tags"]["timecode"] - self.log.debug("__Video Timecode : {}".format(input_timecode)) + self.log.debug("__Video Timecode : {}".format( + input_timecode)) if "width" in stream and "height" in stream: input_width = int(stream["width"]) input_height = int(stream["height"]) if "r_frame_rate" in stream: - # get frame rate in a form of x/y, like 24000/1001 for 23.976 + # get frame rate in a form of + # x/y, like 24000/1001 for 23.976 input_frame_rate = str(stream["r_frame_rate"]) if stream["codec_type"] == "audio": - # get audio details for generating silent audio track for slate + # get audio details + # for generating silent audio track for slate if stream["channels"]: audio_channels = str(stream["channels"]) if stream["sample_rate"]: @@ -112,8 +115,10 @@ class ExtractReviewSlate(openpype.api.Extractor): audio_channel_layout = stream["channel_layout"] # calculate duration of one frame in seconds if input_frame_rate: - # it is divided by two to make sure audio will be shorter then video - one_frame_duration = str( float(1.0 / eval(input_frame_rate)) / 2 ) + # it is halved to make sure audio will be shorter then video + one_frame_duration = str( + float(1.0 / eval(input_frame_rate)) / 2 + ) else: # same sane default (1 frame @ 25 fps) one_frame_duration = 0.04 @@ -436,7 +441,7 @@ class ExtractReviewSlate(openpype.api.Extractor): return codec_args - def _tc_offset(self, timecode='00:00:00:00', framerate=24.0, frame_offset=-1): + def _tc_offset(self, timecode, framerate=24.0, frame_offset=-1): """Offsets timecode by frame""" def _seconds(value, framerate): if isinstance(value, str): @@ -456,16 +461,20 @@ class ExtractReviewSlate(openpype.api.Extractor): def _timecode(seconds, framerate): return '{h:02d}:{m:02d}:{s:02d}:{f:02d}' \ - .format(h=int(seconds/3600), - m=int(seconds/60%60), - s=int(seconds%60), - f=int(round((seconds-int(seconds))*framerate))) + .format(h=int(seconds / 3600), + m=int(seconds / 60 % 60), + s=int(seconds % 60), + f=int(round((seconds -int(seconds)) * framerate))) drop = False if ';' in timecode: timecode = timecode.replace(';', ':') drop = True - frames = _frames(_seconds(timecode, framerate), framerate, frame_offset) + frames = _frames( + _seconds(timecode, framerate), + framerate, + frame_offset + ) tc = _timecode(_seconds(frames, framerate), framerate) if drop: tc = ';'.join(tc.rsplit(':', 1)) - return tc \ No newline at end of file + return tc From c67a5504516b2eddea46d230a1d7d6f9c7ec71c5 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Tue, 22 Mar 2022 14:17:56 +0100 Subject: [PATCH 0016/1227] Fix Hound2 --- openpype/plugins/publish/extract_review_slate.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 5cfb0997a5..d6857c7915 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -105,7 +105,7 @@ class ExtractReviewSlate(openpype.api.Extractor): # x/y, like 24000/1001 for 23.976 input_frame_rate = str(stream["r_frame_rate"]) if stream["codec_type"] == "audio": - # get audio details + # get audio details # for generating silent audio track for slate if stream["channels"]: audio_channels = str(stream["channels"]) @@ -117,7 +117,7 @@ class ExtractReviewSlate(openpype.api.Extractor): if input_frame_rate: # it is halved to make sure audio will be shorter then video one_frame_duration = str( - float(1.0 / eval(input_frame_rate)) / 2 + float(1.0 / eval(input_frame_rate)) / 2 ) else: # same sane default (1 frame @ 25 fps) @@ -460,11 +460,11 @@ class ExtractReviewSlate(openpype.api.Extractor): return _f def _timecode(seconds, framerate): - return '{h:02d}:{m:02d}:{s:02d}:{f:02d}' \ - .format(h=int(seconds / 3600), - m=int(seconds / 60 % 60), - s=int(seconds % 60), - f=int(round((seconds -int(seconds)) * framerate))) + return '{h:02d}:{m:02d}:{s:02d}:{f:02d}'.format( + h = int(seconds / 3600), + m = int(seconds / 60 % 60), + s = int(seconds % 60), + f = int(round((seconds - int(seconds)) * framerate))) drop = False if ';' in timecode: timecode = timecode.replace(';', ':') From 406575ebd8c7056febce78b0575e85204458d10b Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Tue, 22 Mar 2022 14:22:27 +0100 Subject: [PATCH 0017/1227] Hound3 --- .../projects_schema/schemas/schema_representation_tags.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json index 7607e1a8c1..484fbf9d07 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json @@ -24,6 +24,9 @@ }, { "sequence": "Output as image sequence" + }, + { + "no-audio": "Do not add audio" } ] } From 16817c30f67476578d30130b1aa721c66b7aa2dc Mon Sep 17 00:00:00 2001 From: Jiri Sindelar <45896205+jrsndl@users.noreply.github.com> Date: Tue, 22 Mar 2022 14:25:07 +0100 Subject: [PATCH 0018/1227] remove no-audio tag settings --- .../schemas/schema_representation_tags.json | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json deleted file mode 100644 index 484fbf9d07..0000000000 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "key": "tags", - "label": "Tags", - "type": "enum", - "multiselection": true, - "enum_items": [ - { - "burnin": "Add burnins" - }, - { - "review": "Create review" - }, - { - "ftrackreview": "Add review to Ftrack" - }, - { - "delete": "Delete output" - }, - { - "slate-frame": "Add slate frame" - }, - { - "no-handles": "Skip handle frames" - }, - { - "sequence": "Output as image sequence" - }, - { - "no-audio": "Do not add audio" - } - ] -} From f866bf2a757540c925a95864ccd2d9ac4d9b3a03 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 22 Mar 2022 16:36:41 +0100 Subject: [PATCH 0019/1227] adding back `schema_representation_tags` --- .../schemas/schema_representation_tags.json | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json new file mode 100644 index 0000000000..7607e1a8c1 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json @@ -0,0 +1,29 @@ +{ + "key": "tags", + "label": "Tags", + "type": "enum", + "multiselection": true, + "enum_items": [ + { + "burnin": "Add burnins" + }, + { + "review": "Create review" + }, + { + "ftrackreview": "Add review to Ftrack" + }, + { + "delete": "Delete output" + }, + { + "slate-frame": "Add slate frame" + }, + { + "no-handles": "Skip handle frames" + }, + { + "sequence": "Output as image sequence" + } + ] +} From 8996280224aa30ad800e955ff165bdbe48bb8296 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 23 Mar 2022 23:38:05 +0100 Subject: [PATCH 0020/1227] Reduce duplicated logic by implementing `resolve_profile` method --- openpype/plugins/publish/integrate_new.py | 107 ++++++++++------------ 1 file changed, 48 insertions(+), 59 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 2142920a09..e43afbf7f6 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -359,17 +359,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "short": task_code } - elif "task" in anatomy_data: - # Just set 'task_name' variable to context task - task_name = anatomy_data["task"]["name"] - task_type = anatomy_data["task"]["type"] - - else: - task_name = None - task_type = None - # Fill family in anatomy data - anatomy_data["family"] = instance.data.get("family") + anatomy_data["family"] = self.main_family_from_instance(instance) intent_value = instance.context.data.get("intent") if intent_value and isinstance(intent_value, dict): @@ -378,25 +369,44 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if intent_value: anatomy_data["intent"] = intent_value - # Get profile - key_values = { - "families": self.main_family_from_instance(instance), - "tasks": task_name, - "hosts": instance.context.data["hostName"], - "task_types": task_type - } - profile = filter_profiles( - self.template_name_profiles, - key_values, - logger=self.log - ) - + profile, _ = self.resolve_profile(self.template_name_profiles, + instance) template_name = "publish" if profile: template_name = profile["template_name"] return template_name, anatomy_data + def resolve_profile(self, profiles, instance): + """Resolve profile by family, task name, host name and task type""" + + # Anatomy data is pre-filled by Collectors and `self.prepare_anatomy` + anatomy_data = instance.data["anatomyData"] + + # TODO: Resolve below questions + # QUESTION + # - is there a chance that task name is not filled in anatomy data? + # - should we use context task in that case? + # Task can be optional in anatomy data + task = anatomy_data.get("task", {}) + + filter_criteria = { + "families": anatomy_data["family"], + "tasks": task.get("name"), + "hosts": anatomy_data["host"], + "task_types": task.get("type") + } + # Get profile + profile = filter_profiles( + profiles, + filter_criteria, + logger=self.log + ) + + # TODO: See if we can simplify to avoid needing to return filter + # criteria used in `self._get_subset_group` + return profile, filter_criteria + def register(self, instance, file_transactions): instance_stagingdir = instance.data.get("stagingDir") @@ -886,50 +896,29 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if not self.subset_grouping_profiles: return None - # TODO: Resolve below questions - # QUESTION - # - is there a chance that task name is not filled in anatomy data? - # - should we use context task in that case? - anatomy_data = instance.data["anatomyData"] - task_name = None - task_type = None - if "task" in anatomy_data: - task_name = anatomy_data["task"]["name"] - task_type = anatomy_data["task"]["type"] - filtering_criteria = { - "families": instance.data["family"], - "hosts": instance.context.data["hostName"], - "tasks": task_name, - "task_types": task_type - } - matching_profile = filter_profiles( - self.subset_grouping_profiles, - filtering_criteria - ) - # Skip if there is not matching profile - if not matching_profile: + # Skip if there is no matching profile + profile, criteria = self.resolve_profile(self.subset_grouping_profiles, + instance) + if not profile: return None - filled_template = None - template = matching_profile["template"] - fill_pairs = ( - ("family", filtering_criteria["families"]), - ("task", filtering_criteria["tasks"]), - ("host", filtering_criteria["hosts"]), - ("subset", instance.data["subset"]), - ("renderlayer", instance.data.get("renderlayer")) - ) - fill_pairs = prepare_template_data(fill_pairs) + template = profile["template"] + fill_pairs = prepare_template_data({ + "family": criteria["families"], + "task": criteria["tasks"], + "host": criteria["hosts"], + "subset": instance.data["subset"], + "renderlayer": instance.data.get("renderlayer") + }) + + filled_template = None try: filled_template = StringTemplate.format_strict_template( template, fill_pairs ) except (KeyError, TemplateUnsolved): - keys = [] - if fill_pairs: - keys = fill_pairs.keys() - + keys = fill_pairs.keys() msg = "Subset grouping failed. " \ "Only {} are expected in Settings".format(','.join(keys)) self.log.warning(msg) From 177e244bd80bf0b1d472948cba45f40dfecd672e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 23 Mar 2022 23:45:24 +0100 Subject: [PATCH 0021/1227] Remove prepare anatomy data logic that is already collected/generated in CollectAnatomyContextData and CollectAnatomyInstanceData. This currently was duplicated logic and should not be handled in the Integrator --- openpype/plugins/publish/integrate_new.py | 51 +---------------------- 1 file changed, 1 insertion(+), 50 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index e43afbf7f6..a1a116bd43 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -310,58 +310,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): def prepare_anatomy(self, instance): """Prepare anatomy data used to define representation destinations""" - context = instance.context - anatomy_data = instance.data["anatomyData"] - project_entity = instance.data["projectEntity"] - - context_asset_name = None - context_asset_doc = context.data.get("assetEntity") - if context_asset_doc: - context_asset_name = context_asset_doc["name"] - - asset_name = instance.data["asset"] - asset_entity = instance.data.get("assetEntity") - if not asset_entity or asset_entity["name"] != context_asset_name: - asset_entity = io.find_one({ - "type": "asset", - "name": asset_name, - "parent": project_entity["_id"] - }) - assert asset_entity, ( - "No asset found by the name \"{0}\" in project \"{1}\"" - ).format(asset_name, project_entity["name"]) - - instance.data["assetEntity"] = asset_entity - - # update anatomy data with asset specific keys - # - name should already been set - hierarchy = "" - parents = asset_entity["data"]["parents"] - if parents: - hierarchy = "/".join(parents) - anatomy_data["hierarchy"] = hierarchy - - # Make sure task name in anatomy data is same as on instance.data - asset_tasks = ( - asset_entity.get("data", {}).get("tasks") - ) or {} - task_name = instance.data.get("task") - if task_name: - task_info = asset_tasks.get(task_name) or {} - task_type = task_info.get("type") - - project_task_types = project_entity["config"]["tasks"] - task_code = project_task_types.get(task_type, {}).get("short_name") - anatomy_data["task"] = { - "name": task_name, - "type": task_type, - "short": task_code - } - - # Fill family in anatomy data - anatomy_data["family"] = self.main_family_from_instance(instance) + # TODO: This logic should move to CollectAnatomyContextData intent_value = instance.context.data.get("intent") if intent_value and isinstance(intent_value, dict): intent_value = intent_value.get("value") From 3fd2d020149e5b33c0be0ab7000376a0f30ed96f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 23 Mar 2022 23:55:40 +0100 Subject: [PATCH 0022/1227] Move logic to clarify what should be removed/moved and bring logic closer to where it's used --- openpype/plugins/publish/integrate_new.py | 36 ++++++++++------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index a1a116bd43..e57fbaf294 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -293,6 +293,10 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if families & set(self.exclude_families): return + # TODO: Avoid the need to do any adjustments to anatomy data + # Best case scenario that's all handled by collectors + self.prepare_anatomy(instance) + file_transactions = FileTransaction(log=self.log) try: self.register(instance, file_transactions) @@ -309,24 +313,12 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): def prepare_anatomy(self, instance): """Prepare anatomy data used to define representation destinations""" - - anatomy_data = instance.data["anatomyData"] - # TODO: This logic should move to CollectAnatomyContextData intent_value = instance.context.data.get("intent") if intent_value and isinstance(intent_value, dict): intent_value = intent_value.get("value") - - if intent_value: - anatomy_data["intent"] = intent_value - - profile, _ = self.resolve_profile(self.template_name_profiles, - instance) - template_name = "publish" - if profile: - template_name = profile["template_name"] - - return template_name, anatomy_data + if intent_value: + instance.data["anatomyData"]["intent"] = intent_value def resolve_profile(self, profiles, instance): """Resolve profile by family, task name, host name and task type""" @@ -382,6 +374,13 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): ) ) + # Define publish template name from profiles + profile, _ = self.resolve_profile(self.template_name_profiles, + instance) + template_name = "publish" + if profile: + template_name = profile["template_name"] + subset = self.register_subset(instance) version = self.register_version(instance, subset) @@ -393,7 +392,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): })) # Prepare all representations - template_name, anatomy_data = self.prepare_anatomy(instance) prepared_representations = [] for repre in instance.data["representations"]: @@ -404,7 +402,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # todo: reduce/simplify what is returned from this function prepared = self.prepare_representation(repre, - anatomy_data, template_name, archived_repres, version, @@ -544,16 +541,12 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): return version def prepare_representation(self, repre, - anatomy_data, template_name, archived_repres, version, instance_stagingdir, instance): - # create template data for Anatomy - template_data = copy.deepcopy(anatomy_data) - # pre-flight validations if repre["ext"].startswith("."): raise ValueError("Extension must not start with a dot '.': " @@ -565,6 +558,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "the integrator" "Got: {}".format(repre["transfers"])) + # create template data for Anatomy + template_data = copy.deepcopy(instance.data["anatomyData"]) + # required representation keys files = repre['files'] template_data["representation"] = repre["name"] From 8edfb3f7d3f926539f7f060725b3b7e0b1d697e5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 24 Mar 2022 00:10:59 +0100 Subject: [PATCH 0023/1227] Simplify profile filtering --- openpype/plugins/publish/integrate_new.py | 42 +++++++++-------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index e57fbaf294..bdc045d1db 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -320,35 +320,21 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if intent_value: instance.data["anatomyData"]["intent"] = intent_value - def resolve_profile(self, profiles, instance): - """Resolve profile by family, task name, host name and task type""" - - # Anatomy data is pre-filled by Collectors and `self.prepare_anatomy` + def get_profile_filter_criteria(self, instance): + """Return filter criteria for `filter_profiles`""" + # Anatomy data is pre-filled by Collectors anatomy_data = instance.data["anatomyData"] - # TODO: Resolve below questions - # QUESTION - # - is there a chance that task name is not filled in anatomy data? - # - should we use context task in that case? # Task can be optional in anatomy data task = anatomy_data.get("task", {}) - filter_criteria = { + # Return filter criteria + return { "families": anatomy_data["family"], "tasks": task.get("name"), "hosts": anatomy_data["host"], "task_types": task.get("type") } - # Get profile - profile = filter_profiles( - profiles, - filter_criteria, - logger=self.log - ) - - # TODO: See if we can simplify to avoid needing to return filter - # criteria used in `self._get_subset_group` - return profile, filter_criteria def register(self, instance, file_transactions): @@ -375,8 +361,10 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): ) # Define publish template name from profiles - profile, _ = self.resolve_profile(self.template_name_profiles, - instance) + filter_criteria = self.get_profile_filter_criteria(instance) + profile = filter_profiles(self.template_name_profiles, + filter_criteria, + logger=self.log) template_name = "publish" if profile: template_name = profile["template_name"] @@ -844,17 +832,19 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): return None # Skip if there is no matching profile - profile, criteria = self.resolve_profile(self.subset_grouping_profiles, - instance) + filter_criteria = self.get_profile_filter_criteria(instance) + profile = filter_profiles(self.subset_grouping_profiles, + filter_criteria, + logger=self.log) if not profile: return None template = profile["template"] fill_pairs = prepare_template_data({ - "family": criteria["families"], - "task": criteria["tasks"], - "host": criteria["hosts"], + "family": filter_criteria["families"], + "task": filter_criteria["tasks"], + "host": filter_criteria["hosts"], "subset": instance.data["subset"], "renderlayer": instance.data.get("renderlayer") }) From 79286ead4b91504afa30df711e8f751451f53552 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 24 Mar 2022 00:16:32 +0100 Subject: [PATCH 0024/1227] Re-use get families logic --- openpype/plugins/publish/integrate_new.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index bdc045d1db..e66a71c483 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -286,10 +286,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): def process(self, instance): # Exclude instances that also contain families from exclude families - families = set( - # Consider family and families data - [instance.data["family"]] + instance.data.get("families", []) - ) + families = set(self._get_instance_families(instance)) if families & set(self.exclude_families): return From d6c682723de6eb025b21768ced54b2756373fba6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 24 Mar 2022 00:19:16 +0100 Subject: [PATCH 0025/1227] Remove todo since assetEntity already comes from Collectors + re-use families variable --- openpype/plugins/publish/integrate_new.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index e66a71c483..856f8af163 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -755,8 +755,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): return families def register_subset(self, instance): - # todo: rely less on self.prepare_anatomy to create this value - asset = instance.data.get("assetEntity") # stored by prepare_anatomy + asset = instance.data.get("assetEntity") subset_name = instance.data["subset"] self.log.debug("Subset: {}".format(subset_name)) @@ -766,9 +765,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "name": subset_name }) + families = self._get_instance_families(instance) if subset is None: self.log.info("Subset '%s' not found, creating ..." % subset_name) - families = self._get_instance_families(instance) _id = io.insert_one({ "schema": "openpype:subset-3.0", @@ -786,8 +785,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self._set_subset_group(instance, subset["_id"]) # Update families on subset. - families = [instance.data["family"]] - families.extend(instance.data.get("families", [])) io.update_many( {"type": "subset", "_id": ObjectId(subset["_id"])}, {"$set": {"data.families": families}} From 47259f8ef7b177892c76a8dbfde6d147cf664d39 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 24 Mar 2022 00:21:44 +0100 Subject: [PATCH 0026/1227] Add todo to move get subset group logic --- openpype/plugins/publish/integrate_new.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 856f8af163..91d2f3a943 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -821,6 +821,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): Attribute 'subset_grouping_profiles' is defined by OpenPype settings. """ + # TODO: This logic is better suited for a Collector to just store + # instance.data["subsetGroup"] # Skip if 'subset_grouping_profiles' is empty if not self.subset_grouping_profiles: return None From b128e0addffc77991e5ff25f2d219d8ed8613136 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 24 Mar 2022 14:21:32 +0100 Subject: [PATCH 0027/1227] Override stored repre context `udim` for backwards compatibility --- openpype/plugins/publish/integrate_new.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 91d2f3a943..e3abb8f04f 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -275,7 +275,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): exclude_families = ["clip"] db_representation_context_keys = [ "project", "asset", "task", "subset", "version", "representation", - "family", "hierarchy", "task", "username", "frame", "udim" + "family", "hierarchy", "task", "username", "frame" ] default_template_name = "publish" @@ -681,15 +681,17 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # Single file transfer transfers = [(src, dst)] - if repre.get("udim"): - repre_context["udim"] = repre.get("udim") # store list - for key in self.db_representation_context_keys: value = template_data.get(key) if not value: continue repre_context[key] = template_data[key] + # Explicitly store the full list even though template data might + # have a different value + if repre.get("udim"): + repre_context["udim"] = repre.get("udim") # store list + # Define representation id repre_id = ObjectId() From 9997acbbeae32f1473c39df6cf78a8bfa7257aff Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 24 Mar 2022 14:22:49 +0100 Subject: [PATCH 0028/1227] Encapsulate version data completely into its own function --- openpype/plugins/publish/integrate_new.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index e3abb8f04f..6e92f81b8b 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -452,17 +452,12 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): version_number = instance.data["version"] self.log.debug("Version: v{0:03d}".format(version_number)) - version_data = self.create_version_data(instance) - version_data_instance = instance.data.get('versionData') - if version_data_instance: - version_data.update(version_data_instance) - version = { "schema": "openpype:version-3.0", "type": "version", "parent": subset["_id"], "name": version_number, - "data": version_data + "data": self.create_version_data(instance) } repres = instance.data.get("representations", []) @@ -909,6 +904,11 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if key in instance.data: version_data[key] = instance.data[key] + # Include instance.data[versionData] directly + version_data_instance = instance.data.get('versionData') + if version_data_instance: + version_data.update(version_data_instance) + return version_data def main_family_from_instance(self, instance): From 5b1f6eb30c459011fa685dcf325f39c4af72838e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 24 Mar 2022 14:23:27 +0100 Subject: [PATCH 0029/1227] Move logic closer to where it's used --- openpype/plugins/publish/integrate_new.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 6e92f81b8b..a787f8d50d 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -460,9 +460,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "data": self.create_version_data(instance) } - repres = instance.data.get("representations", []) - new_repre_names_low = [_repre["name"].lower() for _repre in repres] - existing_version = io.find_one({ 'type': 'version', 'parent': subset["_id"], @@ -488,6 +485,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): })) # Find representations of existing version and archive them + repres = instance.data.get("representations", []) + new_repre_names_low = [_repre["name"].lower() for _repre in repres] current_repres = io.find({ "type": "representation", "parent": version_id From 3369c15bdf837d6d8e83a8c054794e95fccd061b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 24 Mar 2022 14:25:15 +0100 Subject: [PATCH 0030/1227] Preparation to delay Version document write to database closer to representation write --- openpype/plugins/publish/integrate_new.py | 24 ++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index a787f8d50d..8dd2d57959 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -466,15 +466,16 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): 'name': version_number }) + bulk_writes = [] if existing_version is None: self.log.debug("Creating new version ...") - version_id = io.insert_one(version).inserted_id + version["_id"] = ObjectId() + bulk_writes.append(InsertOne(version)) else: self.log.debug("Updating existing version ...") # Check if instance have set `append` mode which cause that # only replicated representations are set to archive append_repres = instance.data.get("append", False) - bulk_writes = [] # Update version data version_id = existing_version['_id'] @@ -484,6 +485,12 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): '$set': version })) + # Instead of directly writing and querying we reproduce what + # the resulting version would look like so we can hold off making + # changes to the database to avoid the need for 'rollback' + version = copy.deepcopy(version) + version["_id"] = existing_version["_id"] + # Find representations of existing version and archive them repres = instance.data.get("representations", []) new_repre_names_low = [_repre["name"].lower() for _repre in repres] @@ -507,13 +514,12 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): repre["type"] = "archived_representation" bulk_writes.append(InsertOne(repre)) - # bulk updates - if bulk_writes: - io._database[io.Session["AVALON_PROJECT"]].bulk_write( - bulk_writes - ) - - version = io.find_one({"_id": version_id}) + # bulk updates + # todo: Try to avoid writing already until after we've prepared + # representations to allow easier rollback? + io._database[io.Session["AVALON_PROJECT"]].bulk_write( + bulk_writes + ) self.log.info("Registered version: v{0:03d}".format(version["name"])) From 42175ff6f829ce30ef61538243d7bd4b804c8e28 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 24 Mar 2022 14:41:56 +0100 Subject: [PATCH 0031/1227] Fix `get_profile_filter_criteria` anatomy data key for app name --- openpype/plugins/publish/integrate_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 8dd2d57959..e3dcfcc93c 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -329,7 +329,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): return { "families": anatomy_data["family"], "tasks": task.get("name"), - "hosts": anatomy_data["host"], + "hosts": anatomy_data["app"], "task_types": task.get("type") } From 7713af5a1dac4b0080dc6006f08811dcd9fc9d04 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 24 Mar 2022 17:23:02 +0100 Subject: [PATCH 0032/1227] Fix sequence functionality --- openpype/plugins/publish/integrate_new.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index e3dcfcc93c..b5986a62ee 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -645,16 +645,20 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): repre_context = template_filled.used_values self.log.debug("Template filled: {}".format(str(template_filled))) dst_collections, _remainder = clique.assemble( - [os.path.normpath(template_filled)], minimum_items=1 + [os.path.normpath(template_filled)], + minimum_items=1, + patterns=[clique.PATTERNS["frames"]] ) assert not _remainder, "This is a bug" assert len(dst_collections) == 1, "This is a bug" dst_collection = dst_collections[0] # Update the destination indexes and padding - dst_collection.indexes = destination_indexes + dst_collection.indexes.clear() + dst_collection.indexes.update(set(destination_indexes)) dst_collection.padding = destination_padding - assert len(src_collection) == len(dst_collection), "This is a bug" + assert len(src_collection.indexes) == \ + len(dst_collection.indexes), "This is a bug" # Multiple file transfers transfers = [] From 229626bffdbc7e59c2206798b5bb3066a5602228 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 24 Mar 2022 17:36:01 +0100 Subject: [PATCH 0033/1227] Reformat code --- openpype/plugins/publish/integrate_new.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index b5986a62ee..9e3e9de77c 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -657,8 +657,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): dst_collection.indexes.clear() dst_collection.indexes.update(set(destination_indexes)) dst_collection.padding = destination_padding - assert len(src_collection.indexes) == \ - len(dst_collection.indexes), "This is a bug" + assert ( + len(src_collection.indexes) == len(dst_collection.indexes) + ), "This is a bug" # Multiple file transfers transfers = [] From e1eb0887e0bdaaf012e95f289bdaddcf9089d65c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 24 Mar 2022 20:26:10 +0100 Subject: [PATCH 0034/1227] Reduce database calls for register subset + prepare for bulk writes logic --- openpype/plugins/publish/integrate_new.py | 72 ++++++++++------------- 1 file changed, 31 insertions(+), 41 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 9e3e9de77c..44768df368 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -766,63 +766,53 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): subset_name = instance.data["subset"] self.log.debug("Subset: {}".format(subset_name)) + # Get existing subset if it exists subset = io.find_one({ "type": "subset", "parent": asset["_id"], "name": subset_name }) - families = self._get_instance_families(instance) - if subset is None: - self.log.info("Subset '%s' not found, creating ..." % subset_name) + # Define subset data + data = { + "families": self._get_instance_families(instance) + } - _id = io.insert_one({ + subset_group = instance.data.get("subsetGroup") + if not subset_group: + # todo: move _get_subset_group fallback to its own collector + subset_group = self._get_subset_group(instance) + if subset_group: + data["subsetGroup"] = subset_group + + if subset is None: + # Create a new subset + self.log.info("Subset '%s' not found, creating ..." % subset_name) + subset = { + "_id": ObjectId(), "schema": "openpype:subset-3.0", "type": "subset", "name": subset_name, - "data": { - "families": families - }, + "data": data, "parent": asset["_id"] - }).inserted_id + } + io.insert_one(subset) - subset = io.find_one({"_id": _id}) - - # Update subset group - self._set_subset_group(instance, subset["_id"]) - - # Update families on subset. - io.update_many( - {"type": "subset", "_id": ObjectId(subset["_id"])}, - {"$set": {"data.families": families}} - ) + else: + # Update existing subset data with new data and set in database. + # We also change the found subset in-place so we don't need to + # re-query the subset afterwards + subset["data"].update(data) + io.update_many( + {"type": "subset", "_id": subset["_id"]}, + {"$set": { + "data": subset["data"] + }} + ) self.log.info("Registered subset: {}".format(subset_name)) - return subset - def _set_subset_group(self, instance, subset_id): - """ - Mark subset as belonging to group in DB. - - Uses Settings > Global > Publish plugins > IntegrateAssetNew - - Args: - instance (dict): processed instance - subset_id (str): DB's subset _id - - """ - # Fist look into instance data - subset_group = instance.data.get("subsetGroup") - if not subset_group: - subset_group = self._get_subset_group(instance) - - if subset_group: - io.update_many({ - 'type': 'subset', - '_id': ObjectId(subset_id) - }, {'$set': {'data.subsetGroup': subset_group}}) - def _get_subset_group(self, instance): """Look into subset group profiles set by settings. From b906365f593025bf7bbba67ea6d8a907b717c98e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 25 Mar 2022 21:42:39 +0100 Subject: [PATCH 0035/1227] Separate site sync logic further from Integrator plug-in (Draft) --- openpype/plugins/publish/integrate_new.py | 154 ++++++++++++---------- 1 file changed, 88 insertions(+), 66 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 44768df368..138a4fcc06 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -419,7 +419,12 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # Finalize the representations now the published files are integrated # Get 'files' info for representations and its attached resources self.log.debug("Retrieving Representation files information ...") - sites = self.compute_resource_sync_sites(instance) + sites = SiteSync.compute_resource_sync_sites( + system_settings=instance.context.data["system_settings"], + project_settings=instance.context.data["project_settings"] + ) + log.debug("final sites:: {}".format(sites)) + anatomy = instance.context.data["anatomy"] representations = [] for prepared in prepared_representations: @@ -987,63 +992,65 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "sites": sites } - # region sync sites - def compute_resource_sync_sites(self, instance): + +class SiteSync(object): + """Logic for Site Sync Module functionality""" + + @classmethod + def compute_resource_sync_sites(cls, + system_settings, + project_settings): """Get available resource sync sites""" - # Sync server logic - # TODO: Clean up sync settings - local_site = 'studio' # default - remote_site = None - always_accessible = [] - sync_project_presets = None - system_sync_server_presets = ( - instance.context.data["system_settings"] - ["modules"] - ["sync_server"]) + def create_metadata(name, created=True): + """Create sync site metadata for site with `name`""" + metadata = {"name": name} + if created: + metadata["created_dt"] = datetime.now() + return metadata + + default_sites = [create_metadata("studio")] + + # If sync site module is disabled return default fallback site + system_sync_server_presets = system_settings["modules"]["sync_server"] log.debug("system_sett:: {}".format(system_sync_server_presets)) + if not system_sync_server_presets["enabled"]: + return default_sites - if system_sync_server_presets["enabled"]: - sync_project_presets = ( - instance.context.data["project_settings"] - ["global"] - ["sync_server"]) + # If sync site module is disabled in current + # project return default fallback site + sync_project_presets = project_settings["global"]["sync_server"] + if not sync_project_presets["enabled"]: + return default_sites - if sync_project_presets and sync_project_presets["enabled"]: - local_site, remote_site = self._get_sites(sync_project_presets) - always_accessible = sync_project_presets["config"]. \ - get("always_accessible_on", []) + local_site, remote_site = cls._get_sites(sync_project_presets) - already_attached_sites = {} - meta = {"name": local_site, "created_dt": datetime.now()} - sites = [meta] - already_attached_sites[meta["name"]] = meta["created_dt"] + # Attached sites metadata by site name + # That is the local site, remote site, the always accesible sites + # and their alternate sites (alias of sites with different protocol) + attached_sites = dict() + attached_sites[local_site] = create_metadata(local_site) - if sync_project_presets and sync_project_presets["enabled"]: - if remote_site and \ - remote_site not in already_attached_sites.keys(): - # add remote - meta = {"name": remote_site.strip()} - sites.append(meta) - already_attached_sites[meta["name"]] = None + if remote_site and remote_site != local_site: + attached_sites[remote_site] = create_metadata(remote_site, + created=False) - # add skeleton for site where it should be always synced to - for always_on_site in always_accessible: - if always_on_site not in already_attached_sites.keys(): - meta = {"name": always_on_site.strip()} - sites.append(meta) - already_attached_sites[meta["name"]] = None + # add skeleton for sites where it should be always synced to + always_accessible_sites = ( + sync_project_presets["config"].get("always_accessible_on", []) + ) + for site in always_accessible_sites: + site = site.strip() + if site not in attached_sites: + attached_sites[site] = create_metadata(site, created=False) - # add alternative sites - alt = self._add_alternative_sites(system_sync_server_presets, - already_attached_sites) - sites.extend(alt) + # add alternative sites + cls._add_alternative_sites(system_sync_server_presets, attached_sites) - log.debug("final sites:: {}".format(sites)) + return list(attached_sites.values()) - return sites - - def _get_sites(self, sync_project_presets): + @staticmethod + def _get_sites(sync_project_presets): """Returns tuple (local_site, remote_site)""" local_site_id = openpype.api.get_local_site_id() local_site = sync_project_presets["config"]. \ @@ -1053,36 +1060,51 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): local_site = local_site_id remote_site = sync_project_presets["config"].get("remote_site") + if remote_site: + remote_site.strip() if remote_site == 'local': remote_site = local_site_id return local_site, remote_site - def _add_alternative_sites(self, - system_sync_server_presets, - already_attached_sites): + @staticmethod + def _add_alternative_sites(system_sync_server_presets, + attached_sites): """Loop through all configured sites and add alternatives. + For all sites if an alternative site is detected that has an + accessible site then we can also register to that alternative site + with the same "created" state. So we match the existing data. + See SyncServerModule.handle_alternate_site """ conf_sites = system_sync_server_presets.get("sites", {}) - alternative_sites = [] for site_name, site_info in conf_sites.items(): - alt_sites = set(site_info.get("alternative_sites", [])) - already_attached_keys = list(already_attached_sites.keys()) - for added_site in already_attached_keys: - if added_site in alt_sites: - if site_name in already_attached_keys: - continue - meta = {"name": site_name} - real_created = already_attached_sites[added_site] - # alt site inherits state of 'created_dt' - if real_created: - meta["created_dt"] = real_created - alternative_sites.append(meta) - already_attached_sites[meta["name"]] = real_created - return alternative_sites - # endregion + # Skip if already defined + if site_name in attached_sites: + continue + + # Get alternate sites (stripped names) for this site name + alt_sites = site_info.get("alternative_sites", []) + alt_sites = [site.strip() for site in alt_sites] + alt_sites = set(alt_sites) + + # If no alternative sites we don't need to add + if not alt_sites: + continue + + # Take a copy of data of the first alternate site that is already + # defined as an attached site to match the same state. + match_meta = next((attached_sites[site] for site in alt_sites + if site in attached_sites), None) + if not match_meta: + continue + + alt_site_meta = copy.deepcopy(match_meta) + alt_site_meta["name"] = site_name + + # Note: We change mutable `attached_site` dict in-place + attached_sites[site_name] = alt_site_meta From e0aaa5f6cc2fd2a2e6fa708364136d9d6235163d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 26 Mar 2022 14:20:13 +0100 Subject: [PATCH 0036/1227] Move FileTransaction into lib --- openpype/lib/file_transaction.py | 171 ++++++++++++++++++++++ openpype/plugins/publish/integrate_new.py | 167 +-------------------- 2 files changed, 172 insertions(+), 166 deletions(-) create mode 100644 openpype/lib/file_transaction.py diff --git a/openpype/lib/file_transaction.py b/openpype/lib/file_transaction.py new file mode 100644 index 0000000000..57592e297f --- /dev/null +++ b/openpype/lib/file_transaction.py @@ -0,0 +1,171 @@ +import os +import logging +import sys +import errno +import six + +from openpype.lib import create_hard_link + +# this is needed until speedcopy for linux is fixed +if sys.platform == "win32": + from speedcopy import copyfile +else: + from shutil import copyfile + + +class FileTransaction(object): + """ + + The file transaction is a three step process. + + 1) Rename any existing files to a "temporary backup" during `process()` + 2) Copy the files to final destination during `process()` + 3) Remove any backed up files (*no rollback possible!) during `finalize()` + + Step 3 is done during `finalize()`. If not called the .bak files will + remain on disk. + + These steps try to ensure that we don't overwrite half of any existing + files e.g. if they are currently in use. + + Note: + A regular filesystem is *not* a transactional file system and even + though this implementation tries to produce a 'safe copy' with a + potential rollback do keep in mind that it's inherently unsafe due + to how filesystem works and a myriad of things could happen during + the transaction that break the logic. A file storage could go down, + permissions could be changed, other machines could be moving or writing + files. A lot can happen. + + Warning: + Any folders created during the transfer will not be removed. + + """ + + MODE_COPY = 0 + MODE_HARDLINK = 1 + + def __init__(self, log=None): + + if log is None: + log = logging.getLogger("FileTransaction") + + self.log = log + + # The transfer queue + # todo: make this an actual FIFO queue? + self._transfers = {} + + # Destination file paths that a file was transferred to + self._transferred = [] + + # Backup file location mapping to original locations + self._backup_to_original = {} + + def add(self, src, dst, mode=MODE_COPY): + """Add a new file to transfer queue""" + opts = {"mode": mode} + + src = os.path.normpath(src) + dst = os.path.normpath(dst) + + if dst in self._transfers: + queued_src = self._transfers[dst][0] + if src == queued_src: + self.log.debug("File transfer was already " + "in queue: {} -> {}".format(src, dst)) + return + else: + self.log.warning("File transfer in queue replaced..") + self.log.debug("Removed from queue: " + "{} -> {}".format(queued_src, dst)) + self.log.debug("Added to queue: {} -> {}".format(src, dst)) + + self._transfers[dst] = (src, opts) + + def process(self): + + # Backup any existing files + for dst in self._transfers.keys(): + if os.path.exists(dst): + # Backup original file + # todo: add timestamp or uuid to ensure unique + backup = dst + ".bak" + self._backup_to_original[backup] = dst + self.log.debug("Backup existing file: " + "{} -> {}".format(dst, backup)) + os.rename(dst, backup) + + # Copy the files to transfer + for dst, (src, opts) in self._transfers.items(): + self._create_folder_for_file(dst) + + if opts["mode"] == self.MODE_COPY: + self.log.debug("Copying file ... {} -> {}".format(src, dst)) + copyfile(src, dst) + elif opts["mode"] == self.MODE_HARDLINK: + self.log.debug("Hardlinking file ... {} -> {}".format(src, + dst)) + create_hard_link(src, dst) + + self._transferred.append(dst) + + def finalize(self): + # Delete any backed up files + for backup in self._backup_to_original.keys(): + try: + os.remove(backup) + except OSError: + self.log.error("Failed to remove backup file: " + "{}".format(backup), + exc_info=True) + + def rollback(self): + + errors = 0 + + # Rollback any transferred files + for path in self._transferred: + try: + os.remove(path) + except OSError: + errors += 1 + self.log.error("Failed to rollback created file: " + "{}".format(path), + exc_info=True) + + # Rollback the backups + for backup, original in self._backup_to_original.items(): + try: + os.rename(backup, original) + except OSError: + errors += 1 + self.log.error("Failed to restore original file: " + "{} -> {}".format(backup, original), + exc_info=True) + + if errors: + self.log.error("{} errors occurred during " + "rollback.".format(errors), exc_info=True) + six.reraise(*sys.exc_info()) + + @property + def transferred(self): + """Return the processed transfers destination paths""" + return list(self._transferred) + + @property + def backups(self): + """Return the backup file paths""" + return list(self._backup_to_original.keys()) + + def _create_folder_for_file(self, path): + dirname = os.path.dirname(path) + try: + os.makedirs(dirname) + except OSError as e: + if e.errno == errno.EEXIST: + pass + else: + self.log.critical("An unexpected error occurred.") + six.reraise(*sys.exc_info()) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 138a4fcc06..92976e6151 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -3,7 +3,6 @@ import logging import sys import copy import clique -import errno import six from bson.objectid import ObjectId @@ -13,19 +12,13 @@ from avalon import io import openpype.api from datetime import datetime from openpype.lib.profiles_filtering import filter_profiles +from openpype.lib.file_transaction import FileTransaction from openpype.lib import ( prepare_template_data, - create_hard_link, StringTemplate, TemplateUnsolved ) -# this is needed until speedcopy for linux is fixed -if sys.platform == "win32": - from speedcopy import copyfile -else: - from shutil import copyfile - log = logging.getLogger(__name__) @@ -40,164 +33,6 @@ def get_first_frame_padded(collection): return get_frame_padded(start_frame, padding=collection.padding) -class FileTransaction(object): - """ - - The file transaction is a three step process. - - 1) Rename any existing files to a "temporary backup" during `process()` - 2) Copy the files to final destination during `process()` - 3) Remove any backed up files (*no rollback possible!) during `finalize()` - - Step 3 is done during `finalize()`. If not called the .bak files will - remain on disk. - - These steps try to ensure that we don't overwrite half of any existing - files e.g. if they are currently in use. - - Note: - A regular filesystem is *not* a transactional file system and even - though this implementation tries to produce a 'safe copy' with a - potential rollback do keep in mind that it's inherently unsafe due - to how filesystem works and a myriad of things could happen during - the transaction that break the logic. A file storage could go down, - permissions could be changed, other machines could be moving or writing - files. A lot can happen. - - Warning: - Any folders created during the transfer will not be removed. - - """ - - MODE_COPY = 0 - MODE_HARDLINK = 1 - - def __init__(self, log=None): - - if log is None: - log = logging.getLogger("FileTransaction") - - self.log = log - - # The transfer queue - # todo: make this an actual FIFO queue? - self._transfers = {} - - # Destination file paths that a file was transferred to - self._transferred = [] - - # Backup file location mapping to original locations - self._backup_to_original = {} - - def add(self, src, dst, mode=MODE_COPY): - """Add a new file to transfer queue""" - opts = {"mode": mode} - - src = os.path.normpath(src) - dst = os.path.normpath(dst) - - if dst in self._transfers: - queued_src = self._transfers[dst][0] - if src == queued_src: - self.log.debug("File transfer was already " - "in queue: {} -> {}".format(src, dst)) - return - else: - self.log.warning("File transfer in queue replaced..") - self.log.debug("Removed from queue: " - "{} -> {}".format(queued_src, dst)) - self.log.debug("Added to queue: {} -> {}".format(src, dst)) - - self._transfers[dst] = (src, opts) - - def process(self): - - # Backup any existing files - for dst in self._transfers.keys(): - if os.path.exists(dst): - # Backup original file - # todo: add timestamp or uuid to ensure unique - backup = dst + ".bak" - self._backup_to_original[backup] = dst - self.log.debug("Backup existing file: " - "{} -> {}".format(dst, backup)) - os.rename(dst, backup) - - # Copy the files to transfer - for dst, (src, opts) in self._transfers.items(): - self._create_folder_for_file(dst) - - if opts["mode"] == self.MODE_COPY: - self.log.debug("Copying file ... {} -> {}".format(src, dst)) - copyfile(src, dst) - elif opts["mode"] == self.MODE_HARDLINK: - self.log.debug("Hardlinking file ... {} -> {}".format(src, - dst)) - create_hard_link(src, dst) - - self._transferred.append(dst) - - def finalize(self): - # Delete any backed up files - for backup in self._backup_to_original.keys(): - try: - os.remove(backup) - except OSError: - self.log.error("Failed to remove backup file: " - "{}".format(backup), - exc_info=True) - - def rollback(self): - - errors = 0 - - # Rollback any transferred files - for path in self._transferred: - try: - os.remove(path) - except OSError: - errors += 1 - self.log.error("Failed to rollback created file: " - "{}".format(path), - exc_info=True) - - # Rollback the backups - for backup, original in self._backup_to_original.items(): - try: - os.rename(backup, original) - except OSError: - errors += 1 - self.log.error("Failed to restore original file: " - "{} -> {}".format(backup, original), - exc_info=True) - - if errors: - self.log.error("{} errors occurred during " - "rollback.".format(errors), exc_info=True) - six.reraise(*sys.exc_info()) - - @property - def transferred(self): - """Return the processed transfers destination paths""" - return list(self._transferred) - - @property - def backups(self): - """Return the backup file paths""" - return list(self._backup_to_original.keys()) - - def _create_folder_for_file(self, path): - dirname = os.path.dirname(path) - try: - os.makedirs(dirname) - except OSError as e: - if e.errno == errno.EEXIST: - pass - else: - self.log.critical("An unexpected error occurred.") - six.reraise(*sys.exc_info()) - - class IntegrateAssetNew(pyblish.api.InstancePlugin): """Resolve any dependency issues From d3cb32ebe1df79408ff03fddef4d74a55fa1f4b6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 26 Mar 2022 14:32:34 +0100 Subject: [PATCH 0037/1227] Collect subset group in a Collector instead of during Integrator --- .../plugins/publish/collect_subset_group.py | 100 ++++++++++++++++++ openpype/plugins/publish/integrate_new.py | 50 --------- 2 files changed, 100 insertions(+), 50 deletions(-) create mode 100644 openpype/plugins/publish/collect_subset_group.py diff --git a/openpype/plugins/publish/collect_subset_group.py b/openpype/plugins/publish/collect_subset_group.py new file mode 100644 index 0000000000..60c1c04e70 --- /dev/null +++ b/openpype/plugins/publish/collect_subset_group.py @@ -0,0 +1,100 @@ +"""Produces instance.data["subsetGroup"] data used during integration. + +Requires: + dict -> context["anatomyData"] *(pyblish.api.CollectorOrder + 0.49) + +Provides: + instance -> subsetGroup (str) + +""" +import pyblish.api + +from openpype.lib.profiles_filtering import filter_profiles +from openpype.lib import ( + prepare_template_data, + StringTemplate, + TemplateUnsolved +) + + +class CollectSubsetGroup(pyblish.api.ContextPlugin): + """Collect Subset Group for publish.""" + + # Run after CollectAnatomyInstanceData + order = pyblish.api.CollectorOrder + 0.495 + label = "Collect Subset Group" + + def process(self, instance): + """Look into subset group profiles set by settings. + + Attribute 'subset_grouping_profiles' is defined by OpenPype settings. + """ + + # TODO: Move this setting to this Collector instead of Integrator + project_settings = instance.context.data["project_settings"] + subset_grouping_profiles = ( + project_settings["global"] + ["publish"] + ["IntegrateAssetNew"] + ["subset_grouping_profiles"] + ) + + # Skip if 'subset_grouping_profiles' is empty + if not subset_grouping_profiles: + return + + # Skip if there is no matching profile + filter_criteria = self.get_profile_filter_criteria(instance) + profile = filter_profiles(subset_grouping_profiles, + filter_criteria, + logger=self.log) + if not profile: + return + + if instance.data.get("subsetGroup"): + # If subsetGroup is already set then allow that value to remain + self.log.debug("Skipping collect subset group due to existing " + "value: {}".format(instance.data["subsetGroup"])) + return + + template = profile["template"] + + fill_pairs = prepare_template_data({ + "family": filter_criteria["families"], + "task": filter_criteria["tasks"], + "host": filter_criteria["hosts"], + "subset": instance.data["subset"], + "renderlayer": instance.data.get("renderlayer") + }) + + filled_template = None + try: + filled_template = StringTemplate.format_strict_template( + template, fill_pairs + ) + except (KeyError, TemplateUnsolved): + keys = fill_pairs.keys() + msg = "Subset grouping failed. " \ + "Only {} are expected in Settings".format(','.join(keys)) + self.log.warning(msg) + + if filled_template: + instance.data["subsetGroup"] = filled_template + + def get_profile_filter_criteria(self, instance): + """Return filter criteria for `filter_profiles`""" + # TODO: This logic is used in much more plug-ins in one way or another + # Maybe better suited for lib? + # Anatomy data is pre-filled by Collectors + anatomy_data = instance.data["anatomyData"] + + # Task can be optional in anatomy data + task = anatomy_data.get("task", {}) + + # Return filter criteria + return { + "families": anatomy_data["family"], + "tasks": task.get("name"), + "hosts": anatomy_data["app"], + "task_types": task.get("type") + } diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 92976e6151..284e110916 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -13,11 +13,6 @@ import openpype.api from datetime import datetime from openpype.lib.profiles_filtering import filter_profiles from openpype.lib.file_transaction import FileTransaction -from openpype.lib import ( - prepare_template_data, - StringTemplate, - TemplateUnsolved -) log = logging.getLogger(__name__) @@ -619,9 +614,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): } subset_group = instance.data.get("subsetGroup") - if not subset_group: - # todo: move _get_subset_group fallback to its own collector - subset_group = self._get_subset_group(instance) if subset_group: data["subsetGroup"] = subset_group @@ -653,48 +645,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self.log.info("Registered subset: {}".format(subset_name)) return subset - def _get_subset_group(self, instance): - """Look into subset group profiles set by settings. - - Attribute 'subset_grouping_profiles' is defined by OpenPype settings. - """ - # TODO: This logic is better suited for a Collector to just store - # instance.data["subsetGroup"] - # Skip if 'subset_grouping_profiles' is empty - if not self.subset_grouping_profiles: - return None - - # Skip if there is no matching profile - filter_criteria = self.get_profile_filter_criteria(instance) - profile = filter_profiles(self.subset_grouping_profiles, - filter_criteria, - logger=self.log) - if not profile: - return None - - template = profile["template"] - - fill_pairs = prepare_template_data({ - "family": filter_criteria["families"], - "task": filter_criteria["tasks"], - "host": filter_criteria["hosts"], - "subset": instance.data["subset"], - "renderlayer": instance.data.get("renderlayer") - }) - - filled_template = None - try: - filled_template = StringTemplate.format_strict_template( - template, fill_pairs - ) - except (KeyError, TemplateUnsolved): - keys = fill_pairs.keys() - msg = "Subset grouping failed. " \ - "Only {} are expected in Settings".format(','.join(keys)) - self.log.warning(msg) - - return filled_template - def create_version_data(self, instance): """Create the data collection for the version From d7c5ad1f7c9913a39b43087cebbbee7971844f8c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 26 Mar 2022 14:33:37 +0100 Subject: [PATCH 0038/1227] Remove duplicate "source" in families --- openpype/plugins/publish/integrate_new.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 284e110916..08088479d0 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -86,7 +86,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "source", "matchmove", "image", - "source", "assembly", "fbx", "textures", From 8fffc60b5016d63d6fad2b8c3b399537a3736171 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 26 Mar 2022 14:37:23 +0100 Subject: [PATCH 0039/1227] Move remainder of prepare anatomy data to the Collector --- .../plugins/publish/collect_anatomy_context_data.py | 6 ++++++ openpype/plugins/publish/integrate_new.py | 13 ------------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/openpype/plugins/publish/collect_anatomy_context_data.py b/openpype/plugins/publish/collect_anatomy_context_data.py index bd8d9e50c4..346caf6b83 100644 --- a/openpype/plugins/publish/collect_anatomy_context_data.py +++ b/openpype/plugins/publish/collect_anatomy_context_data.py @@ -91,5 +91,11 @@ class CollectAnatomyContextData(pyblish.api.ContextPlugin): } }) + intent = context.data.get("intent") + if intent and isinstance(intent, dict): + intent_value = intent.get("value") + if intent_value: + context_data["intent"] = intent_value + self.log.info("Global anatomy Data collected") self.log.debug(json.dumps(context_data, indent=4)) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 08088479d0..f598c540e5 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -119,10 +119,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if families & set(self.exclude_families): return - # TODO: Avoid the need to do any adjustments to anatomy data - # Best case scenario that's all handled by collectors - self.prepare_anatomy(instance) - file_transactions = FileTransaction(log=self.log) try: self.register(instance, file_transactions) @@ -137,15 +133,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # the try, except. file_transactions.finalize() - def prepare_anatomy(self, instance): - """Prepare anatomy data used to define representation destinations""" - # TODO: This logic should move to CollectAnatomyContextData - intent_value = instance.context.data.get("intent") - if intent_value and isinstance(intent_value, dict): - intent_value = intent_value.get("value") - if intent_value: - instance.data["anatomyData"]["intent"] = intent_value - def get_profile_filter_criteria(self, instance): """Return filter criteria for `filter_profiles`""" # Anatomy data is pre-filled by Collectors From 177e83ec8bf55e28ca551affefc4ac775570fe98 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 26 Mar 2022 14:43:00 +0100 Subject: [PATCH 0040/1227] Restore "published_path" backwards compatibility for IntegrateFtrackInstance on Farm --- openpype/plugins/publish/integrate_new.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index f598c540e5..05cbb357e3 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -532,6 +532,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # Store first transferred destination as published path data # todo: can we remove this? published_path = transfers[0][1] + repre["published_path"] = published_path # Backwards compatibility # todo: `repre` is not the actual `representation` entity # we should simplify/clarify difference between data above From 7189954a3c29ca00139a9a50b58606a3c335de04 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 26 Mar 2022 14:44:19 +0100 Subject: [PATCH 0041/1227] Use `os.path.abspath` instead of `os.path.normpath` when adding a transfer --- openpype/lib/file_transaction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/lib/file_transaction.py b/openpype/lib/file_transaction.py index 57592e297f..1626bec6b6 100644 --- a/openpype/lib/file_transaction.py +++ b/openpype/lib/file_transaction.py @@ -66,8 +66,8 @@ class FileTransaction(object): """Add a new file to transfer queue""" opts = {"mode": mode} - src = os.path.normpath(src) - dst = os.path.normpath(dst) + src = os.path.abspath(src) + dst = os.path.abspath(dst) if dst in self._transfers: queued_src = self._transfers[dst][0] From 8f8b578f0ce660b1c8182ad2486aca21ed1828e2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 26 Mar 2022 19:58:55 +0100 Subject: [PATCH 0042/1227] Move Subset Grouping Profiles settings to Collect Subset Group - This is moved from the Integrate Asset New settings --- .../plugins/publish/collect_subset_group.py | 16 +-- openpype/plugins/publish/integrate_new.py | 1 - .../defaults/project_settings/global.json | 20 ++-- .../schemas/schema_global_publish.json | 101 ++++++++++-------- 4 files changed, 71 insertions(+), 67 deletions(-) diff --git a/openpype/plugins/publish/collect_subset_group.py b/openpype/plugins/publish/collect_subset_group.py index 60c1c04e70..075699e304 100644 --- a/openpype/plugins/publish/collect_subset_group.py +++ b/openpype/plugins/publish/collect_subset_group.py @@ -24,28 +24,22 @@ class CollectSubsetGroup(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder + 0.495 label = "Collect Subset Group" + # Defined in OpenPype settings + subset_grouping_profiles = None + def process(self, instance): """Look into subset group profiles set by settings. Attribute 'subset_grouping_profiles' is defined by OpenPype settings. """ - # TODO: Move this setting to this Collector instead of Integrator - project_settings = instance.context.data["project_settings"] - subset_grouping_profiles = ( - project_settings["global"] - ["publish"] - ["IntegrateAssetNew"] - ["subset_grouping_profiles"] - ) - # Skip if 'subset_grouping_profiles' is empty - if not subset_grouping_profiles: + if not self.subset_grouping_profiles: return # Skip if there is no matching profile filter_criteria = self.get_profile_filter_criteria(instance) - profile = filter_profiles(subset_grouping_profiles, + profile = filter_profiles(self.subset_grouping_profiles, filter_criteria, logger=self.log) if not profile: diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 05cbb357e3..4706d4d093 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -110,7 +110,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # Attributes set by settings template_name_profiles = None - subset_grouping_profiles = None def process(self, instance): diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 30a71b044a..528df111f0 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -20,6 +20,17 @@ ], "skip_hosts_headless_publish": [] }, + "CollectSubsetGroup": { + "subset_grouping_profiles": [ + { + "families": [], + "hosts": [], + "task_types": [], + "tasks": [], + "template": "" + } + ] + }, "ValidateEditorialAssetName": { "enabled": true, "optional": false @@ -193,15 +204,6 @@ "tasks": [], "template_name": "render" } - ], - "subset_grouping_profiles": [ - { - "families": [], - "hosts": [], - "task_types": [], - "tasks": [], - "template": "" - } ] }, "CleanUp": { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 12043d4205..ab968037f6 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -39,6 +39,61 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "CollectSubsetGroup", + "label": "Collect Subset Group", + "is_group": true, + "children": [ + { + "type": "list", + "key": "subset_grouping_profiles", + "label": "Subset grouping profiles", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "label", + "label": "Set all published instances as a part of specific group named according to 'Template'.
Implemented all variants of placeholders [{task},{family},{host},{subset},{renderlayer}]" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "type": "hosts-enum", + "key": "hosts", + "label": "Hosts", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "tasks", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "type": "separator" + }, + { + "type": "text", + "key": "template", + "label": "Template" + } + ] + } + } + ] + }, { "type": "dict", "collapsible": true, @@ -603,52 +658,6 @@ } ] } - }, - { - "type": "list", - "key": "subset_grouping_profiles", - "label": "Subset grouping profiles", - "use_label_wrap": true, - "object_type": { - "type": "dict", - "children": [ - { - "type": "label", - "label": "Set all published instances as a part of specific group named according to 'Template'.
Implemented all variants of placeholders [{task},{family},{host},{subset},{renderlayer}]" - }, - { - "key": "families", - "label": "Families", - "type": "list", - "object_type": "text" - }, - { - "type": "hosts-enum", - "key": "hosts", - "label": "Hosts", - "multiselection": true - }, - { - "key": "task_types", - "label": "Task types", - "type": "task-types-enum" - }, - { - "key": "tasks", - "label": "Task names", - "type": "list", - "object_type": "text" - }, - { - "type": "separator" - }, - { - "type": "text", - "key": "template", - "label": "Template" - } - ] - } } ] }, From 6ff7167d54e8a70441300ba4d21acb5a01eb5071 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 26 Mar 2022 20:09:08 +0100 Subject: [PATCH 0043/1227] Separate get_template_name into its own method + use `self.default_template_name` --- openpype/plugins/publish/integrate_new.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 4706d4d093..c1fa7ccaf2 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -172,14 +172,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): ) ) - # Define publish template name from profiles - filter_criteria = self.get_profile_filter_criteria(instance) - profile = filter_profiles(self.template_name_profiles, - filter_criteria, - logger=self.log) - template_name = "publish" - if profile: - template_name = profile["template_name"] + template_name = self._get_template_name(instance) subset = self.register_subset(instance) @@ -582,6 +575,19 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): return families + def _get_template_name(self, instance): + """Return anatomy template name to use for integration""" + + # Define publish template name from profiles + filter_criteria = self.get_profile_filter_criteria(instance) + profile = filter_profiles(self.template_name_profiles, + filter_criteria, + logger=self.log) + template_name = self.default_template_name + if profile: + template_name = profile["template_name"] + return template_name + def register_subset(self, instance): asset = instance.data.get("assetEntity") subset_name = instance.data["subset"] From 821293d3b855acf2cadd914328a975fd619acd56 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 26 Mar 2022 20:09:31 +0100 Subject: [PATCH 0044/1227] Match comment from Integrator for consistency --- openpype/plugins/publish/collect_subset_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_subset_group.py b/openpype/plugins/publish/collect_subset_group.py index 075699e304..5756563ed3 100644 --- a/openpype/plugins/publish/collect_subset_group.py +++ b/openpype/plugins/publish/collect_subset_group.py @@ -24,7 +24,7 @@ class CollectSubsetGroup(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder + 0.495 label = "Collect Subset Group" - # Defined in OpenPype settings + # Attributes set by settings subset_grouping_profiles = None def process(self, instance): From c3e0162c436a081ccf809cd429f5b828202569d0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 26 Mar 2022 20:11:31 +0100 Subject: [PATCH 0045/1227] Debug log when exclude family was found for the instance --- openpype/plugins/publish/integrate_new.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index c1fa7ccaf2..8a71c0d5aa 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -115,7 +115,10 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # Exclude instances that also contain families from exclude families families = set(self._get_instance_families(instance)) - if families & set(self.exclude_families): + exclude = families & set(self.exclude_families) + if exclude: + self.log.debug("Instance not integrated due to exclude " + "families found: {}".format(", ".join(exclude))) return file_transactions = FileTransaction(log=self.log) From fbdb385e5b855c0762583256311501b78a2ca730 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 26 Mar 2022 20:20:00 +0100 Subject: [PATCH 0046/1227] Perform database registering of Subset and Version in a single Bulk Write --- openpype/plugins/publish/integrate_new.py | 30 +++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 8a71c0d5aa..6f1d745b9a 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -177,11 +177,17 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): template_name = self._get_template_name(instance) - subset = self.register_subset(instance) - - version = self.register_version(instance, subset) + subset, subset_writes = self.register_subset(instance) + version, version_writes = self.register_version(instance, subset) instance.data["versionEntity"] = version + # Bulk write to the database + # todo: Try to avoid writing already until after we've prepared + # representations to allow easier rollback? + io._database[io.Session["AVALON_PROJECT"]].bulk_write( + subset_writes + version_writes + ) + archived_repres = list(io.find({ "parent": version["_id"], "type": "archived_representation" @@ -330,16 +336,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): repre["type"] = "archived_representation" bulk_writes.append(InsertOne(repre)) - # bulk updates - # todo: Try to avoid writing already until after we've prepared - # representations to allow easier rollback? - io._database[io.Session["AVALON_PROJECT"]].bulk_write( - bulk_writes - ) - self.log.info("Registered version: v{0:03d}".format(version["name"])) - return version + return version, bulk_writes def prepare_representation(self, repre, template_name, @@ -612,6 +611,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if subset_group: data["subsetGroup"] = subset_group + bulk_writes = [] if subset is None: # Create a new subset self.log.info("Subset '%s' not found, creating ..." % subset_name) @@ -623,22 +623,22 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "data": data, "parent": asset["_id"] } - io.insert_one(subset) + bulk_writes.append(InsertOne(subset)) else: # Update existing subset data with new data and set in database. # We also change the found subset in-place so we don't need to # re-query the subset afterwards subset["data"].update(data) - io.update_many( + bulk_writes.append(UpdateOne( {"type": "subset", "_id": subset["_id"]}, {"$set": { "data": subset["data"] }} - ) + )) self.log.info("Registered subset: {}".format(subset_name)) - return subset + return subset, bulk_writes def create_version_data(self, instance): """Create the data collection for the version From 1844281c68d0e357eccdc8c277db278ef0651f31 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 26 Mar 2022 20:41:22 +0100 Subject: [PATCH 0047/1227] Match assertion for collection of files (allow no absolute paths) similar to single files --- openpype/plugins/publish/integrate_new.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 6f1d745b9a..ead00452da 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -398,6 +398,10 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): is_sequence_representation = isinstance(files, (list, tuple)) if is_sequence_representation: # Collection of files (sequence) + assert not any(os.path.isabs(fname) for fname in files), ( + "Given file names contain full paths" + ) + # Get the sequence as a collection. The files must be of a single # sequence and have no remainder outside of the collections. collections, remainder = clique.assemble(files, From 8e0161bec7353bff8bc581d4d676b3ba7c090ba8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 28 Mar 2022 15:04:24 +0200 Subject: [PATCH 0048/1227] Also Bulk Write representation changes + more cleanup - Don't create intermediate archived representations - Move writing of Subset + Version to database to just before file transactions - Perform ReplaceOne for version instead of update with "$set" for the full version --- openpype/plugins/publish/integrate_new.py | 166 ++++++++++------------ 1 file changed, 79 insertions(+), 87 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index ead00452da..7a3ca2bdf7 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -6,7 +6,7 @@ import clique import six from bson.objectid import ObjectId -from pymongo import DeleteOne, InsertOne, UpdateOne +from pymongo import DeleteMany, ReplaceOne, InsertOne, UpdateOne import pyblish.api from avalon import io import openpype.api @@ -28,6 +28,11 @@ def get_first_frame_padded(collection): return get_frame_padded(start_frame, padding=collection.padding) +def bulk_write(writes): + """Convenience function to bulk write into active project database""" + return io._database[io.Session["AVALON_PROJECT"]].bulk_write(writes) + + class IntegrateAssetNew(pyblish.api.InstancePlugin): """Resolve any dependency issues @@ -177,21 +182,17 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): template_name = self._get_template_name(instance) - subset, subset_writes = self.register_subset(instance) - version, version_writes = self.register_version(instance, subset) + subset, subset_writes = self.prepare_subset(instance) + version, version_writes = self.prepare_version(instance, subset) instance.data["versionEntity"] = version - # Bulk write to the database - # todo: Try to avoid writing already until after we've prepared - # representations to allow easier rollback? - io._database[io.Session["AVALON_PROJECT"]].bulk_write( - subset_writes + version_writes - ) - - archived_repres = list(io.find({ - "parent": version["_id"], - "type": "archived_representation" - })) + # Get existing representations (if any) + existing_repres_by_name = { + repres["name"].lower(): repres for repres in io.find({ + "parent": version["_id"], + "type": "representation" + }) + } # Prepare all representations prepared_representations = [] @@ -205,7 +206,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # todo: reduce/simplify what is returned from this function prepared = self.prepare_representation(repre, template_name, - archived_repres, + existing_repres_by_name, version, instance_stagingdir, instance) @@ -225,40 +226,70 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): for src, dst in instance.data.get("hardlinks", []): file_transactions.add(src, dst, mode=FileTransaction.MODE_HARDLINK) + # Bulk write to the database + # todo: Can we move this even to after the file transfers? + bulk_write(subset_writes + version_writes) + self.log.info("Subset {subset[name]} and Version {version[name]} " + "written to database..".format(subset=subset, + version=version)) + # Process all file transfers of all integrations now self.log.debug("Integrating source files to destination ...") file_transactions.process() - self.log.debug("Backup files " + self.log.debug("Backed up existing files: " "{}".format(file_transactions.backups)) - self.log.debug("Integrated files " + self.log.debug("Transferred files: " "{}".format(file_transactions.transferred)) # Finalize the representations now the published files are integrated # Get 'files' info for representations and its attached resources - self.log.debug("Retrieving Representation files information ...") + self.log.debug("Retrieving Representation Site Sync information ...") sites = SiteSync.compute_resource_sync_sites( system_settings=instance.context.data["system_settings"], project_settings=instance.context.data["project_settings"] ) - log.debug("final sites:: {}".format(sites)) + self.log.debug("final sites:: {}".format(sites)) anatomy = instance.context.data["anatomy"] - representations = [] + representation_writes = [] + new_repre_names_low = set() for prepared in prepared_representations: transfers = prepared["transfers"] representation = prepared["representation"] representation["files"] = self.get_files_info( transfers, sites, anatomy ) - representations.append(representation) - # Remove all archived representations - if archived_repres: - repre_ids_to_remove = [repre["_id"] for repre in archived_repres] - io.delete_many({"_id": {"$in": repre_ids_to_remove}}) + # Set up representation for writing to the database. Since + # we *might* be overwriting an existing entry if the version + # already existed we'll use ReplaceOnce with `upsert=True` + representation_writes.append(ReplaceOne( + filter={"_id": representation["_id"]}, + replacement=representation, + upsert=True + )) - # Write the new representations to the database - io.insert_many(representations) + new_repre_names_low.add(representation["name"].lower()) + + # Delete any existing representations that didn't get any new data + # if the instance is not set to append mode + if not instance.data.get("append", False): + delete_names = set() + for name, existing_repres in existing_repres_by_name.items(): + if name not in new_repre_names_low: + # We add the exact representation name because `name` is + # lowercase for name matching only and not in the database + delete_names.add(existing_repres["name"]) + if delete_names: + representation_writes.append(DeleteMany( + filter={ + "parent": version["_id"], + "name": {"$in": list(delete_names)} + } + )) + + # Write representations to the database + bulk_write(representation_writes) # Backwards compatibility # todo: can we avoid the need to store this? @@ -267,12 +298,11 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): } self.log.info("Registered {} representations" - "".format(len(representations))) + "".format(len(prepared_representations))) - def register_version(self, instance, subset): + def prepare_version(self, instance, subset): version_number = instance.data["version"] - self.log.debug("Version: v{0:03d}".format(version_number)) version = { "schema": "openpype:version-3.0", @@ -288,61 +318,26 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): 'name': version_number }) - bulk_writes = [] - if existing_version is None: + if existing_version: + self.log.debug("Updating existing version ...") + version["_id"] = existing_version["_id"] + else: self.log.debug("Creating new version ...") version["_id"] = ObjectId() - bulk_writes.append(InsertOne(version)) - else: - self.log.debug("Updating existing version ...") - # Check if instance have set `append` mode which cause that - # only replicated representations are set to archive - append_repres = instance.data.get("append", False) - # Update version data - version_id = existing_version['_id'] - bulk_writes.append(UpdateOne({ - '_id': version_id - }, { - '$set': version - })) + bulk_writes = [ReplaceOne( + filter={"_id": version["_id"]}, + replacement=version, + upsert=True + )] - # Instead of directly writing and querying we reproduce what - # the resulting version would look like so we can hold off making - # changes to the database to avoid the need for 'rollback' - version = copy.deepcopy(version) - version["_id"] = existing_version["_id"] - - # Find representations of existing version and archive them - repres = instance.data.get("representations", []) - new_repre_names_low = [_repre["name"].lower() for _repre in repres] - current_repres = io.find({ - "type": "representation", - "parent": version_id - }) - for repre in current_repres: - if append_repres: - # archive only duplicated representations - if repre["name"].lower() not in new_repre_names_low: - continue - # Representation must change type, - # `_id` must be stored to other key and replaced with new - # - that is because new representations should have same ID - repre_id = repre["_id"] - bulk_writes.append(DeleteOne({"_id": repre_id})) - - repre["orig_id"] = repre_id - repre["_id"] = ObjectId() - repre["type"] = "archived_representation" - bulk_writes.append(InsertOne(repre)) - - self.log.info("Registered version: v{0:03d}".format(version["name"])) + self.log.info("Prepared version: v{0:03d}".format(version["name"])) return version, bulk_writes def prepare_representation(self, repre, template_name, - archived_repres, + existing_repres_by_name, version, instance_stagingdir, instance): @@ -516,15 +511,12 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if repre.get("udim"): repre_context["udim"] = repre.get("udim") # store list - # Define representation id - repre_id = ObjectId() - # Use previous representation's id if there is a name match - repre_name_lower = repre["name"].lower() - for _archived_repres in archived_repres: - if repre_name_lower == _archived_repres["name"].lower(): - repre_id = _archived_repres["orig_id"] - break + existing = existing_repres_by_name.get(repre["name"].lower()) + if existing: + repre_id = existing["_id"] + else: + repre_id = ObjectId() # Backwards compatibility: # Store first transferred destination as published path data @@ -594,7 +586,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): template_name = profile["template_name"] return template_name - def register_subset(self, instance): + def prepare_subset(self, instance): asset = instance.data.get("assetEntity") subset_name = instance.data["subset"] self.log.debug("Subset: {}".format(subset_name)) @@ -631,7 +623,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): else: # Update existing subset data with new data and set in database. - # We also change the found subset in-place so we don't need to + # We also change the found subset in-place so we don't need to # re-query the subset afterwards subset["data"].update(data) bulk_writes.append(UpdateOne( @@ -641,7 +633,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): }} )) - self.log.info("Registered subset: {}".format(subset_name)) + self.log.info("Prepared subset: {}".format(subset_name)) return subset, bulk_writes def create_version_data(self, instance): From ba2c6e6f084e5829f32250735f13f045cabca800 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 28 Mar 2022 15:43:57 +0200 Subject: [PATCH 0049/1227] Fix class type --- openpype/plugins/publish/collect_subset_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_subset_group.py b/openpype/plugins/publish/collect_subset_group.py index 5756563ed3..56cd7de94e 100644 --- a/openpype/plugins/publish/collect_subset_group.py +++ b/openpype/plugins/publish/collect_subset_group.py @@ -17,7 +17,7 @@ from openpype.lib import ( ) -class CollectSubsetGroup(pyblish.api.ContextPlugin): +class CollectSubsetGroup(pyblish.api.InstancePlugin): """Collect Subset Group for publish.""" # Run after CollectAnatomyInstanceData From ef0210a98aba43e8a17ba23dc0a1cf57ad38034f Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Mon, 28 Mar 2022 16:48:01 +0200 Subject: [PATCH 0050/1227] Error Handling, ffmpeg fixes --- igniter/3} | 0 .../plugins/publish/extract_review_slate.py | 172 +++++++++--------- 2 files changed, 90 insertions(+), 82 deletions(-) create mode 100644 igniter/3} diff --git a/igniter/3} b/igniter/3} new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index d6857c7915..2854108022 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -1,4 +1,5 @@ import os +import shutil import openpype.api import pyblish from openpype.lib import ( @@ -73,74 +74,72 @@ class ExtractReviewSlate(openpype.api.Extractor): os.path.normpath(stagingdir), repre["files"]) self.log.debug("__ input_path: {}".format(input_path)) - video_streams = get_ffprobe_streams( + streams = get_ffprobe_streams( input_path, self.log ) - - # Try to find first stream with defined 'width' and 'height' - # - this is to avoid order of streams where audio can be as first - # - there may be a better way (checking `codec_type`?) - input_width = None - input_height = None - input_timecode = None - input_frame_rate = None - input_audio = False - audio_channels = None - audio_sample_rate = None - audio_channel_layout = None - for stream in video_streams: - self.log.debug("__ ffprobe: {}".format(stream)) + # get video metadata + for stream in streams: + input_timecode = None + input_width = None + input_height = None + input_frame_rate = None if "codec_type" in stream: if stream["codec_type"] == "video": - if stream["tags"]["timecode"]: - # get timecode of the first frame - input_timecode = stream["tags"]["timecode"] - self.log.debug("__Video Timecode : {}".format( - input_timecode)) + self.log.debug("__Ffprobe Video: {}".format(stream)) + tags = stream.get("tags") or {} + input_timecode = tags.get("timecode") or "" if "width" in stream and "height" in stream: - input_width = int(stream["width"]) - input_height = int(stream["height"]) + input_width = int(stream.get("width")) + input_height = int(stream.get("height")) if "r_frame_rate" in stream: # get frame rate in a form of # x/y, like 24000/1001 for 23.976 - input_frame_rate = str(stream["r_frame_rate"]) - if stream["codec_type"] == "audio": - # get audio details - # for generating silent audio track for slate - if stream["channels"]: - audio_channels = str(stream["channels"]) - if stream["sample_rate"]: - audio_sample_rate = stream["sample_rate"] - if stream["channel_layout"]: - audio_channel_layout = stream["channel_layout"] - # calculate duration of one frame in seconds - if input_frame_rate: - # it is halved to make sure audio will be shorter then video - one_frame_duration = str( - float(1.0 / eval(input_frame_rate)) / 2 - ) - else: - # same sane default (1 frame @ 25 fps) - one_frame_duration = 0.04 - self.log.debug( - "One frame duration is {} sec".format(one_frame_duration)) - # confirm we gathered all needed audio parameters - if audio_channel_layout: - if audio_sample_rate: - if audio_channels: - input_audio = True - self.log.debug("__Audio : channels {}".format( - audio_channels)) - self.log.debug("__Audio : sample_rate {}".format( - audio_sample_rate)) - self.log.debug("__Audio : channel_layout {}".format( - audio_channel_layout)) - + input_frame_rate = str(stream.get("r_frame_rate")) + if ( + input_timecode + and input_width + and input_height + and input_frame_rate + ): + break # Raise exception of any stream didn't define input resolution if input_width is None: raise AssertionError(( "FFprobe couldn't read resolution from input file: \"{}\"" ).format(input_path)) + # Get audio metadata + for stream in streams: + audio_channels = None + audio_sample_rate = None + audio_channel_layout = None + input_audio = False + if stream["codec_type"] == "audio": + self.log.debug("__Ffprobe Audio: {}".format(stream)) + if stream["channels"]: + audio_channels = str(stream.get("channels")) + if stream["sample_rate"]: + audio_sample_rate = str(stream.get("sample_rate")) + if stream["channel_layout"]: + audio_channel_layout = str(stream.get("channel_layout")) + if ( + audio_channels + and audio_sample_rate + and audio_channel_layout + ): + input_audio = True + break + # Get duration of one frame in micro seconds + one_frame_duration = "40000us" + if input_frame_rate: + items = input_frame_rate.split("/") + if len(items) == 1: + one_frame_duration = float(1.0) / float(items[0]) + elif len(items) == 2: + one_frame_duration = float(items[1]) / float(items[0]) + one_frame_duration *= 1000000 + one_frame_duration = str(int(one_frame_duration))+"us" + self.log.debug( + "One frame duration is {}".format(one_frame_duration)) # values are set in ExtractReview if use_legacy_code: @@ -192,7 +191,16 @@ class ExtractReviewSlate(openpype.api.Extractor): input_args = [] output_args = [] - # if input has audio, add silent audio to the slate + # preset's input data + if use_legacy_code: + input_args.extend(repre["_profile"].get('input', [])) + else: + input_args.extend(repre["outputDef"].get('input', [])) + + input_args.append("-loop 1 -i {}".format( + openpype.lib.path_to_subprocess_arg(slate_path) + )) + # if input has an audio, add silent audio to the slate if input_audio: input_args.extend( ["-f lavfi -i anullsrc=r={}:cl={}:d={}".format( @@ -201,16 +209,9 @@ class ExtractReviewSlate(openpype.api.Extractor): one_frame_duration )] ) - # preset's input data - if use_legacy_code: - input_args.extend(repre["_profile"].get('input', [])) - else: - input_args.extend(repre["outputDef"].get('input', [])) - # enforce framerate before -i - input_args.append("-framerate {} -i {}".format( - input_frame_rate, - path_to_subprocess_arg(slate_path) - )) + + input_args.extend(["-r {}".format(input_frame_rate)]) + input_args.extend(["-frames:v 1"]) # add timecode from source to the slate, substract one frame if input_timecode: offset_timecode = self._tc_offset( @@ -218,9 +219,14 @@ class ExtractReviewSlate(openpype.api.Extractor): framerate=fps, frame_offset=-1 ) - self.log.debug("Timecode: `{}`".format(offset_timecode)) - input_args.extend(["-timecode {}".format(offset_timecode)]) - + self.log.debug("Slate Timecode: `{}`".format( + offset_timecode + )) + if offset_timecode: + input_args.extend(["-timecode {}".format(offset_timecode)]) + else: + # fall back to input timecode if offset fails + input_args.extend(["-timecode {}".format(input_timecode)]) if use_legacy_code: codec_args = repre["_profile"].get('codec', []) output_args.extend(codec_args) @@ -283,10 +289,6 @@ class ExtractReviewSlate(openpype.api.Extractor): # add it to output_args output_args.insert(0, vf_back) - # use video duration for silent audio duration - if input_audio: - output_args.append("-shortest") - # overrides output file output_args.append("-y") @@ -334,17 +336,23 @@ class ExtractReviewSlate(openpype.api.Extractor): "-f", "concat", "-safe", "0", "-i", conc_text_path, - "-c", "copy", + "-c:v", "copy", output_path ] - - # ffmpeg concat subprocess - self.log.debug( - "Executing concat: {}".format(" ".join(concat_args)) - ) - openpype.api.run_subprocess( - concat_args, logger=self.log - ) + if not input_audio: + # ffmpeg concat subprocess + self.log.debug( + "Executing concat: {}".format(" ".join(concat_args)) + ) + openpype.api.run_subprocess( + concat_args, logger=self.log + ) + else: + self.log.warning("Audio found. Creating slate with audio" + " is not supported at this time. Outputing slate-less" + ":\n{}".format(input_file)) + # skip concatenating slate, use slate-less file instead + shutil.copyfile(input_path, output_path) self.log.debug("__ repre[tags]: {}".format(repre["tags"])) repre_update = { From 027081700e47ef4c5eddee20cb30d885b9a3c8d7 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Mon, 28 Mar 2022 19:14:46 +0200 Subject: [PATCH 0051/1227] hound --- openpype/plugins/publish/extract_review_slate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 2854108022..d2a7b9a12a 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -120,7 +120,8 @@ class ExtractReviewSlate(openpype.api.Extractor): if stream["sample_rate"]: audio_sample_rate = str(stream.get("sample_rate")) if stream["channel_layout"]: - audio_channel_layout = str(stream.get("channel_layout")) + audio_channel_layout = str( + stream.get("channel_layout")) if ( audio_channels and audio_sample_rate From 90e29c6f521bd78d84444c357171b6f7b499a8bf Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Mon, 28 Mar 2022 19:35:25 +0200 Subject: [PATCH 0052/1227] hound2 --- openpype/plugins/publish/extract_review_slate.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index d2a7b9a12a..037535342d 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -138,7 +138,7 @@ class ExtractReviewSlate(openpype.api.Extractor): elif len(items) == 2: one_frame_duration = float(items[1]) / float(items[0]) one_frame_duration *= 1000000 - one_frame_duration = str(int(one_frame_duration))+"us" + one_frame_duration = str(int(one_frame_duration)) + "us" self.log.debug( "One frame duration is {}".format(one_frame_duration)) @@ -199,8 +199,7 @@ class ExtractReviewSlate(openpype.api.Extractor): input_args.extend(repre["outputDef"].get('input', [])) input_args.append("-loop 1 -i {}".format( - openpype.lib.path_to_subprocess_arg(slate_path) - )) + openpype.lib.path_to_subprocess_arg(slate_path))) # if input has an audio, add silent audio to the slate if input_audio: input_args.extend( @@ -350,10 +349,10 @@ class ExtractReviewSlate(openpype.api.Extractor): ) else: self.log.warning("Audio found. Creating slate with audio" - " is not supported at this time. Outputing slate-less" - ":\n{}".format(input_file)) + " is not supported at this time. Outputing slate-less" + ":\n{}".format(input_file)) # skip concatenating slate, use slate-less file instead - shutil.copyfile(input_path, output_path) + shutil.copyfile(input_path, output_path) self.log.debug("__ repre[tags]: {}".format(repre["tags"])) repre_update = { From e6665e579ee069b30a02b1034e53d48c85553761 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 28 Mar 2022 20:32:46 +0200 Subject: [PATCH 0053/1227] Restructure code and more cleanup --- openpype/plugins/publish/integrate_new.py | 250 +++++++++++----------- 1 file changed, 123 insertions(+), 127 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 7a3ca2bdf7..6401806394 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -17,6 +17,21 @@ from openpype.lib.file_transaction import FileTransaction log = logging.getLogger(__name__) +def get_instance_families(instance): + """Get all families of the instance""" + # todo: move this to lib? + family = instance.data.get("family") + families = [] + if family: + families.append(family) + + for _family in (instance.data.get("families") or []): + if _family not in families: + families.append(_family) + + return families + + def get_frame_padded(frame, padding): """Return frame number as string with `padding` amount of padded zeros""" return "{frame:0{padding}d}".format(padding=padding, frame=frame) @@ -119,7 +134,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): def process(self, instance): # Exclude instances that also contain families from exclude families - families = set(self._get_instance_families(instance)) + families = set(get_instance_families(instance)) exclude = families & set(self.exclude_families) if exclude: self.log.debug("Instance not integrated due to exclude " @@ -140,22 +155,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # the try, except. file_transactions.finalize() - def get_profile_filter_criteria(self, instance): - """Return filter criteria for `filter_profiles`""" - # Anatomy data is pre-filled by Collectors - anatomy_data = instance.data["anatomyData"] - - # Task can be optional in anatomy data - task = anatomy_data.get("task", {}) - - # Return filter criteria - return { - "families": anatomy_data["family"], - "tasks": task.get("name"), - "hosts": anatomy_data["app"], - "task_types": task.get("type") - } - def register(self, instance, file_transactions): instance_stagingdir = instance.data.get("stagingDir") @@ -171,16 +170,16 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "@ {0}".format(instance_stagingdir) ) - # Ensure at least one file is set up for transfer in staging dir. + # Ensure at least one representation is set up for registering. repres = instance.data.get("representations") - assert repres, "Instance has no files to transfer" + assert repres, "Instance has representations data" assert isinstance(repres, (list, tuple)), ( - "Instance 'files' must be a list, got: {0} {1}".format( + "Instance 'repres' must be a list, got: {0} {1}".format( str(type(repres)), str(repres) ) ) - template_name = self._get_template_name(instance) + template_name = self.get_template_name(instance) subset, subset_writes = self.prepare_subset(instance) version, version_writes = self.prepare_version(instance, subset) @@ -300,6 +299,56 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self.log.info("Registered {} representations" "".format(len(prepared_representations))) + def prepare_subset(self, instance): + asset = instance.data.get("assetEntity") + subset_name = instance.data["subset"] + self.log.debug("Subset: {}".format(subset_name)) + + # Get existing subset if it exists + subset = io.find_one({ + "type": "subset", + "parent": asset["_id"], + "name": subset_name + }) + + # Define subset data + data = { + "families": get_instance_families(instance) + } + + subset_group = instance.data.get("subsetGroup") + if subset_group: + data["subsetGroup"] = subset_group + + bulk_writes = [] + if subset is None: + # Create a new subset + self.log.info("Subset '%s' not found, creating ..." % subset_name) + subset = { + "_id": ObjectId(), + "schema": "openpype:subset-3.0", + "type": "subset", + "name": subset_name, + "data": data, + "parent": asset["_id"] + } + bulk_writes.append(InsertOne(subset)) + + else: + # Update existing subset data with new data and set in database. + # We also change the found subset in-place so we don't need to + # re-query the subset afterwards + subset["data"].update(data) + bulk_writes.append(UpdateOne( + {"type": "subset", "_id": subset["_id"]}, + {"$set": { + "data": subset["data"] + }} + )) + + self.log.info("Prepared subset: {}".format(subset_name)) + return subset, bulk_writes + def prepare_version(self, instance, subset): version_number = instance.data["version"] @@ -559,91 +608,14 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "published_files": [transfer[1] for transfer in transfers] } - def _get_instance_families(self, instance): - """Get all families of the instance""" - # todo: move this to lib? - family = instance.data.get("family") - families = [] - if family: - families.append(family) - - for _family in (instance.data.get("families") or []): - if _family not in families: - families.append(_family) - - return families - - def _get_template_name(self, instance): - """Return anatomy template name to use for integration""" - - # Define publish template name from profiles - filter_criteria = self.get_profile_filter_criteria(instance) - profile = filter_profiles(self.template_name_profiles, - filter_criteria, - logger=self.log) - template_name = self.default_template_name - if profile: - template_name = profile["template_name"] - return template_name - - def prepare_subset(self, instance): - asset = instance.data.get("assetEntity") - subset_name = instance.data["subset"] - self.log.debug("Subset: {}".format(subset_name)) - - # Get existing subset if it exists - subset = io.find_one({ - "type": "subset", - "parent": asset["_id"], - "name": subset_name - }) - - # Define subset data - data = { - "families": self._get_instance_families(instance) - } - - subset_group = instance.data.get("subsetGroup") - if subset_group: - data["subsetGroup"] = subset_group - - bulk_writes = [] - if subset is None: - # Create a new subset - self.log.info("Subset '%s' not found, creating ..." % subset_name) - subset = { - "_id": ObjectId(), - "schema": "openpype:subset-3.0", - "type": "subset", - "name": subset_name, - "data": data, - "parent": asset["_id"] - } - bulk_writes.append(InsertOne(subset)) - - else: - # Update existing subset data with new data and set in database. - # We also change the found subset in-place so we don't need to - # re-query the subset afterwards - subset["data"].update(data) - bulk_writes.append(UpdateOne( - {"type": "subset", "_id": subset["_id"]}, - {"$set": { - "data": subset["data"] - }} - )) - - self.log.info("Prepared subset: {}".format(subset_name)) - return subset, bulk_writes - def create_version_data(self, instance): - """Create the data collection for the version + """Create the data dictionary for the version Args: instance: the current instance being published Returns: - dict: the required information with instance.data as key + dict: the required information for version["data"] """ context = instance.context @@ -658,7 +630,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self.log.debug("Source: {}".format(source)) version_data = { - "families": self._get_instance_families(instance), + "families": get_instance_families(instance), "time": context.data["time"], "author": context.data["user"], "source": source, @@ -692,28 +664,52 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): return version_data - def main_family_from_instance(self, instance): - """Returns main family of entered instance.""" - return self._get_instance_families(instance)[0] + def get_template_name(self, instance): + """Return anatomy template name to use for integration""" + + # Define publish template name from profiles + filter_criteria = self.get_profile_filter_criteria(instance) + profile = filter_profiles(self.template_name_profiles, + filter_criteria, + logger=self.log) + template_name = self.default_template_name + if profile: + template_name = profile["template_name"] + return template_name + + def get_profile_filter_criteria(self, instance): + """Return filter criteria for `filter_profiles`""" + # Anatomy data is pre-filled by Collectors + anatomy_data = instance.data["anatomyData"] + + # Task can be optional in anatomy data + task = anatomy_data.get("task", {}) + + # Return filter criteria + return { + "families": anatomy_data["family"], + "tasks": task.get("name"), + "hosts": anatomy_data["app"], + "task_types": task.get("type") + } def get_rootless_path(self, anatomy, path): - """ Returns, if possible, path without absolute portion from host - (eg. 'c:\' or '/opt/..') - This information is host dependent and shouldn't be captured. - Example: - 'c:/projects/MyProject1/Assets/publish...' > - '{root}/MyProject1/Assets...' + """Returns, if possible, path without absolute portion from root + (eg. 'c:\' or '/opt/..') + + This information is platform dependent and shouldn't be captured. + Example: + 'c:/projects/MyProject1/Assets/publish...' > + '{root}/MyProject1/Assets...' Args: - anatomy: anatomy part from instance - path: path (absolute) + anatomy: anatomy part from instance + path: path (absolute) Returns: - path: modified path if possible, or unmodified path - + warning logged + path: modified path if possible, or unmodified path + + warning logged """ - success, rootless_path = ( - anatomy.find_root_template_from_path(path) - ) + success, rootless_path = anatomy.find_root_template_from_path(path) if success: path = rootless_path else: @@ -731,9 +727,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): Context info. Arguments: - instance: the current instance being published - integrated_file_sizes: dictionary of destination path (absolute) - and its file size + transfers (list): List of transferred files (source, destination) + sites (list): array of published locations + anatomy: anatomy part from instance Returns: output_resources: array of dictionaries to be added to 'files' key in representation @@ -749,14 +745,14 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): """ Prepare information for one file (asset or resource) Arguments: - path: destination url of published file (rootless) - size(optional): size of file in bytes - file_hash(optional): hash of file for synchronization validation - sites(optional): array of published locations, - [ {'name':'studio', 'created_dt':date} by default - keys expected ['studio', 'site1', 'gdrive1'] + path: destination url of published file + anatomy: anatomy part from instance + sites: array of published locations, + [ {'name':'studio', 'created_dt':date} by default + keys expected ['studio', 'site1', 'gdrive1'] + Returns: - rec: dictionary with filled info + dict: file info dictionary """ file_hash = openpype.api.source_hash(path) From 2777c36eb52e7390b15accc93c9b9a9a771ba21d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 28 Mar 2022 20:34:16 +0200 Subject: [PATCH 0054/1227] Rely on `instance.data["fps"] over `context.data["fps"]` if available --- openpype/plugins/publish/integrate_new.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 6401806394..00922b0ed3 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -636,9 +636,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "source": source, "comment": context.data.get("comment"), "machine": context.data.get("machine"), - "fps": context.data.get( - "fps", instance.data.get("fps") - ) + "fps": instance.data.get("fps", context.data.get("fps")) } intent_value = context.data.get("intent") From 13f6b03637e7d696ad45418856c6576883c9892f Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Mon, 28 Mar 2022 20:38:47 +0200 Subject: [PATCH 0055/1227] hound3 --- openpype/plugins/publish/extract_review_slate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 037535342d..c8ee2ec7ed 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -348,7 +348,8 @@ class ExtractReviewSlate(openpype.api.Extractor): concat_args, logger=self.log ) else: - self.log.warning("Audio found. Creating slate with audio" + self.log.warning( + "Audio found. Creating slate with audio" " is not supported at this time. Outputing slate-less" ":\n{}".format(input_file)) # skip concatenating slate, use slate-less file instead From 0db0fa0de9fe085f48f62d2b1ffe766edf95e897 Mon Sep 17 00:00:00 2001 From: Jiri Sindelar <45896205+jrsndl@users.noreply.github.com> Date: Tue, 29 Mar 2022 09:27:33 +0200 Subject: [PATCH 0056/1227] stray empty file? --- igniter/3} | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 igniter/3} diff --git a/igniter/3} b/igniter/3} deleted file mode 100644 index e69de29bb2..0000000000 From add4958d4c9078b6ecad131f6e40beb66ecdd348 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 29 Mar 2022 09:43:27 +0200 Subject: [PATCH 0057/1227] Fix message --- openpype/plugins/publish/integrate_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 00922b0ed3..f6aa720dbb 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -172,7 +172,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # Ensure at least one representation is set up for registering. repres = instance.data.get("representations") - assert repres, "Instance has representations data" + assert repres, "Instance has no representations data" assert isinstance(repres, (list, tuple)), ( "Instance 'repres' must be a list, got: {0} {1}".format( str(type(repres)), str(repres) From 77b5c24370b61615b2380fdc464137d3eba13ab9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 29 Mar 2022 11:44:30 +0200 Subject: [PATCH 0058/1227] Fix message --- openpype/plugins/publish/integrate_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index f6aa720dbb..020b1d2b9c 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -174,7 +174,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): repres = instance.data.get("representations") assert repres, "Instance has no representations data" assert isinstance(repres, (list, tuple)), ( - "Instance 'repres' must be a list, got: {0} {1}".format( + "Instance 'representations' must be a list, got: {0} {1}".format( str(type(repres)), str(repres) ) ) From 127f19873f876d58a2c954c4a56c73ddd4d4d4af Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 29 Mar 2022 11:58:52 +0200 Subject: [PATCH 0059/1227] Streamlining some code, optimize some database queries with projection --- openpype/plugins/publish/integrate_new.py | 36 ++++++++++++----------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 020b1d2b9c..d869a1b6be 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -187,10 +187,14 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # Get existing representations (if any) existing_repres_by_name = { - repres["name"].lower(): repres for repres in io.find({ - "parent": version["_id"], - "type": "representation" - }) + repres["name"].lower(): repres for repres in io.find( + { + "parent": version["_id"], + "type": "representation" + }, + # Only care about id and name of existing representations + projection={"_id": True, "name": True} + ) } # Prepare all representations @@ -239,16 +243,17 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "{}".format(file_transactions.backups)) self.log.debug("Transferred files: " "{}".format(file_transactions.transferred)) - - # Finalize the representations now the published files are integrated - # Get 'files' info for representations and its attached resources self.log.debug("Retrieving Representation Site Sync information ...") + + # Get the accessible sites for Site Sync sites = SiteSync.compute_resource_sync_sites( system_settings=instance.context.data["system_settings"], project_settings=instance.context.data["project_settings"] ) - self.log.debug("final sites:: {}".format(sites)) + self.log.debug("Site Sync Sites: {}".format(sites)) + # Finalize the representations now the published files are integrated + # Get 'files' info for representations and its attached resources anatomy = instance.context.data["anatomy"] representation_writes = [] new_repre_names_low = set() @@ -365,7 +370,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): 'type': 'version', 'parent': subset["_id"], 'name': version_number - }) + }, projection={"_id": True}) if existing_version: self.log.debug("Updating existing version ...") @@ -576,7 +581,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # todo: `repre` is not the actual `representation` entity # we should simplify/clarify difference between data above # and the actual representation entity for the database - data = repre.get("data") or {} + data = repre.get("data", {}) data.update({'path': published_path, 'template': template}) representation = { "_id": repre_id, @@ -664,16 +669,15 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): def get_template_name(self, instance): """Return anatomy template name to use for integration""" - # Define publish template name from profiles filter_criteria = self.get_profile_filter_criteria(instance) profile = filter_profiles(self.template_name_profiles, filter_criteria, logger=self.log) - template_name = self.default_template_name if profile: - template_name = profile["template_name"] - return template_name + return profile["template_name"] + else: + return self.default_template_name def get_profile_filter_criteria(self, instance): """Return filter criteria for `filter_profiles`""" @@ -752,13 +756,11 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): Returns: dict: file info dictionary """ - file_hash = openpype.api.source_hash(path) - return { "_id": ObjectId(), "path": self.get_rootless_path(anatomy, path), "size": os.path.getsize(path), - "hash": file_hash, + "hash": openpype.api.source_hash(path), "sites": sites } From 0c2c60d37b05411193acf8c60f6a2562463ba558 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 29 Mar 2022 12:23:24 +0200 Subject: [PATCH 0060/1227] Unify usage of `clique.assemble` --- openpype/plugins/publish/integrate_new.py | 60 ++++++++++++++--------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index d869a1b6be..1ceb99e9fe 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -17,6 +17,41 @@ from openpype.lib.file_transaction import FileTransaction log = logging.getLogger(__name__) +def assemble(files): + """Convenience `clique.assemble` wrapper for files of a single collection. + + Unlike `clique.assemble` this wrapper does not allow more than a single + Collection nor any remainder files. Errors will be raised when not only + a single collection is assembled. + + Returns: + clique.Collection: A single sequence Collection + + Raises: + ValueError: Error is raised when files do not result in a single + collected Collection. + + """ + # todo: move this to lib? + # Get the sequence as a collection. The files must be of a single + # sequence and have no remainder outside of the collections. + patterns = [clique.PATTERNS["frames"]] + collections, remainder = clique.assemble(files, + minimum_items=1, + patterns=patterns) + if not collections: + raise ValueError("No collections found in files: " + "{}".format(files)) + if remainder: + raise ValueError("Files found not detected as part" + " of a sequence: {}".format(remainder)) + if len(collections) > 1: + raise ValueError("Files in sequence are not part of a" + " single sequence collection: " + "{}".format(collections)) + return collections[0] + + def get_instance_families(instance): """Get all families of the instance""" # todo: move this to lib? @@ -451,21 +486,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "Given file names contain full paths" ) - # Get the sequence as a collection. The files must be of a single - # sequence and have no remainder outside of the collections. - collections, remainder = clique.assemble(files, - minimum_items=1) - if not collections: - raise ValueError("No collections found in files: " - "{}".format(files)) - if remainder: - raise ValueError("Files found not detected as part" - " of a sequence: {}".format(remainder)) - if len(collections) > 1: - raise ValueError("Files in sequence are not part of a" - " single sequence collection: " - "{}".format(collections)) - src_collection = collections[0] + src_collection = assemble(files) # If the representation has `frameStart` set it renumbers the # frame indices of the published collection. It will start from @@ -512,14 +533,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): template_filled = anatomy_filled[template_name]["path"] repre_context = template_filled.used_values self.log.debug("Template filled: {}".format(str(template_filled))) - dst_collections, _remainder = clique.assemble( - [os.path.normpath(template_filled)], - minimum_items=1, - patterns=[clique.PATTERNS["frames"]] - ) - assert not _remainder, "This is a bug" - assert len(dst_collections) == 1, "This is a bug" - dst_collection = dst_collections[0] + dst_collection = assemble([os.path.normpath(template_filled)]) # Update the destination indexes and padding dst_collection.indexes.clear() From 44d6199a9e4ea7342fb2ef6bd583e0e373da2545 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 29 Mar 2022 12:28:47 +0200 Subject: [PATCH 0061/1227] Organize single file code more like sequence file code --- openpype/plugins/publish/integrate_new.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 1ceb99e9fe..1592789390 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -551,21 +551,24 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): else: # Single file - template_data.pop("frame", None) fname = files assert not os.path.isabs(fname), ( "Given file name is a full path" ) - # Store used frame value to template data + + # Manage anatomy template data + template_data.pop("frame", None) if repre.get("udim"): template_data["udim"] = repre["udim"][0] - src = os.path.join(stagingdir, fname) + + # Construct destination filepath from template anatomy_filled = anatomy.format(template_data) template_filled = anatomy_filled[template_name]["path"] repre_context = template_filled.used_values dst = os.path.normpath(template_filled) # Single file transfer + src = os.path.join(stagingdir, fname) transfers = [(src, dst)] for key in self.db_representation_context_keys: From a2a77b8a2099b902e01816ec66a2f308e43004d1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 29 Mar 2022 12:51:08 +0200 Subject: [PATCH 0062/1227] Cleanup `get_files_info` docstring --- openpype/plugins/publish/integrate_new.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 1592789390..0ee2a6286f 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -739,11 +739,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): return path def get_files_info(self, transfers, sites, anatomy): - """ Prepare 'files' portion for attached resources and main asset. - Combining records from 'transfers' and 'hardlinks' parts from - instance. - All attached resources should be added, currently without - Context info. + """Prepare 'files' info portion for representations. Arguments: transfers (list): List of transferred files (source, destination) From 6fe6841c996594871a535daf2c21914e5cc32575 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 29 Mar 2022 13:18:04 +0200 Subject: [PATCH 0063/1227] Capture edge case where all "representations" are tagged for delete --- openpype/plugins/publish/integrate_new.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 0ee2a6286f..80e1909687 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -255,6 +255,12 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): prepared_representations.append(prepared) + if not prepared_representations: + # Even though we check `instance.data["representations"]` earlier + # this could still happen if all representations were tagged with + # "delete" and thus are skipped for integration + raise RuntimeError("No representations prepared to publish.") + # Each instance can also have pre-defined transfers not explicitly # part of a representation - like texture resources used by a # .ma representation. Those destination paths are pre-defined, etc. From a7a908d1348381ab0c4df9c29861d7c02be635cb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 29 Mar 2022 13:20:51 +0200 Subject: [PATCH 0064/1227] Improve docstring --- openpype/plugins/publish/integrate_new.py | 39 +++++++++++------------ 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 80e1909687..8e666f3400 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -84,29 +84,26 @@ def bulk_write(writes): class IntegrateAssetNew(pyblish.api.InstancePlugin): - """Resolve any dependency issues + """Register publish in the database and transfer files to destinations. - This plug-in resolves any paths which, if not updated might break - the published file. + Steps: + 1) Register the subset and version + 2) Transfer the representation files to the destination + 3) Register the representation - The order of families is important, when working with lookdev you want to - first publish the texture, update the texture paths in the nodes and then - publish the shading network. Same goes for file dependent assets. - - Requirements for instance to be correctly integrated - - instance.data['representations'] - must be a list and each member - must be a dictionary with following data: - 'files': list of filenames for sequence, string for single file. - Only the filename is allowed, without the folder path. - 'stagingDir': "path/to/folder/with/files" - 'name': representation name (usually the same as extension) - 'ext': file extension - optional data - "frameStart" - "frameEnd" - 'fps' - "data": additional metadata for each representation. + Requires: + instance.data['representations'] - must be a list and each member + must be a dictionary with following data: + 'files': list of filenames for sequence, string for single file. + Only the filename is allowed, without the folder path. + 'stagingDir': "path/to/folder/with/files" + 'name': representation name (usually the same as extension) + 'ext': file extension + optional data + "frameStart" + "frameEnd" + 'fps' + "data": additional metadata for each representation. """ label = "Integrate Asset New" From c5aa315c30413cc4e78cf113bff159a00f51164d Mon Sep 17 00:00:00 2001 From: "felix.wang" Date: Tue, 29 Mar 2022 11:30:00 -0700 Subject: [PATCH 0065/1227] Add toggle button for Loaders' family filter widget. --- openpype/tools/libraryloader/app.py | 11 +++++++++-- openpype/tools/loader/app.py | 9 ++++++++- openpype/tools/loader/widgets.py | 6 ++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index b73b415128..6825b3c975 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -84,11 +84,15 @@ class LibraryLoaderWindow(QtWidgets.QDialog): families_filter_view = FamilyListView( dbcon, self.family_config_cache, left_side_splitter ) + families_toggle_button = QtWidgets.QPushButton("Toggle All") + left_side_splitter.addWidget(projects_combobox) left_side_splitter.addWidget(assets_widget) left_side_splitter.addWidget(families_filter_view) - left_side_splitter.setStretchFactor(1, 65) - left_side_splitter.setStretchFactor(2, 35) + left_side_splitter.addWidget(families_toggle_button) + left_side_splitter.setStretchFactor(0, 65) + left_side_splitter.setStretchFactor(1, 30) + left_side_splitter.setStretchFactor(2, 5) # --- Middle part --- # Subsets widget @@ -163,6 +167,9 @@ class LibraryLoaderWindow(QtWidgets.QDialog): families_filter_view.active_changed.connect( self._on_family_filter_change ) + families_toggle_button.clicked.connect( + families_filter_view.toggle_all + ) assets_widget.selection_changed.connect(self.on_assetschanged) assets_widget.refresh_triggered.connect(self.on_assetschanged) subsets_widget.active_changed.connect(self.on_subsetschanged) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index 923a1fabdb..68a59e1ccc 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -67,10 +67,14 @@ class LoaderWindow(QtWidgets.QDialog): families_filter_view = FamilyListView( io, self.family_config_cache, left_side_splitter ) + families_toggle_button = QtWidgets.QPushButton("Toggle All") + left_side_splitter.addWidget(assets_widget) left_side_splitter.addWidget(families_filter_view) + left_side_splitter.addWidget(families_toggle_button) left_side_splitter.setStretchFactor(0, 65) - left_side_splitter.setStretchFactor(1, 35) + left_side_splitter.setStretchFactor(1, 30) + left_side_splitter.setStretchFactor(2, 5) # --- Middle part --- # Subsets widget @@ -147,6 +151,9 @@ class LoaderWindow(QtWidgets.QDialog): families_filter_view.active_changed.connect( self._on_family_filter_change ) + families_toggle_button.clicked.connect( + families_filter_view.toggle_all + ) assets_widget.selection_changed.connect(self.on_assetschanged) assets_widget.refresh_triggered.connect(self.on_assetschanged) subsets_widget.active_changed.connect(self.on_subsetschanged) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 42fb62b632..b5f1df1e36 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -1070,6 +1070,12 @@ class FamilyListView(QtWidgets.QListView): def set_all_checked(self): self._set_checkstates(True, self._get_all_indexes()) + def toggle_all(self): + if self.get_enabled_families(): + self.set_all_unchecked() + else: + self.set_all_checked() + def _get_all_indexes(self): indexes = [] model = self._family_model From ea84c18d2f1d6a1c11fca506512a4ce7e164a114 Mon Sep 17 00:00:00 2001 From: "felix.wang" Date: Tue, 29 Mar 2022 11:53:13 -0700 Subject: [PATCH 0066/1227] Fix left splitter indexing. --- openpype/tools/libraryloader/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index 6825b3c975..59f4aa4d7a 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -90,9 +90,9 @@ class LibraryLoaderWindow(QtWidgets.QDialog): left_side_splitter.addWidget(assets_widget) left_side_splitter.addWidget(families_filter_view) left_side_splitter.addWidget(families_toggle_button) - left_side_splitter.setStretchFactor(0, 65) - left_side_splitter.setStretchFactor(1, 30) - left_side_splitter.setStretchFactor(2, 5) + left_side_splitter.setStretchFactor(1, 65) + left_side_splitter.setStretchFactor(2, 30) + left_side_splitter.setStretchFactor(3, 5) # --- Middle part --- # Subsets widget From 3ec9684239b7afc326cad7e184a7c6ed4e7a6058 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 2 Apr 2022 11:28:13 +0200 Subject: [PATCH 0067/1227] Only add `frame` to context if used by the destination template --- openpype/plugins/publish/integrate_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 3543786949..99a915af73 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -158,7 +158,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): exclude_families = ["clip"] db_representation_context_keys = [ "project", "asset", "task", "subset", "version", "representation", - "family", "hierarchy", "task", "username", "frame" + "family", "hierarchy", "task", "username" ] default_template_name = "publish" From 6733df77f1f693b89078f216457621d129eb4f71 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 2 Apr 2022 11:30:23 +0200 Subject: [PATCH 0068/1227] Remove double entry of "task" --- openpype/plugins/publish/integrate_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 99a915af73..da4dafb133 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -158,7 +158,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): exclude_families = ["clip"] db_representation_context_keys = [ "project", "asset", "task", "subset", "version", "representation", - "family", "hierarchy", "task", "username" + "family", "hierarchy", "username" ] default_template_name = "publish" From c95c9f92b92f37eca20b1dbc82c3ef0620f8f753 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 2 Apr 2022 11:34:52 +0200 Subject: [PATCH 0069/1227] Add comment --- openpype/plugins/publish/integrate_new.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index da4dafb133..a2943e2972 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -577,6 +577,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): transfers = [(src, dst)] for key in self.db_representation_context_keys: + # Also add these values to the context even if not used by the + # destination template value = template_data.get(key) if not value: continue From 65691bf5207cf57b679dd4b36b3abb6ae57e0be5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 2 Apr 2022 11:36:32 +0200 Subject: [PATCH 0070/1227] Explain why we write subset+version first --- openpype/plugins/publish/integrate_new.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index a2943e2972..bab46803cb 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -270,7 +270,10 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): file_transactions.add(src, dst, mode=FileTransaction.MODE_HARDLINK) # Bulk write to the database - # todo: Can we move this even to after the file transfers? + # We write the subset and version to the database before the File + # Transaction to reduce the chances of another publish trying to + # publish to the same version number since that chance can greatly + # increase if the file transaction takes a long time. bulk_write(subset_writes + version_writes) self.log.info("Subset {subset[name]} and Version {version[name]} " "written to database..".format(subset=subset, From 0d83f3c76c880d088de718a416370e69529ad4a5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 2 Apr 2022 11:38:43 +0200 Subject: [PATCH 0071/1227] Add to do for potential erroneous case --- openpype/plugins/publish/integrate_new.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index bab46803cb..84adccb633 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -602,6 +602,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # Backwards compatibility: # Store first transferred destination as published path data # todo: can we remove this? + # todo: We shouldn't change data that makes its way back into + # instance.data[] until we know the publish actually succeeded + # otherwise `published_path` might not actually be valid? published_path = transfers[0][1] repre["published_path"] = published_path # Backwards compatibility From 89376a97e4ef85069a3afdfd5e3115b33bd27284 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 2 Apr 2022 20:47:00 +0200 Subject: [PATCH 0072/1227] Also include file infos of resource files like textures into each representation - This should fix Site Sync for lookdev textures, etc. --- openpype/plugins/publish/integrate_new.py | 29 ++++++++++++++++------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 84adccb633..25ab7817c9 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -264,10 +264,13 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # part of a representation - like texture resources used by a # .ma representation. Those destination paths are pre-defined, etc. # todo: should we move or simplify this logic? + resource_destinations = set() for src, dst in instance.data.get("transfers", []): file_transactions.add(src, dst, mode=FileTransaction.MODE_COPY) + resource_destinations.add(os.path.abspath(dst)) for src, dst in instance.data.get("hardlinks", []): file_transactions.add(src, dst, mode=FileTransaction.MODE_HARDLINK) + resource_destinations.add(os.path.abspath(dst)) # Bulk write to the database # We write the subset and version to the database before the File @@ -295,18 +298,29 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): ) self.log.debug("Site Sync Sites: {}".format(sites)) + # Compute the resource file infos once (files belonging to the + # version instance instead of an individual representation) so + # we can re-use those file infos per representation + anatomy = instance.context.data["anatomy"] + resource_file_infos = self.prepare_file_info(resource_destinations, + sites=sites, + anatomy=anatomy) + # Finalize the representations now the published files are integrated # Get 'files' info for representations and its attached resources - anatomy = instance.context.data["anatomy"] representation_writes = [] new_repre_names_low = set() for prepared in prepared_representations: - transfers = prepared["transfers"] representation = prepared["representation"] + transfers = prepared["transfers"] + destinations = [dst for src, dst in transfers] representation["files"] = self.get_files_info( - transfers, sites, anatomy + destinations, sites=sites, anatomy=anatomy ) + # Add the version resource file infos to each representation + representation["files"] += resource_file_infos + # Set up representation for writing to the database. Since # we *might* be overwriting an existing entry if the version # already existed we'll use ReplaceOnce with `upsert=True` @@ -751,11 +765,11 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): ).format(path)) return path - def get_files_info(self, transfers, sites, anatomy): + def get_files_info(self, destinations, sites, anatomy): """Prepare 'files' info portion for representations. Arguments: - transfers (list): List of transferred files (source, destination) + destinations (list): List of transferred file destinations sites (list): array of published locations anatomy: anatomy part from instance Returns: @@ -763,10 +777,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): in representation """ file_infos = [] - for _src, dest in transfers: - file_info = self.prepare_file_info(dest, anatomy, sites=sites) + for file_path in destinations: + file_info = self.prepare_file_info(file_path, anatomy, sites=sites) file_infos.append(file_info) - return file_infos def prepare_file_info(self, path, anatomy, sites): From e6209555b01a0330186bc9176c8331a130325186 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 2 Apr 2022 20:50:13 +0200 Subject: [PATCH 0073/1227] Match behavior more with what integrator did before refactor --- openpype/plugins/publish/collect_anatomy_context_data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/collect_anatomy_context_data.py b/openpype/plugins/publish/collect_anatomy_context_data.py index 346caf6b83..c3fabba2ce 100644 --- a/openpype/plugins/publish/collect_anatomy_context_data.py +++ b/openpype/plugins/publish/collect_anatomy_context_data.py @@ -93,9 +93,9 @@ class CollectAnatomyContextData(pyblish.api.ContextPlugin): intent = context.data.get("intent") if intent and isinstance(intent, dict): - intent_value = intent.get("value") - if intent_value: - context_data["intent"] = intent_value + intent = intent.get("value") + if intent: + context_data["intent"] = intent self.log.info("Global anatomy Data collected") self.log.debug(json.dumps(context_data, indent=4)) From 52fd21d85494dacd0071a3b08d79dbdd04789b30 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 2 Apr 2022 20:51:56 +0200 Subject: [PATCH 0074/1227] Add todo/question regarding `intent` --- openpype/plugins/publish/collect_anatomy_context_data.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/plugins/publish/collect_anatomy_context_data.py b/openpype/plugins/publish/collect_anatomy_context_data.py index c3fabba2ce..3f7e65ecd3 100644 --- a/openpype/plugins/publish/collect_anatomy_context_data.py +++ b/openpype/plugins/publish/collect_anatomy_context_data.py @@ -91,6 +91,8 @@ class CollectAnatomyContextData(pyblish.api.ContextPlugin): } }) + # todo: some code actually expects the dict itself and others doesn't + # question: what should it be? intent = context.data.get("intent") if intent and isinstance(intent, dict): intent = intent.get("value") From 4c78976d3d834a5cb1fd0bce44f465cbf3ac6375 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 2 Apr 2022 20:55:40 +0200 Subject: [PATCH 0075/1227] Add todo --- openpype/plugins/publish/integrate_new.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 25ab7817c9..e0c0632548 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -688,6 +688,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "fps": instance.data.get("fps", context.data.get("fps")) } + # todo: preferably we wouldn't need this "if dict" etc. logic and + # instead be able to rely what the input value is if it's set. intent_value = context.data.get("intent") if intent_value and isinstance(intent_value, dict): intent_value = intent_value.get("value") From 3e095bc7554a24ef13282ccfd87e0327eb3b8745 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 2 Apr 2022 20:57:38 +0200 Subject: [PATCH 0076/1227] Use template name for frame padding anatomy template --- openpype/plugins/publish/integrate_new.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index e0c0632548..0f3b11a025 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -520,8 +520,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if repre.get("frameStart") is not None: index_frame_start = int(repre.get("frameStart")) - # TODO use frame padding from right template group - render_template = anatomy.templates["render"] + render_template = anatomy.templates[template_name] frame_start_padding = int( render_template.get( "frame_padding", From b12b1c80f2facbe343333ba3d70dcbe463383538 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 2 Apr 2022 21:00:10 +0200 Subject: [PATCH 0077/1227] Never shift udim sequences --- openpype/plugins/publish/integrate_new.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 0f3b11a025..fd0d57c646 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -501,6 +501,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): anatomy = instance.context.data['anatomy'] template = os.path.normpath(anatomy.templates[template_name]["path"]) + is_udim = bool(repre.get("udim")) is_sequence_representation = isinstance(files, (list, tuple)) if is_sequence_representation: # Collection of files (sequence) @@ -517,7 +518,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # frame indices from the source collection. destination_indexes = list(src_collection.indexes) destination_padding = len(get_first_frame_padded(src_collection)) - if repre.get("frameStart") is not None: + if repre.get("frameStart") is not None and not is_udim: index_frame_start = int(repre.get("frameStart")) render_template = anatomy.templates[template_name] @@ -543,7 +544,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # from the source indexes, etc. first_index_padded = get_frame_padded(frame=destination_indexes[0], padding=destination_padding) - if repre.get("udim"): + if is_udim: # UDIM representations handle ranges in a different manner template_data["udim"] = first_index_padded else: @@ -579,7 +580,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # Manage anatomy template data template_data.pop("frame", None) - if repre.get("udim"): + if is_udim: template_data["udim"] = repre["udim"][0] # Construct destination filepath from template From f7d35c4fed0885c6656da03eb852706c6bf20117 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 2 Apr 2022 21:01:09 +0200 Subject: [PATCH 0078/1227] add todo/question --- openpype/plugins/publish/integrate_new.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index fd0d57c646..52c7686473 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -522,6 +522,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): index_frame_start = int(repre.get("frameStart")) render_template = anatomy.templates[template_name] + # todo: should we ALWAYS manage the frame padding even when not + # having `frameStart` set? frame_start_padding = int( render_template.get( "frame_padding", From 70bfdd09b40936efc45efa6bbd1ea029447058f2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 2 Apr 2022 21:07:02 +0200 Subject: [PATCH 0079/1227] Remove old "dependencies" data --- openpype/plugins/publish/integrate_new.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 52c7686473..37c68ffa6d 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -636,7 +636,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "parent": version["_id"], "name": repre['name'], "data": data, - "dependencies": instance.data.get("dependencies", "").split(), # Imprint shortcut to context for performance reasons. "context": repre_context From 45745cc514236d64cc7f2feddbff9e6217b720fa Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 3 Apr 2022 20:37:28 +0200 Subject: [PATCH 0080/1227] Improve clarity of comment --- openpype/plugins/publish/integrate_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 37c68ffa6d..cb469251e6 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -604,7 +604,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): repre_context[key] = template_data[key] # Explicitly store the full list even though template data might - # have a different value + # have a different value because it uses just a single udim tile if repre.get("udim"): repre_context["udim"] = repre.get("udim") # store list From fe72197a9feb413c8f6c5f9e02339ed891fdda07 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 3 Apr 2022 20:40:25 +0200 Subject: [PATCH 0081/1227] Add comment --- openpype/plugins/publish/integrate_new.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index cb469251e6..f1cceb9ca7 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -156,11 +156,14 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "usdOverride" ] exclude_families = ["clip"] + default_template_name = "publish" + + # Representation context keys that should always be written to + # the database even if not used by the destination template db_representation_context_keys = [ "project", "asset", "task", "subset", "version", "representation", "family", "hierarchy", "username" ] - default_template_name = "publish" # Attributes set by settings template_name_profiles = None From c3c8281e0134222677b32f91ec644322dd996a74 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 3 Apr 2022 20:41:34 +0200 Subject: [PATCH 0082/1227] tweak comment --- openpype/plugins/publish/integrate_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index f1cceb9ca7..238ae82bba 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -183,7 +183,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self.register(instance, file_transactions) except Exception: # clean destination - # todo: rollback any registered entities? (or how safe are we?) + # todo: preferably we'd also rollback *any* changes to the database file_transactions.rollback() self.log.critical("Error when registering", exc_info=True) six.reraise(*sys.exc_info()) From 0e96071996c20199e028df715d6fc9005d100991 Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Fri, 18 Mar 2022 17:20:58 +0100 Subject: [PATCH 0083/1227] Add shotgrid module --- .editorconfig | 19 ++ .pre-commit-config.yaml | 26 +++ mypy.ini | 5 + .../plugins/publish/submit_maya_deadline.py | 1 + .../plugins/publish/submit_publish_job.py | 1 + openpype/modules/shotgrid/README.md | 19 ++ openpype/modules/shotgrid/__init__.py | 5 + openpype/modules/shotgrid/aop/patch.py | 50 +++++ .../shotgrid/hooks/post_shotgrid_changes.py | 9 + openpype/modules/shotgrid/lib/const.py | 1 + openpype/modules/shotgrid/lib/credentials.py | 125 +++++++++++ openpype/modules/shotgrid/lib/record.py | 18 ++ openpype/modules/shotgrid/lib/server.py | 27 +++ openpype/modules/shotgrid/lib/settings.py | 39 ++++ .../publish/collect_shotgrid_entities.py | 103 +++++++++ .../publish/collect_shotgrid_session.py | 132 ++++++++++++ .../publish/integrate_shotgrid_publish.py | 77 +++++++ .../publish/integrate_shotgrid_version.py | 92 ++++++++ .../plugins/publish/validate_shotgrid_user.py | 36 ++++ openpype/modules/shotgrid/server/README.md | 5 + openpype/modules/shotgrid/shotgrid_module.py | 62 ++++++ .../tests/shotgrid/lib/test_credentials.py | 34 +++ .../shotgrid/tray/credential_dialog.py | 202 ++++++++++++++++++ .../modules/shotgrid/tray/shotgrid_tray.py | 76 +++++++ openpype/resources/app_icons/shotgrid.png | Bin 0 -> 45744 bytes .../defaults/project_settings/shotgrid.json | 22 ++ .../defaults/system_settings/modules.json | 9 +- openpype/settings/entities/__init__.py | 2 + openpype/settings/entities/enum_entity.py | 116 ++++++---- .../schemas/projects_schema/schema_main.json | 4 + .../schema_project_shotgrid.json | 98 +++++++++ .../schemas/schema_representation_tags.json | 3 + .../schemas/system_schema/schema_modules.json | 54 +++++ pyproject.toml | 1 + 34 files changed, 1433 insertions(+), 40 deletions(-) create mode 100644 .editorconfig create mode 100644 .pre-commit-config.yaml create mode 100644 mypy.ini create mode 100644 openpype/modules/shotgrid/README.md create mode 100644 openpype/modules/shotgrid/__init__.py create mode 100644 openpype/modules/shotgrid/aop/patch.py create mode 100644 openpype/modules/shotgrid/hooks/post_shotgrid_changes.py create mode 100644 openpype/modules/shotgrid/lib/const.py create mode 100644 openpype/modules/shotgrid/lib/credentials.py create mode 100644 openpype/modules/shotgrid/lib/record.py create mode 100644 openpype/modules/shotgrid/lib/server.py create mode 100644 openpype/modules/shotgrid/lib/settings.py create mode 100644 openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py create mode 100644 openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py create mode 100644 openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py create mode 100644 openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py create mode 100644 openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py create mode 100644 openpype/modules/shotgrid/server/README.md create mode 100644 openpype/modules/shotgrid/shotgrid_module.py create mode 100644 openpype/modules/shotgrid/tests/shotgrid/lib/test_credentials.py create mode 100644 openpype/modules/shotgrid/tray/credential_dialog.py create mode 100644 openpype/modules/shotgrid/tray/shotgrid_tray.py create mode 100644 openpype/resources/app_icons/shotgrid.png create mode 100644 openpype/settings/defaults/project_settings/shotgrid.json create mode 100644 openpype/settings/entities/schemas/projects_schema/schema_project_shotgrid.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..aeb5534872 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +[*.{js,py}] +charset = utf-8 + +[*.py] +indent_style = space +indent_size = 4 + +[*.yml] +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..6a986c7dd9 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,26 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files +- repo: https://github.com/ambv/black + rev: 21.4b0 + hooks: + - id: black + language_version: "3" + args: + - "--config" + - "./pyproject.toml" +- repo: https://github.com/pycqa/flake8 + rev: "3.9.2" # pick a git hash / tag to point to + hooks: + - id: flake8 +- repo: https://github.com/pre-commit/mirrors-mypy + rev: 'v0.902' + hooks: + - id: mypy + args: [--no-strict-optional, --ignore-missing-imports] + additional_dependencies: [tokenize-rt==3.2.0] diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000000..90cde26676 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,5 @@ +[mypy] +python_version = 3.7 +ignore_missing_imports = false +check_untyped_defs = true +follow_imports = silent diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 34147712bc..f59fd3af1c 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -476,6 +476,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): "FTRACK_API_KEY", "FTRACK_API_USER", "FTRACK_SERVER", + "OPENPYPE_SG_USER", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 3c4e0d2913..e1e1ea6b94 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -113,6 +113,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "celaction": [r".*"]} enviro_filter = [ + "OPENPYPE_SG_USER", "FTRACK_API_USER", "FTRACK_API_KEY", "FTRACK_SERVER", diff --git a/openpype/modules/shotgrid/README.md b/openpype/modules/shotgrid/README.md new file mode 100644 index 0000000000..cbee0e9bf4 --- /dev/null +++ b/openpype/modules/shotgrid/README.md @@ -0,0 +1,19 @@ +## Shotgrid Module + +### Pre-requisites + +Install and launch a [shotgrid leecher](https://github.com/Ellipsanime/shotgrid-leecher) server + +### Quickstart + +The goal of this tutorial is to synchronize an already existing shotgrid project with OpenPype. + +- Activate the shotgrid module in the **system settings** and inform the shotgrid leecher server API url + +- Create a new OpenPype project with the **project manager** + +- Inform the shotgrid authentication infos (url, script name, api key) and the shotgrid project ID related to this OpenPype project in the **project settings** + +- Use the batch interface (Tray > shotgrid > Launch batch), select your project and click "batch" + +- You can now access your shotgrid entities within the **avalon launcher** and publish informations to shotgrid with **pyblish** diff --git a/openpype/modules/shotgrid/__init__.py b/openpype/modules/shotgrid/__init__.py new file mode 100644 index 0000000000..f1337a9492 --- /dev/null +++ b/openpype/modules/shotgrid/__init__.py @@ -0,0 +1,5 @@ +from .shotgrid_module import ( + ShotgridModule, +) + +__all__ = ("ShotgridModule",) diff --git a/openpype/modules/shotgrid/aop/patch.py b/openpype/modules/shotgrid/aop/patch.py new file mode 100644 index 0000000000..6ca81033e2 --- /dev/null +++ b/openpype/modules/shotgrid/aop/patch.py @@ -0,0 +1,50 @@ +from copy import copy +from typing import Any, Iterator, Dict, Set + +from avalon.api import AvalonMongoDB + +from openpype.api import Logger +from openpype.modules.shotgrid.lib import ( + credentials, + settings, + server, +) + +_LOG = Logger().get_logger("ShotgridModule.patch") + + +def _patched_projects( + self: Any, projection: Any = None, only_active: bool = True +) -> Iterator[Dict[str, Any]]: + all_projects = list(self._prev_projects(projection, only_active)) + if ( + not credentials.get_local_login() + or not settings.filter_projects_by_login() + ): + return all_projects + try: + linked_names = _fetch_linked_project_names() or set() + return [x for x in all_projects if _upper(x["name"]) in linked_names] + except Exception as e: + print(e) + return all_projects + + +def _upper(x: Any) -> str: + return str(x).strip().upper() + + +def _fetch_linked_project_names() -> Set[str]: + return { + _upper(x["project_name"]) + for x in server.find_linked_projects(credentials.get_local_login()) + } + + +def patch_avalon_db() -> None: + _LOG.debug("Run avalon patching") + if AvalonMongoDB.projects is _patched_projects: + return None + _LOG.debug("Patch Avalon.projects method") + AvalonMongoDB._prev_projects = copy(AvalonMongoDB.projects) + AvalonMongoDB.projects = _patched_projects diff --git a/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py b/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py new file mode 100644 index 0000000000..e8369ad3cb --- /dev/null +++ b/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py @@ -0,0 +1,9 @@ +from openpype.lib import PostLaunchHook + + +class PostShotgridHook(PostLaunchHook): + order = None + + def execute(self, *args, **kwargs): + print(args, kwargs) + pass diff --git a/openpype/modules/shotgrid/lib/const.py b/openpype/modules/shotgrid/lib/const.py new file mode 100644 index 0000000000..2a34800fac --- /dev/null +++ b/openpype/modules/shotgrid/lib/const.py @@ -0,0 +1 @@ +MODULE_NAME = "shotgrid" diff --git a/openpype/modules/shotgrid/lib/credentials.py b/openpype/modules/shotgrid/lib/credentials.py new file mode 100644 index 0000000000..a334968cda --- /dev/null +++ b/openpype/modules/shotgrid/lib/credentials.py @@ -0,0 +1,125 @@ +from typing import Tuple, Optional +from urllib.parse import urlparse + +import shotgun_api3 +from shotgun_api3.shotgun import AuthenticationFault + +from openpype.lib import OpenPypeSecureRegistry, OpenPypeSettingsRegistry +from openpype.modules.shotgrid.lib.record import Credentials + + +def _get_shotgrid_secure_key(hostname: str, key: str) -> str: + """Secure item key for entered hostname.""" + return f"shotgrid/{hostname}/{key}" + + +def _get_secure_value_and_registry( + hostname: str, + name: str, +) -> Tuple[str, OpenPypeSecureRegistry]: + key = _get_shotgrid_secure_key(hostname, name) + registry = OpenPypeSecureRegistry(key) + return registry.get_item(name, None), registry + + +def get_shotgrid_hostname(shotgrid_url: str) -> str: + + if not shotgrid_url: + raise Exception("Shotgrid url cannot be a null") + valid_shotgrid_url = ( + f"//{shotgrid_url}" if "//" not in shotgrid_url else shotgrid_url + ) + return urlparse(valid_shotgrid_url).hostname + + +# Credentials storing function (using keyring) + + +def get_credentials(shotgrid_url: str) -> Optional[Credentials]: + hostname = get_shotgrid_hostname(shotgrid_url) + if not hostname: + return None + login_value, _ = _get_secure_value_and_registry( + hostname, + Credentials.login_key_prefix(), + ) + password_value, _ = _get_secure_value_and_registry( + hostname, + Credentials.password_key_prefix(), + ) + return Credentials(login_value, password_value) + + +def save_credentials(login: str, password: str, shotgrid_url: str): + hostname = get_shotgrid_hostname(shotgrid_url) + _, login_registry = _get_secure_value_and_registry( + hostname, + Credentials.login_key_prefix(), + ) + _, password_registry = _get_secure_value_and_registry( + hostname, + Credentials.password_key_prefix(), + ) + clear_credentials(shotgrid_url) + login_registry.set_item(Credentials.login_key_prefix(), login) + password_registry.set_item(Credentials.password_key_prefix(), password) + + +def clear_credentials(shotgrid_url: str): + hostname = get_shotgrid_hostname(shotgrid_url) + login_value, login_registry = _get_secure_value_and_registry( + hostname, + Credentials.login_key_prefix(), + ) + password_value, password_registry = _get_secure_value_and_registry( + hostname, + Credentials.password_key_prefix(), + ) + + if login_value is not None: + login_registry.delete_item(Credentials.login_key_prefix()) + + if password_value is not None: + password_registry.delete_item(Credentials.password_key_prefix()) + + +# Login storing function (using json) + + +def get_local_login() -> Optional[str]: + reg = OpenPypeSettingsRegistry() + try: + return str(reg.get_item("shotgrid_login")) + except Exception: + return None + + +def save_local_login(login: str): + reg = OpenPypeSettingsRegistry() + reg.set_item("shotgrid_login", login) + + +def clear_local_login(): + reg = OpenPypeSettingsRegistry() + reg.delete_item("shotgrid_login") + + +def check_credentials( + login: str, + password: str, + shotgrid_url: str, +) -> bool: + + if not shotgrid_url or not login or not password: + return False + try: + session = shotgun_api3.Shotgun( + shotgrid_url, + login=login, + password=password, + ) + session.preferences_read() + session.close() + except AuthenticationFault: + return False + return True diff --git a/openpype/modules/shotgrid/lib/record.py b/openpype/modules/shotgrid/lib/record.py new file mode 100644 index 0000000000..1796e6c019 --- /dev/null +++ b/openpype/modules/shotgrid/lib/record.py @@ -0,0 +1,18 @@ +from dataclasses import dataclass + + +@dataclass(frozen=True) +class Credentials: + login: str + password: str + + def is_empty(self) -> bool: + return not (self.login and self.password) + + @staticmethod + def login_key_prefix() -> str: + return "login" + + @staticmethod + def password_key_prefix() -> str: + return "password" diff --git a/openpype/modules/shotgrid/lib/server.py b/openpype/modules/shotgrid/lib/server.py new file mode 100644 index 0000000000..0fe4b8429e --- /dev/null +++ b/openpype/modules/shotgrid/lib/server.py @@ -0,0 +1,27 @@ +import traceback + +import requests +from typing import Dict, Any, List + +from openpype.api import Logger +from openpype.modules.shotgrid.lib import ( + settings as settings_lib, +) + +_LOG = Logger().get_logger("ShotgridModule.server") + + +def find_linked_projects(email: str) -> List[Dict[str, Any]]: + url = "".join( + [ + settings_lib.get_leecher_backend_url(), + "/user/", + email, + "/project-user-links", + ] + ) + try: + return requests.get(url).json() + except requests.exceptions.RequestException as e: + _LOG.error(e) + traceback.print_stack() diff --git a/openpype/modules/shotgrid/lib/settings.py b/openpype/modules/shotgrid/lib/settings.py new file mode 100644 index 0000000000..0f4fc235cc --- /dev/null +++ b/openpype/modules/shotgrid/lib/settings.py @@ -0,0 +1,39 @@ +import os +from typing import Tuple, Dict, List, Any + +from pymongo import MongoClient +from openpype.api import get_system_settings, get_project_settings +from openpype.modules.shotgrid.lib.const import MODULE_NAME + + +def get_project_list() -> List[str]: + mongo_url = os.getenv("OPENPYPE_MONGO") + client = MongoClient(mongo_url) + db = client['avalon'] + return db.list_collection_names() + + +def get_shotgrid_project_settings(project: str) -> Dict[str, Any]: + return get_project_settings(project).get(MODULE_NAME, {}) + + +def get_shotgrid_settings() -> Dict[str, Any]: + return get_system_settings().get("modules", {}).get(MODULE_NAME, {}) + + +def get_shotgrid_servers() -> Dict[str, Any]: + return get_shotgrid_settings().get("shotgrid_settings", {}) + + +def get_leecher_backend_url() -> str: + return get_shotgrid_settings().get("leecher_backend_url") + + +def filter_projects_by_login() -> bool: + return bool(get_shotgrid_settings().get("filter_projects_by_login", False)) + + +def get_shotgrid_event_mongo_info() -> Tuple[str, str]: + database_name = os.environ["OPENPYPE_DATABASE_NAME"] + collection_name = "shotgrid_events" + return database_name, collection_name diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py new file mode 100644 index 0000000000..b89dda3a80 --- /dev/null +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py @@ -0,0 +1,103 @@ +import os +import pyblish.api +from pymongo import MongoClient +from openpype.api import get_project_settings + + +class CollectShotgridEntities(pyblish.api.ContextPlugin): + """Collect shotgrid entities according to the current context""" + + order = pyblish.api.CollectorOrder + 0.499 + label = "Shotgrid entities" + + def process(self, context): + + avalon_project = context.data.get("projectEntity") + avalon_asset = context.data.get("assetEntity") + avalon_task_name = os.getenv("AVALON_TASK") + + self.log.info(avalon_project) + self.log.info(avalon_asset) + + sg_project = _get_shotgrid_project(avalon_project) + sg_task = _get_shotgrid_task( + avalon_project, avalon_asset, avalon_task_name + ) + sg_entity = _get_shotgrid_entity(avalon_project, avalon_asset) + + if sg_project: + context.data["shotgridProject"] = sg_project + self.log.info( + "Collected correspondig shotgrid project : {}".format( + sg_project + ) + ) + + if sg_task: + context.data["shotgridTask"] = sg_task + self.log.info( + "Collected correspondig shotgrid task : {}".format(sg_task) + ) + + if sg_entity: + context.data["shotgridEntity"] = sg_entity + self.log.info( + "Collected correspondig shotgrid entity : {}".format(sg_entity) + ) + + def _find_existing_version(self, code, context): + + filters = [ + ["project", "is", context.data.get("shotgridProject")], + ["sg_task", "is", context.data.get("shotgridTask")], + ["entity", "is", context.data.get("shotgridEntity")], + ["code", "is", code], + ] + + sg = context.data.get("shotgridSession") + return sg.find_one("Version", filters, []) + + +def _get_shotgrid_collection(project): + mongo_url = os.getenv("OPENPYPE_MONGO") + client = MongoClient(mongo_url) + return client.get_database("shotgrid_openpype").get_collection(project) + + +def _get_shotgrid_project_settings(project): + return get_project_settings(project).get("shotgrid", {}) + + +def _get_shotgrid_project(avalon_project): + proj_settings = _get_shotgrid_project_settings(avalon_project["name"]) + shotgrid_project_id = proj_settings.get("shotgrid_project_id") + if shotgrid_project_id: + return {"type": "Project", "id": shotgrid_project_id} + return {} + + +def _get_shotgrid_task(avalon_project, avalon_asset, avalon_task): + sg_col = _get_shotgrid_collection(avalon_project["name"]) + shotgrid_task_hierarchy_row = sg_col.find_one( + { + "type": "Task", + "_id": {"$regex": "^" + avalon_task + "_[0-9]*"}, + "parent": {"$regex": ".*," + avalon_asset["name"] + ","}, + } + ) + if shotgrid_task_hierarchy_row: + return {"type": "Task", "id": shotgrid_task_hierarchy_row["src_id"]} + return {} + + +def _get_shotgrid_entity(avalon_project, avalon_asset): + sg_col = _get_shotgrid_collection(avalon_project["name"]) + shotgrid_entity_hierarchy_row = sg_col.find_one( + {"_id": avalon_asset["name"]} + ) + if shotgrid_entity_hierarchy_row: + return { + "type": shotgrid_entity_hierarchy_row["type"], + "id": shotgrid_entity_hierarchy_row["src_id"], + } + return {} diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py new file mode 100644 index 0000000000..65a5de9f22 --- /dev/null +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py @@ -0,0 +1,132 @@ +import os +import sys +import pyblish.api +import shotgun_api3 +from shotgun_api3.shotgun import AuthenticationFault +from openpype.lib import OpenPypeSettingsRegistry +from openpype.api import get_project_settings, get_system_settings + + +class CollectShotgridSession(pyblish.api.ContextPlugin): + """Collect shotgrid session using user credentials""" + + order = pyblish.api.CollectorOrder + label = "Shotgrid user session" + + def process(self, context): + + certificate_path = os.getenv("SHOTGUN_API_CACERTS") + if certificate_path is None or not os.path.exists(certificate_path): + self.log.info( + "SHOTGUN_API_CACERTS does not contains a valid \ + path: {}".format( + certificate_path + ) + ) + certificate_path = get_shotgrid_certificate() + self.log.info("Get Certificate from shotgrid_api") + + if not os.path.exists(certificate_path): + self.log.error( + "Could not find certificate in shotgun_api3: \ + {}".format( + certificate_path + ) + ) + return + + set_shotgrid_certificate(certificate_path) + self.log.info("Set Certificate: {}".format(certificate_path)) + + avalon_project = os.getenv("AVALON_PROJECT") + + shotgrid_settings = get_shotgrid_settings(avalon_project) + self.log.info("shotgrid settings: {}".format(shotgrid_settings)) + shotgrid_servers_settings = get_shotgrid_servers() + self.log.info( + "shotgrid_servers_settings: {}".format(shotgrid_servers_settings) + ) + + shotgrid_server = shotgrid_settings.get("shotgrid_server", "") + if not shotgrid_server: + self.log.error( + "No Shotgrid server found, please choose a credential" + "in script name and script key in OpenPype settings" + ) + + shotgrid_server_setting = shotgrid_servers_settings.get( + shotgrid_server, {} + ) + shotgrid_url = shotgrid_server_setting.get("shotgrid_url", "") + + shotgrid_script_name = shotgrid_server_setting.get( + "shotgrid_script_name", "" + ) + shotgrid_script_key = shotgrid_server_setting.get( + "shotgrid_script_key", "" + ) + if not shotgrid_script_name and not shotgrid_script_key: + self.log.error( + "No Shotgrid api credential found, please enter " + "script name and script key in OpenPype settings" + ) + + login = get_login() or os.getenv("OPENPYPE_SG_USER") + + if not login: + self.log.error( + "No Shotgrid login found, please " + "login to shotgrid withing openpype Tray" + ) + + session = shotgun_api3.Shotgun( + base_url=shotgrid_url, + script_name=shotgrid_script_name, + api_key=shotgrid_script_key, + sudo_as_login=login, + ) + + try: + session.preferences_read() + except AuthenticationFault: + raise ValueError( + "Could not connect to shotgrid {} with user {}".format( + shotgrid_url, login + ) + ) + + self.log.info( + "Logged to shotgrid {} with user {}".format(shotgrid_url, login) + ) + context.data["shotgridSession"] = session + context.data["shotgridUser"] = login + + +def get_shotgrid_certificate(): + shotgun_api_path = os.path.dirname(shotgun_api3.__file__) + return os.path.join(shotgun_api_path, "lib", "certifi", "cacert.pem") + + +def set_shotgrid_certificate(certificate): + os.environ["SHOTGUN_API_CACERTS"] = certificate + + +def get_shotgrid_settings(project): + return get_project_settings(project).get("shotgrid", {}) + + +def get_shotgrid_servers(): + return ( + get_system_settings() + .get("modules", {}) + .get("shotgrid", {}) + .get("shotgrid_settings", {}) + ) + + +def get_login(): + reg = OpenPypeSettingsRegistry() + try: + return str(reg.get_item("shotgrid_login")) + except Exception as e: + return None diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py new file mode 100644 index 0000000000..cfd2d10fd9 --- /dev/null +++ b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py @@ -0,0 +1,77 @@ +import os +import pyblish.api + + +class IntegrateShotgridPublish(pyblish.api.InstancePlugin): + """ + Create published Files from representations and add it to version. If + representation is tagged add shotgrid review, it will add it in + path to movie for a movie file or path to frame for an image sequence. + """ + + order = pyblish.api.IntegratorOrder + 0.499 + label = "Shotgrid Published Files" + + def process(self, instance): + + context = instance.context + + self.sg = context.data.get("shotgridSession") + + shotgrid_version = instance.data.get("shotgridVersion") + + for representation in instance.data.get("representations", []): + + local_path = representation.get("published_path") + code = os.path.basename(local_path) + + if representation.get("tags", []): + continue + + published_file = self._find_existing_publish( + code, context, shotgrid_version + ) + + published_file_data = { + "project": context.data.get("shotgridProject"), + "code": code, + "entity": context.data.get("shotgridEntity"), + "task": context.data.get("shotgridTask"), + "version": shotgrid_version, + "path": {"local_path": local_path}, + } + if not published_file: + published_file = self._create_published(published_file_data) + self.log.info( + "Create Shotgrid PublishedFile: {}".format(published_file) + ) + else: + self.sg.update( + published_file["type"], + published_file["id"], + published_file_data, + ) + self.log.info( + "Update Shotgrid PublishedFile: {}".format(published_file) + ) + + if instance.data["family"] == "image": + self.sg.upload_thumbnail( + published_file["type"], published_file["id"], local_path + ) + instance.data["shotgridPublishedFile"] = published_file + + def _find_existing_publish(self, code, context, shotgrid_version): + + filters = [ + ["project", "is", context.data.get("shotgridProject")], + ["task", "is", context.data.get("shotgridTask")], + ["entity", "is", context.data.get("shotgridEntity")], + ["version", "is", shotgrid_version], + ["code", "is", code], + ] + return self.sg.find_one("PublishedFile", filters, []) + + def _create_published(self, published_file_data): + + return self.sg.create("PublishedFile", published_file_data) diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py new file mode 100644 index 0000000000..a1b7140e22 --- /dev/null +++ b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py @@ -0,0 +1,92 @@ +import os +import pyblish.api + + +class IntegrateShotgridVersion(pyblish.api.InstancePlugin): + """Integrate Shotgrid Version""" + + order = pyblish.api.IntegratorOrder + 0.497 + label = "Shotgrid Version" + + sg = None + + def process(self, instance): + + context = instance.context + self.sg = context.data.get("shotgridSession") + + # TODO: Use path template solver to build version code from settings + anatomy = instance.data.get("anatomyData", {}) + code = "_".join( + [ + anatomy["project"]["code"], + anatomy["parent"], + anatomy["asset"], + anatomy["task"]["name"], + "v{:03}".format(int(anatomy["version"])), + ] + ) + + version = self._find_existing_version(code, context) + + if not version: + version = self._create_version(code, context) + self.log.info("Create Shotgrid version: {}".format(version)) + else: + self.log.info("Use existing Shotgrid version: {}".format(version)) + + data_to_update = {} + status = context.data.get("intent", {}).get("value") + if status: + data_to_update["sg_status_list"] = status + + for representation in instance.data.get("representations", []): + local_path = representation.get("published_path") + code = os.path.basename(local_path) + + if "shotgridreview" in representation.get("tags", []): + + if representation["ext"] in ["mov", "avi"]: + self.log.info( + "Upload review: {} for version shotgrid {}".format( + local_path, version.get("id") + ) + ) + self.sg.upload( + "Version", + version.get("id"), + local_path, + field_name="sg_uploaded_movie", + ) + + data_to_update["sg_path_to_movie"] = local_path + + elif representation["ext"] in ["jpg", "png", "exr", "tga"]: + path_to_frame = local_path.replace("0000", "#") + data_to_update["sg_path_to_frames"] = path_to_frame + + self.log.info("Update Shotgrid version with {}".format(data_to_update)) + self.sg.update("Version", version["id"], data_to_update) + + instance.data["shotgridVersion"] = version + + def _find_existing_version(self, code, context): + + filters = [ + ["project", "is", context.data.get("shotgridProject")], + ["sg_task", "is", context.data.get("shotgridTask")], + ["entity", "is", context.data.get("shotgridEntity")], + ["code", "is", code], + ] + return self.sg.find_one("Version", filters, []) + + def _create_version(self, code, context): + + version_data = { + "project": context.data.get("shotgridProject"), + "sg_task": context.data.get("shotgridTask"), + "entity": context.data.get("shotgridEntity"), + "code": code, + } + + return self.sg.create("Version", version_data) diff --git a/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py b/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py new file mode 100644 index 0000000000..7343c47808 --- /dev/null +++ b/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py @@ -0,0 +1,36 @@ +import pyblish.api +import openpype.api + + +class ValidateShotgridUser(pyblish.api.ContextPlugin): + """ + Check if user is valid and have access to the project. + """ + + label = "Validate Shotgrid User" + order = openpype.api.ValidateContentsOrder + + def process(self, context): + sg = context.data.get('shotgridSession') + + login = context.data.get('shotgridUser') + self.log.info("Login shotgrid set in OpenPype is {}".format(login)) + project = context.data.get("shotgridProject") + self.log.info("Current shotgun project is {}".format(project)) + + if not (login and sg and project): + raise KeyError() + + user = sg.find_one( + "HumanUser", + [["login", "is", login]], + ["projects"] + ) + + self.log.info(user) + self.log.info(login) + user_projects_id = [p["id"] for p in user.get("projects", [])] + if not project.get('id') in user_projects_id: + raise PermissionError("Login {} don't have access to the project {}".format(login, project)) + + self.log.info("Login {} have access to the project {}".format(login, project)) diff --git a/openpype/modules/shotgrid/server/README.md b/openpype/modules/shotgrid/server/README.md new file mode 100644 index 0000000000..15e056ff3e --- /dev/null +++ b/openpype/modules/shotgrid/server/README.md @@ -0,0 +1,5 @@ + +### Shotgrid server + +Please refer to the external project that covers Openpype/Shotgrid communication: + - https://github.com/Ellipsanime/shotgrid-leecher diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py new file mode 100644 index 0000000000..75d5f843c5 --- /dev/null +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -0,0 +1,62 @@ +import os +import threading +from typing import Optional, Dict, Any + +from openpype_interfaces import ( + ITrayModule, + IPluginPaths, + ILaunchHookPaths, +) + +from openpype.modules import OpenPypeModule +from .aop.patch import patch_avalon_db +from .tray.shotgrid_tray import ( + ShotgridTrayWrapper, +) + +SHOTGRID_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) + + +class ShotgridModule( + OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths +): + leecher_manager_url: str + name: str = "shotgrid" + enabled: bool = False + project_id: Optional[str] = None + tray_wrapper: ShotgridTrayWrapper + + def initialize(self, modules_settings: Dict[str, Any]): + patch_avalon_db() + threading.Timer(10.0, patch_avalon_db).start() + shotgrid_settings = modules_settings.get(self.name, dict()) + self.enabled = shotgrid_settings.get("enabled", False) + self.leecher_manager_url = shotgrid_settings.get( + "leecher_manager_url", "" + ) + + def connect_with_modules(self, enabled_modules): + pass + + def get_global_environments(self) -> Dict[str, Any]: + return {"PROJECT_ID": self.project_id} + + def get_plugin_paths(self) -> Dict[str, Any]: + return { + "publish": [os.path.join(SHOTGRID_MODULE_DIR, "plugins", "publish")] + } + + def get_launch_hook_paths(self) -> str: + return os.path.join(SHOTGRID_MODULE_DIR, "hooks") + + def tray_init(self): + self.tray_wrapper = ShotgridTrayWrapper(self) + + def tray_start(self): + return self.tray_wrapper.validate() + + def tray_exit(self, *args, **kwargs): + return self.tray_wrapper + + def tray_menu(self, tray_menu): + return self.tray_wrapper.tray_menu(tray_menu) diff --git a/openpype/modules/shotgrid/tests/shotgrid/lib/test_credentials.py b/openpype/modules/shotgrid/tests/shotgrid/lib/test_credentials.py new file mode 100644 index 0000000000..1f78cf77c9 --- /dev/null +++ b/openpype/modules/shotgrid/tests/shotgrid/lib/test_credentials.py @@ -0,0 +1,34 @@ +import pytest +from assertpy import assert_that + +import openpype.modules.shotgrid.lib.credentials as sut + + +def test_missing_shotgrid_url(): + with pytest.raises(Exception) as ex: + # arrange + url = "" + # act + sut.get_shotgrid_hostname(url) + # assert + assert_that(ex).is_equal_to("Shotgrid url cannot be a null") + + +def test_full_shotgrid_url(): + # arrange + url = "https://shotgrid.com/myinstance" + # act + actual = sut.get_shotgrid_hostname(url) + # assert + assert_that(actual).is_not_empty() + assert_that(actual).is_equal_to("shotgrid.com") + + +def test_incomplete_shotgrid_url(): + # arrange + url = "shotgrid.com/myinstance" + # act + actual = sut.get_shotgrid_hostname(url) + # assert + assert_that(actual).is_not_empty() + assert_that(actual).is_equal_to("shotgrid.com") diff --git a/openpype/modules/shotgrid/tray/credential_dialog.py b/openpype/modules/shotgrid/tray/credential_dialog.py new file mode 100644 index 0000000000..8d7d587c6a --- /dev/null +++ b/openpype/modules/shotgrid/tray/credential_dialog.py @@ -0,0 +1,202 @@ +import os +from typing import Any +from Qt import QtCore, QtWidgets, QtGui + +from openpype import style +from openpype import resources +from openpype.modules.shotgrid.lib import settings, credentials + + +class CredentialsDialog(QtWidgets.QDialog): + SIZE_W = 450 + SIZE_H = 200 + + _module: Any = None + _is_logged: bool = False + url_label: QtWidgets.QLabel + login_label: QtWidgets.QLabel + password_label: QtWidgets.QLabel + url_input: QtWidgets.QComboBox + login_input: QtWidgets.QLineEdit + password_input: QtWidgets.QLineEdit + input_layout: QtWidgets.QFormLayout + login_button: QtWidgets.QPushButton + buttons_layout: QtWidgets.QHBoxLayout + main_widget: QtWidgets.QVBoxLayout + + login_changed: QtCore.Signal = QtCore.Signal() + + def __init__(self, module, parent=None): + super(CredentialsDialog, self).__init__(parent) + + self._module = module + self._is_logged = False + + self.setWindowTitle("OpenPype - Shotgrid Login") + + icon = QtGui.QIcon(resources.get_openpype_icon_filepath()) + self.setWindowIcon(icon) + + self.setWindowFlags( + QtCore.Qt.WindowCloseButtonHint + | QtCore.Qt.WindowMinimizeButtonHint + ) + self.setMinimumSize(QtCore.QSize(self.SIZE_W, self.SIZE_H)) + self.setMaximumSize(QtCore.QSize(self.SIZE_W + 100, self.SIZE_H + 100)) + self.setStyleSheet(style.load_stylesheet()) + + self.ui_init() + + def ui_init(self): + self.url_label = QtWidgets.QLabel("Shotgrid server:") + self.login_label = QtWidgets.QLabel("Login:") + self.password_label = QtWidgets.QLabel("Password:") + + self.url_input = QtWidgets.QComboBox() + # self.url_input.setReadOnly(True) + + self.login_input = QtWidgets.QLineEdit() + self.login_input.setPlaceholderText("login") + + self.password_input = QtWidgets.QLineEdit() + self.password_input.setPlaceholderText("password") + self.password_input.setEchoMode(QtWidgets.QLineEdit.Password) + + self.error_label = QtWidgets.QLabel("") + self.error_label.setStyleSheet("color: red;") + self.error_label.setWordWrap(True) + self.error_label.hide() + + self.input_layout = QtWidgets.QFormLayout() + self.input_layout.setContentsMargins(10, 15, 10, 5) + + self.input_layout.addRow(self.url_label, self.url_input) + self.input_layout.addRow(self.login_label, self.login_input) + self.input_layout.addRow(self.password_label, self.password_input) + self.input_layout.addRow(self.error_label) + + self.login_button = QtWidgets.QPushButton("Login") + self.login_button.setToolTip("Log in shotgrid instance") + self.login_button.clicked.connect(self._on_shotgrid_login_clicked) + + self.logout_button = QtWidgets.QPushButton("Logout") + self.logout_button.setToolTip("Log out shotgrid instance") + self.logout_button.clicked.connect(self._on_shotgrid_logout_clicked) + + self.buttons_layout = QtWidgets.QHBoxLayout() + self.buttons_layout.addWidget(self.logout_button) + self.buttons_layout.addWidget(self.login_button) + + self.main_widget = QtWidgets.QVBoxLayout(self) + self.main_widget.addLayout(self.input_layout) + self.main_widget.addLayout(self.buttons_layout) + self.setLayout(self.main_widget) + + def show(self, *args, **kwargs): + super(CredentialsDialog, self).show(*args, **kwargs) + self._fill_shotgrid_url() + self._fill_shotgrid_login() + + def _fill_shotgrid_url(self): + servers = settings.get_shotgrid_servers() + + if servers: + for _, v in servers.items(): + self.url_input.addItem("{}".format(v.get('shotgrid_url'))) + self._valid_input(self.url_input) + self.login_button.show() + self.logout_button.show() + enabled = True + else: + self.set_error("Ask your admin to add shotgrid server in settings") + self._invalid_input(self.url_input) + self.login_button.hide() + self.logout_button.hide() + enabled = False + + self.login_input.setEnabled(enabled) + self.password_input.setEnabled(enabled) + + def _fill_shotgrid_login(self): + login = credentials.get_local_login() + + if login: + self.login_input.setText(login) + + def _clear_shotgrid_login(self): + self.login_input.setText("") + self.password_input.setText("") + + def _on_shotgrid_login_clicked(self): + login = self.login_input.text().strip() + password = self.password_input.text().strip() + missing = [] + + if login == "": + missing.append("login") + self._invalid_input(self.login_input) + + if password == "": + missing.append("password") + self._invalid_input(self.password_input) + + url = self.url_input.currentText() + if url == "": + missing.append("url") + self._invalid_input(self.url_input) + + if len(missing) > 0: + self.set_error("You didn't enter {}".format(" and ".join(missing))) + return + + # if credentials.check_credentials( + # login=login, + # password=password, + # shotgrid_url=url, + # ): + credentials.save_local_login( + login=login + ) + os.environ['OPENPYPE_SG_USER'] = login + self._on_login() + + self.set_error("CANT LOGIN") + + def _on_shotgrid_logout_clicked(self): + credentials.clear_local_login() + del os.environ['OPENPYPE_SG_USER'] + self._clear_shotgrid_login() + self._on_logout() + + def set_error(self, msg: str): + self.error_label.setText(msg) + self.error_label.show() + + def _on_login(self): + self._is_logged = True + self.login_changed.emit() + self._close_widget() + + def _on_logout(self): + self._is_logged = False + self.login_changed.emit() + + def _close_widget(self): + self.hide() + + def _valid_input(self, input_widget: QtWidgets.QLineEdit): + input_widget.setStyleSheet("") + + def _invalid_input(self, input_widget: QtWidgets.QLineEdit): + input_widget.setStyleSheet("border: 1px solid red;") + + def login_with_credentials( + self, url: str, login: str, password: str + ) -> bool: + verification = credentials.check_credentials(url, login, password) + if verification: + credentials.save_credentials(login, password, False) + self._module.set_credentials_to_env(login, password) + self.set_credentials(login, password) + self.login_changed.emit() + return verification diff --git a/openpype/modules/shotgrid/tray/shotgrid_tray.py b/openpype/modules/shotgrid/tray/shotgrid_tray.py new file mode 100644 index 0000000000..0be58e3b20 --- /dev/null +++ b/openpype/modules/shotgrid/tray/shotgrid_tray.py @@ -0,0 +1,76 @@ +import os +import webbrowser +from typing import Any + +from Qt import QtWidgets + +from openpype.modules.shotgrid.lib import credentials +from openpype.modules.shotgrid.tray.credential_dialog import ( + CredentialsDialog, +) + + +class ShotgridTrayWrapper: + module: Any + credentials_dialog: CredentialsDialog + logged_user_label: QtWidgets.QAction + + def __init__(self, module) -> None: + self.module = module + self.credentials_dialog = CredentialsDialog(module) + self.credentials_dialog.login_changed.connect(self.set_login_label) + self.logged_user_label = QtWidgets.QAction("") + self.logged_user_label.setDisabled(True) + self.set_login_label() + + def show_batch_dialog(self): + if self.module.leecher_manager_url: + webbrowser.open(self.module.leecher_manager_url) + + def show_connect_dialog(self): + self.show_credential_dialog() + + def show_credential_dialog(self): + self.credentials_dialog.show() + self.credentials_dialog.activateWindow() + self.credentials_dialog.raise_() + + def set_login_label(self): + login = credentials.get_local_login() + if login: + self.logged_user_label.setText("{}".format(login)) + else: + self.logged_user_label.setText( + "No User logged in {0}".format(login) + ) + + def tray_menu(self, tray_menu): + # Add login to user menu + menu = QtWidgets.QMenu("Shotgrid", tray_menu) + show_connect_action = QtWidgets.QAction("Connect to Shotgrid", menu) + show_connect_action.triggered.connect(self.show_connect_dialog) + menu.addAction(self.logged_user_label) + menu.addSeparator() + menu.addAction(show_connect_action) + tray_menu.addMenu(menu) + + # Add manager to Admin menu + for m in tray_menu.findChildren(QtWidgets.QMenu): + if m.title() == "Admin": + shotgrid_manager_action = QtWidgets.QAction( + "Shotgrid manager", menu + ) + shotgrid_manager_action.triggered.connect( + self.show_batch_dialog + ) + m.addAction(shotgrid_manager_action) + + def validate(self) -> bool: + login = credentials.get_local_login() + + if not login: + self.show_credential_dialog() + else: + os.environ["OPENPYPE_SG_USER"] = login + + return True diff --git a/openpype/resources/app_icons/shotgrid.png b/openpype/resources/app_icons/shotgrid.png new file mode 100644 index 0000000000000000000000000000000000000000..6d0cc047f9ed86e0db45ea557404ab7edb2f5bf2 GIT binary patch literal 45744 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 N8$~sR3OTc&{{b(fTU!7C literal 0 HcmV?d00001 diff --git a/openpype/settings/defaults/project_settings/shotgrid.json b/openpype/settings/defaults/project_settings/shotgrid.json new file mode 100644 index 0000000000..83b6f69074 --- /dev/null +++ b/openpype/settings/defaults/project_settings/shotgrid.json @@ -0,0 +1,22 @@ +{ + "shotgrid_project_id": 0, + "shotgrid_server": "", + "event": { + "enabled": false + }, + "fields": { + "asset": { + "type": "sg_asset_type" + }, + "sequence": { + "episode_link": "episode" + }, + "shot": { + "episode_link": "sg_episode", + "sequence_link": "sg_sequence" + }, + "task": { + "step": "step" + } + } +} diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index d74269922f..a5746e930b 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -137,6 +137,13 @@ } } }, + "shotgrid": { + "enabled": true, + "filter_projects_by_login": true, + "leecher_manager_url": "http://127.0.0.1:3000", + "leecher_backend_url": "http://127.0.0.1:8090", + "shotgrid_settings": {} + }, "timers_manager": { "enabled": true, "auto_stop": true, @@ -205,4 +212,4 @@ "linux": "" } } -} \ No newline at end of file +} diff --git a/openpype/settings/entities/__init__.py b/openpype/settings/entities/__init__.py index a173e2454f..b2cb2204f4 100644 --- a/openpype/settings/entities/__init__.py +++ b/openpype/settings/entities/__init__.py @@ -107,6 +107,7 @@ from .enum_entity import ( TaskTypeEnumEntity, DeadlineUrlEnumEntity, AnatomyTemplatesEnumEntity, + ShotgridUrlEnumEntity ) from .list_entity import ListEntity @@ -171,6 +172,7 @@ __all__ = ( "ToolsEnumEntity", "TaskTypeEnumEntity", "DeadlineUrlEnumEntity", + "ShotgridUrlEnumEntity", "AnatomyTemplatesEnumEntity", "ListEntity", diff --git a/openpype/settings/entities/enum_entity.py b/openpype/settings/entities/enum_entity.py index 92a397afba..643bd5735d 100644 --- a/openpype/settings/entities/enum_entity.py +++ b/openpype/settings/entities/enum_entity.py @@ -1,10 +1,7 @@ import copy from .input_entities import InputEntity from .exceptions import EntitySchemaError -from .lib import ( - NOT_SET, - STRING_TYPE -) +from .lib import NOT_SET, STRING_TYPE class BaseEnumEntity(InputEntity): @@ -26,15 +23,13 @@ class BaseEnumEntity(InputEntity): for item in self.enum_items: key = tuple(item.keys())[0] if key in enum_keys: - reason = "Key \"{}\" is more than once in enum items.".format( - key - ) + reason = 'Key "{}" is more than once in enum items.'.format(key) raise EntitySchemaError(self, reason) enum_keys.add(key) if not isinstance(key, STRING_TYPE): - reason = "Key \"{}\" has invalid type {}, expected {}.".format( + reason = 'Key "{}" has invalid type {}, expected {}.'.format( key, type(key), STRING_TYPE ) raise EntitySchemaError(self, reason) @@ -59,7 +54,7 @@ class BaseEnumEntity(InputEntity): for item in check_values: if item not in self.valid_keys: raise ValueError( - "{} Invalid value \"{}\". Expected one of: {}".format( + '{} Invalid value "{}". Expected one of: {}'.format( self.path, item, self.valid_keys ) ) @@ -84,7 +79,7 @@ class EnumEntity(BaseEnumEntity): self.valid_keys = set(all_keys) if self.multiselection: - self.valid_value_types = (list, ) + self.valid_value_types = (list,) value_on_not_set = [] if enum_default: if not isinstance(enum_default, list): @@ -109,7 +104,7 @@ class EnumEntity(BaseEnumEntity): self.value_on_not_set = key break - self.valid_value_types = (STRING_TYPE, ) + self.valid_value_types = (STRING_TYPE,) # GUI attribute self.placeholder = self.schema_data.get("placeholder") @@ -152,6 +147,7 @@ class HostsEnumEntity(BaseEnumEntity): Host name is not the same as application name. Host name defines implementation instead of application name. """ + schema_types = ["hosts-enum"] all_host_names = [ "aftereffects", @@ -169,7 +165,7 @@ class HostsEnumEntity(BaseEnumEntity): "tvpaint", "unreal", "standalonepublisher", - "webpublisher" + "webpublisher", ] def _item_initialization(self): @@ -210,7 +206,7 @@ class HostsEnumEntity(BaseEnumEntity): self.valid_keys = valid_keys if self.multiselection: - self.valid_value_types = (list, ) + self.valid_value_types = (list,) self.value_on_not_set = [] else: for key in valid_keys: @@ -218,7 +214,7 @@ class HostsEnumEntity(BaseEnumEntity): self.value_on_not_set = key break - self.valid_value_types = (STRING_TYPE, ) + self.valid_value_types = (STRING_TYPE,) # GUI attribute self.placeholder = self.schema_data.get("placeholder") @@ -226,14 +222,10 @@ class HostsEnumEntity(BaseEnumEntity): def schema_validations(self): if self.hosts_filter: enum_len = len(self.enum_items) - if ( - enum_len == 0 - or (enum_len == 1 and self.use_empty_value) - ): - joined_filters = ", ".join([ - '"{}"'.format(item) - for item in self.hosts_filter - ]) + if enum_len == 0 or (enum_len == 1 and self.use_empty_value): + joined_filters = ", ".join( + ['"{}"'.format(item) for item in self.hosts_filter] + ) reason = ( "All host names were removed after applying" " host filters. {}" @@ -246,24 +238,25 @@ class HostsEnumEntity(BaseEnumEntity): invalid_filters.add(item) if invalid_filters: - joined_filters = ", ".join([ - '"{}"'.format(item) - for item in self.hosts_filter - ]) - expected_hosts = ", ".join([ - '"{}"'.format(item) - for item in self.all_host_names - ]) - self.log.warning(( - "Host filters containt invalid host names:" - " \"{}\" Expected values are {}" - ).format(joined_filters, expected_hosts)) + joined_filters = ", ".join( + ['"{}"'.format(item) for item in self.hosts_filter] + ) + expected_hosts = ", ".join( + ['"{}"'.format(item) for item in self.all_host_names] + ) + self.log.warning( + ( + "Host filters containt invalid host names:" + ' "{}" Expected values are {}' + ).format(joined_filters, expected_hosts) + ) super(HostsEnumEntity, self).schema_validations() class AppsEnumEntity(BaseEnumEntity): """Enum of applications for project anatomy attributes.""" + schema_types = ["apps-enum"] def _item_initialization(self): @@ -271,7 +264,7 @@ class AppsEnumEntity(BaseEnumEntity): self.value_on_not_set = [] self.enum_items = [] self.valid_keys = set() - self.valid_value_types = (list, ) + self.valid_value_types = (list,) self.placeholder = None def _get_enum_values(self): @@ -352,7 +345,7 @@ class ToolsEnumEntity(BaseEnumEntity): self.value_on_not_set = [] self.enum_items = [] self.valid_keys = set() - self.valid_value_types = (list, ) + self.valid_value_types = (list,) self.placeholder = None def _get_enum_values(self): @@ -409,10 +402,10 @@ class TaskTypeEnumEntity(BaseEnumEntity): def _item_initialization(self): self.multiselection = self.schema_data.get("multiselection", True) if self.multiselection: - self.valid_value_types = (list, ) + self.valid_value_types = (list,) self.value_on_not_set = [] else: - self.valid_value_types = (STRING_TYPE, ) + self.valid_value_types = (STRING_TYPE,) self.value_on_not_set = "" self.enum_items = [] @@ -507,7 +500,8 @@ class DeadlineUrlEnumEntity(BaseEnumEntity): enum_items_list = [] for server_name, url_entity in deadline_urls_entity.items(): enum_items_list.append( - {server_name: "{}: {}".format(server_name, url_entity.value)}) + {server_name: "{}: {}".format(server_name, url_entity.value)} + ) valid_keys.add(server_name) return enum_items_list, valid_keys @@ -530,6 +524,50 @@ class DeadlineUrlEnumEntity(BaseEnumEntity): self._current_value = tuple(self.valid_keys)[0] +class ShotgridUrlEnumEntity(BaseEnumEntity): + schema_types = ["shotgrid_url-enum"] + + def _item_initialization(self): + self.multiselection = False + + self.enum_items = [] + self.valid_keys = set() + + self.valid_value_types = (STRING_TYPE,) + self.value_on_not_set = "" + + # GUI attribute + self.placeholder = self.schema_data.get("placeholder") + + def _get_enum_values(self): + shotgrid_settings = self.get_entity_from_path( + "system_settings/modules/shotgrid/shotgrid_settings" + ) + + valid_keys = set() + enum_items_list = [] + for server_name, settings in shotgrid_settings.items(): + enum_items_list.append( + { + server_name: "{}: {}".format( + server_name, settings["shotgrid_url"].value + ) + } + ) + valid_keys.add(server_name) + return enum_items_list, valid_keys + + def set_override_state(self, *args, **kwargs): + super(ShotgridUrlEnumEntity, self).set_override_state(*args, **kwargs) + + self.enum_items, self.valid_keys = self._get_enum_values() + if not self.valid_keys: + self._current_value = "" + + elif self._current_value not in self.valid_keys: + self._current_value = tuple(self.valid_keys)[0] + + class AnatomyTemplatesEnumEntity(BaseEnumEntity): schema_types = ["anatomy-templates-enum"] diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json index 8e4eba86ef..521c066964 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_main.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json @@ -62,6 +62,10 @@ "type": "schema", "name": "schema_project_ftrack" }, + { + "type": "schema", + "name": "schema_project_shotgrid" + }, { "type": "schema", "name": "schema_project_deadline" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_shotgrid.json b/openpype/settings/entities/schemas/projects_schema/schema_project_shotgrid.json new file mode 100644 index 0000000000..4faeca89f3 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_shotgrid.json @@ -0,0 +1,98 @@ +{ + "type": "dict", + "key": "shotgrid", + "label": "Shotgrid", + "collapsible": true, + "is_file": true, + "children": [ + { + "type": "number", + "key": "shotgrid_project_id", + "label": "Shotgrid project id" + }, + { + "type": "shotgrid_url-enum", + "key": "shotgrid_server", + "label": "Shotgrid Server" + }, + { + "type": "dict", + "key": "event", + "label": "Event Handler", + "collapsible": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, + { + "type": "dict", + "key": "fields", + "label": "Fields Template", + "collapsible": true, + "children": [ + { + "type": "dict", + "key": "asset", + "label": "Asset", + "collapsible": true, + "children": [ + { + "type": "text", + "key": "type", + "label": "Asset Type" + } + ] + }, + { + "type": "dict", + "key": "sequence", + "label": "Sequence", + "collapsible": true, + "children": [ + { + "type": "text", + "key": "episode_link", + "label": "Episode link" + } + ] + }, + { + "type": "dict", + "key": "shot", + "label": "Shot", + "collapsible": true, + "children": [ + { + "type": "text", + "key": "episode_link", + "label": "Episode link" + }, + { + "type": "text", + "key": "sequence_link", + "label": "Sequence link" + } + ] + }, + { + "type": "dict", + "key": "task", + "label": "Task", + "collapsible": true, + "children": [ + { + "type": "text", + "key": "step", + "label": "Step link" + } + ] + } + ] + } + ] +} diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json index 484fbf9d07..a4b28f47bc 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json @@ -13,6 +13,9 @@ { "ftrackreview": "Add review to Ftrack" }, + { + "shotgridreview": "Add review to Shotgrid" + }, { "delete": "Delete output" }, diff --git a/openpype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json index 52595914ed..dacbc8c3a1 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_modules.json +++ b/openpype/settings/entities/schemas/system_schema/schema_modules.json @@ -44,6 +44,60 @@ "type": "schema", "name": "schema_ftrack" }, + { + "type": "dict", + "key": "shotgrid", + "label": "Shotgrid", + "collapsible": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "leecher_manager_url", + "label": "Shotgrid Leecher Manager URL" + }, + { + "type": "text", + "key": "leecher_backend_url", + "label": "Shotgrid Leecher Backend URL" + }, + { + "type": "boolean", + "key": "filter_projects_by_login", + "label": "Filter projects by SG login" + }, + { + "type": "dict-modifiable", + "key": "shotgrid_settings", + "label": "Shotgrid Servers", + "object_type": { + "type": "dict", + "children": [ + { + "key": "shotgrid_url", + "label": "Server URL", + "type": "text" + }, + { + "key": "shotgrid_script_name", + "label": "Script Name", + "type": "text" + }, + { + "key": "shotgrid_script_key", + "label": "Script api key", + "type": "text" + } + ] + } + } + ] + }, { "type": "dict", "key": "timers_manager", diff --git a/pyproject.toml b/pyproject.toml index 006f6eb4e5..8d9eb8b050 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ clique = "1.6.*" Click = "^7" dnspython = "^2.1.0" ftrack-python-api = "2.0.*" +shotgun_api3 = {git = "https://github.com/shotgunsoftware/python-api.git", rev = "v3.3.3"} google-api-python-client = "^1.12.8" # sync server google support (should be separate?) jsonschema = "^2.6.0" keyring = "^22.0.1" From 00285cacc0d01dea62495cdb04f7155cb5d3da1d Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Fri, 18 Mar 2022 17:47:29 +0100 Subject: [PATCH 0084/1227] Fix hound complaints --- .../publish/collect_shotgrid_session.py | 3 +-- .../plugins/publish/validate_shotgrid_user.py | 24 ++++++++++--------- openpype/modules/shotgrid/shotgrid_module.py | 4 +++- openpype/settings/entities/enum_entity.py | 4 +++- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py index 65a5de9f22..b9eead6244 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py @@ -1,5 +1,4 @@ import os -import sys import pyblish.api import shotgun_api3 from shotgun_api3.shotgun import AuthenticationFault @@ -128,5 +127,5 @@ def get_login(): reg = OpenPypeSettingsRegistry() try: return str(reg.get_item("shotgrid_login")) - except Exception as e: + except Exception as _: return None diff --git a/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py b/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py index 7343c47808..c14c980e2a 100644 --- a/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py +++ b/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py @@ -4,16 +4,16 @@ import openpype.api class ValidateShotgridUser(pyblish.api.ContextPlugin): """ - Check if user is valid and have access to the project. + Check if user is valid and have access to the project. """ label = "Validate Shotgrid User" order = openpype.api.ValidateContentsOrder def process(self, context): - sg = context.data.get('shotgridSession') + sg = context.data.get("shotgridSession") - login = context.data.get('shotgridUser') + login = context.data.get("shotgridUser") self.log.info("Login shotgrid set in OpenPype is {}".format(login)) project = context.data.get("shotgridProject") self.log.info("Current shotgun project is {}".format(project)) @@ -21,16 +21,18 @@ class ValidateShotgridUser(pyblish.api.ContextPlugin): if not (login and sg and project): raise KeyError() - user = sg.find_one( - "HumanUser", - [["login", "is", login]], - ["projects"] - ) + user = sg.find_one("HumanUser", [["login", "is", login]], ["projects"]) self.log.info(user) self.log.info(login) user_projects_id = [p["id"] for p in user.get("projects", [])] - if not project.get('id') in user_projects_id: - raise PermissionError("Login {} don't have access to the project {}".format(login, project)) + if not project.get("id") in user_projects_id: + raise PermissionError( + "Login {} don't have access to the project {}".format( + login, project + ) + ) - self.log.info("Login {} have access to the project {}".format(login, project)) + self.log.info( + "Login {} have access to the project {}".format(login, project) + ) diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index 75d5f843c5..222c4c2e1f 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -43,7 +43,9 @@ class ShotgridModule( def get_plugin_paths(self) -> Dict[str, Any]: return { - "publish": [os.path.join(SHOTGRID_MODULE_DIR, "plugins", "publish")] + "publish": [ + os.path.join(SHOTGRID_MODULE_DIR, "plugins", "publish") + ] } def get_launch_hook_paths(self) -> str: diff --git a/openpype/settings/entities/enum_entity.py b/openpype/settings/entities/enum_entity.py index 643bd5735d..3b3dd47e61 100644 --- a/openpype/settings/entities/enum_entity.py +++ b/openpype/settings/entities/enum_entity.py @@ -23,7 +23,9 @@ class BaseEnumEntity(InputEntity): for item in self.enum_items: key = tuple(item.keys())[0] if key in enum_keys: - reason = 'Key "{}" is more than once in enum items.'.format(key) + reason = 'Key "{}" is more than once in enum items.'.format( + key + ) raise EntitySchemaError(self, reason) enum_keys.add(key) From feae04f0c34abdd87bcb6703962ac66c98e33831 Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Fri, 18 Mar 2022 17:49:42 +0100 Subject: [PATCH 0085/1227] Fix hound Exception complaints --- .../shotgrid/plugins/publish/collect_shotgrid_session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py index b9eead6244..edf804a892 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py @@ -127,5 +127,5 @@ def get_login(): reg = OpenPypeSettingsRegistry() try: return str(reg.get_item("shotgrid_login")) - except Exception as _: + except Exception: return None From 521b56289dea9c7697dfc44bf8d8095278e377c9 Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Fri, 25 Mar 2022 10:07:49 +0100 Subject: [PATCH 0086/1227] Add fixes after first PR --- .editorconfig | 19 -------------- .gitignore | 3 +++ .pre-commit-config.yaml | 26 ------------------- mypy.ini | 5 ---- .../plugins/publish/submit_publish_job.py | 1 - openpype/modules/shotgrid/lib/settings.py | 3 +++ .../publish/collect_shotgrid_session.py | 10 +------ 7 files changed, 7 insertions(+), 60 deletions(-) delete mode 100644 .editorconfig delete mode 100644 .pre-commit-config.yaml delete mode 100644 mypy.ini diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index aeb5534872..0000000000 --- a/.editorconfig +++ /dev/null @@ -1,19 +0,0 @@ -root = true - -[*] -end_of_line = lf -insert_final_newline = true - -[*.{js,py}] -charset = utf-8 - -[*.py] -indent_style = space -indent_size = 4 - -[*.yml] -indent_style = space -indent_size = 2 - -[Makefile] -indent_style = tab diff --git a/.gitignore b/.gitignore index fa3fae1ad2..f90549d0c0 100644 --- a/.gitignore +++ b/.gitignore @@ -100,3 +100,6 @@ website/.docusaurus .poetry/ .python-version +.editorconfig +.pre-commit-config.yaml +mypy.ini diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 6a986c7dd9..0000000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,26 +0,0 @@ -repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: check-added-large-files -- repo: https://github.com/ambv/black - rev: 21.4b0 - hooks: - - id: black - language_version: "3" - args: - - "--config" - - "./pyproject.toml" -- repo: https://github.com/pycqa/flake8 - rev: "3.9.2" # pick a git hash / tag to point to - hooks: - - id: flake8 -- repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v0.902' - hooks: - - id: mypy - args: [--no-strict-optional, --ignore-missing-imports] - additional_dependencies: [tokenize-rt==3.2.0] diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 90cde26676..0000000000 --- a/mypy.ini +++ /dev/null @@ -1,5 +0,0 @@ -[mypy] -python_version = 3.7 -ignore_missing_imports = false -check_untyped_defs = true -follow_imports = silent diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index e1e1ea6b94..3c4e0d2913 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -113,7 +113,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "celaction": [r".*"]} enviro_filter = [ - "OPENPYPE_SG_USER", "FTRACK_API_USER", "FTRACK_API_KEY", "FTRACK_SERVER", diff --git a/openpype/modules/shotgrid/lib/settings.py b/openpype/modules/shotgrid/lib/settings.py index 0f4fc235cc..b34407fbf5 100644 --- a/openpype/modules/shotgrid/lib/settings.py +++ b/openpype/modules/shotgrid/lib/settings.py @@ -1,4 +1,5 @@ import os +from functools import lru_cache from typing import Tuple, Dict, List, Any from pymongo import MongoClient @@ -13,10 +14,12 @@ def get_project_list() -> List[str]: return db.list_collection_names() +@lru_cache(maxsize=64) def get_shotgrid_project_settings(project: str) -> Dict[str, Any]: return get_project_settings(project).get(MODULE_NAME, {}) +@lru_cache(maxsize=64) def get_shotgrid_settings() -> Dict[str, Any]: return get_system_settings().get("modules", {}).get(MODULE_NAME, {}) diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py index edf804a892..60071ad2fc 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py @@ -4,6 +4,7 @@ import shotgun_api3 from shotgun_api3.shotgun import AuthenticationFault from openpype.lib import OpenPypeSettingsRegistry from openpype.api import get_project_settings, get_system_settings +from openpype.modules.shotgrid.lib.settings import get_shotgrid_servers class CollectShotgridSession(pyblish.api.ContextPlugin): @@ -114,15 +115,6 @@ def get_shotgrid_settings(project): return get_project_settings(project).get("shotgrid", {}) -def get_shotgrid_servers(): - return ( - get_system_settings() - .get("modules", {}) - .get("shotgrid", {}) - .get("shotgrid_settings", {}) - ) - - def get_login(): reg = OpenPypeSettingsRegistry() try: From 68941da3c8c08c5995c933d8b21317de6ddb73cc Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Fri, 25 Mar 2022 10:33:23 +0100 Subject: [PATCH 0087/1227] Add fixes for hound --- .../plugins/publish/collect_shotgrid_entities.py | 10 ++++------ .../plugins/publish/collect_shotgrid_session.py | 14 +++++++------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py index b89dda3a80..6778b26550 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py @@ -1,7 +1,9 @@ import os + import pyblish.api from pymongo import MongoClient -from openpype.api import get_project_settings + +from openpype.modules.shotgrid.lib.settings import get_shotgrid_project_settings class CollectShotgridEntities(pyblish.api.ContextPlugin): @@ -64,12 +66,8 @@ def _get_shotgrid_collection(project): return client.get_database("shotgrid_openpype").get_collection(project) -def _get_shotgrid_project_settings(project): - return get_project_settings(project).get("shotgrid", {}) - - def _get_shotgrid_project(avalon_project): - proj_settings = _get_shotgrid_project_settings(avalon_project["name"]) + proj_settings = get_shotgrid_project_settings(avalon_project["name"]) shotgrid_project_id = proj_settings.get("shotgrid_project_id") if shotgrid_project_id: return {"type": "Project", "id": shotgrid_project_id} diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py index 60071ad2fc..9d5d2271bf 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py @@ -1,10 +1,14 @@ import os + import pyblish.api import shotgun_api3 from shotgun_api3.shotgun import AuthenticationFault + from openpype.lib import OpenPypeSettingsRegistry -from openpype.api import get_project_settings, get_system_settings -from openpype.modules.shotgrid.lib.settings import get_shotgrid_servers +from openpype.modules.shotgrid.lib.settings import ( + get_shotgrid_servers, + get_shotgrid_project_settings, +) class CollectShotgridSession(pyblish.api.ContextPlugin): @@ -40,7 +44,7 @@ class CollectShotgridSession(pyblish.api.ContextPlugin): avalon_project = os.getenv("AVALON_PROJECT") - shotgrid_settings = get_shotgrid_settings(avalon_project) + shotgrid_settings = get_shotgrid_project_settings(avalon_project) self.log.info("shotgrid settings: {}".format(shotgrid_settings)) shotgrid_servers_settings = get_shotgrid_servers() self.log.info( @@ -111,10 +115,6 @@ def set_shotgrid_certificate(certificate): os.environ["SHOTGUN_API_CACERTS"] = certificate -def get_shotgrid_settings(project): - return get_project_settings(project).get("shotgrid", {}) - - def get_login(): reg = OpenPypeSettingsRegistry() try: From 1214cb995de0ae33ed52ab788c3df00f4655ac8d Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Fri, 25 Mar 2022 10:36:27 +0100 Subject: [PATCH 0088/1227] Remove an empty line --- repos/avalon-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/avalon-core b/repos/avalon-core index 2fa14cea6f..2200525320 160000 --- a/repos/avalon-core +++ b/repos/avalon-core @@ -1 +1 @@ -Subproject commit 2fa14cea6f6a9d86eec70bbb96860cbe4c75c8eb +Subproject commit 2200525320923f17df3b4c3b19ebd737c8a7e625 From d106ba4694ab542f4c496d2b1cc2e5d693386657 Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Fri, 25 Mar 2022 10:38:12 +0100 Subject: [PATCH 0089/1227] Reformat code after hound barking --- .../shotgrid/plugins/publish/collect_shotgrid_entities.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py index 6778b26550..a770c1eb87 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py @@ -3,7 +3,9 @@ import os import pyblish.api from pymongo import MongoClient -from openpype.modules.shotgrid.lib.settings import get_shotgrid_project_settings +from openpype.modules.shotgrid.lib.settings import ( + get_shotgrid_project_settings, +) class CollectShotgridEntities(pyblish.api.ContextPlugin): From 3c1ecfb36e1e29a7d3eb0b3845356dd9f8cda155 Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Fri, 8 Apr 2022 15:12:29 +0200 Subject: [PATCH 0090/1227] Drawn back to the obsolete version of python --- openpype/modules/shotgrid/aop/patch.py | 11 +- openpype/modules/shotgrid/lib/credentials.py | 30 +- openpype/modules/shotgrid/lib/record.py | 18 +- openpype/modules/shotgrid/lib/server.py | 3 +- openpype/modules/shotgrid/lib/settings.py | 15 +- openpype/modules/shotgrid/shotgrid_module.py | 19 +- .../shotgrid/tray/credential_dialog.py | 37 +- .../modules/shotgrid/tray/shotgrid_tray.py | 11 +- poetry.lock | 812 +++++++++--------- 9 files changed, 493 insertions(+), 463 deletions(-) diff --git a/openpype/modules/shotgrid/aop/patch.py b/openpype/modules/shotgrid/aop/patch.py index 6ca81033e2..208ca715d3 100644 --- a/openpype/modules/shotgrid/aop/patch.py +++ b/openpype/modules/shotgrid/aop/patch.py @@ -1,5 +1,4 @@ from copy import copy -from typing import Any, Iterator, Dict, Set from avalon.api import AvalonMongoDB @@ -14,8 +13,8 @@ _LOG = Logger().get_logger("ShotgridModule.patch") def _patched_projects( - self: Any, projection: Any = None, only_active: bool = True -) -> Iterator[Dict[str, Any]]: + self, projection=None, only_active=True +): all_projects = list(self._prev_projects(projection, only_active)) if ( not credentials.get_local_login() @@ -30,18 +29,18 @@ def _patched_projects( return all_projects -def _upper(x: Any) -> str: +def _upper(x): return str(x).strip().upper() -def _fetch_linked_project_names() -> Set[str]: +def _fetch_linked_project_names(): return { _upper(x["project_name"]) for x in server.find_linked_projects(credentials.get_local_login()) } -def patch_avalon_db() -> None: +def patch_avalon_db(): _LOG.debug("Run avalon patching") if AvalonMongoDB.projects is _patched_projects: return None diff --git a/openpype/modules/shotgrid/lib/credentials.py b/openpype/modules/shotgrid/lib/credentials.py index a334968cda..337c4f6ecb 100644 --- a/openpype/modules/shotgrid/lib/credentials.py +++ b/openpype/modules/shotgrid/lib/credentials.py @@ -1,4 +1,4 @@ -from typing import Tuple, Optional + from urllib.parse import urlparse import shotgun_api3 @@ -8,21 +8,21 @@ from openpype.lib import OpenPypeSecureRegistry, OpenPypeSettingsRegistry from openpype.modules.shotgrid.lib.record import Credentials -def _get_shotgrid_secure_key(hostname: str, key: str) -> str: +def _get_shotgrid_secure_key(hostname, key): """Secure item key for entered hostname.""" return f"shotgrid/{hostname}/{key}" def _get_secure_value_and_registry( - hostname: str, - name: str, -) -> Tuple[str, OpenPypeSecureRegistry]: + hostname, + name, +): key = _get_shotgrid_secure_key(hostname, name) registry = OpenPypeSecureRegistry(key) return registry.get_item(name, None), registry -def get_shotgrid_hostname(shotgrid_url: str) -> str: +def get_shotgrid_hostname(shotgrid_url): if not shotgrid_url: raise Exception("Shotgrid url cannot be a null") @@ -35,7 +35,7 @@ def get_shotgrid_hostname(shotgrid_url: str) -> str: # Credentials storing function (using keyring) -def get_credentials(shotgrid_url: str) -> Optional[Credentials]: +def get_credentials(shotgrid_url): hostname = get_shotgrid_hostname(shotgrid_url) if not hostname: return None @@ -50,7 +50,7 @@ def get_credentials(shotgrid_url: str) -> Optional[Credentials]: return Credentials(login_value, password_value) -def save_credentials(login: str, password: str, shotgrid_url: str): +def save_credentials(login, password, shotgrid_url): hostname = get_shotgrid_hostname(shotgrid_url) _, login_registry = _get_secure_value_and_registry( hostname, @@ -65,7 +65,7 @@ def save_credentials(login: str, password: str, shotgrid_url: str): password_registry.set_item(Credentials.password_key_prefix(), password) -def clear_credentials(shotgrid_url: str): +def clear_credentials(shotgrid_url): hostname = get_shotgrid_hostname(shotgrid_url) login_value, login_registry = _get_secure_value_and_registry( hostname, @@ -86,7 +86,7 @@ def clear_credentials(shotgrid_url: str): # Login storing function (using json) -def get_local_login() -> Optional[str]: +def get_local_login(): reg = OpenPypeSettingsRegistry() try: return str(reg.get_item("shotgrid_login")) @@ -94,7 +94,7 @@ def get_local_login() -> Optional[str]: return None -def save_local_login(login: str): +def save_local_login(login): reg = OpenPypeSettingsRegistry() reg.set_item("shotgrid_login", login) @@ -105,10 +105,10 @@ def clear_local_login(): def check_credentials( - login: str, - password: str, - shotgrid_url: str, -) -> bool: + login, + password, + shotgrid_url, +): if not shotgrid_url or not login or not password: return False diff --git a/openpype/modules/shotgrid/lib/record.py b/openpype/modules/shotgrid/lib/record.py index 1796e6c019..f62f4855d5 100644 --- a/openpype/modules/shotgrid/lib/record.py +++ b/openpype/modules/shotgrid/lib/record.py @@ -1,18 +1,20 @@ -from dataclasses import dataclass - -@dataclass(frozen=True) class Credentials: - login: str - password: str + login = None + password = None - def is_empty(self) -> bool: + def __init__(self, login, password) -> None: + super().__init__() + self.login = login + self.password = password + + def is_empty(self): return not (self.login and self.password) @staticmethod - def login_key_prefix() -> str: + def login_key_prefix(): return "login" @staticmethod - def password_key_prefix() -> str: + def password_key_prefix(): return "password" diff --git a/openpype/modules/shotgrid/lib/server.py b/openpype/modules/shotgrid/lib/server.py index 0fe4b8429e..50645d4089 100644 --- a/openpype/modules/shotgrid/lib/server.py +++ b/openpype/modules/shotgrid/lib/server.py @@ -1,7 +1,6 @@ import traceback import requests -from typing import Dict, Any, List from openpype.api import Logger from openpype.modules.shotgrid.lib import ( @@ -11,7 +10,7 @@ from openpype.modules.shotgrid.lib import ( _LOG = Logger().get_logger("ShotgridModule.server") -def find_linked_projects(email: str) -> List[Dict[str, Any]]: +def find_linked_projects(email): url = "".join( [ settings_lib.get_leecher_backend_url(), diff --git a/openpype/modules/shotgrid/lib/settings.py b/openpype/modules/shotgrid/lib/settings.py index b34407fbf5..954b96f3c2 100644 --- a/openpype/modules/shotgrid/lib/settings.py +++ b/openpype/modules/shotgrid/lib/settings.py @@ -1,13 +1,12 @@ import os from functools import lru_cache -from typing import Tuple, Dict, List, Any from pymongo import MongoClient from openpype.api import get_system_settings, get_project_settings from openpype.modules.shotgrid.lib.const import MODULE_NAME -def get_project_list() -> List[str]: +def get_project_list(): mongo_url = os.getenv("OPENPYPE_MONGO") client = MongoClient(mongo_url) db = client['avalon'] @@ -15,28 +14,28 @@ def get_project_list() -> List[str]: @lru_cache(maxsize=64) -def get_shotgrid_project_settings(project: str) -> Dict[str, Any]: +def get_shotgrid_project_settings(project): return get_project_settings(project).get(MODULE_NAME, {}) @lru_cache(maxsize=64) -def get_shotgrid_settings() -> Dict[str, Any]: +def get_shotgrid_settings(): return get_system_settings().get("modules", {}).get(MODULE_NAME, {}) -def get_shotgrid_servers() -> Dict[str, Any]: +def get_shotgrid_servers(): return get_shotgrid_settings().get("shotgrid_settings", {}) -def get_leecher_backend_url() -> str: +def get_leecher_backend_url(): return get_shotgrid_settings().get("leecher_backend_url") -def filter_projects_by_login() -> bool: +def filter_projects_by_login(): return bool(get_shotgrid_settings().get("filter_projects_by_login", False)) -def get_shotgrid_event_mongo_info() -> Tuple[str, str]: +def get_shotgrid_event_mongo_info(): database_name = os.environ["OPENPYPE_DATABASE_NAME"] collection_name = "shotgrid_events" return database_name, collection_name diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index 222c4c2e1f..1f05ae5a52 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -1,6 +1,5 @@ import os import threading -from typing import Optional, Dict, Any from openpype_interfaces import ( ITrayModule, @@ -20,13 +19,13 @@ SHOTGRID_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) class ShotgridModule( OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths ): - leecher_manager_url: str - name: str = "shotgrid" - enabled: bool = False - project_id: Optional[str] = None - tray_wrapper: ShotgridTrayWrapper + leecher_manager_url = None + name = "shotgrid" + enabled = False + project_id = None + tray_wrapper = None - def initialize(self, modules_settings: Dict[str, Any]): + def initialize(self, modules_settings): patch_avalon_db() threading.Timer(10.0, patch_avalon_db).start() shotgrid_settings = modules_settings.get(self.name, dict()) @@ -38,17 +37,17 @@ class ShotgridModule( def connect_with_modules(self, enabled_modules): pass - def get_global_environments(self) -> Dict[str, Any]: + def get_global_environments(self): return {"PROJECT_ID": self.project_id} - def get_plugin_paths(self) -> Dict[str, Any]: + def get_plugin_paths(self): return { "publish": [ os.path.join(SHOTGRID_MODULE_DIR, "plugins", "publish") ] } - def get_launch_hook_paths(self) -> str: + def get_launch_hook_paths(self): return os.path.join(SHOTGRID_MODULE_DIR, "hooks") def tray_init(self): diff --git a/openpype/modules/shotgrid/tray/credential_dialog.py b/openpype/modules/shotgrid/tray/credential_dialog.py index 8d7d587c6a..9d841d98be 100644 --- a/openpype/modules/shotgrid/tray/credential_dialog.py +++ b/openpype/modules/shotgrid/tray/credential_dialog.py @@ -1,5 +1,4 @@ import os -from typing import Any from Qt import QtCore, QtWidgets, QtGui from openpype import style @@ -11,20 +10,20 @@ class CredentialsDialog(QtWidgets.QDialog): SIZE_W = 450 SIZE_H = 200 - _module: Any = None - _is_logged: bool = False - url_label: QtWidgets.QLabel - login_label: QtWidgets.QLabel - password_label: QtWidgets.QLabel - url_input: QtWidgets.QComboBox - login_input: QtWidgets.QLineEdit - password_input: QtWidgets.QLineEdit - input_layout: QtWidgets.QFormLayout - login_button: QtWidgets.QPushButton - buttons_layout: QtWidgets.QHBoxLayout - main_widget: QtWidgets.QVBoxLayout + _module = None + _is_logged = False + url_label = None + login_label = None + password_label = None + url_input = None + login_input = None + password_input = None + input_layout = None + login_button = None + buttons_layout = None + main_widget = None - login_changed: QtCore.Signal = QtCore.Signal() + login_changed = QtCore.Signal() def __init__(self, module, parent=None): super(CredentialsDialog, self).__init__(parent) @@ -168,7 +167,7 @@ class CredentialsDialog(QtWidgets.QDialog): self._clear_shotgrid_login() self._on_logout() - def set_error(self, msg: str): + def set_error(self, msg): self.error_label.setText(msg) self.error_label.show() @@ -184,15 +183,15 @@ class CredentialsDialog(QtWidgets.QDialog): def _close_widget(self): self.hide() - def _valid_input(self, input_widget: QtWidgets.QLineEdit): + def _valid_input(self, input_widget): input_widget.setStyleSheet("") - def _invalid_input(self, input_widget: QtWidgets.QLineEdit): + def _invalid_input(self, input_widget): input_widget.setStyleSheet("border: 1px solid red;") def login_with_credentials( - self, url: str, login: str, password: str - ) -> bool: + self, url, login, password + ): verification = credentials.check_credentials(url, login, password) if verification: credentials.save_credentials(login, password, False) diff --git a/openpype/modules/shotgrid/tray/shotgrid_tray.py b/openpype/modules/shotgrid/tray/shotgrid_tray.py index 0be58e3b20..4038d77b03 100644 --- a/openpype/modules/shotgrid/tray/shotgrid_tray.py +++ b/openpype/modules/shotgrid/tray/shotgrid_tray.py @@ -1,6 +1,5 @@ import os import webbrowser -from typing import Any from Qt import QtWidgets @@ -11,11 +10,11 @@ from openpype.modules.shotgrid.tray.credential_dialog import ( class ShotgridTrayWrapper: - module: Any - credentials_dialog: CredentialsDialog - logged_user_label: QtWidgets.QAction + module = None + credentials_dialog = None + logged_user_label = None - def __init__(self, module) -> None: + def __init__(self, module): self.module = module self.credentials_dialog = CredentialsDialog(module) self.credentials_dialog.login_changed.connect(self.set_login_label) @@ -65,7 +64,7 @@ class ShotgridTrayWrapper: ) m.addAction(shotgrid_manager_action) - def validate(self) -> bool: + def validate(self): login = credentials.get_local_login() if not login: diff --git a/poetry.lock b/poetry.lock index 7998ede693..4372f34ff7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -94,7 +94,7 @@ python-dateutil = ">=2.7.0" [[package]] name = "astroid" -version = "2.9.3" +version = "2.11.2" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -104,7 +104,7 @@ python-versions = ">=3.6.2" lazy-object-proxy = ">=1.4.0" typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} -wrapt = ">=1.11,<1.14" +wrapt = ">=1.11,<2" [[package]] name = "async-timeout" @@ -236,7 +236,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "charset-normalizer" -version = "2.0.11" +version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -295,7 +295,7 @@ python-versions = "*" [[package]] name = "coverage" -version = "6.3.1" +version = "6.3.2" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -309,7 +309,7 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "36.0.1" +version = "36.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false @@ -346,9 +346,20 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "dill" +version = "0.3.4" +description = "serialize all of python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*" + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + [[package]] name = "dnspython" -version = "2.2.0" +version = "2.2.1" description = "DNS toolkit" category = "main" optional = false @@ -364,7 +375,7 @@ wmi = ["wmi (>=1.5.1,<2.0.0)"] [[package]] name = "docutils" -version = "0.18.1" +version = "0.17.1" description = "Docutils -- Python Documentation Utilities" category = "dev" optional = false @@ -372,7 +383,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "dropbox" -version = "11.26.0" +version = "11.29.0" description = "Official Dropbox API Client" category = "main" optional = false @@ -397,7 +408,7 @@ prefixed = ">=0.3.2" [[package]] name = "evdev" -version = "1.4.0" +version = "1.5.0" description = "Bindings to the Linux input handling subsystem" category = "main" optional = false @@ -464,7 +475,7 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.26" +version = "3.1.27" description = "GitPython is a python library used to interact with Git repositories" category = "dev" optional = false @@ -476,7 +487,7 @@ typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\"" [[package]] name = "google-api-core" -version = "2.4.0" +version = "2.7.1" description = "Google API client core library" category = "main" optional = false @@ -495,7 +506,7 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2)"] [[package]] name = "google-api-python-client" -version = "1.12.10" +version = "1.12.11" description = "Google API Client Library for Python" category = "main" optional = false @@ -511,7 +522,7 @@ uritemplate = ">=3.0.0,<4dev" [[package]] name = "google-auth" -version = "2.6.0" +version = "2.6.3" description = "Google Authentication Library" category = "main" optional = false @@ -543,7 +554,7 @@ six = "*" [[package]] name = "googleapis-common-protos" -version = "1.54.0" +version = "1.56.0" description = "Common protobufs used in Google APIs" category = "main" optional = false @@ -557,7 +568,7 @@ grpc = ["grpcio (>=1.0.0)"] [[package]] name = "httplib2" -version = "0.20.2" +version = "0.20.4" description = "A comprehensive HTTP client library." category = "main" optional = false @@ -584,7 +595,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.10.1" +version = "4.11.3" description = "Read metadata from Python packages" category = "main" optional = false @@ -595,9 +606,9 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -637,14 +648,14 @@ testing = ["colorama", "docopt", "pytest (>=3.1.0)"] [[package]] name = "jeepney" -version = "0.7.1" +version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] -test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio", "async-timeout"] +test = ["pytest", "pytest-trio", "pytest-asyncio (>=0.17)", "testpath", "trio", "async-timeout"] trio = ["trio", "async-generator"] [[package]] @@ -722,11 +733,11 @@ pymongo = "*" [[package]] name = "markupsafe" -version = "2.0.1" +version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "mccabe" @@ -777,7 +788,7 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "paramiko" -version = "2.10.1" +version = "2.10.3" description = "SSH2 protocol library" category = "main" optional = false @@ -809,7 +820,7 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pathlib2" -version = "2.3.6" +version = "2.3.7.post1" description = "Object-oriented filesystem paths" category = "main" optional = false @@ -820,15 +831,19 @@ six = "*" [[package]] name = "pillow" -version = "9.0.0" +version = "9.1.0" description = "Python Imaging Library (Fork)" category = "main" optional = false python-versions = ">=3.7" +[package.extras] +docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + [[package]] name = "platformdirs" -version = "2.4.1" +version = "2.5.1" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false @@ -871,11 +886,11 @@ python-versions = "*" [[package]] name = "protobuf" -version = "3.19.4" +version = "3.20.0" description = "Protocol Buffers" category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" [[package]] name = "py" @@ -966,21 +981,25 @@ python-versions = ">=3.5" [[package]] name = "pylint" -version = "2.12.2" +version = "2.13.5" description = "python code static checker" category = "dev" optional = false python-versions = ">=3.6.2" [package.dependencies] -astroid = ">=2.9.0,<2.10" +astroid = ">=2.11.2,<=2.12.0-dev0" colorama = {version = "*", markers = "sys_platform == \"win32\""} +dill = ">=0.2" isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.7" +mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" -toml = ">=0.9.2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} +[package.extras] +testutil = ["gitpython (>3)"] + [[package]] name = "pymongo" version = "3.12.3" @@ -1031,7 +1050,7 @@ six = "*" [[package]] name = "pyobjc-core" -version = "8.2" +version = "8.4.1" description = "Python<->ObjC Interoperability Module" category = "main" optional = false @@ -1039,39 +1058,39 @@ python-versions = ">=3.6" [[package]] name = "pyobjc-framework-applicationservices" -version = "8.2" +version = "8.4.1" description = "Wrappers for the framework ApplicationServices on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.2" -pyobjc-framework-Cocoa = ">=8.2" -pyobjc-framework-Quartz = ">=8.2" +pyobjc-core = ">=8.4.1" +pyobjc-framework-Cocoa = ">=8.4.1" +pyobjc-framework-Quartz = ">=8.4.1" [[package]] name = "pyobjc-framework-cocoa" -version = "8.2" +version = "8.4.1" description = "Wrappers for the Cocoa frameworks on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.2" +pyobjc-core = ">=8.4.1" [[package]] name = "pyobjc-framework-quartz" -version = "8.2" +version = "8.4.1" description = "Wrappers for the Quartz frameworks on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.2" -pyobjc-framework-Cocoa = ">=8.2" +pyobjc-core = ">=8.4.1" +pyobjc-framework-Cocoa = ">=8.4.1" [[package]] name = "pyparsing" @@ -1175,7 +1194,7 @@ python-versions = "*" [[package]] name = "pytz" -version = "2021.3" +version = "2022.1" description = "World timezone definitions, modern and historical" category = "dev" optional = false @@ -1287,6 +1306,21 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "shotgun-api3" +version = "3.3.3" +description = "Shotgun Python API" +category = "main" +optional = false +python-versions = "*" +develop = false + +[package.source] +type = "git" +url = "https://github.com/shotgunsoftware/python-api.git" +reference = "v3.3.3" +resolved_reference = "b9f066c0edbea6e0733242e18f32f75489064840" + [[package]] name = "six" version = "1.16.0" @@ -1297,7 +1331,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "slack-sdk" -version = "3.13.0" +version = "3.15.2" description = "The Slack API Platform SDK for Python" category = "main" optional = false @@ -1305,7 +1339,7 @@ python-versions = ">=3.6.0" [package.extras] optional = ["aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "SQLAlchemy (>=1,<2)", "websockets (>=10,<11)", "websocket-client (>=1,<2)"] -testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==21.12b0)", "psutil (>=5,<6)", "databases (>=0.3)", "boto3 (<=2)", "moto (<2)"] +testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "itsdangerous (==2.0.1)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==22.1.0)", "psutil (>=5,<6)", "databases (>=0.5)", "boto3 (<=2)", "moto (>=3,<4)"] [[package]] name = "smmap" @@ -1325,7 +1359,7 @@ python-versions = "*" [[package]] name = "speedcopy" -version = "2.1.2" +version = "2.1.3" description = "Replacement or alternative for python copyfile() utilizing server side copy on network shares for faster copying." category = "main" optional = false @@ -1333,18 +1367,19 @@ python-versions = "*" [[package]] name = "sphinx" -version = "3.5.3" +version = "4.5.0" description = "Python documentation generator" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=1.3" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.12" +docutils = ">=0.14,<0.18" imagesize = "*" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} Jinja2 = ">=2.3" packaging = "*" Pygments = ">=2.0" @@ -1352,19 +1387,19 @@ requests = ">=2.5.0" snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "docutils-stubs", "types-typed-ast", "types-requests"] test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-qt-documentation" -version = "0.3" +version = "0.4" description = "Plugin for proper resolve intersphinx references for Qt elements" category = "dev" optional = false @@ -1374,16 +1409,22 @@ python-versions = ">=3.6" docutils = "*" sphinx = "*" +[package.extras] +dev = ["pre-commit"] +lint = ["black", "flake8", "pylint"] +test = ["pytest (>=3.0.0)", "pytest-cov"] + [[package]] name = "sphinx-rtd-theme" -version = "0.5.1" +version = "1.0.0" description = "Read the Docs theme for Sphinx" category = "dev" optional = false -python-versions = "*" +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" [package.dependencies] -sphinx = "*" +docutils = "<0.18" +sphinx = ">=1.6" [package.extras] dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"] @@ -1504,7 +1545,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" -version = "2.0.0" +version = "2.0.1" description = "A lil' TOML parser" category = "dev" optional = false @@ -1520,7 +1561,7 @@ python-versions = ">=3.6" [[package]] name = "typing-extensions" -version = "4.0.1" +version = "4.1.1" description = "Backported and Experimental Type Hints for Python 3.6+" category = "main" optional = false @@ -1536,14 +1577,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "urllib3" -version = "1.26.8" +version = "1.26.9" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -brotli = ["brotlipy (>=0.6.0)"] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] @@ -1568,7 +1609,7 @@ six = "*" [[package]] name = "wrapt" -version = "1.13.3" +version = "1.14.0" description = "Module for decorators, wrappers and monkey patching." category = "dev" optional = false @@ -1606,20 +1647,20 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [[package]] name = "zipp" -version = "3.7.0" +version = "3.8.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "1.1" python-versions = "3.7.*" -content-hash = "b02313c8255a1897b0f0617ad4884a5943696c363512921aab1cb2dd8f4fdbe0" +content-hash = "5df45b7a0d0505ca0db8e62f5a4e945601c53908c54ab752d5362552596bd69c" [metadata.files] acre = [] @@ -1722,8 +1763,8 @@ arrow = [ {file = "arrow-0.17.0.tar.gz", hash = "sha256:ff08d10cda1d36c68657d6ad20d74fbea493d980f8b2d45344e00d6ed2bf6ed4"}, ] astroid = [ - {file = "astroid-2.9.3-py3-none-any.whl", hash = "sha256:506daabe5edffb7e696ad82483ad0228245a9742ed7d2d8c9cdb31537decf9f6"}, - {file = "astroid-2.9.3.tar.gz", hash = "sha256:1efdf4e867d4d8ba4a9f6cf9ce07cd182c4c41de77f23814feb27ca93ca9d877"}, + {file = "astroid-2.11.2-py3-none-any.whl", hash = "sha256:cc8cc0d2d916c42d0a7c476c57550a4557a083081976bf42a73414322a6411d9"}, + {file = "astroid-2.11.2.tar.gz", hash = "sha256:8d0a30fe6481ce919f56690076eafbb2fb649142a89dc874f1ec0e7a011492d0"}, ] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, @@ -1830,8 +1871,8 @@ chardet = [ {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"}, - {file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"}, + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, @@ -1854,69 +1895,69 @@ coolname = [ {file = "coolname-1.1.0.tar.gz", hash = "sha256:410fe6ea9999bf96f2856ef0c726d5f38782bbefb7bb1aca0e91e0dc98ed09e3"}, ] coverage = [ - {file = "coverage-6.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525"}, - {file = "coverage-6.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e"}, - {file = "coverage-6.3.1-cp310-cp310-win32.whl", hash = "sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217"}, - {file = "coverage-6.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb"}, - {file = "coverage-6.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8"}, - {file = "coverage-6.3.1-cp37-cp37m-win32.whl", hash = "sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0"}, - {file = "coverage-6.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687"}, - {file = "coverage-6.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320"}, - {file = "coverage-6.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a"}, - {file = "coverage-6.3.1-cp38-cp38-win32.whl", hash = "sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10"}, - {file = "coverage-6.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f"}, - {file = "coverage-6.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d"}, - {file = "coverage-6.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38"}, - {file = "coverage-6.3.1-cp39-cp39-win32.whl", hash = "sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2"}, - {file = "coverage-6.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa"}, - {file = "coverage-6.3.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2"}, - {file = "coverage-6.3.1.tar.gz", hash = "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8"}, + {file = "coverage-6.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf"}, + {file = "coverage-6.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05"}, + {file = "coverage-6.3.2-cp310-cp310-win32.whl", hash = "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39"}, + {file = "coverage-6.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1"}, + {file = "coverage-6.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4"}, + {file = "coverage-6.3.2-cp37-cp37m-win32.whl", hash = "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca"}, + {file = "coverage-6.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3"}, + {file = "coverage-6.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d"}, + {file = "coverage-6.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2"}, + {file = "coverage-6.3.2-cp38-cp38-win32.whl", hash = "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e"}, + {file = "coverage-6.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1"}, + {file = "coverage-6.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620"}, + {file = "coverage-6.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684"}, + {file = "coverage-6.3.2-cp39-cp39-win32.whl", hash = "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4"}, + {file = "coverage-6.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92"}, + {file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"}, + {file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"}, ] cryptography = [ - {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b"}, - {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2"}, - {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f"}, - {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3"}, - {file = "cryptography-36.0.1-cp36-abi3-win32.whl", hash = "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca"}, - {file = "cryptography-36.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf"}, - {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9"}, - {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1"}, - {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316"}, - {file = "cryptography-36.0.1.tar.gz", hash = "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638"}, + {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6"}, + {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86"}, + {file = "cryptography-36.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2"}, + {file = "cryptography-36.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb"}, + {file = "cryptography-36.0.2-cp36-abi3-win32.whl", hash = "sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6"}, + {file = "cryptography-36.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29"}, + {file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7"}, + {file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e"}, + {file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3"}, + {file = "cryptography-36.0.2.tar.gz", hash = "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9"}, ] cx-freeze = [ {file = "cx_Freeze-6.9-cp310-cp310-win32.whl", hash = "sha256:776d4fb68a4831691acbd3c374362b9b48ce2e568514a73c3d4cb14d5dcf1470"}, @@ -1946,25 +1987,29 @@ cx-logging = [ {file = "cx_Logging-3.0-cp39-cp39-win_amd64.whl", hash = "sha256:302e9c4f65a936c288a4fa59a90e7e142d9ef994aa29676731acafdcccdbb3f5"}, {file = "cx_Logging-3.0.tar.gz", hash = "sha256:ba8a7465facf7b98d8f494030fb481a2e8aeee29dc191e10383bb54ed42bdb34"}, ] +dill = [ + {file = "dill-0.3.4-py2.py3-none-any.whl", hash = "sha256:7e40e4a70304fd9ceab3535d36e58791d9c4a776b38ec7f7ec9afc8d3dca4d4f"}, + {file = "dill-0.3.4.zip", hash = "sha256:9f9734205146b2b353ab3fec9af0070237b6ddae78452af83d2fca84d739e675"}, +] dnspython = [ - {file = "dnspython-2.2.0-py3-none-any.whl", hash = "sha256:081649da27ced5e75709a1ee542136eaba9842a0fe4c03da4fb0a3d3ed1f3c44"}, - {file = "dnspython-2.2.0.tar.gz", hash = "sha256:e79351e032d0b606b98d38a4b0e6e2275b31a5b85c873e587cc11b73aca026d6"}, + {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"}, + {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"}, ] docutils = [ - {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, - {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, + {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, + {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] dropbox = [ - {file = "dropbox-11.26.0-py2-none-any.whl", hash = "sha256:abd29587fa692bde0c3a48ce8efb56c24d8df92c5f402bbcdd78f3089d5ced5c"}, - {file = "dropbox-11.26.0-py3-none-any.whl", hash = "sha256:84becf043a63007ae67620946acb0fa164669ce691c7f1dbfdabb6712a258b29"}, - {file = "dropbox-11.26.0.tar.gz", hash = "sha256:dd79e3dfc216688020959462aefac54ffce7c07a45e89f4fb258348885195a73"}, + {file = "dropbox-11.29.0-py2-none-any.whl", hash = "sha256:2200ad5f42e00ae00d45db4a050fa199fe701ddc979fd1396d2c3e8912476c60"}, + {file = "dropbox-11.29.0-py3-none-any.whl", hash = "sha256:bf81a822e662bd337f4cd33fe39580c0b6ee4781d018ef1b31dcef2f402986f2"}, + {file = "dropbox-11.29.0.tar.gz", hash = "sha256:09b59f962ac28ce5b80d5f870c00c5fe7a637c4ac8d095c7c72fdab1e07376fc"}, ] enlighten = [ {file = "enlighten-1.10.2-py2.py3-none-any.whl", hash = "sha256:b237fe562b320bf9f1d4bb76d0c98e0daf914372a76ab87c35cd02f57aa9d8c1"}, {file = "enlighten-1.10.2.tar.gz", hash = "sha256:7a5b83cd0f4d095e59d80c648ebb5f7ffca0cd8bcf7ae6639828ee1ad000632a"}, ] evdev = [ - {file = "evdev-1.4.0.tar.gz", hash = "sha256:8782740eb1a86b187334c07feb5127d3faa0b236e113206dfe3ae8f77fb1aaf1"}, + {file = "evdev-1.5.0.tar.gz", hash = "sha256:5b33b174f7c84576e7dd6071e438bf5ad227da95efd4356a39fe4c8355412fe6"}, ] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, @@ -2043,32 +2088,32 @@ gitdb = [ {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, ] gitpython = [ - {file = "GitPython-3.1.26-py3-none-any.whl", hash = "sha256:26ac35c212d1f7b16036361ca5cff3ec66e11753a0d677fb6c48fa4e1a9dd8d6"}, - {file = "GitPython-3.1.26.tar.gz", hash = "sha256:fc8868f63a2e6d268fb25f481995ba185a85a66fcad126f039323ff6635669ee"}, + {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, + {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, ] google-api-core = [ - {file = "google-api-core-2.4.0.tar.gz", hash = "sha256:ba8787b7c61632cd0340f095e1c036bef9426b2594f10afb290ba311ae8cb2cb"}, - {file = "google_api_core-2.4.0-py2.py3-none-any.whl", hash = "sha256:58e2c1171a3d51778bf4e428fbb4bf79cbd05007b4b44deaa80cf73c80eebc0f"}, + {file = "google-api-core-2.7.1.tar.gz", hash = "sha256:b0fa577e512f0c8e063386b974718b8614586a798c5894ed34bedf256d9dae24"}, + {file = "google_api_core-2.7.1-py3-none-any.whl", hash = "sha256:6be1fc59e2a7ba9f66808bbc22f976f81e4c3e7ab20fa0620ce42686288787d0"}, ] google-api-python-client = [ - {file = "google-api-python-client-1.12.10.tar.gz", hash = "sha256:1cb773647e7d97048d9d1c7fa746247fbad39fd1a3b5040f2cb2645dd7156b11"}, - {file = "google_api_python_client-1.12.10-py2.py3-none-any.whl", hash = "sha256:5a8742b9b604b34e13462cc3d6aedbbf11d3af1ef558eb95defe74a29ebc5c28"}, + {file = "google-api-python-client-1.12.11.tar.gz", hash = "sha256:1b4bd42a46321e13c0542a9e4d96fa05d73626f07b39f83a73a947d70ca706a9"}, + {file = "google_api_python_client-1.12.11-py2.py3-none-any.whl", hash = "sha256:7e0a1a265c8d3088ee1987778c72683fcb376e32bada8d7767162bd9c503fd9b"}, ] google-auth = [ - {file = "google-auth-2.6.0.tar.gz", hash = "sha256:ad160fc1ea8f19e331a16a14a79f3d643d813a69534ba9611d2c80dc10439dad"}, - {file = "google_auth-2.6.0-py2.py3-none-any.whl", hash = "sha256:218ca03d7744ca0c8b6697b6083334be7df49b7bf76a69d555962fd1a7657b5f"}, + {file = "google-auth-2.6.3.tar.gz", hash = "sha256:d65bb0e3701eaaa64fd2aa85e1325580524b0bddc6dc5db3ab89c481b6a20141"}, + {file = "google_auth-2.6.3-py2.py3-none-any.whl", hash = "sha256:5e079eb4d21df1853d55cf2b6766b77ef36f7f7bdaf7d4a70434aa97c7578d60"}, ] google-auth-httplib2 = [ {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, {file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"}, ] googleapis-common-protos = [ - {file = "googleapis-common-protos-1.54.0.tar.gz", hash = "sha256:a4031d6ec6c2b1b6dc3e0be7e10a1bd72fb0b18b07ef9be7b51f2c1004ce2437"}, - {file = "googleapis_common_protos-1.54.0-py2.py3-none-any.whl", hash = "sha256:e54345a2add15dc5e1a7891c27731ff347b4c33765d79b5ed7026a6c0c7cbcae"}, + {file = "googleapis-common-protos-1.56.0.tar.gz", hash = "sha256:4007500795bcfc269d279f0f7d253ae18d6dc1ff5d5a73613ffe452038b1ec5f"}, + {file = "googleapis_common_protos-1.56.0-py2.py3-none-any.whl", hash = "sha256:60220c89b8bd5272159bed4929ecdc1243ae1f73437883a499a44a1cbc084086"}, ] httplib2 = [ - {file = "httplib2-0.20.2-py3-none-any.whl", hash = "sha256:6b937120e7d786482881b44b8eec230c1ee1c5c1d06bce8cc865f25abbbf713b"}, - {file = "httplib2-0.20.2.tar.gz", hash = "sha256:e404681d2fbcec7506bcb52c503f2b021e95bee0ef7d01e5c221468a2406d8dc"}, + {file = "httplib2-0.20.4-py3-none-any.whl", hash = "sha256:8b6a905cb1c79eefd03f8669fd993c36dc341f7c558f056cb5a33b5c2f458543"}, + {file = "httplib2-0.20.4.tar.gz", hash = "sha256:58a98e45b4b1a48273073f905d2961666ecf0fbac4250ea5b47aef259eb5c585"}, ] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, @@ -2079,8 +2124,8 @@ imagesize = [ {file = "imagesize-1.3.0.tar.gz", hash = "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.10.1-py3-none-any.whl", hash = "sha256:899e2a40a8c4a1aec681feef45733de8a6c58f3f6a0dbed2eb6574b4387a77b6"}, - {file = "importlib_metadata-4.10.1.tar.gz", hash = "sha256:951f0d8a5b7260e9db5e41d429285b5f451e928479f19d80818878527d36e95e"}, + {file = "importlib_metadata-4.11.3-py3-none-any.whl", hash = "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6"}, + {file = "importlib_metadata-4.11.3.tar.gz", hash = "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -2095,8 +2140,8 @@ jedi = [ {file = "jedi-0.13.3.tar.gz", hash = "sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b"}, ] jeepney = [ - {file = "jeepney-0.7.1-py3-none-any.whl", hash = "sha256:1b5a0ea5c0e7b166b2f5895b91a08c14de8915afda4407fb5022a195224958ac"}, - {file = "jeepney-0.7.1.tar.gz", hash = "sha256:fa9e232dfa0c498bd0b8a3a73b8d8a31978304dcef0515adc859d4e096f96f4f"}, + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, ] jinja2 = [ {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, @@ -2157,75 +2202,46 @@ log4mongo = [ {file = "log4mongo-1.7.0.tar.gz", hash = "sha256:dc374617206162a0b14167fbb5feac01dbef587539a235dadba6200362984a68"}, ] markupsafe = [ - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, - {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, + {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -2298,54 +2314,60 @@ packaging = [ {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] paramiko = [ - {file = "paramiko-2.10.1-py2.py3-none-any.whl", hash = "sha256:f6cbd3e1204abfdbcd40b3ecbc9d32f04027cd3080fe666245e21e7540ccfc1b"}, - {file = "paramiko-2.10.1.tar.gz", hash = "sha256:443f4da23ec24e9a9c0ea54017829c282abdda1d57110bf229360775ccd27a31"}, + {file = "paramiko-2.10.3-py2.py3-none-any.whl", hash = "sha256:ac6593479f2b47a9422eca076b22cff9f795495e6733a64723efc75dd8c92101"}, + {file = "paramiko-2.10.3.tar.gz", hash = "sha256:ddb1977853aef82804b35d72a0e597b244fa326c404c350bd00c5b01dbfee71a"}, ] parso = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] pathlib2 = [ - {file = "pathlib2-2.3.6-py2.py3-none-any.whl", hash = "sha256:3a130b266b3a36134dcc79c17b3c7ac9634f083825ca6ea9d8f557ee6195c9c8"}, - {file = "pathlib2-2.3.6.tar.gz", hash = "sha256:7d8bcb5555003cdf4a8d2872c538faa3a0f5d20630cb360e518ca3b981795e5f"}, + {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, + {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, ] pillow = [ - {file = "Pillow-9.0.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:113723312215b25c22df1fdf0e2da7a3b9c357a7d24a93ebbe80bfda4f37a8d4"}, - {file = "Pillow-9.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bb47a548cea95b86494a26c89d153fd31122ed65255db5dcbc421a2d28eb3379"}, - {file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31b265496e603985fad54d52d11970383e317d11e18e856971bdbb86af7242a4"}, - {file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d154ed971a4cc04b93a6d5b47f37948d1f621f25de3e8fa0c26b2d44f24e3e8f"}, - {file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fe92813d208ce8aa7d76da878bdc84b90809f79ccbad2a288e9bcbeac1d9bd"}, - {file = "Pillow-9.0.0-cp310-cp310-win32.whl", hash = "sha256:d5dcea1387331c905405b09cdbfb34611050cc52c865d71f2362f354faee1e9f"}, - {file = "Pillow-9.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:52abae4c96b5da630a8b4247de5428f593465291e5b239f3f843a911a3cf0105"}, - {file = "Pillow-9.0.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:72c3110228944019e5f27232296c5923398496b28be42535e3b2dc7297b6e8b6"}, - {file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97b6d21771da41497b81652d44191489296555b761684f82b7b544c49989110f"}, - {file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72f649d93d4cc4d8cf79c91ebc25137c358718ad75f99e99e043325ea7d56100"}, - {file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aaf07085c756f6cb1c692ee0d5a86c531703b6e8c9cae581b31b562c16b98ce"}, - {file = "Pillow-9.0.0-cp37-cp37m-win32.whl", hash = "sha256:03b27b197deb4ee400ed57d8d4e572d2d8d80f825b6634daf6e2c18c3c6ccfa6"}, - {file = "Pillow-9.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a09a9d4ec2b7887f7a088bbaacfd5c07160e746e3d47ec5e8050ae3b2a229e9f"}, - {file = "Pillow-9.0.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:490e52e99224858f154975db61c060686df8a6b3f0212a678e5d2e2ce24675c9"}, - {file = "Pillow-9.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:500d397ddf4bbf2ca42e198399ac13e7841956c72645513e8ddf243b31ad2128"}, - {file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ebd8b9137630a7bbbff8c4b31e774ff05bbb90f7911d93ea2c9371e41039b52"}, - {file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd0e5062f11cb3e730450a7d9f323f4051b532781026395c4323b8ad055523c4"}, - {file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f3b4522148586d35e78313db4db0df4b759ddd7649ef70002b6c3767d0fdeb7"}, - {file = "Pillow-9.0.0-cp38-cp38-win32.whl", hash = "sha256:0b281fcadbb688607ea6ece7649c5d59d4bbd574e90db6cd030e9e85bde9fecc"}, - {file = "Pillow-9.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5050d681bcf5c9f2570b93bee5d3ec8ae4cf23158812f91ed57f7126df91762"}, - {file = "Pillow-9.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c2067b3bb0781f14059b112c9da5a91c80a600a97915b4f48b37f197895dd925"}, - {file = "Pillow-9.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2d16b6196fb7a54aff6b5e3ecd00f7c0bab1b56eee39214b2b223a9d938c50af"}, - {file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98cb63ca63cb61f594511c06218ab4394bf80388b3d66cd61d0b1f63ee0ea69f"}, - {file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc462d24500ba707e9cbdef436c16e5c8cbf29908278af053008d9f689f56dee"}, - {file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3586e12d874ce2f1bc875a3ffba98732ebb12e18fb6d97be482bd62b56803281"}, - {file = "Pillow-9.0.0-cp39-cp39-win32.whl", hash = "sha256:68e06f8b2248f6dc8b899c3e7ecf02c9f413aab622f4d6190df53a78b93d97a5"}, - {file = "Pillow-9.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:6579f9ba84a3d4f1807c4aab4be06f373017fc65fff43498885ac50a9b47a553"}, - {file = "Pillow-9.0.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:47f5cf60bcb9fbc46011f75c9b45a8b5ad077ca352a78185bd3e7f1d294b98bb"}, - {file = "Pillow-9.0.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fd8053e1f8ff1844419842fd474fc359676b2e2a2b66b11cc59f4fa0a301315"}, - {file = "Pillow-9.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c5439bfb35a89cac50e81c751317faea647b9a3ec11c039900cd6915831064d"}, - {file = "Pillow-9.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95545137fc56ce8c10de646074d242001a112a92de169986abd8c88c27566a05"}, - {file = "Pillow-9.0.0.tar.gz", hash = "sha256:ee6e2963e92762923956fe5d3479b1fdc3b76c83f290aad131a2f98c3df0593e"}, + {file = "Pillow-9.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:af79d3fde1fc2e33561166d62e3b63f0cc3e47b5a3a2e5fea40d4917754734ea"}, + {file = "Pillow-9.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:55dd1cf09a1fd7c7b78425967aacae9b0d70125f7d3ab973fadc7b5abc3de652"}, + {file = "Pillow-9.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66822d01e82506a19407d1afc104c3fcea3b81d5eb11485e593ad6b8492f995a"}, + {file = "Pillow-9.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5eaf3b42df2bcda61c53a742ee2c6e63f777d0e085bbc6b2ab7ed57deb13db7"}, + {file = "Pillow-9.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01ce45deec9df310cbbee11104bae1a2a43308dd9c317f99235b6d3080ddd66e"}, + {file = "Pillow-9.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aea7ce61328e15943d7b9eaca87e81f7c62ff90f669116f857262e9da4057ba3"}, + {file = "Pillow-9.1.0-cp310-cp310-win32.whl", hash = "sha256:7a053bd4d65a3294b153bdd7724dce864a1d548416a5ef61f6d03bf149205160"}, + {file = "Pillow-9.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:97bda660702a856c2c9e12ec26fc6d187631ddfd896ff685814ab21ef0597033"}, + {file = "Pillow-9.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21dee8466b42912335151d24c1665fcf44dc2ee47e021d233a40c3ca5adae59c"}, + {file = "Pillow-9.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b6d4050b208c8ff886fd3db6690bf04f9a48749d78b41b7a5bf24c236ab0165"}, + {file = "Pillow-9.1.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5cfca31ab4c13552a0f354c87fbd7f162a4fafd25e6b521bba93a57fe6a3700a"}, + {file = "Pillow-9.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed742214068efa95e9844c2d9129e209ed63f61baa4d54dbf4cf8b5e2d30ccf2"}, + {file = "Pillow-9.1.0-cp37-cp37m-win32.whl", hash = "sha256:c9efef876c21788366ea1f50ecb39d5d6f65febe25ad1d4c0b8dff98843ac244"}, + {file = "Pillow-9.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:de344bcf6e2463bb25179d74d6e7989e375f906bcec8cb86edb8b12acbc7dfef"}, + {file = "Pillow-9.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:17869489de2fce6c36690a0c721bd3db176194af5f39249c1ac56d0bb0fcc512"}, + {file = "Pillow-9.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:25023a6209a4d7c42154073144608c9a71d3512b648a2f5d4465182cb93d3477"}, + {file = "Pillow-9.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8782189c796eff29dbb37dd87afa4ad4d40fc90b2742704f94812851b725964b"}, + {file = "Pillow-9.1.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:463acf531f5d0925ca55904fa668bb3461c3ef6bc779e1d6d8a488092bdee378"}, + {file = "Pillow-9.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f42364485bfdab19c1373b5cd62f7c5ab7cc052e19644862ec8f15bb8af289e"}, + {file = "Pillow-9.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3fddcdb619ba04491e8f771636583a7cc5a5051cd193ff1aa1ee8616d2a692c5"}, + {file = "Pillow-9.1.0-cp38-cp38-win32.whl", hash = "sha256:4fe29a070de394e449fd88ebe1624d1e2d7ddeed4c12e0b31624561b58948d9a"}, + {file = "Pillow-9.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:c24f718f9dd73bb2b31a6201e6db5ea4a61fdd1d1c200f43ee585fc6dcd21b34"}, + {file = "Pillow-9.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fb89397013cf302f282f0fc998bb7abf11d49dcff72c8ecb320f76ea6e2c5717"}, + {file = "Pillow-9.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c870193cce4b76713a2b29be5d8327c8ccbe0d4a49bc22968aa1e680930f5581"}, + {file = "Pillow-9.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69e5ddc609230d4408277af135c5b5c8fe7a54b2bdb8ad7c5100b86b3aab04c6"}, + {file = "Pillow-9.1.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35be4a9f65441d9982240e6966c1eaa1c654c4e5e931eaf580130409e31804d4"}, + {file = "Pillow-9.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82283af99c1c3a5ba1da44c67296d5aad19f11c535b551a5ae55328a317ce331"}, + {file = "Pillow-9.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a325ac71914c5c043fa50441b36606e64a10cd262de12f7a179620f579752ff8"}, + {file = "Pillow-9.1.0-cp39-cp39-win32.whl", hash = "sha256:a598d8830f6ef5501002ae85c7dbfcd9c27cc4efc02a1989369303ba85573e58"}, + {file = "Pillow-9.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0c51cb9edac8a5abd069fd0758ac0a8bfe52c261ee0e330f363548aca6893595"}, + {file = "Pillow-9.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a336a4f74baf67e26f3acc4d61c913e378e931817cd1e2ef4dfb79d3e051b481"}, + {file = "Pillow-9.1.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb1b89b11256b5b6cad5e7593f9061ac4624f7651f7a8eb4dfa37caa1dfaa4d0"}, + {file = "Pillow-9.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:255c9d69754a4c90b0ee484967fc8818c7ff8311c6dddcc43a4340e10cd1636a"}, + {file = "Pillow-9.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5a3ecc026ea0e14d0ad7cd990ea7f48bfcb3eb4271034657dc9d06933c6629a7"}, + {file = "Pillow-9.1.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5b0ff59785d93b3437c3703e3c64c178aabada51dea2a7f2c5eccf1bcf565a3"}, + {file = "Pillow-9.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7110ec1701b0bf8df569a7592a196c9d07c764a0a74f65471ea56816f10e2c8"}, + {file = "Pillow-9.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8d79c6f468215d1a8415aa53d9868a6b40c4682165b8cb62a221b1baa47db458"}, + {file = "Pillow-9.1.0.tar.gz", hash = "sha256:f401ed2bbb155e1ade150ccc63db1a4f6c1909d3d378f7d1235a44e90d75fb97"}, ] platformdirs = [ - {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, - {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, + {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, + {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, @@ -2360,32 +2382,30 @@ prefixed = [ {file = "prefixed-0.3.2.tar.gz", hash = "sha256:ca48277ba5fa8346dd4b760847da930c7b84416387c39e93affef086add2c029"}, ] protobuf = [ - {file = "protobuf-3.19.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f51d5a9f137f7a2cec2d326a74b6e3fc79d635d69ffe1b036d39fc7d75430d37"}, - {file = "protobuf-3.19.4-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:09297b7972da685ce269ec52af761743714996b4381c085205914c41fcab59fb"}, - {file = "protobuf-3.19.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:072fbc78d705d3edc7ccac58a62c4c8e0cec856987da7df8aca86e647be4e35c"}, - {file = "protobuf-3.19.4-cp310-cp310-win32.whl", hash = "sha256:7bb03bc2873a2842e5ebb4801f5c7ff1bfbdf426f85d0172f7644fcda0671ae0"}, - {file = "protobuf-3.19.4-cp310-cp310-win_amd64.whl", hash = "sha256:f358aa33e03b7a84e0d91270a4d4d8f5df6921abe99a377828839e8ed0c04e07"}, - {file = "protobuf-3.19.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1c91ef4110fdd2c590effb5dca8fdbdcb3bf563eece99287019c4204f53d81a4"}, - {file = "protobuf-3.19.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c438268eebb8cf039552897d78f402d734a404f1360592fef55297285f7f953f"}, - {file = "protobuf-3.19.4-cp36-cp36m-win32.whl", hash = "sha256:835a9c949dc193953c319603b2961c5c8f4327957fe23d914ca80d982665e8ee"}, - {file = "protobuf-3.19.4-cp36-cp36m-win_amd64.whl", hash = "sha256:4276cdec4447bd5015453e41bdc0c0c1234eda08420b7c9a18b8d647add51e4b"}, - {file = "protobuf-3.19.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6cbc312be5e71869d9d5ea25147cdf652a6781cf4d906497ca7690b7b9b5df13"}, - {file = "protobuf-3.19.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:54a1473077f3b616779ce31f477351a45b4fef8c9fd7892d6d87e287a38df368"}, - {file = "protobuf-3.19.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:435bb78b37fc386f9275a7035fe4fb1364484e38980d0dd91bc834a02c5ec909"}, - {file = "protobuf-3.19.4-cp37-cp37m-win32.whl", hash = "sha256:16f519de1313f1b7139ad70772e7db515b1420d208cb16c6d7858ea989fc64a9"}, - {file = "protobuf-3.19.4-cp37-cp37m-win_amd64.whl", hash = "sha256:cdc076c03381f5c1d9bb1abdcc5503d9ca8b53cf0a9d31a9f6754ec9e6c8af0f"}, - {file = "protobuf-3.19.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:69da7d39e39942bd52848438462674c463e23963a1fdaa84d88df7fbd7e749b2"}, - {file = "protobuf-3.19.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:48ed3877fa43e22bcacc852ca76d4775741f9709dd9575881a373bd3e85e54b2"}, - {file = "protobuf-3.19.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd95d1dfb9c4f4563e6093a9aa19d9c186bf98fa54da5252531cc0d3a07977e7"}, - {file = "protobuf-3.19.4-cp38-cp38-win32.whl", hash = "sha256:b38057450a0c566cbd04890a40edf916db890f2818e8682221611d78dc32ae26"}, - {file = "protobuf-3.19.4-cp38-cp38-win_amd64.whl", hash = "sha256:7ca7da9c339ca8890d66958f5462beabd611eca6c958691a8fe6eccbd1eb0c6e"}, - {file = "protobuf-3.19.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:36cecbabbda242915529b8ff364f2263cd4de7c46bbe361418b5ed859677ba58"}, - {file = "protobuf-3.19.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c1068287025f8ea025103e37d62ffd63fec8e9e636246b89c341aeda8a67c934"}, - {file = "protobuf-3.19.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96bd766831596d6014ca88d86dc8fe0fb2e428c0b02432fd9db3943202bf8c5e"}, - {file = "protobuf-3.19.4-cp39-cp39-win32.whl", hash = "sha256:84123274d982b9e248a143dadd1b9815049f4477dc783bf84efe6250eb4b836a"}, - {file = "protobuf-3.19.4-cp39-cp39-win_amd64.whl", hash = "sha256:3112b58aac3bac9c8be2b60a9daf6b558ca3f7681c130dcdd788ade7c9ffbdca"}, - {file = "protobuf-3.19.4-py2.py3-none-any.whl", hash = "sha256:8961c3a78ebfcd000920c9060a262f082f29838682b1f7201889300c1fbe0616"}, - {file = "protobuf-3.19.4.tar.gz", hash = "sha256:9df0c10adf3e83015ced42a9a7bd64e13d06c4cf45c340d2c63020ea04499d0a"}, + {file = "protobuf-3.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9d0f3aca8ca51c8b5e204ab92bd8afdb2a8e3df46bd0ce0bd39065d79aabcaa4"}, + {file = "protobuf-3.20.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:001c2160c03b6349c04de39cf1a58e342750da3632f6978a1634a3dcca1ec10e"}, + {file = "protobuf-3.20.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b5860b790498f233cdc8d635a17fc08de62e59d4dcd8cdb6c6c0d38a31edf2b"}, + {file = "protobuf-3.20.0-cp310-cp310-win32.whl", hash = "sha256:0b250c60256c8824219352dc2a228a6b49987e5bf94d3ffcf4c46585efcbd499"}, + {file = "protobuf-3.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:a1eebb6eb0653e594cb86cd8e536b9b083373fca9aba761ade6cd412d46fb2ab"}, + {file = "protobuf-3.20.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:bc14037281db66aa60856cd4ce4541a942040686d290e3f3224dd3978f88f554"}, + {file = "protobuf-3.20.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:47257d932de14a7b6c4ae1b7dbf592388153ee35ec7cae216b87ae6490ed39a3"}, + {file = "protobuf-3.20.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fbcbb068ebe67c4ff6483d2e2aa87079c325f8470b24b098d6bf7d4d21d57a69"}, + {file = "protobuf-3.20.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:542f25a4adf3691a306dcc00bf9a73176554938ec9b98f20f929a044f80acf1b"}, + {file = "protobuf-3.20.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fd7133b885e356fa4920ead8289bb45dc6f185a164e99e10279f33732ed5ce15"}, + {file = "protobuf-3.20.0-cp37-cp37m-win32.whl", hash = "sha256:8d84453422312f8275455d1cb52d850d6a4d7d714b784e41b573c6f5bfc2a029"}, + {file = "protobuf-3.20.0-cp37-cp37m-win_amd64.whl", hash = "sha256:52bae32a147c375522ce09bd6af4d2949aca32a0415bc62df1456b3ad17c6001"}, + {file = "protobuf-3.20.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25d2fcd6eef340082718ec9ad2c58d734429f2b1f7335d989523852f2bba220b"}, + {file = "protobuf-3.20.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:88c8be0558bdfc35e68c42ae5bf785eb9390d25915d4863bbc7583d23da77074"}, + {file = "protobuf-3.20.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:38fd9eb74b852e4ee14b16e9670cd401d147ee3f3ec0d4f7652e0c921d6227f8"}, + {file = "protobuf-3.20.0-cp38-cp38-win32.whl", hash = "sha256:7dcd84dc31ebb35ade755e06d1561d1bd3b85e85dbdbf6278011fc97b22810db"}, + {file = "protobuf-3.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:1eb13f5a5a59ca4973bcfa2fc8fff644bd39f2109c3f7a60bd5860cb6a49b679"}, + {file = "protobuf-3.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d24c81c2310f0063b8fc1c20c8ed01f3331be9374b4b5c2de846f69e11e21fb"}, + {file = "protobuf-3.20.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8be43a91ab66fe995e85ccdbdd1046d9f0443d59e060c0840319290de25b7d33"}, + {file = "protobuf-3.20.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7a53d4035427b9dbfbb397f46642754d294f131e93c661d056366f2a31438263"}, + {file = "protobuf-3.20.0-cp39-cp39-win32.whl", hash = "sha256:32bf4a90c207a0b4e70ca6dd09d43de3cb9898f7d5b69c2e9e3b966a7f342820"}, + {file = "protobuf-3.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:6efe066a7135233f97ce51a1aa007d4fb0be28ef093b4f88dac4ad1b3a2b7b6f"}, + {file = "protobuf-3.20.0-py2.py3-none-any.whl", hash = "sha256:4eda68bd9e2a4879385e6b1ea528c976f59cd9728382005cc54c28bcce8db983"}, + {file = "protobuf-3.20.0.tar.gz", hash = "sha256:71b2c3d1cd26ed1ec7c8196834143258b2ad7f444efff26fdc366c6f5e752702"}, ] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, @@ -2450,8 +2470,8 @@ pygments = [ {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, ] pylint = [ - {file = "pylint-2.12.2-py3-none-any.whl", hash = "sha256:daabda3f7ed9d1c60f52d563b1b854632fd90035bcf01443e234d3dc794e3b74"}, - {file = "pylint-2.12.2.tar.gz", hash = "sha256:9d945a73640e1fec07ee34b42f5669b770c759acd536ec7b16d7e4b87a9c9ff9"}, + {file = "pylint-2.13.5-py3-none-any.whl", hash = "sha256:c149694cfdeaee1aa2465e6eaab84c87a881a7d55e6e93e09466be7164764d1e"}, + {file = "pylint-2.13.5.tar.gz", hash = "sha256:dab221658368c7a05242e673c275c488670144123f4bd262b2777249c1c0de9b"}, ] pymongo = [ {file = "pymongo-3.12.3-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:c164eda0be9048f83c24b9b2656900041e069ddf72de81c17d874d0c32f6079f"}, @@ -2580,40 +2600,40 @@ pynput = [ {file = "pynput-1.7.6.tar.gz", hash = "sha256:3a5726546da54116b687785d38b1db56997ce1d28e53e8d22fc656d8b92e533c"}, ] pyobjc-core = [ - {file = "pyobjc-core-8.2.tar.gz", hash = "sha256:6afb8ee1dd0647cbfaaf99906eca3b43ce045b27e3d4510462d04e7e5361c89b"}, - {file = "pyobjc_core-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:311e45556c3afa8831713b89017e0204562f51f35661d76c07ffe04985f44e1d"}, - {file = "pyobjc_core-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:711f361e83382e405e4273ff085178b0cf730901ccf6801f834e7037e50278f9"}, - {file = "pyobjc_core-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bdd2e2960ec73214bcfe2d86bb4ca94f5f5119db86d129fa32d3c003b6532c50"}, - {file = "pyobjc_core-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b879f91fc614c399aafa1d08cf5e279c267510e904bad5c336c3a6064c0eb3aa"}, - {file = "pyobjc_core-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:b4ef4bdb99a330f5e15cc6273098964276fccbc432453cdba3c2963292bc066c"}, - {file = "pyobjc_core-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fd8e5be0955790ff8f9d17a0f356b6eb7eb1ce4995e0c94355c462dd52d22d6d"}, + {file = "pyobjc-core-8.4.1.tar.gz", hash = "sha256:df98669e957adb33566d9ef46773a5ac876a81afe8849c282d6a80448e35dd74"}, + {file = "pyobjc_core-8.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a89cac910fbd64728fe7ec0c7a3a7cf20959bc1d7e2f41db4d7800556e47745"}, + {file = "pyobjc_core-8.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2cf1d4348cb99fcba04fa38199a46e35263b2fe7bb66e6dfbd4f19bc2602998d"}, + {file = "pyobjc_core-8.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a130324b25c0f5f4cfe30b6a28b8e70865d6e1eee158caababb603906ef431d2"}, + {file = "pyobjc_core-8.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c31195d1a8f00da99abf79643f902d09c709dbbe9c9b6feb6b585303c57d720c"}, + {file = "pyobjc_core-8.4.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:e7fd2aefb53a96a8f688c8bb36c6ebd5446250a7251bfa6b688a045e05afc60b"}, + {file = "pyobjc_core-8.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b3191173ce268e23c84d84f036fc94c3a8749a6726fc7fe73baf27dbac14f7d0"}, ] pyobjc-framework-applicationservices = [ - {file = "pyobjc-framework-ApplicationServices-8.2.tar.gz", hash = "sha256:f901b2ebb278b7d00033b45cf9ee9d43f651e096ff4c4defa261509238138da8"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b5be1757a8df944a58449c628ed68fc76dd746fcd1e96ebb6db0f734e94cdfc8"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:32657c4b89983a98fbe831a14884954710719e763197968a20ac07e1daa21f1e"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ce2c9701590f752a75151b2fe1e462e7a7ad67dec3119a796711ea93ea01031"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28124bc892045222305cf8953b163495bf662c401e54f48905dafb1104d9c1d1"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d5c60d66c6ed2569cb042a6c14dd5b9f4d01e182a464b893cd6002ce445b4ddf"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e1727fccc1d48922fa28a4cebf4a6d287d633f451cb03779968df60de8cb1bd0"}, + {file = "pyobjc-framework-ApplicationServices-8.4.1.tar.gz", hash = "sha256:b058466266412d2bf24b0303785c91538961367f26db616bf2f9f45c498a83a3"}, + {file = "pyobjc_framework_ApplicationServices-8.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:362d178c624a617fc60c3a35397550193d82da9a59272b215cf1dc6fb68c011c"}, + {file = "pyobjc_framework_ApplicationServices-8.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:41f0f6be6d343f91de92cab053be4a983e3936b364ca267a94c6e06bc34ff7fe"}, + {file = "pyobjc_framework_ApplicationServices-8.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:73287b758a70a037b6e74a60036f4adb1677407dfeaddee94102074d92539e6e"}, + {file = "pyobjc_framework_ApplicationServices-8.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:287b541b66c5c931a11dde90d7be69ddd2e5c3624c2e980e679f5242ec3ee0da"}, + {file = "pyobjc_framework_ApplicationServices-8.4.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:780fbe2e3b02237f79e2f5780094dd95b7308ecdb265c062b78a507b9564eb4d"}, + {file = "pyobjc_framework_ApplicationServices-8.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:092c4835e73e5dab1f3c498511b28b2e96503671fc7c1bc25f732c5e687b7214"}, ] pyobjc-framework-cocoa = [ - {file = "pyobjc-framework-Cocoa-8.2.tar.gz", hash = "sha256:f0901998e2f18415ef6d1f8a12b083f69fc93bd56b3e88040002e3c09bd8c304"}, - {file = "pyobjc_framework_Cocoa-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5af73f150e242542735e0663bb5504f88aabaec2a54c60e856cfca3ff6dd9712"}, - {file = "pyobjc_framework_Cocoa-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d1de3763ee01850c311da74de5c82c85ec199120e85ab45acaf203accc37a470"}, - {file = "pyobjc_framework_Cocoa-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:86d69bf667f99f3c43184d8830567195fff94c675fe7f60f899dd90553d9b265"}, - {file = "pyobjc_framework_Cocoa-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9dbadb22826392c48b00087359f66579f8404a4f4f77498f31f9869c54bb0fa9"}, - {file = "pyobjc_framework_Cocoa-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7adf8b57da1c1292c24375b8e74b6dd45f99a4d3c10ba925a9b38f63a97ba782"}, - {file = "pyobjc_framework_Cocoa-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc8279c8c1544087d46a7e99324f093029df89b8c527c5da2a682bad4cb3197e"}, + {file = "pyobjc-framework-Cocoa-8.4.1.tar.gz", hash = "sha256:dc596bac0f5d424f67944e95b2d0d7c94a07c4166359d7b4a4d4ae4f8e112822"}, + {file = "pyobjc_framework_Cocoa-8.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cfbe038a0108ae1b45f8f7067af70af5811b8352d2dc3d86a7bcb4484ff5d56e"}, + {file = "pyobjc_framework_Cocoa-8.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:118225562064d991bafb41d0913899d6b3d723984d1888cb7181e4dba63b22c2"}, + {file = "pyobjc_framework_Cocoa-8.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d117a1eb24fd317e9f63792ac6a8703ed899de5d42e8a861c7bf885625668c31"}, + {file = "pyobjc_framework_Cocoa-8.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3af8577acbd6b980d3b9270ec99bc0164f66ef8397351a72fcdee527f23c1a34"}, + {file = "pyobjc_framework_Cocoa-8.4.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:91fdc964acb4dee4d37ae81fb603d48397739dbbfcc1eadbe0cdafaa8144b6e6"}, + {file = "pyobjc_framework_Cocoa-8.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:197dd28668e786b55d7d4139afd85c285f780564ebbccc166e84a24be31de34f"}, ] pyobjc-framework-quartz = [ - {file = "pyobjc-framework-Quartz-8.2.tar.gz", hash = "sha256:219d8797235bf071723f8b0f30a681de0b12875e2d04ae902a3a269f72de0b66"}, - {file = "pyobjc_framework_Quartz-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e5ab3117201a494b11bb1b80febf5dd288111a196b35731815b656ae30ee6ce3"}, - {file = "pyobjc_framework_Quartz-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4941b3039ab7bcf89cb4255c381358de2383c1ab45c9e00c3b64655f271a3b32"}, - {file = "pyobjc_framework_Quartz-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ab75a50dea84ec794641522d9f035fc6c19ec4eb37a56c9186b9943575fbc7ab"}, - {file = "pyobjc_framework_Quartz-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8cb687b20ebfc467034868b38945b573d1c50f237e379cf86c4f557c9766b759"}, - {file = "pyobjc_framework_Quartz-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:8b5c3ca1fef900fa66aafe82fff07bc352c60ea7dceed2bb9b5b1db0957b4fea"}, - {file = "pyobjc_framework_Quartz-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7bca7e649da77056348d71032def44e345043319d71b0c592197888d92e3eec1"}, + {file = "pyobjc-framework-Quartz-8.4.1.tar.gz", hash = "sha256:f8acebf0b526f0687e79ea6946e8f2528ced4ef02d0ed3fbf7116124b2749e52"}, + {file = "pyobjc_framework_Quartz-8.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a82d9eab3b2a6fca46602465f731815026d06e4fd0ba65b5b5211f1a0472b861"}, + {file = "pyobjc_framework_Quartz-8.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bd0a506385141b81330464b6e2537a7056cdca56deb98222b6926a04f72a3e6"}, + {file = "pyobjc_framework_Quartz-8.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:64cc616386b7a387dce53d3adb8c12a8461c2720861ab36aeeb53768cd0ba7e4"}, + {file = "pyobjc_framework_Quartz-8.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a2dff998c4d1286998390f58a3131b99bdc9dbf5c3d881562b33f168098bbe2e"}, + {file = "pyobjc_framework_Quartz-8.4.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:a98c58e1b265d5b413f5ecc6ec186b9e305fb6e37909d8b4b97fd681176f5f1c"}, + {file = "pyobjc_framework_Quartz-8.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:39f55426ef883cbe12a53969f90886f71e2a94513c98cf3730efdf404cdc5c83"}, ] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, @@ -2646,8 +2666,8 @@ python3-xlib = [ {file = "python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8"}, ] pytz = [ - {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, - {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, + {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, + {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, ] pywin32 = [ {file = "pywin32-301-cp35-cp35m-win32.whl", hash = "sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7"}, @@ -2697,13 +2717,14 @@ semver = [ {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, ] +shotgun-api3 = [] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] slack-sdk = [ - {file = "slack_sdk-3.13.0-py2.py3-none-any.whl", hash = "sha256:54f2a5f7419f1ab932af9e3200f7f2f93db96e0f0eb8ad7d3b4214aa9f124641"}, - {file = "slack_sdk-3.13.0.tar.gz", hash = "sha256:aae6ce057e286a5e7fe7a9f256e85b886eee556def8e04b82b08f699e64d7f67"}, + {file = "slack_sdk-3.15.2-py2.py3-none-any.whl", hash = "sha256:e1fa26786169176e707676decc287fd9d3d547bbc43c0a1a4f99eb373b07da94"}, + {file = "slack_sdk-3.15.2.tar.gz", hash = "sha256:128f3bb0b5b91454a3d5f140a61f3db370a0e1b50ffe0a8d9e9ebe0e894faed7"}, ] smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, @@ -2714,20 +2735,20 @@ snowballstemmer = [ {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] speedcopy = [ - {file = "speedcopy-2.1.2-py3-none-any.whl", hash = "sha256:91e271b84c00952812dbf669d360b2610fd8fa198670373e02acf2a04db89a4c"}, - {file = "speedcopy-2.1.2.tar.gz", hash = "sha256:1b2d779fadebd53a59384f7d286c40b2ef382e0d000fa53011699fcd3190d33f"}, + {file = "speedcopy-2.1.3-py3-none-any.whl", hash = "sha256:ecbd71b7fcd2a01ce444268fb50db6bf843845aa494018571b6726f5b37c7869"}, + {file = "speedcopy-2.1.3.tar.gz", hash = "sha256:8e14a2b94352b91229c2c0712ea39ed265ce9ec780f899e43e9f2cf5435cf959"}, ] sphinx = [ - {file = "Sphinx-3.5.3-py3-none-any.whl", hash = "sha256:3f01732296465648da43dec8fb40dc451ba79eb3e2cc5c6d79005fd98197107d"}, - {file = "Sphinx-3.5.3.tar.gz", hash = "sha256:ce9c228456131bab09a3d7d10ae58474de562a6f79abb3dc811ae401cf8c1abc"}, + {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"}, + {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"}, ] sphinx-qt-documentation = [ - {file = "sphinx_qt_documentation-0.3-py3-none-any.whl", hash = "sha256:bee247cb9e4fc03fc496d07adfdb943100e1103320c3e5e820e0cfa7c790d9b6"}, - {file = "sphinx_qt_documentation-0.3.tar.gz", hash = "sha256:f09a0c9d9e989172ba3e282b92bf55613bb23ad47315ec5b0d38536b343ac6c8"}, + {file = "sphinx_qt_documentation-0.4-py3-none-any.whl", hash = "sha256:fa131093f75cd1bd48699cd132e18e4d46ba9eaadc070e6026867cea75ecdb7b"}, + {file = "sphinx_qt_documentation-0.4.tar.gz", hash = "sha256:f43ba17baa93e353fb94045027fb67f9d935ed158ce8662de93f08b88eec6774"}, ] sphinx-rtd-theme = [ - {file = "sphinx_rtd_theme-0.5.1-py2.py3-none-any.whl", hash = "sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113"}, - {file = "sphinx_rtd_theme-0.5.1.tar.gz", hash = "sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5"}, + {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, + {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, @@ -2770,8 +2791,8 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ - {file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"}, - {file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"}, + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] typed-ast = [ {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"}, @@ -2800,16 +2821,16 @@ typed-ast = [ {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, ] typing-extensions = [ - {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, - {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, + {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, + {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, ] uritemplate = [ {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"}, {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, ] urllib3 = [ - {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, - {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, + {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, + {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -2820,57 +2841,70 @@ websocket-client = [ {file = "websocket_client-0.59.0-py2.py3-none-any.whl", hash = "sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32"}, ] wrapt = [ - {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca"}, - {file = "wrapt-1.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44"}, - {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056"}, - {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785"}, - {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096"}, - {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33"}, - {file = "wrapt-1.13.3-cp310-cp310-win32.whl", hash = "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f"}, - {file = "wrapt-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755"}, - {file = "wrapt-1.13.3-cp35-cp35m-win32.whl", hash = "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851"}, - {file = "wrapt-1.13.3-cp35-cp35m-win_amd64.whl", hash = "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13"}, - {file = "wrapt-1.13.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918"}, - {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade"}, - {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc"}, - {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf"}, - {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125"}, - {file = "wrapt-1.13.3-cp36-cp36m-win32.whl", hash = "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36"}, - {file = "wrapt-1.13.3-cp36-cp36m-win_amd64.whl", hash = "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10"}, - {file = "wrapt-1.13.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068"}, - {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709"}, - {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df"}, - {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2"}, - {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b"}, - {file = "wrapt-1.13.3-cp37-cp37m-win32.whl", hash = "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829"}, - {file = "wrapt-1.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"}, - {file = "wrapt-1.13.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9"}, - {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554"}, - {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c"}, - {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b"}, - {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce"}, - {file = "wrapt-1.13.3-cp38-cp38-win32.whl", hash = "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79"}, - {file = "wrapt-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb"}, - {file = "wrapt-1.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb"}, - {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32"}, - {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7"}, - {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e"}, - {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640"}, - {file = "wrapt-1.13.3-cp39-cp39-win32.whl", hash = "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374"}, - {file = "wrapt-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb"}, - {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, + {file = "wrapt-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:5a9a1889cc01ed2ed5f34574c90745fab1dd06ec2eee663e8ebeefe363e8efd7"}, + {file = "wrapt-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:9a3ff5fb015f6feb78340143584d9f8a0b91b6293d6b5cf4295b3e95d179b88c"}, + {file = "wrapt-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4b847029e2d5e11fd536c9ac3136ddc3f54bc9488a75ef7d040a3900406a91eb"}, + {file = "wrapt-1.14.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:9a5a544861b21e0e7575b6023adebe7a8c6321127bb1d238eb40d99803a0e8bd"}, + {file = "wrapt-1.14.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:88236b90dda77f0394f878324cfbae05ae6fde8a84d548cfe73a75278d760291"}, + {file = "wrapt-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f0408e2dbad9e82b4c960274214af533f856a199c9274bd4aff55d4634dedc33"}, + {file = "wrapt-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9d8c68c4145041b4eeae96239802cfdfd9ef927754a5be3f50505f09f309d8c6"}, + {file = "wrapt-1.14.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:22626dca56fd7f55a0733e604f1027277eb0f4f3d95ff28f15d27ac25a45f71b"}, + {file = "wrapt-1.14.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:65bf3eb34721bf18b5a021a1ad7aa05947a1767d1aa272b725728014475ea7d5"}, + {file = "wrapt-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09d16ae7a13cff43660155383a2372b4aa09109c7127aa3f24c3cf99b891c330"}, + {file = "wrapt-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:debaf04f813ada978d7d16c7dfa16f3c9c2ec9adf4656efdc4defdf841fc2f0c"}, + {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748df39ed634851350efa87690c2237a678ed794fe9ede3f0d79f071ee042561"}, + {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1807054aa7b61ad8d8103b3b30c9764de2e9d0c0978e9d3fc337e4e74bf25faa"}, + {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:763a73ab377390e2af26042f685a26787c402390f682443727b847e9496e4a2a"}, + {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8529b07b49b2d89d6917cfa157d3ea1dfb4d319d51e23030664a827fe5fd2131"}, + {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:68aeefac31c1f73949662ba8affaf9950b9938b712fb9d428fa2a07e40ee57f8"}, + {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59d7d92cee84a547d91267f0fea381c363121d70fe90b12cd88241bd9b0e1763"}, + {file = "wrapt-1.14.0-cp310-cp310-win32.whl", hash = "sha256:3a88254881e8a8c4784ecc9cb2249ff757fd94b911d5df9a5984961b96113fff"}, + {file = "wrapt-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:9a242871b3d8eecc56d350e5e03ea1854de47b17f040446da0e47dc3e0b9ad4d"}, + {file = "wrapt-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a65bffd24409454b889af33b6c49d0d9bcd1a219b972fba975ac935f17bdf627"}, + {file = "wrapt-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9d9fcd06c952efa4b6b95f3d788a819b7f33d11bea377be6b8980c95e7d10775"}, + {file = "wrapt-1.14.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:db6a0ddc1282ceb9032e41853e659c9b638789be38e5b8ad7498caac00231c23"}, + {file = "wrapt-1.14.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:14e7e2c5f5fca67e9a6d5f753d21f138398cad2b1159913ec9e9a67745f09ba3"}, + {file = "wrapt-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:6d9810d4f697d58fd66039ab959e6d37e63ab377008ef1d63904df25956c7db0"}, + {file = "wrapt-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:d808a5a5411982a09fef6b49aac62986274ab050e9d3e9817ad65b2791ed1425"}, + {file = "wrapt-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b77159d9862374da213f741af0c361720200ab7ad21b9f12556e0eb95912cd48"}, + {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36a76a7527df8583112b24adc01748cd51a2d14e905b337a6fefa8b96fc708fb"}, + {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0057b5435a65b933cbf5d859cd4956624df37b8bf0917c71756e4b3d9958b9e"}, + {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0a4ca02752ced5f37498827e49c414d694ad7cf451ee850e3ff160f2bee9d3"}, + {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8c6be72eac3c14baa473620e04f74186c5d8f45d80f8f2b4eda6e1d18af808e8"}, + {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:21b1106bff6ece8cb203ef45b4f5778d7226c941c83aaaa1e1f0f4f32cc148cd"}, + {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:493da1f8b1bb8a623c16552fb4a1e164c0200447eb83d3f68b44315ead3f9036"}, + {file = "wrapt-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:89ba3d548ee1e6291a20f3c7380c92f71e358ce8b9e48161401e087e0bc740f8"}, + {file = "wrapt-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:729d5e96566f44fccac6c4447ec2332636b4fe273f03da128fff8d5559782b06"}, + {file = "wrapt-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:891c353e95bb11abb548ca95c8b98050f3620a7378332eb90d6acdef35b401d4"}, + {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23f96134a3aa24cc50614920cc087e22f87439053d886e474638c68c8d15dc80"}, + {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6807bcee549a8cb2f38f73f469703a1d8d5d990815c3004f21ddb68a567385ce"}, + {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6915682f9a9bc4cf2908e83caf5895a685da1fbd20b6d485dafb8e218a338279"}, + {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f2f3bc7cd9c9fcd39143f11342eb5963317bd54ecc98e3650ca22704b69d9653"}, + {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3a71dbd792cc7a3d772ef8cd08d3048593f13d6f40a11f3427c000cf0a5b36a0"}, + {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5a0898a640559dec00f3614ffb11d97a2666ee9a2a6bad1259c9facd01a1d4d9"}, + {file = "wrapt-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:167e4793dc987f77fd476862d32fa404d42b71f6a85d3b38cbce711dba5e6b68"}, + {file = "wrapt-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d066ffc5ed0be00cd0352c95800a519cf9e4b5dd34a028d301bdc7177c72daf3"}, + {file = "wrapt-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d9bdfa74d369256e4218000a629978590fd7cb6cf6893251dad13d051090436d"}, + {file = "wrapt-1.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2498762814dd7dd2a1d0248eda2afbc3dd9c11537bc8200a4b21789b6df6cd38"}, + {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f24ca7953f2643d59a9c87d6e272d8adddd4a53bb62b9208f36db408d7aafc7"}, + {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b835b86bd5a1bdbe257d610eecab07bf685b1af2a7563093e0e69180c1d4af1"}, + {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b21650fa6907e523869e0396c5bd591cc326e5c1dd594dcdccac089561cacfb8"}, + {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:354d9fc6b1e44750e2a67b4b108841f5f5ea08853453ecbf44c81fdc2e0d50bd"}, + {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1f83e9c21cd5275991076b2ba1cd35418af3504667affb4745b48937e214bafe"}, + {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:61e1a064906ccba038aa3c4a5a82f6199749efbbb3cef0804ae5c37f550eded0"}, + {file = "wrapt-1.14.0-cp38-cp38-win32.whl", hash = "sha256:28c659878f684365d53cf59dc9a1929ea2eecd7ac65da762be8b1ba193f7e84f"}, + {file = "wrapt-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:b0ed6ad6c9640671689c2dbe6244680fe8b897c08fd1fab2228429b66c518e5e"}, + {file = "wrapt-1.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3f7e671fb19734c872566e57ce7fc235fa953d7c181bb4ef138e17d607dc8a1"}, + {file = "wrapt-1.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87fa943e8bbe40c8c1ba4086971a6fefbf75e9991217c55ed1bcb2f1985bd3d4"}, + {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4775a574e9d84e0212f5b18886cace049a42e13e12009bb0491562a48bb2b758"}, + {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d57677238a0c5411c76097b8b93bdebb02eb845814c90f0b01727527a179e4d"}, + {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00108411e0f34c52ce16f81f1d308a571df7784932cc7491d1e94be2ee93374b"}, + {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d332eecf307fca852d02b63f35a7872de32d5ba8b4ec32da82f45df986b39ff6"}, + {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:01f799def9b96a8ec1ef6b9c1bbaf2bbc859b87545efbecc4a78faea13d0e3a0"}, + {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47045ed35481e857918ae78b54891fac0c1d197f22c95778e66302668309336c"}, + {file = "wrapt-1.14.0-cp39-cp39-win32.whl", hash = "sha256:2eca15d6b947cfff51ed76b2d60fd172c6ecd418ddab1c5126032d27f74bc350"}, + {file = "wrapt-1.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:bb36fbb48b22985d13a6b496ea5fb9bb2a076fea943831643836c9f6febbcfdc"}, + {file = "wrapt-1.14.0.tar.gz", hash = "sha256:8323a43bd9c91f62bb7d4be74cc9ff10090e7ef820e27bfe8815c57e68261311"}, ] wsrpc-aiohttp = [ {file = "wsrpc-aiohttp-3.2.0.tar.gz", hash = "sha256:f467abc51bcdc760fc5aeb7041abdeef46eeca3928dc43dd6e7fa7a533563818"}, @@ -2951,6 +2985,6 @@ yarl = [ {file = "yarl-1.7.2.tar.gz", hash = "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd"}, ] zipp = [ - {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, - {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, + {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, + {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, ] From 3a3e552b0aeb353a1e74144993faab84b0a2c395 Mon Sep 17 00:00:00 2001 From: BenoitConnan Date: Fri, 8 Apr 2022 16:28:28 +0200 Subject: [PATCH 0091/1227] Make the publish work under maya 2018 --- openpype/modules/shotgrid/aop/patch.py | 15 +++++++++------ openpype/modules/shotgrid/lib/__init__.py | 0 openpype/modules/shotgrid/lib/settings.py | 6 +++--- openpype/modules/shotgrid/shotgrid_module.py | 13 +++++++------ 4 files changed, 19 insertions(+), 15 deletions(-) create mode 100644 openpype/modules/shotgrid/lib/__init__.py diff --git a/openpype/modules/shotgrid/aop/patch.py b/openpype/modules/shotgrid/aop/patch.py index 208ca715d3..580aa2341f 100644 --- a/openpype/modules/shotgrid/aop/patch.py +++ b/openpype/modules/shotgrid/aop/patch.py @@ -1,6 +1,5 @@ from copy import copy -from avalon.api import AvalonMongoDB from openpype.api import Logger from openpype.modules.shotgrid.lib import ( @@ -42,8 +41,12 @@ def _fetch_linked_project_names(): def patch_avalon_db(): _LOG.debug("Run avalon patching") - if AvalonMongoDB.projects is _patched_projects: - return None - _LOG.debug("Patch Avalon.projects method") - AvalonMongoDB._prev_projects = copy(AvalonMongoDB.projects) - AvalonMongoDB.projects = _patched_projects + try: + from avalon.api import AvalonMongoDB + if AvalonMongoDB.projects is _patched_projects: + return None + _LOG.debug("Patch Avalon.projects method") + AvalonMongoDB._prev_projects = copy(AvalonMongoDB.projects) + AvalonMongoDB.projects = _patched_projects + except e: + _LOG.error("Unable to patch avalon db "+ e) diff --git a/openpype/modules/shotgrid/lib/__init__.py b/openpype/modules/shotgrid/lib/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/modules/shotgrid/lib/settings.py b/openpype/modules/shotgrid/lib/settings.py index 954b96f3c2..4c21501b99 100644 --- a/openpype/modules/shotgrid/lib/settings.py +++ b/openpype/modules/shotgrid/lib/settings.py @@ -1,5 +1,5 @@ import os -from functools import lru_cache +# from functools import lru_cache from pymongo import MongoClient from openpype.api import get_system_settings, get_project_settings @@ -13,12 +13,12 @@ def get_project_list(): return db.list_collection_names() -@lru_cache(maxsize=64) +# @lru_cache(maxsize=64) def get_shotgrid_project_settings(project): return get_project_settings(project).get(MODULE_NAME, {}) -@lru_cache(maxsize=64) +# @lru_cache(maxsize=64) def get_shotgrid_settings(): return get_system_settings().get("modules", {}).get(MODULE_NAME, {}) diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index 1f05ae5a52..009f0028f0 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -8,10 +8,6 @@ from openpype_interfaces import ( ) from openpype.modules import OpenPypeModule -from .aop.patch import patch_avalon_db -from .tray.shotgrid_tray import ( - ShotgridTrayWrapper, -) SHOTGRID_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -26,8 +22,6 @@ class ShotgridModule( tray_wrapper = None def initialize(self, modules_settings): - patch_avalon_db() - threading.Timer(10.0, patch_avalon_db).start() shotgrid_settings = modules_settings.get(self.name, dict()) self.enabled = shotgrid_settings.get("enabled", False) self.leecher_manager_url = shotgrid_settings.get( @@ -51,8 +45,15 @@ class ShotgridModule( return os.path.join(SHOTGRID_MODULE_DIR, "hooks") def tray_init(self): + from .tray.shotgrid_tray import ShotgridTrayWrapper + from .aop.patch import patch_avalon_db + + patch_avalon_db() + threading.Timer(10.0, patch_avalon_db).start() + self.tray_wrapper = ShotgridTrayWrapper(self) + def tray_start(self): return self.tray_wrapper.validate() From e6fd10925db80eeb73038a06330617325e318aab Mon Sep 17 00:00:00 2001 From: Paolo Berto Durante Date: Sun, 10 Apr 2022 20:17:22 +0900 Subject: [PATCH 0092/1227] plugins: modified labels for multiverse creators/publishers/loader --- openpype/hosts/maya/plugins/create/create_multiverse_usd.py | 4 ++-- .../hosts/maya/plugins/create/create_multiverse_usd_over.py | 2 +- openpype/hosts/maya/plugins/load/load_multiverse_usd.py | 4 ++-- openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py | 4 ++-- .../hosts/maya/plugins/publish/extract_multiverse_usd_over.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py index b2266e5a57..ff08bf3015 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py @@ -2,10 +2,10 @@ from openpype.hosts.maya.api import plugin, lib class CreateMultiverseUsd(plugin.Creator): - """Multiverse USD data""" + """Multiverse USD asset data""" name = "usdMain" - label = "Multiverse USD" + label = "Multiverse USD Asset" family = "usd" icon = "cubes" diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py index bb82ab2039..22c9ce1890 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py @@ -2,7 +2,7 @@ from openpype.hosts.maya.api import plugin, lib class CreateMultiverseUsdOver(plugin.Creator): - """Multiverse USD data""" + """Multiverse USD Override""" name = "usdOverrideMain" label = "Multiverse USD Override" diff --git a/openpype/hosts/maya/plugins/load/load_multiverse_usd.py b/openpype/hosts/maya/plugins/load/load_multiverse_usd.py index c03f2c5d92..ed0ffc8ef1 100644 --- a/openpype/hosts/maya/plugins/load/load_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/load/load_multiverse_usd.py @@ -14,13 +14,13 @@ from openpype.hosts.maya.api.pipeline import containerise class MultiverseUsdLoader(load.LoaderPlugin): - """Load the USD by Multiverse""" + """Read USD data in a Multiverse Compound""" families = ["model", "usd", "usdComposition", "usdOverride", "pointcache", "animation"] representations = ["usd", "usda", "usdc", "usdz", "abc"] - label = "Read USD by Multiverse" + label = "Load USD to Multiverse" order = -10 icon = "code-fork" color = "orange" diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py index 4e4efdc32c..678a4eb7df 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py @@ -8,9 +8,9 @@ from openpype.hosts.maya.api.lib import maintained_selection class ExtractMultiverseUsd(openpype.api.Extractor): - """Extractor for USD by Multiverse.""" + """Extractor for Multiverse USD asset data.""" - label = "Extract Multiverse USD" + label = "Extract Multiverse USD Asset" hosts = ["maya"] families = ["usd"] diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py index ce0e8a392a..4a78635c8b 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py @@ -7,7 +7,7 @@ from maya import cmds class ExtractMultiverseUsdOverride(openpype.api.Extractor): - """Extractor for USD Override by Multiverse.""" + """Extractor for Multiverse USD Override.""" label = "Extract Multiverse USD Override" hosts = ["maya"] From 70fe3f87127b7476f6c3d84437e292dba5022459 Mon Sep 17 00:00:00 2001 From: Paolo Berto Durante Date: Sun, 10 Apr 2022 20:17:55 +0900 Subject: [PATCH 0093/1227] documentation: added initial multiverse docs --- website/docs/artist_hosts_maya.md | 163 ++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/website/docs/artist_hosts_maya.md b/website/docs/artist_hosts_maya.md index 73e89384e8..84285bc6dd 100644 --- a/website/docs/artist_hosts_maya.md +++ b/website/docs/artist_hosts_maya.md @@ -598,6 +598,169 @@ about customizing review process refer to [admin section](project_settings/setti If you don't move `modelMain` into `reviewMain`, review will be generated but it will be published as separate entity. + +## Working with Multiverse in OpenPype + +OpenPype supports creating, publishing and loading of [Multiverse | USD]( +https://multi-verse.io) data. + +More specifically it is possible to: + +- Create USD Assets, USD compositions and USD Overrides. This _creates_ OpenPype + instances as Maya set nodes that contain information for published USD data. +- Publish USD Assets, USD compositions and USD Overrides. This _writes_ USD + files to disk and _publishes_ information to the OpenPype database. +- Load any USD data into Multiverse "Compound" shape nodes. This _reads_ USD + files (and also Alembic files) into Maya by streaming them to the viewport. +- Rendering USD data procedurally with 3DelightNSI, Arnold, Redshift, + RenderMan and VRay. This reads USD files by streaming them procedurally to the renderer, at render time. + +USD files written by Multiverse are 100% native USD data, they can be exchanged +with any other DCC applications able to interchange USD. Likewise, Multiverse +can read native USD data created by other applications. All the extensions are +supported: `.usd`, `.usdc`, `.usda`, `.usdz`. Sequences of USD files can also be +read, as USD clips. + +It is also possible to load Alembic data (`.abc`) in Multiverse Compounds, +further compose it & override it in other USD files, and render it procedurally. +Alembic data is always converted on the fly (in memory) to USD data. USD clip +from Alembic data are also supported. + +### Configuration + +To configure Multiverse in OpenPype, a user with admin privileges needs to setup +a new tool in OpenPype Project Settings, using a similar configuration as +depicted here: + +![Maya - Multiverse Setup](assets/maya-multiverse_setup.png) + +For more information about setup of Multiverse please refer to: LINK. + + +### Understanding Assets, Compositions and Overrides + +In Multiverse there are three main concepts for representing USD data. + + +#### Assets + +In Multiverse, the term "asset" refers to a USD file that contains a hierarchy +of primitives (transforms and shapes). In Maya this is typically a hierarchy of +nodes, as it can be seen in Outliner, that is written out to a USD file. Once +loaded, the same hierarchy will be visible in MEOW (Multiverse Explore and +Override Window). + +![Maya - Outliner vs MEOW](assets/maya-multiverse_asset_outliner_meow.png) + +An asset typically contains static or animated data such as: poly meshes, blend +shapes, reference objects, particles/points, curves, joint, subdivision surfaces +and other attributes such as normals, UVs, color sets, tangents, skin weights, +material assignment and shading networks, cameras, lights etc. It is also +possible to specify Maya data to be part of variants and to be used as proxies +by tagging them with attributes. + +An asset is typically read back into Maya into a Multiverse Compound shape +node and it can be composed and overridden. + +![Maya - Multiverse Compound Shape(assets/maya-multiverse_compound.png) + + +#### Composition + +In Multiverse, the term "composition" refers to a USD file that contains a +hierarchy of Multiverse Compounds shape nodes and Maya transform nodes, as it +can be seen in Outliner. This hierarchy is written out to a USD file that +effectively contains a static or animated composition of other USD files written +as references or payloads (according to the relative setting in the Compound). + +![Maya - Multiverse Composition](assets/maya-multiverse_composition_outliner_meow.png) + +A Composition is is typically read back into Maya into a Multiverse Compound +shape and it can be further composed and overridden. + + +#### Override + +In Multiverse, the term "override" refers to a USD file that contains static or animated overrides for a USD hierarchy. Overrides are set in MEOW, they can be: + +- render visibility overrides +- transform overrides +- attribute overrides +- material assignment overrides +- variant and variant definition overrides +- Instancing state overrides +- activity state overrides + +![Maya - Multiverse Override](assets/maya-multiverse_override_meow.png) + +Overrides are written out to a USD file that effectively contains a static or +animated hierarchy of overrides that can be "layered" on top of Assets, +Compositions and other Overrides. + +![Maya - Multiverse Override](assets/maya-multiverse_override_compound_layer.png) + +An Override is is typically read back into Maya as a layer of a Multiverse +Compound shape and it can be further composed and overridden. + + +### Creators + +It is possible to create OpenPype "instances" (Maya sets) for publishing +Multiverse USD Asset, Composition and Override. + +![Maya - Multiverse Creators](assets/maya-multiverse_openpype_creator.png) + +When creating OpenPype instances for Multiverse USD asset, compositions and +overrides the creator plug-in will put the relative selected data in a Maya set +node which holds the properties used by the Multiverse data writer for +publishing. + + +### Publishers + +The relative publishers for Asset, Composition and Overrides are available. They +all write USD files to disk and communicate publish info to the OpenPype +database. + +![Maya - Multiverse Creators](assets/maya-multiverse_openpype_publisher.png) + + +### Loader + +The loader creates a Multiverse Compound shape node reading the USD file of +choice. All data is streamed to the viewport and not contained in Maya. Thanks +to the various viewport load options the user can strategically decide how to +minimize the cost of viewport draw effectively being able to load any data, this +allows to bring into Maya scenes of virtually unlimited complexity. + +![Maya - Multiverse Loader](assets/maya-multiverse_openpype_loader.png) + +:::tip Note +When using the Loader, Multiverse, by design, never "imports" USD data into the +Maya scene as Maya data. Instead, when desired, Multiverse permits to import +specific USD primitives into the Maya scene as Maya data selectively from MEOW, +it also tracks what is being imported, so upon modification, it is possible to +write (create & publish) the modifies data as a USD file for being layered on +top of its relative Compound. See LINK. +::: + +### Rendering + +Multiverse offers procedural rendering with all the major production renderers: + +- 3DelightNSI +- Arnold +- Redshift +- RenderMan +- VRay + +This is completely transparent to the user: Multiverse Compound nodes present in +the scene, once a render is launched, will stream data to the renderer in a +procedural fashion. + +![Maya - Multiverse Rendering](assets/maya-multiverse_rendering.png) + + ## Working with Yeti in OpenPype OpenPype can work with [Yeti](https://peregrinelabs.com/yeti/) in two data modes. From df1a42c5306b855b893a88a10db2f2713c497620 Mon Sep 17 00:00:00 2001 From: Paolo Berto Durante Date: Sun, 10 Apr 2022 20:18:25 +0900 Subject: [PATCH 0094/1227] website: added jcube as a contributor to homepage --- website/src/pages/index.js | 10 +++++++--- website/static/img/jcube_logo_bw.png | Bin 0 -> 11762 bytes 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 website/static/img/jcube_logo_bw.png diff --git a/website/src/pages/index.js b/website/src/pages/index.js index 791b309bbc..1f4a645f61 100644 --- a/website/src/pages/index.js +++ b/website/src/pages/index.js @@ -68,6 +68,10 @@ const collab = [ title: 'Ellipse Studio', image: '/img/ellipse-studio.png', infoLink: 'http://www.dargaudmedia.com' + }, { + title: 'J Cube Inc', + image: '/img/jcube_logo_bw.png', + infoLink: 'https://j-cube.jp' } ]; @@ -224,7 +228,7 @@ function Home() { Get Support - +

OpenPYPE is developed, maintained and supported by PYPE.club

@@ -285,7 +289,7 @@ function Home() { - +

Integrations

@@ -334,7 +338,7 @@ function Home() { After Effects - + Unreal Engine (Beta) diff --git a/website/static/img/jcube_logo_bw.png b/website/static/img/jcube_logo_bw.png new file mode 100644 index 0000000000000000000000000000000000000000..76b897a865f52e1c38b68509d3f08c64481f0145 GIT binary patch literal 11762 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 Date: Mon, 11 Apr 2022 10:35:39 +0200 Subject: [PATCH 0095/1227] Add memoize implementation for settings purposes --- openpype/modules/shotgrid/lib/settings.py | 6 +++--- openpype/modules/shotgrid/lib/tools.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 openpype/modules/shotgrid/lib/tools.py diff --git a/openpype/modules/shotgrid/lib/settings.py b/openpype/modules/shotgrid/lib/settings.py index 4c21501b99..4a772de5b7 100644 --- a/openpype/modules/shotgrid/lib/settings.py +++ b/openpype/modules/shotgrid/lib/settings.py @@ -1,9 +1,9 @@ import os -# from functools import lru_cache from pymongo import MongoClient from openpype.api import get_system_settings, get_project_settings from openpype.modules.shotgrid.lib.const import MODULE_NAME +from openpype.modules.shotgrid.lib.tools import memoize def get_project_list(): @@ -13,12 +13,12 @@ def get_project_list(): return db.list_collection_names() -# @lru_cache(maxsize=64) +@memoize def get_shotgrid_project_settings(project): return get_project_settings(project).get(MODULE_NAME, {}) -# @lru_cache(maxsize=64) +@memoize def get_shotgrid_settings(): return get_system_settings().get("modules", {}).get(MODULE_NAME, {}) diff --git a/openpype/modules/shotgrid/lib/tools.py b/openpype/modules/shotgrid/lib/tools.py new file mode 100644 index 0000000000..6305e9ca50 --- /dev/null +++ b/openpype/modules/shotgrid/lib/tools.py @@ -0,0 +1,16 @@ +from functools import wraps + + +def memoize(function): + memo = {} + + @wraps(function) + def wrapper(*args): + try: + return memo[args] + except KeyError: + rv = function(*args) + memo[args] = rv + return rv + + return wrapper From 16dc4337ad467629b7ea9d984d616e33f2dd5c39 Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Mon, 11 Apr 2022 10:39:10 +0200 Subject: [PATCH 0096/1227] Fix after hound notes --- openpype/modules/shotgrid/aop/patch.py | 4 ++-- openpype/modules/shotgrid/shotgrid_module.py | 15 +++------------ 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/openpype/modules/shotgrid/aop/patch.py b/openpype/modules/shotgrid/aop/patch.py index 580aa2341f..56477f3bf9 100644 --- a/openpype/modules/shotgrid/aop/patch.py +++ b/openpype/modules/shotgrid/aop/patch.py @@ -48,5 +48,5 @@ def patch_avalon_db(): _LOG.debug("Patch Avalon.projects method") AvalonMongoDB._prev_projects = copy(AvalonMongoDB.projects) AvalonMongoDB.projects = _patched_projects - except e: - _LOG.error("Unable to patch avalon db "+ e) + except Exception as e: + _LOG.error("Unable to patch avalon db {}".format(e)) diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index 009f0028f0..06a07cc4f7 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -12,9 +12,7 @@ from openpype.modules import OpenPypeModule SHOTGRID_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) -class ShotgridModule( - OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths -): +class ShotgridModule(OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths): leecher_manager_url = None name = "shotgrid" enabled = False @@ -24,9 +22,7 @@ class ShotgridModule( def initialize(self, modules_settings): shotgrid_settings = modules_settings.get(self.name, dict()) self.enabled = shotgrid_settings.get("enabled", False) - self.leecher_manager_url = shotgrid_settings.get( - "leecher_manager_url", "" - ) + self.leecher_manager_url = shotgrid_settings.get("leecher_manager_url", "") def connect_with_modules(self, enabled_modules): pass @@ -35,11 +31,7 @@ class ShotgridModule( return {"PROJECT_ID": self.project_id} def get_plugin_paths(self): - return { - "publish": [ - os.path.join(SHOTGRID_MODULE_DIR, "plugins", "publish") - ] - } + return {"publish": [os.path.join(SHOTGRID_MODULE_DIR, "plugins", "publish")]} def get_launch_hook_paths(self): return os.path.join(SHOTGRID_MODULE_DIR, "hooks") @@ -53,7 +45,6 @@ class ShotgridModule( self.tray_wrapper = ShotgridTrayWrapper(self) - def tray_start(self): return self.tray_wrapper.validate() From 57386db03c9980c107488def4ad7a8155ede52a2 Mon Sep 17 00:00:00 2001 From: Vic Bartel Date: Mon, 11 Apr 2022 10:45:02 +0200 Subject: [PATCH 0097/1227] Fix after hound notes, part 2 --- openpype/modules/shotgrid/shotgrid_module.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index 06a07cc4f7..6e0cbe987b 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -12,7 +12,9 @@ from openpype.modules import OpenPypeModule SHOTGRID_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) -class ShotgridModule(OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths): +class ShotgridModule( + OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths +): leecher_manager_url = None name = "shotgrid" enabled = False @@ -22,7 +24,9 @@ class ShotgridModule(OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths def initialize(self, modules_settings): shotgrid_settings = modules_settings.get(self.name, dict()) self.enabled = shotgrid_settings.get("enabled", False) - self.leecher_manager_url = shotgrid_settings.get("leecher_manager_url", "") + self.leecher_manager_url = shotgrid_settings.get( + "leecher_manager_url", "" + ) def connect_with_modules(self, enabled_modules): pass @@ -31,7 +35,11 @@ class ShotgridModule(OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths return {"PROJECT_ID": self.project_id} def get_plugin_paths(self): - return {"publish": [os.path.join(SHOTGRID_MODULE_DIR, "plugins", "publish")]} + return { + "publish": [ + os.path.join(SHOTGRID_MODULE_DIR, "plugins", "publish") + ] + } def get_launch_hook_paths(self): return os.path.join(SHOTGRID_MODULE_DIR, "hooks") From 2e2deb349d082096d056f878a5cf629c2f95e12c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 14 Apr 2022 13:06:14 +0200 Subject: [PATCH 0098/1227] Match changes that were made to original IntegrateAsset Changes of: - https://github.com/pypeclub/OpenPype/commit/312d0309ab92de834629c58587f1a758d1d1e90c - https://github.com/pypeclub/OpenPype/commit/507f3615ab8f42f5664afcac01d339e0517afdf5 - https://github.com/pypeclub/OpenPype/commit/29dca65202d45a79e66c619b95d3408e227a9c05 --- openpype/plugins/publish/integrate_new.py | 61 ++++++++++++++++++----- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 9e8dfefc9e..768c413bf9 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -4,6 +4,7 @@ import sys import copy import clique import six +from collections import deque, defaultdict from bson.objectid import ObjectId from pymongo import DeleteMany, ReplaceOne, InsertOne, UpdateOne @@ -871,18 +872,18 @@ class SiteSync(object): attached_sites[remote_site] = create_metadata(remote_site, created=False) + # add alternative sites + cls._add_alternative_sites(system_sync_server_presets, attached_sites) + # add skeleton for sites where it should be always synced to always_accessible_sites = ( sync_project_presets["config"].get("always_accessible_on", []) ) - for site in always_accessible_sites: + for site in set(always_accessible_sites): site = site.strip() if site not in attached_sites: attached_sites[site] = create_metadata(site, created=False) - # add alternative sites - cls._add_alternative_sites(system_sync_server_presets, attached_sites) - return list(attached_sites.values()) @staticmethod @@ -904,8 +905,9 @@ class SiteSync(object): return local_site, remote_site - @staticmethod - def _add_alternative_sites(system_sync_server_presets, + @classmethod + def _add_alternative_sites(cls, + system_sync_server_presets, attached_sites): """Loop through all configured sites and add alternatives. @@ -916,18 +918,14 @@ class SiteSync(object): See SyncServerModule.handle_alternate_site """ conf_sites = system_sync_server_presets.get("sites", {}) + alt_site_pairs = cls._get_alt_site_pairs(conf_sites) - for site_name, site_info in conf_sites.items(): + for site_name, alt_sites in alt_site_pairs.items(): # Skip if already defined if site_name in attached_sites: continue - # Get alternate sites (stripped names) for this site name - alt_sites = site_info.get("alternative_sites", []) - alt_sites = [site.strip() for site in alt_sites] - alt_sites = set(alt_sites) - # If no alternative sites we don't need to add if not alt_sites: continue @@ -944,3 +942,42 @@ class SiteSync(object): # Note: We change mutable `attached_site` dict in-place attached_sites[site_name] = alt_site_meta + + @staticmethod + def _get_alt_site_pairs(conf_sites): + """Returns dict of site and its alternative sites. + If `site` has alternative site, it means that alt_site has + 'site' as + alternative site + Args: + conf_sites (dict) + Returns: + (dict): {'site': [alternative sites]...} + """ + alt_site_pairs = defaultdict(list) + for site_name, site_info in conf_sites.items(): + alt_sites = set(site_info.get("alternative_sites", [])) + alt_site_pairs[site_name].extend(alt_sites) + + for alt_site in alt_sites: + alt_site_pairs[alt_site].append(site_name) + + for site_name, alt_sites in alt_site_pairs.items(): + sites_queue = deque(alt_sites) + while sites_queue: + alt_site = sites_queue.popleft() + + # safety against wrong config + # {"SFTP": {"alternative_site": "SFTP"} + if alt_site == site_name or alt_site not in alt_site_pairs: + continue + + for alt_alt_site in alt_site_pairs[alt_site]: + if ( + alt_alt_site != site_name + and alt_alt_site not in alt_sites + ): + alt_sites.append(alt_alt_site) + sites_queue.append(alt_alt_site) + + return alt_site_pairs From 0fdd4f1aecd3b5fa09496d4aa48ee605a003e61d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 14 Apr 2022 13:07:33 +0200 Subject: [PATCH 0099/1227] Fix indentation --- openpype/plugins/publish/integrate_new.py | 66 +++++++++++------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 768c413bf9..4eccce4e81 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -943,41 +943,41 @@ class SiteSync(object): # Note: We change mutable `attached_site` dict in-place attached_sites[site_name] = alt_site_meta - @staticmethod - def _get_alt_site_pairs(conf_sites): - """Returns dict of site and its alternative sites. - If `site` has alternative site, it means that alt_site has - 'site' as - alternative site - Args: - conf_sites (dict) - Returns: - (dict): {'site': [alternative sites]...} - """ - alt_site_pairs = defaultdict(list) - for site_name, site_info in conf_sites.items(): - alt_sites = set(site_info.get("alternative_sites", [])) - alt_site_pairs[site_name].extend(alt_sites) + @staticmethod + def _get_alt_site_pairs(conf_sites): + """Returns dict of site and its alternative sites. + If `site` has alternative site, it means that alt_site has + 'site' as + alternative site + Args: + conf_sites (dict) + Returns: + (dict): {'site': [alternative sites]...} + """ + alt_site_pairs = defaultdict(list) + for site_name, site_info in conf_sites.items(): + alt_sites = set(site_info.get("alternative_sites", [])) + alt_site_pairs[site_name].extend(alt_sites) - for alt_site in alt_sites: - alt_site_pairs[alt_site].append(site_name) + for alt_site in alt_sites: + alt_site_pairs[alt_site].append(site_name) - for site_name, alt_sites in alt_site_pairs.items(): - sites_queue = deque(alt_sites) - while sites_queue: - alt_site = sites_queue.popleft() + for site_name, alt_sites in alt_site_pairs.items(): + sites_queue = deque(alt_sites) + while sites_queue: + alt_site = sites_queue.popleft() - # safety against wrong config - # {"SFTP": {"alternative_site": "SFTP"} - if alt_site == site_name or alt_site not in alt_site_pairs: - continue + # safety against wrong config + # {"SFTP": {"alternative_site": "SFTP"} + if alt_site == site_name or alt_site not in alt_site_pairs: + continue - for alt_alt_site in alt_site_pairs[alt_site]: - if ( - alt_alt_site != site_name - and alt_alt_site not in alt_sites - ): - alt_sites.append(alt_alt_site) - sites_queue.append(alt_alt_site) + for alt_alt_site in alt_site_pairs[alt_site]: + if ( + alt_alt_site != site_name + and alt_alt_site not in alt_sites + ): + alt_sites.append(alt_alt_site) + sites_queue.append(alt_alt_site) - return alt_site_pairs + return alt_site_pairs From 1a03bbe48a37cc62918152985488a0bd99d43473 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 14 Apr 2022 13:11:57 +0200 Subject: [PATCH 0100/1227] Store alt sites in a `set` --- openpype/plugins/publish/integrate_new.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 4eccce4e81..2795b59482 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -954,13 +954,13 @@ class SiteSync(object): Returns: (dict): {'site': [alternative sites]...} """ - alt_site_pairs = defaultdict(list) + alt_site_pairs = defaultdict(set) for site_name, site_info in conf_sites.items(): alt_sites = set(site_info.get("alternative_sites", [])) - alt_site_pairs[site_name].extend(alt_sites) + alt_site_pairs[site_name].update(alt_sites) for alt_site in alt_sites: - alt_site_pairs[alt_site].append(site_name) + alt_site_pairs[alt_site].add(site_name) for site_name, alt_sites in alt_site_pairs.items(): sites_queue = deque(alt_sites) @@ -977,7 +977,7 @@ class SiteSync(object): alt_alt_site != site_name and alt_alt_site not in alt_sites ): - alt_sites.append(alt_alt_site) + alt_sites.add(alt_alt_site) sites_queue.append(alt_alt_site) return alt_site_pairs From 5dadfb29386ff242ad335dccc14b2ef5df49297e Mon Sep 17 00:00:00 2001 From: Jiri Sindelar <45896205+jrsndl@users.noreply.github.com> Date: Thu, 14 Apr 2022 19:00:58 +0200 Subject: [PATCH 0101/1227] fix merge conflict --- .../plugins/publish/extract_review_slate.py | 2393 +++-------------- 1 file changed, 400 insertions(+), 1993 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 3ecea1f8bd..c8ee2ec7ed 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -1,1755 +1,387 @@ import os -import re -import copy -import json import shutil - -from abc import ABCMeta, abstractmethod -import six - -import clique - -import pyblish.api import openpype.api +import pyblish from openpype.lib import ( - get_ffmpeg_tool_path, - get_ffprobe_streams, - path_to_subprocess_arg, - - should_convert_for_ffmpeg, - convert_for_ffmpeg, - get_transcode_temp_directory + get_ffmpeg_tool_path, + get_ffprobe_data, + get_ffprobe_streams, + get_ffmpeg_codec_args, + get_ffmpeg_format_args, ) -import speedcopy - -class ExtractReview(pyblish.api.InstancePlugin): - """Extracting Review mov file for Ftrack - - Compulsory attribute of representation is tags list with "review", - otherwise the representation is ignored. - - All new representations are created and encoded by ffmpeg following - presets found in OpenPype Settings interface at - `project_settings/global/publish/ExtractReview/profiles:outputs`. +class ExtractReviewSlate(openpype.api.Extractor): + """ + Will add slate frame at the start of the video files """ - label = "Extract Review" - order = pyblish.api.ExtractorOrder + 0.02 - families = ["review"] - hosts = [ - "nuke", - "maya", - "shell", - "hiero", - "premiere", - "harmony", - "standalonepublisher", - "fusion", - "tvpaint", - "resolve", - "webpublisher", - "aftereffects", - "flame" - ] + label = "Review with Slate frame" + order = pyblish.api.ExtractorOrder + 0.031 + families = ["slate", "review"] + match = pyblish.api.Subset - # Supported extensions - image_exts = ["exr", "jpg", "jpeg", "png", "dpx"] - video_exts = ["mov", "mp4"] - supported_exts = image_exts + video_exts - - alpha_exts = ["exr", "png", "dpx"] - - # FFmpeg tools paths - ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") - - # Preset attributes - profiles = None + hosts = ["nuke", "shell"] + optional = True def process(self, instance): - self.log.debug(str(instance.data["representations"])) - # Skip review when requested. - if not instance.data.get("review", True): - return + inst_data = instance.data + if "representations" not in inst_data: + raise RuntimeError("Burnin needs already created mov to work on.") - # Run processing - self.main_process(instance) - - # Make sure cleanup happens and pop representations with "delete" tag. - for repre in tuple(instance.data["representations"]): - tags = repre.get("tags") or [] - if "delete" in tags and "thumbnail" not in tags: - instance.data["representations"].remove(repre) - - def _get_outputs_for_instance(self, instance): - host_name = instance.context.data["hostName"] - task_name = os.environ["AVALON_TASK"] - family = self.main_family_from_instance(instance) - - self.log.info("Host: \"{}\"".format(host_name)) - self.log.info("Task: \"{}\"".format(task_name)) - self.log.info("Family: \"{}\"".format(family)) - - profile = self.find_matching_profile( - host_name, task_name, family - ) - if not profile: - self.log.info(( - "Skipped instance. None of profiles in presets are for" - " Host: \"{}\" | Family: \"{}\" | Task \"{}\"" - ).format(host_name, family, task_name)) - return - - self.log.debug("Matching profile: \"{}\"".format(json.dumps(profile))) - - subset_name = instance.data.get("subset") - instance_families = self.families_from_instance(instance) - filtered_outputs = self.filter_output_defs( - profile, subset_name, instance_families - ) - # Store `filename_suffix` to save arguments - profile_outputs = [] - for filename_suffix, definition in filtered_outputs.items(): - definition["filename_suffix"] = filename_suffix - profile_outputs.append(definition) - - if not filtered_outputs: - self.log.info(( - "Skipped instance. All output definitions from selected" - " profile does not match to instance families. \"{}\"" - ).format(str(instance_families))) - return profile_outputs - - def _get_outputs_per_representations(self, instance, profile_outputs): - outputs_per_representations = [] - for repre in instance.data["representations"]: - repre_name = str(repre.get("name")) - tags = repre.get("tags") or [] - if "review" not in tags: - self.log.debug(( - "Repre: {} - Didn't found \"review\" in tags. Skipping" - ).format(repre_name)) - continue - - if "thumbnail" in tags: - self.log.debug(( - "Repre: {} - Found \"thumbnail\" in tags. Skipping" - ).format(repre_name)) - continue - - if "passing" in tags: - self.log.debug(( - "Repre: {} - Found \"passing\" in tags. Skipping" - ).format(repre_name)) - continue - - input_ext = repre["ext"] - if input_ext.startswith("."): - input_ext = input_ext[1:] - - if input_ext not in self.supported_exts: - self.log.info( - "Representation has unsupported extension \"{}\"".format( - input_ext - ) - ) - continue - - # Filter output definition by representation tags (optional) - outputs = self.filter_outputs_by_tags(profile_outputs, tags) - if not outputs: - self.log.info(( - "Skipped representation. All output definitions from" - " selected profile does not match to representation's" - " tags. \"{}\"" - ).format(str(tags))) - continue - outputs_per_representations.append((repre, outputs)) - return outputs_per_representations - - @staticmethod - def get_instance_label(instance): - return ( - getattr(instance, "label", None) - or instance.data.get("label") - or instance.data.get("name") - or str(instance) - ) - - def main_process(self, instance): - instance_label = self.get_instance_label(instance) - self.log.debug("Processing instance \"{}\"".format(instance_label)) - profile_outputs = self._get_outputs_for_instance(instance) - if not profile_outputs: - return - - # Loop through representations - outputs_per_repres = self._get_outputs_per_representations( - instance, profile_outputs - ) - fill_data = copy.deepcopy(instance.data["anatomyData"]) - for repre, outputs in outputs_per_repres: - # Check if input should be preconverted before processing - # Store original staging dir (it's value may change) - src_repre_staging_dir = repre["stagingDir"] - # Receive filepath to first file in representation - first_input_path = None - if not self.input_is_sequence(repre): - first_input_path = os.path.join( - src_repre_staging_dir, repre["files"] - ) - else: - for filename in repre["files"]: - first_input_path = os.path.join( - src_repre_staging_dir, filename - ) - break - - # Skip if file is not set - if first_input_path is None: - self.log.warning(( - "Representation \"{}\" have empty files. Skipped." - ).format(repre["name"])) - continue - - # Determine if representation requires pre conversion for ffmpeg - do_convert = should_convert_for_ffmpeg(first_input_path) - # If result is None the requirement of conversion can't be - # determined - if do_convert is None: - self.log.info(( - "Can't determine if representation requires conversion." - " Skipped." - )) - continue - - # Do conversion if needed - # - change staging dir of source representation - # - must be set back after output definitions processing - if do_convert: - new_staging_dir = get_transcode_temp_directory() - repre["stagingDir"] = new_staging_dir - - frame_start = instance.data["frameStart"] - frame_end = instance.data["frameEnd"] - convert_for_ffmpeg( - first_input_path, - new_staging_dir, - frame_start, - frame_end, - self.log - ) - - for _output_def in outputs: - output_def = copy.deepcopy(_output_def) - # Make sure output definition has "tags" key - if "tags" not in output_def: - output_def["tags"] = [] - - if "burnins" not in output_def: - output_def["burnins"] = [] - - # Create copy of representation - new_repre = copy.deepcopy(repre) - # Make sure new representation has origin staging dir - # - this is because source representation may change - # it's staging dir because of ffmpeg conversion - new_repre["stagingDir"] = src_repre_staging_dir - - # Remove "delete" tag from new repre if there is - if "delete" in new_repre["tags"]: - new_repre["tags"].remove("delete") - - # Add additional tags from output definition to representation - for tag in output_def["tags"]: - if tag not in new_repre["tags"]: - new_repre["tags"].append(tag) - - # Add burnin link from output definition to representation - for burnin in output_def["burnins"]: - if burnin not in new_repre.get("burnins", []): - if not new_repre.get("burnins"): - new_repre["burnins"] = [] - new_repre["burnins"].append(str(burnin)) - - self.log.debug( - "Linked burnins: `{}`".format(new_repre.get("burnins")) - ) - - self.log.debug( - "New representation tags: `{}`".format( - new_repre.get("tags")) - ) - - temp_data = self.prepare_temp_data( - instance, repre, output_def) - files_to_clean = [] - if temp_data["input_is_sequence"]: - self.log.info("Filling gaps in sequence.") - files_to_clean = self.fill_sequence_gaps( - temp_data["origin_repre"]["files"], - new_repre["stagingDir"], - temp_data["frame_start"], - temp_data["frame_end"]) - - # create or update outputName - output_name = new_repre.get("outputName", "") - output_ext = new_repre["ext"] - if output_name: - output_name += "_" - output_name += output_def["filename_suffix"] - if temp_data["without_handles"]: - output_name += "_noHandles" - - # add outputName to anatomy format fill_data - fill_data.update({ - "output": output_name, - "ext": output_ext - }) - - try: # temporary until oiiotool is supported cross platform - ffmpeg_args = self._ffmpeg_arguments( - output_def, instance, new_repre, temp_data, fill_data - ) - except ZeroDivisionError: - if 'exr' in temp_data["origin_repre"]["ext"]: - self.log.debug("Unsupported compression on input " + - "files. Skipping!!!") - return - raise NotImplementedError - - subprcs_cmd = " ".join(ffmpeg_args) - - # run subprocess - self.log.debug("Executing: {}".format(subprcs_cmd)) - - openpype.api.run_subprocess( - subprcs_cmd, shell=True, logger=self.log - ) - - # delete files added to fill gaps - if files_to_clean: - for f in files_to_clean: - os.unlink(f) - - new_repre.update({ - "name": "{}_{}".format(output_name, output_ext), - "outputName": output_name, - "outputDef": output_def, - "frameStartFtrack": temp_data["output_frame_start"], - "frameEndFtrack": temp_data["output_frame_end"], - "ffmpeg_cmd": subprcs_cmd - }) - - # Force to pop these key if are in new repre - new_repre.pop("preview", None) - new_repre.pop("thumbnail", None) - if "clean_name" in new_repre.get("tags", []): - new_repre.pop("outputName") - - # adding representation - self.log.debug( - "Adding new representation: {}".format(new_repre) - ) - instance.data["representations"].append(new_repre) - - # Cleanup temp staging dir after procesisng of output definitions - if do_convert: - temp_dir = repre["stagingDir"] - shutil.rmtree(temp_dir) - # Set staging dir of source representation back to previous - # value - repre["stagingDir"] = src_repre_staging_dir - - def input_is_sequence(self, repre): - """Deduce from representation data if input is sequence.""" - # TODO GLOBAL ISSUE - Find better way how to find out if input - # is sequence. Issues( in theory): - # - there may be multiple files ant not be sequence - # - remainders are not checked at all - # - there can be more than one collection - return isinstance(repre["files"], (list, tuple)) - - def prepare_temp_data(self, instance, repre, output_def): - """Prepare dictionary with values used across extractor's process. - - All data are collected from instance, context, origin representation - and output definition. - - There are few required keys in Instance data: "frameStart", "frameEnd" - and "fps". - - Args: - instance (Instance): Currently processed instance. - repre (dict): Representation from which new representation was - copied. - output_def (dict): Definition of output of this plugin. - - Returns: - dict: All data which are used across methods during process. - Their values should not change during process but new keys - with values may be added. - """ - - frame_start = instance.data["frameStart"] - frame_end = instance.data["frameEnd"] - - # Try to get handles from instance - handle_start = instance.data.get("handleStart") - handle_end = instance.data.get("handleEnd") - # If even one of handle values is not set on instance use - # handles from context - if handle_start is None or handle_end is None: - handle_start = instance.context.data["handleStart"] - handle_end = instance.context.data["handleEnd"] - - frame_start_handle = frame_start - handle_start - frame_end_handle = frame_end + handle_end - - # Change output frames when output should be without handles - without_handles = bool("no-handles" in output_def["tags"]) - if without_handles: - output_frame_start = frame_start - output_frame_end = frame_end - else: - output_frame_start = frame_start_handle - output_frame_end = frame_end_handle - - handles_are_set = handle_start > 0 or handle_end > 0 - - with_audio = True - if ( - # Check if has `no-audio` tag - "no-audio" in output_def["tags"] - # Check if instance has ny audio in data - or not instance.data.get("audio") - ): - with_audio = False - - input_is_sequence = self.input_is_sequence(repre) - input_allow_bg = False - if input_is_sequence and repre["files"]: - ext = os.path.splitext(repre["files"][0])[1].replace(".", "") - if ext in self.alpha_exts: - input_allow_bg = True - - return { - "fps": float(instance.data["fps"]), - "frame_start": frame_start, - "frame_end": frame_end, - "handle_start": handle_start, - "handle_end": handle_end, - "frame_start_handle": frame_start_handle, - "frame_end_handle": frame_end_handle, - "output_frame_start": int(output_frame_start), - "output_frame_end": int(output_frame_end), - "pixel_aspect": instance.data.get("pixelAspect", 1), - "resolution_width": instance.data.get("resolutionWidth"), - "resolution_height": instance.data.get("resolutionHeight"), - "origin_repre": repre, - "input_is_sequence": input_is_sequence, - "input_allow_bg": input_allow_bg, - "with_audio": with_audio, - "without_handles": without_handles, - "handles_are_set": handles_are_set - } - - def _ffmpeg_arguments( - self, output_def, instance, new_repre, temp_data, fill_data - ): - """Prepares ffmpeg arguments for expected extraction. - - Prepares input and output arguments based on output definition and - input files. - - Args: - output_def (dict): Currently processed output definition. - instance (Instance): Currently processed instance. - new_repre (dict): Representation representing output of this - process. - temp_data (dict): Base data for successful process. - """ - - # Get FFmpeg arguments from profile presets - out_def_ffmpeg_args = output_def.get("ffmpeg_args") or {} - - _ffmpeg_input_args = out_def_ffmpeg_args.get("input") or [] - _ffmpeg_output_args = out_def_ffmpeg_args.get("output") or [] - _ffmpeg_video_filters = out_def_ffmpeg_args.get("video_filters") or [] - _ffmpeg_audio_filters = out_def_ffmpeg_args.get("audio_filters") or [] - - # Cleanup empty strings - ffmpeg_input_args = [ - value for value in _ffmpeg_input_args if value.strip() - ] - ffmpeg_video_filters = [ - value for value in _ffmpeg_video_filters if value.strip() - ] - ffmpeg_audio_filters = [ - value for value in _ffmpeg_audio_filters if value.strip() - ] - - ffmpeg_output_args = [] - for value in _ffmpeg_output_args: - value = value.strip() - if not value: - continue - try: - value = value.format(**fill_data) - except Exception: - self.log.warning( - "Failed to format ffmpeg argument: {}".format(value), - exc_info=True - ) - pass - ffmpeg_output_args.append(value) - - # Prepare input and output filepaths - self.input_output_paths(new_repre, output_def, temp_data) - - # Set output frames len to 1 when ouput is single image - if ( - temp_data["output_ext_is_image"] - and not temp_data["output_is_sequence"] - ): - output_frames_len = 1 - - else: - output_frames_len = ( - temp_data["output_frame_end"] - - temp_data["output_frame_start"] - + 1 - ) - - duration_seconds = float(output_frames_len / temp_data["fps"]) - - if temp_data["input_is_sequence"]: - # Set start frame of input sequence (just frame in filename) - # - definition of input filepath - ffmpeg_input_args.append( - "-start_number {}".format(temp_data["output_frame_start"]) - ) - - # TODO add fps mapping `{fps: fraction}` ? - # - e.g.: { - # "25": "25/1", - # "24": "24/1", - # "23.976": "24000/1001" - # } - # Add framerate to input when input is sequence - ffmpeg_input_args.append( - "-framerate {}".format(temp_data["fps"]) - ) - - if temp_data["output_is_sequence"]: - # Set start frame of output sequence (just frame in filename) - # - this is definition of an output - ffmpeg_output_args.append( - "-start_number {}".format(temp_data["output_frame_start"]) - ) - - # Change output's duration and start point if should not contain - # handles - start_sec = 0 - if temp_data["without_handles"] and temp_data["handles_are_set"]: - # Set start time without handles - # - check if handle_start is bigger than 0 to avoid zero division - if temp_data["handle_start"] > 0: - start_sec = float(temp_data["handle_start"]) / temp_data["fps"] - ffmpeg_input_args.append("-ss {:0.10f}".format(start_sec)) - - # Set output duration inn seconds - ffmpeg_output_args.append("-t {:0.10}".format(duration_seconds)) - - # Set frame range of output when input or output is sequence - elif temp_data["output_is_sequence"]: - ffmpeg_output_args.append("-frames:v {}".format(output_frames_len)) - - # Add duration of an input sequence if output is video - if ( - temp_data["input_is_sequence"] - and not temp_data["output_is_sequence"] - ): - ffmpeg_input_args.append("-to {:0.10f}".format( - duration_seconds + start_sec - )) - - # Add video/image input path - ffmpeg_input_args.append( - "-i {}".format( - path_to_subprocess_arg(temp_data["full_input_path"]) - ) - ) - - # Add audio arguments if there are any. Skipped when output are images. - if not temp_data["output_ext_is_image"] and temp_data["with_audio"]: - audio_in_args, audio_filters, audio_out_args = self.audio_args( - instance, temp_data, duration_seconds - ) - ffmpeg_input_args.extend(audio_in_args) - ffmpeg_audio_filters.extend(audio_filters) - ffmpeg_output_args.extend(audio_out_args) - - res_filters = self.rescaling_filters(temp_data, output_def, new_repre) - ffmpeg_video_filters.extend(res_filters) - - ffmpeg_input_args = self.split_ffmpeg_args(ffmpeg_input_args) - - lut_filters = self.lut_filters(new_repre, instance, ffmpeg_input_args) - ffmpeg_video_filters.extend(lut_filters) - - bg_alpha = 0 - bg_color = output_def.get("bg_color") - if bg_color: - bg_red, bg_green, bg_blue, bg_alpha = bg_color - - if bg_alpha > 0: - if not temp_data["input_allow_bg"]: - self.log.info(( - "Output definition has defined BG color input was" - " resolved as does not support adding BG." - )) - else: - bg_color_hex = "#{0:0>2X}{1:0>2X}{2:0>2X}".format( - bg_red, bg_green, bg_blue - ) - bg_color_alpha = float(bg_alpha) / 255 - bg_color_str = "{}@{}".format(bg_color_hex, bg_color_alpha) - - self.log.info("Applying BG color {}".format(bg_color_str)) - color_args = [ - "split=2[bg][fg]", - "[bg]drawbox=c={}:replace=1:t=fill[bg]".format( - bg_color_str - ), - "[bg][fg]overlay=format=auto" - ] - # Prepend bg color change before all video filters - # NOTE at the time of creation it is required as video filters - # from settings may affect color of BG - # e.g. `eq` can remove alpha from input - for arg in reversed(color_args): - ffmpeg_video_filters.insert(0, arg) - - # Add argument to override output file - ffmpeg_output_args.append("-y") - - # NOTE This must be latest added item to output arguments. - ffmpeg_output_args.append( - path_to_subprocess_arg(temp_data["full_output_path"]) - ) - - return self.ffmpeg_full_args( - ffmpeg_input_args, - ffmpeg_video_filters, - ffmpeg_audio_filters, - ffmpeg_output_args - ) - - def split_ffmpeg_args(self, in_args): - """Makes sure all entered arguments are separated in individual items. - - Split each argument string with " -" to identify if string contains - one or more arguments. - """ - splitted_args = [] - for arg in in_args: - sub_args = arg.split(" -") - if len(sub_args) == 1: - if arg and arg not in splitted_args: - splitted_args.append(arg) - continue - - for idx, arg in enumerate(sub_args): - if idx != 0: - arg = "-" + arg - - if arg and arg not in splitted_args: - splitted_args.append(arg) - return splitted_args - - def ffmpeg_full_args( - self, input_args, video_filters, audio_filters, output_args - ): - """Post processing of collected FFmpeg arguments. - - Just verify that output arguments does not contain video or audio - filters which may cause issues because of duplicated argument entry. - Filters found in output arguments are moved to list they belong to. - - Args: - input_args (list): All collected ffmpeg arguments with inputs. - video_filters (list): All collected video filters. - audio_filters (list): All collected audio filters. - output_args (list): All collected ffmpeg output arguments with - output filepath. - - Returns: - list: Containing all arguments ready to run in subprocess. - """ - output_args = self.split_ffmpeg_args(output_args) - - video_args_dentifiers = ["-vf", "-filter:v"] - audio_args_dentifiers = ["-af", "-filter:a"] - for arg in tuple(output_args): - for identifier in video_args_dentifiers: - if arg.startswith("{} ".format(identifier)): - output_args.remove(arg) - arg = arg.replace(identifier, "").strip() - video_filters.append(arg) - - for identifier in audio_args_dentifiers: - if arg.startswith("{} ".format(identifier)): - output_args.remove(arg) - arg = arg.replace(identifier, "").strip() - audio_filters.append(arg) - - all_args = [] - all_args.append(path_to_subprocess_arg(self.ffmpeg_path)) - all_args.extend(input_args) - if video_filters: - all_args.append("-filter:v") - all_args.append("\"{}\"".format(",".join(video_filters))) - - if audio_filters: - all_args.append("-filter:a") - all_args.append("\"{}\"".format(",".join(audio_filters))) - - all_args.extend(output_args) - - return all_args - - def fill_sequence_gaps(self, files, staging_dir, start_frame, end_frame): - # type: (list, str, int, int) -> list - """Fill missing files in sequence by duplicating existing ones. - - This will take nearest frame file and copy it with so as to fill - gaps in sequence. Last existing file there is is used to for the - hole ahead. - - Args: - files (list): List of representation files. - staging_dir (str): Path to staging directory. - start_frame (int): Sequence start (no matter what files are there) - end_frame (int): Sequence end (no matter what files are there) - - Returns: - list of added files. Those should be cleaned after work - is done. - - Raises: - AssertionError: if more then one collection is obtained. - - """ - start_frame = int(start_frame) - end_frame = int(end_frame) - collections = clique.assemble(files)[0] - assert len(collections) == 1, "Multiple collections found." - col = collections[0] - - # do nothing if no gap is found in input range - not_gap = True - for fr in range(start_frame, end_frame + 1): - if fr not in col.indexes: - not_gap = False - - if not_gap: - return [] - - holes = col.holes() - - # generate ideal sequence - complete_col = clique.assemble( - [("{}{:0" + str(col.padding) + "d}{}").format( - col.head, f, col.tail - ) for f in range(start_frame, end_frame)] - )[0][0] # type: clique.Collection - - new_files = {} - last_existing_file = None - - for idx in holes.indexes: - # get previous existing file - test_file = os.path.normpath(os.path.join( - staging_dir, - ("{}{:0" + str(complete_col.padding) + "d}{}").format( - complete_col.head, idx - 1, complete_col.tail))) - if os.path.isfile(test_file): - new_files[idx] = test_file - last_existing_file = test_file - else: - if not last_existing_file: - # previous file is not found (sequence has a hole - # at the beginning. Use first available frame - # there is. - try: - last_existing_file = list(col)[0] - except IndexError: - # empty collection? - raise AssertionError( - "Invalid sequence collected") - new_files[idx] = os.path.normpath( - os.path.join(staging_dir, last_existing_file)) - - files_to_clean = [] - if new_files: - # so now new files are dict with missing frame as a key and - # existing file as a value. - for frame, file in new_files.items(): - self.log.info( - "Filling gap {} with {}".format(frame, file)) - - hole = os.path.join( - staging_dir, - ("{}{:0" + str(col.padding) + "d}{}").format( - col.head, frame, col.tail)) - speedcopy.copyfile(file, hole) - files_to_clean.append(hole) - - return files_to_clean - - def input_output_paths(self, new_repre, output_def, temp_data): - """Deduce input nad output file paths based on entered data. - - Input may be sequence of images, video file or single image file and - same can be said about output, this method helps to find out what - their paths are. - - It is validated that output directory exist and creates if not. - - During process are set "files", "stagingDir", "ext" and - "sequence_file" (if output is sequence) keys to new representation. - """ - - repre = temp_data["origin_repre"] - src_staging_dir = repre["stagingDir"] - dst_staging_dir = new_repre["stagingDir"] - - if temp_data["input_is_sequence"]: - collections = clique.assemble(repre["files"])[0] - full_input_path = os.path.join( - src_staging_dir, - collections[0].format("{head}{padding}{tail}") - ) - - filename = collections[0].format("{head}") - if filename.endswith("."): - filename = filename[:-1] - - # Make sure to have full path to one input file - full_input_path_single_file = os.path.join( - src_staging_dir, repre["files"][0] - ) - - else: - full_input_path = os.path.join( - src_staging_dir, repre["files"] - ) - filename = os.path.splitext(repre["files"])[0] - - # Make sure to have full path to one input file - full_input_path_single_file = full_input_path - - filename_suffix = output_def["filename_suffix"] - - output_ext = output_def.get("ext") - # Use input extension if output definition do not specify it - if output_ext is None: - output_ext = os.path.splitext(full_input_path)[1] - - # TODO Define if extension should have dot or not - if output_ext.startswith("."): - output_ext = output_ext[1:] - - # Store extension to representation - new_repre["ext"] = output_ext - - self.log.debug("New representation ext: `{}`".format(output_ext)) - - # Output is image file sequence witht frames - output_ext_is_image = bool(output_ext in self.image_exts) - output_is_sequence = bool( - output_ext_is_image - and "sequence" in output_def["tags"] - ) - if output_is_sequence: - new_repre_files = [] - frame_start = temp_data["output_frame_start"] - frame_end = temp_data["output_frame_end"] - - filename_base = "{}_{}".format(filename, filename_suffix) - # Temporary tempalte for frame filling. Example output: - # "basename.%04d.exr" when `frame_end` == 1001 - repr_file = "{}.%{:0>2}d.{}".format( - filename_base, len(str(frame_end)), output_ext - ) - - for frame in range(frame_start, frame_end + 1): - new_repre_files.append(repr_file % frame) - - new_repre["sequence_file"] = repr_file - full_output_path = os.path.join( - dst_staging_dir, filename_base, repr_file - ) - - else: - repr_file = "{}_{}.{}".format( - filename, filename_suffix, output_ext - ) - full_output_path = os.path.join(dst_staging_dir, repr_file) - new_repre_files = repr_file - - # Store files to representation - new_repre["files"] = new_repre_files - - # Make sure stagingDire exists - dst_staging_dir = os.path.normpath(os.path.dirname(full_output_path)) - if not os.path.exists(dst_staging_dir): - self.log.debug("Creating dir: {}".format(dst_staging_dir)) - os.makedirs(dst_staging_dir) - - # Store stagingDir to representaion - new_repre["stagingDir"] = dst_staging_dir - - # Store paths to temp data - temp_data["full_input_path"] = full_input_path - temp_data["full_input_path_single_file"] = full_input_path_single_file - temp_data["full_output_path"] = full_output_path - - # Store information about output - temp_data["output_ext_is_image"] = output_ext_is_image - temp_data["output_is_sequence"] = output_is_sequence - - self.log.debug("Input path {}".format(full_input_path)) - self.log.debug("Output path {}".format(full_output_path)) - - def audio_args(self, instance, temp_data, duration_seconds): - """Prepares FFMpeg arguments for audio inputs.""" - audio_in_args = [] - audio_filters = [] - audio_out_args = [] - audio_inputs = instance.data.get("audio") - if not audio_inputs: - return audio_in_args, audio_filters, audio_out_args - - for audio in audio_inputs: - # NOTE modified, always was expected "frameStartFtrack" which is - # STRANGE?!!! There should be different key, right? - # TODO use different frame start! - offset_seconds = 0 - frame_start_ftrack = instance.data.get("frameStartFtrack") - if frame_start_ftrack is not None: - offset_frames = frame_start_ftrack - audio["offset"] - offset_seconds = offset_frames / temp_data["fps"] - - if offset_seconds > 0: - audio_in_args.append( - "-ss {}".format(offset_seconds) - ) - - elif offset_seconds < 0: - audio_in_args.append( - "-itsoffset {}".format(abs(offset_seconds)) - ) - - # Audio duration is offset from `-ss` - audio_duration = duration_seconds + offset_seconds - - # Set audio duration - audio_in_args.append("-to {:0.10f}".format(audio_duration)) - - # Add audio input path - audio_in_args.append("-i {}".format( - path_to_subprocess_arg(audio["filename"]) - )) - - # NOTE: These were changed from input to output arguments. - # NOTE: value in "-ac" was hardcoded to 2, changed to audio inputs len. - # Need to merge audio if there are more than 1 input. - if len(audio_inputs) > 1: - audio_out_args.append("-filter_complex amerge") - audio_out_args.append("-ac {}".format(len(audio_inputs))) - - return audio_in_args, audio_filters, audio_out_args - - def get_letterbox_filters( - self, - letter_box_def, - output_width, - output_height - ): - output = [] - - ratio = letter_box_def["ratio"] - fill_color = letter_box_def["fill_color"] - f_red, f_green, f_blue, f_alpha = fill_color - fill_color_hex = "{0:0>2X}{1:0>2X}{2:0>2X}".format( - f_red, f_green, f_blue - ) - fill_color_alpha = float(f_alpha) / 255 - - line_thickness = letter_box_def["line_thickness"] - line_color = letter_box_def["line_color"] - l_red, l_green, l_blue, l_alpha = line_color - line_color_hex = "{0:0>2X}{1:0>2X}{2:0>2X}".format( - l_red, l_green, l_blue - ) - line_color_alpha = float(l_alpha) / 255 - - # test ratios and define if pillar or letter boxes - output_ratio = float(output_width) / float(output_height) - self.log.debug("Output ratio: {} LetterBox ratio: {}".format( - output_ratio, ratio - )) - pillar = output_ratio > ratio - need_mask = format(output_ratio, ".3f") != format(ratio, ".3f") - if not need_mask: - return [] - - if not pillar: - if fill_color_alpha > 0: - top_box = ( - "drawbox=0:0:{width}" - ":round(({height}-({width}/{ratio}))/2)" - ":t=fill:c={color}@{alpha}" - ).format( - width=output_width, - height=output_height, - ratio=ratio, - color=fill_color_hex, - alpha=fill_color_alpha - ) - - bottom_box = ( - "drawbox=0" - ":{height}-round(({height}-({width}/{ratio}))/2)" - ":{width}" - ":round(({height}-({width}/{ratio}))/2)" - ":t=fill:c={color}@{alpha}" - ).format( - width=output_width, - height=output_height, - ratio=ratio, - color=fill_color_hex, - alpha=fill_color_alpha - ) - output.extend([top_box, bottom_box]) - - if line_color_alpha > 0 and line_thickness > 0: - top_line = ( - "drawbox=0" - ":round(({height}-({width}/{ratio}))/2)-{l_thick}" - ":{width}:{l_thick}:t=fill:c={l_color}@{l_alpha}" - ).format( - width=output_width, - height=output_height, - ratio=ratio, - l_thick=line_thickness, - l_color=line_color_hex, - l_alpha=line_color_alpha - ) - bottom_line = ( - "drawbox=0" - ":{height}-round(({height}-({width}/{ratio}))/2)" - ":{width}:{l_thick}:t=fill:c={l_color}@{l_alpha}" - ).format( - width=output_width, - height=output_height, - ratio=ratio, - l_thick=line_thickness, - l_color=line_color_hex, - l_alpha=line_color_alpha - ) - output.extend([top_line, bottom_line]) - - else: - if fill_color_alpha > 0: - left_box = ( - "drawbox=0:0" - ":round(({width}-({height}*{ratio}))/2)" - ":{height}" - ":t=fill:c={color}@{alpha}" - ).format( - width=output_width, - height=output_height, - ratio=ratio, - color=fill_color_hex, - alpha=fill_color_alpha - ) - - right_box = ( - "drawbox=" - "{width}-round(({width}-({height}*{ratio}))/2)" - ":0" - ":round(({width}-({height}*{ratio}))/2)" - ":{height}" - ":t=fill:c={color}@{alpha}" - ).format( - width=output_width, - height=output_height, - ratio=ratio, - color=fill_color_hex, - alpha=fill_color_alpha - ) - output.extend([left_box, right_box]) - - if line_color_alpha > 0 and line_thickness > 0: - left_line = ( - "drawbox=round(({width}-({height}*{ratio}))/2)" - ":0:{l_thick}:{height}:t=fill:c={l_color}@{l_alpha}" - ).format( - width=output_width, - height=output_height, - ratio=ratio, - l_thick=line_thickness, - l_color=line_color_hex, - l_alpha=line_color_alpha - ) - - right_line = ( - "drawbox={width}-round(({width}-({height}*{ratio}))/2)" - ":0:{l_thick}:{height}:t=fill:c={l_color}@{l_alpha}" - ).format( - width=output_width, - height=output_height, - ratio=ratio, - l_thick=line_thickness, - l_color=line_color_hex, - l_alpha=line_color_alpha - ) - output.extend([left_line, right_line]) - - return output - - def rescaling_filters(self, temp_data, output_def, new_repre): - """Prepare vieo filters based on tags in new representation. - - It is possible to add letterboxes to output video or rescale to - different resolution. - - During this preparation "resolutionWidth" and "resolutionHeight" are - set to new representation. - """ - filters = [] - - # if reformat input video file is already reforamted from upstream - reformat_in_baking = bool("reformated" in new_repre["tags"]) - self.log.debug("reformat_in_baking: `{}`".format(reformat_in_baking)) - - # Get instance data - pixel_aspect = temp_data["pixel_aspect"] - - if reformat_in_baking: - self.log.debug(( - "Using resolution from input. It is already " - "reformated from upstream process" - )) - pixel_aspect = 1 - - # NOTE Skipped using instance's resolution - full_input_path_single_file = temp_data["full_input_path_single_file"] - try: - streams = get_ffprobe_streams( - full_input_path_single_file, self.log - ) - except Exception as exc: - raise AssertionError(( - "FFprobe couldn't read information about input file: \"{}\"." - " Error message: {}" - ).format(full_input_path_single_file, str(exc))) + suffix = "_slate" + slate_path = inst_data.get("slateFrame") + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") + slate_streams = get_ffprobe_streams(slate_path, self.log) # Try to find first stream with defined 'width' and 'height' # - this is to avoid order of streams where audio can be as first - # - there may be a better way (checking `codec_type`?) - input_width = None - input_height = None - output_width = None - output_height = None - for stream in streams: - if "width" in stream and "height" in stream: - input_width = int(stream["width"]) - input_height = int(stream["height"]) + # - there may be a better way (checking `codec_type`?)+ + slate_width = None + slate_height = None + for slate_stream in slate_streams: + if "width" in slate_stream and "height" in slate_stream: + slate_width = int(slate_stream["width"]) + slate_height = int(slate_stream["height"]) break - # Get instance data - pixel_aspect = temp_data["pixel_aspect"] - - if reformat_in_baking: - self.log.debug(( - "Using resolution from input. It is already " - "reformated from upstream process" - )) - pixel_aspect = 1 - output_width = input_width - output_height = input_height - # Raise exception of any stream didn't define input resolution - if input_width is None: + if slate_width is None: raise AssertionError(( "FFprobe couldn't read resolution from input file: \"{}\"" - ).format(full_input_path_single_file)) + ).format(slate_path)) - # NOTE Setting only one of `width` or `heigth` is not allowed - # - settings value can't have None but has value of 0 - output_width = output_def.get("width") or output_width or None - output_height = output_def.get("height") or output_height or None + if "reviewToWidth" in inst_data: + use_legacy_code = True + else: + use_legacy_code = False - # Overscal color - overscan_color_value = "black" - overscan_color = output_def.get("overscan_color") - if overscan_color: - bg_red, bg_green, bg_blue, _ = overscan_color - overscan_color_value = "#{0:0>2X}{1:0>2X}{2:0>2X}".format( - bg_red, bg_green, bg_blue + pixel_aspect = inst_data.get("pixelAspect", 1) + fps = inst_data.get("fps") + self.log.debug("fps {} ".format(fps)) + + for idx, repre in enumerate(inst_data["representations"]): + self.log.debug("repre ({}): `{}`".format(idx + 1, repre)) + + p_tags = repre.get("tags", []) + if "slate-frame" not in p_tags: + continue + + # get repre file + stagingdir = repre["stagingDir"] + input_file = "{0}".format(repre["files"]) + input_path = os.path.join( + os.path.normpath(stagingdir), repre["files"]) + self.log.debug("__ input_path: {}".format(input_path)) + + streams = get_ffprobe_streams( + input_path, self.log ) - self.log.debug("Overscan color: `{}`".format(overscan_color_value)) - - # Convert overscan value video filters - overscan_crop = output_def.get("overscan_crop") - overscan = OverscanCrop( - input_width, input_height, overscan_crop, overscan_color_value - ) - overscan_crop_filters = overscan.video_filters() - # Add overscan filters to filters if are any and modify input - # resolution by it's values - if overscan_crop_filters: - filters.extend(overscan_crop_filters) - input_width = overscan.width() - input_height = overscan.height() - # Use output resolution as inputs after cropping to skip usage of - # instance data resolution - if output_width is None or output_height is None: - output_width = input_width - output_height = input_height - - # Make sure input width and height is not an odd number - input_width_is_odd = bool(input_width % 2 != 0) - input_height_is_odd = bool(input_height % 2 != 0) - if input_width_is_odd or input_height_is_odd: - # Add padding to input and make sure this filter is at first place - filters.append("pad=width=ceil(iw/2)*2:height=ceil(ih/2)*2") - - # Change input width or height as first filter will change them - if input_width_is_odd: - self.log.info(( - "Converting input width from odd to even number. {} -> {}" - ).format(input_width, input_width + 1)) - input_width += 1 - - if input_height_is_odd: - self.log.info(( - "Converting input height from odd to even number. {} -> {}" - ).format(input_height, input_height + 1)) - input_height += 1 - - self.log.debug("pixel_aspect: `{}`".format(pixel_aspect)) - self.log.debug("input_width: `{}`".format(input_width)) - self.log.debug("input_height: `{}`".format(input_height)) - - # Use instance resolution if output definition has not set it. - if output_width is None or output_height is None: - output_width = temp_data["resolution_width"] - output_height = temp_data["resolution_height"] - - # Use source's input resolution instance does not have set it. - if output_width is None or output_height is None: - self.log.debug("Using resolution from input.") - output_width = input_width - output_height = input_height - - output_width = int(output_width) - output_height = int(output_height) - - # Make sure output width and height is not an odd number - # When this can happen: - # - if output definition has set width and height with odd number - # - `instance.data` contain width and height with odd numbeer - if output_width % 2 != 0: - self.log.warning(( - "Converting output width from odd to even number. {} -> {}" - ).format(output_width, output_width + 1)) - output_width += 1 - - if output_height % 2 != 0: - self.log.warning(( - "Converting output height from odd to even number. {} -> {}" - ).format(output_height, output_height + 1)) - output_height += 1 - - self.log.debug( - "Output resolution is {}x{}".format(output_width, output_height) - ) - - letter_box_def = output_def["letter_box"] - letter_box_enabled = letter_box_def["enabled"] - - # Skip processing if resolution is same as input's and letterbox is - # not set - if ( - output_width == input_width - and output_height == input_height - and not letter_box_enabled - and pixel_aspect == 1 - ): + # get video metadata + for stream in streams: + input_timecode = None + input_width = None + input_height = None + input_frame_rate = None + if "codec_type" in stream: + if stream["codec_type"] == "video": + self.log.debug("__Ffprobe Video: {}".format(stream)) + tags = stream.get("tags") or {} + input_timecode = tags.get("timecode") or "" + if "width" in stream and "height" in stream: + input_width = int(stream.get("width")) + input_height = int(stream.get("height")) + if "r_frame_rate" in stream: + # get frame rate in a form of + # x/y, like 24000/1001 for 23.976 + input_frame_rate = str(stream.get("r_frame_rate")) + if ( + input_timecode + and input_width + and input_height + and input_frame_rate + ): + break + # Raise exception of any stream didn't define input resolution + if input_width is None: + raise AssertionError(( + "FFprobe couldn't read resolution from input file: \"{}\"" + ).format(input_path)) + # Get audio metadata + for stream in streams: + audio_channels = None + audio_sample_rate = None + audio_channel_layout = None + input_audio = False + if stream["codec_type"] == "audio": + self.log.debug("__Ffprobe Audio: {}".format(stream)) + if stream["channels"]: + audio_channels = str(stream.get("channels")) + if stream["sample_rate"]: + audio_sample_rate = str(stream.get("sample_rate")) + if stream["channel_layout"]: + audio_channel_layout = str( + stream.get("channel_layout")) + if ( + audio_channels + and audio_sample_rate + and audio_channel_layout + ): + input_audio = True + break + # Get duration of one frame in micro seconds + one_frame_duration = "40000us" + if input_frame_rate: + items = input_frame_rate.split("/") + if len(items) == 1: + one_frame_duration = float(1.0) / float(items[0]) + elif len(items) == 2: + one_frame_duration = float(items[1]) / float(items[0]) + one_frame_duration *= 1000000 + one_frame_duration = str(int(one_frame_duration)) + "us" self.log.debug( - "Output resolution is same as input's" - " and \"letter_box\" key is not set. Skipping reformat part." - ) - new_repre["resolutionWidth"] = input_width - new_repre["resolutionHeight"] = input_height - return filters + "One frame duration is {}".format(one_frame_duration)) - # defining image ratios - input_res_ratio = ( - (float(input_width) * pixel_aspect) / input_height - ) - output_res_ratio = float(output_width) / float(output_height) - self.log.debug("input_res_ratio: `{}`".format(input_res_ratio)) - self.log.debug("output_res_ratio: `{}`".format(output_res_ratio)) - - # Round ratios to 2 decimal places for comparing - input_res_ratio = round(input_res_ratio, 2) - output_res_ratio = round(output_res_ratio, 2) - - # get scale factor - scale_factor_by_width = ( - float(output_width) / (input_width * pixel_aspect) - ) - scale_factor_by_height = ( - float(output_height) / input_height - ) - - self.log.debug( - "scale_factor_by_with: `{}`".format(scale_factor_by_width) - ) - self.log.debug( - "scale_factor_by_height: `{}`".format(scale_factor_by_height) - ) - - # scaling none square pixels and 1920 width - if ( - input_height != output_height - or input_width != output_width - or pixel_aspect != 1 - ): - if input_res_ratio < output_res_ratio: - self.log.debug( - "Input's resolution ratio is lower then output's" - ) - width_scale = int(input_width * scale_factor_by_height) - width_half_pad = int((output_width - width_scale) / 2) - height_scale = output_height - height_half_pad = 0 + # values are set in ExtractReview + if use_legacy_code: + to_width = inst_data["reviewToWidth"] + to_height = inst_data["reviewToHeight"] else: - self.log.debug("Input is heigher then output") - width_scale = output_width - width_half_pad = 0 - height_scale = int(input_height * scale_factor_by_width) - height_half_pad = int((output_height - height_scale) / 2) + to_width = input_width + to_height = input_height - self.log.debug("width_scale: `{}`".format(width_scale)) - self.log.debug("width_half_pad: `{}`".format(width_half_pad)) - self.log.debug("height_scale: `{}`".format(height_scale)) - self.log.debug("height_half_pad: `{}`".format(height_half_pad)) + self.log.debug("to_width: `{}`".format(to_width)) + self.log.debug("to_height: `{}`".format(to_height)) - filters.extend([ - "scale={}x{}:flags=lanczos".format( - width_scale, height_scale - ), - "pad={}:{}:{}:{}:{}".format( - output_width, output_height, - width_half_pad, height_half_pad, - overscan_color_value - ), - "setsar=1" + # defining image ratios + resolution_ratio = ( + (float(slate_width) * pixel_aspect) / slate_height + ) + delivery_ratio = float(to_width) / float(to_height) + self.log.debug("resolution_ratio: `{}`".format(resolution_ratio)) + self.log.debug("delivery_ratio: `{}`".format(delivery_ratio)) + + # get scale factor + scale_factor_by_height = float(to_height) / slate_height + scale_factor_by_width = float(to_width) / ( + slate_width * pixel_aspect + ) + + # shorten two decimals long float number for testing conditions + resolution_ratio_test = float("{:0.2f}".format(resolution_ratio)) + delivery_ratio_test = float("{:0.2f}".format(delivery_ratio)) + + self.log.debug("__ scale_factor_by_width: `{}`".format( + scale_factor_by_width + )) + self.log.debug("__ scale_factor_by_height: `{}`".format( + scale_factor_by_height + )) + + _remove_at_end = [] + + ext = os.path.splitext(input_file)[1] + output_file = input_file.replace(ext, "") + suffix + ext + + _remove_at_end.append(input_path) + + output_path = os.path.join( + os.path.normpath(stagingdir), output_file) + self.log.debug("__ output_path: {}".format(output_path)) + + input_args = [] + output_args = [] + + # preset's input data + if use_legacy_code: + input_args.extend(repre["_profile"].get('input', [])) + else: + input_args.extend(repre["outputDef"].get('input', [])) + + input_args.append("-loop 1 -i {}".format( + openpype.lib.path_to_subprocess_arg(slate_path))) + # if input has an audio, add silent audio to the slate + if input_audio: + input_args.extend( + ["-f lavfi -i anullsrc=r={}:cl={}:d={}".format( + audio_sample_rate, + audio_channel_layout, + one_frame_duration + )] + ) + + input_args.extend(["-r {}".format(input_frame_rate)]) + input_args.extend(["-frames:v 1"]) + # add timecode from source to the slate, substract one frame + if input_timecode: + offset_timecode = self._tc_offset( + str(input_timecode), + framerate=fps, + frame_offset=-1 + ) + self.log.debug("Slate Timecode: `{}`".format( + offset_timecode + )) + if offset_timecode: + input_args.extend(["-timecode {}".format(offset_timecode)]) + else: + # fall back to input timecode if offset fails + input_args.extend(["-timecode {}".format(input_timecode)]) + if use_legacy_code: + codec_args = repre["_profile"].get('codec', []) + output_args.extend(codec_args) + # preset's output data + output_args.extend(repre["_profile"].get('output', [])) + else: + # Codecs are copied from source for whole input + codec_args = self._get_codec_args(repre) + output_args.extend(codec_args) + + # make sure colors are correct + output_args.extend([ + "-vf scale=out_color_matrix=bt709", + "-color_primaries bt709", + "-color_trc bt709", + "-colorspace bt709" ]) - # letter_box - if letter_box_enabled: - filters.extend( - self.get_letterbox_filters( - letter_box_def, - output_width, - output_height + # scaling none square pixels and 1920 width + if ( + # Always scale slate if not legacy + not use_legacy_code or + # Legacy code required reformat tag + (use_legacy_code and "reformat" in p_tags) + ): + if resolution_ratio_test < delivery_ratio_test: + self.log.debug("lower then delivery") + width_scale = int(slate_width * scale_factor_by_height) + width_half_pad = int((to_width - width_scale) / 2) + height_scale = to_height + height_half_pad = 0 + else: + self.log.debug("heigher then delivery") + width_scale = to_width + width_half_pad = 0 + height_scale = int(slate_height * scale_factor_by_width) + height_half_pad = int((to_height - height_scale) / 2) + + self.log.debug( + "__ width_scale: `{}`".format(width_scale) ) + self.log.debug( + "__ width_half_pad: `{}`".format(width_half_pad) + ) + self.log.debug( + "__ height_scale: `{}`".format(height_scale) + ) + self.log.debug( + "__ height_half_pad: `{}`".format(height_half_pad) + ) + + scaling_arg = ("scale={0}x{1}:flags=lanczos," + "pad={2}:{3}:{4}:{5}:black,setsar=1").format( + width_scale, height_scale, to_width, to_height, + width_half_pad, height_half_pad + ) + # add output frame rate as a filter, just in case + scaling_arg += ",fps={}".format(input_frame_rate) + vf_back = self.add_video_filter_args(output_args, scaling_arg) + # add it to output_args + output_args.insert(0, vf_back) + + # overrides output file + output_args.append("-y") + + slate_v_path = slate_path.replace(".png", ext) + output_args.append( + path_to_subprocess_arg(slate_v_path) + ) + _remove_at_end.append(slate_v_path) + + slate_args = [ + path_to_subprocess_arg(ffmpeg_path), + " ".join(input_args), + " ".join(output_args) + ] + slate_subprocess_cmd = " ".join(slate_args) + + # run slate generation subprocess + self.log.debug( + "Slate Executing: {}".format(slate_subprocess_cmd) + ) + openpype.api.run_subprocess( + slate_subprocess_cmd, shell=True, logger=self.log ) - new_repre["resolutionWidth"] = output_width - new_repre["resolutionHeight"] = output_height + # create ffmpeg concat text file path + conc_text_file = input_file.replace(ext, "") + "_concat" + ".txt" + conc_text_path = os.path.join( + os.path.normpath(stagingdir), conc_text_file) + _remove_at_end.append(conc_text_path) + self.log.debug("__ conc_text_path: {}".format(conc_text_path)) - return filters + new_line = "\n" + with open(conc_text_path, "w") as conc_text_f: + conc_text_f.writelines([ + "file {}".format( + slate_v_path.replace("\\", "/")), + new_line, + "file {}".format(input_path.replace("\\", "/")) + ]) - def lut_filters(self, new_repre, instance, input_args): - """Add lut file to output ffmpeg filters.""" - filters = [] - # baking lut file application - lut_path = instance.data.get("lutPath") - if not lut_path or "bake-lut" not in new_repre["tags"]: - return filters - - # Prepare path for ffmpeg argument - lut_path = lut_path.replace("\\", "/").replace(":", "\\:") - - # Remove gamma from input arguments - if "-gamma" in input_args: - input_args.remove("-gamme") - - # Prepare filters - filters.append("lut3d=file='{}'".format(lut_path)) - # QUESTION hardcoded colormatrix? - filters.append("colormatrix=bt601:bt709") - - self.log.info("Added Lut to ffmpeg command.") - - return filters - - def main_family_from_instance(self, instance): - """Returns main family of entered instance.""" - family = instance.data.get("family") - if not family: - family = instance.data["families"][0] - return family - - def families_from_instance(self, instance): - """Returns all families of entered instance.""" - families = [] - family = instance.data.get("family") - if family: - families.append(family) - - for family in (instance.data.get("families") or tuple()): - if family not in families: - families.append(family) - return families - - def compile_list_of_regexes(self, in_list): - """Convert strings in entered list to compiled regex objects.""" - regexes = [] - if not in_list: - return regexes - - for item in in_list: - if not item: - continue - - try: - regexes.append(re.compile(item)) - except TypeError: - self.log.warning(( - "Invalid type \"{}\" value \"{}\"." - " Expected string based object. Skipping." - ).format(str(type(item)), str(item))) - - return regexes - - def validate_value_by_regexes(self, value, in_list): - """Validates in any regex from list match entered value. - - Args: - in_list (list): List with regexes. - value (str): String where regexes is checked. - - Returns: - int: Returns `0` when list is not set or is empty. Returns `1` when - any regex match value and returns `-1` when none of regexes - match value entered. - """ - if not in_list: - return 0 - - output = -1 - regexes = self.compile_list_of_regexes(in_list) - for regex in regexes: - if re.match(regex, value): - output = 1 - break - return output - - def profile_exclusion(self, matching_profiles): - """Find out most matching profile byt host, task and family match. - - Profiles are selectively filtered. Each profile should have - "__value__" key with list of booleans. Each boolean represents - existence of filter for specific key (host, tasks, family). - Profiles are looped in sequence. In each sequence are split into - true_list and false_list. For next sequence loop are used profiles in - true_list if there are any profiles else false_list is used. - - Filtering ends when only one profile left in true_list. Or when all - existence booleans loops passed, in that case first profile from left - profiles is returned. - - Args: - matching_profiles (list): Profiles with same values. - - Returns: - dict: Most matching profile. - """ - self.log.info( - "Search for first most matching profile in match order:" - " Host name -> Task name -> Family." - ) - # Filter all profiles with highest points value. First filter profiles - # with matching host if there are any then filter profiles by task - # name if there are any and lastly filter by family. Else use first in - # list. - idx = 0 - final_profile = None - while True: - profiles_true = [] - profiles_false = [] - for profile in matching_profiles: - value = profile["__value__"] - # Just use first profile when idx is greater than values. - if not idx < len(value): - final_profile = profile - break - - if value[idx]: - profiles_true.append(profile) - else: - profiles_false.append(profile) - - if final_profile is not None: - break - - if profiles_true: - matching_profiles = profiles_true - else: - matching_profiles = profiles_false - - if len(matching_profiles) == 1: - final_profile = matching_profiles[0] - break - idx += 1 - - final_profile.pop("__value__") - return final_profile - - def find_matching_profile(self, host_name, task_name, family): - """ Filter profiles by Host name, Task name and main Family. - - Filtering keys are "hosts" (list), "tasks" (list), "families" (list). - If key is not find or is empty than it's expected to match. - - Args: - profiles (list): Profiles definition from presets. - host_name (str): Current running host name. - task_name (str): Current context task name. - family (str): Main family of current Instance. - - Returns: - dict/None: Return most matching profile or None if none of profiles - match at least one criteria. - """ - - matching_profiles = None - if not self.profiles: - return matching_profiles - - highest_profile_points = -1 - # Each profile get 1 point for each matching filter. Profile with most - # points is returned. For cases when more than one profile will match - # are also stored ordered lists of matching values. - for profile in self.profiles: - profile_points = 0 - profile_value = [] - - # Host filtering - host_names = profile.get("hosts") - match = self.validate_value_by_regexes(host_name, host_names) - if match == -1: - self.log.debug( - "\"{}\" not found in {}".format(host_name, host_names) - ) - continue - profile_points += match - profile_value.append(bool(match)) - - # Task filtering - task_names = profile.get("tasks") - match = self.validate_value_by_regexes(task_name, task_names) - if match == -1: - self.log.debug( - "\"{}\" not found in {}".format(task_name, task_names) - ) - continue - profile_points += match - profile_value.append(bool(match)) - - # Family filtering - families = profile.get("families") - match = self.validate_value_by_regexes(family, families) - if match == -1: - self.log.debug( - "\"{}\" not found in {}".format(family, families) - ) - continue - profile_points += match - profile_value.append(bool(match)) - - if profile_points < highest_profile_points: - continue - - if profile_points > highest_profile_points: - matching_profiles = [] - highest_profile_points = profile_points - - if profile_points == highest_profile_points: - profile["__value__"] = profile_value - matching_profiles.append(profile) - - if not matching_profiles: - self.log.warning(( - "None of profiles match your setup." - " Host \"{}\" | Task: \"{}\" | Family: \"{}\"" - ).format(host_name, task_name, family)) - return - - if len(matching_profiles) == 1: - # Pop temporary key `__value__` - matching_profiles[0].pop("__value__") - return matching_profiles[0] - - self.log.warning(( - "More than one profile match your setup." - " Host \"{}\" | Task: \"{}\" | Family: \"{}\"" - ).format(host_name, task_name, family)) - - return self.profile_exclusion(matching_profiles) - - def families_filter_validation(self, families, output_families_filter): - """Determines if entered families intersect with families filters. - - All family values are lowered to avoid unexpected results. - """ - if not output_families_filter: - return True - - single_families = [] - combination_families = [] - for family_filter in output_families_filter: - if not family_filter: - continue - if isinstance(family_filter, (list, tuple)): - _family_filter = [] - for family in family_filter: - if family: - _family_filter.append(family.lower()) - combination_families.append(_family_filter) - else: - single_families.append(family_filter.lower()) - - for family in single_families: - if family in families: - return True - - for family_combination in combination_families: - valid = True - for family in family_combination: - if family not in families: - valid = False - break - - if valid: - return True - return False - - def filter_output_defs(self, profile, subset_name, families): - """Return outputs matching input instance families. - - Output definitions without families filter are marked as valid. - - Args: - profile (dict): Profile from presets matching current context. - families (list): All families of current instance. - - Returns: - list: Containg all output definitions matching entered families. - """ - outputs = profile.get("outputs") or [] - if not outputs: - return outputs - - # lower values - # QUESTION is this valid operation? - families = [family.lower() for family in families] - - filtered_outputs = {} - for filename_suffix, output_def in outputs.items(): - output_filters = output_def.get("filter") - # If no filter on output preset, skip filtering and add output - # profile for farther processing - if not output_filters: - filtered_outputs[filename_suffix] = output_def - continue - - families_filters = output_filters.get("families") - if not self.families_filter_validation(families, families_filters): - continue - - # Subsets name filters - subset_filters = [ - subset_filter - for subset_filter in output_filters.get("subsets", []) - # Skip empty strings - if subset_filter + # concat slate and videos together + concat_args = [ + ffmpeg_path, + "-y", + "-f", "concat", + "-safe", "0", + "-i", conc_text_path, + "-c:v", "copy", + output_path ] - if subset_name and subset_filters: - match = False - for subset_filter in subset_filters: - compiled = re.compile(subset_filter) - if compiled.search(subset_name): - match = True - break + if not input_audio: + # ffmpeg concat subprocess + self.log.debug( + "Executing concat: {}".format(" ".join(concat_args)) + ) + openpype.api.run_subprocess( + concat_args, logger=self.log + ) + else: + self.log.warning( + "Audio found. Creating slate with audio" + " is not supported at this time. Outputing slate-less" + ":\n{}".format(input_file)) + # skip concatenating slate, use slate-less file instead + shutil.copyfile(input_path, output_path) - if not match: - continue + self.log.debug("__ repre[tags]: {}".format(repre["tags"])) + repre_update = { + "files": output_file, + "name": repre["name"], + "tags": [x for x in repre["tags"] if x != "delete"] + } + inst_data["representations"][idx].update(repre_update) + self.log.debug( + "_ representation {}: `{}`".format( + idx, inst_data["representations"][idx])) - filtered_outputs[filename_suffix] = output_def + # removing temp files + for f in _remove_at_end: + os.remove(f) + self.log.debug("Removed: `{}`".format(f)) - return filtered_outputs + # Remove any representations tagged for deletion. + for repre in inst_data.get("representations", []): + if "delete" in repre.get("tags", []): + self.log.debug("Removing representation: {}".format(repre)) + inst_data["representations"].remove(repre) - def filter_outputs_by_tags(self, outputs, tags): - """Filter output definitions by entered representation tags. - - Output definitions without tags filter are marked as valid. - - Args: - outputs (list): Contain list of output definitions from presets. - tags (list): Tags of processed representation. - - Returns: - list: Containg all output definitions matching entered tags. - """ - filtered_outputs = [] - repre_tags_low = [tag.lower() for tag in tags] - for output_def in outputs: - valid = True - output_filters = output_def.get("filter") - if output_filters: - # Check tag filters - tag_filters = output_filters.get("tags") - if tag_filters: - tag_filters_low = [tag.lower() for tag in tag_filters] - valid = False - for tag in repre_tags_low: - if tag in tag_filters_low: - valid = True - break - - if not valid: - continue - - if valid: - filtered_outputs.append(output_def) - - return filtered_outputs + self.log.debug(inst_data["representations"]) def add_video_filter_args(self, args, inserting_arg): """ - Fixing video filter arguments to be one long string + Fixing video filter argumets to be one long string Args: args (list): list of string arguments @@ -1784,299 +416,74 @@ class ExtractReview(pyblish.api.InstancePlugin): return vf_back + def _get_codec_args(self, repre): + """Detect possible codec arguments from representation.""" + codec_args = [] -@six.add_metaclass(ABCMeta) -class _OverscanValue: - def __repr__(self): - return "<{}> {}".format(self.__class__.__name__, str(self)) + # Get one filename of representation files + filename = repre["files"] + # If files is list then pick first filename in list + if isinstance(filename, (tuple, list)): + filename = filename[0] + # Get full path to the file + full_input_path = os.path.join(repre["stagingDir"], filename) - @abstractmethod - def copy(self): - """Create a copy of object.""" - pass - - @abstractmethod - def size_for(self, value): - """Calculate new value for passed value.""" - pass - - -class PixValueExplicit(_OverscanValue): - def __init__(self, value): - self._value = int(value) - - def __str__(self): - return "{}px".format(self._value) - - def copy(self): - return PixValueExplicit(self._value) - - def size_for(self, value): - if self._value == 0: - return value - return self._value - - -class PercentValueExplicit(_OverscanValue): - def __init__(self, value): - self._value = float(value) - - def __str__(self): - return "{}%".format(abs(self._value)) - - def copy(self): - return PercentValueExplicit(self._value) - - def size_for(self, value): - if self._value == 0: - return value - return int((value / 100) * self._value) - - -class PixValueRelative(_OverscanValue): - def __init__(self, value): - self._value = int(value) - - def __str__(self): - sign = "-" if self._value < 0 else "+" - return "{}{}px".format(sign, abs(self._value)) - - def copy(self): - return PixValueRelative(self._value) - - def size_for(self, value): - return value + self._value - - -class PercentValueRelative(_OverscanValue): - def __init__(self, value): - self._value = float(value) - - def __str__(self): - return "{}%".format(self._value) - - def copy(self): - return PercentValueRelative(self._value) - - def size_for(self, value): - if self._value == 0: - return value - - offset = int((value / 100) * self._value) - - return value + offset - - -class PercentValueRelativeSource(_OverscanValue): - def __init__(self, value, source_sign): - self._value = float(value) - if source_sign not in ("-", "+"): - raise ValueError( - "Invalid sign value \"{}\" expected \"-\" or \"+\"".format( - source_sign - ) + try: + # Get information about input file via ffprobe tool + ffprobe_data = get_ffprobe_data(full_input_path, self.log) + except Exception: + self.log.warning( + "Could not get codec data from input.", + exc_info=True ) - self._source_sign = source_sign + return codec_args - def __str__(self): - return "{}%{}".format(self._value, self._source_sign) - - def copy(self): - return PercentValueRelativeSource(self._value, self._source_sign) - - def size_for(self, value): - if self._value == 0: - return value - return int((value * 100) / (100 - self._value)) - - -class OverscanCrop: - """Helper class to read overscan string and calculate output resolution. - - It is possible to enter single value for both width and heigh or two values - for width and height. Overscan string may have a few variants. Each variant - define output size for input size. - - ### Example - For input size: 2200px - - | String | Output | Description | - |----------|--------|-------------------------------------------------| - | "" | 2200px | Empty string does nothing. | - | "10%" | 220px | Explicit percent size. | - | "-10%" | 1980px | Relative percent size (decrease). | - | "+10%" | 2420px | Relative percent size (increase). | - | "-10%+" | 2000px | Relative percent size to output size. | - | "300px" | 300px | Explicit output size cropped or expanded. | - | "-300px" | 1900px | Relative pixel size (decrease). | - | "+300px" | 2500px | Relative pixel size (increase). | - | "300" | 300px | Value without "%" and "px" is used as has "px". | - - Value without sign (+/-) in is always explicit and value with sign is - relative. Output size for "200px" and "+200px" are not the same. - Values "0", "0px" or "0%" are ignored. - - All values that cause output resolution smaller than 1 pixel are invalid. - - Value "-10%+" is a special case which says that input's resolution is - bigger by 10% than expected output. - - It is possible to combine these variants to define different output for - width and height. - - Resolution: 2000px 1000px - - | String | Output | - |---------------|---------------| - | "100px 120px" | 2100px 1120px | - | "-10% -200px" | 1800px 800px | - """ - - item_regex = re.compile(r"([\+\-])?([0-9]+)(.+)?") - relative_source_regex = re.compile(r"%([\+\-])") - - def __init__( - self, input_width, input_height, string_value, overscal_color=None - ): - # Make sure that is not None - string_value = string_value or "" - - self.input_width = input_width - self.input_height = input_height - self.overscal_color = overscal_color - - width, height = self._convert_string_to_values(string_value) - self._width_value = width - self._height_value = height - - self._string_value = string_value - - def __str__(self): - return "{}".format(self._string_value) - - def __repr__(self): - return "<{}>".format(self.__class__.__name__) - - def width(self): - """Calculated width.""" - return self._width_value.size_for(self.input_width) - - def height(self): - """Calculated height.""" - return self._height_value.size_for(self.input_height) - - def video_filters(self): - """FFmpeg video filters to achieve expected result. - - Filter may be empty, use "crop" filter, "pad" filter or combination of - "crop" and "pad". - - Returns: - list: FFmpeg video filters. - """ - # crop=width:height:x:y - explicit start x, y position - # crop=width:height - x, y are related to center by width/height - # pad=width:heigth:x:y - explicit start x, y position - # pad=width:heigth - x, y are set to 0 by default - - width = self.width() - height = self.height() - - output = [] - if self.input_width == width and self.input_height == height: - return output - - # Make sure resolution has odd numbers - if width % 2 == 1: - width -= 1 - - if height % 2 == 1: - height -= 1 - - if width <= self.input_width and height <= self.input_height: - output.append("crop={}:{}".format(width, height)) - - elif width >= self.input_width and height >= self.input_height: - output.append( - "pad={}:{}:(iw-ow)/2:(ih-oh)/2:{}".format( - width, height, self.overscal_color - ) + source_ffmpeg_cmd = repre.get("ffmpeg_cmd") + codec_args.extend( + get_ffmpeg_format_args(ffprobe_data, source_ffmpeg_cmd) + ) + codec_args.extend( + get_ffmpeg_codec_args( + ffprobe_data, source_ffmpeg_cmd, logger=self.log ) - - elif width > self.input_width and height < self.input_height: - output.append("crop=iw:{}".format(height)) - output.append("pad={}:ih:(iw-ow)/2:(ih-oh)/2:{}".format( - width, self.overscal_color - )) - - elif width < self.input_width and height > self.input_height: - output.append("crop={}:ih".format(width)) - output.append("pad=iw:{}:(iw-ow)/2:(ih-oh)/2:{}".format( - height, self.overscal_color - )) - - return output - - def _convert_string_to_values(self, orig_string_value): - string_value = orig_string_value.strip().lower() - if not string_value: - return [PixValueRelative(0), PixValueRelative(0)] - - # Replace "px" (and spaces before) with single space - string_value = re.sub(r"([ ]+)?px", " ", string_value) - string_value = re.sub(r"([ ]+)%", "%", string_value) - # Make sure +/- sign at the beggining of string is next to number - string_value = re.sub(r"^([\+\-])[ ]+", "\g<1>", string_value) - # Make sure +/- sign in the middle has zero spaces before number under - # which belongs - string_value = re.sub( - r"[ ]([\+\-])[ ]+([0-9])", - r" \g<1>\g<2>", - string_value ) - string_parts = [ - part - for part in string_value.split(" ") - if part - ] - error_msg = "Invalid string for rescaling \"{}\"".format( - orig_string_value - ) - if 1 > len(string_parts) > 2: - raise ValueError(error_msg) + return codec_args - output = [] - for item in string_parts: - groups = self.item_regex.findall(item) - if not groups: - raise ValueError(error_msg) - - relative_sign, value, ending = groups[0] - if not relative_sign: - if not ending: - output.append(PixValueExplicit(value)) - else: - output.append(PercentValueExplicit(value)) + def _tc_offset(self, timecode, framerate=24.0, frame_offset=-1): + """Offsets timecode by frame""" + def _seconds(value, framerate): + if isinstance(value, str): + _zip_ft = zip((3600, 60, 1, 1/framerate), value.split(':')) + _s = sum(f * float(t) for f,t in _zip_ft) + elif isinstance(value, (int, float)): + _s = value / framerate else: - source_sign_group = self.relative_source_regex.findall(ending) - if not ending: - output.append(PixValueRelative(int(relative_sign + value))) + _s = 0 + return _s - elif source_sign_group: - source_sign = source_sign_group[0] - output.append(PercentValueRelativeSource( - float(relative_sign + value), source_sign - )) - else: - output.append( - PercentValueRelative(float(relative_sign + value)) - ) + def _frames(seconds, framerate, frame_offset): + _f = seconds * framerate + frame_offset + if _f < 0: + _f = framerate * 60 * 60 * 24 + _f + return _f - if len(output) == 1: - width = output.pop(0) - height = width.copy() - else: - width, height = output - - return width, height + def _timecode(seconds, framerate): + return '{h:02d}:{m:02d}:{s:02d}:{f:02d}'.format( + h = int(seconds / 3600), + m = int(seconds / 60 % 60), + s = int(seconds % 60), + f = int(round((seconds - int(seconds)) * framerate))) + drop = False + if ';' in timecode: + timecode = timecode.replace(';', ':') + drop = True + frames = _frames( + _seconds(timecode, framerate), + framerate, + frame_offset + ) + tc = _timecode(_seconds(frames, framerate), framerate) + if drop: + tc = ';'.join(tc.rsplit(':', 1)) + return tc From 8abc3ff7953001b687af2d860e0892a3d968c0a6 Mon Sep 17 00:00:00 2001 From: Jiri Sindelar <45896205+jrsndl@users.noreply.github.com> Date: Thu, 14 Apr 2022 19:19:28 +0200 Subject: [PATCH 0102/1227] hound --- openpype/plugins/publish/extract_review_slate.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index c8ee2ec7ed..13526ece66 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -454,8 +454,8 @@ class ExtractReviewSlate(openpype.api.Extractor): """Offsets timecode by frame""" def _seconds(value, framerate): if isinstance(value, str): - _zip_ft = zip((3600, 60, 1, 1/framerate), value.split(':')) - _s = sum(f * float(t) for f,t in _zip_ft) + _zip_ft = zip((3600, 60, 1, 1 / framerate), value.split(':')) + _s = sum(f * float(t) for f, t in _zip_ft) elif isinstance(value, (int, float)): _s = value / framerate else: @@ -470,10 +470,10 @@ class ExtractReviewSlate(openpype.api.Extractor): def _timecode(seconds, framerate): return '{h:02d}:{m:02d}:{s:02d}:{f:02d}'.format( - h = int(seconds / 3600), - m = int(seconds / 60 % 60), - s = int(seconds % 60), - f = int(round((seconds - int(seconds)) * framerate))) + h=int(seconds / 3600), + m=int(seconds / 60 % 60), + s=int(seconds % 60), + f=int(round((seconds - int(seconds)) * framerate))) drop = False if ';' in timecode: timecode = timecode.replace(';', ':') From 8a970b123c1697d7db28f31debf4f7113c3c3177 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 21 Apr 2022 11:24:49 +0200 Subject: [PATCH 0103/1227] Use logic directly from Sync Server module --- openpype/plugins/publish/integrate_new.py | 165 +--------------------- 1 file changed, 6 insertions(+), 159 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 2795b59482..cc6856e407 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -4,14 +4,13 @@ import sys import copy import clique import six -from collections import deque, defaultdict from bson.objectid import ObjectId from pymongo import DeleteMany, ReplaceOne, InsertOne, UpdateOne import pyblish.api from avalon import io import openpype.api -from datetime import datetime +from openpype.modules import ModulesManager from openpype.lib.profiles_filtering import filter_profiles from openpype.lib.file_transaction import FileTransaction @@ -299,11 +298,12 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self.log.debug("Retrieving Representation Site Sync information ...") # Get the accessible sites for Site Sync - sites = SiteSync.compute_resource_sync_sites( - system_settings=instance.context.data["system_settings"], - project_settings=instance.context.data["project_settings"] + manager = ModulesManager() + sync_server_module = manager.modules_by_name["sync_server"] + sites = sync_server_module.compute_resource_sync_sites( + project_name=instance.data["projectEntity"]["name"] ) - self.log.debug("Site Sync Sites: {}".format(sites)) + self.log.debug("Sync Server Sites: {}".format(sites)) # Compute the resource file infos once (files belonging to the # version instance instead of an individual representation) so @@ -828,156 +828,3 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "hash": openpype.api.source_hash(path), "sites": sites } - - -class SiteSync(object): - """Logic for Site Sync Module functionality""" - - @classmethod - def compute_resource_sync_sites(cls, - system_settings, - project_settings): - """Get available resource sync sites""" - - def create_metadata(name, created=True): - """Create sync site metadata for site with `name`""" - metadata = {"name": name} - if created: - metadata["created_dt"] = datetime.now() - return metadata - - default_sites = [create_metadata("studio")] - - # If sync site module is disabled return default fallback site - system_sync_server_presets = system_settings["modules"]["sync_server"] - log.debug("system_sett:: {}".format(system_sync_server_presets)) - if not system_sync_server_presets["enabled"]: - return default_sites - - # If sync site module is disabled in current - # project return default fallback site - sync_project_presets = project_settings["global"]["sync_server"] - if not sync_project_presets["enabled"]: - return default_sites - - local_site, remote_site = cls._get_sites(sync_project_presets) - - # Attached sites metadata by site name - # That is the local site, remote site, the always accesible sites - # and their alternate sites (alias of sites with different protocol) - attached_sites = dict() - attached_sites[local_site] = create_metadata(local_site) - - if remote_site and remote_site != local_site: - attached_sites[remote_site] = create_metadata(remote_site, - created=False) - - # add alternative sites - cls._add_alternative_sites(system_sync_server_presets, attached_sites) - - # add skeleton for sites where it should be always synced to - always_accessible_sites = ( - sync_project_presets["config"].get("always_accessible_on", []) - ) - for site in set(always_accessible_sites): - site = site.strip() - if site not in attached_sites: - attached_sites[site] = create_metadata(site, created=False) - - return list(attached_sites.values()) - - @staticmethod - def _get_sites(sync_project_presets): - """Returns tuple (local_site, remote_site)""" - local_site_id = openpype.api.get_local_site_id() - local_site = sync_project_presets["config"]. \ - get("active_site", "studio").strip() - - if local_site == 'local': - local_site = local_site_id - - remote_site = sync_project_presets["config"].get("remote_site") - if remote_site: - remote_site.strip() - - if remote_site == 'local': - remote_site = local_site_id - - return local_site, remote_site - - @classmethod - def _add_alternative_sites(cls, - system_sync_server_presets, - attached_sites): - """Loop through all configured sites and add alternatives. - - For all sites if an alternative site is detected that has an - accessible site then we can also register to that alternative site - with the same "created" state. So we match the existing data. - - See SyncServerModule.handle_alternate_site - """ - conf_sites = system_sync_server_presets.get("sites", {}) - alt_site_pairs = cls._get_alt_site_pairs(conf_sites) - - for site_name, alt_sites in alt_site_pairs.items(): - - # Skip if already defined - if site_name in attached_sites: - continue - - # If no alternative sites we don't need to add - if not alt_sites: - continue - - # Take a copy of data of the first alternate site that is already - # defined as an attached site to match the same state. - match_meta = next((attached_sites[site] for site in alt_sites - if site in attached_sites), None) - if not match_meta: - continue - - alt_site_meta = copy.deepcopy(match_meta) - alt_site_meta["name"] = site_name - - # Note: We change mutable `attached_site` dict in-place - attached_sites[site_name] = alt_site_meta - - @staticmethod - def _get_alt_site_pairs(conf_sites): - """Returns dict of site and its alternative sites. - If `site` has alternative site, it means that alt_site has - 'site' as - alternative site - Args: - conf_sites (dict) - Returns: - (dict): {'site': [alternative sites]...} - """ - alt_site_pairs = defaultdict(set) - for site_name, site_info in conf_sites.items(): - alt_sites = set(site_info.get("alternative_sites", [])) - alt_site_pairs[site_name].update(alt_sites) - - for alt_site in alt_sites: - alt_site_pairs[alt_site].add(site_name) - - for site_name, alt_sites in alt_site_pairs.items(): - sites_queue = deque(alt_sites) - while sites_queue: - alt_site = sites_queue.popleft() - - # safety against wrong config - # {"SFTP": {"alternative_site": "SFTP"} - if alt_site == site_name or alt_site not in alt_site_pairs: - continue - - for alt_alt_site in alt_site_pairs[alt_site]: - if ( - alt_alt_site != site_name - and alt_alt_site not in alt_sites - ): - alt_sites.add(alt_alt_site) - sites_queue.append(alt_alt_site) - - return alt_site_pairs From ae1acb950bbb69b203c36f19d40e3952eca46bfd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 21 Apr 2022 14:08:53 +0200 Subject: [PATCH 0104/1227] Fix: refactor to use correct function --- openpype/plugins/publish/integrate_new.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index cc6856e407..419e2b4e4b 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -309,9 +309,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # version instance instead of an individual representation) so # we can re-use those file infos per representation anatomy = instance.context.data["anatomy"] - resource_file_infos = self.prepare_file_info(resource_destinations, - sites=sites, - anatomy=anatomy) + resource_file_infos = self.get_files_info(resource_destinations, + sites=sites, + anatomy=anatomy) # Finalize the representations now the published files are integrated # Get 'files' info for representations and its attached resources From b74774d02fc4cf2efc8fc00c482845ab1b2dbdaa Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Apr 2022 18:09:35 +0200 Subject: [PATCH 0105/1227] Implement `get_visible_in_frame_range` for extracting Alembics --- openpype/hosts/maya/api/lib.py | 204 ++++++++++++++++++ .../maya/plugins/publish/extract_animation.py | 13 +- .../plugins/publish/extract_pointcache.py | 13 +- 3 files changed, 228 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 801cdb16f4..5304112dcf 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3176,3 +3176,207 @@ def parent_nodes(nodes, parent=None): node[0].setParent(node[1]) if delete_parent: pm.delete(parent_node) + + +@contextlib.contextmanager +def maintained_time(): + ct = cmds.currentTime(query=True) + try: + yield + finally: + cmds.currentTime(ct, edit=True) + + +def get_visible_in_frame_range(nodes, start, end): + """Return nodes that are visible in start-end frame range. + + - Ignores intermediateObjects completely. + - Considers animated visibility attributes + upstream visibilities. + + This is optimized for large scenes where some nodes in the parent + hierarchy might have some input connections to the visibilities, + e.g. key, driven keys, connections to other attributes, etc. + + This only does a single time step to `start` if current frame is + not inside frame range since the assumption is made that changing + a frame isn't so slow that it beats querying all visibility + plugs through MDGContext on another frame. + + Args: + nodes (list): List of node names to consider. + start (int): Start frame. + end (int): End frame. + + Returns: + list: List of node names. These will be long full path names so + might have a longer name than the input nodes. + + """ + # States we consider per node + VISIBLE = 1 # always visible + INVISIBLE = 0 # always invisible + ANIMATED = -1 # animated visibility + + # Ensure integers + start = int(start) + end = int(end) + + # Consider only non-intermediate dag nodes and use the "long" names. + nodes = cmds.ls(nodes, long=True, noIntermediate=True, type="dagNode") + if not nodes: + return [] + + with maintained_time(): + # Go to first frame of the range if we current time is outside of + # the queried range. This is to do a single query on which are at + # least visible at a time inside the range, (e.g those that are + # always visible) + current_time = cmds.currentTime(query=True) + if not (start <= current_time <= end): + cmds.currentTime(start) + + visible = cmds.ls(nodes, long=True, visible=True) + if len(visible) == len(nodes) or start == end: + # All are visible on frame one, so they are at least visible once + # inside the frame range. + return visible + + # For the invisible ones check whether its visibility and/or + # any of its parents visibility attributes are animated. If so, it might + # get visible on other frames in the range. + def memodict(f): + """Memoization decorator for a function taking a single argument. + + See: http://code.activestate.com/recipes/ + 578231-probably-the-fastest-memoization-decorator-in-the-/ + """ + + class memodict(dict): + def __missing__(self, key): + ret = self[key] = f(key) + return ret + + return memodict().__getitem__ + + @memodict + def get_state(node): + plug = node + ".visibility" + connections = cmds.listConnections(plug, + source=True, + destination=False) + if connections: + return ANIMATED + else: + return VISIBLE if cmds.getAttr(plug) else INVISIBLE + + visible = set(visible) + invisible = [node for node in nodes if node not in visible] + always_invisible = set() + # Iterate over the nodes by short to long names, so we iterate the highest + # in hierarcy nodes first. So the collected data can be used from the + # cache for parent queries in next iterations. + node_dependencies = dict() + for node in sorted(invisible, key=len): + + state = get_state(node) + if state == INVISIBLE: + always_invisible.add(node) + continue + + # If not always invisible by itself we should go through and check + # the parents to see if any of them are always invisible. For those + # that are "ANIMATED" we consider that this node is dependent on + # that attribute, we store them as dependency. + dependencies = set() + if state == ANIMATED: + dependencies.add(node) + + traversed_parents = list() + for parent in iter_parents(node): + + if not parent: + # Workaround bug in iter_parents + continue + + if parent in always_invisible or get_state(parent) == INVISIBLE: + # When parent is always invisible then consider this parent, + # this node we started from and any of the parents we + # have traversed in-between to be *always invisible* + always_invisible.add(parent) + always_invisible.add(node) + always_invisible.update(traversed_parents) + break + + # If we have traversed the parent before and its visibility + # was dependent on animated visibilities then we can just extend + # its dependencies for to those for this node and break further + # iteration upwards. + parent_dependencies = node_dependencies.get(parent, None) + if parent_dependencies is not None: + dependencies.update(parent_dependencies) + break + + state = get_state(parent) + if state == ANIMATED: + dependencies.add(parent) + + traversed_parents.append(parent) + + if node not in always_invisible and dependencies: + node_dependencies[node] = dependencies + + if not node_dependencies: + return list(visible) + + # Now we only have to check the visibilities for nodes that have animated + # visibility dependencies upstream. The fastest way to check these + # visibility attributes across different frames is with Python api 2.0 + # so we do that. + @memodict + def get_visibility_mplug(node): + """Return api 2.0 MPlug with cached memoize decorator""" + sel = om.MSelectionList() + sel.add(node) + dag = sel.getDagPath(0) + return om.MFnDagNode(dag).findPlug("visibility", True) + + # We skip the first frame as we already used that frame to check for + # overall visibilities. And end+1 to include the end frame. + scene_units = om.MTime.uiUnit() + for frame in range(start + 1, end + 1): + + mtime = om.MTime(frame, unit=scene_units) + context = om.MDGContext(mtime) + + # Build little cache so we don't query the same MPlug's value + # again if it was checked on this frame and also is a dependency + # for another node + frame_visibilities = {} + + for node, dependencies in list(node_dependencies.items()): + + for dependency in dependencies: + + dependency_visible = frame_visibilities.get(dependency, None) + if dependency_visible is None: + mplug = get_visibility_mplug(dependency) + dependency_visible = mplug.asBool(context) + frame_visibilities[dependency] = dependency_visible + + if not dependency_visible: + # One dependency is not visible, thus the + # node is not visible. + break + + else: + # All dependencies are visible. + visible.add(node) + # Remove node with dependencies for next iterations + # because it was visible at least once. + node_dependencies.pop(node) + + # If no more nodes to process break the frame iterations.. + if not node_dependencies: + break + + return list(visible) diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py index 8a8bd67cd8..b63cebde04 100644 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_animation.py @@ -6,7 +6,8 @@ import openpype.api from openpype.hosts.maya.api.lib import ( extract_alembic, suspended_refresh, - maintained_selection + maintained_selection, + get_visible_in_frame_range ) @@ -70,6 +71,16 @@ class ExtractAnimation(openpype.api.Extractor): # Since Maya 2017 alembic supports multiple uv sets - write them. options["writeUVSets"] = True + if instance.data.get("visibleOnly", False): + # If we only want to include nodes that are visible in the frame + # range then we need to do our own check. Alembic's `visibleOnly` + # flag does not filter out those that are only hidden on some + # frames as it counts "animated" or "connected" visibilities as + # if it's always visible. + nodes = get_visible_in_frame_range(nodes, + start=start, + end=end) + with suspended_refresh(): with maintained_selection(): cmds.select(nodes, noExpand=True) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 60502fdde1..4510943a48 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -6,7 +6,8 @@ import openpype.api from openpype.hosts.maya.api.lib import ( extract_alembic, suspended_refresh, - maintained_selection + maintained_selection, + get_visible_in_frame_range ) @@ -73,6 +74,16 @@ class ExtractAlembic(openpype.api.Extractor): # Since Maya 2017 alembic supports multiple uv sets - write them. options["writeUVSets"] = True + if instance.data.get("visibleOnly", False): + # If we only want to include nodes that are visible in the frame + # range then we need to do our own check. Alembic's `visibleOnly` + # flag does not filter out those that are only hidden on some + # frames as it counts "animated" or "connected" visibilities as + # if it's always visible. + nodes = get_visible_in_frame_range(nodes, + start=start, + end=end) + with suspended_refresh(): with maintained_selection(): cmds.select(nodes, noExpand=True) From ddddb86e77939214d818a54efc7a0eda5587588d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 25 Apr 2022 18:56:20 +0200 Subject: [PATCH 0106/1227] wip on ue5 support --- openpype/hosts/unreal/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/unreal/lib.py b/openpype/hosts/unreal/lib.py index d4a776e892..c0b4c7061c 100644 --- a/openpype/hosts/unreal/lib.py +++ b/openpype/hosts/unreal/lib.py @@ -74,7 +74,7 @@ def get_editor_executable_path(engine_path: Path) -> Path: """Get UE4 Editor executable path.""" ue4_path = engine_path / "Engine/Binaries" if platform.system().lower() == "windows": - ue4_path /= "Win64/UE4Editor.exe" + ue4_path /= "Win64/UnrealEditor.exe" elif platform.system().lower() == "linux": ue4_path /= "Linux/UE4Editor" @@ -420,7 +420,7 @@ class {1}_API A{0}GameModeBase : public AGameModeBase f.write(game_mode_h) u_build_tool = Path( - engine_path / "Engine/Binaries/DotNET/UnrealBuildTool.exe") + engine_path / "Engine/Binaries/DotNET/UnrealBuildTool/UnrealBuildTool.exe") u_header_tool = None arch = "Win64" From 66f8ebbd0f175e275bae7ae4b555f55b7fe12163 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 26 Apr 2022 09:10:03 +0200 Subject: [PATCH 0107/1227] Use MDGContext as context manager - Functionality added in Maya 2018 - Usage without `MDGContext.makeCurrent()` is deprecated since Maya 2022 --- openpype/hosts/maya/api/lib.py | 51 +++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 5304112dcf..82de105d16 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3340,40 +3340,47 @@ def get_visible_in_frame_range(nodes, start, end): dag = sel.getDagPath(0) return om.MFnDagNode(dag).findPlug("visibility", True) + @contextlib.contextmanager + def dgcontext(mtime): + """MDGContext context manager""" + context = om.MDGContext(mtime) + try: + previous = context.makeCurrent() + yield context + finally: + previous.makeCurrent() + # We skip the first frame as we already used that frame to check for # overall visibilities. And end+1 to include the end frame. scene_units = om.MTime.uiUnit() for frame in range(start + 1, end + 1): - mtime = om.MTime(frame, unit=scene_units) - context = om.MDGContext(mtime) # Build little cache so we don't query the same MPlug's value # again if it was checked on this frame and also is a dependency # for another node frame_visibilities = {} + with dgcontext(mtime) as context: + for node, dependencies in list(node_dependencies.items()): + for dependency in dependencies: + dependency_visible = frame_visibilities.get(dependency, + None) + if dependency_visible is None: + mplug = get_visibility_mplug(dependency) + dependency_visible = mplug.asBool(context) + frame_visibilities[dependency] = dependency_visible - for node, dependencies in list(node_dependencies.items()): + if not dependency_visible: + # One dependency is not visible, thus the + # node is not visible. + break - for dependency in dependencies: - - dependency_visible = frame_visibilities.get(dependency, None) - if dependency_visible is None: - mplug = get_visibility_mplug(dependency) - dependency_visible = mplug.asBool(context) - frame_visibilities[dependency] = dependency_visible - - if not dependency_visible: - # One dependency is not visible, thus the - # node is not visible. - break - - else: - # All dependencies are visible. - visible.add(node) - # Remove node with dependencies for next iterations - # because it was visible at least once. - node_dependencies.pop(node) + else: + # All dependencies are visible. + visible.add(node) + # Remove node with dependencies for next frame iterations + # because it was visible at least once. + node_dependencies.pop(node) # If no more nodes to process break the frame iterations.. if not node_dependencies: From 338bb1560a33582364f3a45724cf2ff19f89a2da Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 26 Apr 2022 09:43:16 +0200 Subject: [PATCH 0108/1227] Fix bug in `iter_parents` Previously an empty string could be yielded for e.g. `"|cube"` splitting to `["", "cube"]` --- openpype/hosts/maya/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 82de105d16..901b8c4a4c 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1876,7 +1876,7 @@ def iter_parents(node): """ while True: split = node.rsplit("|", 1) - if len(split) == 1: + if len(split) == 1 or not split[0]: return node = split[0] From b7d52214101960e8e821efdca417306ab99957e8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 26 Apr 2022 09:43:56 +0200 Subject: [PATCH 0109/1227] Remove redundant workaround --- openpype/hosts/maya/api/lib.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 901b8c4a4c..52e84c00ab 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3294,10 +3294,6 @@ def get_visible_in_frame_range(nodes, start, end): traversed_parents = list() for parent in iter_parents(node): - if not parent: - # Workaround bug in iter_parents - continue - if parent in always_invisible or get_state(parent) == INVISIBLE: # When parent is always invisible then consider this parent, # this node we started from and any of the parents we From faff541bc73529f1a2ebe16ff4b496d01d77f1af Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 26 Apr 2022 16:43:19 +0200 Subject: [PATCH 0110/1227] Removed submodule repos/avalon-core --- .gitmodules | 0 repos/avalon-core | 1 - 2 files changed, 1 deletion(-) delete mode 100644 .gitmodules delete mode 160000 repos/avalon-core diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/repos/avalon-core b/repos/avalon-core deleted file mode 160000 index 2fa14cea6f..0000000000 --- a/repos/avalon-core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2fa14cea6f6a9d86eec70bbb96860cbe4c75c8eb From 4c259c443e1c9a96c92bf48113eda9d31bff309b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Apr 2022 17:30:21 +0200 Subject: [PATCH 0111/1227] Improve comments --- openpype/hosts/maya/api/lib.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 52e84c00ab..79f2442d16 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3227,10 +3227,9 @@ def get_visible_in_frame_range(nodes, start, end): return [] with maintained_time(): - # Go to first frame of the range if we current time is outside of - # the queried range. This is to do a single query on which are at - # least visible at a time inside the range, (e.g those that are - # always visible) + # Go to first frame of the range if the current time is outside + # the queried range so can directly query all visible nodes on + # that frame. current_time = cmds.currentTime(query=True) if not (start <= current_time <= end): cmds.currentTime(start) @@ -3272,8 +3271,8 @@ def get_visible_in_frame_range(nodes, start, end): visible = set(visible) invisible = [node for node in nodes if node not in visible] always_invisible = set() - # Iterate over the nodes by short to long names, so we iterate the highest - # in hierarcy nodes first. So the collected data can be used from the + # Iterate over the nodes by short to long names to iterate the highest + # in hierarchy nodes first. So the collected data can be used from the # cache for parent queries in next iterations. node_dependencies = dict() for node in sorted(invisible, key=len): From fb2327ad3b6a71278def4632de287b16560b7142 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Fri, 29 Apr 2022 13:25:23 +0200 Subject: [PATCH 0112/1227] concat timecode fix --- .../plugins/publish/extract_review_slate.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 13526ece66..71a1fccf53 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -213,7 +213,9 @@ class ExtractReviewSlate(openpype.api.Extractor): input_args.extend(["-r {}".format(input_frame_rate)]) input_args.extend(["-frames:v 1"]) # add timecode from source to the slate, substract one frame + offset_timecode = "00:00:00:00" if input_timecode: + offset_timecode = str(input_timecode) offset_timecode = self._tc_offset( str(input_timecode), framerate=fps, @@ -222,11 +224,7 @@ class ExtractReviewSlate(openpype.api.Extractor): self.log.debug("Slate Timecode: `{}`".format( offset_timecode )) - if offset_timecode: - input_args.extend(["-timecode {}".format(offset_timecode)]) - else: - # fall back to input timecode if offset fails - input_args.extend(["-timecode {}".format(input_timecode)]) + input_args.extend(["-timecode {}".format(offset_timecode)]) if use_legacy_code: codec_args = repre["_profile"].get('codec', []) output_args.extend(codec_args) @@ -283,11 +281,12 @@ class ExtractReviewSlate(openpype.api.Extractor): width_scale, height_scale, to_width, to_height, width_half_pad, height_half_pad ) - # add output frame rate as a filter, just in case - scaling_arg += ",fps={}".format(input_frame_rate) - vf_back = self.add_video_filter_args(output_args, scaling_arg) - # add it to output_args - output_args.insert(0, vf_back) + # add output frame rate as a filter, just in case + scaling_arg += ",fps={}".format(input_frame_rate) + + vf_back = self.add_video_filter_args(output_args, scaling_arg) + # add it to output_args + output_args.insert(0, vf_back) # overrides output file output_args.append("-y") @@ -336,7 +335,8 @@ class ExtractReviewSlate(openpype.api.Extractor): "-f", "concat", "-safe", "0", "-i", conc_text_path, - "-c:v", "copy", + "-c", "copy", + "-timecode", offset_timecode, output_path ] if not input_audio: From 5d8cea50461e9190ca1838fca55779235010333a Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Fri, 29 Apr 2022 13:27:14 +0200 Subject: [PATCH 0113/1227] hound --- openpype/plugins/publish/extract_review_slate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 71a1fccf53..46bd4f3a7b 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -336,7 +336,7 @@ class ExtractReviewSlate(openpype.api.Extractor): "-safe", "0", "-i", conc_text_path, "-c", "copy", - "-timecode", offset_timecode, + "-timecode", offset_timecode, output_path ] if not input_audio: From 49725781f4e8afc97f43f711dc3effb8d3ded7e0 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Fri, 29 Apr 2022 17:43:02 +0200 Subject: [PATCH 0114/1227] Fix concatenating metadata Slate concatenation didn't pass metadata to output --- .../plugins/publish/extract_review_slate.py | 19 +++++++++++++++++-- openpype/scripts/otio_burnin.py | 1 + 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 46bd4f3a7b..d5741273a8 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -77,7 +77,7 @@ class ExtractReviewSlate(openpype.api.Extractor): streams = get_ffprobe_streams( input_path, self.log ) - # get video metadata + # Get video metadata for stream in streams: input_timecode = None input_width = None @@ -337,8 +337,23 @@ class ExtractReviewSlate(openpype.api.Extractor): "-i", conc_text_path, "-c", "copy", "-timecode", offset_timecode, - output_path ] + # Use arguments from ffmpeg preset + source_ffmpeg_cmd = repre.get("ffmpeg_cmd") + if source_ffmpeg_cmd: + copy_args = ( + "-metadata", + "-metadata:s:v:0", + ) + args = source_ffmpeg_cmd.split(" ") + for indx, arg in enumerate(args): + if arg in copy_args: + concat_args.append(arg) + # assumes arg has one parameter + concat_args.append(args[indx + 1]) + # add output + concat_args.append(output_path) + if not input_audio: # ffmpeg concat subprocess self.log.debug( diff --git a/openpype/scripts/otio_burnin.py b/openpype/scripts/otio_burnin.py index 1f57891b84..4c3a5de2ec 100644 --- a/openpype/scripts/otio_burnin.py +++ b/openpype/scripts/otio_burnin.py @@ -568,6 +568,7 @@ def burnins_from_data( if source_ffmpeg_cmd: copy_args = ( "-metadata", + "-metadata:s:v:0", ) args = source_ffmpeg_cmd.split(" ") for idx, arg in enumerate(args): From a80d37847dad918a0b8f401a0432a9c9f5faf1b5 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Fri, 29 Apr 2022 17:46:34 +0200 Subject: [PATCH 0115/1227] hound --- openpype/plugins/publish/extract_review_slate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index d5741273a8..59150e9d4a 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -353,7 +353,7 @@ class ExtractReviewSlate(openpype.api.Extractor): concat_args.append(args[indx + 1]) # add output concat_args.append(output_path) - + if not input_audio: # ffmpeg concat subprocess self.log.debug( From 590ece0ed898b7265e5198b82f54be1e5741cd45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 2 May 2022 18:11:35 +0200 Subject: [PATCH 0116/1227] fix image prefix warning --- .../maya/plugins/publish/validate_rendersettings.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 023e27de17..dc2a9c5d1c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -92,6 +92,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): def get_invalid(cls, instance): invalid = False + multipart = False renderer = instance.data['renderer'] layer = instance.data['setMembers'] @@ -111,6 +112,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): "{aov_separator}", instance.data.get("aovSeparator", "_")) required_prefix = "maya/" + default_prefix = cls.ImagePrefixTokens[renderer] if not anim_override: invalid = True @@ -211,14 +213,20 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): cls.log.error("Wrong image prefix [ {} ] - " "You can't use '' token " "with merge AOVs turned on".format(prefix)) + default_prefix = re.sub( + cls.R_AOV_TOKEN, "", default_prefix) + # remove aov token from prefix to pass validation + # first resolve it and then remove if dangling + default_prefix = default_prefix.replace( + "{aov_separator}", instance.data.get("aovSeparator", "_")) + default_prefix = default_prefix.rstrip( + instance.data.get("aovSeparator", "_")) elif not re.search(cls.R_AOV_TOKEN, prefix): invalid = True cls.log.error("Wrong image prefix [ {} ] - " "doesn't have: '' or " "token".format(prefix)) - # prefix check - default_prefix = cls.ImagePrefixTokens[renderer] default_prefix = default_prefix.replace( "{aov_separator}", instance.data.get("aovSeparator", "_")) if prefix.lower() != default_prefix.lower(): From 25fd05df9ea82f4698cc03d113b3b149d9bf2536 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 3 May 2022 16:29:45 +0100 Subject: [PATCH 0117/1227] Updated plugin for UE5 --- .../Source/OpenPype/OpenPype.Build.cs | 2 + .../Source/OpenPype/Private/OpenPype.cpp | 110 ++++++++---------- .../OpenPype/Private/OpenPypeCommands.cpp | 13 +++ .../Source/OpenPype/Private/OpenPypeStyle.cpp | 53 ++++----- .../Source/OpenPype/Public/OpenPype.h | 8 +- .../Source/OpenPype/Public/OpenPypeCommands.h | 24 ++++ .../Source/OpenPype/Public/OpenPypeStyle.h | 12 +- 7 files changed, 116 insertions(+), 106 deletions(-) create mode 100644 openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypeCommands.cpp create mode 100644 openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypeCommands.h diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/OpenPype.Build.cs b/openpype/hosts/unreal/integration/Source/OpenPype/OpenPype.Build.cs index c30835b63d..fcfd268234 100644 --- a/openpype/hosts/unreal/integration/Source/OpenPype/OpenPype.Build.cs +++ b/openpype/hosts/unreal/integration/Source/OpenPype/OpenPype.Build.cs @@ -36,7 +36,9 @@ public class OpenPype : ModuleRules { "Projects", "InputCore", + "EditorFramework", "UnrealEd", + "ToolMenus", "LevelEditor", "CoreUObject", "Engine", diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPype.cpp b/openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPype.cpp index 15c46b3862..b3bd9a81b3 100644 --- a/openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPype.cpp +++ b/openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPype.cpp @@ -1,7 +1,10 @@ #include "OpenPype.h" -#include "LevelEditor.h" -#include "OpenPypePythonBridge.h" #include "OpenPypeStyle.h" +#include "OpenPypeCommands.h" +#include "OpenPypePythonBridge.h" +#include "LevelEditor.h" +#include "Misc/MessageDialog.h" +#include "ToolMenus.h" static const FName OpenPypeTabName("OpenPype"); @@ -11,82 +14,61 @@ static const FName OpenPypeTabName("OpenPype"); // This function is triggered when the plugin is staring up void FOpenPypeModule::StartupModule() { - FOpenPypeStyle::Initialize(); - FOpenPypeStyle::SetIcon("Logo", "openpype40"); + FOpenPypeStyle::ReloadTextures(); + FOpenPypeCommands::Register(); - // Create the Extender that will add content to the menu - FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); - - TSharedPtr MenuExtender = MakeShareable(new FExtender()); - TSharedPtr ToolbarExtender = MakeShareable(new FExtender()); + PluginCommands = MakeShareable(new FUICommandList); - MenuExtender->AddMenuExtension( - "LevelEditor", - EExtensionHook::After, - NULL, - FMenuExtensionDelegate::CreateRaw(this, &FOpenPypeModule::AddMenuEntry) - ); - ToolbarExtender->AddToolBarExtension( - "Settings", - EExtensionHook::After, - NULL, - FToolBarExtensionDelegate::CreateRaw(this, &FOpenPypeModule::AddToobarEntry)); - - - LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender); - LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); + PluginCommands->MapAction( + FOpenPypeCommands::Get().OpenPypeTools, + FExecuteAction::CreateRaw(this, &FOpenPypeModule::MenuPopup), + FCanExecuteAction()); + PluginCommands->MapAction( + FOpenPypeCommands::Get().OpenPypeToolsDialog, + FExecuteAction::CreateRaw(this, &FOpenPypeModule::MenuDialog), + FCanExecuteAction()); + UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FOpenPypeModule::RegisterMenus)); } void FOpenPypeModule::ShutdownModule() { + UToolMenus::UnRegisterStartupCallback(this); + + UToolMenus::UnregisterOwner(this); + FOpenPypeStyle::Shutdown(); + + FOpenPypeCommands::Unregister(); } - -void FOpenPypeModule::AddMenuEntry(FMenuBuilder& MenuBuilder) +void FOpenPypeModule::RegisterMenus() { - // Create Section - MenuBuilder.BeginSection("OpenPype", TAttribute(FText::FromString("OpenPype"))); + // Owner will be used for cleanup in call to UToolMenus::UnregisterOwner + FToolMenuOwnerScoped OwnerScoped(this); + { - // Create a Submenu inside of the Section - MenuBuilder.AddMenuEntry( - FText::FromString("Tools..."), - FText::FromString("Pipeline tools"), - FSlateIcon(FOpenPypeStyle::GetStyleSetName(), "OpenPype.Logo"), - FUIAction(FExecuteAction::CreateRaw(this, &FOpenPypeModule::MenuPopup)) - ); - - MenuBuilder.AddMenuEntry( - FText::FromString("Tools dialog..."), - FText::FromString("Pipeline tools dialog"), - FSlateIcon(FOpenPypeStyle::GetStyleSetName(), "OpenPype.Logo"), - FUIAction(FExecuteAction::CreateRaw(this, &FOpenPypeModule::MenuDialog)) - ); - + UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Tools"); + { + // FToolMenuSection& Section = Menu->FindOrAddSection("OpenPype"); + FToolMenuSection& Section = Menu->AddSection( + "OpenPype", + TAttribute(FText::FromString("OpenPype")), + FToolMenuInsert("Programming", EToolMenuInsertType::Before) + ); + Section.AddMenuEntryWithCommandList(FOpenPypeCommands::Get().OpenPypeTools, PluginCommands); + Section.AddMenuEntryWithCommandList(FOpenPypeCommands::Get().OpenPypeToolsDialog, PluginCommands); + } + UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.PlayToolBar"); + { + FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("PluginTools"); + { + FToolMenuEntry& Entry = Section.AddEntry(FToolMenuEntry::InitToolBarButton(FOpenPypeCommands::Get().OpenPypeTools)); + Entry.SetCommandList(PluginCommands); + } + } } - MenuBuilder.EndSection(); -} - -void FOpenPypeModule::AddToobarEntry(FToolBarBuilder& ToolbarBuilder) -{ - ToolbarBuilder.BeginSection(TEXT("OpenPype")); - { - ToolbarBuilder.AddToolBarButton( - FUIAction( - FExecuteAction::CreateRaw(this, &FOpenPypeModule::MenuPopup), - NULL, - FIsActionChecked() - - ), - NAME_None, - LOCTEXT("OpenPype_label", "OpenPype"), - LOCTEXT("OpenPype_tooltip", "OpenPype Tools"), - FSlateIcon(FOpenPypeStyle::GetStyleSetName(), "OpenPype.Logo") - ); - } - ToolbarBuilder.EndSection(); } diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypeCommands.cpp b/openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypeCommands.cpp new file mode 100644 index 0000000000..6187bd7c7e --- /dev/null +++ b/openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypeCommands.cpp @@ -0,0 +1,13 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OpenPypeCommands.h" + +#define LOCTEXT_NAMESPACE "FOpenPypeModule" + +void FOpenPypeCommands::RegisterCommands() +{ + UI_COMMAND(OpenPypeTools, "OpenPype Tools", "Pipeline tools", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(OpenPypeToolsDialog, "OpenPype Tools Dialog", "Pipeline tools dialog", EUserInterfaceActionType::Button, FInputChord()); +} + +#undef LOCTEXT_NAMESPACE diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypeStyle.cpp b/openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypeStyle.cpp index a51c2d6aa5..4a53af26b5 100644 --- a/openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypeStyle.cpp +++ b/openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypeStyle.cpp @@ -1,10 +1,14 @@ +#include "OpenPype.h" #include "OpenPypeStyle.h" #include "Framework/Application/SlateApplication.h" -#include "Styling/SlateStyle.h" #include "Styling/SlateStyleRegistry.h" +#include "Slate/SlateGameResources.h" +#include "Interfaces/IPluginManager.h" +#include "Styling/SlateStyleMacros.h" +#define RootToContentDir Style->RootToContentDir -TUniquePtr< FSlateStyleSet > FOpenPypeStyle::OpenPypeStyleInstance = nullptr; +TSharedPtr FOpenPypeStyle::OpenPypeStyleInstance = nullptr; void FOpenPypeStyle::Initialize() { @@ -17,11 +21,9 @@ void FOpenPypeStyle::Initialize() void FOpenPypeStyle::Shutdown() { - if (OpenPypeStyleInstance.IsValid()) - { - FSlateStyleRegistry::UnRegisterSlateStyle(*OpenPypeStyleInstance); - OpenPypeStyleInstance.Reset(); - } + FSlateStyleRegistry::UnRegisterSlateStyle(*OpenPypeStyleInstance); + ensure(OpenPypeStyleInstance.IsUnique()); + OpenPypeStyleInstance.Reset(); } FName FOpenPypeStyle::GetStyleSetName() @@ -30,41 +32,30 @@ FName FOpenPypeStyle::GetStyleSetName() return StyleSetName; } -FName FOpenPypeStyle::GetContextName() -{ - static FName ContextName(TEXT("OpenPype")); - return ContextName; -} - -#define IMAGE_BRUSH(RelativePath, ...) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) - +const FVector2D Icon16x16(16.0f, 16.0f); +const FVector2D Icon20x20(20.0f, 20.0f); const FVector2D Icon40x40(40.0f, 40.0f); -TUniquePtr< FSlateStyleSet > FOpenPypeStyle::Create() +TSharedRef< FSlateStyleSet > FOpenPypeStyle::Create() { - TUniquePtr< FSlateStyleSet > Style = MakeUnique(GetStyleSetName()); - Style->SetContentRoot(FPaths::ProjectPluginsDir() / TEXT("OpenPype/Resources")); + TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("OpenPypeStyle")); + Style->SetContentRoot(IPluginManager::Get().FindPlugin("OpenPype")->GetBaseDir() / TEXT("Resources")); + + Style->Set("OpenPype.OpenPypeTools", new IMAGE_BRUSH(TEXT("openpype40"), Icon40x40)); + Style->Set("OpenPype.OpenPypeToolsDialog", new IMAGE_BRUSH(TEXT("openpype40"), Icon40x40)); return Style; } -void FOpenPypeStyle::SetIcon(const FString& StyleName, const FString& ResourcePath) +void FOpenPypeStyle::ReloadTextures() { - FSlateStyleSet* Style = OpenPypeStyleInstance.Get(); - - FString Name(GetContextName().ToString()); - Name = Name + "." + StyleName; - Style->Set(*Name, new FSlateImageBrush(Style->RootToContentDir(ResourcePath, TEXT(".png")), Icon40x40)); - - - FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); + if (FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); + } } -#undef IMAGE_BRUSH - const ISlateStyle& FOpenPypeStyle::Get() { - check(OpenPypeStyleInstance); - return *OpenPypeStyleInstance; return *OpenPypeStyleInstance; } diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPype.h b/openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPype.h index db3f299354..3ee5eaa65f 100644 --- a/openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPype.h +++ b/openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPype.h @@ -2,7 +2,8 @@ #pragma once -#include "Engine.h" +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" class FOpenPypeModule : public IModuleInterface @@ -12,10 +13,11 @@ public: virtual void ShutdownModule() override; private: + void RegisterMenus(); - void AddMenuEntry(FMenuBuilder& MenuBuilder); - void AddToobarEntry(FToolBarBuilder& ToolbarBuilder); void MenuPopup(); void MenuDialog(); +private: + TSharedPtr PluginCommands; }; diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypeCommands.h b/openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypeCommands.h new file mode 100644 index 0000000000..62ffb8de33 --- /dev/null +++ b/openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypeCommands.h @@ -0,0 +1,24 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Commands/Commands.h" +#include "OpenPypeStyle.h" + +class FOpenPypeCommands : public TCommands +{ +public: + + FOpenPypeCommands() + : TCommands(TEXT("OpenPype"), NSLOCTEXT("Contexts", "OpenPype", "OpenPype Tools"), NAME_None, FOpenPypeStyle::GetStyleSetName()) + { + } + + // TCommands<> interface + virtual void RegisterCommands() override; + +public: + TSharedPtr< FUICommandInfo > OpenPypeTools; + TSharedPtr< FUICommandInfo > OpenPypeToolsDialog; +}; diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypeStyle.h b/openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypeStyle.h index fbc8bcdd5b..ae704251e1 100644 --- a/openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypeStyle.h +++ b/openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypeStyle.h @@ -1,22 +1,18 @@ #pragma once #include "CoreMinimal.h" - -class FSlateStyleSet; -class ISlateStyle; - +#include "Styling/SlateStyle.h" class FOpenPypeStyle { public: static void Initialize(); static void Shutdown(); + static void ReloadTextures(); static const ISlateStyle& Get(); static FName GetStyleSetName(); - static FName GetContextName(); - static void SetIcon(const FString& StyleName, const FString& ResourcePath); private: - static TUniquePtr< FSlateStyleSet > Create(); - static TUniquePtr< FSlateStyleSet > OpenPypeStyleInstance; + static TSharedRef< class FSlateStyleSet > Create(); + static TSharedPtr< class FSlateStyleSet > OpenPypeStyleInstance; }; \ No newline at end of file From e2076c0f2fa264f4dc7ae6959c3292b60616ca91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 3 Feb 2022 19:03:16 +0100 Subject: [PATCH 0118/1227] Module: Kitsu module --- .../modules/default_modules/kitsu/__init__.py | 15 ++ .../default_modules/kitsu/kitsu_module.py | 147 ++++++++++++++++++ .../kitsu/plugins/publish/example_plugin.py | 9 ++ .../schemas/project_schemas/main.json | 30 ++++ .../schemas/project_schemas/the_template.json | 30 ++++ .../modules/default_modules/kitsu/widgets.py | 31 ++++ .../defaults/project_settings/kitsu.json | 3 + .../defaults/system_settings/modules.json | 4 + .../schemas/projects_schema/schema_main.json | 4 + .../projects_schema/schema_project_kitsu.json | 17 ++ .../module_settings/schema_kitsu.json | 23 +++ .../schemas/system_schema/schema_modules.json | 4 + 12 files changed, 317 insertions(+) create mode 100644 openpype/modules/default_modules/kitsu/__init__.py create mode 100644 openpype/modules/default_modules/kitsu/kitsu_module.py create mode 100644 openpype/modules/default_modules/kitsu/plugins/publish/example_plugin.py create mode 100644 openpype/modules/default_modules/kitsu/settings/schemas/project_schemas/main.json create mode 100644 openpype/modules/default_modules/kitsu/settings/schemas/project_schemas/the_template.json create mode 100644 openpype/modules/default_modules/kitsu/widgets.py create mode 100644 openpype/settings/defaults/project_settings/kitsu.json create mode 100644 openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json create mode 100644 openpype/settings/entities/schemas/system_schema/module_settings/schema_kitsu.json diff --git a/openpype/modules/default_modules/kitsu/__init__.py b/openpype/modules/default_modules/kitsu/__init__.py new file mode 100644 index 0000000000..cd0c2ea8af --- /dev/null +++ b/openpype/modules/default_modules/kitsu/__init__.py @@ -0,0 +1,15 @@ +""" Addon class definition and Settings definition must be imported here. + +If addon class or settings definition won't be here their definition won't +be found by OpenPype discovery. +""" + +from .kitsu_module import ( + AddonSettingsDef, + KitsuModule +) + +__all__ = ( + "AddonSettingsDef", + "KitsuModule" +) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py new file mode 100644 index 0000000000..81d7e56a81 --- /dev/null +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -0,0 +1,147 @@ +"""Addon definition is located here. + +Import of python packages that may not be available should not be imported +in global space here until are required or used. +- Qt related imports +- imports of Python 3 packages + - we still support Python 2 hosts where addon definition should available +""" + +import os +import click + +from openpype.modules import ( + JsonFilesSettingsDef, + OpenPypeModule, + ModulesManager +) +# Import interface defined by this addon to be able find other addons using it +from openpype_interfaces import ( + IPluginPaths, + ITrayAction +) + + +# Settings definition of this addon using `JsonFilesSettingsDef` +# - JsonFilesSettingsDef is prepared settings definition using json files +# to define settings and store default values +class AddonSettingsDef(JsonFilesSettingsDef): + # This will add prefixes to every schema and template from `schemas` + # subfolder. + # - it is not required to fill the prefix but it is highly + # recommended as schemas and templates may have name clashes across + # multiple addons + # - it is also recommended that prefix has addon name in it + schema_prefix = "kitsu" + + def get_settings_root_path(self): + """Implemented abstract class of JsonFilesSettingsDef. + + Return directory path where json files defying addon settings are + located. + """ + return os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "settings" + ) + + +class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): + """This Addon has defined it's settings and interface. + + This example has system settings with an enabled option. And use + few other interfaces: + - `IPluginPaths` to define custom plugin paths + - `ITrayAction` to be shown in tray tool + """ + label = "Kitsu" + name = "kitsu" + + def initialize(self, settings): + """Initialization of addon.""" + module_settings = settings[self.name] + # Enabled by settings + self.enabled = module_settings.get("enabled", False) + + # Prepare variables that can be used or set afterwards + self._connected_modules = None + # UI which must not be created at this time + self._dialog = None + + def tray_init(self): + """Implementation of abstract method for `ITrayAction`. + + We're definitely in tray tool so we can pre create dialog. + """ + + self._create_dialog() + + def _create_dialog(self): + # Don't recreate dialog if already exists + if self._dialog is not None: + return + + from .widgets import MyExampleDialog + + self._dialog = MyExampleDialog() + + def show_dialog(self): + """Show dialog with connected modules. + + This can be called from anywhere but can also crash in headless mode. + There is no way to prevent addon to do invalid operations if he's + not handling them. + """ + # Make sure dialog is created + self._create_dialog() + # Show dialog + self._dialog.open() + + def get_connected_modules(self): + """Custom implementation of addon.""" + names = set() + if self._connected_modules is not None: + for module in self._connected_modules: + names.add(module.name) + return names + + def on_action_trigger(self): + """Implementation of abstract method for `ITrayAction`.""" + self.show_dialog() + + def get_plugin_paths(self): + """Implementation of abstract method for `IPluginPaths`.""" + current_dir = os.path.dirname(os.path.abspath(__file__)) + + return { + "publish": [os.path.join(current_dir, "plugins", "publish")] + } + + def cli(self, click_group): + click_group.add_command(cli_main) + + +@click.group(KitsuModule.name, help="Kitsu dynamic cli commands.") +def cli_main(): + pass + + +@cli_main.command() +def nothing(): + """Does nothing but print a message.""" + print("You've triggered \"nothing\" command.") + + +@cli_main.command() +def show_dialog(): + """Show ExampleAddon dialog. + + We don't have access to addon directly through cli so we have to create + it again. + """ + from openpype.tools.utils.lib import qt_app_context + + manager = ModulesManager() + example_addon = manager.modules_by_name[KitsuModule.name] + with qt_app_context(): + example_addon.show_dialog() diff --git a/openpype/modules/default_modules/kitsu/plugins/publish/example_plugin.py b/openpype/modules/default_modules/kitsu/plugins/publish/example_plugin.py new file mode 100644 index 0000000000..61602f4e78 --- /dev/null +++ b/openpype/modules/default_modules/kitsu/plugins/publish/example_plugin.py @@ -0,0 +1,9 @@ +import pyblish.api + + +class CollectExampleAddon(pyblish.api.ContextPlugin): + order = pyblish.api.CollectorOrder + 0.4 + label = "Collect Kitsu" + + def process(self, context): + self.log.info("I'm in Kitsu's plugin!") diff --git a/openpype/modules/default_modules/kitsu/settings/schemas/project_schemas/main.json b/openpype/modules/default_modules/kitsu/settings/schemas/project_schemas/main.json new file mode 100644 index 0000000000..82e58ce9ab --- /dev/null +++ b/openpype/modules/default_modules/kitsu/settings/schemas/project_schemas/main.json @@ -0,0 +1,30 @@ +{ + "type": "dict", + "key": "kitsu", + "label": " Kitsu", + "collapsible": true, + "children": [ + { + "type": "number", + "key": "number", + "label": "This is your lucky number:", + "minimum": 7, + "maximum": 7, + "decimals": 0 + }, + { + "type": "template", + "name": "kitsu/the_template", + "template_data": [ + { + "name": "color_1", + "label": "Color 1" + }, + { + "name": "color_2", + "label": "Color 2" + } + ] + } + ] +} diff --git a/openpype/modules/default_modules/kitsu/settings/schemas/project_schemas/the_template.json b/openpype/modules/default_modules/kitsu/settings/schemas/project_schemas/the_template.json new file mode 100644 index 0000000000..af8fd9dae4 --- /dev/null +++ b/openpype/modules/default_modules/kitsu/settings/schemas/project_schemas/the_template.json @@ -0,0 +1,30 @@ +[ + { + "type": "list-strict", + "key": "{name}", + "label": "{label}", + "object_types": [ + { + "label": "Red", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, + { + "label": "Green", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, + { + "label": "Blue", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + } + ] + } +] diff --git a/openpype/modules/default_modules/kitsu/widgets.py b/openpype/modules/default_modules/kitsu/widgets.py new file mode 100644 index 0000000000..de232113fe --- /dev/null +++ b/openpype/modules/default_modules/kitsu/widgets.py @@ -0,0 +1,31 @@ +from Qt import QtWidgets + +from openpype.style import load_stylesheet + + +class MyExampleDialog(QtWidgets.QDialog): + def __init__(self, parent=None): + super(MyExampleDialog, self).__init__(parent) + + self.setWindowTitle("Connected modules") + + msg = "This is example dialog of Kitsu." + label_widget = QtWidgets.QLabel(msg, self) + + ok_btn = QtWidgets.QPushButton("OK", self) + btns_layout = QtWidgets.QHBoxLayout() + btns_layout.addStretch(1) + btns_layout.addWidget(ok_btn) + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(label_widget) + layout.addLayout(btns_layout) + + ok_btn.clicked.connect(self._on_ok_clicked) + + self._label_widget = label_widget + + self.setStyleSheet(load_stylesheet()) + + def _on_ok_clicked(self): + self.done(1) diff --git a/openpype/settings/defaults/project_settings/kitsu.json b/openpype/settings/defaults/project_settings/kitsu.json new file mode 100644 index 0000000000..b4d2ccc611 --- /dev/null +++ b/openpype/settings/defaults/project_settings/kitsu.json @@ -0,0 +1,3 @@ +{ + "number": 0 +} \ No newline at end of file diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index d74269922f..9cfaddecbe 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -137,6 +137,10 @@ } } }, + "kitsu": { + "enabled": false, + "kitsu_server": "" + }, "timers_manager": { "enabled": true, "auto_stop": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json index dbddd18c80..6c07209de3 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_main.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json @@ -62,6 +62,10 @@ "type": "schema", "name": "schema_project_ftrack" }, + { + "type": "schema", + "name": "schema_project_kitsu" + }, { "type": "schema", "name": "schema_project_deadline" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json b/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json new file mode 100644 index 0000000000..93976cc03b --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json @@ -0,0 +1,17 @@ +{ + "type": "dict", + "key": "kitsu", + "label": "Kitsu", + "collapsible": true, + "is_file": true, + "children": [ + { + "type": "number", + "key": "number", + "label": "This is your lucky number:", + "minimum": 7, + "maximum": 7, + "decimals": 0 + } + ] +} diff --git a/openpype/settings/entities/schemas/system_schema/module_settings/schema_kitsu.json b/openpype/settings/entities/schemas/system_schema/module_settings/schema_kitsu.json new file mode 100644 index 0000000000..8e496dc783 --- /dev/null +++ b/openpype/settings/entities/schemas/system_schema/module_settings/schema_kitsu.json @@ -0,0 +1,23 @@ +{ + "type": "dict", + "key": "kitsu", + "label": "Kitsu", + "collapsible": true, + "require_restart": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "kitsu_server", + "label": "Server" + }, + { + "type": "splitter" + } + ] +} diff --git a/openpype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json index 52595914ed..d22b9016a7 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_modules.json +++ b/openpype/settings/entities/schemas/system_schema/schema_modules.json @@ -44,6 +44,10 @@ "type": "schema", "name": "schema_ftrack" }, + { + "type": "schema", + "name": "schema_kitsu" + }, { "type": "dict", "key": "timers_manager", From 70440290cafcf02753f8c499951cc164b011c132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Feb 2022 15:38:02 +0100 Subject: [PATCH 0119/1227] Fist step to sync from Zou to local --- .../default_modules/kitsu/kitsu_module.py | 125 +++++++++++++++++- .../defaults/system_settings/modules.json | 4 +- .../module_settings/schema_kitsu.json | 12 +- 3 files changed, 135 insertions(+), 6 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index 81d7e56a81..55e4640fa2 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -6,20 +6,27 @@ in global space here until are required or used. - imports of Python 3 packages - we still support Python 2 hosts where addon definition should available """ - +import collections import os import click +from avalon.api import AvalonMongoDB +import gazu +from openpype.api import get_project_basic_paths, create_project_folders +from openpype.lib import create_project +from openpype.lib.anatomy import Anatomy from openpype.modules import ( JsonFilesSettingsDef, OpenPypeModule, ModulesManager ) +from openpype.tools.project_manager.project_manager.model import AssetItem, ProjectItem, TaskItem # Import interface defined by this addon to be able find other addons using it from openpype_interfaces import ( IPluginPaths, ITrayAction ) +from pymongo import UpdateOne, DeleteOne # Settings definition of this addon using `JsonFilesSettingsDef` @@ -60,9 +67,27 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): def initialize(self, settings): """Initialization of addon.""" module_settings = settings[self.name] + # Enabled by settings self.enabled = module_settings.get("enabled", False) + # Add API URL schema + kitsu_url = module_settings["server"].strip() + if kitsu_url: + # Ensure web url + if not kitsu_url.startswith("http"): + kitsu_url = "https://" + kitsu_url + + # Check for "/api" url validity + if not kitsu_url.endswith("api"): + kitsu_url = f"{kitsu_url}{'' if kitsu_url.endswith('/') else '/'}api" + + self.server_url = kitsu_url + + # Set credentials + self.script_login = module_settings["script_login"] + self.script_pwd = module_settings["script_pwd"] + # Prepare variables that can be used or set afterwards self._connected_modules = None # UI which must not be created at this time @@ -76,6 +101,14 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): self._create_dialog() + def get_global_environments(self): + """Kitsu's global environments.""" + return { + "KITSU_SERVER": self.server_url, + "KITSU_LOGIN": self.script_login, + "KITSU_PWD": self.script_pwd + } + def _create_dialog(self): # Don't recreate dialog if already exists if self._dialog is not None: @@ -127,10 +160,94 @@ def cli_main(): @cli_main.command() -def nothing(): - """Does nothing but print a message.""" - print("You've triggered \"nothing\" command.") +def sync_local(): + """Synchronize local database from Zou sever database.""" + # Connect to server + gazu.client.set_host(os.environ["KITSU_SERVER"]) + + # Authenticate + gazu.log_in(os.environ["KITSU_LOGIN"], os.environ["KITSU_PWD"]) + + # Iterate projects + dbcon = AvalonMongoDB() + dbcon.install() + all_projects = gazu.project.all_projects() + for project in all_projects: + # Create project locally + # Try to find project document + project_name = project["name"] + project_code = project_name + dbcon.Session["AVALON_PROJECT"] = project_name + project_doc = dbcon.find_one({ + "type": "project" + }) + + # Get all assets from zou + all_assets = gazu.asset.all_assets_for_project(project) + + # Query all assets of the local project + project_col = dbcon.database[project_code] + asset_docs_zou_ids = { + asset_doc["data"]["zou_id"] + for asset_doc in project_col.find({"type": "asset"}) + } + + # Create project if is not available + # - creation is required to be able set project anatomy and attributes + if not project_doc: + print(f"Creating project '{project_name}'") + project_doc = create_project(project_name, project_code, dbcon=dbcon) + + # Create project item + insert_list = [] + + bulk_writes = [] + for zou_asset in all_assets: + doc_data = {"zou_id": zou_asset["id"]} + + # Create Asset + new_doc = { + "name": zou_asset["name"], + "type": "asset", + "schema": "openpype:asset-3.0", + "data": doc_data, + "parent": project_doc["_id"] + } + + if zou_asset["id"] not in asset_docs_zou_ids: # Item is new + insert_list.append(new_doc) + + # TODO tasks + + # elif item.data(REMOVED_ROLE): # TODO removal + # if item.data(HIERARCHY_CHANGE_ABLE_ROLE): + # bulk_writes.append(DeleteOne( + # {"_id": item.asset_id} + # )) + # else: + # bulk_writes.append(UpdateOne( + # {"_id": item.asset_id}, + # {"$set": {"type": "archived_asset"}} + # )) + + # else: TODO update data + # update_data = new_item.update_data() + # if update_data: + # bulk_writes.append(UpdateOne( + # {"_id": new_item.asset_id}, + # update_data + # )) + + # Insert new docs if created + if insert_list: + project_col.insert_many(insert_list) + + # Write into DB TODO + # if bulk_writes: + # project_col.bulk_write(bulk_writes) + + dbcon.uninstall() @cli_main.command() def show_dialog(): diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 9cfaddecbe..ddb2edc360 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -139,7 +139,9 @@ }, "kitsu": { "enabled": false, - "kitsu_server": "" + "server": "", + "script_login": "", + "script_pwd": "" }, "timers_manager": { "enabled": true, diff --git a/openpype/settings/entities/schemas/system_schema/module_settings/schema_kitsu.json b/openpype/settings/entities/schemas/system_schema/module_settings/schema_kitsu.json index 8e496dc783..ae2b52df0d 100644 --- a/openpype/settings/entities/schemas/system_schema/module_settings/schema_kitsu.json +++ b/openpype/settings/entities/schemas/system_schema/module_settings/schema_kitsu.json @@ -13,9 +13,19 @@ }, { "type": "text", - "key": "kitsu_server", + "key": "server", "label": "Server" }, + { + "type": "text", + "key": "script_login", + "label": "Script Login" + }, + { + "type": "text", + "key": "script_pwd", + "label": "Script Password" + }, { "type": "splitter" } From 719afdcc8ae5f8710537d55da24040bd7ccfcd95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Feb 2022 15:41:02 +0100 Subject: [PATCH 0120/1227] black --- .../default_modules/kitsu/kitsu_module.py | 37 ++++++------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index 55e4640fa2..6eb37dfaed 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -6,27 +6,17 @@ in global space here until are required or used. - imports of Python 3 packages - we still support Python 2 hosts where addon definition should available """ -import collections import os import click from avalon.api import AvalonMongoDB import gazu -from openpype.api import get_project_basic_paths, create_project_folders from openpype.lib import create_project from openpype.lib.anatomy import Anatomy -from openpype.modules import ( - JsonFilesSettingsDef, - OpenPypeModule, - ModulesManager -) -from openpype.tools.project_manager.project_manager.model import AssetItem, ProjectItem, TaskItem +from openpype.modules import JsonFilesSettingsDef, OpenPypeModule, ModulesManager + # Import interface defined by this addon to be able find other addons using it -from openpype_interfaces import ( - IPluginPaths, - ITrayAction -) -from pymongo import UpdateOne, DeleteOne +from openpype_interfaces import IPluginPaths, ITrayAction # Settings definition of this addon using `JsonFilesSettingsDef` @@ -47,10 +37,7 @@ class AddonSettingsDef(JsonFilesSettingsDef): Return directory path where json files defying addon settings are located. """ - return os.path.join( - os.path.dirname(os.path.abspath(__file__)), - "settings" - ) + return os.path.join(os.path.dirname(os.path.abspath(__file__)), "settings") class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): @@ -61,6 +48,7 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): - `IPluginPaths` to define custom plugin paths - `ITrayAction` to be shown in tray tool """ + label = "Kitsu" name = "kitsu" @@ -106,7 +94,7 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): return { "KITSU_SERVER": self.server_url, "KITSU_LOGIN": self.script_login, - "KITSU_PWD": self.script_pwd + "KITSU_PWD": self.script_pwd, } def _create_dialog(self): @@ -146,9 +134,7 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): """Implementation of abstract method for `IPluginPaths`.""" current_dir = os.path.dirname(os.path.abspath(__file__)) - return { - "publish": [os.path.join(current_dir, "plugins", "publish")] - } + return {"publish": [os.path.join(current_dir, "plugins", "publish")]} def cli(self, click_group): click_group.add_command(cli_main) @@ -179,10 +165,8 @@ def sync_local(): project_name = project["name"] project_code = project_name dbcon.Session["AVALON_PROJECT"] = project_name - project_doc = dbcon.find_one({ - "type": "project" - }) - + project_doc = dbcon.find_one({"type": "project"}) + # Get all assets from zou all_assets = gazu.asset.all_assets_for_project(project) @@ -212,7 +196,7 @@ def sync_local(): "type": "asset", "schema": "openpype:asset-3.0", "data": doc_data, - "parent": project_doc["_id"] + "parent": project_doc["_id"], } if zou_asset["id"] not in asset_docs_zou_ids: # Item is new @@ -249,6 +233,7 @@ def sync_local(): dbcon.uninstall() + @cli_main.command() def show_dialog(): """Show ExampleAddon dialog. From 7c63d3a374637ee8630d293b8ac8dcc8a34ffb95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Feb 2022 16:28:06 +0100 Subject: [PATCH 0121/1227] Add tasks to asset --- .../default_modules/kitsu/kitsu_module.py | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index 6eb37dfaed..9730437ec2 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -187,22 +187,43 @@ def sync_local(): insert_list = [] bulk_writes = [] - for zou_asset in all_assets: - doc_data = {"zou_id": zou_asset["id"]} + for asset in all_assets: + asset_data = {"zou_id": asset["id"]} + + # Set tasks + asset_tasks = gazu.task.all_tasks_for_asset(asset) + asset_data["tasks"] = { + t["task_type_name"]: {"type": t["task_type_name"]} for t in asset_tasks + } # Create Asset - new_doc = { - "name": zou_asset["name"], + asset_doc = { + "name": asset["name"], "type": "asset", "schema": "openpype:asset-3.0", - "data": doc_data, + "data": asset_data, "parent": project_doc["_id"], } - if zou_asset["id"] not in asset_docs_zou_ids: # Item is new - insert_list.append(new_doc) + if asset["id"] not in asset_docs_zou_ids: # Item is new + insert_list.append(asset_doc) + else: + asset_doc = project_col.find_one({"data": {"zou_id": asset["id"]}}) - # TODO tasks + # TODO update + # for task in asset_tasks: + # # print(task) + # task_data = {"zou_id": task["id"]} + + # # Create Task + # task_doc = { + # "name": task["name"], + # "type": "task", + # "schema": "openpype:asset-3.0", + # "data": task_data, + # "parent": asset_doc["_id"], + # } + # insert_list.append(task_doc) # elif item.data(REMOVED_ROLE): # TODO removal # if item.data(HIERARCHY_CHANGE_ABLE_ROLE): From 9a1dd4fc0630f94cff1ef554fd31ede714b8ad49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Feb 2022 19:26:26 +0100 Subject: [PATCH 0122/1227] All bulk write. Updating assets --- .../default_modules/kitsu/kitsu_module.py | 68 ++++++++----------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index 9730437ec2..1a71a05a7e 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -12,10 +12,8 @@ import click from avalon.api import AvalonMongoDB import gazu from openpype.lib import create_project -from openpype.lib.anatomy import Anatomy from openpype.modules import JsonFilesSettingsDef, OpenPypeModule, ModulesManager - -# Import interface defined by this addon to be able find other addons using it +from pymongo import DeleteOne, InsertOne, UpdateOne from openpype_interfaces import IPluginPaths, ITrayAction @@ -183,9 +181,6 @@ def sync_local(): print(f"Creating project '{project_name}'") project_doc = create_project(project_name, project_code, dbcon=dbcon) - # Create project item - insert_list = [] - bulk_writes = [] for asset in all_assets: asset_data = {"zou_id": asset["id"]} @@ -196,34 +191,33 @@ def sync_local(): t["task_type_name"]: {"type": t["task_type_name"]} for t in asset_tasks } - # Create Asset - asset_doc = { - "name": asset["name"], - "type": "asset", - "schema": "openpype:asset-3.0", - "data": asset_data, - "parent": project_doc["_id"], - } + # Update or create asset + if asset["id"] in asset_docs_zou_ids: # Update asset + asset_doc = project_col.find_one({"data.zou_id": asset["id"]}) - if asset["id"] not in asset_docs_zou_ids: # Item is new - insert_list.append(asset_doc) - else: - asset_doc = project_col.find_one({"data": {"zou_id": asset["id"]}}) + # Override all 'data' TODO filter data to update? + diff_data = { + k: asset_data[k] + for k in asset_data.keys() + if asset_doc["data"].get(k) != asset_data[k] + } + if diff_data: + bulk_writes.append( + UpdateOne( + {"_id": asset_doc["_id"]}, {"$set": {"data": asset_data}} + ) + ) + else: # Create + asset_doc = { + "name": asset["name"], + "type": "asset", + "schema": "openpype:asset-3.0", + "data": asset_data, + "parent": project_doc["_id"], + } - # TODO update - # for task in asset_tasks: - # # print(task) - # task_data = {"zou_id": task["id"]} - - # # Create Task - # task_doc = { - # "name": task["name"], - # "type": "task", - # "schema": "openpype:asset-3.0", - # "data": task_data, - # "parent": asset_doc["_id"], - # } - # insert_list.append(task_doc) + # Insert new doc + bulk_writes.append(InsertOne(asset_doc)) # elif item.data(REMOVED_ROLE): # TODO removal # if item.data(HIERARCHY_CHANGE_ABLE_ROLE): @@ -244,13 +238,9 @@ def sync_local(): # update_data # )) - # Insert new docs if created - if insert_list: - project_col.insert_many(insert_list) - - # Write into DB TODO - # if bulk_writes: - # project_col.bulk_write(bulk_writes) + # Write into DB + if bulk_writes: + project_col.bulk_write(bulk_writes) dbcon.uninstall() From 206bf9f3c18873a6f81734841779eeab4f8829d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 9 Feb 2022 09:45:11 +0100 Subject: [PATCH 0123/1227] Delete assets --- .../default_modules/kitsu/kitsu_module.py | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index 1a71a05a7e..1ce1bef6a2 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -170,10 +170,13 @@ def sync_local(): # Query all assets of the local project project_col = dbcon.database[project_code] - asset_docs_zou_ids = { - asset_doc["data"]["zou_id"] + asset_doc_ids = { + asset_doc["_id"]: asset_doc for asset_doc in project_col.find({"type": "asset"}) } + asset_docs_zou_ids = { + asset_doc["data"]["zou_id"] for asset_doc in asset_doc_ids.values() + } # Create project if is not available # - creation is required to be able set project anatomy and attributes @@ -182,6 +185,7 @@ def sync_local(): project_doc = create_project(project_name, project_code, dbcon=dbcon) bulk_writes = [] + sync_assets = set() for asset in all_assets: asset_data = {"zou_id": asset["id"]} @@ -195,13 +199,13 @@ def sync_local(): if asset["id"] in asset_docs_zou_ids: # Update asset asset_doc = project_col.find_one({"data.zou_id": asset["id"]}) - # Override all 'data' TODO filter data to update? - diff_data = { + # Override all 'data' + updated_data = { k: asset_data[k] for k in asset_data.keys() if asset_doc["data"].get(k) != asset_data[k] } - if diff_data: + if updated_data: bulk_writes.append( UpdateOne( {"_id": asset_doc["_id"]}, {"$set": {"data": asset_data}} @@ -219,24 +223,16 @@ def sync_local(): # Insert new doc bulk_writes.append(InsertOne(asset_doc)) - # elif item.data(REMOVED_ROLE): # TODO removal - # if item.data(HIERARCHY_CHANGE_ABLE_ROLE): - # bulk_writes.append(DeleteOne( - # {"_id": item.asset_id} - # )) - # else: - # bulk_writes.append(UpdateOne( - # {"_id": item.asset_id}, - # {"$set": {"type": "archived_asset"}} - # )) + # Keep synchronized asset for diff + sync_assets.add(asset_doc["_id"]) - # else: TODO update data - # update_data = new_item.update_data() - # if update_data: - # bulk_writes.append(UpdateOne( - # {"_id": new_item.asset_id}, - # update_data - # )) + # Delete from diff of assets in OP and synchronized assets to detect deleted assets + diff_assets = set(asset_doc_ids.keys()) - sync_assets + if diff_assets: + # Delete doc + bulk_writes.extend( + [DeleteOne(asset_doc_ids[asset_id]) for asset_id in diff_assets] + ) # Write into DB if bulk_writes: From e882de52f04d2ca79e30fb23801257cefd085e56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 10 Feb 2022 17:35:03 +0100 Subject: [PATCH 0124/1227] Assets hierarchy --- .../default_modules/kitsu/kitsu_module.py | 165 ++++++++++++------ 1 file changed, 114 insertions(+), 51 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index 1ce1bef6a2..92d724be67 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -7,13 +7,15 @@ in global space here until are required or used. - we still support Python 2 hosts where addon definition should available """ import os +from typing import Dict, List, Set import click from avalon.api import AvalonMongoDB import gazu from openpype.lib import create_project from openpype.modules import JsonFilesSettingsDef, OpenPypeModule, ModulesManager -from pymongo import DeleteOne, InsertOne, UpdateOne +from pymongo import DeleteOne, UpdateOne +from pymongo.collection import Collection from openpype_interfaces import IPluginPaths, ITrayAction @@ -144,8 +146,8 @@ def cli_main(): @cli_main.command() -def sync_local(): - """Synchronize local database from Zou sever database.""" +def sync_openpype(): + """Synchronize openpype database from Zou sever database.""" # Connect to server gazu.client.set_host(os.environ["KITSU_SERVER"]) @@ -167,69 +169,61 @@ def sync_local(): # Get all assets from zou all_assets = gazu.asset.all_assets_for_project(project) - - # Query all assets of the local project - project_col = dbcon.database[project_code] - asset_doc_ids = { - asset_doc["_id"]: asset_doc - for asset_doc in project_col.find({"type": "asset"}) - } - asset_docs_zou_ids = { - asset_doc["data"]["zou_id"] for asset_doc in asset_doc_ids.values() - } + all_episodes = gazu.shot.all_episodes_for_project(project) + all_seqs = gazu.shot.all_sequences_for_project(project) + all_shots = gazu.shot.all_shots_for_project(project) # Create project if is not available # - creation is required to be able set project anatomy and attributes + to_insert = [] if not project_doc: print(f"Creating project '{project_name}'") project_doc = create_project(project_name, project_code, dbcon=dbcon) - bulk_writes = [] - sync_assets = set() - for asset in all_assets: - asset_data = {"zou_id": asset["id"]} + # Query all assets of the local project + project_col = dbcon.database[project_code] + asset_doc_ids = { + asset_doc["data"]["zou_id"]: asset_doc + for asset_doc in project_col.find({"type": "asset"}) + if asset_doc["data"].get("zou_id") + } + asset_doc_ids[project["id"]] = project_doc - # Set tasks - asset_tasks = gazu.task.all_tasks_for_asset(asset) - asset_data["tasks"] = { - t["task_type_name"]: {"type": t["task_type_name"]} for t in asset_tasks - } - - # Update or create asset - if asset["id"] in asset_docs_zou_ids: # Update asset - asset_doc = project_col.find_one({"data.zou_id": asset["id"]}) - - # Override all 'data' - updated_data = { - k: asset_data[k] - for k in asset_data.keys() - if asset_doc["data"].get(k) != asset_data[k] - } - if updated_data: - bulk_writes.append( - UpdateOne( - {"_id": asset_doc["_id"]}, {"$set": {"data": asset_data}} - ) - ) - else: # Create - asset_doc = { - "name": asset["name"], + # Create + to_insert.extend( + [ + { + "name": item["name"], "type": "asset", "schema": "openpype:asset-3.0", - "data": asset_data, - "parent": project_doc["_id"], + "data": {"zou_id": item["id"], "tasks": {}}, } + for item in all_episodes + all_assets + all_seqs + all_shots + if item["id"] not in asset_doc_ids.keys() + ] + ) + if to_insert: + # Insert in doc + project_col.insert_many(to_insert) - # Insert new doc - bulk_writes.append(InsertOne(asset_doc)) + # Update existing docs + asset_doc_ids.update( + { + asset_doc["data"]["zou_id"]: asset_doc + for asset_doc in project_col.find({"type": "asset"}) + if asset_doc["data"].get("zou_id") + } + ) - # Keep synchronized asset for diff - sync_assets.add(asset_doc["_id"]) + # Update + all_entities = all_assets + all_episodes + all_seqs + all_shots + bulk_writes = update_op_assets(project_col, all_entities, asset_doc_ids) - # Delete from diff of assets in OP and synchronized assets to detect deleted assets - diff_assets = set(asset_doc_ids.keys()) - sync_assets + # Delete + diff_assets = set(asset_doc_ids.keys()) - { + e["id"] for e in all_entities + [project] + } if diff_assets: - # Delete doc bulk_writes.extend( [DeleteOne(asset_doc_ids[asset_id]) for asset_id in diff_assets] ) @@ -241,6 +235,75 @@ def sync_local(): dbcon.uninstall() +def update_op_assets( + project_col: Collection, items_list: List[dict], asset_doc_ids: Dict[str, dict] +) -> List[UpdateOne]: + """Update OpenPype assets. + Set 'data' and 'parent' fields. + + :param project_col: Project collection to query data from + :param items_list: List of zou items to update + :param asset_doc_ids: Dicts of [{zou_id: asset_doc}, ...] + :return: List of UpdateOne objects + """ + bulk_writes = [] + for item in items_list: + # Update asset + item_doc = project_col.find_one({"data.zou_id": item["id"]}) + item_data = item_doc["data"].copy() + + # Tasks + tasks_list = None + if item["type"] == "Asset": + tasks_list = gazu.task.all_tasks_for_asset(item) + elif item["type"] == "Shot": + tasks_list = gazu.task.all_tasks_for_shot(item) + if tasks_list: + item_data["tasks"] = { + t["task_type_name"]: {"type": t["task_type_name"]} for t in tasks_list + } + + # Visual parent for hierarchy + direct_parent_id = item["parent_id"] or item["source_id"] + if direct_parent_id: + visual_parent_doc = asset_doc_ids[direct_parent_id] + item_data["visualParent"] = visual_parent_doc["_id"] + + # Add parents for hierarchy + parent_zou_id = item["parent_id"] + item_data["parents"] = [] + while parent_zou_id is not None: + parent_doc = asset_doc_ids[parent_zou_id] + item_data["parents"].insert(0, parent_doc["name"]) + + parent_zou_id = next( + i for i in items_list if i["id"] == parent_doc["data"]["zou_id"] + )["parent_id"] + + # TODO create missing tasks before + + # Update 'data' different in zou DB + updated_data = { + k: item_data[k] + for k in item_data.keys() + if item_doc["data"].get(k) != item_data[k] + } + if updated_data or not item_doc.get("parent"): + bulk_writes.append( + UpdateOne( + {"_id": item_doc["_id"]}, + { + "$set": { + "data": item_data, + "parent": asset_doc_ids[item["project_id"]]["_id"], + } + }, + ) + ) + + return bulk_writes + + @cli_main.command() def show_dialog(): """Show ExampleAddon dialog. From ee281f740d58821ab4d5e190e6b4863af5c2ac44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 10 Feb 2022 18:03:27 +0100 Subject: [PATCH 0125/1227] Project tasks --- .../default_modules/kitsu/kitsu_module.py | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index 92d724be67..84709bc0a2 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -160,6 +160,8 @@ def sync_openpype(): dbcon.install() all_projects = gazu.project.all_projects() for project in all_projects: + bulk_writes = [] + # Create project locally # Try to find project document project_name = project["name"] @@ -180,6 +182,23 @@ def sync_openpype(): print(f"Creating project '{project_name}'") project_doc = create_project(project_name, project_code, dbcon=dbcon) + # Project tasks + bulk_writes.append( + UpdateOne( + {"_id": project_doc["_id"]}, + { + "$set": { + "config.tasks": { + t["name"]: { + "short_name": t.get("short_name", t["name"]) + } + for t in gazu.task.all_task_types_for_project(project) + } + } + }, + ) + ) + # Query all assets of the local project project_col = dbcon.database[project_code] asset_doc_ids = { @@ -217,7 +236,7 @@ def sync_openpype(): # Update all_entities = all_assets + all_episodes + all_seqs + all_shots - bulk_writes = update_op_assets(project_col, all_entities, asset_doc_ids) + bulk_writes.extend(update_op_assets(project_col, all_entities, asset_doc_ids)) # Delete diff_assets = set(asset_doc_ids.keys()) - { @@ -228,7 +247,7 @@ def sync_openpype(): [DeleteOne(asset_doc_ids[asset_id]) for asset_id in diff_assets] ) - # Write into DB + # Write into DB # TODO make it common for all projects if bulk_writes: project_col.bulk_write(bulk_writes) From d63c5fcae8ec1ee8c49c4a21dea86b3e9da157d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Fri, 11 Feb 2022 09:29:08 +0100 Subject: [PATCH 0126/1227] Optim: bulkwrite and queries mutualization --- .../default_modules/kitsu/kitsu_module.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index 84709bc0a2..89312a344c 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -159,9 +159,8 @@ def sync_openpype(): dbcon = AvalonMongoDB() dbcon.install() all_projects = gazu.project.all_projects() + bulk_writes = [] for project in all_projects: - bulk_writes = [] - # Create project locally # Try to find project document project_name = project["name"] @@ -236,7 +235,7 @@ def sync_openpype(): # Update all_entities = all_assets + all_episodes + all_seqs + all_shots - bulk_writes.extend(update_op_assets(project_col, all_entities, asset_doc_ids)) + bulk_writes.extend(update_op_assets(all_entities, asset_doc_ids)) # Delete diff_assets = set(asset_doc_ids.keys()) - { @@ -247,20 +246,19 @@ def sync_openpype(): [DeleteOne(asset_doc_ids[asset_id]) for asset_id in diff_assets] ) - # Write into DB # TODO make it common for all projects - if bulk_writes: - project_col.bulk_write(bulk_writes) + # Write into DB + if bulk_writes: + project_col.bulk_write(bulk_writes) dbcon.uninstall() def update_op_assets( - project_col: Collection, items_list: List[dict], asset_doc_ids: Dict[str, dict] + items_list: List[dict], asset_doc_ids: Dict[str, dict] ) -> List[UpdateOne]: """Update OpenPype assets. Set 'data' and 'parent' fields. - :param project_col: Project collection to query data from :param items_list: List of zou items to update :param asset_doc_ids: Dicts of [{zou_id: asset_doc}, ...] :return: List of UpdateOne objects @@ -268,7 +266,7 @@ def update_op_assets( bulk_writes = [] for item in items_list: # Update asset - item_doc = project_col.find_one({"data.zou_id": item["id"]}) + item_doc = asset_doc_ids[item["id"]] item_data = item_doc["data"].copy() # Tasks @@ -299,8 +297,6 @@ def update_op_assets( i for i in items_list if i["id"] == parent_doc["data"]["zou_id"] )["parent_id"] - # TODO create missing tasks before - # Update 'data' different in zou DB updated_data = { k: item_data[k] From 4e68bcf55fd2552f53904dcd0a6c47b56946c927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Fri, 11 Feb 2022 09:31:14 +0100 Subject: [PATCH 0127/1227] cleaning --- openpype/modules/default_modules/kitsu/kitsu_module.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index 89312a344c..e52d18b84b 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -15,7 +15,6 @@ import gazu from openpype.lib import create_project from openpype.modules import JsonFilesSettingsDef, OpenPypeModule, ModulesManager from pymongo import DeleteOne, UpdateOne -from pymongo.collection import Collection from openpype_interfaces import IPluginPaths, ITrayAction From 294b93f65a97d17f503a82962f121c3202002a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Sat, 12 Feb 2022 15:06:22 +0100 Subject: [PATCH 0128/1227] Create episode --- .../default_modules/kitsu/kitsu_module.py | 226 +++++++++++++++++- .../defaults/project_settings/kitsu.json | 6 +- .../projects_schema/schema_project_kitsu.json | 26 +- 3 files changed, 243 insertions(+), 15 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index e52d18b84b..c4f627d5ad 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -6,12 +6,14 @@ in global space here until are required or used. - imports of Python 3 packages - we still support Python 2 hosts where addon definition should available """ -import os -from typing import Dict, List, Set import click +import os +import re +from typing import Dict, List from avalon.api import AvalonMongoDB import gazu +from openpype.api import get_project_settings from openpype.lib import create_project from openpype.modules import JsonFilesSettingsDef, OpenPypeModule, ModulesManager from pymongo import DeleteOne, UpdateOne @@ -145,8 +147,8 @@ def cli_main(): @cli_main.command() -def sync_openpype(): - """Synchronize openpype database from Zou sever database.""" +def sync_zou(): + """Synchronize Zou server database (Kitsu backend) with openpype database.""" # Connect to server gazu.client.set_host(os.environ["KITSU_SERVER"]) @@ -157,8 +159,108 @@ def sync_openpype(): # Iterate projects dbcon = AvalonMongoDB() dbcon.install() - all_projects = gazu.project.all_projects() + + op_projects = [p for p in dbcon.projects()] bulk_writes = [] + for op_project in op_projects: + # Create project locally + # Try to find project document + project_name = op_project["name"] + project_code = op_project["data"]["code"] + dbcon.Session["AVALON_PROJECT"] = project_name + + # Get all entities from zou + zou_project = gazu.project.get_project_by_name(project_name) + + # Create project + if zou_project is None: + raise RuntimeError( + f"Project '{project_name}' doesn't exist in Zou database, please create it in Kitsu and add logged user to it before running synchronization." + ) + + # Update project settings and data + zou_project.update( + { + "code": op_project["data"]["code"], + "fps": op_project["data"]["fps"], + "resolution": f"{op_project['data']['resolutionWidth']}x{op_project['data']['resolutionHeight']}", + } + ) + gazu.project.update_project(zou_project) + gazu.project.update_project_data(zou_project, data=op_project["data"]) + + all_assets = gazu.asset.all_assets_for_project(zou_project) + all_episodes = gazu.shot.all_episodes_for_project(zou_project) + all_seqs = gazu.shot.all_sequences_for_project(zou_project) + all_shots = gazu.shot.all_shots_for_project(zou_project) + print(zou_project["name"]) + all_entities_ids = { + e["id"] for e in all_episodes + all_seqs + all_shots + all_assets + } + + project_module_settings = get_project_settings(project_name)["kitsu"] + + # Create new assets + # Query all assets of the local project + project_col = dbcon.database[project_name] + asset_docs = [asset_doc for asset_doc in project_col.find({"type": "asset"})] + + new_assets_docs = [ + doc + for doc in asset_docs + if doc["data"].get("zou_id") not in all_entities_ids + ] + naming_pattern = project_module_settings["entities_naming_pattern"] + regex_ep = re.compile( + r"({})|({})|({})".format( + naming_pattern["episode"].replace("#", "\d"), + naming_pattern["sequence"].replace("#", "\d"), + naming_pattern["shot"].replace("#", "\d"), + ), + re.IGNORECASE, + ) + for doc in new_assets_docs: + match = regex_ep.match(doc["name"]) + if not match: + # TODO asset + continue + + print(doc) + if match.group(1): # Episode + new_episode = gazu.shot.new_episode(zou_project, doc["name"]) + + # Update doc with zou id + bulk_writes.append( + UpdateOne( + {"_id": doc["_id"]}, + {"$set": {"data.zou_id": new_episode["id"]}}, + ) + ) + elif match.group(2): # Sequence + # TODO match zou episode + new_sequence = gazu.shot.new_sequence(zou_project, doc["name"]) + + # Update doc with zou id + bulk_writes.append( + UpdateOne( + {"_id": doc["_id"]}, + {"$set": {"data.zou_id": new_sequence["id"]}}, + ) + ) + elif match.group(3): # Shot + pass + + # Delete + # if gazu. + + # Write into DB + if bulk_writes: + project_col.bulk_write(bulk_writes) + + dbcon.uninstall() + + return + for project in all_projects: # Create project locally # Try to find project document @@ -245,9 +347,117 @@ def sync_openpype(): [DeleteOne(asset_doc_ids[asset_id]) for asset_id in diff_assets] ) - # Write into DB - if bulk_writes: - project_col.bulk_write(bulk_writes) + +@cli_main.command() +def sync_openpype(): + """Synchronize openpype database from Zou sever database.""" + + # Connect to server + gazu.client.set_host(os.environ["KITSU_SERVER"]) + + # Authenticate + gazu.log_in(os.environ["KITSU_LOGIN"], os.environ["KITSU_PWD"]) + + # Iterate projects + dbcon = AvalonMongoDB() + dbcon.install() + all_projects = gazu.project.all_projects() + bulk_writes = [] + for project in all_projects: + # Create project locally + # Try to find project document + project_name = project["name"] + project_code = project_name + dbcon.Session["AVALON_PROJECT"] = project_name + project_doc = dbcon.find_one({"type": "project"}) + + # Get all assets from zou + all_assets = gazu.asset.all_assets_for_project(project) + all_episodes = gazu.shot.all_episodes_for_project(project) + all_seqs = gazu.shot.all_sequences_for_project(project) + all_shots = gazu.shot.all_shots_for_project(project) + + # Create project if is not available + # - creation is required to be able set project anatomy and attributes + to_insert = [] + if not project_doc: + print(f"Creating project '{project_name}'") + project_doc = create_project(project_name, project_code, dbcon=dbcon) + + # Project data and tasks + bulk_writes.append( + UpdateOne( + {"_id": project_doc["_id"]}, + { + "$set": { + "config.tasks": { + t["name"]: {"short_name": t.get("short_name", t["name"])} + for t in gazu.task.all_task_types_for_project(project) + }, + "data": project["data"].update( + { + "code": project["code"], + "fps": project_code["fps"], + "resolutionWidth": project["resolution"].split("x")[0], + "resolutionHeight": project["resolution"].split("x")[1], + } + ), + } + }, + ) + ) + + # Query all assets of the local project + project_col = dbcon.database[project_code] + asset_doc_ids = { + asset_doc["data"]["zou_id"]: asset_doc + for asset_doc in project_col.find({"type": "asset"}) + if asset_doc["data"].get("zou_id") + } + asset_doc_ids[project["id"]] = project_doc + + # Create + to_insert.extend( + [ + { + "name": item["name"], + "type": "asset", + "schema": "openpype:asset-3.0", + "data": {"zou_id": item["id"], "tasks": {}}, + } + for item in all_episodes + all_assets + all_seqs + all_shots + if item["id"] not in asset_doc_ids.keys() + ] + ) + if to_insert: + # Insert in doc + project_col.insert_many(to_insert) + + # Update existing docs + asset_doc_ids.update( + { + asset_doc["data"]["zou_id"]: asset_doc + for asset_doc in project_col.find({"type": "asset"}) + if asset_doc["data"].get("zou_id") + } + ) + + # Update + all_entities = all_assets + all_episodes + all_seqs + all_shots + bulk_writes.extend(update_op_assets(all_entities, asset_doc_ids)) + + # Delete + diff_assets = set(asset_doc_ids.keys()) - { + e["id"] for e in all_entities + [project] + } + if diff_assets: + bulk_writes.extend( + [DeleteOne(asset_doc_ids[asset_id]) for asset_id in diff_assets] + ) + + # Write into DB + if bulk_writes: + project_col.bulk_write(bulk_writes) dbcon.uninstall() diff --git a/openpype/settings/defaults/project_settings/kitsu.json b/openpype/settings/defaults/project_settings/kitsu.json index b4d2ccc611..435814a9d1 100644 --- a/openpype/settings/defaults/project_settings/kitsu.json +++ b/openpype/settings/defaults/project_settings/kitsu.json @@ -1,3 +1,7 @@ { - "number": 0 + "entities_naming_pattern": { + "episode": "E##", + "sequence": "SQ##", + "shot": "SH##" + } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json b/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json index 93976cc03b..a504959001 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json @@ -6,12 +6,26 @@ "is_file": true, "children": [ { - "type": "number", - "key": "number", - "label": "This is your lucky number:", - "minimum": 7, - "maximum": 7, - "decimals": 0 + "type": "dict", + "key": "entities_naming_pattern", + "label": "Entities naming pattern", + "children": [ + { + "type": "text", + "key": "episode", + "label": "Episode:" + }, + { + "type": "text", + "key": "sequence", + "label": "Sequence:" + }, + { + "type": "text", + "key": "shot", + "label": "Shot:" + } + ] } ] } From bad16651e1b2ed2d307496941ac95adb1224040a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Mon, 14 Feb 2022 17:58:06 +0100 Subject: [PATCH 0129/1227] Create asset, ep, seq and shot in zou --- .../default_modules/kitsu/kitsu_module.py | 123 +++++++++++++----- 1 file changed, 93 insertions(+), 30 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index c4f627d5ad..453d1c5315 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -189,6 +189,7 @@ def sync_zou(): gazu.project.update_project(zou_project) gazu.project.update_project_data(zou_project, data=op_project["data"]) + all_asset_types = gazu.asset.all_asset_types() all_assets = gazu.asset.all_assets_for_project(zou_project) all_episodes = gazu.shot.all_episodes_for_project(zou_project) all_seqs = gazu.shot.all_sequences_for_project(zou_project) @@ -199,15 +200,17 @@ def sync_zou(): } project_module_settings = get_project_settings(project_name)["kitsu"] + project_col = dbcon.database[project_name] + asset_docs = { + asset_doc["_id"]: asset_doc + for asset_doc in project_col.find({"type": "asset"}) + } # Create new assets # Query all assets of the local project - project_col = dbcon.database[project_name] - asset_docs = [asset_doc for asset_doc in project_col.find({"type": "asset"})] - new_assets_docs = [ doc - for doc in asset_docs + for doc in asset_docs.values() if doc["data"].get("zou_id") not in all_entities_ids ] naming_pattern = project_module_settings["entities_naming_pattern"] @@ -220,37 +223,95 @@ def sync_zou(): re.IGNORECASE, ) for doc in new_assets_docs: + visual_parent_id = doc["data"]["visualParent"] + match = regex_ep.match(doc["name"]) - if not match: - # TODO asset - continue - - print(doc) - if match.group(1): # Episode - new_episode = gazu.shot.new_episode(zou_project, doc["name"]) - - # Update doc with zou id - bulk_writes.append( - UpdateOne( - {"_id": doc["_id"]}, - {"$set": {"data.zou_id": new_episode["id"]}}, - ) + if not match: # Asset + new_entity = gazu.asset.new_asset( + zou_project, all_asset_types[0], doc["name"] ) + + elif match.group(1): # Episode + new_entity = gazu.shot.new_episode(zou_project, doc["name"]) + elif match.group(2): # Sequence - # TODO match zou episode - new_sequence = gazu.shot.new_sequence(zou_project, doc["name"]) - - # Update doc with zou id - bulk_writes.append( - UpdateOne( - {"_id": doc["_id"]}, - {"$set": {"data.zou_id": new_sequence["id"]}}, - ) + parent_doc = asset_docs[visual_parent_id] + new_entity = gazu.shot.new_sequence( + zou_project, doc["name"], episode=parent_doc["data"]["zou_id"] ) - elif match.group(3): # Shot - pass - # Delete + elif match.group(3): # Shot + # Match and check parent doc + parent_doc = asset_docs[visual_parent_id] + zou_parent_id = parent_doc["data"]["zou_id"] + if parent_doc["data"].get("zou", {}).get("type") != "Sequence": + # Warn + print( + f"Shot {doc['name']} must be parented to a Sequence in Kitsu. Creating automatically one substitute sequence..." + ) + + # Create new sequence + digits_padding = naming_pattern["sequence"].count("#") + substitute_sequence_name = f'{naming_pattern["sequence"].replace("#" * digits_padding, "1".zfill(digits_padding))}' + new_sequence = gazu.shot.new_sequence( + zou_project, substitute_sequence_name, episode=zou_parent_id + ) + + # Insert doc + inserted = project_col.insert_one( + { + "name": substitute_sequence_name, + "type": "asset", + "schema": "openpype:asset-3.0", + "data": { + "zou_id": new_sequence["id"], + "tasks": {}, + "parents": parent_doc["data"]["parents"] + + [parent_doc["name"]], + "visualParent": parent_doc["_id"], + }, + "parent": parent_doc["_id"], + } + ) + visual_parent_id = inserted.inserted_id + + # Update parent ID + zou_parent_id = new_sequence["id"] + + # Create shot + new_entity = gazu.shot.new_shot( + zou_project, + zou_parent_id, + doc["name"], + frame_in=doc["data"]["frameStart"], + frame_out=doc["data"]["frameEnd"], + nb_frames=doc["data"]["frameEnd"] - doc["data"]["frameStart"], + ) + + # Update doc with zou id + doc["data"].update( + { + "zou_id": new_entity["id"], + "visualParent": visual_parent_id, + "zou": new_entity, + } + ) + bulk_writes.append( + UpdateOne( + {"_id": doc["_id"]}, + { + "$set": { + "data.zou_id": new_entity["id"], + "data.visualParent": visual_parent_id, + "data.zou": new_entity, + } + }, + ) + ) + + # TODO update / tasks + + # Delete TODO # if gazu. # Write into DB @@ -477,6 +538,7 @@ def update_op_assets( # Update asset item_doc = asset_doc_ids[item["id"]] item_data = item_doc["data"].copy() + item_data["zou"] = item # Tasks tasks_list = None @@ -484,6 +546,7 @@ def update_op_assets( tasks_list = gazu.task.all_tasks_for_asset(item) elif item["type"] == "Shot": tasks_list = gazu.task.all_tasks_for_shot(item) + # TODO frame in and out if tasks_list: item_data["tasks"] = { t["task_type_name"]: {"type": t["task_type_name"]} for t in tasks_list From 9af6264d1e02f92244d678964da5c5902428016f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 15 Feb 2022 18:21:26 +0100 Subject: [PATCH 0130/1227] Update and delete in zou --- .../default_modules/kitsu/kitsu_module.py | 146 +++++++----------- 1 file changed, 53 insertions(+), 93 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index 453d1c5315..e42edbc52b 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -6,6 +6,8 @@ in global space here until are required or used. - imports of Python 3 packages - we still support Python 2 hosts where addon definition should available """ +from asyncio import all_tasks +from turtle import update import click import os import re @@ -170,6 +172,7 @@ def sync_zou(): dbcon.Session["AVALON_PROJECT"] = project_name # Get all entities from zou + print(f"Synchronizing {project_name}...") zou_project = gazu.project.get_project_by_name(project_name) # Create project @@ -194,11 +197,11 @@ def sync_zou(): all_episodes = gazu.shot.all_episodes_for_project(zou_project) all_seqs = gazu.shot.all_sequences_for_project(zou_project) all_shots = gazu.shot.all_shots_for_project(zou_project) - print(zou_project["name"]) all_entities_ids = { e["id"] for e in all_episodes + all_seqs + all_shots + all_assets } + # Query all assets of the local project project_module_settings = get_project_settings(project_name)["kitsu"] project_col = dbcon.database[project_name] asset_docs = { @@ -207,7 +210,6 @@ def sync_zou(): } # Create new assets - # Query all assets of the local project new_assets_docs = [ doc for doc in asset_docs.values() @@ -225,6 +227,7 @@ def sync_zou(): for doc in new_assets_docs: visual_parent_id = doc["data"]["visualParent"] + # Match asset type by it's name match = regex_ep.match(doc["name"]) if not match: # Asset new_entity = gazu.asset.new_asset( @@ -269,6 +272,7 @@ def sync_zou(): "parents": parent_doc["data"]["parents"] + [parent_doc["name"]], "visualParent": parent_doc["_id"], + "zou": new_sequence, }, "parent": parent_doc["_id"], } @@ -309,10 +313,54 @@ def sync_zou(): ) ) - # TODO update / tasks + # Update assets + all_tasks_types = {t["name"]: t for t in gazu.task.all_task_types()} + assets_docs_to_update = [ + doc + for doc in asset_docs.values() + if doc["data"].get("zou_id") in all_entities_ids + ] + for doc in assets_docs_to_update: + zou_id = doc["data"].get("zou", {}).get("id") + if zou_id: + # Data + entity_data = {} + frame_in = doc["data"].get("frameStart") + frame_out = doc["data"].get("frameEnd") + if frame_in or frame_out: + entity_data.update( + { + "data": {"frame_in": frame_in, "frame_out": frame_out}, + "nb_frames": frame_out - frame_in, + } + ) + entity = gazu.raw.update("entities", zou_id, entity_data) - # Delete TODO - # if gazu. + # Tasks + all_tasks_func = getattr( + gazu.task, f"all_tasks_for_{entity['type'].lower()}" + ) + entity_tasks = {t["name"] for t in all_tasks_func(entity)} + for task_name in doc["data"]["tasks"].keys(): + # Create only if new + if task_name not in entity_tasks: + task_type = all_tasks_types.get(task_name) + + # Create non existing task + if not task_type: + task_type = gazu.task.new_task_type(task_name) + all_tasks_types[task_name] = task_type + + # New task for entity + gazu.task.new_task(entity, task_type) + + # Delete + deleted_entities = all_entities_ids - { + asset_doc["data"].get("zou", {}).get("id") + for asset_doc in asset_docs.values() + } + for entity_id in deleted_entities: + gazu.raw.delete(f"data/entities/{entity_id}") # Write into DB if bulk_writes: @@ -320,94 +368,6 @@ def sync_zou(): dbcon.uninstall() - return - - for project in all_projects: - # Create project locally - # Try to find project document - project_name = project["name"] - project_code = project_name - dbcon.Session["AVALON_PROJECT"] = project_name - project_doc = dbcon.find_one({"type": "project"}) - - # Get all assets from zou - all_assets = gazu.asset.all_assets_for_project(project) - all_episodes = gazu.shot.all_episodes_for_project(project) - all_seqs = gazu.shot.all_sequences_for_project(project) - all_shots = gazu.shot.all_shots_for_project(project) - - # Create project if is not available - # - creation is required to be able set project anatomy and attributes - to_insert = [] - if not project_doc: - print(f"Creating project '{project_name}'") - project_doc = create_project(project_name, project_code, dbcon=dbcon) - - # Project tasks - bulk_writes.append( - UpdateOne( - {"_id": project_doc["_id"]}, - { - "$set": { - "config.tasks": { - t["name"]: { - "short_name": t.get("short_name", t["name"]) - } - for t in gazu.task.all_task_types_for_project(project) - } - } - }, - ) - ) - - # Query all assets of the local project - project_col = dbcon.database[project_code] - asset_doc_ids = { - asset_doc["data"]["zou_id"]: asset_doc - for asset_doc in project_col.find({"type": "asset"}) - if asset_doc["data"].get("zou_id") - } - asset_doc_ids[project["id"]] = project_doc - - # Create - to_insert.extend( - [ - { - "name": item["name"], - "type": "asset", - "schema": "openpype:asset-3.0", - "data": {"zou_id": item["id"], "tasks": {}}, - } - for item in all_episodes + all_assets + all_seqs + all_shots - if item["id"] not in asset_doc_ids.keys() - ] - ) - if to_insert: - # Insert in doc - project_col.insert_many(to_insert) - - # Update existing docs - asset_doc_ids.update( - { - asset_doc["data"]["zou_id"]: asset_doc - for asset_doc in project_col.find({"type": "asset"}) - if asset_doc["data"].get("zou_id") - } - ) - - # Update - all_entities = all_assets + all_episodes + all_seqs + all_shots - bulk_writes.extend(update_op_assets(all_entities, asset_doc_ids)) - - # Delete - diff_assets = set(asset_doc_ids.keys()) - { - e["id"] for e in all_entities + [project] - } - if diff_assets: - bulk_writes.extend( - [DeleteOne(asset_doc_ids[asset_id]) for asset_id in diff_assets] - ) - @cli_main.command() def sync_openpype(): From b2ce0ac07a04ebdc2e61b4289ec2c2fcde624c63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 15 Feb 2022 18:29:43 +0100 Subject: [PATCH 0131/1227] Shot naming matching --- .../default_modules/kitsu/kitsu_module.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index e42edbc52b..a67f0e2d58 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -233,16 +233,7 @@ def sync_zou(): new_entity = gazu.asset.new_asset( zou_project, all_asset_types[0], doc["name"] ) - - elif match.group(1): # Episode - new_entity = gazu.shot.new_episode(zou_project, doc["name"]) - - elif match.group(2): # Sequence - parent_doc = asset_docs[visual_parent_id] - new_entity = gazu.shot.new_sequence( - zou_project, doc["name"], episode=parent_doc["data"]["zou_id"] - ) - + # Match case in shot Date: Tue, 15 Feb 2022 18:30:06 +0100 Subject: [PATCH 0132/1227] cleaning --- openpype/modules/default_modules/kitsu/kitsu_module.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index a67f0e2d58..47af25ce60 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -6,8 +6,6 @@ in global space here until are required or used. - imports of Python 3 packages - we still support Python 2 hosts where addon definition should available """ -from asyncio import all_tasks -from turtle import update import click import os import re From a8611c07a8d56942950a2294a95f06f07aa87d94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 16 Feb 2022 15:42:23 +0100 Subject: [PATCH 0133/1227] Clean sync zou --- .../default_modules/kitsu/kitsu_module.py | 138 +++++++++--------- 1 file changed, 72 insertions(+), 66 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index 47af25ce60..ce4c579607 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -13,6 +13,24 @@ from typing import Dict, List from avalon.api import AvalonMongoDB import gazu +from gazu.asset import all_assets_for_project, all_asset_types, new_asset +from gazu.shot import ( + all_episodes_for_project, + all_sequences_for_project, + all_shots_for_project, + new_episode, + new_sequence, + new_shot, + update_sequence, +) +from gazu.task import ( + all_tasks_for_asset, + all_tasks_for_shot, + all_task_types, + all_task_types_for_project, + new_task, + new_task_type, +) from openpype.api import get_project_settings from openpype.lib import create_project from openpype.modules import JsonFilesSettingsDef, OpenPypeModule, ModulesManager @@ -176,7 +194,7 @@ def sync_zou(): # Create project if zou_project is None: raise RuntimeError( - f"Project '{project_name}' doesn't exist in Zou database, please create it in Kitsu and add logged user to it before running synchronization." + f"Project '{project_name}' doesn't exist in Zou database, please create it in Kitsu and add OpenPype user to it before running synchronization." ) # Update project settings and data @@ -190,11 +208,11 @@ def sync_zou(): gazu.project.update_project(zou_project) gazu.project.update_project_data(zou_project, data=op_project["data"]) - all_asset_types = gazu.asset.all_asset_types() - all_assets = gazu.asset.all_assets_for_project(zou_project) - all_episodes = gazu.shot.all_episodes_for_project(zou_project) - all_seqs = gazu.shot.all_sequences_for_project(zou_project) - all_shots = gazu.shot.all_shots_for_project(zou_project) + asset_types = all_asset_types() + all_assets = all_assets_for_project(zou_project) + all_episodes = all_episodes_for_project(zou_project) + all_seqs = all_sequences_for_project(zou_project) + all_shots = all_shots_for_project(zou_project) all_entities_ids = { e["id"] for e in all_episodes + all_seqs + all_shots + all_assets } @@ -211,14 +229,14 @@ def sync_zou(): new_assets_docs = [ doc for doc in asset_docs.values() - if doc["data"].get("zou_id") not in all_entities_ids + if doc["data"].get("zou", {}).get("id") not in all_entities_ids ] naming_pattern = project_module_settings["entities_naming_pattern"] regex_ep = re.compile( - r"({})|({})|({})".format( - naming_pattern["episode"].replace("#", "\d"), - naming_pattern["sequence"].replace("#", "\d"), - naming_pattern["shot"].replace("#", "\d"), + r"(.*{}.*)|(.*{}.*)|(.*{}.*)".format( + naming_pattern["shot"].replace("#", ""), + naming_pattern["sequence"].replace("#", ""), + naming_pattern["episode"].replace("#", ""), ), re.IGNORECASE, ) @@ -228,51 +246,38 @@ def sync_zou(): # Match asset type by it's name match = regex_ep.match(doc["name"]) if not match: # Asset - new_entity = gazu.asset.new_asset( - zou_project, all_asset_types[0], doc["name"] - ) + new_entity = new_asset(zou_project, asset_types[0], doc["name"]) # Match case in shot Date: Wed, 16 Feb 2022 17:30:35 +0100 Subject: [PATCH 0134/1227] Fix sync --- .../default_modules/kitsu/kitsu_module.py | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index ce4c579607..266aaa9139 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -184,7 +184,6 @@ def sync_zou(): # Create project locally # Try to find project document project_name = op_project["name"] - project_code = op_project["data"]["code"] dbcon.Session["AVALON_PROJECT"] = project_name # Get all entities from zou @@ -198,15 +197,16 @@ def sync_zou(): ) # Update project settings and data - zou_project.update( - { - "code": op_project["data"]["code"], - "fps": op_project["data"]["fps"], - "resolution": f"{op_project['data']['resolutionWidth']}x{op_project['data']['resolutionHeight']}", - } - ) + if op_project["data"]: + zou_project.update( + { + "code": op_project["data"]["code"], + "fps": op_project["data"]["fps"], + "resolution": f"{op_project['data']['resolutionWidth']}x{op_project['data']['resolutionHeight']}", + } + ) + gazu.project.update_project_data(zou_project, data=op_project["data"]) gazu.project.update_project(zou_project) - gazu.project.update_project_data(zou_project, data=op_project["data"]) asset_types = all_asset_types() all_assets = all_assets_for_project(zou_project) @@ -270,8 +270,9 @@ def sync_zou(): created_sequence = new_sequence( zou_project, substitute_sequence_name, episode=zou_parent_id ) - created_sequence["is_substitute"] = True - update_sequence(created_sequence) + gazu.shot.update_sequence_data( + created_sequence, {"is_substitute": True} + ) # Update parent ID zou_parent_id = created_sequence["id"] @@ -395,11 +396,18 @@ def sync_openpype(): dbcon.Session["AVALON_PROJECT"] = project_name project_doc = dbcon.find_one({"type": "project"}) + print(f"Synchronizing {project_name}...") + # Get all assets from zou all_assets = all_assets_for_project(project) all_episodes = all_episodes_for_project(project) all_seqs = all_sequences_for_project(project) all_shots = all_shots_for_project(project) + all_entities = [ + e + for e in all_assets + all_episodes + all_seqs + all_shots + if not e["data"].get("is_substitute") + ] # Create project if is not available # - creation is required to be able set project anatomy and attributes @@ -421,7 +429,7 @@ def sync_openpype(): "data": project["data"].update( { "code": project["code"], - "fps": project_code["fps"], + "fps": project["fps"], "resolutionWidth": project["resolution"].split("x")[0], "resolutionHeight": project["resolution"].split("x")[1], } @@ -449,9 +457,8 @@ def sync_openpype(): "schema": "openpype:asset-3.0", "data": {"zou": item, "tasks": {}}, } - for item in all_episodes + all_assets + all_seqs + all_shots + for item in all_entities if item["id"] not in asset_doc_ids.keys() - and not item.get("is_substitute") ] ) if to_insert: @@ -468,7 +475,6 @@ def sync_openpype(): ) # Update - all_entities = all_assets + all_episodes + all_seqs + all_shots bulk_writes.extend(update_op_assets(all_entities, asset_doc_ids)) # Delete From 93fb1ac0f1331439852104189733671386366861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 17 Feb 2022 11:12:13 +0100 Subject: [PATCH 0135/1227] Use substitutes id --- .../default_modules/kitsu/kitsu_module.py | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index 266aaa9139..0529d38a0e 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -242,6 +242,7 @@ def sync_zou(): ) for doc in new_assets_docs: visual_parent_id = doc["data"]["visualParent"] + parent_substitutes = [] # Match asset type by it's name match = regex_ep.match(doc["name"]) @@ -273,6 +274,7 @@ def sync_zou(): gazu.shot.update_sequence_data( created_sequence, {"is_substitute": True} ) + parent_substitutes.append(created_sequence) # Update parent ID zou_parent_id = created_sequence["id"] @@ -310,6 +312,7 @@ def sync_zou(): "$set": { "data.visualParent": visual_parent_id, "data.zou": new_entity, + "data.parent_substitutes": parent_substitutes, } }, ) @@ -494,17 +497,17 @@ def sync_openpype(): def update_op_assets( - items_list: List[dict], asset_doc_ids: Dict[str, dict] + entities_list: List[dict], asset_doc_ids: Dict[str, dict] ) -> List[UpdateOne]: """Update OpenPype assets. Set 'data' and 'parent' fields. - :param items_list: List of zou items to update + :param entities_list: List of zou entities to update :param asset_doc_ids: Dicts of [{zou_id: asset_doc}, ...] :return: List of UpdateOne objects """ bulk_writes = [] - for item in items_list: + for item in entities_list: # Update asset item_doc = asset_doc_ids[item["id"]] item_data = item_doc["data"].copy() @@ -523,20 +526,28 @@ def update_op_assets( } # Visual parent for hierarchy - direct_parent_id = item["parent_id"] or item["source_id"] - if direct_parent_id: - visual_parent_doc = asset_doc_ids[direct_parent_id] + substitute_parent_item = ( + item_data["parent_substitutes"][0] + if item_data.get("parent_substitutes") + else None + ) + parent_zou_id = item["parent_id"] or item["source_id"] + if substitute_parent_item: + parent_zou_id = ( + substitute_parent_item["parent_id"] + or substitute_parent_item["source_id"] + ) + visual_parent_doc = asset_doc_ids[parent_zou_id] item_data["visualParent"] = visual_parent_doc["_id"] # Add parents for hierarchy - parent_zou_id = item["parent_id"] item_data["parents"] = [] while parent_zou_id is not None: parent_doc = asset_doc_ids[parent_zou_id] item_data["parents"].insert(0, parent_doc["name"]) parent_zou_id = next( - i for i in items_list if i["id"] == parent_doc["data"]["zou"]["id"] + i for i in entities_list if i["id"] == parent_doc["data"]["zou"]["id"] )["parent_id"] # Update 'data' different in zou DB From c058ba54c2763b4563f5912f8a8a8930cceafc1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 22 Feb 2022 18:27:06 +0100 Subject: [PATCH 0136/1227] Project sync and code DRYing --- .../modules/default_modules/kitsu/__init__.py | 10 +-- .../default_modules/kitsu/kitsu_module.py | 52 ++++++---------- .../default_modules/kitsu/listeners.py | 61 +++++++++++++++++++ .../default_modules/kitsu/utils/__init__.py | 0 .../default_modules/kitsu/utils/openpype.py | 47 ++++++++++++++ 5 files changed, 128 insertions(+), 42 deletions(-) create mode 100644 openpype/modules/default_modules/kitsu/listeners.py create mode 100644 openpype/modules/default_modules/kitsu/utils/__init__.py create mode 100644 openpype/modules/default_modules/kitsu/utils/openpype.py diff --git a/openpype/modules/default_modules/kitsu/__init__.py b/openpype/modules/default_modules/kitsu/__init__.py index cd0c2ea8af..dc7c2dad50 100644 --- a/openpype/modules/default_modules/kitsu/__init__.py +++ b/openpype/modules/default_modules/kitsu/__init__.py @@ -4,12 +4,6 @@ If addon class or settings definition won't be here their definition won't be found by OpenPype discovery. """ -from .kitsu_module import ( - AddonSettingsDef, - KitsuModule -) +from .kitsu_module import AddonSettingsDef, KitsuModule -__all__ = ( - "AddonSettingsDef", - "KitsuModule" -) +__all__ = ("AddonSettingsDef", "KitsuModule") diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index 0529d38a0e..3fddc48ee9 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -11,7 +11,6 @@ import os import re from typing import Dict, List -from avalon.api import AvalonMongoDB import gazu from gazu.asset import all_assets_for_project, all_asset_types, new_asset from gazu.shot import ( @@ -21,7 +20,6 @@ from gazu.shot import ( new_episode, new_sequence, new_shot, - update_sequence, ) from gazu.task import ( all_tasks_for_asset, @@ -31,11 +29,15 @@ from gazu.task import ( new_task, new_task_type, ) +from pymongo import DeleteOne, UpdateOne + +from avalon.api import AvalonMongoDB from openpype.api import get_project_settings from openpype.lib import create_project from openpype.modules import JsonFilesSettingsDef, OpenPypeModule, ModulesManager -from pymongo import DeleteOne, UpdateOne +from openpype.modules.default_modules.kitsu.utils.openpype import sync_project from openpype_interfaces import IPluginPaths, ITrayAction +from .listeners import add_listeners # Settings definition of this addon using `JsonFilesSettingsDef` @@ -371,8 +373,6 @@ def sync_zou(): if bulk_writes: project_col.bulk_write(bulk_writes) - # TODO Create events daemons - dbcon.uninstall() @@ -412,35 +412,8 @@ def sync_openpype(): if not e["data"].get("is_substitute") ] - # Create project if is not available - # - creation is required to be able set project anatomy and attributes - to_insert = [] - if not project_doc: - print(f"Creating project '{project_name}'") - project_doc = create_project(project_name, project_code, dbcon=dbcon) - - # Project data and tasks - bulk_writes.append( - UpdateOne( - {"_id": project_doc["_id"]}, - { - "$set": { - "config.tasks": { - t["name"]: {"short_name": t.get("short_name", t["name"])} - for t in all_task_types_for_project(project) - }, - "data": project["data"].update( - { - "code": project["code"], - "fps": project["fps"], - "resolutionWidth": project["resolution"].split("x")[0], - "resolutionHeight": project["resolution"].split("x")[1], - } - ), - } - }, - ) - ) + # Sync project. Create if doesn't exist + bulk_writes.append(sync_project(project, dbcon)) # Query all assets of the local project project_col = dbcon.database[project_code] @@ -452,6 +425,7 @@ def sync_openpype(): asset_doc_ids[project["id"]] = project_doc # Create + to_insert = [] to_insert.extend( [ { @@ -572,6 +546,16 @@ def update_op_assets( return bulk_writes +@cli_main.command() +def listen(): + """Show ExampleAddon dialog. + + We don't have access to addon directly through cli so we have to create + it again. + """ + add_listeners() + + @cli_main.command() def show_dialog(): """Show ExampleAddon dialog. diff --git a/openpype/modules/default_modules/kitsu/listeners.py b/openpype/modules/default_modules/kitsu/listeners.py new file mode 100644 index 0000000000..5303933bce --- /dev/null +++ b/openpype/modules/default_modules/kitsu/listeners.py @@ -0,0 +1,61 @@ +from turtle import update +import gazu +import os + +from pymongo import DeleteOne, UpdateOne + +from avalon.api import AvalonMongoDB +from openpype.modules.default_modules.kitsu.utils.openpype import sync_project + + +def add_listeners(): + # Connect to server + gazu.client.set_host(os.environ["KITSU_SERVER"]) + + # Authenticate + gazu.log_in(os.environ["KITSU_LOGIN"], os.environ["KITSU_PWD"]) + gazu.set_event_host(os.environ["KITSU_SERVER"].replace("api", "socket.io")) + + # Connect to DB + dbcon = AvalonMongoDB() + dbcon.install() + + def new_project(data): + """Create new project into DB.""" + + # Use update process to avoid duplicating code + update_project(data) + + def update_project(data): + """Update project into DB.""" + # Get project entity + project = gazu.project.get_project(data["project_id"]) + project_name = project["name"] + dbcon.Session["AVALON_PROJECT"] = project_name + + update_project = sync_project(project, dbcon) + + # Write into DB + if update_project: + project_col = dbcon.database[project_name] + project_col.bulk_write([update_project]) + + def delete_project(data): + # Get project entity + print(data) # TODO check bugfix + project = gazu.project.get_project(data["project_id"]) + + # Delete project collection + project_col = dbcon.database[project["name"]] + project_col.drop() + + def new_asset(data): + print("Asset created %s" % data) + + event_client = gazu.events.init() + gazu.events.add_listener(event_client, "project:new", new_project) + gazu.events.add_listener(event_client, "project:update", update_project) + gazu.events.add_listener(event_client, "project:delete", delete_project) + gazu.events.add_listener(event_client, "asset:new", new_asset) + gazu.events.run_client(event_client) + print("ll") diff --git a/openpype/modules/default_modules/kitsu/utils/__init__.py b/openpype/modules/default_modules/kitsu/utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/modules/default_modules/kitsu/utils/openpype.py b/openpype/modules/default_modules/kitsu/utils/openpype.py new file mode 100644 index 0000000000..9e795bb8ca --- /dev/null +++ b/openpype/modules/default_modules/kitsu/utils/openpype.py @@ -0,0 +1,47 @@ +import gazu + +from pymongo import DeleteOne, UpdateOne + +from avalon.api import AvalonMongoDB +from openpype.lib import create_project + + +def sync_project(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: + """Sync project with database. + Create project if doesn't exist. + + :param project: Gazu project + :param dbcon: DB to create project in + :return: Update instance for the project + """ + project_name = project["name"] + project_doc = dbcon.find_one({"type": "project"}) + if not project_doc: + print(f"Creating project '{project_name}'") + project_doc = create_project(project_name, project_name, dbcon=dbcon) + + print(f"Synchronizing {project_name}...") + + # Project data and tasks + if not project["data"]: # Sentinel + project["data"] = {} + + return UpdateOne( + {"_id": project_doc["_id"]}, + { + "$set": { + "config.tasks": { + t["name"]: {"short_name": t.get("short_name", t["name"])} + for t in gazu.task.all_task_types_for_project(project) + }, + "data": project["data"].update( + { + "code": project["code"], + "fps": project["fps"], + "resolutionWidth": project["resolution"].split("x")[0], + "resolutionHeight": project["resolution"].split("x")[1], + } + ), + } + }, + ) From 0d38ccc5127bd00157aa9d19157f5d7c3ec3e20f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 23 Feb 2022 15:14:38 +0100 Subject: [PATCH 0137/1227] Listen asset and DRY --- .../default_modules/kitsu/kitsu_module.py | 117 ++++-------------- .../default_modules/kitsu/listeners.py | 70 +++++++++-- .../default_modules/kitsu/utils/openpype.py | 114 +++++++++++++++++ 3 files changed, 198 insertions(+), 103 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index 3fddc48ee9..0bdd4b12e6 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -22,10 +22,7 @@ from gazu.shot import ( new_shot, ) from gazu.task import ( - all_tasks_for_asset, - all_tasks_for_shot, all_task_types, - all_task_types_for_project, new_task, new_task_type, ) @@ -33,9 +30,12 @@ from pymongo import DeleteOne, UpdateOne from avalon.api import AvalonMongoDB from openpype.api import get_project_settings -from openpype.lib import create_project from openpype.modules import JsonFilesSettingsDef, OpenPypeModule, ModulesManager -from openpype.modules.default_modules.kitsu.utils.openpype import sync_project +from openpype.modules.default_modules.kitsu.utils.openpype import ( + create_op_asset, + sync_project, + update_op_assets, +) from openpype_interfaces import IPluginPaths, ITrayAction from .listeners import add_listeners @@ -417,33 +417,28 @@ def sync_openpype(): # Query all assets of the local project project_col = dbcon.database[project_code] - asset_doc_ids = { + zou_ids_and_asset_docs = { asset_doc["data"]["zou"]["id"]: asset_doc for asset_doc in project_col.find({"type": "asset"}) if asset_doc["data"].get("zou", {}).get("id") } - asset_doc_ids[project["id"]] = project_doc + zou_ids_and_asset_docs[project["id"]] = project_doc # Create to_insert = [] to_insert.extend( [ - { - "name": item["name"], - "type": "asset", - "schema": "openpype:asset-3.0", - "data": {"zou": item, "tasks": {}}, - } + create_op_asset(item) for item in all_entities - if item["id"] not in asset_doc_ids.keys() + if item["id"] not in zou_ids_and_asset_docs.keys() ] ) if to_insert: - # Insert in doc + # Insert doc in DB project_col.insert_many(to_insert) # Update existing docs - asset_doc_ids.update( + zou_ids_and_asset_docs.update( { asset_doc["data"]["zou"]["id"]: asset_doc for asset_doc in project_col.find({"type": "asset"}) @@ -452,15 +447,23 @@ def sync_openpype(): ) # Update - bulk_writes.extend(update_op_assets(all_entities, asset_doc_ids)) + bulk_writes.extend( + [ + UpdateOne({"_id": id}, update) + for id, update in update_op_assets(all_entities, zou_ids_and_asset_docs) + ] + ) # Delete - diff_assets = set(asset_doc_ids.keys()) - { + diff_assets = set(zou_ids_and_asset_docs.keys()) - { e["id"] for e in all_entities + [project] } if diff_assets: bulk_writes.extend( - [DeleteOne(asset_doc_ids[asset_id]) for asset_id in diff_assets] + [ + DeleteOne(zou_ids_and_asset_docs[asset_id]) + for asset_id in diff_assets + ] ) # Write into DB @@ -470,82 +473,6 @@ def sync_openpype(): dbcon.uninstall() -def update_op_assets( - entities_list: List[dict], asset_doc_ids: Dict[str, dict] -) -> List[UpdateOne]: - """Update OpenPype assets. - Set 'data' and 'parent' fields. - - :param entities_list: List of zou entities to update - :param asset_doc_ids: Dicts of [{zou_id: asset_doc}, ...] - :return: List of UpdateOne objects - """ - bulk_writes = [] - for item in entities_list: - # Update asset - item_doc = asset_doc_ids[item["id"]] - item_data = item_doc["data"].copy() - item_data["zou"] = item - - # Tasks - tasks_list = None - if item["type"] == "Asset": - tasks_list = all_tasks_for_asset(item) - elif item["type"] == "Shot": - tasks_list = all_tasks_for_shot(item) - # TODO frame in and out - if tasks_list: - item_data["tasks"] = { - t["task_type_name"]: {"type": t["task_type_name"]} for t in tasks_list - } - - # Visual parent for hierarchy - substitute_parent_item = ( - item_data["parent_substitutes"][0] - if item_data.get("parent_substitutes") - else None - ) - parent_zou_id = item["parent_id"] or item["source_id"] - if substitute_parent_item: - parent_zou_id = ( - substitute_parent_item["parent_id"] - or substitute_parent_item["source_id"] - ) - visual_parent_doc = asset_doc_ids[parent_zou_id] - item_data["visualParent"] = visual_parent_doc["_id"] - - # Add parents for hierarchy - item_data["parents"] = [] - while parent_zou_id is not None: - parent_doc = asset_doc_ids[parent_zou_id] - item_data["parents"].insert(0, parent_doc["name"]) - - parent_zou_id = next( - i for i in entities_list if i["id"] == parent_doc["data"]["zou"]["id"] - )["parent_id"] - - # Update 'data' different in zou DB - updated_data = { - k: item_data[k] - for k in item_data.keys() - if item_doc["data"].get(k) != item_data[k] - } - if updated_data or not item_doc.get("parent"): - bulk_writes.append( - UpdateOne( - {"_id": item_doc["_id"]}, - { - "$set": { - "data": item_data, - "parent": asset_doc_ids[item["project_id"]]["_id"], - } - }, - ) - ) - - return bulk_writes - - @cli_main.command() def listen(): """Show ExampleAddon dialog. diff --git a/openpype/modules/default_modules/kitsu/listeners.py b/openpype/modules/default_modules/kitsu/listeners.py index 5303933bce..fd4cf6dd3d 100644 --- a/openpype/modules/default_modules/kitsu/listeners.py +++ b/openpype/modules/default_modules/kitsu/listeners.py @@ -5,7 +5,12 @@ import os from pymongo import DeleteOne, UpdateOne from avalon.api import AvalonMongoDB -from openpype.modules.default_modules.kitsu.utils.openpype import sync_project +from openpype.modules.default_modules.kitsu.utils.openpype import ( + create_op_asset, + set_op_project, + sync_project, + update_op_assets, +) def add_listeners(): @@ -15,19 +20,22 @@ def add_listeners(): # Authenticate gazu.log_in(os.environ["KITSU_LOGIN"], os.environ["KITSU_PWD"]) gazu.set_event_host(os.environ["KITSU_SERVER"].replace("api", "socket.io")) + event_client = gazu.events.init() # Connect to DB dbcon = AvalonMongoDB() dbcon.install() + # == Project == + def new_project(data): - """Create new project into DB.""" + """Create new project into OP DB.""" # Use update process to avoid duplicating code update_project(data) def update_project(data): - """Update project into DB.""" + """Update project into OP DB.""" # Get project entity project = gazu.project.get_project(data["project_id"]) project_name = project["name"] @@ -41,6 +49,7 @@ def add_listeners(): project_col.bulk_write([update_project]) def delete_project(data): + """Delete project.""" # Get project entity print(data) # TODO check bugfix project = gazu.project.get_project(data["project_id"]) @@ -49,13 +58,58 @@ def add_listeners(): project_col = dbcon.database[project["name"]] project_col.drop() - def new_asset(data): - print("Asset created %s" % data) - - event_client = gazu.events.init() gazu.events.add_listener(event_client, "project:new", new_project) gazu.events.add_listener(event_client, "project:update", update_project) gazu.events.add_listener(event_client, "project:delete", delete_project) + + # == Asset == + + def new_asset(data): + """Create new asset into OP DB.""" + # Get project entity + project_col = set_op_project(dbcon, data["project_id"]) + + # Get gazu entity + asset = gazu.asset.get_asset(data["asset_id"]) + + # Insert doc in DB + project_col.insert_one(create_op_asset(asset)) + + # Update + update_asset(data) + + def update_asset(data): + """Update asset into OP DB.""" + project_col = set_op_project(dbcon, data["project_id"]) + project_doc = dbcon.find_one({"type": "project"}) + + # Get gazu entity + asset = gazu.asset.get_asset(data["asset_id"]) + + # Find asset doc + # Query all assets of the local project + zou_ids_and_asset_docs = { + asset_doc["data"]["zou"]["id"]: asset_doc + for asset_doc in project_col.find({"type": "asset"}) + if asset_doc["data"].get("zou", {}).get("id") + } + zou_ids_and_asset_docs[asset["project_id"]] = project_doc + + # Update + asset_doc_id, asset_update = update_op_assets([asset], zou_ids_and_asset_docs)[ + 0 + ] + project_col.update_one({"_id": asset_doc_id}, asset_update) + + def delete_asset(data): + """Delete asset of OP DB.""" + project_col = set_op_project(dbcon, data["project_id"]) + + # Delete + project_col.delete_one({"type": "asset", "data.zou.id": data["asset_id"]}) + gazu.events.add_listener(event_client, "asset:new", new_asset) + gazu.events.add_listener(event_client, "asset:update", update_asset) + gazu.events.add_listener(event_client, "asset:delete", delete_asset) + gazu.events.run_client(event_client) - print("ll") diff --git a/openpype/modules/default_modules/kitsu/utils/openpype.py b/openpype/modules/default_modules/kitsu/utils/openpype.py index 9e795bb8ca..8ef987898d 100644 --- a/openpype/modules/default_modules/kitsu/utils/openpype.py +++ b/openpype/modules/default_modules/kitsu/utils/openpype.py @@ -1,11 +1,125 @@ +from typing import Dict, List import gazu from pymongo import DeleteOne, UpdateOne +from pymongo.collection import Collection + +from gazu.task import ( + all_tasks_for_asset, + all_tasks_for_shot, +) from avalon.api import AvalonMongoDB from openpype.lib import create_project +def create_op_asset(gazu_entity: dict) -> dict: + """Create OP asset dict from gazu entity. + + :param gazu_entity: + """ + return { + "name": gazu_entity["name"], + "type": "asset", + "schema": "openpype:asset-3.0", + "data": {"zou": gazu_entity, "tasks": {}}, + } + + +def set_op_project(dbcon, project_id) -> Collection: + """Set project context. + + :param dbcon: Connection to DB. + :param project_id: Project zou ID + """ + project = gazu.project.get_project(project_id) + project_name = project["name"] + dbcon.Session["AVALON_PROJECT"] = project_name + + return dbcon.database[project_name] + + +def update_op_assets( + entities_list: List[dict], asset_doc_ids: Dict[str, dict] +) -> List[Dict[str, dict]]: + """Update OpenPype assets. + Set 'data' and 'parent' fields. + + :param entities_list: List of zou entities to update + :param asset_doc_ids: Dicts of [{zou_id: asset_doc}, ...] + :return: List of (doc_id, update_dict) tuples + """ + assets_with_update = [] + for item in entities_list: + # Update asset + item_doc = asset_doc_ids[item["id"]] + item_data = item_doc["data"].copy() + item_data["zou"] = item + + # Tasks + tasks_list = [] + if item["type"] == "Asset": + tasks_list = all_tasks_for_asset(item) + elif item["type"] == "Shot": + tasks_list = all_tasks_for_shot(item) + # TODO frame in and out + item_data["tasks"] = { + t["task_type_name"]: {"type": t["task_type_name"]} for t in tasks_list + } + + # Get zou parent id for correct hierarchy + # Use parent substitutes if existing + substitute_parent_item = ( + item_data["parent_substitutes"][0] + if item_data.get("parent_substitutes") + else None + ) + if substitute_parent_item: + parent_zou_id = substitute_parent_item["id"] + else: + parent_zou_id = ( + item.get("parent_id") or item.get("episode_id") or item.get("source_id") + ) # TODO check consistency + + # Visual parent for hierarchy + visual_parent_doc_id = ( + asset_doc_ids[parent_zou_id]["_id"] if parent_zou_id else None + ) + item_data["visualParent"] = visual_parent_doc_id + + # Add parents for hierarchy + item_data["parents"] = [] + while parent_zou_id is not None: + parent_doc = asset_doc_ids[parent_zou_id] + item_data["parents"].insert(0, parent_doc["name"]) + + # Get parent entity + parent_entity = parent_doc["data"]["zou"] + parent_zou_id = parent_entity["parent_id"] + + # Update 'data' different in zou DB + updated_data = { + k: item_data[k] + for k in item_data.keys() + if item_doc["data"].get(k) != item_data[k] + } + if updated_data or not item_doc.get("parent"): + assets_with_update.append( + ( + item_doc["_id"], + { + "$set": { + "name": item["name"], + "data": item_data, + "parent": asset_doc_ids[item["project_id"]]["_id"], + } + }, + ) + ) + + return assets_with_update + + def sync_project(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: """Sync project with database. Create project if doesn't exist. From fe5486886712a77cd40f37cac6ec41d329c17748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 23 Feb 2022 17:04:21 +0100 Subject: [PATCH 0138/1227] Listen Episode, Sequence and Shot --- .../default_modules/kitsu/listeners.py | 147 ++++++++++++++++++ .../default_modules/kitsu/utils/openpype.py | 2 +- 2 files changed, 148 insertions(+), 1 deletion(-) diff --git a/openpype/modules/default_modules/kitsu/listeners.py b/openpype/modules/default_modules/kitsu/listeners.py index fd4cf6dd3d..bb217452df 100644 --- a/openpype/modules/default_modules/kitsu/listeners.py +++ b/openpype/modules/default_modules/kitsu/listeners.py @@ -112,4 +112,151 @@ def add_listeners(): gazu.events.add_listener(event_client, "asset:update", update_asset) gazu.events.add_listener(event_client, "asset:delete", delete_asset) + # == Episode == + def new_episode(data): + """Create new episode into OP DB.""" + # Get project entity + project_col = set_op_project(dbcon, data["project_id"]) + + # Get gazu entity + episode = gazu.shot.get_episode(data["episode_id"]) + + # Insert doc in DB + project_col.insert_one(create_op_asset(episode)) + + # Update + update_episode(data) + + def update_episode(data): + """Update episode into OP DB.""" + project_col = set_op_project(dbcon, data["project_id"]) + project_doc = dbcon.find_one({"type": "project"}) + + # Get gazu entity + episode = gazu.shot.get_episode(data["episode_id"]) + + # Find asset doc + # Query all assets of the local project + zou_ids_and_asset_docs = { + asset_doc["data"]["zou"]["id"]: asset_doc + for asset_doc in project_col.find({"type": "asset"}) + if asset_doc["data"].get("zou", {}).get("id") + } + zou_ids_and_asset_docs[episode["project_id"]] = project_doc + + # Update + asset_doc_id, asset_update = update_op_assets( + [episode], zou_ids_and_asset_docs + )[0] + project_col.update_one({"_id": asset_doc_id}, asset_update) + + def delete_episode(data): + """Delete shot of OP DB.""" + project_col = set_op_project(dbcon, data["project_id"]) + print("delete episode") # TODO check bugfix + + # Delete + project_col.delete_one({"type": "asset", "data.zou.id": data["episode_id"]}) + + gazu.events.add_listener(event_client, "episode:new", new_episode) + gazu.events.add_listener(event_client, "episode:update", update_episode) + gazu.events.add_listener(event_client, "episode:delete", delete_episode) + + # == Sequence == + def new_sequence(data): + """Create new sequnce into OP DB.""" + # Get project entity + project_col = set_op_project(dbcon, data["project_id"]) + + # Get gazu entity + sequence = gazu.shot.get_sequence(data["sequence_id"]) + + # Insert doc in DB + project_col.insert_one(create_op_asset(sequence)) + + # Update + update_sequence(data) + + def update_sequence(data): + """Update sequence into OP DB.""" + project_col = set_op_project(dbcon, data["project_id"]) + project_doc = dbcon.find_one({"type": "project"}) + + # Get gazu entity + sequence = gazu.shot.get_sequence(data["sequence_id"]) + + # Find asset doc + # Query all assets of the local project + zou_ids_and_asset_docs = { + asset_doc["data"]["zou"]["id"]: asset_doc + for asset_doc in project_col.find({"type": "asset"}) + if asset_doc["data"].get("zou", {}).get("id") + } + zou_ids_and_asset_docs[sequence["project_id"]] = project_doc + + # Update + asset_doc_id, asset_update = update_op_assets( + [sequence], zou_ids_and_asset_docs + )[0] + project_col.update_one({"_id": asset_doc_id}, asset_update) + + def delete_sequence(data): + """Delete sequence of OP DB.""" + project_col = set_op_project(dbcon, data["project_id"]) + print("delete sequence") # TODO check bugfix + + # Delete + project_col.delete_one({"type": "asset", "data.zou.id": data["sequence_id"]}) + + gazu.events.add_listener(event_client, "sequence:new", new_sequence) + gazu.events.add_listener(event_client, "sequence:update", update_sequence) + gazu.events.add_listener(event_client, "sequence:delete", delete_sequence) + + # == Shot == + def new_shot(data): + """Create new shot into OP DB.""" + # Get project entity + project_col = set_op_project(dbcon, data["project_id"]) + + # Get gazu entity + shot = gazu.shot.get_shot(data["shot_id"]) + + # Insert doc in DB + project_col.insert_one(create_op_asset(shot)) + + # Update + update_shot(data) + + def update_shot(data): + """Update shot into OP DB.""" + project_col = set_op_project(dbcon, data["project_id"]) + project_doc = dbcon.find_one({"type": "project"}) + + # Get gazu entity + shot = gazu.shot.get_shot(data["shot_id"]) + + # Find asset doc + # Query all assets of the local project + zou_ids_and_asset_docs = { + asset_doc["data"]["zou"]["id"]: asset_doc + for asset_doc in project_col.find({"type": "asset"}) + if asset_doc["data"].get("zou", {}).get("id") + } + zou_ids_and_asset_docs[shot["project_id"]] = project_doc + + # Update + asset_doc_id, asset_update = update_op_assets([shot], zou_ids_and_asset_docs)[0] + project_col.update_one({"_id": asset_doc_id}, asset_update) + + def delete_shot(data): + """Delete shot of OP DB.""" + project_col = set_op_project(dbcon, data["project_id"]) + + # Delete + project_col.delete_one({"type": "asset", "data.zou.id": data["shot_id"]}) + + gazu.events.add_listener(event_client, "shot:new", new_shot) + gazu.events.add_listener(event_client, "shot:update", update_shot) + gazu.events.add_listener(event_client, "shot:delete", delete_shot) + gazu.events.run_client(event_client) diff --git a/openpype/modules/default_modules/kitsu/utils/openpype.py b/openpype/modules/default_modules/kitsu/utils/openpype.py index 8ef987898d..809748fa78 100644 --- a/openpype/modules/default_modules/kitsu/utils/openpype.py +++ b/openpype/modules/default_modules/kitsu/utils/openpype.py @@ -75,7 +75,7 @@ def update_op_assets( else None ) if substitute_parent_item: - parent_zou_id = substitute_parent_item["id"] + parent_zou_id = substitute_parent_item["parent_id"] else: parent_zou_id = ( item.get("parent_id") or item.get("episode_id") or item.get("source_id") From de153a0450d9cbdcf9229775fa8fa9afa94eef79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 24 Feb 2022 15:53:03 +0100 Subject: [PATCH 0139/1227] Listen tasks, clean code --- .../default_modules/kitsu/kitsu_module.py | 1 - .../default_modules/kitsu/listeners.py | 55 ++++++++++++++++++- .../default_modules/kitsu/utils/openpype.py | 2 +- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index 0bdd4b12e6..10cbe76db2 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -9,7 +9,6 @@ in global space here until are required or used. import click import os import re -from typing import Dict, List import gazu from gazu.asset import all_assets_for_project, all_asset_types, new_asset diff --git a/openpype/modules/default_modules/kitsu/listeners.py b/openpype/modules/default_modules/kitsu/listeners.py index bb217452df..16c4e0e69e 100644 --- a/openpype/modules/default_modules/kitsu/listeners.py +++ b/openpype/modules/default_modules/kitsu/listeners.py @@ -1,9 +1,6 @@ -from turtle import update import gazu import os -from pymongo import DeleteOne, UpdateOne - from avalon.api import AvalonMongoDB from openpype.modules.default_modules.kitsu.utils.openpype import ( create_op_asset, @@ -259,4 +256,56 @@ def add_listeners(): gazu.events.add_listener(event_client, "shot:update", update_shot) gazu.events.add_listener(event_client, "shot:delete", delete_shot) + # == Task == + def new_task(data): + """Create new task into OP DB.""" + print("new", data) + # Get project entity + project_col = set_op_project(dbcon, data["project_id"]) + + # Get gazu entity + task = gazu.task.get_task(data["task_id"]) + + # Find asset doc + asset_doc = project_col.find_one( + {"type": "asset", "data.zou.id": task["entity"]["id"]} + ) + + # Update asset tasks with new one + asset_tasks = asset_doc["data"].get("tasks") + task_type_name = task["task_type"]["name"] + asset_tasks[task_type_name] = {"type": task_type_name, "zou": task} + project_col.update_one( + {"_id": asset_doc["_id"]}, {"$set": {"data.tasks": asset_tasks}} + ) + + def update_task(data): + """Update task into OP DB.""" + # TODO is it necessary? + pass + + def delete_task(data): + """Delete task of OP DB.""" + project_col = set_op_project(dbcon, data["project_id"]) + + # Find asset doc + asset_docs = [doc for doc in project_col.find({"type": "asset"})] + for doc in asset_docs: + # Match task + for name, task in doc["data"]["tasks"].items(): + if task.get("zou") and data["task_id"] == task["zou"]["id"]: + # Pop task + asset_tasks = doc["data"].get("tasks") + asset_tasks.pop(name) + + # Delete task in DB + project_col.update_one( + {"_id": doc["_id"]}, {"$set": {"data.tasks": asset_tasks}} + ) + return + + gazu.events.add_listener(event_client, "task:new", new_task) + gazu.events.add_listener(event_client, "task:update", update_task) + gazu.events.add_listener(event_client, "task:delete", delete_task) + gazu.events.run_client(event_client) diff --git a/openpype/modules/default_modules/kitsu/utils/openpype.py b/openpype/modules/default_modules/kitsu/utils/openpype.py index 809748fa78..edcf233ea8 100644 --- a/openpype/modules/default_modules/kitsu/utils/openpype.py +++ b/openpype/modules/default_modules/kitsu/utils/openpype.py @@ -1,7 +1,7 @@ from typing import Dict, List import gazu -from pymongo import DeleteOne, UpdateOne +from pymongo import UpdateOne from pymongo.collection import Collection from gazu.task import ( From 6cf38e1a35ba73a5df64969befb71728ffec4813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Fri, 25 Feb 2022 16:23:14 +0100 Subject: [PATCH 0140/1227] Publish comment to kitsu --- .../default_modules/kitsu/kitsu_module.py | 2 +- .../kitsu/plugins/publish/example_plugin.py | 40 +++++++++++++++++++ .../default_modules/kitsu/utils/openpype.py | 24 +++++------ 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index 10cbe76db2..f62a86f04d 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -408,7 +408,7 @@ def sync_openpype(): all_entities = [ e for e in all_assets + all_episodes + all_seqs + all_shots - if not e["data"].get("is_substitute") + if e["data"] and not e["data"].get("is_substitute") ] # Sync project. Create if doesn't exist diff --git a/openpype/modules/default_modules/kitsu/plugins/publish/example_plugin.py b/openpype/modules/default_modules/kitsu/plugins/publish/example_plugin.py index 61602f4e78..614d9ecc38 100644 --- a/openpype/modules/default_modules/kitsu/plugins/publish/example_plugin.py +++ b/openpype/modules/default_modules/kitsu/plugins/publish/example_plugin.py @@ -1,9 +1,49 @@ +import os + +import gazu + import pyblish.api +import debugpy + class CollectExampleAddon(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder + 0.4 label = "Collect Kitsu" def process(self, context): + debugpy.breakpoint() self.log.info("I'm in Kitsu's plugin!") + + +class IntegrateRig(pyblish.api.InstancePlugin): + """Copy files to an appropriate location where others may reach it""" + + order = pyblish.api.IntegratorOrder + families = ["model"] + + def process(self, instance): + print(instance.data["version"]) + + # Connect to server + gazu.client.set_host(os.environ["KITSU_SERVER"]) + + # Authenticate + gazu.log_in(os.environ["KITSU_LOGIN"], os.environ["KITSU_PWD"]) + + asset_data = instance.data["assetEntity"]["data"] + + # TODO Set local settings for login and password + + # Get task + task_type = gazu.task.get_task_type_by_name(instance.data["task"]) + entity_task = gazu.task.get_task_by_entity(asset_data["zou"], task_type) + + # Comment entity + gazu.task.add_comment( + entity_task, + entity_task["task_status_id"], + comment=f"Version {instance.data['version']} has been published!", + ) + + self.log.info("Copied successfully!") diff --git a/openpype/modules/default_modules/kitsu/utils/openpype.py b/openpype/modules/default_modules/kitsu/utils/openpype.py index edcf233ea8..518872a71c 100644 --- a/openpype/modules/default_modules/kitsu/utils/openpype.py +++ b/openpype/modules/default_modules/kitsu/utils/openpype.py @@ -134,11 +134,18 @@ def sync_project(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: print(f"Creating project '{project_name}'") project_doc = create_project(project_name, project_name, dbcon=dbcon) - print(f"Synchronizing {project_name}...") - # Project data and tasks - if not project["data"]: # Sentinel - project["data"] = {} + project_data = project["data"] or {} + + # Update data + project_data.update( + { + "code": project["code"], + "fps": project["fps"], + "resolutionWidth": project["resolution"].split("x")[0], + "resolutionHeight": project["resolution"].split("x")[1], + } + ) return UpdateOne( {"_id": project_doc["_id"]}, @@ -148,14 +155,7 @@ def sync_project(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: t["name"]: {"short_name": t.get("short_name", t["name"])} for t in gazu.task.all_task_types_for_project(project) }, - "data": project["data"].update( - { - "code": project["code"], - "fps": project["fps"], - "resolutionWidth": project["resolution"].split("x")[0], - "resolutionHeight": project["resolution"].split("x")[1], - } - ), + "data": project_data, } }, ) From cb01f8338c3b651897d626479a1a1bd306a7e05f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 1 Mar 2022 17:56:08 +0100 Subject: [PATCH 0141/1227] Sign in dialog, credentials as local setting and cleaning --- .../default_modules/kitsu/kitsu_module.py | 72 +++---- .../default_modules/kitsu/kitsu_widgets.py | 193 ++++++++++++++++++ .../{example_plugin.py => kitsu_plugin.py} | 8 +- .../schemas/project_schemas/main.json | 30 --- .../schemas/project_schemas/the_template.json | 30 --- .../kitsu/{ => utils}/listeners.py | 2 +- .../modules/default_modules/kitsu/widgets.py | 31 --- .../defaults/system_settings/modules.json | 4 +- .../module_settings/schema_kitsu.json | 10 - pyproject.toml | 3 +- 10 files changed, 224 insertions(+), 159 deletions(-) create mode 100644 openpype/modules/default_modules/kitsu/kitsu_widgets.py rename openpype/modules/default_modules/kitsu/plugins/publish/{example_plugin.py => kitsu_plugin.py} (85%) delete mode 100644 openpype/modules/default_modules/kitsu/settings/schemas/project_schemas/main.json delete mode 100644 openpype/modules/default_modules/kitsu/settings/schemas/project_schemas/the_template.json rename openpype/modules/default_modules/kitsu/{ => utils}/listeners.py (99%) delete mode 100644 openpype/modules/default_modules/kitsu/widgets.py diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/default_modules/kitsu/kitsu_module.py index f62a86f04d..51ab8aaa42 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/default_modules/kitsu/kitsu_module.py @@ -1,11 +1,5 @@ -"""Addon definition is located here. +"""Kitsu module.""" -Import of python packages that may not be available should not be imported -in global space here until are required or used. -- Qt related imports -- imports of Python 3 packages - - we still support Python 2 hosts where addon definition should available -""" import click import os import re @@ -35,20 +29,12 @@ from openpype.modules.default_modules.kitsu.utils.openpype import ( sync_project, update_op_assets, ) +from openpype.settings.lib import get_local_settings from openpype_interfaces import IPluginPaths, ITrayAction -from .listeners import add_listeners +from .utils.listeners import start_listeners -# Settings definition of this addon using `JsonFilesSettingsDef` -# - JsonFilesSettingsDef is prepared settings definition using json files -# to define settings and store default values class AddonSettingsDef(JsonFilesSettingsDef): - # This will add prefixes to every schema and template from `schemas` - # subfolder. - # - it is not required to fill the prefix but it is highly - # recommended as schemas and templates may have name clashes across - # multiple addons - # - it is also recommended that prefix has addon name in it schema_prefix = "kitsu" def get_settings_root_path(self): @@ -61,20 +47,15 @@ class AddonSettingsDef(JsonFilesSettingsDef): class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): - """This Addon has defined it's settings and interface. - - This example has system settings with an enabled option. And use - few other interfaces: - - `IPluginPaths` to define custom plugin paths - - `ITrayAction` to be shown in tray tool - """ + """Kitsu module class.""" label = "Kitsu" name = "kitsu" def initialize(self, settings): - """Initialization of addon.""" + """Initialization of module.""" module_settings = settings[self.name] + local_kitsu_settings = get_local_settings().get("kitsu", {}) # Enabled by settings self.enabled = module_settings.get("enabled", False) @@ -93,8 +74,8 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): self.server_url = kitsu_url # Set credentials - self.script_login = module_settings["script_login"] - self.script_pwd = module_settings["script_pwd"] + self.kitsu_login = local_kitsu_settings["login"] + self.kitsu_password = local_kitsu_settings["password"] # Prepare variables that can be used or set afterwards self._connected_modules = None @@ -113,8 +94,8 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): """Kitsu's global environments.""" return { "KITSU_SERVER": self.server_url, - "KITSU_LOGIN": self.script_login, - "KITSU_PWD": self.script_pwd, + "KITSU_LOGIN": self.kitsu_login, + "KITSU_PWD": self.kitsu_password, } def _create_dialog(self): @@ -122,9 +103,9 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): if self._dialog is not None: return - from .widgets import MyExampleDialog + from .kitsu_widgets import PasswordDialog - self._dialog = MyExampleDialog() + self._dialog = PasswordDialog() def show_dialog(self): """Show dialog with connected modules. @@ -376,7 +357,10 @@ def sync_zou(): @cli_main.command() -def sync_openpype(): +@click.option( + "-l", "--listen", is_flag=True, help="Listen Kitsu server after synchronization." +) +def sync_openpype(listen: bool): """Synchronize openpype database from Zou sever database.""" # Connect to server @@ -471,27 +455,23 @@ def sync_openpype(): dbcon.uninstall() + # Run listening + if listen: + start_listeners() + @cli_main.command() def listen(): - """Show ExampleAddon dialog. - - We don't have access to addon directly through cli so we have to create - it again. - """ - add_listeners() + """Listen to Kitsu server.""" + start_listeners() @cli_main.command() -def show_dialog(): - """Show ExampleAddon dialog. - - We don't have access to addon directly through cli so we have to create - it again. - """ +def sign_in(): + """Show credentials dialog.""" from openpype.tools.utils.lib import qt_app_context manager = ModulesManager() - example_addon = manager.modules_by_name[KitsuModule.name] + kitsu_addon = manager.modules_by_name[KitsuModule.name] with qt_app_context(): - example_addon.show_dialog() + kitsu_addon.show_dialog() diff --git a/openpype/modules/default_modules/kitsu/kitsu_widgets.py b/openpype/modules/default_modules/kitsu/kitsu_widgets.py new file mode 100644 index 0000000000..6bd436f460 --- /dev/null +++ b/openpype/modules/default_modules/kitsu/kitsu_widgets.py @@ -0,0 +1,193 @@ +import os + +import gazu +from Qt import QtWidgets, QtCore, QtGui + +from openpype import style +from openpype.resources import get_resource +from openpype.settings.lib import ( + get_local_settings, + get_system_settings, + save_local_settings, +) + +from openpype.widgets.password_dialog import PressHoverButton + + +class PasswordDialog(QtWidgets.QDialog): + """Stupidly simple dialog to compare password from general settings.""" + + finished = QtCore.Signal(bool) + + def __init__(self, parent=None): + super(PasswordDialog, self).__init__(parent) + + self.setWindowTitle("Kitsu Credentials") + self.resize(300, 120) + + system_settings = get_system_settings() + kitsu_settings = get_local_settings().get("kitsu", {}) + remembered = kitsu_settings.get("remember") + + self._final_result = None + self._connectable = bool( + system_settings["modules"].get("kitsu", {}).get("server") + ) + + # Server label + server_label = QtWidgets.QLabel( + f"Server: {system_settings['modules']['kitsu']['server'] if self._connectable else 'no server url set in Studio Settings...'}", + self, + ) + + # Login input + login_widget = QtWidgets.QWidget(self) + + login_label = QtWidgets.QLabel("Login:", login_widget) + + login_input = QtWidgets.QLineEdit( + login_widget, text=kitsu_settings.get("login") if remembered else None + ) + login_input.setPlaceholderText("Your Kitsu account login...") + + login_layout = QtWidgets.QHBoxLayout(login_widget) + login_layout.setContentsMargins(0, 0, 0, 0) + login_layout.addWidget(login_label) + login_layout.addWidget(login_input) + + # Password input + password_widget = QtWidgets.QWidget(self) + + password_label = QtWidgets.QLabel("Password:", password_widget) + + password_input = QtWidgets.QLineEdit( + password_widget, text=kitsu_settings.get("password") if remembered else None + ) + password_input.setPlaceholderText("Your password...") + password_input.setEchoMode(QtWidgets.QLineEdit.Password) + + show_password_icon_path = get_resource("icons", "eye.png") + show_password_icon = QtGui.QIcon(show_password_icon_path) + show_password_btn = PressHoverButton(password_widget) + show_password_btn.setObjectName("PasswordBtn") + show_password_btn.setIcon(show_password_icon) + show_password_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + + password_layout = QtWidgets.QHBoxLayout(password_widget) + password_layout.setContentsMargins(0, 0, 0, 0) + password_layout.addWidget(password_label) + password_layout.addWidget(password_input) + password_layout.addWidget(show_password_btn) + + # Message label + message_label = QtWidgets.QLabel("", self) + + # Buttons + buttons_widget = QtWidgets.QWidget(self) + + remember_checkbox = QtWidgets.QCheckBox("Remember", buttons_widget) + remember_checkbox.setObjectName("RememberCheckbox") + remember_checkbox.setChecked(remembered if remembered is not None else True) + + ok_btn = QtWidgets.QPushButton("Ok", buttons_widget) + cancel_btn = QtWidgets.QPushButton("Cancel", buttons_widget) + + buttons_layout = QtWidgets.QHBoxLayout(buttons_widget) + buttons_layout.setContentsMargins(0, 0, 0, 0) + buttons_layout.addWidget(remember_checkbox) + buttons_layout.addStretch(1) + buttons_layout.addWidget(ok_btn) + buttons_layout.addWidget(cancel_btn) + + # Main layout + layout = QtWidgets.QVBoxLayout(self) + layout.addSpacing(5) + layout.addWidget(server_label, 0) + layout.addSpacing(5) + layout.addWidget(login_widget, 0) + layout.addWidget(password_widget, 0) + layout.addWidget(message_label, 0) + layout.addStretch(1) + layout.addWidget(buttons_widget, 0) + + ok_btn.clicked.connect(self._on_ok_click) + cancel_btn.clicked.connect(self._on_cancel_click) + show_password_btn.change_state.connect(self._on_show_password) + + self.login_input = login_input + self.password_input = password_input + self.remember_checkbox = remember_checkbox + self.message_label = message_label + + self.setStyleSheet(style.load_stylesheet()) + + def result(self): + return self._final_result + + def keyPressEvent(self, event): + if event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter): + self._on_ok_click() + return event.accept() + super(PasswordDialog, self).keyPressEvent(event) + + def closeEvent(self, event): + super(PasswordDialog, self).closeEvent(event) + self.finished.emit(self.result()) + + def _on_ok_click(self): + # Check if is connectable + if not self._connectable: + self.message_label.setText("Please set server url in Studio Settings!") + return + + # Collect values + login_value = self.login_input.text() + pwd_value = self.password_input.text() + remember = self.remember_checkbox.isChecked() + + # Connect to server + gazu.client.set_host(os.environ["KITSU_SERVER"]) + + # Authenticate + gazu.log_in(login_value, pwd_value) + + # Set logging-in env vars + os.environ["KITSU_LOGIN"] = login_value + os.environ["KITSU_PWD"] = pwd_value + + # Get settings + local_settings = get_local_settings() + local_settings.setdefault("kitsu", {}) + + # Remember password cases + if remember: + # Set local settings + local_settings["kitsu"]["login"] = login_value + local_settings["kitsu"]["password"] = pwd_value + else: + # Clear local settings + local_settings["kitsu"]["login"] = None + local_settings["kitsu"]["password"] = None + + # Clear input fields + self.login_input.clear() + self.password_input.clear() + + # Keep 'remember' parameter + local_settings["kitsu"]["remember"] = remember + + # Save settings + save_local_settings(local_settings) + + self._final_result = True + self.close() + + def _on_show_password(self, show_password): + if show_password: + echo_mode = QtWidgets.QLineEdit.Normal + else: + echo_mode = QtWidgets.QLineEdit.Password + self.password_input.setEchoMode(echo_mode) + + def _on_cancel_click(self): + self.close() diff --git a/openpype/modules/default_modules/kitsu/plugins/publish/example_plugin.py b/openpype/modules/default_modules/kitsu/plugins/publish/kitsu_plugin.py similarity index 85% rename from openpype/modules/default_modules/kitsu/plugins/publish/example_plugin.py rename to openpype/modules/default_modules/kitsu/plugins/publish/kitsu_plugin.py index 614d9ecc38..86ba40a5f4 100644 --- a/openpype/modules/default_modules/kitsu/plugins/publish/example_plugin.py +++ b/openpype/modules/default_modules/kitsu/plugins/publish/kitsu_plugin.py @@ -4,15 +4,12 @@ import gazu import pyblish.api -import debugpy - class CollectExampleAddon(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder + 0.4 label = "Collect Kitsu" def process(self, context): - debugpy.breakpoint() self.log.info("I'm in Kitsu's plugin!") @@ -23,7 +20,6 @@ class IntegrateRig(pyblish.api.InstancePlugin): families = ["model"] def process(self, instance): - print(instance.data["version"]) # Connect to server gazu.client.set_host(os.environ["KITSU_SERVER"]) @@ -33,8 +29,6 @@ class IntegrateRig(pyblish.api.InstancePlugin): asset_data = instance.data["assetEntity"]["data"] - # TODO Set local settings for login and password - # Get task task_type = gazu.task.get_task_type_by_name(instance.data["task"]) entity_task = gazu.task.get_task_by_entity(asset_data["zou"], task_type) @@ -46,4 +40,4 @@ class IntegrateRig(pyblish.api.InstancePlugin): comment=f"Version {instance.data['version']} has been published!", ) - self.log.info("Copied successfully!") + self.log.info("Version published to Kitsu successfully!") diff --git a/openpype/modules/default_modules/kitsu/settings/schemas/project_schemas/main.json b/openpype/modules/default_modules/kitsu/settings/schemas/project_schemas/main.json deleted file mode 100644 index 82e58ce9ab..0000000000 --- a/openpype/modules/default_modules/kitsu/settings/schemas/project_schemas/main.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "type": "dict", - "key": "kitsu", - "label": " Kitsu", - "collapsible": true, - "children": [ - { - "type": "number", - "key": "number", - "label": "This is your lucky number:", - "minimum": 7, - "maximum": 7, - "decimals": 0 - }, - { - "type": "template", - "name": "kitsu/the_template", - "template_data": [ - { - "name": "color_1", - "label": "Color 1" - }, - { - "name": "color_2", - "label": "Color 2" - } - ] - } - ] -} diff --git a/openpype/modules/default_modules/kitsu/settings/schemas/project_schemas/the_template.json b/openpype/modules/default_modules/kitsu/settings/schemas/project_schemas/the_template.json deleted file mode 100644 index af8fd9dae4..0000000000 --- a/openpype/modules/default_modules/kitsu/settings/schemas/project_schemas/the_template.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "type": "list-strict", - "key": "{name}", - "label": "{label}", - "object_types": [ - { - "label": "Red", - "type": "number", - "minimum": 0, - "maximum": 1, - "decimal": 3 - }, - { - "label": "Green", - "type": "number", - "minimum": 0, - "maximum": 1, - "decimal": 3 - }, - { - "label": "Blue", - "type": "number", - "minimum": 0, - "maximum": 1, - "decimal": 3 - } - ] - } -] diff --git a/openpype/modules/default_modules/kitsu/listeners.py b/openpype/modules/default_modules/kitsu/utils/listeners.py similarity index 99% rename from openpype/modules/default_modules/kitsu/listeners.py rename to openpype/modules/default_modules/kitsu/utils/listeners.py index 16c4e0e69e..c99870fa36 100644 --- a/openpype/modules/default_modules/kitsu/listeners.py +++ b/openpype/modules/default_modules/kitsu/utils/listeners.py @@ -2,7 +2,7 @@ import gazu import os from avalon.api import AvalonMongoDB -from openpype.modules.default_modules.kitsu.utils.openpype import ( +from .openpype import ( create_op_asset, set_op_project, sync_project, diff --git a/openpype/modules/default_modules/kitsu/widgets.py b/openpype/modules/default_modules/kitsu/widgets.py deleted file mode 100644 index de232113fe..0000000000 --- a/openpype/modules/default_modules/kitsu/widgets.py +++ /dev/null @@ -1,31 +0,0 @@ -from Qt import QtWidgets - -from openpype.style import load_stylesheet - - -class MyExampleDialog(QtWidgets.QDialog): - def __init__(self, parent=None): - super(MyExampleDialog, self).__init__(parent) - - self.setWindowTitle("Connected modules") - - msg = "This is example dialog of Kitsu." - label_widget = QtWidgets.QLabel(msg, self) - - ok_btn = QtWidgets.QPushButton("OK", self) - btns_layout = QtWidgets.QHBoxLayout() - btns_layout.addStretch(1) - btns_layout.addWidget(ok_btn) - - layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(label_widget) - layout.addLayout(btns_layout) - - ok_btn.clicked.connect(self._on_ok_clicked) - - self._label_widget = label_widget - - self.setStyleSheet(load_stylesheet()) - - def _on_ok_clicked(self): - self.done(1) diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index ddb2edc360..537e287366 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -139,9 +139,7 @@ }, "kitsu": { "enabled": false, - "server": "", - "script_login": "", - "script_pwd": "" + "server": "" }, "timers_manager": { "enabled": true, diff --git a/openpype/settings/entities/schemas/system_schema/module_settings/schema_kitsu.json b/openpype/settings/entities/schemas/system_schema/module_settings/schema_kitsu.json index ae2b52df0d..15a2ccc58d 100644 --- a/openpype/settings/entities/schemas/system_schema/module_settings/schema_kitsu.json +++ b/openpype/settings/entities/schemas/system_schema/module_settings/schema_kitsu.json @@ -16,16 +16,6 @@ "key": "server", "label": "Server" }, - { - "type": "text", - "key": "script_login", - "label": "Script Login" - }, - { - "type": "text", - "key": "script_pwd", - "label": "Script Password" - }, { "type": "splitter" } diff --git a/pyproject.toml b/pyproject.toml index f32e385e80..93caa5ca70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ clique = "1.6.*" Click = "^7" dnspython = "^2.1.0" ftrack-python-api = "2.0.*" +gazu = "^0.8" google-api-python-client = "^1.12.8" # sync server google support (should be separate?) jsonschema = "^2.6.0" keyring = "^22.0.1" @@ -64,7 +65,7 @@ jinxed = [ python3-xlib = { version="*", markers = "sys_platform == 'linux'"} enlighten = "^1.9.0" slack-sdk = "^3.6.0" -requests = "2.25.1" +requests = "^2.25.1" pysftp = "^0.2.9" dropbox = "^11.20.0" From 6b3987708711e72d984b39096efec9a0421ffd0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 1 Mar 2022 18:11:42 +0100 Subject: [PATCH 0142/1227] Moved to modules --- openpype/modules/{default_modules => }/kitsu/__init__.py | 0 openpype/modules/{default_modules => }/kitsu/kitsu_module.py | 2 +- openpype/modules/{default_modules => }/kitsu/kitsu_widgets.py | 0 .../kitsu/plugins/publish/kitsu_plugin.py | 0 openpype/modules/{default_modules => }/kitsu/utils/__init__.py | 0 .../modules/{default_modules => }/kitsu/utils/listeners.py | 3 ++- openpype/modules/{default_modules => }/kitsu/utils/openpype.py | 0 7 files changed, 3 insertions(+), 2 deletions(-) rename openpype/modules/{default_modules => }/kitsu/__init__.py (100%) rename openpype/modules/{default_modules => }/kitsu/kitsu_module.py (99%) rename openpype/modules/{default_modules => }/kitsu/kitsu_widgets.py (100%) rename openpype/modules/{default_modules => }/kitsu/plugins/publish/kitsu_plugin.py (100%) rename openpype/modules/{default_modules => }/kitsu/utils/__init__.py (100%) rename openpype/modules/{default_modules => }/kitsu/utils/listeners.py (99%) rename openpype/modules/{default_modules => }/kitsu/utils/openpype.py (100%) diff --git a/openpype/modules/default_modules/kitsu/__init__.py b/openpype/modules/kitsu/__init__.py similarity index 100% rename from openpype/modules/default_modules/kitsu/__init__.py rename to openpype/modules/kitsu/__init__.py diff --git a/openpype/modules/default_modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py similarity index 99% rename from openpype/modules/default_modules/kitsu/kitsu_module.py rename to openpype/modules/kitsu/kitsu_module.py index 51ab8aaa42..a17b509047 100644 --- a/openpype/modules/default_modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -24,7 +24,7 @@ from pymongo import DeleteOne, UpdateOne from avalon.api import AvalonMongoDB from openpype.api import get_project_settings from openpype.modules import JsonFilesSettingsDef, OpenPypeModule, ModulesManager -from openpype.modules.default_modules.kitsu.utils.openpype import ( +from openpype.modules.kitsu.utils.openpype import ( create_op_asset, sync_project, update_op_assets, diff --git a/openpype/modules/default_modules/kitsu/kitsu_widgets.py b/openpype/modules/kitsu/kitsu_widgets.py similarity index 100% rename from openpype/modules/default_modules/kitsu/kitsu_widgets.py rename to openpype/modules/kitsu/kitsu_widgets.py diff --git a/openpype/modules/default_modules/kitsu/plugins/publish/kitsu_plugin.py b/openpype/modules/kitsu/plugins/publish/kitsu_plugin.py similarity index 100% rename from openpype/modules/default_modules/kitsu/plugins/publish/kitsu_plugin.py rename to openpype/modules/kitsu/plugins/publish/kitsu_plugin.py diff --git a/openpype/modules/default_modules/kitsu/utils/__init__.py b/openpype/modules/kitsu/utils/__init__.py similarity index 100% rename from openpype/modules/default_modules/kitsu/utils/__init__.py rename to openpype/modules/kitsu/utils/__init__.py diff --git a/openpype/modules/default_modules/kitsu/utils/listeners.py b/openpype/modules/kitsu/utils/listeners.py similarity index 99% rename from openpype/modules/default_modules/kitsu/utils/listeners.py rename to openpype/modules/kitsu/utils/listeners.py index c99870fa36..961aa1691b 100644 --- a/openpype/modules/default_modules/kitsu/utils/listeners.py +++ b/openpype/modules/kitsu/utils/listeners.py @@ -10,7 +10,8 @@ from .openpype import ( ) -def add_listeners(): +def start_listeners(): + """Start listeners to keep OpenPype up-to-date with Kitsu.""" # Connect to server gazu.client.set_host(os.environ["KITSU_SERVER"]) diff --git a/openpype/modules/default_modules/kitsu/utils/openpype.py b/openpype/modules/kitsu/utils/openpype.py similarity index 100% rename from openpype/modules/default_modules/kitsu/utils/openpype.py rename to openpype/modules/kitsu/utils/openpype.py From a33b3d3b5cdfa37204da8570d175997be3544001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 2 Mar 2022 09:30:33 +0100 Subject: [PATCH 0143/1227] Remove AddonSettingsDef --- openpype/modules/kitsu/__init__.py | 4 ++-- openpype/modules/kitsu/kitsu_module.py | 12 ------------ 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/openpype/modules/kitsu/__init__.py b/openpype/modules/kitsu/__init__.py index dc7c2dad50..6cb62bbb15 100644 --- a/openpype/modules/kitsu/__init__.py +++ b/openpype/modules/kitsu/__init__.py @@ -4,6 +4,6 @@ If addon class or settings definition won't be here their definition won't be found by OpenPype discovery. """ -from .kitsu_module import AddonSettingsDef, KitsuModule +from .kitsu_module import KitsuModule -__all__ = ("AddonSettingsDef", "KitsuModule") +__all__ = "KitsuModule" diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index a17b509047..9efcac9714 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -34,18 +34,6 @@ from openpype_interfaces import IPluginPaths, ITrayAction from .utils.listeners import start_listeners -class AddonSettingsDef(JsonFilesSettingsDef): - schema_prefix = "kitsu" - - def get_settings_root_path(self): - """Implemented abstract class of JsonFilesSettingsDef. - - Return directory path where json files defying addon settings are - located. - """ - return os.path.join(os.path.dirname(os.path.abspath(__file__)), "settings") - - class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): """Kitsu module class.""" From 4907ce1362602efef2692253d9b93fbc79d2e522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 2 Mar 2022 09:31:02 +0100 Subject: [PATCH 0144/1227] Moved up module 'base' changes --- openpype/modules/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 0dd512ee8b..d77189be6c 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -860,6 +860,7 @@ class TrayModulesManager(ModulesManager): modules_menu_order = ( "user", "ftrack", + "kitsu", "muster", "launcher_tool", "avalon", From a15c4d2895a5eaf433ec9d3912200a271cac16a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 2 Mar 2022 09:48:39 +0100 Subject: [PATCH 0145/1227] import gazu only at start --- openpype/modules/kitsu/kitsu_module.py | 63 ++++++++++------------- openpype/modules/kitsu/utils/listeners.py | 3 +- openpype/modules/kitsu/utils/openpype.py | 13 ++--- 3 files changed, 35 insertions(+), 44 deletions(-) diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index 9efcac9714..502ed8ff96 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -4,34 +4,19 @@ import click import os import re -import gazu -from gazu.asset import all_assets_for_project, all_asset_types, new_asset -from gazu.shot import ( - all_episodes_for_project, - all_sequences_for_project, - all_shots_for_project, - new_episode, - new_sequence, - new_shot, -) -from gazu.task import ( - all_task_types, - new_task, - new_task_type, -) from pymongo import DeleteOne, UpdateOne from avalon.api import AvalonMongoDB from openpype.api import get_project_settings -from openpype.modules import JsonFilesSettingsDef, OpenPypeModule, ModulesManager -from openpype.modules.kitsu.utils.openpype import ( +from openpype.modules import OpenPypeModule, ModulesManager +from openpype.settings.lib import get_local_settings +from openpype_interfaces import IPluginPaths, ITrayAction +from .utils.listeners import start_listeners +from .utils.openpype import ( create_op_asset, sync_project, update_op_assets, ) -from openpype.settings.lib import get_local_settings -from openpype_interfaces import IPluginPaths, ITrayAction -from .utils.listeners import start_listeners class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): @@ -137,6 +122,7 @@ def cli_main(): @cli_main.command() def sync_zou(): """Synchronize Zou server database (Kitsu backend) with openpype database.""" + import gazu # Connect to server gazu.client.set_host(os.environ["KITSU_SERVER"]) @@ -178,11 +164,11 @@ def sync_zou(): gazu.project.update_project_data(zou_project, data=op_project["data"]) gazu.project.update_project(zou_project) - asset_types = all_asset_types() - all_assets = all_assets_for_project(zou_project) - all_episodes = all_episodes_for_project(zou_project) - all_seqs = all_sequences_for_project(zou_project) - all_shots = all_shots_for_project(zou_project) + asset_types = gazu.asset.all_asset_types() + all_assets = gazu.asset.all_assets_for_project(zou_project) + all_episodes = gazu.shot.all_episodes_for_project(zou_project) + all_seqs = gazu.shot.all_sequences_for_project(zou_project) + all_shots = gazu.shot.all_shots_for_project(zou_project) all_entities_ids = { e["id"] for e in all_episodes + all_seqs + all_shots + all_assets } @@ -217,7 +203,9 @@ def sync_zou(): # Match asset type by it's name match = regex_ep.match(doc["name"]) if not match: # Asset - new_entity = new_asset(zou_project, asset_types[0], doc["name"]) + new_entity = gazu.asset.new_asset( + zou_project, asset_types[0], doc["name"] + ) # Match case in shot Collection: :param dbcon: Connection to DB. :param project_id: Project zou ID """ + import gazu + project = gazu.project.get_project(project_id) project_name = project["name"] dbcon.Session["AVALON_PROJECT"] = project_name @@ -49,6 +45,11 @@ def update_op_assets( :param asset_doc_ids: Dicts of [{zou_id: asset_doc}, ...] :return: List of (doc_id, update_dict) tuples """ + from gazu.task import ( + all_tasks_for_asset, + all_tasks_for_shot, + ) + assets_with_update = [] for item in entities_list: # Update asset From 44100b0da7d30bc34af0635b5e577e2a6df03c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 2 Mar 2022 09:52:26 +0100 Subject: [PATCH 0146/1227] line length to 79 --- openpype/modules/kitsu/kitsu_module.py | 33 ++++++++++++++----- openpype/modules/kitsu/kitsu_widgets.py | 14 +++++--- .../kitsu/plugins/publish/kitsu_plugin.py | 4 ++- openpype/modules/kitsu/utils/listeners.py | 29 +++++++++++----- openpype/modules/kitsu/utils/openpype.py | 7 ++-- 5 files changed, 63 insertions(+), 24 deletions(-) diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index 502ed8ff96..a7b3b17eb5 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -42,7 +42,9 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): # Check for "/api" url validity if not kitsu_url.endswith("api"): - kitsu_url = f"{kitsu_url}{'' if kitsu_url.endswith('/') else '/'}api" + kitsu_url = ( + f"{kitsu_url}{'' if kitsu_url.endswith('/') else '/'}api" + ) self.server_url = kitsu_url @@ -161,7 +163,9 @@ def sync_zou(): "resolution": f"{op_project['data']['resolutionWidth']}x{op_project['data']['resolutionHeight']}", } ) - gazu.project.update_project_data(zou_project, data=op_project["data"]) + gazu.project.update_project_data( + zou_project, data=op_project["data"] + ) gazu.project.update_project(zou_project) asset_types = gazu.asset.all_asset_types() @@ -227,7 +231,9 @@ def sync_zou(): # Create new sequence and set it as substitute created_sequence = gazu.shot.new_sequence( - zou_project, substitute_sequence_name, episode=zou_parent_id + zou_project, + substitute_sequence_name, + episode=zou_parent_id, ) gazu.shot.update_sequence_data( created_sequence, {"is_substitute": True} @@ -244,13 +250,16 @@ def sync_zou(): doc["name"], frame_in=doc["data"]["frameStart"], frame_out=doc["data"]["frameEnd"], - nb_frames=doc["data"]["frameEnd"] - doc["data"]["frameStart"], + nb_frames=doc["data"]["frameEnd"] + - doc["data"]["frameStart"], ) elif match.group(2): # Sequence parent_doc = asset_docs[visual_parent_id] new_entity = gazu.shot.new_sequence( - zou_project, doc["name"], episode=parent_doc["data"]["zou"]["id"] + zou_project, + doc["name"], + episode=parent_doc["data"]["zou"]["id"], ) elif match.group(3): # Episode @@ -293,7 +302,10 @@ def sync_zou(): if frame_in or frame_out: entity_data.update( { - "data": {"frame_in": frame_in, "frame_out": frame_out}, + "data": { + "frame_in": frame_in, + "frame_out": frame_out, + }, "nb_frames": frame_out - frame_in, } ) @@ -334,7 +346,10 @@ def sync_zou(): @cli_main.command() @click.option( - "-l", "--listen", is_flag=True, help="Listen Kitsu server after synchronization." + "-l", + "--listen", + is_flag=True, + help="Listen Kitsu server after synchronization.", ) def sync_openpype(listen: bool): """Synchronize openpype database from Zou sever database.""" @@ -410,7 +425,9 @@ def sync_openpype(listen: bool): bulk_writes.extend( [ UpdateOne({"_id": id}, update) - for id, update in update_op_assets(all_entities, zou_ids_and_asset_docs) + for id, update in update_op_assets( + all_entities, zou_ids_and_asset_docs + ) ] ) diff --git a/openpype/modules/kitsu/kitsu_widgets.py b/openpype/modules/kitsu/kitsu_widgets.py index 6bd436f460..1a32182795 100644 --- a/openpype/modules/kitsu/kitsu_widgets.py +++ b/openpype/modules/kitsu/kitsu_widgets.py @@ -46,7 +46,8 @@ class PasswordDialog(QtWidgets.QDialog): login_label = QtWidgets.QLabel("Login:", login_widget) login_input = QtWidgets.QLineEdit( - login_widget, text=kitsu_settings.get("login") if remembered else None + login_widget, + text=kitsu_settings.get("login") if remembered else None, ) login_input.setPlaceholderText("Your Kitsu account login...") @@ -61,7 +62,8 @@ class PasswordDialog(QtWidgets.QDialog): password_label = QtWidgets.QLabel("Password:", password_widget) password_input = QtWidgets.QLineEdit( - password_widget, text=kitsu_settings.get("password") if remembered else None + password_widget, + text=kitsu_settings.get("password") if remembered else None, ) password_input.setPlaceholderText("Your password...") password_input.setEchoMode(QtWidgets.QLineEdit.Password) @@ -87,7 +89,9 @@ class PasswordDialog(QtWidgets.QDialog): remember_checkbox = QtWidgets.QCheckBox("Remember", buttons_widget) remember_checkbox.setObjectName("RememberCheckbox") - remember_checkbox.setChecked(remembered if remembered is not None else True) + remember_checkbox.setChecked( + remembered if remembered is not None else True + ) ok_btn = QtWidgets.QPushButton("Ok", buttons_widget) cancel_btn = QtWidgets.QPushButton("Cancel", buttons_widget) @@ -137,7 +141,9 @@ class PasswordDialog(QtWidgets.QDialog): def _on_ok_click(self): # Check if is connectable if not self._connectable: - self.message_label.setText("Please set server url in Studio Settings!") + self.message_label.setText( + "Please set server url in Studio Settings!" + ) return # Collect values diff --git a/openpype/modules/kitsu/plugins/publish/kitsu_plugin.py b/openpype/modules/kitsu/plugins/publish/kitsu_plugin.py index 86ba40a5f4..24f1e4e80c 100644 --- a/openpype/modules/kitsu/plugins/publish/kitsu_plugin.py +++ b/openpype/modules/kitsu/plugins/publish/kitsu_plugin.py @@ -31,7 +31,9 @@ class IntegrateRig(pyblish.api.InstancePlugin): # Get task task_type = gazu.task.get_task_type_by_name(instance.data["task"]) - entity_task = gazu.task.get_task_by_entity(asset_data["zou"], task_type) + entity_task = gazu.task.get_task_by_entity( + asset_data["zou"], task_type + ) # Comment entity gazu.task.add_comment( diff --git a/openpype/modules/kitsu/utils/listeners.py b/openpype/modules/kitsu/utils/listeners.py index 18f67b13e3..3768b4e8e6 100644 --- a/openpype/modules/kitsu/utils/listeners.py +++ b/openpype/modules/kitsu/utils/listeners.py @@ -95,9 +95,9 @@ def start_listeners(): zou_ids_and_asset_docs[asset["project_id"]] = project_doc # Update - asset_doc_id, asset_update = update_op_assets([asset], zou_ids_and_asset_docs)[ - 0 - ] + asset_doc_id, asset_update = update_op_assets( + [asset], zou_ids_and_asset_docs + )[0] project_col.update_one({"_id": asset_doc_id}, asset_update) def delete_asset(data): @@ -105,7 +105,9 @@ def start_listeners(): project_col = set_op_project(dbcon, data["project_id"]) # Delete - project_col.delete_one({"type": "asset", "data.zou.id": data["asset_id"]}) + project_col.delete_one( + {"type": "asset", "data.zou.id": data["asset_id"]} + ) gazu.events.add_listener(event_client, "asset:new", new_asset) gazu.events.add_listener(event_client, "asset:update", update_asset) @@ -155,7 +157,9 @@ def start_listeners(): print("delete episode") # TODO check bugfix # Delete - project_col.delete_one({"type": "asset", "data.zou.id": data["episode_id"]}) + project_col.delete_one( + {"type": "asset", "data.zou.id": data["episode_id"]} + ) gazu.events.add_listener(event_client, "episode:new", new_episode) gazu.events.add_listener(event_client, "episode:update", update_episode) @@ -205,7 +209,9 @@ def start_listeners(): print("delete sequence") # TODO check bugfix # Delete - project_col.delete_one({"type": "asset", "data.zou.id": data["sequence_id"]}) + project_col.delete_one( + {"type": "asset", "data.zou.id": data["sequence_id"]} + ) gazu.events.add_listener(event_client, "sequence:new", new_sequence) gazu.events.add_listener(event_client, "sequence:update", update_sequence) @@ -244,7 +250,9 @@ def start_listeners(): zou_ids_and_asset_docs[shot["project_id"]] = project_doc # Update - asset_doc_id, asset_update = update_op_assets([shot], zou_ids_and_asset_docs)[0] + asset_doc_id, asset_update = update_op_assets( + [shot], zou_ids_and_asset_docs + )[0] project_col.update_one({"_id": asset_doc_id}, asset_update) def delete_shot(data): @@ -252,7 +260,9 @@ def start_listeners(): project_col = set_op_project(dbcon, data["project_id"]) # Delete - project_col.delete_one({"type": "asset", "data.zou.id": data["shot_id"]}) + project_col.delete_one( + {"type": "asset", "data.zou.id": data["shot_id"]} + ) gazu.events.add_listener(event_client, "shot:new", new_shot) gazu.events.add_listener(event_client, "shot:update", update_shot) @@ -302,7 +312,8 @@ def start_listeners(): # Delete task in DB project_col.update_one( - {"_id": doc["_id"]}, {"$set": {"data.tasks": asset_tasks}} + {"_id": doc["_id"]}, + {"$set": {"data.tasks": asset_tasks}}, ) return diff --git a/openpype/modules/kitsu/utils/openpype.py b/openpype/modules/kitsu/utils/openpype.py index 2443323893..8aabba6de0 100644 --- a/openpype/modules/kitsu/utils/openpype.py +++ b/openpype/modules/kitsu/utils/openpype.py @@ -65,7 +65,8 @@ def update_op_assets( tasks_list = all_tasks_for_shot(item) # TODO frame in and out item_data["tasks"] = { - t["task_type_name"]: {"type": t["task_type_name"]} for t in tasks_list + t["task_type_name"]: {"type": t["task_type_name"]} + for t in tasks_list } # Get zou parent id for correct hierarchy @@ -79,7 +80,9 @@ def update_op_assets( parent_zou_id = substitute_parent_item["parent_id"] else: parent_zou_id = ( - item.get("parent_id") or item.get("episode_id") or item.get("source_id") + item.get("parent_id") + or item.get("episode_id") + or item.get("source_id") ) # TODO check consistency # Visual parent for hierarchy From c26c2f09a8f271419e76cd7407f35b19d579d851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 2 Mar 2022 09:54:47 +0100 Subject: [PATCH 0147/1227] fix import --- openpype/modules/kitsu/utils/openpype.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/modules/kitsu/utils/openpype.py b/openpype/modules/kitsu/utils/openpype.py index 8aabba6de0..56c99effff 100644 --- a/openpype/modules/kitsu/utils/openpype.py +++ b/openpype/modules/kitsu/utils/openpype.py @@ -132,6 +132,8 @@ def sync_project(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: :param dbcon: DB to create project in :return: Update instance for the project """ + import gazu + project_name = project["name"] project_doc = dbcon.find_one({"type": "project"}) if not project_doc: From e8831947e6c2de902470e0fffdb69cad6429ce32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 2 Mar 2022 09:57:00 +0100 Subject: [PATCH 0148/1227] Fix __all__ --- openpype/modules/kitsu/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/__init__.py b/openpype/modules/kitsu/__init__.py index 6cb62bbb15..9737a054f6 100644 --- a/openpype/modules/kitsu/__init__.py +++ b/openpype/modules/kitsu/__init__.py @@ -6,4 +6,4 @@ be found by OpenPype discovery. from .kitsu_module import KitsuModule -__all__ = "KitsuModule" +__all__ = ("KitsuModule", ) From cde925c09b06130f6b4e1f36d993d21aba0fd7bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 2 Mar 2022 11:38:47 +0100 Subject: [PATCH 0149/1227] Use OpenPypeSecureRegistry for authentication --- openpype/modules/kitsu/__init__.py | 2 +- openpype/modules/kitsu/kitsu_module.py | 22 ++++++++++++++--- openpype/modules/kitsu/kitsu_widgets.py | 33 +++++++++++-------------- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/openpype/modules/kitsu/__init__.py b/openpype/modules/kitsu/__init__.py index 9737a054f6..9220cb1762 100644 --- a/openpype/modules/kitsu/__init__.py +++ b/openpype/modules/kitsu/__init__.py @@ -6,4 +6,4 @@ be found by OpenPype discovery. from .kitsu_module import KitsuModule -__all__ = ("KitsuModule", ) +__all__ = ("KitsuModule",) diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index a7b3b17eb5..ebfa0dbeea 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -8,6 +8,7 @@ from pymongo import DeleteOne, UpdateOne from avalon.api import AvalonMongoDB from openpype.api import get_project_settings +from openpype.lib.local_settings import OpenPypeSecureRegistry from openpype.modules import OpenPypeModule, ModulesManager from openpype.settings.lib import get_local_settings from openpype_interfaces import IPluginPaths, ITrayAction @@ -28,7 +29,9 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): def initialize(self, settings): """Initialization of module.""" module_settings = settings[self.name] - local_kitsu_settings = get_local_settings().get("kitsu", {}) + + # Get user registry + user_registry = OpenPypeSecureRegistry("kitsu_user") # Enabled by settings self.enabled = module_settings.get("enabled", False) @@ -49,8 +52,8 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): self.server_url = kitsu_url # Set credentials - self.kitsu_login = local_kitsu_settings["login"] - self.kitsu_password = local_kitsu_settings["password"] + self.kitsu_login = user_registry.get_item("login", None) + self.kitsu_password = user_registry.get_item("password", None) # Prepare variables that can be used or set afterwards self._connected_modules = None @@ -359,7 +362,13 @@ def sync_openpype(listen: bool): gazu.client.set_host(os.environ["KITSU_SERVER"]) # Authenticate - gazu.log_in(os.environ["KITSU_LOGIN"], os.environ["KITSU_PWD"]) + kitsu_login = os.environ.get("KITSU_LOGIN") + kitsu_pwd = os.environ.get("KITSU_PWD") + if not kitsu_login or not kitsu_pwd: # Sentinel to log-in + log_in_dialog() + return + + gazu.log_in(kitsu_login, kitsu_pwd) # Iterate projects dbcon = AvalonMongoDB() @@ -462,6 +471,11 @@ def listen(): @cli_main.command() def sign_in(): + """Sign-in command.""" + log_in_dialog() + + +def log_in_dialog(): """Show credentials dialog.""" from openpype.tools.utils.lib import qt_app_context diff --git a/openpype/modules/kitsu/kitsu_widgets.py b/openpype/modules/kitsu/kitsu_widgets.py index 1a32182795..1a48e6dbc0 100644 --- a/openpype/modules/kitsu/kitsu_widgets.py +++ b/openpype/modules/kitsu/kitsu_widgets.py @@ -4,11 +4,10 @@ import gazu from Qt import QtWidgets, QtCore, QtGui from openpype import style +from openpype.lib.local_settings import OpenPypeSecureRegistry from openpype.resources import get_resource from openpype.settings.lib import ( - get_local_settings, get_system_settings, - save_local_settings, ) from openpype.widgets.password_dialog import PressHoverButton @@ -26,8 +25,11 @@ class PasswordDialog(QtWidgets.QDialog): self.resize(300, 120) system_settings = get_system_settings() - kitsu_settings = get_local_settings().get("kitsu", {}) - remembered = kitsu_settings.get("remember") + user_registry = OpenPypeSecureRegistry("kitsu_user") + remembered = bool( + user_registry.get_item("login", None) + or user_registry.get_item("password", None) + ) self._final_result = None self._connectable = bool( @@ -47,7 +49,7 @@ class PasswordDialog(QtWidgets.QDialog): login_input = QtWidgets.QLineEdit( login_widget, - text=kitsu_settings.get("login") if remembered else None, + text=user_registry.get_item("login") if remembered else None, ) login_input.setPlaceholderText("Your Kitsu account login...") @@ -63,7 +65,7 @@ class PasswordDialog(QtWidgets.QDialog): password_input = QtWidgets.QLineEdit( password_widget, - text=kitsu_settings.get("password") if remembered else None, + text=user_registry.get_item("password") if remembered else None, ) password_input.setPlaceholderText("Your password...") password_input.setEchoMode(QtWidgets.QLineEdit.Password) @@ -161,30 +163,23 @@ class PasswordDialog(QtWidgets.QDialog): os.environ["KITSU_LOGIN"] = login_value os.environ["KITSU_PWD"] = pwd_value - # Get settings - local_settings = get_local_settings() - local_settings.setdefault("kitsu", {}) + # Get user registry + user_registry = OpenPypeSecureRegistry("kitsu_user") # Remember password cases if remember: # Set local settings - local_settings["kitsu"]["login"] = login_value - local_settings["kitsu"]["password"] = pwd_value + user_registry.set_item("login", login_value) + user_registry.set_item("password", pwd_value) else: # Clear local settings - local_settings["kitsu"]["login"] = None - local_settings["kitsu"]["password"] = None + user_registry.delete_item("login") + user_registry.delete_item("password") # Clear input fields self.login_input.clear() self.password_input.clear() - # Keep 'remember' parameter - local_settings["kitsu"]["remember"] = remember - - # Save settings - save_local_settings(local_settings) - self._final_result = True self.close() From c9ea0b3bb262484b43ebd5c1c5d649dcdaf75d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 2 Mar 2022 11:49:49 +0100 Subject: [PATCH 0150/1227] Line length max 79 --- openpype/modules/kitsu/kitsu_module.py | 28 +++++++++++++++++-------- openpype/modules/kitsu/kitsu_widgets.py | 7 ++++++- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index ebfa0dbeea..6a2e517832 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -10,7 +10,6 @@ from avalon.api import AvalonMongoDB from openpype.api import get_project_settings from openpype.lib.local_settings import OpenPypeSecureRegistry from openpype.modules import OpenPypeModule, ModulesManager -from openpype.settings.lib import get_local_settings from openpype_interfaces import IPluginPaths, ITrayAction from .utils.listeners import start_listeners from .utils.openpype import ( @@ -126,7 +125,7 @@ def cli_main(): @cli_main.command() def sync_zou(): - """Synchronize Zou server database (Kitsu backend) with openpype database.""" + """Synchronize Zou database (Kitsu backend) with openpype database.""" import gazu # Connect to server @@ -154,7 +153,9 @@ def sync_zou(): # Create project if zou_project is None: raise RuntimeError( - f"Project '{project_name}' doesn't exist in Zou database, please create it in Kitsu and add OpenPype user to it before running synchronization." + f"Project '{project_name}' doesn't exist in Zou database, " + "please create it in Kitsu and add OpenPype user to it before " + "running synchronization." ) # Update project settings and data @@ -163,7 +164,8 @@ def sync_zou(): { "code": op_project["data"]["code"], "fps": op_project["data"]["fps"], - "resolution": f"{op_project['data']['resolutionWidth']}x{op_project['data']['resolutionHeight']}", + "resolution": f"{op_project['data']['resolutionWidth']}" + f"x{op_project['data']['resolutionHeight']}", } ) gazu.project.update_project_data( @@ -213,7 +215,8 @@ def sync_zou(): new_entity = gazu.asset.new_asset( zou_project, asset_types[0], doc["name"] ) - # Match case in shot Date: Wed, 2 Mar 2022 12:11:05 +0100 Subject: [PATCH 0151/1227] Rename module to Kitsu Connect --- openpype/modules/kitsu/kitsu_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index 6a2e517832..d5e744ceb5 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -22,7 +22,7 @@ from .utils.openpype import ( class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): """Kitsu module class.""" - label = "Kitsu" + label = "Kitsu Connect" name = "kitsu" def initialize(self, settings): From c39bdee4330b1578cfe821d0506dbc7b59373621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 3 Mar 2022 15:50:35 +0100 Subject: [PATCH 0152/1227] Refactor for login system following recommendations. --- openpype/modules/kitsu/kitsu_module.py | 457 ++---------------- openpype/modules/kitsu/kitsu_widgets.py | 59 +-- .../kitsu/plugins/publish/kitsu_plugin.py | 2 +- openpype/modules/kitsu/utils/credentials.py | 92 ++++ .../utils/{listeners.py => sync_service.py} | 238 +++++---- .../{openpype.py => update_op_with_zou.py} | 153 +++++- .../modules/kitsu/utils/update_zou_with_op.py | 262 ++++++++++ 7 files changed, 717 insertions(+), 546 deletions(-) create mode 100644 openpype/modules/kitsu/utils/credentials.py rename openpype/modules/kitsu/utils/{listeners.py => sync_service.py} (54%) rename openpype/modules/kitsu/utils/{openpype.py => update_op_with_zou.py} (52%) create mode 100644 openpype/modules/kitsu/utils/update_zou_with_op.py diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index d5e744ceb5..dca6133e88 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -2,21 +2,9 @@ import click import os -import re -from pymongo import DeleteOne, UpdateOne - -from avalon.api import AvalonMongoDB -from openpype.api import get_project_settings -from openpype.lib.local_settings import OpenPypeSecureRegistry -from openpype.modules import OpenPypeModule, ModulesManager +from openpype.modules import OpenPypeModule from openpype_interfaces import IPluginPaths, ITrayAction -from .utils.listeners import start_listeners -from .utils.openpype import ( - create_op_asset, - sync_project, - update_op_assets, -) class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): @@ -29,9 +17,6 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): """Initialization of module.""" module_settings = settings[self.name] - # Get user registry - user_registry = OpenPypeSecureRegistry("kitsu_user") - # Enabled by settings self.enabled = module_settings.get("enabled", False) @@ -44,66 +29,58 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): # Check for "/api" url validity if not kitsu_url.endswith("api"): - kitsu_url = ( - f"{kitsu_url}{'' if kitsu_url.endswith('/') else '/'}api" + kitsu_url = "{}{}api".format( + kitsu_url, "" if kitsu_url.endswith("/") else "/" ) self.server_url = kitsu_url - # Set credentials - self.kitsu_login = user_registry.get_item("login", None) - self.kitsu_password = user_registry.get_item("password", None) - - # Prepare variables that can be used or set afterwards - self._connected_modules = None # UI which must not be created at this time self._dialog = None def tray_init(self): - """Implementation of abstract method for `ITrayAction`. - - We're definitely in tray tool so we can pre create dialog. - """ + """Tray init.""" self._create_dialog() + def tray_start(self): + """Tray start.""" + from .utils.credentials import ( + load_credentials, + validate_credentials, + set_credentials_envs, + ) + + username, password = load_credentials() + + # Check credentials, ask them if needed + if validate_credentials(username, password): + set_credentials_envs(username, password) + else: + self.show_dialog() + def get_global_environments(self): """Kitsu's global environments.""" - return { - "KITSU_SERVER": self.server_url, - "KITSU_LOGIN": self.kitsu_login, - "KITSU_PWD": self.kitsu_password, - } + return {"KITSU_SERVER": self.server_url} def _create_dialog(self): # Don't recreate dialog if already exists if self._dialog is not None: return - from .kitsu_widgets import PasswordDialog + from .kitsu_widgets import KitsuPasswordDialog - self._dialog = PasswordDialog() + self._dialog = KitsuPasswordDialog() def show_dialog(self): - """Show dialog with connected modules. + """Show dialog to log-in.""" - This can be called from anywhere but can also crash in headless mode. - There is no way to prevent addon to do invalid operations if he's - not handling them. - """ # Make sure dialog is created self._create_dialog() + # Show dialog self._dialog.open() - def get_connected_modules(self): - """Custom implementation of addon.""" - names = set() - if self._connected_modules is not None: - for module in self._connected_modules: - names.add(module.name) - return names - def on_action_trigger(self): """Implementation of abstract method for `ITrayAction`.""" self.show_dialog() @@ -124,372 +101,36 @@ def cli_main(): @cli_main.command() -def sync_zou(): - """Synchronize Zou database (Kitsu backend) with openpype database.""" - import gazu - - # Connect to server - gazu.client.set_host(os.environ["KITSU_SERVER"]) - - # Authenticate - gazu.log_in(os.environ["KITSU_LOGIN"], os.environ["KITSU_PWD"]) - - # Iterate projects - dbcon = AvalonMongoDB() - dbcon.install() - - op_projects = [p for p in dbcon.projects()] - bulk_writes = [] - for op_project in op_projects: - # Create project locally - # Try to find project document - project_name = op_project["name"] - dbcon.Session["AVALON_PROJECT"] = project_name - - # Get all entities from zou - print(f"Synchronizing {project_name}...") - zou_project = gazu.project.get_project_by_name(project_name) - - # Create project - if zou_project is None: - raise RuntimeError( - f"Project '{project_name}' doesn't exist in Zou database, " - "please create it in Kitsu and add OpenPype user to it before " - "running synchronization." - ) - - # Update project settings and data - if op_project["data"]: - zou_project.update( - { - "code": op_project["data"]["code"], - "fps": op_project["data"]["fps"], - "resolution": f"{op_project['data']['resolutionWidth']}" - f"x{op_project['data']['resolutionHeight']}", - } - ) - gazu.project.update_project_data( - zou_project, data=op_project["data"] - ) - gazu.project.update_project(zou_project) - - asset_types = gazu.asset.all_asset_types() - all_assets = gazu.asset.all_assets_for_project(zou_project) - all_episodes = gazu.shot.all_episodes_for_project(zou_project) - all_seqs = gazu.shot.all_sequences_for_project(zou_project) - all_shots = gazu.shot.all_shots_for_project(zou_project) - all_entities_ids = { - e["id"] for e in all_episodes + all_seqs + all_shots + all_assets - } - - # Query all assets of the local project - project_module_settings = get_project_settings(project_name)["kitsu"] - project_col = dbcon.database[project_name] - asset_docs = { - asset_doc["_id"]: asset_doc - for asset_doc in project_col.find({"type": "asset"}) - } - - # Create new assets - new_assets_docs = [ - doc - for doc in asset_docs.values() - if doc["data"].get("zou", {}).get("id") not in all_entities_ids - ] - naming_pattern = project_module_settings["entities_naming_pattern"] - regex_ep = re.compile( - r"(.*{}.*)|(.*{}.*)|(.*{}.*)".format( - naming_pattern["shot"].replace("#", ""), - naming_pattern["sequence"].replace("#", ""), - naming_pattern["episode"].replace("#", ""), - ), - re.IGNORECASE, - ) - for doc in new_assets_docs: - visual_parent_id = doc["data"]["visualParent"] - parent_substitutes = [] - - # Match asset type by it's name - match = regex_ep.match(doc["name"]) - if not match: # Asset - new_entity = gazu.asset.new_asset( - zou_project, asset_types[0], doc["name"] - ) - # Match case in shot bool: + """Validate credentials by trying to connect to Kitsu host URL. + + :param login: Kitsu Login + :param password: Kitsu Password + :param kitsu_url: Kitsu host URL + :return: Are credentials valid? + """ + # Connect to server + validate_host(kitsu_url) + + # Authenticate + try: + gazu.log_in(login, password) + except gazu.exception.AuthFailedException: + return False + + return True + + +def validate_host(kitsu_url: str) -> bool: + """Validate credentials by trying to connect to Kitsu host URL. + + :param kitsu_url: Kitsu host URL + :return: Is host valid? + """ + # Connect to server + gazu.set_host(kitsu_url) + + # Test host + if gazu.client.host_is_valid(): + return True + else: + raise gazu.exception.HostException(f"Host '{kitsu_url}' is invalid.") + + +def clear_credentials(): + """Clear credentials in Secure Registry.""" + # Get user registry + user_registry = OpenPypeSecureRegistry("kitsu_user") + + # Set local settings + user_registry.delete_item("login") + user_registry.delete_item("password") + + +def save_credentials(login: str, password: str): + """Save credentials in Secure Registry. + + :param login: Kitsu Login + :param password: Kitsu Password + """ + # Get user registry + user_registry = OpenPypeSecureRegistry("kitsu_user") + + # Set local settings + user_registry.set_item("login", login) + user_registry.set_item("password", password) + + +def load_credentials() -> Tuple[str, str]: + """Load registered credentials. + + :return: Login, Password + """ + # Get user registry + user_registry = OpenPypeSecureRegistry("kitsu_user") + + return user_registry.get_item("login", None), user_registry.get_item( + "password", None + ) + + +def set_credentials_envs(login: str, password: str): + """Set environment variables with Kitsu login and password. + + :param login: Kitsu Login + :param password: Kitsu Password + """ + os.environ["KITSU_LOGIN"] = login + os.environ["KITSU_PWD"] = password diff --git a/openpype/modules/kitsu/utils/listeners.py b/openpype/modules/kitsu/utils/sync_service.py similarity index 54% rename from openpype/modules/kitsu/utils/listeners.py rename to openpype/modules/kitsu/utils/sync_service.py index 3768b4e8e6..831673ec0d 100644 --- a/openpype/modules/kitsu/utils/listeners.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -1,72 +1,143 @@ import os +import gazu + from avalon.api import AvalonMongoDB -from .openpype import ( +from .credentials import load_credentials, validate_credentials +from .update_op_with_zou import ( create_op_asset, set_op_project, - sync_project, + write_project_to_op, update_op_assets, ) -def start_listeners(): - """Start listeners to keep OpenPype up-to-date with Kitsu.""" - import gazu +class Listener: + """Host Kitsu listener.""" - # Connect to server - gazu.client.set_host(os.environ["KITSU_SERVER"]) + def __init__(self, login, password): + """Create client and add listeners to events without starting it. - # Authenticate - gazu.log_in(os.environ["KITSU_LOGIN"], os.environ["KITSU_PWD"]) - gazu.set_event_host(os.environ["KITSU_SERVER"].replace("api", "socket.io")) - event_client = gazu.events.init() + Run `listener.start()` to actually start the service. - # Connect to DB - dbcon = AvalonMongoDB() - dbcon.install() + Args: + login (str): Kitsu user login + password (str): Kitsu user password + + Raises: + AuthFailedException: Wrong user login and/or password + """ + self.dbcon = AvalonMongoDB() + self.dbcon.install() + + gazu.client.set_host(os.environ["KITSU_SERVER"]) + + # Authenticate + if not validate_credentials(login, password): + raise gazu.exception.AuthFailedException( + f"Kitsu authentication failed for login: '{login}'..." + ) + + gazu.set_event_host( + os.environ["KITSU_SERVER"].replace("api", "socket.io") + ) + self.event_client = gazu.events.init() + + gazu.events.add_listener( + self.event_client, "project:new", self._new_project + ) + gazu.events.add_listener( + self.event_client, "project:update", self._update_project + ) + gazu.events.add_listener( + self.event_client, "project:delete", self._delete_project + ) + + gazu.events.add_listener( + self.event_client, "asset:new", self._new_asset + ) + gazu.events.add_listener( + self.event_client, "asset:update", self._update_asset + ) + gazu.events.add_listener( + self.event_client, "asset:delete", self._delete_asset + ) + + gazu.events.add_listener( + self.event_client, "episode:new", self._new_episode + ) + gazu.events.add_listener( + self.event_client, "episode:update", self._update_episode + ) + gazu.events.add_listener( + self.event_client, "episode:delete", self._delete_episode + ) + + gazu.events.add_listener( + self.event_client, "sequence:new", self._new_sequence + ) + gazu.events.add_listener( + self.event_client, "sequence:update", self._update_sequence + ) + gazu.events.add_listener( + self.event_client, "sequence:delete", self._delete_sequence + ) + + gazu.events.add_listener(self.event_client, "shot:new", self._new_shot) + gazu.events.add_listener( + self.event_client, "shot:update", self._update_shot + ) + gazu.events.add_listener( + self.event_client, "shot:delete", self._delete_shot + ) + + gazu.events.add_listener(self.event_client, "task:new", self._new_task) + gazu.events.add_listener( + self.event_client, "task:update", self._update_task + ) + gazu.events.add_listener( + self.event_client, "task:delete", self._delete_task + ) + + def start(self): + gazu.events.run_client(self.event_client) # == Project == - - def new_project(data): + def _new_project(self, data): """Create new project into OP DB.""" # Use update process to avoid duplicating code - update_project(data) + self._update_project(data) - def update_project(data): + def _update_project(self, data): """Update project into OP DB.""" # Get project entity project = gazu.project.get_project(data["project_id"]) project_name = project["name"] - dbcon.Session["AVALON_PROJECT"] = project_name - update_project = sync_project(project, dbcon) + update_project = write_project_to_op(project, self.dbcon) # Write into DB if update_project: - project_col = dbcon.database[project_name] + project_col = self.dbcon.database[project_name] project_col.bulk_write([update_project]) - def delete_project(data): + def _delete_project(self, data): """Delete project.""" # Get project entity print(data) # TODO check bugfix project = gazu.project.get_project(data["project_id"]) # Delete project collection - project_col = dbcon.database[project["name"]] + project_col = self.dbcon.database[project["name"]] project_col.drop() - gazu.events.add_listener(event_client, "project:new", new_project) - gazu.events.add_listener(event_client, "project:update", update_project) - gazu.events.add_listener(event_client, "project:delete", delete_project) - # == Asset == - def new_asset(data): + def _new_asset(self, data): """Create new asset into OP DB.""" # Get project entity - project_col = set_op_project(dbcon, data["project_id"]) + project_col = set_op_project(self.dbcon, data["project_id"]) # Get gazu entity asset = gazu.asset.get_asset(data["asset_id"]) @@ -75,12 +146,12 @@ def start_listeners(): project_col.insert_one(create_op_asset(asset)) # Update - update_asset(data) + self._update_asset(data) - def update_asset(data): + def _update_asset(self, data): """Update asset into OP DB.""" - project_col = set_op_project(dbcon, data["project_id"]) - project_doc = dbcon.find_one({"type": "project"}) + project_col = set_op_project(self.dbcon, data["project_id"]) + project_doc = self.dbcon.find_one({"type": "project"}) # Get gazu entity asset = gazu.asset.get_asset(data["asset_id"]) @@ -100,24 +171,20 @@ def start_listeners(): )[0] project_col.update_one({"_id": asset_doc_id}, asset_update) - def delete_asset(data): + def _delete_asset(self, data): """Delete asset of OP DB.""" - project_col = set_op_project(dbcon, data["project_id"]) + project_col = set_op_project(self.dbcon, data["project_id"]) # Delete project_col.delete_one( {"type": "asset", "data.zou.id": data["asset_id"]} ) - gazu.events.add_listener(event_client, "asset:new", new_asset) - gazu.events.add_listener(event_client, "asset:update", update_asset) - gazu.events.add_listener(event_client, "asset:delete", delete_asset) - # == Episode == - def new_episode(data): + def _new_episode(self, data): """Create new episode into OP DB.""" # Get project entity - project_col = set_op_project(dbcon, data["project_id"]) + project_col = set_op_project(self.dbcon, data["project_id"]) # Get gazu entity episode = gazu.shot.get_episode(data["episode_id"]) @@ -126,12 +193,12 @@ def start_listeners(): project_col.insert_one(create_op_asset(episode)) # Update - update_episode(data) + self._update_episode(data) - def update_episode(data): + def _update_episode(self, data): """Update episode into OP DB.""" - project_col = set_op_project(dbcon, data["project_id"]) - project_doc = dbcon.find_one({"type": "project"}) + project_col = set_op_project(self.dbcon, data["project_id"]) + project_doc = self.dbcon.find_one({"type": "project"}) # Get gazu entity episode = gazu.shot.get_episode(data["episode_id"]) @@ -151,9 +218,9 @@ def start_listeners(): )[0] project_col.update_one({"_id": asset_doc_id}, asset_update) - def delete_episode(data): + def _delete_episode(self, data): """Delete shot of OP DB.""" - project_col = set_op_project(dbcon, data["project_id"]) + project_col = set_op_project(self.dbcon, data["project_id"]) print("delete episode") # TODO check bugfix # Delete @@ -161,15 +228,11 @@ def start_listeners(): {"type": "asset", "data.zou.id": data["episode_id"]} ) - gazu.events.add_listener(event_client, "episode:new", new_episode) - gazu.events.add_listener(event_client, "episode:update", update_episode) - gazu.events.add_listener(event_client, "episode:delete", delete_episode) - # == Sequence == - def new_sequence(data): + def _new_sequence(self, data): """Create new sequnce into OP DB.""" # Get project entity - project_col = set_op_project(dbcon, data["project_id"]) + project_col = set_op_project(self.dbcon, data["project_id"]) # Get gazu entity sequence = gazu.shot.get_sequence(data["sequence_id"]) @@ -178,12 +241,12 @@ def start_listeners(): project_col.insert_one(create_op_asset(sequence)) # Update - update_sequence(data) + self._update_sequence(data) - def update_sequence(data): + def _update_sequence(self, data): """Update sequence into OP DB.""" - project_col = set_op_project(dbcon, data["project_id"]) - project_doc = dbcon.find_one({"type": "project"}) + project_col = set_op_project(self.dbcon, data["project_id"]) + project_doc = self.dbcon.find_one({"type": "project"}) # Get gazu entity sequence = gazu.shot.get_sequence(data["sequence_id"]) @@ -203,9 +266,9 @@ def start_listeners(): )[0] project_col.update_one({"_id": asset_doc_id}, asset_update) - def delete_sequence(data): + def _delete_sequence(self, data): """Delete sequence of OP DB.""" - project_col = set_op_project(dbcon, data["project_id"]) + project_col = set_op_project(self.dbcon, data["project_id"]) print("delete sequence") # TODO check bugfix # Delete @@ -213,15 +276,11 @@ def start_listeners(): {"type": "asset", "data.zou.id": data["sequence_id"]} ) - gazu.events.add_listener(event_client, "sequence:new", new_sequence) - gazu.events.add_listener(event_client, "sequence:update", update_sequence) - gazu.events.add_listener(event_client, "sequence:delete", delete_sequence) - # == Shot == - def new_shot(data): + def _new_shot(self, data): """Create new shot into OP DB.""" # Get project entity - project_col = set_op_project(dbcon, data["project_id"]) + project_col = set_op_project(self.dbcon, data["project_id"]) # Get gazu entity shot = gazu.shot.get_shot(data["shot_id"]) @@ -230,12 +289,12 @@ def start_listeners(): project_col.insert_one(create_op_asset(shot)) # Update - update_shot(data) + self._update_shot(data) - def update_shot(data): + def _update_shot(self, data): """Update shot into OP DB.""" - project_col = set_op_project(dbcon, data["project_id"]) - project_doc = dbcon.find_one({"type": "project"}) + project_col = set_op_project(self.dbcon, data["project_id"]) + project_doc = self.dbcon.find_one({"type": "project"}) # Get gazu entity shot = gazu.shot.get_shot(data["shot_id"]) @@ -255,25 +314,20 @@ def start_listeners(): )[0] project_col.update_one({"_id": asset_doc_id}, asset_update) - def delete_shot(data): + def _delete_shot(self, data): """Delete shot of OP DB.""" - project_col = set_op_project(dbcon, data["project_id"]) + project_col = set_op_project(self.dbcon, data["project_id"]) # Delete project_col.delete_one( {"type": "asset", "data.zou.id": data["shot_id"]} ) - gazu.events.add_listener(event_client, "shot:new", new_shot) - gazu.events.add_listener(event_client, "shot:update", update_shot) - gazu.events.add_listener(event_client, "shot:delete", delete_shot) - # == Task == - def new_task(data): + def _new_task(self, data): """Create new task into OP DB.""" - print("new", data) # Get project entity - project_col = set_op_project(dbcon, data["project_id"]) + project_col = set_op_project(self.dbcon, data["project_id"]) # Get gazu entity task = gazu.task.get_task(data["task_id"]) @@ -291,14 +345,14 @@ def start_listeners(): {"_id": asset_doc["_id"]}, {"$set": {"data.tasks": asset_tasks}} ) - def update_task(data): + def _update_task(self, data): """Update task into OP DB.""" # TODO is it necessary? pass - def delete_task(data): + def _delete_task(self, data): """Delete task of OP DB.""" - project_col = set_op_project(dbcon, data["project_id"]) + project_col = set_op_project(self.dbcon, data["project_id"]) # Find asset doc asset_docs = [doc for doc in project_col.find({"type": "asset"})] @@ -307,7 +361,7 @@ def start_listeners(): for name, task in doc["data"]["tasks"].items(): if task.get("zou") and data["task_id"] == task["zou"]["id"]: # Pop task - asset_tasks = doc["data"].get("tasks") + asset_tasks = doc["data"].get("tasks", {}) asset_tasks.pop(name) # Delete task in DB @@ -317,8 +371,20 @@ def start_listeners(): ) return - gazu.events.add_listener(event_client, "task:new", new_task) - gazu.events.add_listener(event_client, "task:update", update_task) - gazu.events.add_listener(event_client, "task:delete", delete_task) - gazu.events.run_client(event_client) +def start_listeners(login: str, password: str): + """Start listeners to keep OpenPype up-to-date with Kitsu. + + Args: + login (str): Kitsu user login + password (str): Kitsu user password + """ + + # Connect to server + listener = Listener(login, password) + listener.start() + + +if __name__ == "__main__": + # TODO not sure when this can be run and if this system is reliable + start_listeners(load_credentials()) diff --git a/openpype/modules/kitsu/utils/openpype.py b/openpype/modules/kitsu/utils/update_op_with_zou.py similarity index 52% rename from openpype/modules/kitsu/utils/openpype.py rename to openpype/modules/kitsu/utils/update_op_with_zou.py index 56c99effff..eb675ad09e 100644 --- a/openpype/modules/kitsu/utils/openpype.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -1,10 +1,17 @@ +"""Functions to update OpenPype data using Kitsu DB (a.k.a Zou).""" from typing import Dict, List -from pymongo import UpdateOne +from pymongo import DeleteOne, UpdateOne from pymongo.collection import Collection +import gazu +from gazu.task import ( + all_tasks_for_asset, + all_tasks_for_shot, +) from avalon.api import AvalonMongoDB from openpype.lib import create_project +from openpype.modules.kitsu.utils.credentials import validate_credentials def create_op_asset(gazu_entity: dict) -> dict: @@ -26,8 +33,6 @@ def set_op_project(dbcon, project_id) -> Collection: :param dbcon: Connection to DB. :param project_id: Project zou ID """ - import gazu - project = gazu.project.get_project(project_id) project_name = project["name"] dbcon.Session["AVALON_PROJECT"] = project_name @@ -45,11 +50,6 @@ def update_op_assets( :param asset_doc_ids: Dicts of [{zou_id: asset_doc}, ...] :return: List of (doc_id, update_dict) tuples """ - from gazu.task import ( - all_tasks_for_asset, - all_tasks_for_shot, - ) - assets_with_update = [] for item in entities_list: # Update asset @@ -124,18 +124,19 @@ def update_op_assets( return assets_with_update -def sync_project(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: - """Sync project with database. +def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: + """Write gazu project to OP database. Create project if doesn't exist. - :param project: Gazu project - :param dbcon: DB to create project in - :return: Update instance for the project - """ - import gazu + Args: + project (dict): Gazu project + dbcon (AvalonMongoDB): DB to create project in + Returns: + UpdateOne: Update instance for the project + """ project_name = project["name"] - project_doc = dbcon.find_one({"type": "project"}) + project_doc = dbcon.database[project_name].find_one({"type": "project"}) if not project_doc: print(f"Creating project '{project_name}'") project_doc = create_project(project_name, project_name, dbcon=dbcon) @@ -165,3 +166,123 @@ def sync_project(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: } }, ) + + +def sync_all_project(login: str, password: str): + """Update all OP projects in DB with Zou data. + + Args: + login (str): Kitsu user login + password (str): Kitsu user password + + Raises: + gazu.exception.AuthFailedException: Wrong user login and/or password + """ + + # Authenticate + if not validate_credentials(login, password): + raise gazu.exception.AuthFailedException( + f"Kitsu authentication failed for login: '{login}'..." + ) + + # Iterate projects + dbcon = AvalonMongoDB() + dbcon.install() + all_projects = gazu.project.all_projects() + for project in all_projects: + sync_project_from_kitsu(project["name"], dbcon, project) + + +def sync_project_from_kitsu( + project_name: str, dbcon: AvalonMongoDB, project: dict = None +): + """Update OP project in DB with Zou data. + + Args: + project_name (str): Name of project to sync + dbcon (AvalonMongoDB): MongoDB connection + project (dict, optional): Project dict got using gazu. + Defaults to None. + """ + bulk_writes = [] + + # Get project from zou + if not project: + project = gazu.project.get_project_by_name(project_name) + project_code = project_name + + # Try to find project document + project_col = dbcon.database[project_code] + project_doc = project_col.find_one({"type": "project"}) + + print(f"Synchronizing {project_name}...") + + # Get all assets from zou + all_assets = gazu.asset.all_assets_for_project(project) + all_episodes = gazu.shot.all_episodes_for_project(project) + all_seqs = gazu.shot.all_sequences_for_project(project) + all_shots = gazu.shot.all_shots_for_project(project) + all_entities = [ + e + for e in all_assets + all_episodes + all_seqs + all_shots + if e["data"] and not e["data"].get("is_substitute") + ] + + # Sync project. Create if doesn't exist + bulk_writes.append(write_project_to_op(project, dbcon)) + + # Query all assets of the local project + zou_ids_and_asset_docs = { + asset_doc["data"]["zou"]["id"]: asset_doc + for asset_doc in project_col.find({"type": "asset"}) + if asset_doc["data"].get("zou", {}).get("id") + } + zou_ids_and_asset_docs[project["id"]] = project_doc + + # Create + to_insert = [] + to_insert.extend( + [ + create_op_asset(item) + for item in all_entities + if item["id"] not in zou_ids_and_asset_docs.keys() + ] + ) + if to_insert: + # Insert doc in DB + project_col.insert_many(to_insert) + + # Update existing docs + zou_ids_and_asset_docs.update( + { + asset_doc["data"]["zou"]["id"]: asset_doc + for asset_doc in project_col.find({"type": "asset"}) + if asset_doc["data"].get("zou") + } + ) + + # Update + bulk_writes.extend( + [ + UpdateOne({"_id": id}, update) + for id, update in update_op_assets( + all_entities, zou_ids_and_asset_docs + ) + ] + ) + + # Delete + diff_assets = set(zou_ids_and_asset_docs.keys()) - { + e["id"] for e in all_entities + [project] + } + if diff_assets: + bulk_writes.extend( + [ + DeleteOne(zou_ids_and_asset_docs[asset_id]) + for asset_id in diff_assets + ] + ) + + # Write into DB + if bulk_writes: + project_col.bulk_write(bulk_writes) diff --git a/openpype/modules/kitsu/utils/update_zou_with_op.py b/openpype/modules/kitsu/utils/update_zou_with_op.py new file mode 100644 index 0000000000..d1fcde5601 --- /dev/null +++ b/openpype/modules/kitsu/utils/update_zou_with_op.py @@ -0,0 +1,262 @@ +"""Functions to update Kitsu DB (a.k.a Zou) using OpenPype Data.""" + +import re +from typing import List + +import gazu +from pymongo import UpdateOne + +from avalon.api import AvalonMongoDB +from openpype.api import get_project_settings +from openpype.modules.kitsu.utils.credentials import validate_credentials + + +def sync_zou(login: str, password: str): + """Synchronize Zou database (Kitsu backend) with openpype database. + This is an utility function to help updating zou data with OP's, it may not + handle correctly all cases, a human intervention might + be required after all. + Will work better if OP DB has been previously synchronized from zou/kitsu. + + Args: + login (str): Kitsu user login + password (str): Kitsu user password + + Raises: + gazu.exception.AuthFailedException: Wrong user login and/or password + """ + + # Authenticate + if not validate_credentials(login, password): + raise gazu.exception.AuthFailedException( + f"Kitsu authentication failed for login: '{login}'..." + ) + + # Iterate projects + dbcon = AvalonMongoDB() + dbcon.install() + + op_projects = [p for p in dbcon.projects()] + for project_doc in op_projects: + sync_zou_from_op_project(project_doc["name"], dbcon, project_doc) + + +def sync_zou_from_op_project( + project_name: str, dbcon: AvalonMongoDB, project_doc: dict = None +) -> List[UpdateOne]: + """Update OP project in DB with Zou data. + + Args: + project_name (str): Name of project to sync + dbcon (AvalonMongoDB): MongoDB connection + project_doc (str, optional): Project doc to sync + """ + # Get project doc if not provided + if not project_doc: + project_doc = dbcon.database[project_name].find_one( + {"type": "project"} + ) + + # Get all entities from zou + print(f"Synchronizing {project_name}...") + zou_project = gazu.project.get_project_by_name(project_name) + + # Create project + if zou_project is None: + raise RuntimeError( + f"Project '{project_name}' doesn't exist in Zou database, " + "please create it in Kitsu and add OpenPype user to it before " + "running synchronization." + ) + + # Update project settings and data + if project_doc["data"]: + zou_project.update( + { + "code": project_doc["data"]["code"], + "fps": project_doc["data"]["fps"], + "resolution": f"{project_doc['data']['resolutionWidth']}" + f"x{project_doc['data']['resolutionHeight']}", + } + ) + gazu.project.update_project_data(zou_project, data=project_doc["data"]) + gazu.project.update_project(zou_project) + + asset_types = gazu.asset.all_asset_types() + all_assets = gazu.asset.all_assets_for_project(zou_project) + all_episodes = gazu.shot.all_episodes_for_project(zou_project) + all_seqs = gazu.shot.all_sequences_for_project(zou_project) + all_shots = gazu.shot.all_shots_for_project(zou_project) + all_entities_ids = { + e["id"] for e in all_episodes + all_seqs + all_shots + all_assets + } + + # Query all assets of the local project + project_module_settings = get_project_settings(project_name)["kitsu"] + project_col = dbcon.database[project_name] + asset_docs = { + asset_doc["_id"]: asset_doc + for asset_doc in project_col.find({"type": "asset"}) + } + + # Create new assets + new_assets_docs = [ + doc + for doc in asset_docs.values() + if doc["data"].get("zou", {}).get("id") not in all_entities_ids + ] + naming_pattern = project_module_settings["entities_naming_pattern"] + regex_ep = re.compile( + r"(.*{}.*)|(.*{}.*)|(.*{}.*)".format( + naming_pattern["shot"].replace("#", ""), + naming_pattern["sequence"].replace("#", ""), + naming_pattern["episode"].replace("#", ""), + ), + re.IGNORECASE, + ) + bulk_writes = [] + for doc in new_assets_docs: + visual_parent_id = doc["data"]["visualParent"] + parent_substitutes = [] + + # Match asset type by it's name + match = regex_ep.match(doc["name"]) + if not match: # Asset + new_entity = gazu.asset.new_asset( + zou_project, asset_types[0], doc["name"] + ) + # Match case in shot Date: Thu, 3 Mar 2022 15:55:56 +0100 Subject: [PATCH 0153/1227] Cleaning. --- openpype/modules/kitsu/utils/credentials.py | 36 ++++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/openpype/modules/kitsu/utils/credentials.py b/openpype/modules/kitsu/utils/credentials.py index b4dd5ee4a2..0529380d6d 100644 --- a/openpype/modules/kitsu/utils/credentials.py +++ b/openpype/modules/kitsu/utils/credentials.py @@ -8,15 +8,21 @@ from openpype.lib.local_settings import OpenPypeSecureRegistry def validate_credentials( - login: str, password: str, kitsu_url: str = os.environ.get("KITSU_SERVER") + login: str, password: str, kitsu_url: str = None ) -> bool: """Validate credentials by trying to connect to Kitsu host URL. - :param login: Kitsu Login - :param password: Kitsu Password - :param kitsu_url: Kitsu host URL - :return: Are credentials valid? + Args: + login (str): Kitsu user login + password (str): Kitsu user password + kitsu_url (str, optional): Kitsu host URL. Defaults to None. + + Returns: + bool: Are credentials valid? """ + if kitsu_url is None: + kitsu_url = os.environ.get("KITSU_SERVER") + # Connect to server validate_host(kitsu_url) @@ -32,8 +38,11 @@ def validate_credentials( def validate_host(kitsu_url: str) -> bool: """Validate credentials by trying to connect to Kitsu host URL. - :param kitsu_url: Kitsu host URL - :return: Is host valid? + Args: + kitsu_url (str, optional): Kitsu host URL. + + Returns: + bool: Is host valid? """ # Connect to server gazu.set_host(kitsu_url) @@ -58,8 +67,9 @@ def clear_credentials(): def save_credentials(login: str, password: str): """Save credentials in Secure Registry. - :param login: Kitsu Login - :param password: Kitsu Password + Args: + login (str): Kitsu user login + password (str): Kitsu user password """ # Get user registry user_registry = OpenPypeSecureRegistry("kitsu_user") @@ -72,7 +82,8 @@ def save_credentials(login: str, password: str): def load_credentials() -> Tuple[str, str]: """Load registered credentials. - :return: Login, Password + Returns: + Tuple[str, str]: (Login, Password) """ # Get user registry user_registry = OpenPypeSecureRegistry("kitsu_user") @@ -85,8 +96,9 @@ def load_credentials() -> Tuple[str, str]: def set_credentials_envs(login: str, password: str): """Set environment variables with Kitsu login and password. - :param login: Kitsu Login - :param password: Kitsu Password + Args: + login (str): Kitsu user login + password (str): Kitsu user password """ os.environ["KITSU_LOGIN"] = login os.environ["KITSU_PWD"] = password From 5db0c1dfa7cbf483976b6e52f6d4cde5813daedc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Fri, 4 Mar 2022 10:12:08 +0100 Subject: [PATCH 0154/1227] Python 2 compat and cleaning --- openpype/modules/kitsu/kitsu_module.py | 4 ++-- openpype/modules/kitsu/utils/sync_service.py | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index dca6133e88..53edfddf9a 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -105,7 +105,7 @@ def cli_main(): @click.option( "--password", envvar="KITSU_PWD", help="Password for kitsu username" ) -def push_to_zou(login: str, password: str): +def push_to_zou(login, password): """Synchronize Zou database (Kitsu backend) with openpype database. Args: @@ -122,7 +122,7 @@ def push_to_zou(login: str, password: str): @click.option( "-p", "--password", envvar="KITSU_PWD", help="Password for kitsu username" ) -def sync_service(login: str, password: str): +def sync_service(login, password): """Synchronize openpype database from Zou sever database. Args: diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 831673ec0d..6bf98cf308 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -383,8 +383,3 @@ def start_listeners(login: str, password: str): # Connect to server listener = Listener(login, password) listener.start() - - -if __name__ == "__main__": - # TODO not sure when this can be run and if this system is reliable - start_listeners(load_credentials()) From 8c3b510887564d52d7230c7114a17f9044157be5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Fri, 4 Mar 2022 10:12:58 +0100 Subject: [PATCH 0155/1227] Cleaning --- openpype/modules/kitsu/utils/sync_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 6bf98cf308..2e8fbf77f5 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -3,7 +3,7 @@ import os import gazu from avalon.api import AvalonMongoDB -from .credentials import load_credentials, validate_credentials +from .credentials import validate_credentials from .update_op_with_zou import ( create_op_asset, set_op_project, From e5ae5459e135d10d01e1549f8ac3f8a8cb83e689 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Fri, 4 Mar 2022 15:58:06 +0100 Subject: [PATCH 0156/1227] Kitsu docs --- website/docs/artist_kitsu.md | 17 ++++++++ .../docs/assets/kitsu/kitsu_credentials.png | Bin 0 -> 15798 bytes website/docs/module_kitsu.md | 37 ++++++++++++++++++ website/sidebars.js | 2 + 4 files changed, 56 insertions(+) create mode 100644 website/docs/artist_kitsu.md create mode 100644 website/docs/assets/kitsu/kitsu_credentials.png create mode 100644 website/docs/module_kitsu.md diff --git a/website/docs/artist_kitsu.md b/website/docs/artist_kitsu.md new file mode 100644 index 0000000000..9ef782c297 --- /dev/null +++ b/website/docs/artist_kitsu.md @@ -0,0 +1,17 @@ +--- +id: artist_kitsu +title: Kitsu +sidebar_label: Kitsu +--- + +# How to use Kitsu in OpenPype + +## Login to Kitsu module in OpenPype +1. Launch OpenPype, the `Kitsu Credentials` window will open automatically, if not, or if you want to log-in with another account, go to systray OpenPype icon and click on `Kitsu Connect`. +2. Enter your credentials and press *Ok*: + + ![kitsu-login](assets/kitsu/kitsu_credentials.png) + +:::tip +In Kitsu, All the publish actions executed by `pyblish` will be attributed to the currently logged-in user. +::: \ No newline at end of file diff --git a/website/docs/assets/kitsu/kitsu_credentials.png b/website/docs/assets/kitsu/kitsu_credentials.png new file mode 100644 index 0000000000000000000000000000000000000000..25c1ad93c4d966a083d1fbdcd15e78a54e659b76 GIT binary patch literal 15798 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 literal 0 HcmV?d00001 diff --git a/website/docs/module_kitsu.md b/website/docs/module_kitsu.md new file mode 100644 index 0000000000..ec38cce5e1 --- /dev/null +++ b/website/docs/module_kitsu.md @@ -0,0 +1,37 @@ +--- +id: module_kitsu +title: Kitsu Administration +sidebar_label: Kitsu +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Kitsu is a great open source production tracker and can be used for project management instead of Ftrack. This documentation assumes that you are familiar with Kitsu and it's basic principles. If you're new to Kitsu, we recommend having a thorough look at [Kitsu Official Documentation](https://kitsu.cg-wire.com/). + +## Prepare Kitsu for OpenPype + +### Server URL +If you want to connect Kitsu to OpenPype you have to set the `Server` url in Kitsu settings. And that's all! +This setting is available for all the users of the OpenPype instance. + +## Synchronize +Updating OP with Kitsu data is executed running the `sync-service`, which requires to provide your Kitsu credentials with `-l, --login` and `-p, --password` or by setting the environment variables `KITSU_LOGIN` and `KITSU_PWD`. This process will request data from Kitsu and create/delete/update OP assets. +Once this sync is done, the thread will automatically start a loop to listen to Kitsu events. + +```bash +openpype_console module kitsu sync-service -l me@domain.ext -p my_password +``` + +### Events listening +Listening to Kitsu events is the key to automation of many tasks like _project/episode/sequence/shot/asset/task create/update/delete_ and some more. Events listening should run at all times to perform the required processing as it is not possible to catch some of them retrospectively with strong reliability. If such timeout has been encountered, you must relaunch the `sync-service` command to run the synchronization step again. + +### Push to Kitsu +An utility function is provided to help update Kitsu data (a.k.a Zou database) with OpenPype data if the publishing to the production tracker hasn't been possible for some time. Running `push-to-zou` will create the data on behalf of the user. +:::caution +This functionality cannot deal with all cases and is not error proof, some intervention by a human being might be required. +::: + +```bash +openpype_console module kitsu push-to-zou -l me@domain.ext -p my_password +``` diff --git a/website/sidebars.js b/website/sidebars.js index 105afc30eb..eecdcc6e9a 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -28,6 +28,7 @@ module.exports = { "artist_hosts_photoshop", "artist_hosts_tvpaint", "artist_hosts_unreal", + "artist_kitsu", { type: "category", label: "Ftrack", @@ -75,6 +76,7 @@ module.exports = { label: "Modules", items: [ "module_ftrack", + "module_kitsu", "module_site_sync", "module_deadline", "module_muster", From 05158585c778a3bed91c023626bcf5ee3baf29a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Mar 2022 09:22:16 +0100 Subject: [PATCH 0157/1227] Add pyblish comment to kitsu --- openpype/modules/kitsu/kitsu_module.py | 6 +++--- openpype/modules/kitsu/plugins/publish/kitsu_plugin.py | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index 53edfddf9a..8e7ab6f78c 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -51,11 +51,11 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): set_credentials_envs, ) - username, password = load_credentials() + login, password = load_credentials() # Check credentials, ask them if needed - if validate_credentials(username, password): - set_credentials_envs(username, password) + if validate_credentials(login, password): + set_credentials_envs(login, password) else: self.show_dialog() diff --git a/openpype/modules/kitsu/plugins/publish/kitsu_plugin.py b/openpype/modules/kitsu/plugins/publish/kitsu_plugin.py index b556f2b91f..5fce123d7e 100644 --- a/openpype/modules/kitsu/plugins/publish/kitsu_plugin.py +++ b/openpype/modules/kitsu/plugins/publish/kitsu_plugin.py @@ -39,7 +39,9 @@ class IntegrateRig(pyblish.api.InstancePlugin): gazu.task.add_comment( entity_task, entity_task["task_status_id"], - comment=f"Version {instance.data['version']} has been published!", - ) # TODO add comment from pyblish + comment=f"Version {instance.data['version']} has been published!\n" + "\n" # Add written comment in Pyblish + f"{instance.data['versionEntity']['data']['comment']}".strip(), + ) self.log.info("Version published to Kitsu successfully!") From 1591b91c54611e2bfe55902ad31929e72416515f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 8 Mar 2022 09:57:37 +0100 Subject: [PATCH 0158/1227] Python2 compat --- openpype/modules/kitsu/plugins/publish/kitsu_plugin.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/kitsu_plugin.py b/openpype/modules/kitsu/plugins/publish/kitsu_plugin.py index 5fce123d7e..5d6c76bc3f 100644 --- a/openpype/modules/kitsu/plugins/publish/kitsu_plugin.py +++ b/openpype/modules/kitsu/plugins/publish/kitsu_plugin.py @@ -39,9 +39,11 @@ class IntegrateRig(pyblish.api.InstancePlugin): gazu.task.add_comment( entity_task, entity_task["task_status_id"], - comment=f"Version {instance.data['version']} has been published!\n" - "\n" # Add written comment in Pyblish - f"{instance.data['versionEntity']['data']['comment']}".strip(), + comment="Version {} has been published!\n".format( + instance.data["version"] + ) + # Add written comment in Pyblish + + "\n{}".format(instance.data["versionEntity"]["data"]["comment"]), ) self.log.info("Version published to Kitsu successfully!") From 9c0c43a0612c70fa7fa26e71c1f91b7605b3792d Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Tue, 15 Mar 2022 18:01:04 +0100 Subject: [PATCH 0159/1227] fix first sync crash --- .../modules/kitsu/utils/update_op_with_zou.py | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index eb675ad09e..e76d54d1ad 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -145,14 +145,15 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: project_data = project["data"] or {} # Update data - project_data.update( - { - "code": project["code"], - "fps": project["fps"], - "resolutionWidth": project["resolution"].split("x")[0], - "resolutionHeight": project["resolution"].split("x")[1], - } - ) + if project_data: + project_data.update( + { + "code": project["code"], + "fps": project["fps"], + "resolutionWidth": project["resolution"].split("x")[0], + "resolutionHeight": project["resolution"].split("x")[1], + } + ) return UpdateOne( {"_id": project_doc["_id"]}, @@ -211,10 +212,6 @@ def sync_project_from_kitsu( project = gazu.project.get_project_by_name(project_name) project_code = project_name - # Try to find project document - project_col = dbcon.database[project_code] - project_doc = project_col.find_one({"type": "project"}) - print(f"Synchronizing {project_name}...") # Get all assets from zou @@ -222,15 +219,15 @@ def sync_project_from_kitsu( all_episodes = gazu.shot.all_episodes_for_project(project) all_seqs = gazu.shot.all_sequences_for_project(project) all_shots = gazu.shot.all_shots_for_project(project) - all_entities = [ - e - for e in all_assets + all_episodes + all_seqs + all_shots - if e["data"] and not e["data"].get("is_substitute") - ] + all_entities = all_assets + all_episodes + all_seqs + all_shots # Sync project. Create if doesn't exist bulk_writes.append(write_project_to_op(project, dbcon)) + # Try to find project document + project_col = dbcon.database[project_code] + project_doc = project_col.find_one({"type": "project"}) + # Query all assets of the local project zou_ids_and_asset_docs = { asset_doc["data"]["zou"]["id"]: asset_doc From 17dd018aa060f8fbaf043e19e86a9a0e318f14dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 16 Mar 2022 17:01:30 +0100 Subject: [PATCH 0160/1227] Build project code --- openpype/modules/kitsu/utils/update_op_with_zou.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index e76d54d1ad..98f263efe1 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -144,11 +144,20 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: # Project data and tasks project_data = project["data"] or {} + # Build project code and update Kitsu + project_code = project.get("code") + if not project_code: + project_code = project["name"].replace(" ", "_").lower() + project["code"] = project_code + + # Update Zou + gazu.project.update_project(project) + # Update data if project_data: project_data.update( { - "code": project["code"], + "code": project_code, "fps": project["fps"], "resolutionWidth": project["resolution"].split("x")[0], "resolutionHeight": project["resolution"].split("x")[1], From 093e801b0b02074e4d5bd7ecd0ff6d791ba6ea92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 16 Mar 2022 17:31:22 +0100 Subject: [PATCH 0161/1227] Sync project FPS and resolution --- .../modules/kitsu/utils/update_op_with_zou.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 98f263efe1..cb2c79b942 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -154,15 +154,14 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: gazu.project.update_project(project) # Update data - if project_data: - project_data.update( - { - "code": project_code, - "fps": project["fps"], - "resolutionWidth": project["resolution"].split("x")[0], - "resolutionHeight": project["resolution"].split("x")[1], - } - ) + project_data.update( + { + "code": project_code, + "fps": project["fps"], + "resolutionWidth": project["resolution"].split("x")[0], + "resolutionHeight": project["resolution"].split("x")[1], + } + ) return UpdateOne( {"_id": project_doc["_id"]}, From bd06809ffaf8ff8ae8bde463a5ad990b486288c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 16 Mar 2022 18:44:26 +0100 Subject: [PATCH 0162/1227] Fix shot syncs --- openpype/modules/kitsu/utils/update_op_with_zou.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index cb2c79b942..6aea2e3930 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -1,4 +1,5 @@ """Functions to update OpenPype data using Kitsu DB (a.k.a Zou).""" +from copy import deepcopy from typing import Dict, List from pymongo import DeleteOne, UpdateOne @@ -54,9 +55,14 @@ def update_op_assets( for item in entities_list: # Update asset item_doc = asset_doc_ids[item["id"]] - item_data = item_doc["data"].copy() + item_data = deepcopy(item_doc["data"]) + item_data.update(item.get("data") or {}) item_data["zou"] = item + # Asset settings + item_data["frameStart"] = item_data.get("frame_in") + item_data["frameEnd"] = item_data.get("frame_out") + # Tasks tasks_list = [] if item["type"] == "Asset": @@ -103,9 +109,7 @@ def update_op_assets( # Update 'data' different in zou DB updated_data = { - k: item_data[k] - for k in item_data.keys() - if item_doc["data"].get(k) != item_data[k] + k: v for k, v in item_data.items() if item_doc["data"].get(k) != v } if updated_data or not item_doc.get("parent"): assets_with_update.append( From 84a13e4eb6ff99b433ea1d2fddce32d106381e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 17 Mar 2022 15:52:14 +0100 Subject: [PATCH 0163/1227] shots and assets custom folders root --- openpype/modules/kitsu/utils/sync_service.py | 8 +- .../modules/kitsu/utils/update_op_with_zou.py | 82 +++++++++++++++++-- .../defaults/project_settings/kitsu.json | 4 + .../projects_schema/schema_project_kitsu.json | 17 ++++ 4 files changed, 99 insertions(+), 12 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 2e8fbf77f5..746cb843e9 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -167,7 +167,7 @@ class Listener: # Update asset_doc_id, asset_update = update_op_assets( - [asset], zou_ids_and_asset_docs + project_col[asset], zou_ids_and_asset_docs )[0] project_col.update_one({"_id": asset_doc_id}, asset_update) @@ -214,7 +214,7 @@ class Listener: # Update asset_doc_id, asset_update = update_op_assets( - [episode], zou_ids_and_asset_docs + project_col, [episode], zou_ids_and_asset_docs )[0] project_col.update_one({"_id": asset_doc_id}, asset_update) @@ -262,7 +262,7 @@ class Listener: # Update asset_doc_id, asset_update = update_op_assets( - [sequence], zou_ids_and_asset_docs + project_col, [sequence], zou_ids_and_asset_docs )[0] project_col.update_one({"_id": asset_doc_id}, asset_update) @@ -310,7 +310,7 @@ class Listener: # Update asset_doc_id, asset_update = update_op_assets( - [shot], zou_ids_and_asset_docs + project_col, [shot], zou_ids_and_asset_docs )[0] project_col.update_one({"_id": asset_doc_id}, asset_update) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 6aea2e3930..e2ad29bfa0 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -11,6 +11,7 @@ from gazu.task import ( ) from avalon.api import AvalonMongoDB +from openpype.api import get_project_settings from openpype.lib import create_project from openpype.modules.kitsu.utils.credentials import validate_credentials @@ -42,15 +43,23 @@ def set_op_project(dbcon, project_id) -> Collection: def update_op_assets( - entities_list: List[dict], asset_doc_ids: Dict[str, dict] + project_col: Collection, + entities_list: List[dict], + asset_doc_ids: Dict[str, dict], ) -> List[Dict[str, dict]]: """Update OpenPype assets. Set 'data' and 'parent' fields. - :param entities_list: List of zou entities to update - :param asset_doc_ids: Dicts of [{zou_id: asset_doc}, ...] - :return: List of (doc_id, update_dict) tuples + Args: + project_col (Collection): Mongo project collection to sync + entities_list (List[dict]): List of zou entities to update + asset_doc_ids (Dict[str, dict]): Dicts of [{zou_id: asset_doc}, ...] + + Returns: + List[Dict[str, dict]]: List of (doc_id, update_dict) tuples """ + project_name = project_col.name + assets_with_update = [] for item in entities_list: # Update asset @@ -65,9 +74,10 @@ def update_op_assets( # Tasks tasks_list = [] - if item["type"] == "Asset": + item_type = item["type"] + if item_type == "Asset": tasks_list = all_tasks_for_asset(item) - elif item["type"] == "Shot": + elif item_type == "Shot": tasks_list = all_tasks_for_shot(item) # TODO frame in and out item_data["tasks"] = { @@ -91,10 +101,39 @@ def update_op_assets( or item.get("source_id") ) # TODO check consistency - # Visual parent for hierarchy + # Substitute Episode and Sequence by Shot + project_module_settings = get_project_settings(project_name)["kitsu"] + substitute_item_type = ( + "shots" + if item_type in ["Episode", "Sequence"] + else f"{item_type.lower()}s" + ) + entity_parent_folders = [ + f + for f in project_module_settings["entities_root"] + .get(substitute_item_type) + .split("/") + if f + ] + + # Root parent folder if exist visual_parent_doc_id = ( asset_doc_ids[parent_zou_id]["_id"] if parent_zou_id else None ) + if visual_parent_doc_id is None: + # Find root folder doc + root_folder_doc = project_col.find_one( + { + "type": "asset", + "name": entity_parent_folders[-1], + "data.root_of": substitute_item_type, + }, + ["_id"], + ) + if root_folder_doc: + visual_parent_doc_id = root_folder_doc["_id"] + + # Visual parent for hierarchy item_data["visualParent"] = visual_parent_doc_id # Add parents for hierarchy @@ -107,6 +146,9 @@ def update_op_assets( parent_entity = parent_doc["data"]["zou"] parent_zou_id = parent_entity["parent_id"] + # Set root folders parents + item_data["parents"] = entity_parent_folders + item_data["parents"] + # Update 'data' different in zou DB updated_data = { k: v for k, v in item_data.items() if item_doc["data"].get(k) != v @@ -248,6 +290,30 @@ def sync_project_from_kitsu( } zou_ids_and_asset_docs[project["id"]] = project_doc + # Create entities root folders + project_module_settings = get_project_settings(project_name)["kitsu"] + for entity_type, root in project_module_settings["entities_root"].items(): + parent_folders = root.split("/") + direct_parent_doc = None + for i, folder in enumerate(parent_folders, 1): + parent_doc = project_col.find_one( + {"type": "asset", "name": folder, "data.root_of": entity_type} + ) + if not parent_doc: + direct_parent_doc = project_col.insert_one( + { + "name": folder, + "type": "asset", + "schema": "openpype:asset-3.0", + "data": { + "root_of": entity_type, + "parents": parent_folders[:i], + "visualParent": direct_parent_doc, + "tasks": {}, + }, + } + ) + # Create to_insert = [] to_insert.extend( @@ -275,7 +341,7 @@ def sync_project_from_kitsu( [ UpdateOne({"_id": id}, update) for id, update in update_op_assets( - all_entities, zou_ids_and_asset_docs + project_col, all_entities, zou_ids_and_asset_docs ) ] ) diff --git a/openpype/settings/defaults/project_settings/kitsu.json b/openpype/settings/defaults/project_settings/kitsu.json index 435814a9d1..a37146e1d2 100644 --- a/openpype/settings/defaults/project_settings/kitsu.json +++ b/openpype/settings/defaults/project_settings/kitsu.json @@ -1,4 +1,8 @@ { + "entities_root": { + "assets": "Assets", + "shots": "Shots" + }, "entities_naming_pattern": { "episode": "E##", "sequence": "SQ##", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json b/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json index a504959001..8d71d0ecd6 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json @@ -5,6 +5,23 @@ "collapsible": true, "is_file": true, "children": [ + { + "type": "dict", + "key": "entities_root", + "label": "Entities root folder", + "children": [ + { + "type": "text", + "key": "assets", + "label": "Assets:" + }, + { + "type": "text", + "key": "shots", + "label": "Shots (includes Episodes & Sequences if any):" + } + ] + }, { "type": "dict", "key": "entities_naming_pattern", From 3970229ce5908abfd62c90a88b656f08f232dbfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 17 Mar 2022 16:09:15 +0100 Subject: [PATCH 0164/1227] Fix fps fallback to project's value --- openpype/modules/kitsu/utils/update_op_with_zou.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index e2ad29bfa0..288efe30da 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -71,6 +71,10 @@ def update_op_assets( # Asset settings item_data["frameStart"] = item_data.get("frame_in") item_data["frameEnd"] = item_data.get("frame_out") + # Sentinel for fps, fallback to project's value when entity fps is deleted + if not item_data.get("fps") and item_doc["data"].get("fps"): + project_doc = project_col.find_one({"type": "project"}) + item_data["fps"] = project_doc["data"]["fps"] # Tasks tasks_list = [] From 78bda28da4ec26cf4b8b8673cabc01316f578678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 17 Mar 2022 16:49:13 +0100 Subject: [PATCH 0165/1227] frame_in/out fallbacks --- .../modules/kitsu/utils/update_op_with_zou.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 288efe30da..f5c6406722 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -68,10 +68,19 @@ def update_op_assets( item_data.update(item.get("data") or {}) item_data["zou"] = item - # Asset settings - item_data["frameStart"] = item_data.get("frame_in") - item_data["frameEnd"] = item_data.get("frame_out") - # Sentinel for fps, fallback to project's value when entity fps is deleted + # == Asset settings == + # Frame in, fallback on 0 + frame_in = int(item_data.get("frame_in") or 0) + item_data["frameStart"] = frame_in + # Frame out, fallback on frame_in + duration + frames_duration = int(item.get("nb_frames") or 1) + frame_out = ( + item_data["frame_out"] + if item_data.get("frame_out") + else frame_in + frames_duration + ) + item_data["frameEnd"] = int(frame_out) + # Fps, fallback to project's value when entity fps is deleted if not item_data.get("fps") and item_doc["data"].get("fps"): project_doc = project_col.find_one({"type": "project"}) item_data["fps"] = project_doc["data"]["fps"] From 527f4a710dbddd21feac630bdafb7b986a354c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Fri, 25 Mar 2022 10:03:22 +0100 Subject: [PATCH 0166/1227] Sync only open projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Clément Hector --- openpype/modules/kitsu/utils/update_op_with_zou.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index f5c6406722..f43223cdf7 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -256,7 +256,7 @@ def sync_all_project(login: str, password: str): # Iterate projects dbcon = AvalonMongoDB() dbcon.install() - all_projects = gazu.project.all_projects() + all_projects = gazu.project.all_open_projects() for project in all_projects: sync_project_from_kitsu(project["name"], dbcon, project) From 63096b4e6b819081bfc3ea962ea82ab8d2ef507e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 4 May 2022 18:26:58 +0200 Subject: [PATCH 0167/1227] change avalon API --- openpype/modules/kitsu/utils/sync_service.py | 2 +- openpype/modules/kitsu/utils/update_op_with_zou.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 746cb843e9..01596e2667 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -2,7 +2,7 @@ import os import gazu -from avalon.api import AvalonMongoDB +from openpype.pipeline import AvalonMongoDB from .credentials import validate_credentials from .update_op_with_zou import ( create_op_asset, diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index f43223cdf7..25c89800d4 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -10,7 +10,7 @@ from gazu.task import ( all_tasks_for_shot, ) -from avalon.api import AvalonMongoDB +from openpype.pipeline import AvalonMongoDB from openpype.api import get_project_settings from openpype.lib import create_project from openpype.modules.kitsu.utils.credentials import validate_credentials From 0359ec6da4e03bf9c7df49060a423c028195d86c Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Thu, 7 Apr 2022 15:46:40 +0200 Subject: [PATCH 0168/1227] create kitsu collector --- .../publish/collect_kitsu_credential.py | 18 ++++++ .../plugins/publish/collect_kitsu_entities.py | 64 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py create mode 100644 openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py new file mode 100644 index 0000000000..bd0af16c8b --- /dev/null +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py @@ -0,0 +1,18 @@ +import os + +import gazu + +import pyblish.api + + +class CollectKitsuSession(pyblish.api.ContextPlugin): + """Collect Kitsu session using user credentials""" + + order = pyblish.api.CollectorOrder + label = "Kitsu user session" + + + def process(self, context): + + gazu.client.set_host(os.environ["KITSU_SERVER"]) + gazu.log_in(os.environ["KITSU_LOGIN"], os.environ["KITSU_PWD"]) \ No newline at end of file diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py new file mode 100644 index 0000000000..e4773f7b2a --- /dev/null +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -0,0 +1,64 @@ +import os + +import gazu + +import pyblish.api + + +class CollectKitsuEntities(pyblish.api.ContextPlugin): + """Collect Kitsu entities according to the current context""" + + order = pyblish.api.CollectorOrder + 0.499 + label = "Kitsu entities" + + def process(self, context): + + os.environ["AVALON_PROJECT"], + os.environ["AVALON_ASSET"], + os.environ["AVALON_TASK"], + os.environ["AVALON_APP_NAME"] + + asset_data = context.data["assetEntity"]["data"] + zoo_asset_data = asset_data.get("zou") + if not zoo_asset_data: + raise + + kitsu_project = gazu.project.get_project(zoo_asset_data["project_id"]) + if not kitsu_project: + raise + context.data["kitsu_project"] = kitsu_project + + kitsu_asset = gazu.asset.get_asset(zoo_asset_data["entity_type_id"]) + if not kitsu_asset: + raise + context.data["kitsu_asset"] = kitsu_asset + + # kitsu_task_type = gazu.task.get_task_type_by_name(instance.data["task"]) + # if not kitsu_task_type: + # raise + # context.data["kitsu_task_type"] = kitsu_task_type + + zoo_task_data = asset_data["tasks"][os.environ["AVALON_TASK"]].get("zou") + kitsu_task = gazu.task.get_task( + asset_data["zou"], + kitsu_task_type + ) + if not kitsu_task: + raise + context.data["kitsu_task"] = kitsu_task + + wip = gazu.task.get_task_status_by_short_name("wip") + + task = gazu.task.get_task_by_name(asset, modeling) + comment = gazu.task.add_comment(task, wip, "Change status to work in progress") + + person = gazu.person.get_person_by_desktop_login("john.doe") + + # task_type = gazu.task.get_task_type_by_name(instance.data["task"]) + + # entity_task = gazu.task.get_task_by_entity( + # asset_data["zou"], + # task_type + # ) + + raise \ No newline at end of file From 196c81ab78dffdafcca28c00657d11c799a9d7f4 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Fri, 8 Apr 2022 12:03:43 +0200 Subject: [PATCH 0169/1227] collect all kitsu entities in context --- .../plugins/publish/collect_kitsu_entities.py | 51 ++++++------------- 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index e4773f7b2a..f599fd0c14 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -13,52 +13,31 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): def process(self, context): - os.environ["AVALON_PROJECT"], - os.environ["AVALON_ASSET"], - os.environ["AVALON_TASK"], - os.environ["AVALON_APP_NAME"] - asset_data = context.data["assetEntity"]["data"] zoo_asset_data = asset_data.get("zou") if not zoo_asset_data: - raise + raise AssertionError("Zoo asset data not found in OpenPype!") + self.log.debug("Collected zoo asset data: {}".format(zoo_asset_data)) + + zoo_task_data = asset_data["tasks"][os.environ["AVALON_TASK"]].get("zou") + if not zoo_task_data: + raise AssertionError("Zoo task data not found in OpenPype!") + self.log.debug("Collected zoo task data: {}".format(zoo_task_data)) kitsu_project = gazu.project.get_project(zoo_asset_data["project_id"]) if not kitsu_project: - raise + raise AssertionError("Project not not found in kitsu!") context.data["kitsu_project"] = kitsu_project + self.log.debug("Collect kitsu project: {}".format(kitsu_project)) - kitsu_asset = gazu.asset.get_asset(zoo_asset_data["entity_type_id"]) + kitsu_asset = gazu.asset.get_asset(zoo_asset_data["id"]) if not kitsu_asset: - raise + raise AssertionError("Asset not not found in kitsu!") context.data["kitsu_asset"] = kitsu_asset + self.log.debug("Collect kitsu asset: {}".format(kitsu_asset)) - # kitsu_task_type = gazu.task.get_task_type_by_name(instance.data["task"]) - # if not kitsu_task_type: - # raise - # context.data["kitsu_task_type"] = kitsu_task_type - - zoo_task_data = asset_data["tasks"][os.environ["AVALON_TASK"]].get("zou") - kitsu_task = gazu.task.get_task( - asset_data["zou"], - kitsu_task_type - ) + kitsu_task = gazu.task.get_task(zoo_task_data["id"]) if not kitsu_task: - raise + raise AssertionError("Task not not found in kitsu!") context.data["kitsu_task"] = kitsu_task - - wip = gazu.task.get_task_status_by_short_name("wip") - - task = gazu.task.get_task_by_name(asset, modeling) - comment = gazu.task.add_comment(task, wip, "Change status to work in progress") - - person = gazu.person.get_person_by_desktop_login("john.doe") - - # task_type = gazu.task.get_task_type_by_name(instance.data["task"]) - - # entity_task = gazu.task.get_task_by_entity( - # asset_data["zou"], - # task_type - # ) - - raise \ No newline at end of file + self.log.debug("Collect kitsu task: {}".format(kitsu_task)) From eb288959f50666e0c9a4751d7908decd540de9de Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Sat, 9 Apr 2022 11:13:48 +0200 Subject: [PATCH 0170/1227] integrate note and status --- .../publish/collect_kitsu_credential.py | 2 +- .../plugins/publish/integrate_kitsu_note.py | 36 +++++++++++++++++++ .../plugins/publish/integrate_kitsu_review.py | 16 +++++++++ .../plugins/publish/validate_kitsu_intent.py | 27 ++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py create mode 100644 openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py create mode 100644 openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py index bd0af16c8b..c9d94d128a 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py @@ -10,7 +10,7 @@ class CollectKitsuSession(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder label = "Kitsu user session" - + # families = ["kitsu"] def process(self, context): diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py new file mode 100644 index 0000000000..5601dea586 --- /dev/null +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -0,0 +1,36 @@ +import gazu +import pyblish.api + + +class IntegrateKitsuNote(pyblish.api.ContextPlugin): + """Integrate Kitsu Note""" + + order = pyblish.api.IntegratorOrder + label = "Kitsu Note and Status" + # families = ["kitsu"] + optional = True + + def process(self, context): + + publish_comment = context.data.get("comment") + if not publish_comment: + self.log.info("Comment is not set.") + + publish_status = context.data.get("intent", {}).get("value") + if not publish_status: + self.log.info("Status is not set.") + + self.log.debug("Comment is `{}`".format(publish_comment)) + self.log.debug("Status is `{}`".format(publish_status)) + + kitsu_status = context.data.get("kitsu_status") + if not kitsu_status: + self.log.info("The status will not be changed") + kitsu_status = context.data["kitsu_task"].get("task_status") + self.log.debug("Kitsu status: {}".format(kitsu_status)) + + gazu.task.add_comment( + context.data["kitsu_task"], + kitsu_status, + comment = publish_comment + ) \ No newline at end of file diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py new file mode 100644 index 0000000000..1853bf569f --- /dev/null +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -0,0 +1,16 @@ +# import gazu +import pyblish.api + + +class IntegrateKitsuVersion(pyblish.api.InstancePlugin): + """Integrate Kitsu Review""" + + order = pyblish.api.IntegratorOrder + label = "Kitsu Review" + # families = ["kitsu"] + + def process(self, instance): + pass + + # gazu.task.upload_preview_file(preview, file_path, normalize_movie=True, client=) + # gazu.task.add_preview(task, comment, preview_file_path, normalize_movie=True, client=) \ No newline at end of file diff --git a/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py b/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py new file mode 100644 index 0000000000..9708ebb0dd --- /dev/null +++ b/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py @@ -0,0 +1,27 @@ +from typing import Optional +import pyblish.api +import gazu + + +class IntegrateKitsuNote(pyblish.api.ContextPlugin): + """Integrate Kitsu Note""" + + order = pyblish.api.ValidatorOrder + label = "Kitsu Intent/Status" + # families = ["kitsu"] + optional = True + + def process(self, context): + + publish_status = context.data.get("intent", {}).get("value") + if not publish_status: + self.log.info("Status is not set.") + + kitsu_status = gazu.task.get_task_status_by_short_name(publish_status) + if not kitsu_status: + raise AssertionError( + "Status `{}` not not found in kitsu!".format(kitsu_status) + ) + self.log.debug("Collect kitsu status: {}".format(kitsu_status)) + + context.data["kitsu_status"] = kitsu_status \ No newline at end of file From ff6c8a6a54b2146fce11b78ea7dccf048536d1e8 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Sun, 10 Apr 2022 10:47:11 +0200 Subject: [PATCH 0171/1227] Add kitsu log out --- .../plugins/publish/collect_kitsu_credential.py | 4 ++-- .../plugins/publish/collect_kitsu_entities.py | 2 +- .../plugins/publish/integrate_kitsu_file.py | 1 + .../plugins/publish/integrate_kitsu_note.py | 1 + .../plugins/publish/integrate_kitsu_review.py | 3 ++- .../kitsu/plugins/publish/other_kitsu_log_out.py | 16 ++++++++++++++++ 6 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 openpype/modules/kitsu/plugins/publish/integrate_kitsu_file.py create mode 100644 openpype/modules/kitsu/plugins/publish/other_kitsu_log_out.py diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py index c9d94d128a..4a27117e03 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py @@ -1,11 +1,11 @@ +# -*- coding: utf-8 -*- import os import gazu - import pyblish.api -class CollectKitsuSession(pyblish.api.ContextPlugin): +class CollectKitsuSession(pyblish.api.ContextPlugin): #rename log in """Collect Kitsu session using user credentials""" order = pyblish.api.CollectorOrder diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index f599fd0c14..c5df20b349 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -1,7 +1,7 @@ +# -*- coding: utf-8 -*- import os import gazu - import pyblish.api diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_file.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_file.py new file mode 100644 index 0000000000..7c68785e9d --- /dev/null +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_file.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- \ No newline at end of file diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index 5601dea586..8844581237 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import gazu import pyblish.api diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index 1853bf569f..a800ca9b57 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -1,4 +1,5 @@ -# import gazu +# -*- coding: utf-8 -*- +import gazu import pyblish.api diff --git a/openpype/modules/kitsu/plugins/publish/other_kitsu_log_out.py b/openpype/modules/kitsu/plugins/publish/other_kitsu_log_out.py new file mode 100644 index 0000000000..ff76d9c4f6 --- /dev/null +++ b/openpype/modules/kitsu/plugins/publish/other_kitsu_log_out.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +""".""" +import gazu +import pyblish.api + + +class KitsuLogOut(pyblish.api.ContextPlugin): + """ + Log out from Kitsu API + """ + + order = pyblish.api.IntegratorOrder + 10 + label = "Kitsu Log Out" + + def process(self, context): + gazu.client.log_out() From ed8c01c2639321126b43d84ad83bd06496cb1bad Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Wed, 20 Apr 2022 11:57:33 +0200 Subject: [PATCH 0172/1227] upload file to kitsu --- .../plugins/publish/integrate_kitsu_note.py | 8 ++++--- .../plugins/publish/integrate_kitsu_review.py | 24 +++++++++++++++---- .../plugins/publish/validate_kitsu_intent.py | 5 ++-- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index 8844581237..afe388fd82 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -9,7 +9,7 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder label = "Kitsu Note and Status" # families = ["kitsu"] - optional = True + # optional = True def process(self, context): @@ -30,8 +30,10 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): kitsu_status = context.data["kitsu_task"].get("task_status") self.log.debug("Kitsu status: {}".format(kitsu_status)) - gazu.task.add_comment( + kitsu_comment = gazu.task.add_comment( context.data["kitsu_task"], kitsu_status, comment = publish_comment - ) \ No newline at end of file + ) + + context.data["kitsu_comment"] = kitsu_comment \ No newline at end of file diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index a800ca9b57..e69937e9bf 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import os import gazu import pyblish.api @@ -6,12 +7,27 @@ import pyblish.api class IntegrateKitsuVersion(pyblish.api.InstancePlugin): """Integrate Kitsu Review""" - order = pyblish.api.IntegratorOrder + order = pyblish.api.IntegratorOrder + 0.01 label = "Kitsu Review" # families = ["kitsu"] def process(self, instance): - pass - # gazu.task.upload_preview_file(preview, file_path, normalize_movie=True, client=) - # gazu.task.add_preview(task, comment, preview_file_path, normalize_movie=True, client=) \ No newline at end of file + context = instance.context + task = context.data["kitsu_task"] + comment = context.data["kitsu_comment"] + + for representation in instance.data.get("representations", []): + + local_path = representation.get("published_path") + self.log.info("*"*40) + self.log.info(local_path) + self.log.info(representation.get("tags", [])) + + # code = os.path.basename(local_path) + + if representation.get("tags", []): + continue + + # gazu.task.upload_preview_file(preview, file_path, normalize_movie=True) + gazu.task.add_preview(task, comment, local_path, normalize_movie=True) \ No newline at end of file diff --git a/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py b/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py index 9708ebb0dd..0597e8546a 100644 --- a/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py +++ b/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py @@ -1,10 +1,9 @@ -from typing import Optional import pyblish.api import gazu -class IntegrateKitsuNote(pyblish.api.ContextPlugin): - """Integrate Kitsu Note""" +class ValidateKitsuIntent(pyblish.api.ContextPlugin): + """Validate Kitsu Status""" order = pyblish.api.ValidatorOrder label = "Kitsu Intent/Status" From 2e0f6ce42d631674d910dc3caffc1654b6d781f2 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Wed, 20 Apr 2022 17:47:17 +0200 Subject: [PATCH 0173/1227] use task type if no task data in OP --- .../plugins/publish/collect_kitsu_entities.py | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index c5df20b349..c907c22e0f 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -21,7 +21,7 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): zoo_task_data = asset_data["tasks"][os.environ["AVALON_TASK"]].get("zou") if not zoo_task_data: - raise AssertionError("Zoo task data not found in OpenPype!") + self.log.warning("Zoo task data not found in OpenPype!") self.log.debug("Collected zoo task data: {}".format(zoo_task_data)) kitsu_project = gazu.project.get_project(zoo_asset_data["project_id"]) @@ -36,8 +36,29 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): context.data["kitsu_asset"] = kitsu_asset self.log.debug("Collect kitsu asset: {}".format(kitsu_asset)) - kitsu_task = gazu.task.get_task(zoo_task_data["id"]) - if not kitsu_task: - raise AssertionError("Task not not found in kitsu!") - context.data["kitsu_task"] = kitsu_task - self.log.debug("Collect kitsu task: {}".format(kitsu_task)) + if zoo_task_data: + kitsu_task = gazu.task.get_task(zoo_task_data["id"]) + if not kitsu_task: + raise AssertionError("Task not not found in kitsu!") + context.data["kitsu_task"] = kitsu_task + self.log.debug("Collect kitsu task: {}".format(kitsu_task)) + + else: + kitsu_task_type = gazu.task.get_task_type_by_name( + os.environ["AVALON_TASK"] + ) + if not kitsu_task_type: + raise AssertionError( + "Task type {} not found in Kitsu!".format( + os.environ["AVALON_TASK"] + ) + ) + + kitsu_task = gazu.task.get_task_by_name( + kitsu_asset, + kitsu_task_type + ) + if not kitsu_task: + raise AssertionError("Task not not found in kitsu!") + context.data["kitsu_task"] = kitsu_task + self.log.debug("Collect kitsu task: {}".format(kitsu_task)) \ No newline at end of file From f72cb8ba9e6201be23f09515e3e3190408fc30a0 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Wed, 20 Apr 2022 17:56:39 +0200 Subject: [PATCH 0174/1227] fix log out --- openpype/modules/kitsu/plugins/publish/other_kitsu_log_out.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/plugins/publish/other_kitsu_log_out.py b/openpype/modules/kitsu/plugins/publish/other_kitsu_log_out.py index ff76d9c4f6..d7e1616f8d 100644 --- a/openpype/modules/kitsu/plugins/publish/other_kitsu_log_out.py +++ b/openpype/modules/kitsu/plugins/publish/other_kitsu_log_out.py @@ -13,4 +13,4 @@ class KitsuLogOut(pyblish.api.ContextPlugin): label = "Kitsu Log Out" def process(self, context): - gazu.client.log_out() + gazu.log_out() From 3d6a6fcbf065c5ed000aea43c7ce4b5db91cd47a Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Wed, 20 Apr 2022 18:10:58 +0200 Subject: [PATCH 0175/1227] upload review --- .../plugins/publish/integrate_kitsu_review.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index e69937e9bf..23ee4a668e 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -1,15 +1,17 @@ # -*- coding: utf-8 -*- import os +from typing import Optional import gazu import pyblish.api -class IntegrateKitsuVersion(pyblish.api.InstancePlugin): +class IntegrateKitsuReview(pyblish.api.InstancePlugin): """Integrate Kitsu Review""" order = pyblish.api.IntegratorOrder + 0.01 label = "Kitsu Review" # families = ["kitsu"] + optional = True def process(self, instance): @@ -20,14 +22,16 @@ class IntegrateKitsuVersion(pyblish.api.InstancePlugin): for representation in instance.data.get("representations", []): local_path = representation.get("published_path") - self.log.info("*"*40) - self.log.info(local_path) - self.log.info(representation.get("tags", [])) - # code = os.path.basename(local_path) - - if representation.get("tags", []): + if 'review' not in representation.get("tags", []): continue - # gazu.task.upload_preview_file(preview, file_path, normalize_movie=True) - gazu.task.add_preview(task, comment, local_path, normalize_movie=True) \ No newline at end of file + self.log.debug("Found review at: {}".format(local_path)) + + gazu.task.add_preview( + task, + comment, + local_path, + normalize_movie=True + ) + self.log.info("Review upload on comment") From db8719b895cf553de6f24ad80f610fff28acfc21 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Wed, 20 Apr 2022 18:11:38 +0200 Subject: [PATCH 0176/1227] remove unused import --- .../modules/kitsu/plugins/publish/integrate_kitsu_review.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index 23ee4a668e..59b3bcf53e 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -import os -from typing import Optional import gazu import pyblish.api From 7787056c969b0ca91f2f1eee1d58c6c92de2e4f8 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Wed, 20 Apr 2022 18:14:14 +0200 Subject: [PATCH 0177/1227] Do some cleanup --- .../plugins/publish/collect_kitsu_entities.py | 37 +++++++------- .../plugins/publish/integrate_kitsu_file.py | 1 - .../plugins/publish/integrate_kitsu_note.py | 5 +- .../plugins/publish/integrate_kitsu_review.py | 8 +-- .../kitsu/plugins/publish/kitsu_plugin.py | 49 ------------------- .../plugins/publish/other_kitsu_log_out.py | 1 - .../plugins/publish/validate_kitsu_intent.py | 5 +- 7 files changed, 28 insertions(+), 78 deletions(-) delete mode 100644 openpype/modules/kitsu/plugins/publish/integrate_kitsu_file.py delete mode 100644 openpype/modules/kitsu/plugins/publish/kitsu_plugin.py diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index c907c22e0f..935b020641 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -14,35 +14,36 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): def process(self, context): asset_data = context.data["assetEntity"]["data"] - zoo_asset_data = asset_data.get("zou") - if not zoo_asset_data: - raise AssertionError("Zoo asset data not found in OpenPype!") - self.log.debug("Collected zoo asset data: {}".format(zoo_asset_data)) + zou_asset_data = asset_data.get("zou") + if not zou_asset_data: + raise AssertionError("Zou asset data not found in OpenPype!") + self.log.debug("Collected zou asset data: {}".format(zou_asset_data)) - zoo_task_data = asset_data["tasks"][os.environ["AVALON_TASK"]].get("zou") - if not zoo_task_data: - self.log.warning("Zoo task data not found in OpenPype!") - self.log.debug("Collected zoo task data: {}".format(zoo_task_data)) + zou_task_data = asset_data["tasks"][ + os.environ["AVALON_TASK"]].get("zou") + if not zou_task_data: + self.log.warning("Zou task data not found in OpenPype!") + self.log.debug("Collected zou task data: {}".format(zou_task_data)) - kitsu_project = gazu.project.get_project(zoo_asset_data["project_id"]) + kitsu_project = gazu.project.get_project(zou_asset_data["project_id"]) if not kitsu_project: - raise AssertionError("Project not not found in kitsu!") + raise AssertionError("Project not found in kitsu!") context.data["kitsu_project"] = kitsu_project self.log.debug("Collect kitsu project: {}".format(kitsu_project)) - kitsu_asset = gazu.asset.get_asset(zoo_asset_data["id"]) + kitsu_asset = gazu.asset.get_asset(zou_asset_data["id"]) if not kitsu_asset: - raise AssertionError("Asset not not found in kitsu!") + raise AssertionError("Asset not found in kitsu!") context.data["kitsu_asset"] = kitsu_asset self.log.debug("Collect kitsu asset: {}".format(kitsu_asset)) - if zoo_task_data: - kitsu_task = gazu.task.get_task(zoo_task_data["id"]) + if zou_task_data: + kitsu_task = gazu.task.get_task(zou_task_data["id"]) if not kitsu_task: - raise AssertionError("Task not not found in kitsu!") + raise AssertionError("Task not found in kitsu!") context.data["kitsu_task"] = kitsu_task self.log.debug("Collect kitsu task: {}".format(kitsu_task)) - + else: kitsu_task_type = gazu.task.get_task_type_by_name( os.environ["AVALON_TASK"] @@ -59,6 +60,6 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): kitsu_task_type ) if not kitsu_task: - raise AssertionError("Task not not found in kitsu!") + raise AssertionError("Task not found in kitsu!") context.data["kitsu_task"] = kitsu_task - self.log.debug("Collect kitsu task: {}".format(kitsu_task)) \ No newline at end of file + self.log.debug("Collect kitsu task: {}".format(kitsu_task)) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_file.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_file.py deleted file mode 100644 index 7c68785e9d..0000000000 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_file.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- \ No newline at end of file diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index afe388fd82..61e4d2454c 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -9,7 +9,6 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder label = "Kitsu Note and Status" # families = ["kitsu"] - # optional = True def process(self, context): @@ -31,8 +30,8 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): self.log.debug("Kitsu status: {}".format(kitsu_status)) kitsu_comment = gazu.task.add_comment( - context.data["kitsu_task"], - kitsu_status, + context.data["kitsu_task"], + kitsu_status, comment = publish_comment ) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index 59b3bcf53e..c38f14e8a4 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -19,17 +19,17 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): for representation in instance.data.get("representations", []): - local_path = representation.get("published_path") + review_path = representation.get("published_path") if 'review' not in representation.get("tags", []): continue - - self.log.debug("Found review at: {}".format(local_path)) + + self.log.debug("Found review at: {}".format(review_path)) gazu.task.add_preview( task, comment, - local_path, + review_path, normalize_movie=True ) self.log.info("Review upload on comment") diff --git a/openpype/modules/kitsu/plugins/publish/kitsu_plugin.py b/openpype/modules/kitsu/plugins/publish/kitsu_plugin.py deleted file mode 100644 index 5d6c76bc3f..0000000000 --- a/openpype/modules/kitsu/plugins/publish/kitsu_plugin.py +++ /dev/null @@ -1,49 +0,0 @@ -import os - -import gazu - -import pyblish.api - - -class CollectExampleAddon(pyblish.api.ContextPlugin): - order = pyblish.api.CollectorOrder + 0.4 - label = "Collect Kitsu" - - def process(self, context): - self.log.info("I'm in Kitsu's plugin!") - - -class IntegrateRig(pyblish.api.InstancePlugin): - """Copy files to an appropriate location where others may reach it""" - - order = pyblish.api.IntegratorOrder - families = ["model"] - - def process(self, instance): - - # Connect to server - gazu.client.set_host(os.environ["KITSU_SERVER"]) - - # Authenticate - gazu.log_in(os.environ["KITSU_LOGIN"], os.environ["KITSU_PWD"]) - - asset_data = instance.data["assetEntity"]["data"] - - # Get task - task_type = gazu.task.get_task_type_by_name(instance.data["task"]) - entity_task = gazu.task.get_task_by_entity( - asset_data["zou"], task_type - ) - - # Comment entity - gazu.task.add_comment( - entity_task, - entity_task["task_status_id"], - comment="Version {} has been published!\n".format( - instance.data["version"] - ) - # Add written comment in Pyblish - + "\n{}".format(instance.data["versionEntity"]["data"]["comment"]), - ) - - self.log.info("Version published to Kitsu successfully!") diff --git a/openpype/modules/kitsu/plugins/publish/other_kitsu_log_out.py b/openpype/modules/kitsu/plugins/publish/other_kitsu_log_out.py index d7e1616f8d..c4a5b390e0 100644 --- a/openpype/modules/kitsu/plugins/publish/other_kitsu_log_out.py +++ b/openpype/modules/kitsu/plugins/publish/other_kitsu_log_out.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -""".""" import gazu import pyblish.api diff --git a/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py b/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py index 0597e8546a..c82130b33b 100644 --- a/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py +++ b/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import pyblish.api import gazu @@ -9,7 +10,7 @@ class ValidateKitsuIntent(pyblish.api.ContextPlugin): label = "Kitsu Intent/Status" # families = ["kitsu"] optional = True - + def process(self, context): publish_status = context.data.get("intent", {}).get("value") @@ -19,7 +20,7 @@ class ValidateKitsuIntent(pyblish.api.ContextPlugin): kitsu_status = gazu.task.get_task_status_by_short_name(publish_status) if not kitsu_status: raise AssertionError( - "Status `{}` not not found in kitsu!".format(kitsu_status) + "Status `{}` not found in kitsu!".format(kitsu_status) ) self.log.debug("Collect kitsu status: {}".format(kitsu_status)) From d9062b762ab82b85fb048084ef27ebf863a92366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 5 May 2022 11:49:16 +0200 Subject: [PATCH 0178/1227] Update openpype/modules/kitsu/utils/update_zou_with_op.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/modules/kitsu/utils/update_zou_with_op.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_zou_with_op.py b/openpype/modules/kitsu/utils/update_zou_with_op.py index d1fcde5601..526159d101 100644 --- a/openpype/modules/kitsu/utils/update_zou_with_op.py +++ b/openpype/modules/kitsu/utils/update_zou_with_op.py @@ -6,7 +6,7 @@ from typing import List import gazu from pymongo import UpdateOne -from avalon.api import AvalonMongoDB +from openpype.pipeline import AvalonMongoDB from openpype.api import get_project_settings from openpype.modules.kitsu.utils.credentials import validate_credentials From 19b6cb7b6cbee03b60cf3d152af322a914f35514 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Thu, 5 May 2022 18:05:22 +0200 Subject: [PATCH 0179/1227] concatenation output formats fix --- .../plugins/publish/extract_review_slate.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 59150e9d4a..77b40b785d 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -226,13 +226,15 @@ class ExtractReviewSlate(openpype.api.Extractor): )) input_args.extend(["-timecode {}".format(offset_timecode)]) if use_legacy_code: + format_args = [] codec_args = repre["_profile"].get('codec', []) output_args.extend(codec_args) # preset's output data output_args.extend(repre["_profile"].get('output', [])) else: # Codecs are copied from source for whole input - codec_args = self._get_codec_args(repre) + format_args, codec_args = self._get_format_codec_args(repre) + output_args.extend(format_args) output_args.extend(codec_args) # make sure colors are correct @@ -338,6 +340,11 @@ class ExtractReviewSlate(openpype.api.Extractor): "-c", "copy", "-timecode", offset_timecode, ] + # NOTE: Added because of OP Atom demuxers + # Add format arguments if there are any + # - keep format of output + if format_args: + concat_args.extend(format_args) # Use arguments from ffmpeg preset source_ffmpeg_cmd = repre.get("ffmpeg_cmd") if source_ffmpeg_cmd: @@ -351,7 +358,7 @@ class ExtractReviewSlate(openpype.api.Extractor): concat_args.append(arg) # assumes arg has one parameter concat_args.append(args[indx + 1]) - # add output + # add final output path concat_args.append(output_path) if not input_audio: @@ -431,7 +438,7 @@ class ExtractReviewSlate(openpype.api.Extractor): return vf_back - def _get_codec_args(self, repre): +def _get_format_codec_args(self, repre): """Detect possible codec arguments from representation.""" codec_args = [] @@ -454,16 +461,12 @@ class ExtractReviewSlate(openpype.api.Extractor): return codec_args source_ffmpeg_cmd = repre.get("ffmpeg_cmd") - codec_args.extend( - get_ffmpeg_format_args(ffprobe_data, source_ffmpeg_cmd) - ) - codec_args.extend( - get_ffmpeg_codec_args( - ffprobe_data, source_ffmpeg_cmd, logger=self.log - ) + format_args = get_ffmpeg_format_args(ffprobe_data, source_ffmpeg_cmd) + codec_args = get_ffmpeg_codec_args( + ffprobe_data, source_ffmpeg_cmd, logger=self.log ) - return codec_args + return format_args, codec_args def _tc_offset(self, timecode, framerate=24.0, frame_offset=-1): """Offsets timecode by frame""" From 2e7c82316a24d98c152d5571e20bcde512a1924e Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Thu, 5 May 2022 18:06:43 +0200 Subject: [PATCH 0180/1227] mad dog fix --- openpype/plugins/publish/extract_review_slate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 77b40b785d..17ac4b68bc 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -438,6 +438,7 @@ class ExtractReviewSlate(openpype.api.Extractor): return vf_back + def _get_format_codec_args(self, repre): """Detect possible codec arguments from representation.""" codec_args = [] From fe514cf385bf99683ef87d0fa78bd022f9fa69dd Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Thu, 5 May 2022 19:30:38 +0200 Subject: [PATCH 0181/1227] more fixes --- openpype/plugins/publish/extract_review_slate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 17ac4b68bc..832799601c 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -439,7 +439,7 @@ class ExtractReviewSlate(openpype.api.Extractor): return vf_back -def _get_format_codec_args(self, repre): + def _get_format_codec_args(self, repre): """Detect possible codec arguments from representation.""" codec_args = [] From e9afd17b296677b1558b83469cdf68cd3cf4e555 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 6 May 2022 10:02:20 +0200 Subject: [PATCH 0182/1227] 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 40d487221aa0ff0c60e89132db6aad7b95a31aa4 Mon Sep 17 00:00:00 2001 From: DMO Date: Sat, 7 May 2022 08:05:59 +0900 Subject: [PATCH 0183/1227] OP/MV: Export correct node , export composition with absolute path. --- .../maya/plugins/publish/extract_multiverse_usd.py | 6 ------ .../plugins/publish/extract_multiverse_usd_comp.py | 12 ++++++------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py index 4e4efdc32c..9726c7e14c 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py @@ -149,12 +149,6 @@ class ExtractMultiverseUsd(openpype.api.Extractor): with maintained_selection(): members = instance.data("setMembers") - members = cmds.ls(members, - dag=True, - shapes=True, - type=("mesh"), - noIntermediate=True, - long=True) self.log.info('Collected object {}'.format(members)) import multiverse diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py index 8fccc412e6..685e3d4592 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py @@ -90,12 +90,6 @@ class ExtractMultiverseUsdComposition(openpype.api.Extractor): with maintained_selection(): members = instance.data("setMembers") - members = cmds.ls(members, - dag=True, - shapes=True, - type="mvUsdCompoundShape", - noIntermediate=True, - long=True) self.log.info('Collected object {}'.format(members)) import multiverse @@ -119,6 +113,12 @@ class ExtractMultiverseUsdComposition(openpype.api.Extractor): time_opts.framePerSecond = fps comp_write_opts = multiverse.CompositionWriteOptions() + if not hasattr(comp_write_opts,"forceAbsolutePaths"): + self.log.warning("multiverse.CompositionWriteOptions is " + + "missing forceAbsolutePaths', extract will yield " + + "inccorect reference paths.") + else: + comp_write_opts.forceAbsolutePaths = True options_discard_keys = { 'numTimeSamples', 'timeSamplesSpan', From b796c2563decaf953f130e93df75d79d8dae54d2 Mon Sep 17 00:00:00 2001 From: DMO Date: Mon, 9 May 2022 16:52:51 +0900 Subject: [PATCH 0184/1227] PEP Formatting and typo fixing. --- .../maya/plugins/publish/extract_multiverse_usd_comp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py index 685e3d4592..7bd7768da6 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py @@ -113,10 +113,10 @@ class ExtractMultiverseUsdComposition(openpype.api.Extractor): time_opts.framePerSecond = fps comp_write_opts = multiverse.CompositionWriteOptions() - if not hasattr(comp_write_opts,"forceAbsolutePaths"): + if not hasattr(comp_write_opts, "forceAbsolutePaths"): self.log.warning("multiverse.CompositionWriteOptions is " + - "missing forceAbsolutePaths', extract will yield " + - "inccorect reference paths.") + "missing 'forceAbsolutePaths', extract " + + "will yield incorrect reference paths.") else: comp_write_opts.forceAbsolutePaths = True options_discard_keys = { From 8bb0c2779113aaa4ec1b8681a3d89232912fface Mon Sep 17 00:00:00 2001 From: DMO Date: Mon, 9 May 2022 17:21:47 +0900 Subject: [PATCH 0185/1227] Removing attr check and explaining in a comment. --- .../publish/extract_multiverse_usd_comp.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py index 7bd7768da6..e6bc0c68b5 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py @@ -113,12 +113,18 @@ class ExtractMultiverseUsdComposition(openpype.api.Extractor): time_opts.framePerSecond = fps comp_write_opts = multiverse.CompositionWriteOptions() - if not hasattr(comp_write_opts, "forceAbsolutePaths"): - self.log.warning("multiverse.CompositionWriteOptions is " + - "missing 'forceAbsolutePaths', extract " + - "will yield incorrect reference paths.") - else: - comp_write_opts.forceAbsolutePaths = True + + """ + OP tells MV to write to a staging directory, and then moves the + file to it's final publish directory. By default, MV write relative + paths, but these paths will break when the referencing file moves. + This option forces writes to absolute paths, which is ok within OP + because all published assets have static paths, and MV can only + reference published assets. When a proper UsdAssetResolver is used, + this won't be needed. + """ + comp_write_opts.forceAbsolutePaths = True + options_discard_keys = { 'numTimeSamples', 'timeSamplesSpan', From 7cf7f5ee7434c0a818a161de74677422f0a4f667 Mon Sep 17 00:00:00 2001 From: DMO Date: Tue, 10 May 2022 12:18:52 +0900 Subject: [PATCH 0186/1227] Adding support for choosing .usd/.usda/.usdz. Fix a bug in extract override where it was selecting a locator that shouldn't be selected. --- .../plugins/create/create_multiverse_usd.py | 3 ++- .../create/create_multiverse_usd_comp.py | 1 + .../create/create_multiverse_usd_over.py | 3 ++- .../plugins/publish/extract_multiverse_usd.py | 18 ++++++++++----- .../publish/extract_multiverse_usd_comp.py | 18 ++++++++++----- .../publish/extract_multiverse_usd_over.py | 22 +++++++++++++------ 6 files changed, 46 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py index b2266e5a57..e64d7d90b8 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py @@ -2,7 +2,7 @@ from openpype.hosts.maya.api import plugin, lib class CreateMultiverseUsd(plugin.Creator): - """Multiverse USD data""" + """Create Multiverse USD Asset""" name = "usdMain" label = "Multiverse USD" @@ -15,6 +15,7 @@ class CreateMultiverseUsd(plugin.Creator): # Add animation data first, since it maintains order. self.data.update(lib.collect_animation_data(True)) + self.data["fileFormat"] = ["usd", "usda", "usdz"] self.data["stripNamespaces"] = False self.data["mergeTransformAndShape"] = False self.data["writeAncestors"] = True diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py index 77b808c459..accdffad46 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py @@ -15,6 +15,7 @@ class CreateMultiverseUsdComp(plugin.Creator): # Add animation data first, since it maintains order. self.data.update(lib.collect_animation_data(True)) + self.data["fileFormat"] = ["usd", "usda"] self.data["stripNamespaces"] = False self.data["mergeTransformAndShape"] = False self.data["flattenContent"] = False diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py index bb82ab2039..25892f68bb 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py @@ -2,7 +2,7 @@ from openpype.hosts.maya.api import plugin, lib class CreateMultiverseUsdOver(plugin.Creator): - """Multiverse USD data""" + """Create Multiverse USD Override""" name = "usdOverrideMain" label = "Multiverse USD Override" @@ -15,6 +15,7 @@ class CreateMultiverseUsdOver(plugin.Creator): # Add animation data first, since it maintains order. self.data.update(lib.collect_animation_data(True)) + self.data["fileFormat"] = ["usd", "usda"] self.data["writeAll"] = False self.data["writeTransforms"] = True self.data["writeVisibility"] = True diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py index 9726c7e14c..74fcf2bee7 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py @@ -13,6 +13,8 @@ class ExtractMultiverseUsd(openpype.api.Extractor): label = "Extract Multiverse USD" hosts = ["maya"] families = ["usd"] + scene_type = "usd" + file_formats = ["usd", "usda", "usdz"] @property def options(self): @@ -129,13 +131,19 @@ class ExtractMultiverseUsd(openpype.api.Extractor): return options + def get_file_format(self, instance): + fileFormat = instance.data["fileFormat"] + if fileFormat in range(len(self.file_formats)): + self.scene_type = self.file_formats[fileFormat] + def process(self, instance): - # Load plugin firstly + # Load plugin first cmds.loadPlugin("MultiverseForMaya", quiet=True) # Define output file path staging_dir = self.staging_dir(instance) - file_name = "{}.usd".format(instance.name) + self.get_file_format(instance) + file_name = "{0}.{1}".format(instance.name, self.scene_type) file_path = os.path.join(staging_dir, file_name) file_path = file_path.replace('\\', '/') @@ -193,10 +201,10 @@ class ExtractMultiverseUsd(openpype.api.Extractor): instance.data["representations"] = [] representation = { - 'name': 'usd', - 'ext': 'usd', + 'name': self.scene_type, + 'ext': self.scene_type, 'files': file_name, - "stagingDir": staging_dir + 'stagingDir': staging_dir } instance.data["representations"].append(representation) diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py index e6bc0c68b5..c686b2a600 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py @@ -12,6 +12,8 @@ class ExtractMultiverseUsdComposition(openpype.api.Extractor): label = "Extract Multiverse USD Composition" hosts = ["maya"] families = ["usdComposition"] + scene_type = "usd" + file_formats = ["usd", "usda"] @property def options(self): @@ -70,13 +72,19 @@ class ExtractMultiverseUsdComposition(openpype.api.Extractor): return options + def get_file_format(self, instance): + fileFormat = instance.data["fileFormat"] + if fileFormat in range(len(self.file_formats)): + self.scene_type = self.file_formats[fileFormat] + def process(self, instance): - # Load plugin firstly + # Load plugin first cmds.loadPlugin("MultiverseForMaya", quiet=True) # Define output file path staging_dir = self.staging_dir(instance) - file_name = "{}.usd".format(instance.name) + self.get_file_format(instance) + file_name = "{0}.{1}".format(instance.name, self.scene_type) file_path = os.path.join(staging_dir, file_name) file_path = file_path.replace('\\', '/') @@ -146,10 +154,10 @@ class ExtractMultiverseUsdComposition(openpype.api.Extractor): instance.data["representations"] = [] representation = { - 'name': 'usd', - 'ext': 'usd', + 'name': self.scene_type, + 'ext': self.scene_type, 'files': file_name, - "stagingDir": staging_dir + 'stagingDir': staging_dir } instance.data["representations"].append(representation) diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py index ce0e8a392a..77680d09f9 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py @@ -12,6 +12,8 @@ class ExtractMultiverseUsdOverride(openpype.api.Extractor): label = "Extract Multiverse USD Override" hosts = ["maya"] families = ["usdOverride"] + scene_type = "usd" + file_formats = ["usd", "usda"] @property def options(self): @@ -57,13 +59,19 @@ class ExtractMultiverseUsdOverride(openpype.api.Extractor): "timeSamplesSpan": 0.0 } + def get_file_format(self, instance): + fileFormat = instance.data["fileFormat"] + if fileFormat in range(len(self.file_formats)): + self.scene_type = self.file_formats[fileFormat] + def process(self, instance): - # Load plugin firstly + # Load plugin first cmds.loadPlugin("MultiverseForMaya", quiet=True) # Define output file path staging_dir = self.staging_dir(instance) - file_name = "{}.usda".format(instance.name) + self.get_file_format(instance) + file_name = "{0}.{1}".format(instance.name, self.scene_type) file_path = os.path.join(staging_dir, file_name) file_path = file_path.replace("\\", "/") @@ -78,7 +86,7 @@ class ExtractMultiverseUsdOverride(openpype.api.Extractor): members = instance.data("setMembers") members = cmds.ls(members, dag=True, - shapes=True, + shapes=False, type="mvUsdCompoundShape", noIntermediate=True, long=True) @@ -128,10 +136,10 @@ class ExtractMultiverseUsdOverride(openpype.api.Extractor): instance.data["representations"] = [] representation = { - "name": "usd", - "ext": "usd", - "files": file_name, - "stagingDir": staging_dir + 'name': self.scene_type, + 'ext': self.scene_type, + 'files': file_name, + 'stagingDir': staging_dir } instance.data["representations"].append(representation) From f7fc6a3e7007d80d0f75ccedcf381267c676f089 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 10 May 2022 16:52:13 +0200 Subject: [PATCH 0187/1227] flame: fixing attr_name issue --- openpype/hosts/flame/api/lib.py | 2 +- openpype/hosts/flame/otio/flame_export.py | 91 +++++------------------ 2 files changed, 21 insertions(+), 72 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index c7c444c1fb..2e9b535764 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -560,7 +560,7 @@ def get_segment_attributes(segment): if not hasattr(segment, attr_name): continue attr = getattr(segment, attr_name) - segment_attrs_data[attr] = str(attr).replace("+", ":") + segment_attrs_data[attr_name] = str(attr).replace("+", ":") if attr_name in ["record_in", "record_out"]: clip_data[attr_name] = attr.relative_frame diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index 4fe05ec1d8..d84ee98256 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -94,83 +94,30 @@ def create_otio_time_range(start_frame, frame_duration, fps): def _get_metadata(item): if hasattr(item, 'metadata'): - if not item.metadata: - return {} - return {key: value for key, value in dict(item.metadata)} + return dict(dict(item.metadata)) if item.metadata else {} return {} -def create_time_effects(otio_clip, item): - # todo #2426: add retiming effects to export - # get all subtrack items - # subTrackItems = flatten(track_item.parent().subTrackItems()) - # speed = track_item.playbackSpeed() +# def create_time_effects(otio_clip, clip_data): +# otio_effect = None - # otio_effect = None - # # retime on track item - # if speed != 1.: - # # make effect - # otio_effect = otio.schema.LinearTimeWarp() - # otio_effect.name = "Speed" - # otio_effect.time_scalar = speed - # otio_effect.metadata = {} +# # retime on track item +# if speed != 1.: +# # make effect +# otio_effect = otio.schema.LinearTimeWarp() +# otio_effect.name = "Speed" +# otio_effect.time_scalar = speed +# otio_effect.metadata = {} - # # freeze frame effect - # if speed == 0.: - # otio_effect = otio.schema.FreezeFrame() - # otio_effect.name = "FreezeFrame" - # otio_effect.metadata = {} +# # freeze frame effect +# if speed == 0.: +# otio_effect = otio.schema.FreezeFrame() +# otio_effect.name = "FreezeFrame" +# otio_effect.metadata = {} - # if otio_effect: - # # add otio effect to clip effects - # otio_clip.effects.append(otio_effect) - - # # loop through and get all Timewarps - # for effect in subTrackItems: - # if ((track_item not in effect.linkedItems()) - # and (len(effect.linkedItems()) > 0)): - # continue - # # avoid all effect which are not TimeWarp and disabled - # if "TimeWarp" not in effect.name(): - # continue - - # if not effect.isEnabled(): - # continue - - # node = effect.node() - # name = node["name"].value() - - # # solve effect class as effect name - # _name = effect.name() - # if "_" in _name: - # effect_name = re.sub(r"(?:_)[_0-9]+", "", _name) # more numbers - # else: - # effect_name = re.sub(r"\d+", "", _name) # one number - - # metadata = {} - # # add knob to metadata - # for knob in ["lookup", "length"]: - # value = node[knob].value() - # animated = node[knob].isAnimated() - # if animated: - # value = [ - # ((node[knob].getValueAt(i)) - i) - # for i in range( - # track_item.timelineIn(), - # track_item.timelineOut() + 1) - # ] - - # metadata[knob] = value - - # # make effect - # otio_effect = otio.schema.TimeEffect() - # otio_effect.name = name - # otio_effect.effect_name = effect_name - # otio_effect.metadata = metadata - - # # add otio effect to clip effects - # otio_clip.effects.append(otio_effect) - pass +# if otio_effect: +# # add otio effect to clip effects +# otio_clip.effects.append(otio_effect) def _get_marker_color(flame_colour): @@ -363,6 +310,8 @@ def create_otio_clip(clip_data): if MARKERS_INCLUDE: create_otio_markers(otio_clip, segment) + create_time_effects(otio_clip, clip_data) + return otio_clip From 21b83e341b754506d982c8ec05cb28d647c296cd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 10 May 2022 16:59:20 +0200 Subject: [PATCH 0188/1227] flame" adding empty families to workfile instance --- openpype/hosts/flame/plugins/publish/collect_timeline_otio.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py b/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py index f2ae1f62a9..0a9b0db334 100644 --- a/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py @@ -39,7 +39,8 @@ class CollecTimelineOTIO(pyblish.api.ContextPlugin): "name": subset_name, "asset": asset_doc["name"], "subset": subset_name, - "family": "workfile" + "family": "workfile", + "families": [] } # create instance with workfile From 9483adc91eaa0958f14090ba8af3e773b80247f9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 10 May 2022 16:59:39 +0200 Subject: [PATCH 0189/1227] Flame: commenting out effects - not yet implemented --- openpype/hosts/flame/otio/flame_export.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index d84ee98256..fc960b670c 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -310,7 +310,7 @@ def create_otio_clip(clip_data): if MARKERS_INCLUDE: create_otio_markers(otio_clip, segment) - create_time_effects(otio_clip, clip_data) + # create_time_effects(otio_clip, clip_data) return otio_clip From de31b8d1df9458f1daa1ac9a385e648f588f4cf6 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Tue, 10 May 2022 20:14:02 +0200 Subject: [PATCH 0190/1227] add silent audio to slate --- .../plugins/publish/extract_review_slate.py | 74 ++++++++++++------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 832799601c..2ef5308225 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -1,5 +1,4 @@ import os -import shutil import openpype.api import pyblish from openpype.lib import ( @@ -11,6 +10,7 @@ from openpype.lib import ( get_ffmpeg_format_args, ) + class ExtractReviewSlate(openpype.api.Extractor): """ Will add slate frame at the start of the video files @@ -122,10 +122,14 @@ class ExtractReviewSlate(openpype.api.Extractor): if stream["channel_layout"]: audio_channel_layout = str( stream.get("channel_layout")) + if stream["codec_name"]: + audio_codec = str( + stream.get("codec_name")) if ( audio_channels and audio_sample_rate and audio_channel_layout + and audio_codec ): input_audio = True break @@ -200,16 +204,6 @@ class ExtractReviewSlate(openpype.api.Extractor): input_args.append("-loop 1 -i {}".format( openpype.lib.path_to_subprocess_arg(slate_path))) - # if input has an audio, add silent audio to the slate - if input_audio: - input_args.extend( - ["-f lavfi -i anullsrc=r={}:cl={}:d={}".format( - audio_sample_rate, - audio_channel_layout, - one_frame_duration - )] - ) - input_args.extend(["-r {}".format(input_frame_rate)]) input_args.extend(["-frames:v 1"]) # add timecode from source to the slate, substract one frame @@ -314,6 +308,41 @@ class ExtractReviewSlate(openpype.api.Extractor): slate_subprocess_cmd, shell=True, logger=self.log ) + # Create slate with silent audio track + if input_audio: + # silent slate output path + slate_silent_path = slate_path.replace(".png", "Silent" + ext) + _remove_at_end.append(slate_silent_path) + + slate_silent_args = [ + ffmpeg_path, + "-i", slate_v_path, + "-f", "lavfi", "-i", + "anullsrc=r={}:cl={}:d={}".format( + audio_sample_rate, + audio_channel_layout, + one_frame_duration + ), + "-c:v", "copy", + "-c:a", audio_codec, + "-map", "0:v", + "-map", "1:a", + "-shortest", + "-y", + slate_silent_path + ] + slate_silent_subprocess_cmd = " ".join(slate_silent_args) + # run slate generation subprocess + self.log.debug( + "Silent Slate Executing: {}".format(slate_silent_subprocess_cmd) + ) + openpype.api.run_subprocess( + slate_silent_subprocess_cmd, shell=True, logger=self.log + ) + + # replace slate with silent slate for concat + slate_v_path = slate_silent_path + # create ffmpeg concat text file path conc_text_file = input_file.replace(ext, "") + "_concat" + ".txt" conc_text_path = os.path.join( @@ -361,21 +390,13 @@ class ExtractReviewSlate(openpype.api.Extractor): # add final output path concat_args.append(output_path) - if not input_audio: - # ffmpeg concat subprocess - self.log.debug( - "Executing concat: {}".format(" ".join(concat_args)) - ) - openpype.api.run_subprocess( - concat_args, logger=self.log - ) - else: - self.log.warning( - "Audio found. Creating slate with audio" - " is not supported at this time. Outputing slate-less" - ":\n{}".format(input_file)) - # skip concatenating slate, use slate-less file instead - shutil.copyfile(input_path, output_path) + # ffmpeg concat subprocess + self.log.debug( + "Executing concat: {}".format(" ".join(concat_args)) + ) + openpype.api.run_subprocess( + concat_args, logger=self.log + ) self.log.debug("__ repre[tags]: {}".format(repre["tags"])) repre_update = { @@ -438,7 +459,6 @@ class ExtractReviewSlate(openpype.api.Extractor): return vf_back - def _get_format_codec_args(self, repre): """Detect possible codec arguments from representation.""" codec_args = [] From b0c9e8a29d4c16d3276989fee37c8d8762d67809 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Tue, 10 May 2022 20:35:04 +0200 Subject: [PATCH 0191/1227] hound --- openpype/plugins/publish/extract_review_slate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 2ef5308225..d3f8934620 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -334,8 +334,8 @@ class ExtractReviewSlate(openpype.api.Extractor): slate_silent_subprocess_cmd = " ".join(slate_silent_args) # run slate generation subprocess self.log.debug( - "Silent Slate Executing: {}".format(slate_silent_subprocess_cmd) - ) + "Silent Slate Executing: {}".format( + slate_silent_subprocess_cmd)) openpype.api.run_subprocess( slate_silent_subprocess_cmd, shell=True, logger=self.log ) From ff37e6135b6a2e22fede236268683ae3e5e1936c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 10 May 2022 20:51:28 +0200 Subject: [PATCH 0192/1227] global: removing `clip` family from excluded - obsolete --- openpype/plugins/publish/integrate_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index bf13a4050e..353314fff2 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -113,7 +113,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "usdOverride", "simpleUnrealTexture" ] - exclude_families = ["clip", "render.farm"] + exclude_families = ["render.farm"] db_representation_context_keys = [ "project", "asset", "task", "subset", "version", "representation", "family", "hierarchy", "task", "username" From ba1587efb6faf0e8cf21bf949fa36dbc5061d780 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Wed, 11 May 2022 12:22:34 +0200 Subject: [PATCH 0193/1227] safer code --- .../plugins/publish/extract_review_slate.py | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index d3f8934620..b84dbbc2ac 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -79,7 +79,7 @@ class ExtractReviewSlate(openpype.api.Extractor): ) # Get video metadata for stream in streams: - input_timecode = None + input_timecode = "" input_width = None input_height = None input_frame_rate = None @@ -89,18 +89,20 @@ class ExtractReviewSlate(openpype.api.Extractor): tags = stream.get("tags") or {} input_timecode = tags.get("timecode") or "" if "width" in stream and "height" in stream: - input_width = int(stream.get("width")) - input_height = int(stream.get("height")) + input_width = stream.get("width") + input_height = stream.get("height") if "r_frame_rate" in stream: # get frame rate in a form of # x/y, like 24000/1001 for 23.976 - input_frame_rate = str(stream.get("r_frame_rate")) + input_frame_rate = stream.get("r_frame_rate") if ( - input_timecode - and input_width + input_width and input_height and input_frame_rate ): + input_width = int(input_width) + input_height = int(input_height) + input_frame_rate = str(input_frame_rate) break # Raise exception of any stream didn't define input resolution if input_width is None: @@ -131,7 +133,10 @@ class ExtractReviewSlate(openpype.api.Extractor): and audio_channel_layout and audio_codec ): - input_audio = True + input_audio = str(input_audio) + audio_sample_rate = str(audio_sample_rate) + audio_channel_layout = str(audio_channel_layout) + audio_codec = str(audio_codec) break # Get duration of one frame in micro seconds one_frame_duration = "40000us" @@ -207,7 +212,7 @@ class ExtractReviewSlate(openpype.api.Extractor): input_args.extend(["-r {}".format(input_frame_rate)]) input_args.extend(["-frames:v 1"]) # add timecode from source to the slate, substract one frame - offset_timecode = "00:00:00:00" + offset_timecode = "" if input_timecode: offset_timecode = str(input_timecode) offset_timecode = self._tc_offset( @@ -311,7 +316,8 @@ class ExtractReviewSlate(openpype.api.Extractor): # Create slate with silent audio track if input_audio: # silent slate output path - slate_silent_path = slate_path.replace(".png", "Silent" + ext) + slate_silent_path = "_silent".join( + os.path.splitext(slate_v_path)) _remove_at_end.append(slate_silent_path) slate_silent_args = [ @@ -331,13 +337,12 @@ class ExtractReviewSlate(openpype.api.Extractor): "-y", slate_silent_path ] - slate_silent_subprocess_cmd = " ".join(slate_silent_args) # run slate generation subprocess self.log.debug( "Silent Slate Executing: {}".format( - slate_silent_subprocess_cmd)) + " ".join(slate_silent_args))) openpype.api.run_subprocess( - slate_silent_subprocess_cmd, shell=True, logger=self.log + slate_silent_args, logger=self.log ) # replace slate with silent slate for concat @@ -367,8 +372,9 @@ class ExtractReviewSlate(openpype.api.Extractor): "-safe", "0", "-i", conc_text_path, "-c", "copy", - "-timecode", offset_timecode, ] + if offset_timecode: + concat_args.extend(["-timecode", offset_timecode]) # NOTE: Added because of OP Atom demuxers # Add format arguments if there are any # - keep format of output From 26fcd19a5d8bed9849c924c62a949b25e62c6189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 11 May 2022 12:29:30 +0200 Subject: [PATCH 0194/1227] use dbcon.Session instead of collection --- openpype/modules/kitsu/utils/sync_service.py | 83 +++++++++---------- .../modules/kitsu/utils/update_op_with_zou.py | 42 +++++----- .../modules/kitsu/utils/update_zou_with_op.py | 6 +- 3 files changed, 63 insertions(+), 68 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 01596e2667..ad18e1f391 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -119,8 +119,8 @@ class Listener: # Write into DB if update_project: - project_col = self.dbcon.database[project_name] - project_col.bulk_write([update_project]) + self.dbcon = self.dbcon.database[project_name] + self.dbcon.bulk_write([update_project]) def _delete_project(self, data): """Delete project.""" @@ -129,28 +129,27 @@ class Listener: project = gazu.project.get_project(data["project_id"]) # Delete project collection - project_col = self.dbcon.database[project["name"]] - project_col.drop() + # self.dbcon = self.dbcon.database[project["name"]] # == Asset == def _new_asset(self, data): """Create new asset into OP DB.""" # Get project entity - project_col = set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data["project_id"]) # Get gazu entity asset = gazu.asset.get_asset(data["asset_id"]) # Insert doc in DB - project_col.insert_one(create_op_asset(asset)) + self.dbcon.insert_one(create_op_asset(asset)) # Update self._update_asset(data) def _update_asset(self, data): """Update asset into OP DB.""" - project_col = set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data["project_id"]) project_doc = self.dbcon.find_one({"type": "project"}) # Get gazu entity @@ -160,23 +159,23 @@ class Listener: # Query all assets of the local project zou_ids_and_asset_docs = { asset_doc["data"]["zou"]["id"]: asset_doc - for asset_doc in project_col.find({"type": "asset"}) + for asset_doc in self.dbcon.find({"type": "asset"}) if asset_doc["data"].get("zou", {}).get("id") } zou_ids_and_asset_docs[asset["project_id"]] = project_doc # Update asset_doc_id, asset_update = update_op_assets( - project_col[asset], zou_ids_and_asset_docs + self.dbcon[asset], zou_ids_and_asset_docs )[0] - project_col.update_one({"_id": asset_doc_id}, asset_update) + self.dbcon.update_one({"_id": asset_doc_id}, asset_update) def _delete_asset(self, data): """Delete asset of OP DB.""" - project_col = set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data["project_id"]) # Delete - project_col.delete_one( + self.dbcon.delete_one( {"type": "asset", "data.zou.id": data["asset_id"]} ) @@ -184,20 +183,20 @@ class Listener: def _new_episode(self, data): """Create new episode into OP DB.""" # Get project entity - project_col = set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data["project_id"]) # Get gazu entity episode = gazu.shot.get_episode(data["episode_id"]) # Insert doc in DB - project_col.insert_one(create_op_asset(episode)) + self.dbcon.insert_one(create_op_asset(episode)) # Update self._update_episode(data) def _update_episode(self, data): """Update episode into OP DB.""" - project_col = set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data["project_id"]) project_doc = self.dbcon.find_one({"type": "project"}) # Get gazu entity @@ -207,24 +206,24 @@ class Listener: # Query all assets of the local project zou_ids_and_asset_docs = { asset_doc["data"]["zou"]["id"]: asset_doc - for asset_doc in project_col.find({"type": "asset"}) + for asset_doc in self.dbcon.find({"type": "asset"}) if asset_doc["data"].get("zou", {}).get("id") } zou_ids_and_asset_docs[episode["project_id"]] = project_doc # Update asset_doc_id, asset_update = update_op_assets( - project_col, [episode], zou_ids_and_asset_docs + self.dbcon, [episode], zou_ids_and_asset_docs )[0] - project_col.update_one({"_id": asset_doc_id}, asset_update) + self.dbcon.update_one({"_id": asset_doc_id}, asset_update) def _delete_episode(self, data): """Delete shot of OP DB.""" - project_col = set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data["project_id"]) print("delete episode") # TODO check bugfix # Delete - project_col.delete_one( + self.dbcon.delete_one( {"type": "asset", "data.zou.id": data["episode_id"]} ) @@ -232,20 +231,20 @@ class Listener: def _new_sequence(self, data): """Create new sequnce into OP DB.""" # Get project entity - project_col = set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data["project_id"]) # Get gazu entity sequence = gazu.shot.get_sequence(data["sequence_id"]) # Insert doc in DB - project_col.insert_one(create_op_asset(sequence)) + self.dbcon.insert_one(create_op_asset(sequence)) # Update self._update_sequence(data) def _update_sequence(self, data): """Update sequence into OP DB.""" - project_col = set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data["project_id"]) project_doc = self.dbcon.find_one({"type": "project"}) # Get gazu entity @@ -255,24 +254,24 @@ class Listener: # Query all assets of the local project zou_ids_and_asset_docs = { asset_doc["data"]["zou"]["id"]: asset_doc - for asset_doc in project_col.find({"type": "asset"}) + for asset_doc in self.dbcon.find({"type": "asset"}) if asset_doc["data"].get("zou", {}).get("id") } zou_ids_and_asset_docs[sequence["project_id"]] = project_doc # Update asset_doc_id, asset_update = update_op_assets( - project_col, [sequence], zou_ids_and_asset_docs + self.dbcon, [sequence], zou_ids_and_asset_docs )[0] - project_col.update_one({"_id": asset_doc_id}, asset_update) + self.dbcon.update_one({"_id": asset_doc_id}, asset_update) def _delete_sequence(self, data): """Delete sequence of OP DB.""" - project_col = set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data["project_id"]) print("delete sequence") # TODO check bugfix # Delete - project_col.delete_one( + self.dbcon.delete_one( {"type": "asset", "data.zou.id": data["sequence_id"]} ) @@ -280,20 +279,20 @@ class Listener: def _new_shot(self, data): """Create new shot into OP DB.""" # Get project entity - project_col = set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data["project_id"]) # Get gazu entity shot = gazu.shot.get_shot(data["shot_id"]) # Insert doc in DB - project_col.insert_one(create_op_asset(shot)) + self.dbcon.insert_one(create_op_asset(shot)) # Update self._update_shot(data) def _update_shot(self, data): """Update shot into OP DB.""" - project_col = set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data["project_id"]) project_doc = self.dbcon.find_one({"type": "project"}) # Get gazu entity @@ -303,23 +302,23 @@ class Listener: # Query all assets of the local project zou_ids_and_asset_docs = { asset_doc["data"]["zou"]["id"]: asset_doc - for asset_doc in project_col.find({"type": "asset"}) + for asset_doc in self.dbcon.find({"type": "asset"}) if asset_doc["data"].get("zou", {}).get("id") } zou_ids_and_asset_docs[shot["project_id"]] = project_doc # Update asset_doc_id, asset_update = update_op_assets( - project_col, [shot], zou_ids_and_asset_docs + self.dbcon, [shot], zou_ids_and_asset_docs )[0] - project_col.update_one({"_id": asset_doc_id}, asset_update) + self.dbcon.update_one({"_id": asset_doc_id}, asset_update) def _delete_shot(self, data): """Delete shot of OP DB.""" - project_col = set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data["project_id"]) # Delete - project_col.delete_one( + self.dbcon.delete_one( {"type": "asset", "data.zou.id": data["shot_id"]} ) @@ -327,13 +326,13 @@ class Listener: def _new_task(self, data): """Create new task into OP DB.""" # Get project entity - project_col = set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data["project_id"]) # Get gazu entity task = gazu.task.get_task(data["task_id"]) # Find asset doc - asset_doc = project_col.find_one( + asset_doc = self.dbcon.find_one( {"type": "asset", "data.zou.id": task["entity"]["id"]} ) @@ -341,7 +340,7 @@ class Listener: asset_tasks = asset_doc["data"].get("tasks") task_type_name = task["task_type"]["name"] asset_tasks[task_type_name] = {"type": task_type_name, "zou": task} - project_col.update_one( + self.dbcon.update_one( {"_id": asset_doc["_id"]}, {"$set": {"data.tasks": asset_tasks}} ) @@ -352,10 +351,10 @@ class Listener: def _delete_task(self, data): """Delete task of OP DB.""" - project_col = set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data["project_id"]) # Find asset doc - asset_docs = [doc for doc in project_col.find({"type": "asset"})] + asset_docs = [doc for doc in self.dbcon.find({"type": "asset"})] for doc in asset_docs: # Match task for name, task in doc["data"]["tasks"].items(): @@ -365,7 +364,7 @@ class Listener: asset_tasks.pop(name) # Delete task in DB - project_col.update_one( + self.dbcon.update_one( {"_id": doc["_id"]}, {"$set": {"data.tasks": asset_tasks}}, ) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 25c89800d4..6e82ffbd05 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -29,18 +29,17 @@ def create_op_asset(gazu_entity: dict) -> dict: } -def set_op_project(dbcon, project_id) -> Collection: +def set_op_project(dbcon: AvalonMongoDB, project_id: str): """Set project context. - :param dbcon: Connection to DB. - :param project_id: Project zou ID + Args: + dbcon (AvalonMongoDB): Connection to DB. + project_id (str): Project zou ID """ project = gazu.project.get_project(project_id) project_name = project["name"] dbcon.Session["AVALON_PROJECT"] = project_name - return dbcon.database[project_name] - def update_op_assets( project_col: Collection, @@ -258,28 +257,25 @@ def sync_all_project(login: str, password: str): dbcon.install() all_projects = gazu.project.all_open_projects() for project in all_projects: - sync_project_from_kitsu(project["name"], dbcon, project) + sync_project_from_kitsu(dbcon, project) def sync_project_from_kitsu( - project_name: str, dbcon: AvalonMongoDB, project: dict = None + dbcon: AvalonMongoDB, project: dict ): """Update OP project in DB with Zou data. Args: - project_name (str): Name of project to sync dbcon (AvalonMongoDB): MongoDB connection - project (dict, optional): Project dict got using gazu. - Defaults to None. + project (dict): Project dict got using gazu. """ bulk_writes = [] # Get project from zou if not project: - project = gazu.project.get_project_by_name(project_name) - project_code = project_name + project = gazu.project.get_project_by_name(project["name"]) - print(f"Synchronizing {project_name}...") + print(f"Synchronizing {project['name']}...") # Get all assets from zou all_assets = gazu.asset.all_assets_for_project(project) @@ -292,28 +288,28 @@ def sync_project_from_kitsu( bulk_writes.append(write_project_to_op(project, dbcon)) # Try to find project document - project_col = dbcon.database[project_code] - project_doc = project_col.find_one({"type": "project"}) + dbcon.Session["AVALON_PROJECT"] = project["name"] + project_doc = dbcon.find_one({"type": "project"}) # Query all assets of the local project zou_ids_and_asset_docs = { asset_doc["data"]["zou"]["id"]: asset_doc - for asset_doc in project_col.find({"type": "asset"}) + for asset_doc in dbcon.find({"type": "asset"}) if asset_doc["data"].get("zou", {}).get("id") } zou_ids_and_asset_docs[project["id"]] = project_doc # Create entities root folders - project_module_settings = get_project_settings(project_name)["kitsu"] + project_module_settings = get_project_settings(project["name"])["kitsu"] for entity_type, root in project_module_settings["entities_root"].items(): parent_folders = root.split("/") direct_parent_doc = None for i, folder in enumerate(parent_folders, 1): - parent_doc = project_col.find_one( + parent_doc = dbcon.find_one( {"type": "asset", "name": folder, "data.root_of": entity_type} ) if not parent_doc: - direct_parent_doc = project_col.insert_one( + direct_parent_doc = dbcon.insert_one( { "name": folder, "type": "asset", @@ -338,13 +334,13 @@ def sync_project_from_kitsu( ) if to_insert: # Insert doc in DB - project_col.insert_many(to_insert) + dbcon.insert_many(to_insert) # Update existing docs zou_ids_and_asset_docs.update( { asset_doc["data"]["zou"]["id"]: asset_doc - for asset_doc in project_col.find({"type": "asset"}) + for asset_doc in dbcon.find({"type": "asset"}) if asset_doc["data"].get("zou") } ) @@ -354,7 +350,7 @@ def sync_project_from_kitsu( [ UpdateOne({"_id": id}, update) for id, update in update_op_assets( - project_col, all_entities, zou_ids_and_asset_docs + dbcon, all_entities, zou_ids_and_asset_docs ) ] ) @@ -373,4 +369,4 @@ def sync_project_from_kitsu( # Write into DB if bulk_writes: - project_col.bulk_write(bulk_writes) + dbcon.bulk_write(bulk_writes) diff --git a/openpype/modules/kitsu/utils/update_zou_with_op.py b/openpype/modules/kitsu/utils/update_zou_with_op.py index 526159d101..81d421206f 100644 --- a/openpype/modules/kitsu/utils/update_zou_with_op.py +++ b/openpype/modules/kitsu/utils/update_zou_with_op.py @@ -93,10 +93,10 @@ def sync_zou_from_op_project( # Query all assets of the local project project_module_settings = get_project_settings(project_name)["kitsu"] - project_col = dbcon.database[project_name] + dbcon.Session["AVALON_PROJECT"] = project_name asset_docs = { asset_doc["_id"]: asset_doc - for asset_doc in project_col.find({"type": "asset"}) + for asset_doc in dbcon.find({"type": "asset"}) } # Create new assets @@ -259,4 +259,4 @@ def sync_zou_from_op_project( # Write into DB if bulk_writes: - project_col.bulk_write(bulk_writes) + dbcon.bulk_write(bulk_writes) From 765546e6f96cf8ba480c20e65ad3f19cc8a24267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 11 May 2022 12:31:08 +0200 Subject: [PATCH 0195/1227] fix unused var --- openpype/modules/kitsu/utils/sync_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index ad18e1f391..8c7ce730a7 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -126,7 +126,7 @@ class Listener: """Delete project.""" # Get project entity print(data) # TODO check bugfix - project = gazu.project.get_project(data["project_id"]) + # project = gazu.project.get_project(data["project_id"]) # Delete project collection # self.dbcon = self.dbcon.database[project["name"]] From 43baf02d98e2fa899ec84ab163cb97d8453167d8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 11 May 2022 13:22:42 +0200 Subject: [PATCH 0196/1227] flame: refactory MediaInfo class - to be able match collection if sequence with holes - also make code more explicit about input arguments --- openpype/hosts/flame/api/lib.py | 119 ++++++++++++++++++++++++++++---- 1 file changed, 105 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 2e9b535764..15e6f8ae80 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -3,6 +3,7 @@ import os import re import json import pickle +import clique import tempfile import itertools import contextlib @@ -773,17 +774,24 @@ class MediaInfoFile(object): self._validate_media_script_path() # derivate other feed variables - self.feed_basename = os.path.basename(path) - self.feed_dir = os.path.dirname(path) - self.feed_ext = os.path.splitext(self.feed_basename)[1][1:].lower() + feed_basename = os.path.basename(path) + feed_dir = os.path.dirname(path) + feed_ext = os.path.splitext(feed_basename)[1][1:].lower() with maintained_temp_file_path(".clip") as tmp_path: self.log.info("Temp File: {}".format(tmp_path)) - self._generate_media_info_file(tmp_path) + self._generate_media_info_file(tmp_path, feed_ext, feed_dir) + + if os.path.exists(os.path.join(feed_dir, feed_basename)): + test_fname = feed_basename + else: + # get collection containing feed_basename from path + test_fname = self._get_collection(feed_basename, feed_dir, feed_ext) # get clip data and make them single if there is multiple # clips data - xml_data = self._make_single_clip_media_info(tmp_path) + xml_data = self._make_single_clip_media_info( + tmp_path, feed_basename, test_fname) self.log.debug("xml_data: {}".format(xml_data)) self.log.debug("type: {}".format(type(xml_data))) @@ -794,6 +802,73 @@ class MediaInfoFile(object): self.log.debug("drop frame: {}".format(self.drop_mode)) self.clip_data = xml_data + def _get_collection(self, feed_basename, feed_dir, feed_ext): + partialname = self._separate_file_head(feed_basename, feed_ext) + log.debug("__ partialname: {}".format(partialname)) + + # make sure partial input basename is having correct extensoon + if not partialname: + raise IOError("File doesnt exists. Basename - {}, Ext - {}".format( + feed_basename, feed_ext + )) + + # get all related files + files = [ + f for f in os.listdir(feed_dir) + if partialname == self._separate_file_head(f, feed_ext) + ] + + # ignore reminders as we dont need them + collections = clique.assemble(files)[0] + + # if no collection rise + if not collections: + raise IOError("_get_collection is failing on: {} {} {}".format( + feed_basename, feed_dir, feed_ext + )) + else: + # we expect only one collection + collection = collections[0] + + if collection.is_contiguous(): + # if no holes then return collection + return collection.format("{head}[{range}]{tail}") + + # add `[` in front to make sure it want capture + # shot name with the same number + number_from_path = "[" + self._separate_number(feed_basename, feed_ext) + # convert to multiple collections + _continues_colls = collection.separate() + for _coll in _continues_colls: + coll_to_text = _coll.format("{head}[{range}]{tail}") + log.debug("__ coll_to_text: {}".format(coll_to_text)) + if number_from_path in coll_to_text: + return coll_to_text + + def _separate_file_head(self, basename, extension): + # in case sequence file + found = re.findall( + r"(.*)[._][\d]*(?=.{})".format(extension), + basename, + ) + if found: + return found.pop() + + # in case single file + name, ext = os.path.splitext(basename) + + if extension == ext[1:]: + return name + + def _separate_number(self, basename, extension): + # in case sequence file + found = re.findall( + r"[._]([\d]*)(?=.{})".format(extension), + basename, + ) + if found: + return found.pop() + @property def clip_data(self): """Clip's xml clip data @@ -851,13 +926,13 @@ class MediaInfoFile(object): raise IOError("Media Scirpt does not exist: `{}`".format( self.MEDIA_SCRIPT_PATH)) - def _generate_media_info_file(self, fpath): + def _generate_media_info_file(self, fpath, feed_ext, feed_dir): # Create cmd arguments for gettig xml file info file cmd_args = [ self.MEDIA_SCRIPT_PATH, - "-e", self.feed_ext, + "-e", feed_ext, "-o", fpath, - self.feed_dir + feed_dir ] try: @@ -867,7 +942,7 @@ class MediaInfoFile(object): raise TypeError( "Error creating `{}` due: {}".format(fpath, error)) - def _make_single_clip_media_info(self, fpath): + def _make_single_clip_media_info(self, fpath, feed_basename, path_pattern): with open(fpath) as f: lines = f.readlines() _added_root = itertools.chain( @@ -878,14 +953,32 @@ class MediaInfoFile(object): xml_clips = new_root.findall("clip") matching_clip = None for xml_clip in xml_clips: - if xml_clip.find("name").text in self.feed_basename: - matching_clip = xml_clip + clip_name = xml_clip.find("name").text + log.debug("__ clip_name: `{}`".format(clip_name)) + if clip_name not in feed_basename: + continue + + # test path pattern + for out_track in xml_clip.iter("track"): + for out_feed in out_track.iter("feed"): + for span in out_feed.iter("span"): + # start frame + span_path = span.find("path") + if not span_path: + continue + log.debug( + "__ span_path.text: {}, path_pattern: {}".format( + span_path.text, path_pattern + ) + ) + if path_pattern in span_path.text: + matching_clip = xml_clip if matching_clip is None: # return warning there is missing clip raise ET.ParseError( "Missing clip in `{}`. Available clips {}".format( - self.feed_basename, [ + feed_basename, [ xml_clip.find("name").text for xml_clip in xml_clips ] @@ -912,8 +1005,6 @@ class MediaInfoFile(object): 'startTimecode/dropMode') self.drop_mode = out_feed_drop_mode_obj.text break - else: - continue except Exception as msg: self.log.warning(msg) From c34ecfd9127004e0c739be7bb0efdb11d162c447 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 11 May 2022 13:35:35 +0200 Subject: [PATCH 0197/1227] flame: fixing logger --- openpype/hosts/flame/api/lib.py | 8 ++++---- openpype/hosts/flame/otio/flame_export.py | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 15e6f8ae80..6fff3edc30 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -804,7 +804,7 @@ class MediaInfoFile(object): def _get_collection(self, feed_basename, feed_dir, feed_ext): partialname = self._separate_file_head(feed_basename, feed_ext) - log.debug("__ partialname: {}".format(partialname)) + self.log.debug("__ partialname: {}".format(partialname)) # make sure partial input basename is having correct extensoon if not partialname: @@ -841,7 +841,7 @@ class MediaInfoFile(object): _continues_colls = collection.separate() for _coll in _continues_colls: coll_to_text = _coll.format("{head}[{range}]{tail}") - log.debug("__ coll_to_text: {}".format(coll_to_text)) + self.log.debug("__ coll_to_text: {}".format(coll_to_text)) if number_from_path in coll_to_text: return coll_to_text @@ -954,7 +954,7 @@ class MediaInfoFile(object): matching_clip = None for xml_clip in xml_clips: clip_name = xml_clip.find("name").text - log.debug("__ clip_name: `{}`".format(clip_name)) + self.log.debug("__ clip_name: `{}`".format(clip_name)) if clip_name not in feed_basename: continue @@ -966,7 +966,7 @@ class MediaInfoFile(object): span_path = span.find("path") if not span_path: continue - log.debug( + self.log.debug( "__ span_path.text: {}, path_pattern: {}".format( span_path.text, path_pattern ) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index fc960b670c..c54ebb43d3 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -280,7 +280,9 @@ def create_otio_clip(clip_data): segment = clip_data["PySegment"] # calculate source in - media_info = MediaInfoFile(clip_data["fpath"]) + media_info = MediaInfoFile(clip_data["fpath"], **{ + "logger": log + }) media_timecode_start = media_info.start_frame media_fps = media_info.fps From 27d9e9bd3e9a9ef03489290101535aeb79617e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 11 May 2022 14:08:05 +0200 Subject: [PATCH 0198/1227] Fix delete project --- openpype/modules/kitsu/utils/sync_service.py | 9 +++++---- openpype/modules/kitsu/utils/update_op_with_zou.py | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 8c7ce730a7..8d1ffb199d 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -124,12 +124,13 @@ class Listener: def _delete_project(self, data): """Delete project.""" - # Get project entity - print(data) # TODO check bugfix - # project = gazu.project.get_project(data["project_id"]) + project_doc = self.dbcon.find_one( + {"type": "project", "data.zou_id": data["project_id"]} + ) # Delete project collection - # self.dbcon = self.dbcon.database[project["name"]] + self.dbcon.database[project_doc["name"]].drop() + # == Asset == diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 6e82ffbd05..03a10e76a6 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -218,6 +218,7 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: "fps": project["fps"], "resolutionWidth": project["resolution"].split("x")[0], "resolutionHeight": project["resolution"].split("x")[1], + "zou_id": project["id"], } ) From aac7797f06934dfc123c8a97e53106b6c442c54b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 11 May 2022 14:26:36 +0200 Subject: [PATCH 0199/1227] flame: check collection first --- openpype/hosts/flame/api/lib.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 6fff3edc30..8e2ef2d624 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -782,11 +782,15 @@ class MediaInfoFile(object): self.log.info("Temp File: {}".format(tmp_path)) self._generate_media_info_file(tmp_path, feed_ext, feed_dir) - if os.path.exists(os.path.join(feed_dir, feed_basename)): + # get collection containing feed_basename from path + test_fname = self._get_collection( + feed_basename, feed_dir, feed_ext) + + if ( + not test_fname + and os.path.exists(os.path.join(feed_dir, feed_basename)) + ): test_fname = feed_basename - else: - # get collection containing feed_basename from path - test_fname = self._get_collection(feed_basename, feed_dir, feed_ext) # get clip data and make them single if there is multiple # clips data From 82e62230fa78520d6cf167b0eaaec8095e5d18a6 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Wed, 11 May 2022 14:35:38 +0200 Subject: [PATCH 0200/1227] int back --- openpype/plugins/publish/extract_review_slate.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index b84dbbc2ac..7b2df4dc5f 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -89,8 +89,8 @@ class ExtractReviewSlate(openpype.api.Extractor): tags = stream.get("tags") or {} input_timecode = tags.get("timecode") or "" if "width" in stream and "height" in stream: - input_width = stream.get("width") - input_height = stream.get("height") + input_width = int(stream.get("width")) + input_height = int(stream.get("height")) if "r_frame_rate" in stream: # get frame rate in a form of # x/y, like 24000/1001 for 23.976 @@ -100,8 +100,6 @@ class ExtractReviewSlate(openpype.api.Extractor): and input_height and input_frame_rate ): - input_width = int(input_width) - input_height = int(input_height) input_frame_rate = str(input_frame_rate) break # Raise exception of any stream didn't define input resolution From b7f7af46fbd00e8b62822efd8b999f28fdb5bf6b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 11 May 2022 14:47:34 +0200 Subject: [PATCH 0201/1227] flame: redundant condition --- openpype/hosts/flame/api/lib.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 8e2ef2d624..660466b0aa 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -968,8 +968,6 @@ class MediaInfoFile(object): for span in out_feed.iter("span"): # start frame span_path = span.find("path") - if not span_path: - continue self.log.debug( "__ span_path.text: {}, path_pattern: {}".format( span_path.text, path_pattern From 7f1b8f7f038d63cd53ee36e6d419aff62271adb3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 11 May 2022 16:00:30 +0200 Subject: [PATCH 0202/1227] flame: adding docstrings --- openpype/hosts/flame/api/lib.py | 39 ++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 660466b0aa..7125a540a0 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -807,12 +807,26 @@ class MediaInfoFile(object): self.clip_data = xml_data def _get_collection(self, feed_basename, feed_dir, feed_ext): + """ Get collection string + + Args: + feed_basename (str): file base name + feed_dir (str): file's directory + feed_ext (str): file extension + + Raises: + AttributeError: feed_ext is not matching feed_basename + IOError: Failing on not correct input data + + Returns: + str: collection basename with range of sequence + """ partialname = self._separate_file_head(feed_basename, feed_ext) self.log.debug("__ partialname: {}".format(partialname)) # make sure partial input basename is having correct extensoon if not partialname: - raise IOError("File doesnt exists. Basename - {}, Ext - {}".format( + raise AttributeError("Wrong input attributes. Basename - {}, Ext - {}".format( feed_basename, feed_ext )) @@ -850,6 +864,15 @@ class MediaInfoFile(object): return coll_to_text def _separate_file_head(self, basename, extension): + """ Get only head with out sequence and extension + + Args: + basename (str): file base name + extension (str): file extension + + Returns: + str: file head + """ # in case sequence file found = re.findall( r"(.*)[._][\d]*(?=.{})".format(extension), @@ -865,6 +888,15 @@ class MediaInfoFile(object): return name def _separate_number(self, basename, extension): + """ Get only sequence number as string + + Args: + basename (str): file base name + extension (str): file extension + + Returns: + str: number with padding + """ # in case sequence file found = re.findall( r"[._]([\d]*)(?=.{})".format(extension), @@ -989,6 +1021,11 @@ class MediaInfoFile(object): return matching_clip def _get_time_info_from_origin(self, xml_data): + """Set time info to class attributes + + Args: + xml_data (ET.Element): clip data + """ try: for out_track in xml_data.iter('track'): for out_feed in out_track.iter('feed'): From ac16e1f8bb6f79a3dca750848b5d7450571ff6b3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 11 May 2022 16:05:22 +0200 Subject: [PATCH 0203/1227] flame: adding more docstring --- openpype/hosts/flame/api/lib.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 7125a540a0..03e3d117a3 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -963,6 +963,16 @@ class MediaInfoFile(object): self.MEDIA_SCRIPT_PATH)) def _generate_media_info_file(self, fpath, feed_ext, feed_dir): + """ Generate media info xml .clip file + + Args: + fpath (str): .clip file path + feed_ext (str): file extension to be filtered + feed_dir (str): look up directory + + Raises: + TypeError: Type error if it fails + """ # Create cmd arguments for gettig xml file info file cmd_args = [ self.MEDIA_SCRIPT_PATH, @@ -979,6 +989,19 @@ class MediaInfoFile(object): "Error creating `{}` due: {}".format(fpath, error)) def _make_single_clip_media_info(self, fpath, feed_basename, path_pattern): + """ Separate only relative clip object form .clip file + + Args: + fpath (str): clip file path + feed_basename (str): search basename + path_pattern (str): search file pattern (file.[1-2].exr) + + Raises: + ET.ParseError: if nothing found + + Returns: + ET.Element: xml element data of matching clip + """ with open(fpath) as f: lines = f.readlines() _added_root = itertools.chain( From af77d5a888f371c3bab4c2e38483ecaa7e7c6023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 11 May 2022 16:37:17 +0200 Subject: [PATCH 0204/1227] fix wrong name entities makes crash: they are skipped --- openpype/modules/kitsu/utils/sync_service.py | 2 +- .../modules/kitsu/utils/update_op_with_zou.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 8d1ffb199d..46d3422727 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -131,7 +131,6 @@ class Listener: # Delete project collection self.dbcon.database[project_doc["name"]].drop() - # == Asset == def _new_asset(self, data): @@ -150,6 +149,7 @@ class Listener: def _update_asset(self, data): """Update asset into OP DB.""" + # TODO check if asset doesn't exist, create it (case where name wasn't valid) set_op_project(self.dbcon, data["project_id"]) project_doc = self.dbcon.find_one({"type": "project"}) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 03a10e76a6..fbc23cf52e 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -1,5 +1,6 @@ """Functions to update OpenPype data using Kitsu DB (a.k.a Zou).""" from copy import deepcopy +import re from typing import Dict, List from pymongo import DeleteOne, UpdateOne @@ -16,6 +17,10 @@ from openpype.lib import create_project from openpype.modules.kitsu.utils.credentials import validate_credentials +# Accepted namin pattern for OP +naming_pattern = re.compile("^[a-zA-Z0-9_.]*$") + + def create_op_asset(gazu_entity: dict) -> dict: """Create OP asset dict from gazu entity. @@ -261,9 +266,7 @@ def sync_all_project(login: str, password: str): sync_project_from_kitsu(dbcon, project) -def sync_project_from_kitsu( - dbcon: AvalonMongoDB, project: dict -): +def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): """Update OP project in DB with Zou data. Args: @@ -283,7 +286,11 @@ def sync_project_from_kitsu( all_episodes = gazu.shot.all_episodes_for_project(project) all_seqs = gazu.shot.all_sequences_for_project(project) all_shots = gazu.shot.all_shots_for_project(project) - all_entities = all_assets + all_episodes + all_seqs + all_shots + all_entities = [ + item + for item in all_assets + all_episodes + all_seqs + all_shots + if naming_pattern.match(item["name"]) + ] # Sync project. Create if doesn't exist bulk_writes.append(write_project_to_op(project, dbcon)) From ea54b0dc2512eeb428b1f3ea702a10a55ac6ccf3 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 9 May 2022 10:28:09 +0200 Subject: [PATCH 0205/1227] refactor --- openpype/hosts/nuke/startup/menu.py | 31 +++++++++++++++++++ .../defaults/project_settings/nuke.json | 4 +++ .../projects_schema/schema_project_maya.json | 2 +- .../projects_schema/schema_project_nuke.json | 4 +++ ...riptsmenu.json => schema_scriptsmenu.json} | 0 5 files changed, 40 insertions(+), 1 deletion(-) rename openpype/settings/entities/schemas/projects_schema/schemas/{schema_maya_scriptsmenu.json => schema_scriptsmenu.json} (100%) diff --git a/openpype/hosts/nuke/startup/menu.py b/openpype/hosts/nuke/startup/menu.py index 9ed43b2110..dee1d9d868 100644 --- a/openpype/hosts/nuke/startup/menu.py +++ b/openpype/hosts/nuke/startup/menu.py @@ -1,5 +1,7 @@ import nuke +import os +import avalon.api from openpype.api import Logger from openpype.pipeline import install_host from openpype.hosts.nuke import api @@ -9,6 +11,7 @@ from openpype.hosts.nuke.api.lib import ( WorkfileSettings, dirmap_file_name_filter ) +from openpype.settings import get_project_settings log = Logger.get_logger(__name__) @@ -28,3 +31,31 @@ nuke.addOnScriptLoad(WorkfileSettings().set_context_settings) nuke.addFilenameFilter(dirmap_file_name_filter) log.info('Automatic syncing of write file knob to script version') + + +def add_scripts_menu(): + try: + from scriptsmenu import launchfornuke + except ImportError: + log.warning( + "Skipping studio.menu install, because " + "'scriptsmenu' module seems unavailable." + ) + return + + # load configuration of custom menu + project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) + config = project_settings["nuke"]["scriptsmenu"]["definition"] + _menu = project_settings["nuke"]["scriptsmenu"]["name"] + + if not config: + log.warning("Skipping studio menu, no definition found.") + return + + # run the launcher for Maya menu + studio_menu = launchfornuke.main(title=_menu.title()) + + # apply configuration + studio_menu.build_from_configuration(studio_menu, config) + +add_scripts_menu() diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 128d440732..a9d284873c 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -15,6 +15,10 @@ "destination-path": [] } }, + "scriptsmenu": { + "name": "OpenPype Tools", + "definition": [] + }, "create": { "CreateWriteRender": { "fpath_template": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index cc70516c72..0c7943447b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -49,7 +49,7 @@ }, { "type": "schema", - "name": "schema_maya_scriptsmenu" + "name": "schema_scriptsmenu" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json index bc572cbdc8..1ae4efd8ea 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json @@ -79,6 +79,10 @@ } ] }, + { + "type": "schema", + "name": "schema_scriptsmenu" + }, { "type": "dict", "collapsible": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_scriptsmenu.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_scriptsmenu.json similarity index 100% rename from openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_scriptsmenu.json rename to openpype/settings/entities/schemas/projects_schema/schemas/schema_scriptsmenu.json From 8a1f7c10061387b03674eb0e21faf05f0bf8fbd5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 11 May 2022 17:12:54 +0200 Subject: [PATCH 0206/1227] flame: fix for single file use --- openpype/hosts/flame/api/lib.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 03e3d117a3..933cfbe267 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -839,14 +839,13 @@ class MediaInfoFile(object): # ignore reminders as we dont need them collections = clique.assemble(files)[0] - # if no collection rise + # in case no collection found return None + # it is probably just single file if not collections: - raise IOError("_get_collection is failing on: {} {} {}".format( - feed_basename, feed_dir, feed_ext - )) - else: - # we expect only one collection - collection = collections[0] + return + + # we expect only one collection + collection = collections[0] if collection.is_contiguous(): # if no holes then return collection From f7d4cbecfe36826865cb3aba3e33da10fd0aeb71 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 2 May 2022 17:17:29 +0200 Subject: [PATCH 0207/1227] add the scriptsmenu schema to nuke --- .../projects_schema/schema_project_nuke.json | 4 ++++ .../schemas/schema_nuke_scriptsmenu.json | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsmenu.json diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json index 1ae4efd8ea..1fc925557b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json @@ -290,6 +290,10 @@ } ] }, + { + "type": "schema", + "name": "schema_nuke_scriptsmenu" + }, { "type": "schema", "name": "schema_nuke_publish", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsmenu.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsmenu.json new file mode 100644 index 0000000000..e841d6ba77 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsmenu.json @@ -0,0 +1,22 @@ +{ + "type": "dict", + "collapsible": true, + "key": "scriptsmenu", + "label": "Scripts Menu Definition", + "children": [ + { + "type": "text", + "key": "name", + "label": "Menu Name" + }, + { + "type": "splitter" + }, + { + "type": "raw-json", + "key": "definition", + "label": "Menu definition", + "is_list": true + } + ] +} \ No newline at end of file From eb5605a3ade0e4f59b8ffb21a7914c446b75372d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 11 May 2022 17:24:11 +0200 Subject: [PATCH 0208/1227] fix updated asset skipped because of wrong name --- openpype/modules/kitsu/utils/sync_service.py | 9 ++++--- .../modules/kitsu/utils/update_op_with_zou.py | 24 ++++++++++++------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 46d3422727..6c003942f8 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -149,7 +149,6 @@ class Listener: def _update_asset(self, data): """Update asset into OP DB.""" - # TODO check if asset doesn't exist, create it (case where name wasn't valid) set_op_project(self.dbcon, data["project_id"]) project_doc = self.dbcon.find_one({"type": "project"}) @@ -167,7 +166,7 @@ class Listener: # Update asset_doc_id, asset_update = update_op_assets( - self.dbcon[asset], zou_ids_and_asset_docs + self.dbcon, project_doc, [asset], zou_ids_and_asset_docs )[0] self.dbcon.update_one({"_id": asset_doc_id}, asset_update) @@ -214,7 +213,7 @@ class Listener: # Update asset_doc_id, asset_update = update_op_assets( - self.dbcon, [episode], zou_ids_and_asset_docs + self.dbcon, project_doc, [episode], zou_ids_and_asset_docs )[0] self.dbcon.update_one({"_id": asset_doc_id}, asset_update) @@ -262,7 +261,7 @@ class Listener: # Update asset_doc_id, asset_update = update_op_assets( - self.dbcon, [sequence], zou_ids_and_asset_docs + self.dbcon, project_doc, [sequence], zou_ids_and_asset_docs )[0] self.dbcon.update_one({"_id": asset_doc_id}, asset_update) @@ -310,7 +309,7 @@ class Listener: # Update asset_doc_id, asset_update = update_op_assets( - self.dbcon, [shot], zou_ids_and_asset_docs + self.dbcon, project_doc, [shot], zou_ids_and_asset_docs )[0] self.dbcon.update_one({"_id": asset_doc_id}, asset_update) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index fbc23cf52e..fa0bee8365 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -38,7 +38,7 @@ def set_op_project(dbcon: AvalonMongoDB, project_id: str): """Set project context. Args: - dbcon (AvalonMongoDB): Connection to DB. + dbcon (AvalonMongoDB): Connection to DB project_id (str): Project zou ID """ project = gazu.project.get_project(project_id) @@ -47,7 +47,8 @@ def set_op_project(dbcon: AvalonMongoDB, project_id: str): def update_op_assets( - project_col: Collection, + dbcon: AvalonMongoDB, + project_doc: dict, entities_list: List[dict], asset_doc_ids: Dict[str, dict], ) -> List[Dict[str, dict]]: @@ -55,19 +56,27 @@ def update_op_assets( Set 'data' and 'parent' fields. Args: - project_col (Collection): Mongo project collection to sync + dbcon (AvalonMongoDB): Connection to DB entities_list (List[dict]): List of zou entities to update asset_doc_ids (Dict[str, dict]): Dicts of [{zou_id: asset_doc}, ...] Returns: List[Dict[str, dict]]: List of (doc_id, update_dict) tuples """ - project_name = project_col.name + project_name = project_doc["name"] assets_with_update = [] for item in entities_list: + # Check asset exists + item_doc = asset_doc_ids.get(item["id"]) + if not item_doc: # Create asset + op_asset = create_op_asset(item) + insert_result = dbcon.insert_one(op_asset) + item_doc = dbcon.find_one( + {"type": "asset", "_id": insert_result.inserted_id} + ) + # Update asset - item_doc = asset_doc_ids[item["id"]] item_data = deepcopy(item_doc["data"]) item_data.update(item.get("data") or {}) item_data["zou"] = item @@ -86,7 +95,6 @@ def update_op_assets( item_data["frameEnd"] = int(frame_out) # Fps, fallback to project's value when entity fps is deleted if not item_data.get("fps") and item_doc["data"].get("fps"): - project_doc = project_col.find_one({"type": "project"}) item_data["fps"] = project_doc["data"]["fps"] # Tasks @@ -139,7 +147,7 @@ def update_op_assets( ) if visual_parent_doc_id is None: # Find root folder doc - root_folder_doc = project_col.find_one( + root_folder_doc = dbcon.find_one( { "type": "asset", "name": entity_parent_folders[-1], @@ -358,7 +366,7 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): [ UpdateOne({"_id": id}, update) for id, update in update_op_assets( - dbcon, all_entities, zou_ids_and_asset_docs + dbcon, project_doc, all_entities, zou_ids_and_asset_docs ) ] ) From c90a949076a1c8d2237fcc6ec118549344a50343 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 3 May 2022 12:41:37 +0200 Subject: [PATCH 0209/1227] call the launchfornuke module from the nuke menu.py to generate custom menu --- openpype/hosts/nuke/startup/menu.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/nuke/startup/menu.py b/openpype/hosts/nuke/startup/menu.py index dee1d9d868..3a0bfdb28f 100644 --- a/openpype/hosts/nuke/startup/menu.py +++ b/openpype/hosts/nuke/startup/menu.py @@ -58,4 +58,5 @@ def add_scripts_menu(): # apply configuration studio_menu.build_from_configuration(studio_menu, config) + add_scripts_menu() From a18c4d3d16e5454f86cd1b4f539c6e3031bd7c9c Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 3 May 2022 17:27:32 +0200 Subject: [PATCH 0210/1227] add a function in the nuke menu.py module to also add gizmos --- openpype/hosts/nuke/startup/menu.py | 58 +++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/openpype/hosts/nuke/startup/menu.py b/openpype/hosts/nuke/startup/menu.py index 3a0bfdb28f..bb81ee7fac 100644 --- a/openpype/hosts/nuke/startup/menu.py +++ b/openpype/hosts/nuke/startup/menu.py @@ -1,5 +1,6 @@ import nuke import os +import json import avalon.api from openpype.api import Logger @@ -59,4 +60,61 @@ def add_scripts_menu(): studio_menu.build_from_configuration(studio_menu, config) +def add_gizmos(): + """ Build a custom gizmo menu from a yaml description file. + """ + quad_plugin_path = os.environ.get("QUAD_PLUGIN_PATH") + gizmos_folder = os.path.join(quad_plugin_path, 'nuke/gizmos') + icons_folder = os.path.join(quad_plugin_path, 'nuke/icons') + json_file = os.path.join(quad_plugin_path, 'nuke/toolbar.json') + + if os.path.isdir(gizmos_folder): + for p in os.listdir(gizmos_folder): + if os.path.isdir(os.path.join(gizmos_folder, p)): + nuke.pluginAddPath(os.path.join(gizmos_folder, p)) + nuke.pluginAddPath(gizmos_folder) + + with open(json_file, 'rb') as fd: + try: + data = json.loads(fd.read()) + except Exception as e: + print(f"Problem occurs when reading toolbar file: {e}") + return + + if data is None or not isinstance(data, list): + # return early if the json file is empty or not well structured + return + + bar = nuke.menu("Nodes") + menu = bar.addMenu( + "FixStudio", + icon=os.path.join(icons_folder, 'fixstudio.png') + ) + + # populate the menu + for entry in data: + # make fail if the name or command key doesn't exists + name = entry['name'] + + command = entry.get('command', "") + + if command.find('{pipe_path}') > -1: + command = command.format(pipe_path=os.environ['QUAD_PLUGIN_PATH']) + + hotkey = entry.get('hotkey', "") + icon = entry.get('icon', "") + + parent_name = os.path.dirname(name) + + if 'separator' in name: + current = menu.findItem(parent_name) + if current: + current.addSeparator() + else: + menu.addCommand( + name, command=command, shortcut=hotkey, icon=icon, + ) + + +add_gizmos() add_scripts_menu() From f479dd8635b95c1463bab44d79c85d19fc72095e Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 3 May 2022 17:54:30 +0200 Subject: [PATCH 0211/1227] Revert "add a function in the nuke menu.py module to also add gizmos" This reverts commit 2f3bafb2fb8cbf247c354a8904acbc78ae081731. --- openpype/hosts/nuke/startup/menu.py | 58 ----------------------------- 1 file changed, 58 deletions(-) diff --git a/openpype/hosts/nuke/startup/menu.py b/openpype/hosts/nuke/startup/menu.py index bb81ee7fac..3a0bfdb28f 100644 --- a/openpype/hosts/nuke/startup/menu.py +++ b/openpype/hosts/nuke/startup/menu.py @@ -1,6 +1,5 @@ import nuke import os -import json import avalon.api from openpype.api import Logger @@ -60,61 +59,4 @@ def add_scripts_menu(): studio_menu.build_from_configuration(studio_menu, config) -def add_gizmos(): - """ Build a custom gizmo menu from a yaml description file. - """ - quad_plugin_path = os.environ.get("QUAD_PLUGIN_PATH") - gizmos_folder = os.path.join(quad_plugin_path, 'nuke/gizmos') - icons_folder = os.path.join(quad_plugin_path, 'nuke/icons') - json_file = os.path.join(quad_plugin_path, 'nuke/toolbar.json') - - if os.path.isdir(gizmos_folder): - for p in os.listdir(gizmos_folder): - if os.path.isdir(os.path.join(gizmos_folder, p)): - nuke.pluginAddPath(os.path.join(gizmos_folder, p)) - nuke.pluginAddPath(gizmos_folder) - - with open(json_file, 'rb') as fd: - try: - data = json.loads(fd.read()) - except Exception as e: - print(f"Problem occurs when reading toolbar file: {e}") - return - - if data is None or not isinstance(data, list): - # return early if the json file is empty or not well structured - return - - bar = nuke.menu("Nodes") - menu = bar.addMenu( - "FixStudio", - icon=os.path.join(icons_folder, 'fixstudio.png') - ) - - # populate the menu - for entry in data: - # make fail if the name or command key doesn't exists - name = entry['name'] - - command = entry.get('command', "") - - if command.find('{pipe_path}') > -1: - command = command.format(pipe_path=os.environ['QUAD_PLUGIN_PATH']) - - hotkey = entry.get('hotkey', "") - icon = entry.get('icon', "") - - parent_name = os.path.dirname(name) - - if 'separator' in name: - current = menu.findItem(parent_name) - if current: - current.addSeparator() - else: - menu.addCommand( - name, command=command, shortcut=hotkey, icon=icon, - ) - - -add_gizmos() add_scripts_menu() From 4e694a3f36f740fe9b5488cf75ba421e4b520d97 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 9 May 2022 11:27:51 +0200 Subject: [PATCH 0212/1227] changes from comments --- .../entities/schemas/projects_schema/schema_project_nuke.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json index 1fc925557b..1ae4efd8ea 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json @@ -290,10 +290,6 @@ } ] }, - { - "type": "schema", - "name": "schema_nuke_scriptsmenu" - }, { "type": "schema", "name": "schema_nuke_publish", From 2eea2522be0ecce65c8fc5876a628b15169f3189 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 9 May 2022 11:31:05 +0200 Subject: [PATCH 0213/1227] delete schema_nuke_scriptsmenu.json since we use a schema_scriptsmenu.json for both maya and nuke --- .../schemas/schema_nuke_scriptsmenu.json | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsmenu.json diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsmenu.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsmenu.json deleted file mode 100644 index e841d6ba77..0000000000 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsmenu.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "type": "dict", - "collapsible": true, - "key": "scriptsmenu", - "label": "Scripts Menu Definition", - "children": [ - { - "type": "text", - "key": "name", - "label": "Menu Name" - }, - { - "type": "splitter" - }, - { - "type": "raw-json", - "key": "definition", - "label": "Menu definition", - "is_list": true - } - ] -} \ No newline at end of file From f081fe3521158cba9425f86a866fd50fc8106f6f Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 9 May 2022 11:58:28 +0200 Subject: [PATCH 0214/1227] add nuke doc for custom menu --- website/docs/admin_hosts_nuke.md | 14 ++++++++++++++ website/docs/assets/nuke-admin_scriptsmenu.png | Bin 0 -> 21712 bytes website/sidebars.js | 1 + 3 files changed, 15 insertions(+) create mode 100644 website/docs/admin_hosts_nuke.md create mode 100644 website/docs/assets/nuke-admin_scriptsmenu.png diff --git a/website/docs/admin_hosts_nuke.md b/website/docs/admin_hosts_nuke.md new file mode 100644 index 0000000000..46f596a2dc --- /dev/null +++ b/website/docs/admin_hosts_nuke.md @@ -0,0 +1,14 @@ +--- +id: admin_hosts_nuke +title: Nuke +sidebar_label: Nuke +--- + +## Custom Menu +You can add your custom tools menu into Nuke by extending definitions in **Nuke -> Scripts Menu Definition**. +![Custom menu definition](assets/nuke-admin_scriptsmenu.png) + +:::note Work in progress +This is still work in progress. Menu definition will be handled more friendly with widgets and not +raw json. +::: diff --git a/website/docs/assets/nuke-admin_scriptsmenu.png b/website/docs/assets/nuke-admin_scriptsmenu.png new file mode 100644 index 0000000000000000000000000000000000000000..cad2a4411d058a35995863a57bdbcc4cd2793457 GIT binary patch literal 21712 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 literal 0 HcmV?d00001 diff --git a/website/sidebars.js b/website/sidebars.js index 105afc30eb..0e33bed949 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -88,6 +88,7 @@ module.exports = { items: [ "admin_hosts_blender", "admin_hosts_maya", + "admin_hosts_nuke", "admin_hosts_resolve", "admin_hosts_harmony", "admin_hosts_aftereffects", From 70bb0fd7bc5c37447e6f7d7d9dcdce5f940de919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 11 May 2022 17:38:46 +0200 Subject: [PATCH 0215/1227] fix flake --- openpype/modules/kitsu/utils/update_op_with_zou.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index fa0bee8365..10349a999c 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -4,7 +4,6 @@ import re from typing import Dict, List from pymongo import DeleteOne, UpdateOne -from pymongo.collection import Collection import gazu from gazu.task import ( all_tasks_for_asset, From d5fa437912b8f03dfa705967c60fe2212065996a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 11 May 2022 17:51:11 +0200 Subject: [PATCH 0216/1227] flame: thumbnail frame number if not `Sequence Publish` --- .../flame/plugins/publish/extract_subset_resources.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index fd0ece2590..7dcaec7eee 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -236,6 +236,17 @@ class ExtractSubsetResources(openpype.api.Extractor): # define kwargs based on preset type if "thumbnail" in unique_name: + if export_type != "Sequence Publish": + # if not sequence preset + in_mark = int(source_start_handles - source_first_frame) + + self.log.debug("__ in_mark: {}".format(in_mark)) + self.log.debug("__ source_duration_handles: {}".format( + source_duration_handles)) + self.log.debug("__ thumb_frame_number: {}".format( + int(in_mark + (source_duration_handles / 2)) + )) + export_kwargs["thumb_frame_number"] = int(in_mark + ( source_duration_handles / 2)) else: From 8f24a764fd3f5061573931757e0eb6817949e77f Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 11 May 2022 18:02:22 +0200 Subject: [PATCH 0217/1227] remove avalon.api import --- openpype/hosts/nuke/startup/menu.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/nuke/startup/menu.py b/openpype/hosts/nuke/startup/menu.py index 3a0bfdb28f..49edb22a89 100644 --- a/openpype/hosts/nuke/startup/menu.py +++ b/openpype/hosts/nuke/startup/menu.py @@ -1,7 +1,6 @@ import nuke import os -import avalon.api from openpype.api import Logger from openpype.pipeline import install_host from openpype.hosts.nuke import api From 8892422b0edb6182a119dc848bdfa534e344744b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 11 May 2022 21:22:01 +0200 Subject: [PATCH 0218/1227] flame: attempt to solve issue with single frame imported clip --- openpype/hosts/flame/api/lib.py | 24 ++++++++++++++---- .../publish/extract_subset_resources.py | 25 ++++++++++++++----- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 933cfbe267..80818fbbfd 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -763,6 +763,7 @@ class MediaInfoFile(object): _start_frame = None _fps = None _drop_mode = None + _file_pattern = None def __init__(self, path, **kwargs): @@ -778,24 +779,25 @@ class MediaInfoFile(object): feed_dir = os.path.dirname(path) feed_ext = os.path.splitext(feed_basename)[1][1:].lower() + with maintained_temp_file_path(".clip") as tmp_path: self.log.info("Temp File: {}".format(tmp_path)) self._generate_media_info_file(tmp_path, feed_ext, feed_dir) # get collection containing feed_basename from path - test_fname = self._get_collection( + self.file_pattern = self._get_collection( feed_basename, feed_dir, feed_ext) if ( - not test_fname + not self.file_pattern and os.path.exists(os.path.join(feed_dir, feed_basename)) ): - test_fname = feed_basename + self.file_pattern = feed_basename # get clip data and make them single if there is multiple # clips data xml_data = self._make_single_clip_media_info( - tmp_path, feed_basename, test_fname) + tmp_path, feed_basename, self.file_pattern) self.log.debug("xml_data: {}".format(xml_data)) self.log.debug("type: {}".format(type(xml_data))) @@ -816,7 +818,6 @@ class MediaInfoFile(object): Raises: AttributeError: feed_ext is not matching feed_basename - IOError: Failing on not correct input data Returns: str: collection basename with range of sequence @@ -956,6 +957,19 @@ class MediaInfoFile(object): def drop_mode(self, text): self._drop_mode = str(text) + @property + def file_pattern(self): + """Clips file patter + + Returns: + str: file pattern. ex. file.[1-2].exr + """ + return self._file_pattern + + @file_pattern.setter + def file_pattern(self, fpattern): + self._file_pattern = fpattern + def _validate_media_script_path(self): if not os.path.isfile(self.MEDIA_SCRIPT_PATH): raise IOError("Media Scirpt does not exist: `{}`".format( diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 7dcaec7eee..d8cc14a506 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -6,6 +6,7 @@ from copy import deepcopy import pyblish.api import openpype.api from openpype.hosts.flame import api as opfapi +from openpype.hosts.flame.api import MediaInfoFile import flame @@ -67,6 +68,7 @@ class ExtractSubsetResources(openpype.api.Extractor): instance.data["representations"] = [] # flame objects + self.project = instance.context.data["flameProject"] segment = instance.data["item"] asset_name = instance.data["asset"] segment_name = segment.name.get_value() @@ -239,16 +241,18 @@ class ExtractSubsetResources(openpype.api.Extractor): if export_type != "Sequence Publish": # if not sequence preset in_mark = int(source_start_handles - source_first_frame) + thumb_frame_number = int(in_mark + ( + source_duration_handles / 2)) + else: + thumb_frame_number = int(in_mark + ( + (clip_out - clip_in) / 2)) self.log.debug("__ in_mark: {}".format(in_mark)) - self.log.debug("__ source_duration_handles: {}".format( - source_duration_handles)) self.log.debug("__ thumb_frame_number: {}".format( - int(in_mark + (source_duration_handles / 2)) + thumb_frame_number )) - export_kwargs["thumb_frame_number"] = int(in_mark + ( - source_duration_handles / 2)) + export_kwargs["thumb_frame_number"] = thumb_frame_number else: export_kwargs.update({ "in_mark": in_mark, @@ -419,7 +423,16 @@ class ExtractSubsetResources(openpype.api.Extractor): """ Import clip from path """ - clips = flame.import_clips(path) + media_info = MediaInfoFile(path, **{ + "logger": self.log + }) + file_pattern = media_info.file_pattern + self.log.debug("__ file_pattern: {}".format(file_pattern)) + + project_desktop = self.project.current_workspace.desktop + reel = project_desktop.reel_groups[0].reels[0] + + clips = flame.import_clips(file_pattern, reel) self.log.info("Clips [{}] imported from `{}`".format(clips, path)) if not clips: self.log.warning("Path `{}` is not having any clips".format(path)) From 12abc9e2bec0715836bd7d0ad3fe740b92e6eab4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 11 May 2022 21:31:12 +0200 Subject: [PATCH 0219/1227] flame: thumbnails and mov presets are created correctly now --- .../flame/plugins/publish/extract_subset_resources.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index d8cc14a506..6098f2e1e9 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -423,17 +423,19 @@ class ExtractSubsetResources(openpype.api.Extractor): """ Import clip from path """ + dir_path = os.path.dirname(path) media_info = MediaInfoFile(path, **{ "logger": self.log }) file_pattern = media_info.file_pattern self.log.debug("__ file_pattern: {}".format(file_pattern)) - project_desktop = self.project.current_workspace.desktop - reel = project_desktop.reel_groups[0].reels[0] + # rejoin the pattern to dir path + new_path = os.path.join(dir_path, file_pattern) - clips = flame.import_clips(file_pattern, reel) + clips = flame.import_clips(new_path) self.log.info("Clips [{}] imported from `{}`".format(clips, path)) + if not clips: self.log.warning("Path `{}` is not having any clips".format(path)) return None From df1b5c6e66cdf48f5f26fca6812d208ad24dd5b3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 12 May 2022 08:57:04 +0200 Subject: [PATCH 0220/1227] flame: reducing code redundancy --- .../publish/extract_subset_resources.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 6098f2e1e9..176629fbfc 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -68,7 +68,6 @@ class ExtractSubsetResources(openpype.api.Extractor): instance.data["representations"] = [] # flame objects - self.project = instance.context.data["flameProject"] segment = instance.data["item"] asset_name = instance.data["asset"] segment_name = segment.name.get_value() @@ -182,15 +181,15 @@ class ExtractSubsetResources(openpype.api.Extractor): name_patern_xml = ( "__{}.").format( unique_name) + + # change in/out marks to timeline in/out + in_mark = clip_in + out_mark = clip_out else: exporting_clip = self.import_clip(clip_path) exporting_clip.name.set_value("{}_{}".format( asset_name, segment_name)) - # change in/out marks to timeline in/out - in_mark = clip_in - out_mark = clip_out - # add xml tags modifications modify_xml_data.update({ "exportHandles": True, @@ -238,14 +237,8 @@ class ExtractSubsetResources(openpype.api.Extractor): # define kwargs based on preset type if "thumbnail" in unique_name: - if export_type != "Sequence Publish": - # if not sequence preset - in_mark = int(source_start_handles - source_first_frame) - thumb_frame_number = int(in_mark + ( - source_duration_handles / 2)) - else: - thumb_frame_number = int(in_mark + ( - (clip_out - clip_in) / 2)) + thumb_frame_number = int(in_mark + ( + source_duration_handles / 2)) self.log.debug("__ in_mark: {}".format(in_mark)) self.log.debug("__ thumb_frame_number: {}".format( From 987b5df1ddf44a0feabe27feca8930782a05d18b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 12 May 2022 09:16:49 +0200 Subject: [PATCH 0221/1227] optim get_project_settings --- openpype/modules/kitsu/utils/update_op_with_zou.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 10349a999c..0c72537c94 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -63,6 +63,7 @@ def update_op_assets( List[Dict[str, dict]]: List of (doc_id, update_dict) tuples """ project_name = project_doc["name"] + project_module_settings = get_project_settings(project_name)["kitsu"] assets_with_update = [] for item in entities_list: @@ -126,7 +127,6 @@ def update_op_assets( ) # TODO check consistency # Substitute Episode and Sequence by Shot - project_module_settings = get_project_settings(project_name)["kitsu"] substitute_item_type = ( "shots" if item_type in ["Episode", "Sequence"] From e0bd8777d1b9563d292a72ff592e461eb47e50f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 12 May 2022 09:42:51 +0200 Subject: [PATCH 0222/1227] pop useless item_data --- openpype/modules/kitsu/utils/update_op_with_zou.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 0c72537c94..673a195747 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -85,6 +85,7 @@ def update_op_assets( # Frame in, fallback on 0 frame_in = int(item_data.get("frame_in") or 0) item_data["frameStart"] = frame_in + item_data.pop("frame_in") # Frame out, fallback on frame_in + duration frames_duration = int(item.get("nb_frames") or 1) frame_out = ( @@ -93,6 +94,7 @@ def update_op_assets( else frame_in + frames_duration ) item_data["frameEnd"] = int(frame_out) + item_data.pop("frame_out") # Fps, fallback to project's value when entity fps is deleted if not item_data.get("fps") and item_doc["data"].get("fps"): item_data["fps"] = project_doc["data"]["fps"] From ae69db29cac488410fa00c547042126831d3be0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 12 May 2022 09:59:02 +0200 Subject: [PATCH 0223/1227] black pyblish --- .../plugins/publish/collect_kitsu_credential.py | 4 ++-- .../plugins/publish/collect_kitsu_entities.py | 16 ++++------------ .../plugins/publish/integrate_kitsu_note.py | 6 ++---- .../plugins/publish/integrate_kitsu_review.py | 9 ++------- .../plugins/publish/validate_kitsu_intent.py | 6 ++---- 5 files changed, 12 insertions(+), 29 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py index 4a27117e03..b7f6f67a40 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py @@ -5,7 +5,7 @@ import gazu import pyblish.api -class CollectKitsuSession(pyblish.api.ContextPlugin): #rename log in +class CollectKitsuSession(pyblish.api.ContextPlugin): # rename log in """Collect Kitsu session using user credentials""" order = pyblish.api.CollectorOrder @@ -15,4 +15,4 @@ class CollectKitsuSession(pyblish.api.ContextPlugin): #rename log in def process(self, context): gazu.client.set_host(os.environ["KITSU_SERVER"]) - gazu.log_in(os.environ["KITSU_LOGIN"], os.environ["KITSU_PWD"]) \ No newline at end of file + gazu.log_in(os.environ["KITSU_LOGIN"], os.environ["KITSU_PWD"]) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index 935b020641..66c35e54c4 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -19,8 +19,7 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): raise AssertionError("Zou asset data not found in OpenPype!") self.log.debug("Collected zou asset data: {}".format(zou_asset_data)) - zou_task_data = asset_data["tasks"][ - os.environ["AVALON_TASK"]].get("zou") + zou_task_data = asset_data["tasks"][os.environ["AVALON_TASK"]].get("zou") if not zou_task_data: self.log.warning("Zou task data not found in OpenPype!") self.log.debug("Collected zou task data: {}".format(zou_task_data)) @@ -45,20 +44,13 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): self.log.debug("Collect kitsu task: {}".format(kitsu_task)) else: - kitsu_task_type = gazu.task.get_task_type_by_name( - os.environ["AVALON_TASK"] - ) + kitsu_task_type = gazu.task.get_task_type_by_name(os.environ["AVALON_TASK"]) if not kitsu_task_type: raise AssertionError( - "Task type {} not found in Kitsu!".format( - os.environ["AVALON_TASK"] - ) + "Task type {} not found in Kitsu!".format(os.environ["AVALON_TASK"]) ) - kitsu_task = gazu.task.get_task_by_name( - kitsu_asset, - kitsu_task_type - ) + kitsu_task = gazu.task.get_task_by_name(kitsu_asset, kitsu_task_type) if not kitsu_task: raise AssertionError("Task not found in kitsu!") context.data["kitsu_task"] = kitsu_task diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index 61e4d2454c..99d891d514 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -30,9 +30,7 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): self.log.debug("Kitsu status: {}".format(kitsu_status)) kitsu_comment = gazu.task.add_comment( - context.data["kitsu_task"], - kitsu_status, - comment = publish_comment + context.data["kitsu_task"], kitsu_status, comment=publish_comment ) - context.data["kitsu_comment"] = kitsu_comment \ No newline at end of file + context.data["kitsu_comment"] = kitsu_comment diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index c38f14e8a4..65179bc0bf 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -21,15 +21,10 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): review_path = representation.get("published_path") - if 'review' not in representation.get("tags", []): + if "review" not in representation.get("tags", []): continue self.log.debug("Found review at: {}".format(review_path)) - gazu.task.add_preview( - task, - comment, - review_path, - normalize_movie=True - ) + gazu.task.add_preview(task, comment, review_path, normalize_movie=True) self.log.info("Review upload on comment") diff --git a/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py b/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py index c82130b33b..e0fad3b79f 100644 --- a/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py +++ b/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py @@ -19,9 +19,7 @@ class ValidateKitsuIntent(pyblish.api.ContextPlugin): kitsu_status = gazu.task.get_task_status_by_short_name(publish_status) if not kitsu_status: - raise AssertionError( - "Status `{}` not found in kitsu!".format(kitsu_status) - ) + raise AssertionError("Status `{}` not found in kitsu!".format(kitsu_status)) self.log.debug("Collect kitsu status: {}".format(kitsu_status)) - context.data["kitsu_status"] = kitsu_status \ No newline at end of file + context.data["kitsu_status"] = kitsu_status From 3583d85cd2a7d25abf184411c23a273b0f8671c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 12 May 2022 09:59:35 +0200 Subject: [PATCH 0224/1227] black pyblish --- .../plugins/publish/collect_kitsu_entities.py | 16 ++++++++++++---- .../plugins/publish/integrate_kitsu_review.py | 4 +++- .../plugins/publish/validate_kitsu_intent.py | 4 +++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index 66c35e54c4..84c400bde9 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -19,7 +19,9 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): raise AssertionError("Zou asset data not found in OpenPype!") self.log.debug("Collected zou asset data: {}".format(zou_asset_data)) - zou_task_data = asset_data["tasks"][os.environ["AVALON_TASK"]].get("zou") + zou_task_data = asset_data["tasks"][os.environ["AVALON_TASK"]].get( + "zou" + ) if not zou_task_data: self.log.warning("Zou task data not found in OpenPype!") self.log.debug("Collected zou task data: {}".format(zou_task_data)) @@ -44,13 +46,19 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): self.log.debug("Collect kitsu task: {}".format(kitsu_task)) else: - kitsu_task_type = gazu.task.get_task_type_by_name(os.environ["AVALON_TASK"]) + kitsu_task_type = gazu.task.get_task_type_by_name( + os.environ["AVALON_TASK"] + ) if not kitsu_task_type: raise AssertionError( - "Task type {} not found in Kitsu!".format(os.environ["AVALON_TASK"]) + "Task type {} not found in Kitsu!".format( + os.environ["AVALON_TASK"] + ) ) - kitsu_task = gazu.task.get_task_by_name(kitsu_asset, kitsu_task_type) + kitsu_task = gazu.task.get_task_by_name( + kitsu_asset, kitsu_task_type + ) if not kitsu_task: raise AssertionError("Task not found in kitsu!") context.data["kitsu_task"] = kitsu_task diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index 65179bc0bf..08fa4ee010 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -26,5 +26,7 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): self.log.debug("Found review at: {}".format(review_path)) - gazu.task.add_preview(task, comment, review_path, normalize_movie=True) + gazu.task.add_preview( + task, comment, review_path, normalize_movie=True + ) self.log.info("Review upload on comment") diff --git a/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py b/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py index e0fad3b79f..6b2635bf05 100644 --- a/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py +++ b/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py @@ -19,7 +19,9 @@ class ValidateKitsuIntent(pyblish.api.ContextPlugin): kitsu_status = gazu.task.get_task_status_by_short_name(publish_status) if not kitsu_status: - raise AssertionError("Status `{}` not found in kitsu!".format(kitsu_status)) + raise AssertionError( + "Status `{}` not found in kitsu!".format(kitsu_status) + ) self.log.debug("Collect kitsu status: {}".format(kitsu_status)) context.data["kitsu_status"] = kitsu_status From 2a94ac1248446d8735bf34ebda62356af5658358 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Thu, 12 May 2022 10:10:57 +0200 Subject: [PATCH 0225/1227] Add doc to default value --- openpype/settings/defaults/project_settings/nuke.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index a9d284873c..7f916424ed 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -17,7 +17,15 @@ }, "scriptsmenu": { "name": "OpenPype Tools", - "definition": [] + "definition": [ + { + "type": "action", + "sourcetype": "python", + "title": "OpenPype Docs", + "command": "import webbrowser;webbrowser.open(url='https://openpype.io/docs/artist_hosts_nuke_tut')", + "tooltip": "Open the OpenPype Nuke user doc page" + } + ] }, "create": { "CreateWriteRender": { From 687f7260ceef7c0c3b2966f387e0f678a0fa0f8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 12 May 2022 10:57:18 +0200 Subject: [PATCH 0226/1227] optim publish status intent --- .../modules/kitsu/plugins/publish/validate_kitsu_intent.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py b/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py index 6b2635bf05..e2023b171e 100644 --- a/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py +++ b/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py @@ -12,10 +12,11 @@ class ValidateKitsuIntent(pyblish.api.ContextPlugin): optional = True def process(self, context): - + # Check publish status exists publish_status = context.data.get("intent", {}).get("value") if not publish_status: self.log.info("Status is not set.") + return kitsu_status = gazu.task.get_task_status_by_short_name(publish_status) if not kitsu_status: From 20f819c1dd2eab2e6401a80c3996c3a93ca0a089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 12 May 2022 12:36:47 +0200 Subject: [PATCH 0227/1227] change intent wrongly used as status choice --- .../plugins/publish/collect_kitsu_entities.py | 16 +++-------- .../plugins/publish/integrate_kitsu_note.py | 20 ++++++++----- .../plugins/publish/integrate_kitsu_review.py | 21 ++++++++------ .../plugins/publish/validate_kitsu_intent.py | 28 ------------------- 4 files changed, 30 insertions(+), 55 deletions(-) delete mode 100644 openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index 84c400bde9..66c35e54c4 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -19,9 +19,7 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): raise AssertionError("Zou asset data not found in OpenPype!") self.log.debug("Collected zou asset data: {}".format(zou_asset_data)) - zou_task_data = asset_data["tasks"][os.environ["AVALON_TASK"]].get( - "zou" - ) + zou_task_data = asset_data["tasks"][os.environ["AVALON_TASK"]].get("zou") if not zou_task_data: self.log.warning("Zou task data not found in OpenPype!") self.log.debug("Collected zou task data: {}".format(zou_task_data)) @@ -46,19 +44,13 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): self.log.debug("Collect kitsu task: {}".format(kitsu_task)) else: - kitsu_task_type = gazu.task.get_task_type_by_name( - os.environ["AVALON_TASK"] - ) + kitsu_task_type = gazu.task.get_task_type_by_name(os.environ["AVALON_TASK"]) if not kitsu_task_type: raise AssertionError( - "Task type {} not found in Kitsu!".format( - os.environ["AVALON_TASK"] - ) + "Task type {} not found in Kitsu!".format(os.environ["AVALON_TASK"]) ) - kitsu_task = gazu.task.get_task_by_name( - kitsu_asset, kitsu_task_type - ) + kitsu_task = gazu.task.get_task_by_name(kitsu_asset, kitsu_task_type) if not kitsu_task: raise AssertionError("Task not found in kitsu!") context.data["kitsu_task"] = kitsu_task diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index 99d891d514..980589365d 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -11,24 +11,30 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): # families = ["kitsu"] def process(self, context): + # Check if work version for user + is_work_version = bool(context.data.get("intent", {}).get("value")) + if is_work_version: + self.log.info("Work version, nothing pushed to Kitsu.") + return + # Get comment text body publish_comment = context.data.get("comment") if not publish_comment: self.log.info("Comment is not set.") - publish_status = context.data.get("intent", {}).get("value") - if not publish_status: - self.log.info("Status is not set.") - self.log.debug("Comment is `{}`".format(publish_comment)) - self.log.debug("Status is `{}`".format(publish_status)) - kitsu_status = context.data.get("kitsu_status") + # Get Waiting for Approval status + kitsu_status = gazu.task.get_task_status_by_short_name("wfa") if not kitsu_status: - self.log.info("The status will not be changed") + self.log.info( + "Cannot find 'Waiting For Approval' status." + "The status will not be changed" + ) kitsu_status = context.data["kitsu_task"].get("task_status") self.log.debug("Kitsu status: {}".format(kitsu_status)) + # Add comment to kitsu task kitsu_comment = gazu.task.add_comment( context.data["kitsu_task"], kitsu_status, comment=publish_comment ) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index 08fa4ee010..76cfe62988 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -15,18 +15,23 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): context = instance.context task = context.data["kitsu_task"] - comment = context.data["kitsu_comment"] + comment = context.data.get("kitsu_comment") - for representation in instance.data.get("representations", []): + # Check comment has been created + if not comment: + self.log.debug("Comment not created, review not pushed to preview.") + return + + # Add review representations as preview of comment + for representation in [ + r + for r in instance.data.get("representations", []) + if "review" in representation.get("tags", []) + ]: review_path = representation.get("published_path") - if "review" not in representation.get("tags", []): - continue - self.log.debug("Found review at: {}".format(review_path)) - gazu.task.add_preview( - task, comment, review_path, normalize_movie=True - ) + gazu.task.add_preview(task, comment, review_path, normalize_movie=True) self.log.info("Review upload on comment") diff --git a/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py b/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py deleted file mode 100644 index e2023b171e..0000000000 --- a/openpype/modules/kitsu/plugins/publish/validate_kitsu_intent.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -import pyblish.api -import gazu - - -class ValidateKitsuIntent(pyblish.api.ContextPlugin): - """Validate Kitsu Status""" - - order = pyblish.api.ValidatorOrder - label = "Kitsu Intent/Status" - # families = ["kitsu"] - optional = True - - def process(self, context): - # Check publish status exists - publish_status = context.data.get("intent", {}).get("value") - if not publish_status: - self.log.info("Status is not set.") - return - - kitsu_status = gazu.task.get_task_status_by_short_name(publish_status) - if not kitsu_status: - raise AssertionError( - "Status `{}` not found in kitsu!".format(kitsu_status) - ) - self.log.debug("Collect kitsu status: {}".format(kitsu_status)) - - context.data["kitsu_status"] = kitsu_status From 330d4340cc082307bd2a5bda951d7f2e491279f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 12 May 2022 12:39:03 +0200 Subject: [PATCH 0228/1227] black --- .../plugins/publish/collect_kitsu_entities.py | 16 ++++++++++++---- .../plugins/publish/integrate_kitsu_review.py | 10 +++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index 66c35e54c4..84c400bde9 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -19,7 +19,9 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): raise AssertionError("Zou asset data not found in OpenPype!") self.log.debug("Collected zou asset data: {}".format(zou_asset_data)) - zou_task_data = asset_data["tasks"][os.environ["AVALON_TASK"]].get("zou") + zou_task_data = asset_data["tasks"][os.environ["AVALON_TASK"]].get( + "zou" + ) if not zou_task_data: self.log.warning("Zou task data not found in OpenPype!") self.log.debug("Collected zou task data: {}".format(zou_task_data)) @@ -44,13 +46,19 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): self.log.debug("Collect kitsu task: {}".format(kitsu_task)) else: - kitsu_task_type = gazu.task.get_task_type_by_name(os.environ["AVALON_TASK"]) + kitsu_task_type = gazu.task.get_task_type_by_name( + os.environ["AVALON_TASK"] + ) if not kitsu_task_type: raise AssertionError( - "Task type {} not found in Kitsu!".format(os.environ["AVALON_TASK"]) + "Task type {} not found in Kitsu!".format( + os.environ["AVALON_TASK"] + ) ) - kitsu_task = gazu.task.get_task_by_name(kitsu_asset, kitsu_task_type) + kitsu_task = gazu.task.get_task_by_name( + kitsu_asset, kitsu_task_type + ) if not kitsu_task: raise AssertionError("Task not found in kitsu!") context.data["kitsu_task"] = kitsu_task diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index 76cfe62988..57e0286b00 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -19,19 +19,23 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): # Check comment has been created if not comment: - self.log.debug("Comment not created, review not pushed to preview.") + self.log.debug( + "Comment not created, review not pushed to preview." + ) return # Add review representations as preview of comment for representation in [ r for r in instance.data.get("representations", []) - if "review" in representation.get("tags", []) + if "review" in r.get("tags", []) ]: review_path = representation.get("published_path") self.log.debug("Found review at: {}".format(review_path)) - gazu.task.add_preview(task, comment, review_path, normalize_movie=True) + gazu.task.add_preview( + task, comment, review_path, normalize_movie=True + ) self.log.info("Review upload on comment") From dd77f7cd9bff9bfa5405ba13aa5760675b1b2a89 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 12 May 2022 13:37:34 +0200 Subject: [PATCH 0229/1227] flame: fixing padding in collection ranges --- openpype/hosts/flame/api/lib.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 80818fbbfd..f2f5db184b 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -848,9 +848,10 @@ class MediaInfoFile(object): # we expect only one collection collection = collections[0] + self.log.debug("__ collection: {}".format(collection)) + if collection.is_contiguous(): - # if no holes then return collection - return collection.format("{head}[{range}]{tail}") + return self._format_collection(collection) # add `[` in front to make sure it want capture # shot name with the same number @@ -858,11 +859,25 @@ class MediaInfoFile(object): # convert to multiple collections _continues_colls = collection.separate() for _coll in _continues_colls: - coll_to_text = _coll.format("{head}[{range}]{tail}") + coll_to_text = self._format_collection(_coll) self.log.debug("__ coll_to_text: {}".format(coll_to_text)) if number_from_path in coll_to_text: return coll_to_text + @staticmethod + def _format_collection(collection): + # if no holes then return collection + head = collection.format("{head}") + tail = collection.format("{tail}") + range_template = "[{{:0{0}d}}-{{:0{0}d}}]".format( + len(str(max(collection.indexes)))) + ranges = range_template.format( + min(collection.indexes), + max(collection.indexes) + ) + # if no holes then return collection + return "{}{}{}".format(head, ranges, tail) + def _separate_file_head(self, basename, extension): """ Get only head with out sequence and extension From aeff57dab75cb1a37d0f971b96d36b3cebd0ef0e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 12 May 2022 15:08:38 +0200 Subject: [PATCH 0230/1227] flame: expanding retiming features --- openpype/hosts/flame/otio/flame_export.py | 74 ++++++++++++++--------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index c54ebb43d3..e3801a0a4f 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -98,26 +98,26 @@ def _get_metadata(item): return {} -# def create_time_effects(otio_clip, clip_data): -# otio_effect = None +def create_time_effects(otio_clip, speed): + otio_effect = None -# # retime on track item -# if speed != 1.: -# # make effect -# otio_effect = otio.schema.LinearTimeWarp() -# otio_effect.name = "Speed" -# otio_effect.time_scalar = speed -# otio_effect.metadata = {} + # retime on track item + if speed != 1.: + # make effect + otio_effect = otio.schema.LinearTimeWarp() + otio_effect.name = "Speed" + otio_effect.time_scalar = speed + otio_effect.metadata = {} -# # freeze frame effect -# if speed == 0.: -# otio_effect = otio.schema.FreezeFrame() -# otio_effect.name = "FreezeFrame" -# otio_effect.metadata = {} + # freeze frame effect + if speed == 0.: + otio_effect = otio.schema.FreezeFrame() + otio_effect.name = "FreezeFrame" + otio_effect.metadata = {} -# if otio_effect: -# # add otio effect to clip effects -# otio_clip.effects.append(otio_effect) + if otio_effect: + # add otio effect to clip effects + otio_clip.effects.append(otio_effect) def _get_marker_color(flame_colour): @@ -205,7 +205,7 @@ def create_otio_markers(otio_item, item): otio_item.markers.append(otio_marker) -def create_otio_reference(clip_data, fps=None): +def create_otio_reference(clip_data, duration, fps=None): metadata = _get_metadata(clip_data) # get file info for path and start frame @@ -220,7 +220,6 @@ def create_otio_reference(clip_data, fps=None): # get padding and other file infos log.debug("_ path: {}".format(path)) - frame_duration = clip_data["source_duration"] otio_ex_ref_item = None is_sequence = frame_number = utils.get_frame_from_filename(file_name) @@ -247,7 +246,7 @@ def create_otio_reference(clip_data, fps=None): rate=fps, available_range=create_otio_time_range( frame_start, - frame_duration, + duration, fps ) ) @@ -263,7 +262,7 @@ def create_otio_reference(clip_data, fps=None): target_url=reformated_path, available_range=create_otio_time_range( frame_start, - frame_duration, + duration, fps ) ) @@ -286,19 +285,39 @@ def create_otio_clip(clip_data): media_timecode_start = media_info.start_frame media_fps = media_info.fps - # create media reference - media_reference = create_otio_reference(clip_data, media_fps) - # define first frame first_frame = media_timecode_start or utils.get_frame_from_filename( clip_data["fpath"]) or 0 - source_in = int(clip_data["source_in"]) - int(first_frame) + _clip_source_in = int(clip_data["source_in"]) + _clip_source_out = int(clip_data["source_out"]) + _clip_record_duration = int(clip_data["record_duration"]) + + # first solve if the reverse timing + speed = 1 + if clip_data["source_in"] > clip_data["source_out"]: + source_in = _clip_source_out - int(first_frame) + source_out = _clip_source_in - int(first_frame) + speed = -1 + else: + source_in = _clip_source_in - int(first_frame) + source_out = _clip_source_out - int(first_frame) + + source_duration = (source_out - source_in + 1) + + # secondly check if any change of speed + if source_duration != _clip_record_duration: + retime_speed = source_duration / _clip_record_duration + speed *= retime_speed + + # create media reference + media_reference = create_otio_reference( + clip_data, source_duration, media_fps) # creatae source range source_range = create_otio_time_range( source_in, - clip_data["record_duration"], + _clip_record_duration, CTX.get_fps() ) @@ -312,7 +331,8 @@ def create_otio_clip(clip_data): if MARKERS_INCLUDE: create_otio_markers(otio_clip, segment) - # create_time_effects(otio_clip, clip_data) + if speed != 1: + create_time_effects(otio_clip, speed) return otio_clip From ce250a6749cd0f6d6f220ada9830ed154ef4e481 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 12 May 2022 15:10:17 +0200 Subject: [PATCH 0231/1227] flame: debug logging --- openpype/hosts/flame/otio/flame_export.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index e3801a0a4f..500b1a3eb1 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -305,6 +305,11 @@ def create_otio_clip(clip_data): source_duration = (source_out - source_in + 1) + log.debug("_ source_in: {}".format(source_in)) + log.debug("_ source_out: {}".format(source_out)) + log.debug("_ speed: {}".format(speed)) + log.debug("_ source_duration: {}".format(source_duration)) + # secondly check if any change of speed if source_duration != _clip_record_duration: retime_speed = source_duration / _clip_record_duration From ee274f81e33fdb8a9741f2c2d397355702de5699 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 12 May 2022 15:12:03 +0200 Subject: [PATCH 0232/1227] flame: solving issue with frame longer renders --- .../hosts/flame/plugins/publish/extract_subset_resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 176629fbfc..0e04336211 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -160,7 +160,7 @@ class ExtractSubsetResources(openpype.api.Extractor): # get frame range with handles for representation range frame_start_handle = frame_start - handle_start source_duration_handles = ( - source_end_handles - source_start_handles) + 1 + source_end_handles - source_start_handles) # define in/out marks in_mark = (source_start_handles - source_first_frame) + 1 From 3f917055c135be7b6ba984a6c1245384b3389c79 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 12 May 2022 15:21:15 +0200 Subject: [PATCH 0233/1227] flame: improving logging --- openpype/hosts/flame/otio/flame_export.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index 500b1a3eb1..8562a766e9 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -305,15 +305,17 @@ def create_otio_clip(clip_data): source_duration = (source_out - source_in + 1) + # secondly check if any change of speed + if source_duration != _clip_record_duration: + retime_speed = source_duration / _clip_record_duration + log.debug("_ retime_speed: {}".format(retime_speed)) + speed *= retime_speed + log.debug("_ source_in: {}".format(source_in)) log.debug("_ source_out: {}".format(source_out)) log.debug("_ speed: {}".format(speed)) log.debug("_ source_duration: {}".format(source_duration)) - - # secondly check if any change of speed - if source_duration != _clip_record_duration: - retime_speed = source_duration / _clip_record_duration - speed *= retime_speed + log.debug("_ _clip_record_duration: {}".format(_clip_record_duration)) # create media reference media_reference = create_otio_reference( From 71bd7eb337bfc372b66973a10ff00d32c838a628 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 12 May 2022 15:24:38 +0200 Subject: [PATCH 0234/1227] flame: retime is float value --- openpype/hosts/flame/otio/flame_export.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index 8562a766e9..08478d4b98 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -307,7 +307,7 @@ def create_otio_clip(clip_data): # secondly check if any change of speed if source_duration != _clip_record_duration: - retime_speed = source_duration / _clip_record_duration + retime_speed = float(source_duration / _clip_record_duration) log.debug("_ retime_speed: {}".format(retime_speed)) speed *= retime_speed From 334ef56d63bebea8167776c9c6776719f8adc939 Mon Sep 17 00:00:00 2001 From: pberto Date: Fri, 13 May 2022 13:17:09 +0900 Subject: [PATCH 0235/1227] docs: finished multiverse documentation --- website/docs/artist_hosts_maya.md | 222 ++++++++++-------- ...maya-multiverse_openpype_asset_creator.png | Bin 0 -> 193795 bytes ...ultiverse_openpype_composition_creator.png | Bin 0 -> 114263 bytes .../maya-multiverse_openpype_loader.png | Bin 0 -> 80108 bytes .../maya-multiverse_openpype_look_creator.png | Bin 0 -> 26190 bytes ...a-multiverse_openpype_override_creator.png | Bin 0 -> 125997 bytes .../maya-multiverse_openpype_publishers.png | Bin 0 -> 182542 bytes website/docs/assets/maya-multiverse_setup.png | Bin 0 -> 164855 bytes 8 files changed, 128 insertions(+), 94 deletions(-) create mode 100644 website/docs/assets/maya-multiverse_openpype_asset_creator.png create mode 100644 website/docs/assets/maya-multiverse_openpype_composition_creator.png create mode 100644 website/docs/assets/maya-multiverse_openpype_loader.png create mode 100644 website/docs/assets/maya-multiverse_openpype_look_creator.png create mode 100644 website/docs/assets/maya-multiverse_openpype_override_creator.png create mode 100644 website/docs/assets/maya-multiverse_openpype_publishers.png create mode 100644 website/docs/assets/maya-multiverse_setup.png diff --git a/website/docs/artist_hosts_maya.md b/website/docs/artist_hosts_maya.md index 84285bc6dd..7f7f360b82 100644 --- a/website/docs/artist_hosts_maya.md +++ b/website/docs/artist_hosts_maya.md @@ -602,134 +602,116 @@ be published as separate entity. ## Working with Multiverse in OpenPype OpenPype supports creating, publishing and loading of [Multiverse | USD]( -https://multi-verse.io) data. +https://multi-verse.io) data. The minimum Multiverse version supported is v6.7, +and version 7.0 is recommended. -More specifically it is possible to: +In a nutshell it is possible to: + +- Create USD Assets, USD compositions, USD Overrides. + + This _creates_ OpenPype instances as Maya set nodes that contain information + for published USD data. + +- Create Multiverse Looks. + + This _creates_ OpenPype instances as Maya set nodes that contain information + for published Maya shading networks data. + +- Publish USD Assets, USD compositions and USD Overrides. + + This _writes_ USD files to disk and _publishes_ information to the OpenPype + database. + +- Publish Multiverse Looks. + + This _writes_ Maya files to disk and _publishes_ information to the OpenPype + database. + + +- Load any USD data into Multiverse "Compound" shape nodes. + + This _reads_ USD files (and also Alembic files) into Maya by _streaming_ them + to the viewport. -- Create USD Assets, USD compositions and USD Overrides. This _creates_ OpenPype - instances as Maya set nodes that contain information for published USD data. -- Publish USD Assets, USD compositions and USD Overrides. This _writes_ USD - files to disk and _publishes_ information to the OpenPype database. -- Load any USD data into Multiverse "Compound" shape nodes. This _reads_ USD - files (and also Alembic files) into Maya by streaming them to the viewport. - Rendering USD data procedurally with 3DelightNSI, Arnold, Redshift, - RenderMan and VRay. This reads USD files by streaming them procedurally to the renderer, at render time. + RenderMan and VRay. + + This reads USD files by _streaming_ them procedurally to the renderer, at + render time. USD files written by Multiverse are 100% native USD data, they can be exchanged with any other DCC applications able to interchange USD. Likewise, Multiverse -can read native USD data created by other applications. All the extensions are -supported: `.usd`, `.usdc`, `.usda`, `.usdz`. Sequences of USD files can also be -read, as USD clips. +can read native USD data created by other applications. The USD extensions are +supported: `.usd` (binary), `.usda` (ASCII), `.usdz`. (zipped, optionally with +textures). Sequences of USD files can also be read via "USD clips". It is also possible to load Alembic data (`.abc`) in Multiverse Compounds, further compose it & override it in other USD files, and render it procedurally. Alembic data is always converted on the fly (in memory) to USD data. USD clip from Alembic data are also supported. + ### Configuration -To configure Multiverse in OpenPype, a user with admin privileges needs to setup -a new tool in OpenPype Project Settings, using a similar configuration as -depicted here: +To configure Multiverse in OpenPype, an admin privileges needs to setup a new +OpenPype tool in the OpenPype Project Settings, using a similar configuration as +the one depicted here: ![Maya - Multiverse Setup](assets/maya-multiverse_setup.png) -For more information about setup of Multiverse please refer to: LINK. +For more information about setup of Multiverse please refer to the relative page +on the [Multiverse official documentation](https://multi-verse.io/docs). -### Understanding Assets, Compositions and Overrides +### Understanding Assets, Compounds, Compositions, Overrides and Layering -In Multiverse there are three main concepts for representing USD data. +In Multiverse we use some terminology that relates to USD I/O: terms like +"Assets", "Compounds", "Compositions", "Overrides" and "Layering". - -#### Assets - -In Multiverse, the term "asset" refers to a USD file that contains a hierarchy -of primitives (transforms and shapes). In Maya this is typically a hierarchy of -nodes, as it can be seen in Outliner, that is written out to a USD file. Once -loaded, the same hierarchy will be visible in MEOW (Multiverse Explore and -Override Window). - -![Maya - Outliner vs MEOW](assets/maya-multiverse_asset_outliner_meow.png) - -An asset typically contains static or animated data such as: poly meshes, blend -shapes, reference objects, particles/points, curves, joint, subdivision surfaces -and other attributes such as normals, UVs, color sets, tangents, skin weights, -material assignment and shading networks, cameras, lights etc. It is also -possible to specify Maya data to be part of variants and to be used as proxies -by tagging them with attributes. - -An asset is typically read back into Maya into a Multiverse Compound shape -node and it can be composed and overridden. - -![Maya - Multiverse Compound Shape(assets/maya-multiverse_compound.png) - - -#### Composition - -In Multiverse, the term "composition" refers to a USD file that contains a -hierarchy of Multiverse Compounds shape nodes and Maya transform nodes, as it -can be seen in Outliner. This hierarchy is written out to a USD file that -effectively contains a static or animated composition of other USD files written -as references or payloads (according to the relative setting in the Compound). - -![Maya - Multiverse Composition](assets/maya-multiverse_composition_outliner_meow.png) - -A Composition is is typically read back into Maya into a Multiverse Compound -shape and it can be further composed and overridden. - - -#### Override - -In Multiverse, the term "override" refers to a USD file that contains static or animated overrides for a USD hierarchy. Overrides are set in MEOW, they can be: - -- render visibility overrides -- transform overrides -- attribute overrides -- material assignment overrides -- variant and variant definition overrides -- Instancing state overrides -- activity state overrides - -![Maya - Multiverse Override](assets/maya-multiverse_override_meow.png) - -Overrides are written out to a USD file that effectively contains a static or -animated hierarchy of overrides that can be "layered" on top of Assets, -Compositions and other Overrides. - -![Maya - Multiverse Override](assets/maya-multiverse_override_compound_layer.png) - -An Override is is typically read back into Maya as a layer of a Multiverse -Compound shape and it can be further composed and overridden. +Please hop to the new [Multiverse Introduction]( +https://j-cube.jp/solutions/multiverse/docs/usage/introduction) page on the +official documentation to understand them before reading the next sections. ### Creators -It is possible to create OpenPype "instances" (Maya sets) for publishing -Multiverse USD Asset, Composition and Override. +It is possible to create OpenPype "instances" (resulting in Maya set containers) +for publishing Multiverse USD Assets, Compositions, Overrides and Looks. -![Maya - Multiverse Creators](assets/maya-multiverse_openpype_creator.png) - -When creating OpenPype instances for Multiverse USD asset, compositions and -overrides the creator plug-in will put the relative selected data in a Maya set -node which holds the properties used by the Multiverse data writer for +When creating OpenPype instances for Multiverse USD Asset, Composition, +Override and Look, the creator plug-in will put the relative selected data in a +Maya set node which holds the properties used by the Multiverse data writer for publishing. +You can choose the USD file format in the Creators' set nodes: + +- Assets: `.usd` or `.usda` or `.usdz` +- Compositions: `.usd` or `.usda` +- Overrides: `.usd` or `.usda` +- Looks: `.ma` + +![Maya - Multiverse Asset Creator](assets/maya-multiverse_openpype_asset_creator.png) + +![Maya - Multiverse Asset Creator](assets/maya-multiverse_openpype_composition_creator.png) + +![Maya - Multiverse Asset Creator](assets/maya-multiverse_openpype_override_creator.png) + +![Maya - Multiverse Asset Creator](assets/maya-multiverse_openpype_look_creator.png) ### Publishers -The relative publishers for Asset, Composition and Overrides are available. They -all write USD files to disk and communicate publish info to the OpenPype -database. +The relative publishers for Multiverse USD Asset, Composition, Override and Look +are available. The first three write USD files to disk, while look writes a maya +file. All communicate publish info to the OpenPype database. -![Maya - Multiverse Creators](assets/maya-multiverse_openpype_publisher.png) +![Maya - Multiverse Publisher](assets/maya-multiverse_openpype_publishers.png) ### Loader -The loader creates a Multiverse Compound shape node reading the USD file of -choice. All data is streamed to the viewport and not contained in Maya. Thanks -to the various viewport load options the user can strategically decide how to +The loader creates a Multiverse "Compound" shape node reading the USD file of +choice. All data is _streamed_ to the viewport and not contained in Maya. Thanks +to the various viewport draw options the user can strategically decide how to minimize the cost of viewport draw effectively being able to load any data, this allows to bring into Maya scenes of virtually unlimited complexity. @@ -741,9 +723,44 @@ Maya scene as Maya data. Instead, when desired, Multiverse permits to import specific USD primitives into the Maya scene as Maya data selectively from MEOW, it also tracks what is being imported, so upon modification, it is possible to write (create & publish) the modifies data as a USD file for being layered on -top of its relative Compound. See LINK. +top of its relative Compound. See the [Multiverse Importer]( +https://j-cube.jp/solutions/multiverse/docs/usage/importer)) documentation. ::: +### Look + +In OpenPype a Multiverse Look is a Maya file that contains shading networks that +are assigned to the items of a Multiverse Compound. + +Multiverse Looks are typically Maya-referenced in the lighting and shot scenes. + +Materials are assigned to the USD items in the Compound via the "material +assignment" information that is output in the lookdev stage by a Multiverse +Override. Once published the override can be Layered on the Compound so that +materials will be assigned to items. Finally, an attribute Override on the root +item of the Compound is used to define the `namespace` with which the shading +networks were referenced in Maya. At this point the renderer knows which +material to assign to which item and it is possible to render and edit the +materials as usual. Because the material exists in Maya you can perform IPR and +tune the materials as you please. + +As of Multiverse 7 it is also possible to write shading networks inside the USD +files: that is achieved by using either the Asset writer (if material are +defined in the modeling stage) and the Override writer (if materials are +defined in the lookdev or later stage). Shading networks in USD can then be +rendered in 3Delight NSI or used for interchange with DCC apps. + +Some interesting consequences of USD shading networks in Multiverse: + +1. they can be overridden by a shading network in Maya by assigning in MEOW a + Maya material as an override +2. they are available for assignment in MEOW, so you can assign a USD material + to an item as an override +3. From Hypershade you can use the Multiverse USD shading network write File> + Export option to write USD shading network libraries to then layer on an asset + and perform 2. again. + + ### Rendering Multiverse offers procedural rendering with all the major production renderers: @@ -754,11 +771,28 @@ Multiverse offers procedural rendering with all the major production renderers: - RenderMan - VRay +Procedural rendering effectively means that data is _streamed_ to the renderer +at render-time, without the need to store the data in the Maya scene (this +effectively means small .ma/.mb files that load fast) nor in the renderer native +file format scene description file (this effectively means tiny `.nsi` / `.ass` +/ `.vrscene` / `.rib` files that load fast). + This is completely transparent to the user: Multiverse Compound nodes present in the scene, once a render is launched, will stream data to the renderer in a procedural fashion. -![Maya - Multiverse Rendering](assets/maya-multiverse_rendering.png) + +### Example Multiverse Pipeline and API + +An example diagram of the data flow in a Maya pipeline using Multiverse is +available, see the [Multiverse Pipeline]( +https://j-cube.jp/solutions/multiverse/docs/pipeline) documentation. + + +A very easy to use Python API to automate any task is available, the API is +user friendly and does not require any knowledge of the vast and complex USD +APIs. See the [Multiverse Python API]( +https://j-cube.jp/solutions/multiverse/docs/dev/python-api.html) documentation. ## Working with Yeti in OpenPype diff --git a/website/docs/assets/maya-multiverse_openpype_asset_creator.png b/website/docs/assets/maya-multiverse_openpype_asset_creator.png new file mode 100644 index 0000000000000000000000000000000000000000..0426e9f82302f0306a53005fcba6c22b960f2368 GIT binary patch literal 193795 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 literal 0 HcmV?d00001 diff --git a/website/docs/assets/maya-multiverse_openpype_composition_creator.png b/website/docs/assets/maya-multiverse_openpype_composition_creator.png new file mode 100644 index 0000000000000000000000000000000000000000..eb0e5e33487b804e405d5a27d8048a485693ed6f GIT binary patch literal 114263 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 literal 0 HcmV?d00001 diff --git a/website/docs/assets/maya-multiverse_openpype_look_creator.png b/website/docs/assets/maya-multiverse_openpype_look_creator.png new file mode 100644 index 0000000000000000000000000000000000000000..14541e9e398fc99e3910d50a5286bdd8906b9f1a GIT binary patch literal 26190 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 XNN#wif}p^6lEBGJD?=(?y$$*wxlGVg literal 0 HcmV?d00001 diff --git a/website/docs/assets/maya-multiverse_openpype_override_creator.png b/website/docs/assets/maya-multiverse_openpype_override_creator.png new file mode 100644 index 0000000000000000000000000000000000000000..d7b1299ba652208c3842add85501e590453f4992 GIT binary patch literal 125997 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

$B)J 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 literal 0 HcmV?d00001 diff --git a/website/docs/assets/maya-multiverse_openpype_publishers.png b/website/docs/assets/maya-multiverse_openpype_publishers.png new file mode 100644 index 0000000000000000000000000000000000000000..b83fa5f59cd95ac8f6feab2e043fb9514ab257a7 GIT binary patch literal 182542 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¼zTC%cnFn=R3lm5XW25oz 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

+jTLKMEVQn~<}sDNh&32k4p!94^-h8nk-?qrSH7 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

V^Fk7mW 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 literal 0 HcmV?d00001 diff --git a/website/docs/assets/maya-multiverse_setup.png b/website/docs/assets/maya-multiverse_setup.png new file mode 100644 index 0000000000000000000000000000000000000000..8aa89ef7e504272e7b5f9abbfea0ec0ca3d1d783 GIT binary patch literal 164855 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

?;P|97R-)#?HUS+R~+heNwi? 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 diff --git a/openpype/resources/app_icons/nuke.png b/openpype/resources/app_icons/nuke.png index 423445409618d59ff822dc09e1dac2e4d3e241aa..e734b4984e1c5ce86e957274b455fd039c5cd150 100644 GIT binary patch literal 86832 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 V+x*Ep#xxg4tS#)!Yt6hK{RcYQ(zyTt literal 49012 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 NBt&F|s|EG_{|{S*X`BE6 From 8f26e683576ffc9d7be2abb9c3eb40ef01db1e09 Mon Sep 17 00:00:00 2001 From: pberto Date: Tue, 31 May 2022 14:30:38 +0900 Subject: [PATCH 0410/1227] multiverse documentation: see commit body - better docstrings for pyblish UI - updated images - re-organized Maya content in website by splitting plugin docs and sidebar menu (existing links are not broken since the same entry point doc exists) --- .../publish/extract_multiverse_look.py | 27 +- .../plugins/publish/extract_multiverse_usd.py | 16 +- .../publish/extract_multiverse_usd_comp.py | 16 +- .../publish/extract_multiverse_usd_over.py | 14 +- website/docs/artist_hosts_maya.md | 387 ------------------ website/docs/artist_hosts_maya_multiverse.md | 237 +++++++++++ website/docs/artist_hosts_maya_redshift.md | 31 ++ website/docs/artist_hosts_maya_vray.md | 44 ++ website/docs/artist_hosts_maya_xgen.md | 29 ++ website/docs/artist_hosts_maya_yeti.md | 108 +++++ .../maya-multiverse_openpype_look_creator.png | Bin 26190 -> 125727 bytes .../maya-multiverse_openpype_publishers.png | Bin 182542 -> 309158 bytes website/sidebars.js | 13 +- 13 files changed, 530 insertions(+), 392 deletions(-) create mode 100644 website/docs/artist_hosts_maya_multiverse.md create mode 100644 website/docs/artist_hosts_maya_redshift.md create mode 100644 website/docs/artist_hosts_maya_vray.md create mode 100644 website/docs/artist_hosts_maya_xgen.md create mode 100644 website/docs/artist_hosts_maya_yeti.md diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py index 5ba840f2b7..d59d03ee58 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py @@ -7,7 +7,32 @@ from openpype.hosts.maya.api.lib import maintained_selection class ExtractMultiverseLook(openpype.api.Extractor): - """Extractor for Multiverse USD look data into a Maya Scene.""" + """Extractor for Multiverse USD look data. + + This will extract: + + - the shading networks that are assigned in MEOW as Maya material overrides + to a Multiverse Compound + - settings for a Multiverse Write Override operation. + + Relevant settings are visible in the Maya set node created by a Multiverse + USD Look instance creator. + + The input data contained in the set is: + + - a single Multiverse Compound node with any number of Maya material + overrides (typically set in MEOW) + + Upon publish two files will be written: + + - a .usda override file containing material assignment information + - a .ma file containing shading networks + + Note: when layering the material assignment override on a loaded Compound, + remember to set a matching attribute override with the namespace of + the loaded compound in order for the material assignment to resolve. + + """ label = "Extract Multiverse USD Look" hosts = ["maya"] diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py index fe071b08a5..0671813c32 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py @@ -8,7 +8,21 @@ from openpype.hosts.maya.api.lib import maintained_selection class ExtractMultiverseUsd(openpype.api.Extractor): - """Extractor for Multiverse USD asset data.""" + """Extractor for Multiverse USD Asset data. + + This will extract settings for a Multiverse Write Asset operation: + they are visible in the Maya set node created by a Multiverse USD + Asset instance creator. + + The input data contained in the set is: + + - a single hierarchy of Maya nodes. Multiverse supports a variety of Maya + nodes such as transforms, mesh, curves, particles, instances, particle + instancers, pfx, MASH, lights, cameras, joints, connected materials, + shading networks etc. including many of their attributes. + + Upon publish a .usd (or .usdz) asset file will be typically written. + """ label = "Extract Multiverse USD Asset" hosts = ["maya"] diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py index 7be6b101d9..45d0cad368 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py @@ -7,7 +7,21 @@ from openpype.hosts.maya.api.lib import maintained_selection class ExtractMultiverseUsdComposition(openpype.api.Extractor): - """Extractor of Multiverse USD Composition.""" + """Extractor of Multiverse USD Composition data. + + This will extract settings for a Multiverse Write Composition operation: + they are visible in the Maya set node created by a Multiverse USD + Composition instance creator. + + The input data contained in the set is either: + + - a single hierarchy consisting of several Multiverse Compound nodes, with + any number of layers, and Maya transform nodes + - a single Compound node with more than one layer (in this case the "Write + as Compound Layers" option should be set). + + Upon publish a .usda composition file will be written. + """ label = "Extract Multiverse USD Composition" hosts = ["maya"] diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py index 091750c32b..8b14ac3388 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py @@ -7,7 +7,19 @@ from maya import cmds class ExtractMultiverseUsdOverride(openpype.api.Extractor): - """Extractor for Multiverse USD Override.""" + """Extractor for Multiverse USD Override data. + + This will extract settings for a Multiverse Write Override operation: + they are visible in the Maya set node created by a Multiverse USD + Override instance creator. + + The input data contained in the set is: + + - a single Multiverse Compound node with any number of overrides (typically + set in MEOW) + + Upon publish a .usda override file will be written. + """ label = "Extract Multiverse USD Override" hosts = ["maya"] diff --git a/website/docs/artist_hosts_maya.md b/website/docs/artist_hosts_maya.md index 7f7f360b82..79031ccaed 100644 --- a/website/docs/artist_hosts_maya.md +++ b/website/docs/artist_hosts_maya.md @@ -597,390 +597,3 @@ about customizing review process refer to [admin section](project_settings/setti If you don't move `modelMain` into `reviewMain`, review will be generated but it will be published as separate entity. - - -## Working with Multiverse in OpenPype - -OpenPype supports creating, publishing and loading of [Multiverse | USD]( -https://multi-verse.io) data. The minimum Multiverse version supported is v6.7, -and version 7.0 is recommended. - -In a nutshell it is possible to: - -- Create USD Assets, USD compositions, USD Overrides. - - This _creates_ OpenPype instances as Maya set nodes that contain information - for published USD data. - -- Create Multiverse Looks. - - This _creates_ OpenPype instances as Maya set nodes that contain information - for published Maya shading networks data. - -- Publish USD Assets, USD compositions and USD Overrides. - - This _writes_ USD files to disk and _publishes_ information to the OpenPype - database. - -- Publish Multiverse Looks. - - This _writes_ Maya files to disk and _publishes_ information to the OpenPype - database. - - -- Load any USD data into Multiverse "Compound" shape nodes. - - This _reads_ USD files (and also Alembic files) into Maya by _streaming_ them - to the viewport. - -- Rendering USD data procedurally with 3DelightNSI, Arnold, Redshift, - RenderMan and VRay. - - This reads USD files by _streaming_ them procedurally to the renderer, at - render time. - -USD files written by Multiverse are 100% native USD data, they can be exchanged -with any other DCC applications able to interchange USD. Likewise, Multiverse -can read native USD data created by other applications. The USD extensions are -supported: `.usd` (binary), `.usda` (ASCII), `.usdz`. (zipped, optionally with -textures). Sequences of USD files can also be read via "USD clips". - -It is also possible to load Alembic data (`.abc`) in Multiverse Compounds, -further compose it & override it in other USD files, and render it procedurally. -Alembic data is always converted on the fly (in memory) to USD data. USD clip -from Alembic data are also supported. - - -### Configuration - -To configure Multiverse in OpenPype, an admin privileges needs to setup a new -OpenPype tool in the OpenPype Project Settings, using a similar configuration as -the one depicted here: - -![Maya - Multiverse Setup](assets/maya-multiverse_setup.png) - -For more information about setup of Multiverse please refer to the relative page -on the [Multiverse official documentation](https://multi-verse.io/docs). - - -### Understanding Assets, Compounds, Compositions, Overrides and Layering - -In Multiverse we use some terminology that relates to USD I/O: terms like -"Assets", "Compounds", "Compositions", "Overrides" and "Layering". - -Please hop to the new [Multiverse Introduction]( -https://j-cube.jp/solutions/multiverse/docs/usage/introduction) page on the -official documentation to understand them before reading the next sections. - - -### Creators - -It is possible to create OpenPype "instances" (resulting in Maya set containers) -for publishing Multiverse USD Assets, Compositions, Overrides and Looks. - -When creating OpenPype instances for Multiverse USD Asset, Composition, -Override and Look, the creator plug-in will put the relative selected data in a -Maya set node which holds the properties used by the Multiverse data writer for -publishing. - -You can choose the USD file format in the Creators' set nodes: - -- Assets: `.usd` or `.usda` or `.usdz` -- Compositions: `.usd` or `.usda` -- Overrides: `.usd` or `.usda` -- Looks: `.ma` - -![Maya - Multiverse Asset Creator](assets/maya-multiverse_openpype_asset_creator.png) - -![Maya - Multiverse Asset Creator](assets/maya-multiverse_openpype_composition_creator.png) - -![Maya - Multiverse Asset Creator](assets/maya-multiverse_openpype_override_creator.png) - -![Maya - Multiverse Asset Creator](assets/maya-multiverse_openpype_look_creator.png) - -### Publishers - -The relative publishers for Multiverse USD Asset, Composition, Override and Look -are available. The first three write USD files to disk, while look writes a maya -file. All communicate publish info to the OpenPype database. - -![Maya - Multiverse Publisher](assets/maya-multiverse_openpype_publishers.png) - - -### Loader - -The loader creates a Multiverse "Compound" shape node reading the USD file of -choice. All data is _streamed_ to the viewport and not contained in Maya. Thanks -to the various viewport draw options the user can strategically decide how to -minimize the cost of viewport draw effectively being able to load any data, this -allows to bring into Maya scenes of virtually unlimited complexity. - -![Maya - Multiverse Loader](assets/maya-multiverse_openpype_loader.png) - -:::tip Note -When using the Loader, Multiverse, by design, never "imports" USD data into the -Maya scene as Maya data. Instead, when desired, Multiverse permits to import -specific USD primitives into the Maya scene as Maya data selectively from MEOW, -it also tracks what is being imported, so upon modification, it is possible to -write (create & publish) the modifies data as a USD file for being layered on -top of its relative Compound. See the [Multiverse Importer]( -https://j-cube.jp/solutions/multiverse/docs/usage/importer)) documentation. -::: - -### Look - -In OpenPype a Multiverse Look is a Maya file that contains shading networks that -are assigned to the items of a Multiverse Compound. - -Multiverse Looks are typically Maya-referenced in the lighting and shot scenes. - -Materials are assigned to the USD items in the Compound via the "material -assignment" information that is output in the lookdev stage by a Multiverse -Override. Once published the override can be Layered on the Compound so that -materials will be assigned to items. Finally, an attribute Override on the root -item of the Compound is used to define the `namespace` with which the shading -networks were referenced in Maya. At this point the renderer knows which -material to assign to which item and it is possible to render and edit the -materials as usual. Because the material exists in Maya you can perform IPR and -tune the materials as you please. - -As of Multiverse 7 it is also possible to write shading networks inside the USD -files: that is achieved by using either the Asset writer (if material are -defined in the modeling stage) and the Override writer (if materials are -defined in the lookdev or later stage). Shading networks in USD can then be -rendered in 3Delight NSI or used for interchange with DCC apps. - -Some interesting consequences of USD shading networks in Multiverse: - -1. they can be overridden by a shading network in Maya by assigning in MEOW a - Maya material as an override -2. they are available for assignment in MEOW, so you can assign a USD material - to an item as an override -3. From Hypershade you can use the Multiverse USD shading network write File> - Export option to write USD shading network libraries to then layer on an asset - and perform 2. again. - - -### Rendering - -Multiverse offers procedural rendering with all the major production renderers: - -- 3DelightNSI -- Arnold -- Redshift -- RenderMan -- VRay - -Procedural rendering effectively means that data is _streamed_ to the renderer -at render-time, without the need to store the data in the Maya scene (this -effectively means small .ma/.mb files that load fast) nor in the renderer native -file format scene description file (this effectively means tiny `.nsi` / `.ass` -/ `.vrscene` / `.rib` files that load fast). - -This is completely transparent to the user: Multiverse Compound nodes present in -the scene, once a render is launched, will stream data to the renderer in a -procedural fashion. - - -### Example Multiverse Pipeline and API - -An example diagram of the data flow in a Maya pipeline using Multiverse is -available, see the [Multiverse Pipeline]( -https://j-cube.jp/solutions/multiverse/docs/pipeline) documentation. - - -A very easy to use Python API to automate any task is available, the API is -user friendly and does not require any knowledge of the vast and complex USD -APIs. See the [Multiverse Python API]( -https://j-cube.jp/solutions/multiverse/docs/dev/python-api.html) documentation. - - -## Working with Yeti in OpenPype - -OpenPype can work with [Yeti](https://peregrinelabs.com/yeti/) in two data modes. -It can handle Yeti caches and Yeti rigs. - -### Creating and publishing Yeti caches - -Let start by creating simple Yeti setup, just one object and Yeti node. Open new -empty scene in Maya and create sphere. Then select sphere and go **Yeti → Create Yeti Node on Mesh** -Open Yeti node graph **Yeti → Open Graph Editor** and create setup like this: - -![Maya - Yeti Basic Graph](assets/maya-yeti_basic_setup.jpg) - -It doesn't matter what setting you use now, just select proper shape in first -*Import* node. Select your Yeti node and create *Yeti Cache instance* - **OpenPype → Create...** -and select **Yeti Cache**. Leave `Use selection` checked. You should end up with this setup: - -![Maya - Yeti Basic Setup](assets/maya-yeti_basic_setup_outline.jpg) - -You can see there is `yeticacheDefault` set. Instead of *Default* it could be named with -whatever name you've entered in `subset` field during instance creation. - -We are almost ready for publishing cache. You can check basic settings by selecting -Yeti cache set and opening *Extra attributes* in Maya **Attribute Editor**. - -![Maya - Yeti Basic Setup](assets/maya-yeti_cache_attributes.jpg) - -Those attributes there are self-explanatory, but: - -- `Preroll` is number of frames simulation will run before cache frames are stored. -This is useful to "steady" simulation for example. -- `Frame Start` from what frame we start to store cache files -- `Frame End` to what frame we are storing cache files -- `Fps` of cache -- `Samples` how many time samples we take during caching - -You can now publish Yeti cache as any other types. **OpenPype → Publish**. It will -create sequence of `.fur` files and `.fursettings` metadata file with Yeti node -setting. - -### Loading Yeti caches - -You can load Yeti cache by **OpenPype → Load ...**. Select your cache, right+click on -it and select **Load Yeti cache**. This will create Yeti node in scene and set its -cache path to point to your published cache files. Note that this Yeti node will -be named with same name as the one you've used to publish cache. Also notice that -when you open graph on this Yeti node, all nodes are as they were in publishing node. - -### Creating and publishing Yeti Rig - -Yeti Rigs are working in similar way as caches, but are more complex and they deal with -other data used by Yeti, like geometry and textures. - -Let's start by [loading](artist_hosts_maya.md#loading-model) into new scene some model. -I've loaded my Buddha model. - -Create select model mesh, create Yeti node - **Yeti → Create Yeti Node on Mesh** and -setup similar Yeti graph as in cache example above. - -Then select this Yeti node (mine is called with default name `pgYetiMaya1`) and -create *Yeti Rig instance* - **OpenPype → Create...** and select **Yeti Cache**. -Leave `Use selection` checked. - -Last step is to add our model geometry to rig instance, so middle+drag its -geometry to `input_SET` under `yetiRigDefault` set representing rig instance. -Note that its name can differ and is based on your subset name. - -![Maya - Yeti Rig Setup](assets/maya-yeti_rig.jpg) - -Save your scene and ready for publishing our new simple Yeti Rig! - -Go to publish **OpenPype → Publish** and run. This will publish rig with its geometry -as `.ma` scene, save Yeti node settings and export one frame of Yeti cache from -the beginning of your timeline. It will also collect all textures used in Yeti -node, copy them to publish folder `resource` directory and set *Image search path* -of published node to this location. - -:::note Collect Yeti Cache failure -If you encounter **Collect Yeti Cache** failure during collecting phase, and the error is like -```fix -No object matches name: pgYetiMaya1Shape.cbId -``` -then it is probably caused by scene not being saved before publishing. -::: - -### Loading Yeti Rig - -You can load published Yeti Rigs as any other thing in OpenPype - **OpenPype → Load ...**, -select you Yeti rig and right+click on it. In context menu you should see -**Load Yeti Cache** and **Load Yeti Rig** items (among others). First one will -load that one frame cache. The other one will load whole rig. - -Notice that although we put only geometry into `input_SET`, whole hierarchy was -pulled inside also. This allows you to store complex scene element along Yeti -node. - -:::tip auto-connecting rig mesh to existing one -If you select some objects before loading rig it will try to find shapes -under selected hierarchies and match them with shapes loaded with rig (published -under `input_SET`). This mechanism uses *cbId* attribute on those shapes. -If match is found shapes are connected using their `outMesh` and `outMesh`. Thus you can easily connect existing animation to loaded rig. -::: - -## Working with Xgen in OpenPype - -OpenPype support publishing and loading of Xgen interactive grooms. You can publish -them as mayaAscii files with scalps that can be loaded into another maya scene, or as -alembic caches. - -### Publishing Xgen Grooms - -To prepare xgen for publishing just select all the descriptions that should be published together and the create Xgen Subset in the scene using - **OpenPype menu** → **Create**... and select **Xgen Interactive**. Leave Use selection checked. - -For actual publishing of your groom to go **OpenPype → Publish** and then press ▶ to publish. This will export `.ma` file containing your grooms with any geometries they are attached to and also a baked cache in `.abc` format - - -:::tip adding more descriptions -You can add multiple xgen description into the subset you are about to publish, simply by -adding them to the maya set that was created for you. Please make sure that only xgen description nodes are present inside of the set and not the scalp geometry. -::: - -### Loading Xgen - -You can use published xgens by loading them using OpenPype Publisher. You can choose to reference or import xgen. We don't have any automatic mesh linking at the moment and it is expected, that groom is published with a scalp, that can then be manually attached to your animated mesh for example. - -The alembic representation can be loaded too and it contains the groom converted to curves. Keep in mind that the density of the alembic directly depends on your viewport xgen density at the point of export. - - - -## Using Redshift Proxies - -OpenPype supports working with Redshift Proxy files. You can create Redshift Proxy from almost -any hierarchy in Maya and it will be included there. Redshift can export animation -proxy file per frame. - -### Creating Redshift Proxy - -To mark data to publish as Redshift Proxy, select them in Maya and - **OpenPype → Create ...** and -then select **Redshift Proxy**. You can name your subset and hit **Create** button. - -You can enable animation in Attribute Editor: - -![Maya - Yeti Rig Setup](assets/maya-create_rs_proxy.jpg) - -### Publishing Redshift Proxies - -Once data are marked as Redshift Proxy instance, they can be published - **OpenPype → Publish ...** - -### Using Redshift Proxies - -Published proxy files can be loaded with OpenPype Loader. It will create mesh and attach Redshift Proxy -parameters to it - Redshift will then represent proxy with bounding box. - -## Using VRay Proxies - -OpenPype support publishing, loading and using of VRay Proxy in look management. Their underlying format -can be either vrmesh or alembic. - -:::warning vrmesh or alembic and look management -Be aware that **vrmesh** cannot be used with looks as it doesn't retain IDs necessary to map shaders to geometry. -::: - -### Creating VRay Proxy - -To create VRay Proxy, select geometry you want and - **OpenPype → Create ...** select **VRay Proxy**. Name your -subset as you want and press **Create** button. - -This will create `vrayproxy` set for your subset. You can set some options in Attribute editor, mainly if you want -export animation instead of single frame. - -![Maya - VRay Proxy Creation](assets/maya-vray_proxy.jpg) - -### Publishing VRay Proxies - -VRay Proxy can be published - **OpenPype → Publish ...**. It will publish data as VRays `vrmesh` format and as -Alembic file. - -## Using VRay Proxies - -You can load VRay Proxy using loader - **OpenPype → Loader ...** - -![Maya - VRay Proxy Creation](assets/maya-vray_proxy-loader.jpg) - -Select your subset and right-click. Select **Import VRay Proxy (vrmesh)** to import it. - -:::note -Note that even if it states `vrmesh` in descriptions, if loader finds Alembic published along (default behavior) it will -use abc file instead of vrmesh as it is more flexible and without it looks doesn't work. -::: diff --git a/website/docs/artist_hosts_maya_multiverse.md b/website/docs/artist_hosts_maya_multiverse.md new file mode 100644 index 0000000000..e6520bafa0 --- /dev/null +++ b/website/docs/artist_hosts_maya_multiverse.md @@ -0,0 +1,237 @@ +--- +id: artist_hosts_maya_multiverse +title: Multiverse for Maya +sidebar_label: Multiverse USD +--- + +## Working with Multiverse in OpenPype + +OpenPype supports creating, publishing and loading of [Multiverse | USD]( +https://multi-verse.io) data. The minimum Multiverse version supported is v6.7, +and version 7.0 is recommended. + +In a nutshell it is possible to: + +- Create USD Assets, USD compositions, USD Overrides. + + This _creates_ OpenPype instances as Maya set nodes that contain information + for published USD data. + +- Create Multiverse Looks. + + This _creates_ OpenPype instances as Maya set nodes that contain information + for published Maya shading networks data and USD material assignment data. + +- Publish USD Assets, USD compositions and USD Overrides. + + This _writes_ USD files to disk and _publishes_ information to the OpenPype + database. + +- Publish Multiverse Looks. + + This _writes_ a Maya file containing shading networks (to import in Maya), a + USD override file containing material assignment information (to layer in a + Multiverse Compound), it copies original & mip-mapped textures to disk and + _publishes_ information to the OpenPype database. + +- Load any USD data into Multiverse "Compound" shape nodes. + + This _reads_ USD files (and also Alembic files) into Maya by _streaming_ them + to the viewport. + +- Rendering USD data procedurally with 3DelightNSI, Arnold, Redshift, + RenderMan and VRay. + + This reads USD files by _streaming_ them procedurally to the renderer, at + render time. + +USD files written by Multiverse are 100% native USD data, they can be exchanged +with any other DCC applications able to interchange USD. Likewise, Multiverse +can read native USD data created by other applications. The USD extensions are +supported: `.usd` (binary), `.usda` (ASCII), `.usdz`. (zipped, optionally with +textures). Sequences of USD files can also be read via "USD clips". + +It is also possible to load Alembic data (`.abc`) in Multiverse Compounds, +further compose it & override it in other USD files, and render it procedurally. +Alembic data is always converted on the fly (in memory) to USD data. USD clip +from Alembic data are also supported. + + +### Configuration + +To configure Multiverse in OpenPype, an admin privileges needs to setup a new +OpenPype tool in the OpenPype Project Settings, using a similar configuration as +the one depicted here: + +![Maya - Multiverse Setup](assets/maya-multiverse_setup.png) + +For more information about setup of Multiverse please refer to the relative page +on the [Multiverse official documentation](https://multi-verse.io/docs). + + +### Understanding Assets, Compounds, Compositions, Overrides and Layering + +In Multiverse we use some terminology that relates to USD I/O: terms like +"Assets", "Compounds", "Compositions", "Overrides" and "Layering". + +Please hop to the new [Multiverse Introduction]( +https://j-cube.jp/solutions/multiverse/docs/usage/introduction) page on the +official documentation to understand them before reading the next sections. + + +### Creators + +It is possible to create OpenPype "instances" (resulting in Maya set containers) +for publishing Multiverse USD Assets, Compositions, Overrides and Looks. + +When creating OpenPype instances for Multiverse USD Asset, Composition, +Override and Look, the creator plug-in will put the relative selected data in a +Maya set node which holds the properties used by the Multiverse data writer for +publishing. + +You can choose the USD file format in the Creators' set nodes: + +- Assets: `.usd` (default) or `.usda` or `.usdz` +- Compositions: `.usda` (default) or `.usd` +- Overrides: `.usda` (default) or `.usd` +- Looks: `.ma` + +![Maya - Multiverse Asset Creator](assets/maya-multiverse_openpype_asset_creator.png) + +![Maya - Multiverse Asset Creator](assets/maya-multiverse_openpype_composition_creator.png) + +![Maya - Multiverse Asset Creator](assets/maya-multiverse_openpype_override_creator.png) + +![Maya - Multiverse Asset Creator](assets/maya-multiverse_openpype_look_creator.png) + +### Publishers + +The relative publishers for Multiverse USD Asset, Composition, Override and Look +are available. The first three write USD files to disk, while look writes a Maya +file along with the mip-mapped textures. All communicate publish info to the +OpenPype database. + +![Maya - Multiverse Publisher](assets/maya-multiverse_openpype_publishers.png) + + +### Loader + +The loader creates a Multiverse "Compound" shape node reading the USD file of +choice. All data is _streamed_ to the viewport and not contained in Maya. Thanks +to the various viewport draw options the user can strategically decide how to +minimize the cost of viewport draw effectively being able to load any data, this +allows to bring into Maya scenes of virtually unlimited complexity. + +![Maya - Multiverse Loader](assets/maya-multiverse_openpype_loader.png) + +:::tip Note +When using the Loader, Multiverse, by design, never "imports" USD data into the +Maya scene as Maya data. Instead, when desired, Multiverse permits to import +specific USD primitives, or entire hierarchies, into the Maya scene as Maya data +selectively from MEOW, it also tracks what is being imported with a "live +connection" , so upon modification, it is possible to write (create & publish) +the modifies data as a USD file for being layered on top of its relative +Compound. See the [Multiverse Importer]( +https://j-cube.jp/solutions/multiverse/docs/usage/importer)) documentation. +::: + +### Look + +In OpenPype a Multiverse Look is the combination of: + +- a Maya file that contains the shading networks that were assigned to the items + of a Multiverse Compound. +- a Multiverse USD Override file that contains the material assignment + information (which Maya material was assigned to which USD item) +- mip-mapped textures + +Multiverse Look shading networks are typically Maya-referenced in the lighting +and shot scenes. + +Materials are assigned to the USD items in the Compound via the "material +assignment" information that is output in the lookdev stage by a Multiverse +Override. Once published the override can be Layered on the Compound so that +materials will be assigned to items. Finally, an attribute Override on the root +item of the Compound is used to define the `namespace` with which the shading +networks were referenced in Maya. At this point the renderer knows which +material to assign to which item and it is possible to render and edit the +materials as usual. Because the material exists in Maya you can perform IPR and +tune the materials as you please. + +The Multiverse Look will also publish textures in optimized mip-map format, +currently supporting the `.tdl` (Texture Delight) mip map format of the 3Delight +NSI renderer. MipMaps are required when the relative option is checked and you +are publishing Multiverse Looks with the `final` or `-` subset, while they are +not required with the `WIP` or `test` subsets. MipMaps are found automatically +as long as they exist alongside the original textures. Their generation can be +automatic when using 3Delight for Maya or can be manual by using the `tdlmake` +binary utility. + + +### About embedding shading networks in USD + +Alternatively, but also complementary to the Multiverse Look, as of Multiverse +7 it is also possible to write shading networks _inside_ USD files: that is +achieved by using either the Asset writer (if material are defined in the +modeling stage) and the Override writer (if materials are defined in the lookdev +or later stage). + +Some interesting consequences of USD shading networks in Multiverse: + +1. they can be overridden by a shading network in Maya by assigning in MEOW a + Maya material as an override +2. they are available for assignment in MEOW, so you can assign a USD material + to an item as an override +3. From Hypershade you can use the Multiverse USD shading network write File> + Export option to write USD shading network libraries to then layer on an asset + and perform 2. again. + +Note that: + +- Shading networks in USD can then be currently rendered with + 3DelightNSI +- Shading networks in USD can be used for interchange with DCC apps. Multiverse + shading networks are written natively with the USD Shade schema. +- usdPreviewSurface shading networks are too considered embedded shading + networks, though they are classified separately from non-preview / final + quality shading networks +- USDZ files use usdPreviewSurface shading networks, and therefore can be, too, + rendered (with 3DelightNSI) +- in case both usdPreviewSurface and final quality shading networks, the latter + will be used for rendering (while the former can be previewed in the viewport) +- it is possible to disable rendering of any embedded shading network via the + relative option in the Compound Attribute Editor. + + +### Rendering + +Multiverse offers procedural rendering with all the major production renderers: + +- 3DelightNSI +- Arnold +- Redshift +- RenderMan +- VRay + +Procedural rendering effectively means that data is _streamed_ to the renderer +at render-time, without the need to store the data in the Maya scene (this +effectively means small .ma/.mb files that load fast) nor in the renderer native +file format scene description file (this effectively means tiny `.nsi` / `.ass` +/ `.vrscene` / `.rib` files that load fast). + +This is completely transparent to the user: Multiverse Compound nodes present in +the scene, once a render is launched, will stream data to the renderer in a +procedural fashion. + + +### Example Multiverse Pipeline and API + +An example diagram of the data flow in a Maya pipeline using Multiverse is +available, see the [Multiverse Pipeline]( +https://j-cube.jp/solutions/multiverse/docs/pipeline) documentation. + + +A very easy to use Python API to automate any task is available, the API is +user friendly and does not require any knowledge of the vast and complex USD +APIs. See the [Multiverse Python API]( +https://j-cube.jp/solutions/multiverse/docs/dev/python-api.html) documentation. diff --git a/website/docs/artist_hosts_maya_redshift.md b/website/docs/artist_hosts_maya_redshift.md new file mode 100644 index 0000000000..268c49d75c --- /dev/null +++ b/website/docs/artist_hosts_maya_redshift.md @@ -0,0 +1,31 @@ +--- +id: artist_hosts_maya_redshift +title: Redshift for Maya +sidebar_label: Redshift +--- + +## Working with Redshift in OpenPype + +### Using Redshift Proxies + +OpenPype supports working with Redshift Proxy files. You can create Redshift Proxy from almost +any hierarchy in Maya and it will be included there. Redshift can export animation +proxy file per frame. + +### Creating Redshift Proxy + +To mark data to publish as Redshift Proxy, select them in Maya and - **OpenPype → Create ...** and +then select **Redshift Proxy**. You can name your subset and hit **Create** button. + +You can enable animation in Attribute Editor: + +![Maya - Yeti Rig Setup](assets/maya-create_rs_proxy.jpg) + +### Publishing Redshift Proxies + +Once data are marked as Redshift Proxy instance, they can be published - **OpenPype → Publish ...** + +### Using Redshift Proxies + +Published proxy files can be loaded with OpenPype Loader. It will create mesh and attach Redshift Proxy +parameters to it - Redshift will then represent proxy with bounding box. diff --git a/website/docs/artist_hosts_maya_vray.md b/website/docs/artist_hosts_maya_vray.md new file mode 100644 index 0000000000..a19fce9735 --- /dev/null +++ b/website/docs/artist_hosts_maya_vray.md @@ -0,0 +1,44 @@ +--- +id: artist_hosts_maya_vray +title: VRay for Maya +sidebar_label: VRay +--- + +## Working with VRay in OpenPype + +### #Using VRay Proxies + +OpenPype support publishing, loading and using of VRay Proxy in look management. Their underlying format +can be either vrmesh or alembic. + +:::warning vrmesh or alembic and look management +Be aware that **vrmesh** cannot be used with looks as it doesn't retain IDs necessary to map shaders to geometry. +::: + +### Creating VRay Proxy + +To create VRay Proxy, select geometry you want and - **OpenPype → Create ...** select **VRay Proxy**. Name your +subset as you want and press **Create** button. + +This will create `vrayproxy` set for your subset. You can set some options in Attribute editor, mainly if you want +export animation instead of single frame. + +![Maya - VRay Proxy Creation](assets/maya-vray_proxy.jpg) + +### Publishing VRay Proxies + +VRay Proxy can be published - **OpenPype → Publish ...**. It will publish data as VRays `vrmesh` format and as +Alembic file. + +## Using VRay Proxies + +You can load VRay Proxy using loader - **OpenPype → Loader ...** + +![Maya - VRay Proxy Creation](assets/maya-vray_proxy-loader.jpg) + +Select your subset and right-click. Select **Import VRay Proxy (vrmesh)** to import it. + +:::note +Note that even if it states `vrmesh` in descriptions, if loader finds Alembic published along (default behavior) it will +use abc file instead of vrmesh as it is more flexible and without it looks doesn't work. +::: diff --git a/website/docs/artist_hosts_maya_xgen.md b/website/docs/artist_hosts_maya_xgen.md new file mode 100644 index 0000000000..8b0174a29f --- /dev/null +++ b/website/docs/artist_hosts_maya_xgen.md @@ -0,0 +1,29 @@ +--- +id: artist_hosts_maya_xgen +title: Xgen for Maya +sidebar_label: Xgen +--- + +## Working with Xgen in OpenPype + +OpenPype support publishing and loading of Xgen interactive grooms. You can publish +them as mayaAscii files with scalps that can be loaded into another maya scene, or as +alembic caches. + +### Publishing Xgen Grooms + +To prepare xgen for publishing just select all the descriptions that should be published together and the create Xgen Subset in the scene using - **OpenPype menu** → **Create**... and select **Xgen Interactive**. Leave Use selection checked. + +For actual publishing of your groom to go **OpenPype → Publish** and then press ▶ to publish. This will export `.ma` file containing your grooms with any geometries they are attached to and also a baked cache in `.abc` format + + +:::tip adding more descriptions +You can add multiple xgen description into the subset you are about to publish, simply by +adding them to the maya set that was created for you. Please make sure that only xgen description nodes are present inside of the set and not the scalp geometry. +::: + +### Loading Xgen + +You can use published xgens by loading them using OpenPype Publisher. You can choose to reference or import xgen. We don't have any automatic mesh linking at the moment and it is expected, that groom is published with a scalp, that can then be manually attached to your animated mesh for example. + +The alembic representation can be loaded too and it contains the groom converted to curves. Keep in mind that the density of the alembic directly depends on your viewport xgen density at the point of export. diff --git a/website/docs/artist_hosts_maya_yeti.md b/website/docs/artist_hosts_maya_yeti.md new file mode 100644 index 0000000000..f5a6a4d2c9 --- /dev/null +++ b/website/docs/artist_hosts_maya_yeti.md @@ -0,0 +1,108 @@ +--- +id: artist_hosts_maya_yeti +title: Yeti for Maya +sidebar_label: Yeti +--- + +## Working with Yeti in OpenPype + +OpenPype can work with [Yeti](https://peregrinelabs.com/yeti/) in two data modes. +It can handle Yeti caches and Yeti rigs. + +### Creating and publishing Yeti caches + +Let start by creating simple Yeti setup, just one object and Yeti node. Open new +empty scene in Maya and create sphere. Then select sphere and go **Yeti → Create Yeti Node on Mesh** +Open Yeti node graph **Yeti → Open Graph Editor** and create setup like this: + +![Maya - Yeti Basic Graph](assets/maya-yeti_basic_setup.jpg) + +It doesn't matter what setting you use now, just select proper shape in first +*Import* node. Select your Yeti node and create *Yeti Cache instance* - **OpenPype → Create...** +and select **Yeti Cache**. Leave `Use selection` checked. You should end up with this setup: + +![Maya - Yeti Basic Setup](assets/maya-yeti_basic_setup_outline.jpg) + +You can see there is `yeticacheDefault` set. Instead of *Default* it could be named with +whatever name you've entered in `subset` field during instance creation. + +We are almost ready for publishing cache. You can check basic settings by selecting +Yeti cache set and opening *Extra attributes* in Maya **Attribute Editor**. + +![Maya - Yeti Basic Setup](assets/maya-yeti_cache_attributes.jpg) + +Those attributes there are self-explanatory, but: + +- `Preroll` is number of frames simulation will run before cache frames are stored. +This is useful to "steady" simulation for example. +- `Frame Start` from what frame we start to store cache files +- `Frame End` to what frame we are storing cache files +- `Fps` of cache +- `Samples` how many time samples we take during caching + +You can now publish Yeti cache as any other types. **OpenPype → Publish**. It will +create sequence of `.fur` files and `.fursettings` metadata file with Yeti node +setting. + +### Loading Yeti caches + +You can load Yeti cache by **OpenPype → Load ...**. Select your cache, right+click on +it and select **Load Yeti cache**. This will create Yeti node in scene and set its +cache path to point to your published cache files. Note that this Yeti node will +be named with same name as the one you've used to publish cache. Also notice that +when you open graph on this Yeti node, all nodes are as they were in publishing node. + +### Creating and publishing Yeti Rig + +Yeti Rigs are working in similar way as caches, but are more complex and they deal with +other data used by Yeti, like geometry and textures. + +Let's start by [loading](artist_hosts_maya.md#loading-model) into new scene some model. +I've loaded my Buddha model. + +Create select model mesh, create Yeti node - **Yeti → Create Yeti Node on Mesh** and +setup similar Yeti graph as in cache example above. + +Then select this Yeti node (mine is called with default name `pgYetiMaya1`) and +create *Yeti Rig instance* - **OpenPype → Create...** and select **Yeti Cache**. +Leave `Use selection` checked. + +Last step is to add our model geometry to rig instance, so middle+drag its +geometry to `input_SET` under `yetiRigDefault` set representing rig instance. +Note that its name can differ and is based on your subset name. + +![Maya - Yeti Rig Setup](assets/maya-yeti_rig.jpg) + +Save your scene and ready for publishing our new simple Yeti Rig! + +Go to publish **OpenPype → Publish** and run. This will publish rig with its geometry +as `.ma` scene, save Yeti node settings and export one frame of Yeti cache from +the beginning of your timeline. It will also collect all textures used in Yeti +node, copy them to publish folder `resource` directory and set *Image search path* +of published node to this location. + +:::note Collect Yeti Cache failure +If you encounter **Collect Yeti Cache** failure during collecting phase, and the error is like +```fix +No object matches name: pgYetiMaya1Shape.cbId +``` +then it is probably caused by scene not being saved before publishing. +::: + +### Loading Yeti Rig + +You can load published Yeti Rigs as any other thing in OpenPype - **OpenPype → Load ...**, +select you Yeti rig and right+click on it. In context menu you should see +**Load Yeti Cache** and **Load Yeti Rig** items (among others). First one will +load that one frame cache. The other one will load whole rig. + +Notice that although we put only geometry into `input_SET`, whole hierarchy was +pulled inside also. This allows you to store complex scene element along Yeti +node. + +:::tip auto-connecting rig mesh to existing one +If you select some objects before loading rig it will try to find shapes +under selected hierarchies and match them with shapes loaded with rig (published +under `input_SET`). This mechanism uses *cbId* attribute on those shapes. +If match is found shapes are connected using their `outMesh` and `outMesh`. Thus you can easily connect existing animation to loaded rig. +::: diff --git a/website/docs/assets/maya-multiverse_openpype_look_creator.png b/website/docs/assets/maya-multiverse_openpype_look_creator.png index 14541e9e398fc99e3910d50a5286bdd8906b9f1a..fd27d5fd1af7d8ac4d9bc83799c52972615f251e 100644 GIT binary patch literal 125727 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 Pp4Go+auDm7PT|Bi* literal 26190 zcmbTeWl&vB*DZ>>yK8WFcMlH1J-EBOyA#|!xI=I!IKkcBHtuqF-skTm=LKlo133Oc@3W*z&rrJqBzDv=kFl za#oQNC6SR3<7DCC76A6ZK1xrXfe*BI(P-}@o!|%a*gS7e4tRnt3wSkiJn8#i+*5vYH^Y+)4_teR{b0496 zWv135r1mf?S%IOUnH%G_Z(%lEhCBjo#ZP2Kg)kbPO?yY4hxUO{wN=2Dau*Jlnu8h~ z^3Qf7*m&4*4f>58h8-`C5_-5im{xq1{Hxfa5xBQ=hlbufB>X?WVcM)i!AA`UUWcs4 zWM#A{!9j(`?}1Cm1R?-ZvBYQGt~O7 zw&Lo9L!bC1RHCC?Ym^8(3q^pDO@8X+bE`Wuax+)d%>ZSL*!Hy#w6@q{7M@wY0iMc-vio!4s4@jrn0BPSOo(nUm`*6Hv}5NLF)xpr6f$@^UCm0O$Q|msEuxwR>x3p%SMc- zj9&NjomQB*V^!hK0B<=`u>FDbedm6M*=m>PWl%P}l(YbWmOR zb>{y2cu2UbywBvKbu7}jh#fp$E^tfIUG+Faz8Z*44nf}cUh;A(Z|oWVAX~JM5VA1q zV_g{fdRRpx6GRPM=D8=$U-0R06X1e4eVnkOqOb=nFa;+NPD)UxSlj;e*PfF6y;;2! zsZR)oIS=n6y~-Eff)25%J*09vAIMjatB-IO0N!mtz-j3hheAko5|gy4Hc|3Lx43tM^_~&$m~nmxw%XTIEpp zTbg~O@|J%7k3rnsZ`-voEqbtTu&%KNoU2w+?THWC+f0Gj%0!pMu!6Y`x(F#&TX~>< z*kXPnFuO3PQctv}rT3sjDWG%DEvS#Ar;;4d-zSOp*%>Cnw|-}QvvO3l+F9&-W-1)E z^C1-7$c-G@`qExiJ-eT+jpb>vA3&3Z1_Ks~U1Em_f$~+;4GAo!eDChj_ zP*;b$GK_Eu4~SoD+8Vpp&Q+3>Is(fl7tgT=(c<*X4&V~c{glLY%25iwol}CEj`5DF zXDeVnOsM&OE;kH-)C=UVFTefHU{iydib*zoUv3=5UgVvdc=>hjRo@WvJ@~)gJ09{wXbeUvuo2w(P4b!<&I^oE8;U{2870YA`i; zYLFMRXH(mSc5a^KPA$$wF36j~b~@H0Bt!hA<3==Iji)I5tH7xbjHoU%;?iq5@g4r~ zqZ`8{xa6`-iunWlaOw`&(+{i`W!YDJhPOOe_Wmx2xbV{59+s`ye%FC}xW0{I1FP%s zfP3Lm`3~787jFi~#-_Hd)%}IYA%_KP%O3kkotmMps?ak%z)Ko1==MJ@Oh-&g zVTZnZ&Nw}NqflmR^S8w1j)FNCPaSpGU_u~B`F1y!+Pqzt{MDO}fu9SzSZF=Yol37A z3uuoO507B;VkcmR-B@-UbzZsFbw`w7R930r8>l+8K(~cz=lT|N3a4l0<8Om=pnxm6 zX<;FbOR5CbAK7j+VYiwr`DUt}3rjsv=j9c>9CG4X zwl;5zR@(~73UlYOSvS~I_MQ`kx0Y=#V?60B{pwBh0Dh6z;Pd(Y$qbJ2dt2C){+rOT z=@ayUVo|&JpYI&sA}?vPN$bv3Vpw%44?#ZYw3h(^;{!f-mxZIk5ifb7xX){_*{3}i z9OTy3uR`SWgvbg-W>E43sUBw>C*qUor<%7De?BfL?}WW8m4(Vj6ZrZ$zM*^{BG}`t z30=T5GQUFJKFwV&f{1r4DKg7Fhx*B;EJ?D;Qqwcr?*rjY;%2Oi!p-@B^Knhf^if}> zE4z}Wtr&s$f|It8RYwl_d4o#qXRK5nN@#wpZ$WRY>)q5OR5$qEh_zG{`e?DU$WlXW z1k_KL`)!$VQcg+US4UgkNBG9bEqYv567PcKhY-r^h# zixi*%=h!>7mnoP3=mN!IVg3EcQDi%%``%gUl`m-o@|3cb?uZ!X!ZGl@fA%?*9Uex1 zcgFs?hN&=S-|=E;ce*epSwFSgW*ch$7m`M+pTfnAK#fCQe12z77|aPjgjX{z1mktx zE5H1%5 zsPn2|nC27m)$Tx1nJvB@+Xe5QHYrC%TIh;l9;c8g>ctzDfb)qU5%IRolwM&eUadyd z)fP&rHZ=Ws9MlVjVBdAuK<sPxjyPb|(el0bZ?{6{B024&=|NivzNVXvohd8Hk^d2b(_xi^qsG~p_ z&1VGr3(U)ii%Lj~nh-8M?NNXH{4_4tkdq8LDPI5x38x5*;3Z!fWQq6eGr@=*7M1_F zPm}nHBQQQ?ESWM$RzoTK{@dl=tjcT^n$To?Q1NECD0t0_jixDTi8Wd z?%ncr2=xkZRs~0D<&gemHnZuja>JivQ*!gKhQutZ=6cyYD;A&MiY7qk`}-s$eF57B zJ{dtO(CJn_6HERslm%EcnU+&xo}^w`4a%$XbFnuA40Y^Wu1_#l%4SWhJ(j~G#`}M! zpIzR@%JRjLu!}Y|_$80sk5rT-9av5y-%0(3z#FT#Mr!kwqIro`V>#357Rm~-+89~K zvI4xqRSi6A@~m(9%L-G-unKDKjrx1O383D4YX!6;N!5SG$_q18xEAVuAi~oN+x9wdzm$R?@AxmdQJp@r zV7e;YZ7o0)b&f0bOe-A?T&6q47=NYkAbm(Dfh@tUsT9iucnSzRJbv3kAthcG-{!L+ z29e;rF9zp;=q><=Rcq>-$-D+&3Bi|BJ_+z}1p;u@EgUJz)686XHs_uU{z!{d?{n_? zeV^N8(0)6k)}z0D@6dv%fPGS`y{2!VIRZ=3y)k02CAQ-D6wSI*cVf6t*9$g}1^9EG zKK-EBunel*0!taIyvNICiL)mf%jdU`ToUp(Col&;rK}(Bk=yo8J+gHCG~0hSkR@WW zf~KT9v=dvj{uB&kl9-n4lJlE)lJiy$6%59Nqn_@&r8ubX8;D?^>5 zGMxKm-4!XooiaQ-Fy;8=^`ckfGoFRA2!T@*+9$(8jFDwQ^}fB*2MXzKIJ_s$*v@V7 z%poqF8Qr7J7)D)Z8~nx1NRxpW23{Y?(z$u;Pxk`2l8i639ZIrV>~zNrsj zo)Gv>Nelhx#qO`p8=$VWa!+S3-DzhP%`Bp>g$6@hT7$rIcy~?`rpe`hV;T#$f~KC{ z=a9{|^FTt&xz@%ntxWkgxBiC%>KS z6Q6n{$nfSmkN(U*4~6L~`2_nG+}pl$Q?M!j>OBuX*0a&{cVFW&vBiy{bK=SEY!X!lkRZVV~qm- z?lWcmvW$_Gc%EsMROExn4~EmBd%-OJlk&?*+z+@@e(WOsiVvnYfXk^)6HwL=_5}29 zDXAXM!Iy`JZ+25DHEiZd3bgfHRqcG*jeRw>Yo zJq6R3%ZKVlrtob7xzB3Bqq|+582ab@t7=lQ2^1P_mezB(!ze@b|*+=h`7mX3TM09R(}-l{GMy<@Hn6qHD(p!r3gSVZ-MzWULxU_14A_P38k_JX_Q z4h|n=vf15f2j9RRljloP@A&P}+d>U=BU%&kH{~ts2jAgJ#2~FuxPEN+9cjRP!&o+t z%(EUmQ%qCL_e-a2$M;7Z(_7&AnlI>qe>!5k=`sgvCKNaZdstdf>Me+E>f^Re{HPt) zKB};{B@X`9A$Hb;!aeho_%ZYey1_NEPf=%#V=X5L&)p-l;1(?~xMAATUr|J_18F|i zf%LTWiRSc0Faqsx4*ICb2x<(x9m7?S4c4bOB(t>jLo!#)(|22l>IHBkkpkMVNB(r* zBKMOp56d1eUxwQLpakXM_&&qf{m&H>1VFbsjU!x`RP6oH0WmOFm*|v#Ld;Y52$kCh zb+Q4v>vAu(g;3&R(e`rsSGsT{RdQ`2NNBVN9^)DE1*B|eO%6NZLBBTFx4N%BZ?GIh zhJpozkX?vBjhuUsq1^Gu8Dil7n(ghl^P(+4Lkc1C^_7I8@-TRKS!Hs`7D^&OWcUv^R@Wo;tjtbP5UigUXxp9 z4`?@Jpd!6$@OXG8A^S*)I+{f`@sPgzX+N`RuvO#cJ707UzUfFMX@3#Ls`Gpx>!y2w zNuoS>YAjxIXUuP2^*k~M<0Z)ycc3>R`sV4C;;dERv+>2Rirb$lZ+95Yu3mh{u=Z6y zPtEF#?Riy?$1`szntS?`dWu~{O$q-rD2F~yexl+j+^KtL-B3_?sSib)k&Ae9;YVBo z853iD?IR|47*O!-xEp||RRldyJJyr#T3d?W0)Hj^s9rZ1&<|wAD6o5Q*w1fAIh1Hi z=&fFM#ePAZSHDeG0-t!{RBt97Yn@U#IkFNO8}}J{5<*`v^~CcYAVB`kBUPq^9Dh-s z(FCoGoJ~`nvAx0Rd52q9vbyCjdBJTWxN=VcH(*(L`GY|i5q`OPZ8iF{z(QdU`9N-P2|mSN@fBLXiFR@r)`n zzQ7L6+2#ttYFKVddx8M{25nh@E3Ks%h5Pb`$S>H5#p;2U=s1iHn`6ylkq?ri&R5`c z^#r-#Yf#sYCj{P_^!-L)J0;DqIiuBF;!n=!^BMoMN@Zi2cE?fScejapsrOvJr6(cC zC#myrF(lMuOugEJXY!AsLRIBl$1vp)!t&5M%4tbgcH>ejEoOGuW`)zVAN{2pFa0WWlP!$l%xix0Xb-N|M&)JI z8~_al%c^nG30!es-X3I*XG5p1lInNr=}OekWxZlOdnX3h)k#`)%^S^Dk*t7B0%r66 zT#<{r+w4@cfL`2+C3?pmu?@||X7L^gSIp{Y<;er)MJ7o7|yuQOq22y5>Zj0uawJ@|YxCrl^FG`z}Mde#> zuafD{D0AlP@%e5D4LVesL3voR>%>ZW^R+0QDF@tv7s$BU)yOylk&<8VT>gf06-PLeI=Yn&+rv)1|BbeD{UC zf8E3&+w9D%f+L6J(v4i!xR#i&8nZ2TWmZU8y62nk_S$i)yDn@e$>aL?WV0eQX_Q}8 zD9W`~;<{ZZM+edk*@C5Io>(lOh@r;t{??T{VHI0MK7Vz5QR&F768xuMWc$j+CKY>Z z9Z&Z)!VXvAa`=m5l~^oAr%B2d5!r>5lHH5weEd7cXu6bpgWbsqec;sT(*$E7$} zc(ZZ;8uz|R{9=#|egW}BTwvofdAq(KFW+we`@H5MG%zY%w8 zcjqv;d}|i#EdNT7L7&*2Cscdeo5AYWZ_)YY+ro$bd4J>SLWDxkIHnPRC4V$4S>keh zJ13;1ZyLU>FFMDf<%*eFlJZCT42iSR)vP}gm{;|Om@cNkU^w6p2SBrq(jSo}2_%E7@XZ<{L?H&7pL zT3f`8d;ZFR;|#W_PnoOK2j1yDgEXvh&WC@8b^lj?e~C%avF}<)JN~MWr|vCbVqy{! z!&WcLHmkV?(RYCteZeWya%-CubFsMQ%Gj)MGh`+4hniCW!grY5ho$s^bkND8VOU2MvPs z1Z98%Ms$`mOco`in2dr#0wIkAW>o4MpH`3O6LZ8Ad?UqkAgNalU7)PoKmKxB!_>VwPBx0c{x|~-Y z&r=NITwa-_oyQa6t3Lqz?a236p0%y%l@C8b?q||&(1Q%bFE201WH^6Ji_^3XT-p_31{w&HbciE@q}U zX!Ztzosto^MnK)Q>#(SLb!XB^(JJMR?l#bri9De^NO-K7xw(NkMWM;Ok2@+XdQIk4 zdHC-`QJDPK`^oDKHn_k_0zw2I&@4s%=Pz{XRsS_jN&knvC@nG4-L3Yc&f9?mw)4w` zrPrp=81a^87IGYsasNEc9U-5>C-jK)^zVFs!vNTyk;n9xM=OMc6PnFQbW-DA2|Oc5 z+yKTev&~gGN66lmLUP0{6hz=7R57$#T3RNirl1K~S>vJD`rKv$HNcs28gDQa86IcX zc7w9zCU7cF#9P2gQv}YFimFUT`qUYqYusB(X#4ou`0YcOt7m-;ib^rj=?0^0OF)j2 zCi@O?25^hIOE~)F2!=_ZGG-C6Ju%adLW8pQl{!XQ-}{1N(;Fb*fhid2+=+oHjk>JfShenM*Q+OUE*F4)E^Er?#zqV8Guv6SsqJqD z#P8tXq+6FrgICO03g%ma$RKQ3HQx_@%+Z5$x{e zw6txWZ+5#tRL-_1)$(}1Us~yKXUcmTq-wkBhnMQ$G5QmWoxofK$rAT>f=Klysr|li zg-^92IXneF{&E;Ic~POd;!4vW>yF#{!}Z8Nb$3t06l@;-sNt@3TbWoskpzb442k80+UL)I~K=+JNyo4LL?(kjwvcbcga9F`5f z_R{PBB`zxqzohGoczZNWd{G)hQ^=}|Yl)to!4oNSWXjAUK)6+a%q?UK=I`!GB;?T$ zcuQHTnvy1UI_NSxoqe!hjPUaMve2eEuQ!BC-#NytaLd>y0}UVsWlZlwm?k31D)9cw zdG4P|7+f=sU?xtBJz%YENpt)Am0+jt#%+T6h{BN2NysPtF<1;dZX|<{o|{?Y^JPcP zGcwD+PVvw?+iZ=fc)DVwZZ#)>V4=!5h5PE^{E0Xb?Ikx}GmZ;zgFnLK+VWvjEtSK) zFcTlNlN-0|71STRtNA`0sfs{wY-D$6awwNI7Me`SVm$lBOg=+Sauq@$k0+XGNNTlD zOzsTd@TK@x2L2ZT8E2`%uOu~ia|iKXvVooc=>uj<7t&O@1AQ_<@DoEk17ahxsW+tt zy)HKW;OLH~x#-img!~-_@SJ9t!O7v&Y^!eg&s*%{f;VV{L{w?N#d~=a?d_RH;t26C z%t9#Ug*#n=rWF?#iyt{=tal$<*%_pdgutRpgChAok*cy$&H_Pmuoon4+VN^%LpS@J z*zj_H6r^}nx_ww&SYvE-ws|h_M0a<6Ah)mYtWtBiK5K*Tp$~ydVeKRJ$BFJQCz^2g zbF=V2{;Hen{C1kn2_tT=1Mk7_c(-5CUN-PsrcFa!|9lyE_+?PJP)+t@OI0w$}V&wb}6}Ac0)oa!M-6 z?qRvpBnRy6Sn+dwKN5X(bYg#LW8-Zy+h*zk`D!f~dd1^;B!knD>?aM)#E-NpNRbG1 zy6*bic?+!kFf#i?xffqSq#WL%&m4~EQOgs#uZnxC(Tscm!-tlZ?`y-D6J1n z5tGeGWA5hM;GB2n-#2gX5F7{E$I8Q&d{5$e!u>yI0o;94r>maX-K#6N7q;F>886Tv zcGk%mh|by=KbNJRCo&bm2j}l^J6A@-nCy4Yw${;RnmhL%R4$)yN{w1&ja+(otvu3A ztERIMh3%yfw|po1#+Wb?(;)9$X0Ezu*K>GM1fM{Lw6xtB9@U9fRR+&?mIDA}x zE!FS9A5_SF`-SXZ8+ABEy$8;`21bZnYm{|I2nj0P>lC5Y$k^{GV<)HAt%Yqc*u1ol z?8d;<{Z#nmj(a<%CDaif7l-5(xZ-(Wv!WudZVyc;AgoR_WYG3Qw|)6llqo^VFEG(v z0jn`^6I=TPL1)y}X1Op%#a;@UkgzM^$H)rF(Xe8$G{ zCE(K7Zf(ZgCaot}rZIJ7l;u?%MUMDQjWB&`c4mDqNDBj~)!XV|ZrL8Ii4bzEcU4E( zKF-g8Cj1y8$ae<7-+_5PFM%p0U_!!=&W&%hB!=@D&cH}Uah;rydJlkC zO{+?tT}t1>^>dRKz$*zgHziOJz4F8ZHb-M+;Xyg}+AeGOR@UCwx87d5CURX_^4rsU zWR54kBX8scl6Gyjo+rBrj_nZ&Z;HY>)rzfD`=XjZJ2f^2jropO#+azPDGWY|G(=HP zMZ53^G5g^r{txuBaL z(F~-frAg%)Bh7@qdULSf-QhVlE>!Jw9e$sczQ)R|Y-=sRZirfL*l8amC?)xVOG86b zyzX^Y-M!UA^u3De>wh-)&ckVd+D(DSQI0!J*E_N2(T13aqlfBF-H#MQW0=ZW1-BY2(7K^Lx9AuWxi4*$(IiR=B1ThTF2!_HSZqH~7ve5Zssy zv(mv|MtUB8B+y+!is0l8uB-XH{Cw%7K*-m>c%Etj+}yEQ(FAXdFYt~iW^W7m-y-#m4FpNVPxp$cN@50jh+U-8msKt<3sjK(=d4LvTb+ROi|!AP628X z{Ll6TCfld)35gxnzM&V~89+GWgp3v<>ezW1Ub-}WMfM-;Yu$>Z?aFz?xP&}c((T@^ zeFF9nvWFmlUP;+}?!*__wE{;${T=nX{T3@LG%+o5tOKztr|-<)T28+Jloz<@>_1ZOt<)rz8V)F=dP`u6!N{*;D9NAZ zJ(u((g97RrZE@`w{_RYv&T80~_+$bGw?@>0^_KTHlkMjbV7@WL6rtPia2f?&u@v}T_ zj-jF&B$EsqW2xVJ0A|B9@vy(WLEKrGj|oE=$G=X)GYMad!g(m1_3KIb=|jKbHBh@f z4Ft6Z{c63LNu?)hb@@DB3r+ldy$Xfad=kwMPu6Mw!JyHDvZK44*TG}v+j3IGhg<+l zAKZ6_Y1QfZ?F|~%pZySi_nhNRzzA5b; zF~n`rL1v~Xyc_s5uJ~H;x@}ANj%$m;N?cSvS|Tn&Kv?3_($5rYRFL%38!n5vGKdBMBNy5&$Dhw z1u87i7iEB3B5H{_Mm=8oI~4_aAyOi6_gmNE?k=Z<0YREH6`h{ULSmyB>aW=)G`Vgq z4YebMDps?d@km+QBHwAo=^LtpS2UO?v6Unt@Q7p$TWdUt3AH4u*w;*&epU0dKR{=9 zw0a#cjI-kQd&2ZN6EY7ISeLq}h-UuM+7QP5O4?c*^{5tbV>5%QRaR3Z<>1To9 zj(OIxQbn%_2!!Z%qSFustYFyFKzmu@e9J;^5D;r107{Ye_#N9?Sn^z z)a-Skw`aBFnDlb*vP+By8Ef;jU}+uOkop<~r?JIZx2?79m`Gz>WQy?nC9U5Dz7DtF zZiwxA@q~=e-r3{Lguh^4l#bnB9rYIvt+$r@IUurl(oF9 zBHU%h_Q}W@(p=~cPR6A%>Sd)%d;4OKoI_fo%iqRbFzl2REZ(~DLeH|fex;)i2S-4V z)NM6k**2KHdP?qtI^A9aT;1E-8U2M}pa8?)57ax&=6do{oFGaioK#EM?eEfeHINgX zwsPI+E`thROAiHy%A23OXJ5Yo(OH+EZ7K*0BkCC`;ZJi~zr5+w8cHyA4YO?Ap}~3# zTP-eve|*H#Y8x`JlM*BE8q#X{8N6nyx`|FW+>l5+X$o@^%Qg4U_nA+>(vhrF*uA`Yzxj*$;Ics9Q$LEdy(=<_i?x2(TE@ll?T zr{*x-M_6Su=GRmuX>`9iwckk+woMDBjiPJ)BU_2^mG^voBraW0wGWV9wFmlwWvS3;< z&_Ef+HlNCdW7g&s%!_9;S4ec+EGA7O{$!~EZ$7u1Hdo7VX8x_!k|}Y)AX4a`yXq7b z9W`H85i3#i74BN8qY`ms7pw`5TJt>LSA!up;khc5YwmEe?7ONG!Whgihj}%G)S{SF ziq1kLDd80)^{OyiNg@rxs%1^HCN>p)5gegA{_Q# zi+IkX;#j>i&c>;5f(~X6ax0*#l>-NWkE8w^iHc@pRj|>V31-ef1oyk_hb_@eOA}O3 zN7RgDG%bkxhE+ukLr5tloV{xBi#+P>3cIOu;+Te}jF>Cpw-nW^uG*?l=$y3(4X~p8G6`T6Mjm!_`ydoE+JUDLmKf?PYP0gL%D;>f;C|ZZ zvf^A=+70P+WwKJ4Fx{va{J~J`o{@J_?Pq6~#*2X_s?ScKPT+?KZg6&Tyk@?DLpNOrRFhj>0;l91@B$++kWG?k> z26s+i>9Ce?_kgTUqj?>ES^NNQY-ubSr{+MB5w|BWv62bQ2~#RLZ_deOwT57BV%bQw zA|RJn)I)&S>tWt+3Qzw$N#s>mOPkRz@nlin2PY{@sW^QL$bX=9whRehy`gQuue4Me zLYSh}9w;(+<;d;~A{i8&c%_XiuaY;SKx8auY~R{^f(>Fen=!R5tVb97(TBVDO3O>5 zlM~-M#UKf+VSo&8KbTm7p29)T=u6b!HA0BgF+(AyY5S}3txf`q6%%AxroiHd%RdPj zLSqjHeH4g3qW>gL2w8{!QwuTvn?)fUT~KpLt?w$OpFNu@$FAkT#Rmcq&8}4&9G8|$ ztxLA4jlE5ApZc;Q5DUC^FC8;7Z`CDro%K;doKuw-vRkd1_NoU;&a`-9a{|A8xmT-B zT@~NcLm4J7D(4}tdcCyLlT(W=-RZ7}fZA*Ax0;Js*~gA`7s26*$vy0hz3eD+!@h_c z!r;ExU7*Axy^S93Ac?`3RCE?hx%6&(s%PY8-rCve74O|!;WZ=7USfSU4-=4(NHTAMoM!azdeJ}FCN@IKe;JF<7EfEp;mXlP*>-Hp{-GQ@|9v`s%Y0OSa$^o zBOerv0oUZps8pq4v&*fU=p41Cwi-O`9ofr$?8?Tlh3>>%N)5HyCN-3##5QkTXs_*u zB*Q%y)lrpWPw(Fcn<+iLXg6dA`nSe@0{dvdsZrmpLHcvwU#a8+$#q?5_N`_Y-Lv+3 zEiAfiV+Z;5VG94`?X;)iJph&c(f)a;)$AHQTBLncZK#x=#GagCNO)vgQw10>lwCc? z>VEdC3wBkSGf%%Q7!*95YS6sSs&-DP`FI~)1oMVXB6mDFMX!X>JppQo_T%gsOzozL z#=7q?6dDEB?%WOrMGbLrT)T-}JBQbM4zRbh;Sd8y7-5chuhW`O{G#$)zB!JZ()-!2 z%iU^hNsElky+z>v@`aK3Xdi>Dp}o91PxzjCs}s_9o2jMvZIom8VO39#!LNWaKOT)o;(fZ9j`V)|s4h85Pe`_%Z&C}eS zu0hBac@#l9HbvDmYjYy|&H(P=yEMmct~Z6~ToggPPumT2T%3X8GR6HQS)t)3OEzg) zMQ9QRfgH;@Glx-AcxlBv?)`?XaRsW2yOX(VtIE98G)sMda;^#AsHI;)XP!T52|=(% zdT58D-`qzoY{q)vwDtE(lS(Eoo-%Gi8q8VJAZ=E)hA0_E0&SeH-fVe0?jy>c?lOaY zyv;Eg&UOM;5aD$cQZ|~-wI(ijESS^?<@rIdonKq+pf?ZAH;mBX`pX@a)sfI=&-q-? zXJ^}}>B1kkyv?OeU-d637|6+uxi8@RqTkAQB&}p?3X@B&`qlNJ2f*RQ9_Yh(=RVKc z1=g>t>DpkQ#5dR8&MU*hmwXez1)!fZFS!3yC0u;oWMV7pb;P~}Rr|Da=<+Df0=u;u z9fEvu6L vhsZI4OVh}sTj7}lNB=ZQHDt}l1oG4r?dG@27V+aCbqfi*0y$!1$M5j zq+l=KUppsE(&+11{y~MF%L&*Is1)CVM{1{f$47(OKTqewa-Le3h=u15C1kT#WYR? z6nwyg*30DoDQf>4gyPc8F_BvUGXFqTSbN`@63YJpsW4SmG{rL5bbTm7$ydIJpbSGe za_EL;;aS3%+Wdq6Msq0gKx$eO)2U|(Np8rLq_stw(^5zGcnkf(&dzB?e7pXW6>RT( zyh)|a66c6pJLymx=7`qOe{}UM6tfjmrQ;V3N#|7y8YYi8T$tAxzLUi_D&J}6v@=5@l}FyQ+IFj%CT&A+;|`J6)UV}K>wtbIzqwk(n*QBpNVSrC!1lO z10LlY3&T+hklfY+VP56QK|@HZ?hCtpGFq|lQhJa>%|rvvKa1;5{8HGG6^DlHbR{Hc z?TskZ;&^o>nz1Nmr4I%p$}%|ZIWV)}ELRCkVdWW`I&|r2LQ3=L|0C}fDfuTtV^^Q4 ztBU(C(?vtW`PA2}NnyxkfdH13Yd=um)_dg^gMXR`;dYj(ZQ-7xWVOm_4*DF&sh#=e z58$+CRb;c>^hvdd3u1ME(%bO3Z-Aj^)8(n(DQM}8u+mMvu6A(wLs-$Or#S|vZTWy> zE5;Y5G{XLUgcjzSf<-jwuR-dn+f_qipwCSWli(i$t-Jc93>S>FHEK1|K%vgPTA4zX zk{lcCLyL$1bH>TzC{w*();$et=clROAJ7GPr@+>!EBgLaHkaulOXox@bD;ED;80Q~ zGP9u#w+S!lf(l(a3?|R37)JBYcN7#Qnu8&(BpHl`pvmXlsnL;Nuni^1htQ_G0zoB= z%C;d$oDHeC{wusH>zgT}JLffUi1anM^zFIU#;P|+1F{n?G{8R<#;ht<6d=zs2YwXo z?F3-o7Oc^mKY+d9!C|%-p~){j5sg0arIkfC3F_W#e3VxkeAim=V+HRzKI%Vs#E9pZ<2cTnQ9>Y+fFLM5>G^l%(9}Jux#kVQtXdZn4fN!L|8!W^Ru1SS z*=A{begkTIJ)kVSHp{wkYew8$ClcuFj5?R}7uYTl{FX)!l%rOY0bXYgw(77&jAcMt z{#jhmuACK9SKPc9to@On*8IDq-QnZSO_pjRGGe=N9cVLVCA#Jtz zT~iuS6|}~~x5z!>FLgXXDM{B}6>BjCA4H=0{})gHF5LaMVyL2+Cn&ILivl4M5jh_d z6Z6L|7+7JHi!q9qWj`7rPOSN<>nwt*@%NL_H;$v6VV*_7QO7CK1okCe97}O$zb1!a zAVMk$6h^qpcx!{zp76L&hPI*l4=k+0QM4mrAXrT;nKpip6RK)(1}HO=z>aB|Zi0esv|xTyAfRF+nAbtDbrzs#|js zfR=^*&uTfNl~Lry^=e5gt!YT5Vn->c>8n3Fv>m+rXY@&jS=_s}$%+d1ZQfM=pr|-^mq+YI@e(llW`D_ximj^#egs+@ z%zG|D>znA;7)D1WiSS+*rP)3ObWdaQ0s@)w@zsYyGTrLvpLpwX5WRg-3&Wn&75ST4 ziV}NP?Q}7>N(;k<3k9>-I~Bp03y04UN(R53s|c-@tp!tzSy$WY5l(z4g$jA5c?x-m zr@R|m2(RWZ3peU&Ny&r2e^0Tx{tU?e>MU5yhLQ(oX&ElDRKX`=In@ebIYkGqJP8NQ-ETFe z5jf7;Mp&Os+TzoA^2epIkT>ZXu9_#_lHI1!bnnW|IE%rKT4L=yoB9;qHO|;bJWd9u zGUCuD;(_YNVP^qn<;|+eVrR*qfRIZOp1iLc-V@6=F0zp?F>2z@n_tnarjX-qe|%eG z%3z*~6};4J8^!d<4GR!FJfPRPU}947EtslDB(3!fij#>z1`5QmcmN+;)r|yj?5yRK z&MCrgC49C%Qw%{m58iS#zcAfqF*3w9pedq9C%x(C$ z_`j}{fHIXUFz~kxnwWs-9P8meXD1jL>Glx6*74z7^8%Q4W3J)xen8l);X+es!C^Fl zDd(^VV#fR8LS-$25+a_EnE0PFZ~On22mg&x{;ARr*X^K9KJSzFa{!6BOY*KRWCwl8 zRrl)x>)@;R6l;q{+O|b)18Qh=>DAYkY1g%JtauvH9;Cg$-ZI7bd5zDc@X9isLu(u= zK}%k^TQSEyQooNH@XxD4s$`3U7b~T5NvrReJ5!__qel~3|j2y3W$8I>r+*!32 zDxDZFyHb(WTTQhB8N2x;ibe()T}PK$?`!Q>6eoD(l_?pma=4y$AT20i6)T|wZfRn$ z#hpJ0X0uz>ByFu&zWmtp$rTNI9%o$B1Pji*(lKh@jE%j0#u&x~?se{&?2&|TaYvqT z=%r~F*ooi5*i7E)rpuUfr6*(;{}=NG&t`8Og2JJx;D2@Alo)CbotZbWn9_i>oYJu1 z=``XvLM~^Fk@fUEvl2iZ!+i-6nL8v~nCkzES7A+Z{-C97F|~HcaXWa;ABw=1WmN>b zwGgWjuh_GMTelcceDY5~l5>$#&LattVn3?>Qh)eHmNsb; z&Y{>jrTXQZeW!L3IVRrRx4%vFZ+GLNPnz}zie?#yV)m4(xTvl{I2Q5JOUQKyaAV1r z+ylS4;h*mX2tvVjnS8c556Kn868{_57mNQp7x+)g;GeznFUZDQ$&~|MN={B5{114C z{{`=ek-wP*mN0}R&7}klQ6DllDwrZgF!m$x0kl$2qFAUdHqBsPrBX@YP>PSt+jb+M zfay~c)6!sPni?+EAgU6vmp81S!Kha-nStENCUZPRVJ>GmSc;@AZum2luXAcMP0aF;-U z*Kp2T_q*%9yS~3*3c9Ru6Dys%vHs8 zFFV)Lt@~@RvO4C$*0R7sOsP_C4hI^&jA>t^+df}Efr1eBwD(HQOiWup*nc8$f&5GG?IiijHdmmglgIVGr*jRlJYMNOr^V<(?N9POKd{ZPTQZ z4FM4T^GETm+iqA}+a=q&R^w%``+kel@1%P0?i4R2w!b7EzVsZR&EaeN07F_anU-x# zs^n4BN*|wcQ#$o7_sdeegp&}JmwI0StNjq`vGiX=_9OXGSvz+M)boBD?it^@A}4f}oSFQT~>0S>|(w#9QAa^qW!7=k6W0CYfC9;!{<;fM7`-s&PpQ^Bjj zf@q)PmkQIYu!oGB*S10J2nwis zVHmPh^FM0=?Aq(I{xk_*w+X_7boMrC2+BsImHlwcJ6oEJo&GbgKG>9ts6pOq#vtg9 zZJd(g*aCaAGs&9wy%SK}Ll2espP2U})bR-=(0VB119&IZrao6vFdhVi^Mfn}N_H`~2$FDQ@k1z+t=;l6+Fc7ZAQO=rTW=Pv!I zs7Wj$>)|AHh*T{}0&2$Cp%16HiO?u#TbI{+( zt(D3h1(u=06`E9^2p7aPMaDezThWz+ysPq0jAF*8^5H)HZDPQ9F(K_Z_z^N%j2=#e zqmiPSyovcrryClJK7lYh2XhRgWkCP`_+)=$?hm`h44Cy8W)PHvr-E-A@RG>X)YOl$ zuV3{QVU1Xc^zlEoVKe3z!UUlLZhu11yI3STcJW}g!=lV(e?#M}O&`IVL~*y-oZ3eX zm5_kxvn=3{h#P;MPWt$Hq&4(A7aYa93vH|w!^J+hc951PBq4ev zh7?2GY)Xlb>@&Cg>l~jc>{F&nk4&CDw_eZ(D9|V2hd)2^3kq;OFxA!60xPHXT}{O~`shC6ap zq@<(=0)R9Ei5=Od=NA<8tevMr3&P_U&r)du97_h8{|G4VkS4FP%zP!d3fo(oOvX!U z^eWa^5+p?GP_ebL+8%Y;S{gW=rE>deBxB?FBArl9GQemJg9M3=S$=S6sK<3>F_m}A z$d$jwH*2GL$-?g>jdCF~U@oFD)UwXut)A<8Py0}!6 zALdOgB0r`*h)Ix#H2IwL5h*;X7kp_vo26A1fiUN2ZzG15>QBu51EeQcoE0;s5%B$o z###yJUU+h2)`)m5@V(9~-{1Q|znrFwhXnuj^iz(v0gz(>0&>Kj2@+fnBGv&oA~c(a zYMc}cqRy4!ODJ9X7)0Vg7~#%;YJl}+I?f&{TOOPG^96! zas>obhS7ZE912Eqn%?6c<5_BtF#0*+wup)OT~XVSr~z&Q=`!k3-;(A}c0(DXKJg)I z_!qMJvebi$uC#*T3mwNCO};}hbe*vkTO?g<-l+$v1a`d0l0vd4R1)OPeeO@;5wMqK z<+t+Siv~8v2GMAC{o0Z)AZWNag^bK`3vKsT!NzIec13&1iGAhRy@);(&tL7QDD3F@ z?I%E-Un(eIFb^iPpJ7`(W=A`%p*F+P`GvhE@w@ocg~ z!mc;@r0kM-rfd7Nycy!|AL+xFElqE_>5@P-t7)kT9YB?^b-t4*RjLYnrrAgPy1<5y zS2x`;sb{_Rjbr0uJA0x8u7J0Wrgbx%LC4N6w&ll7QSoU)V}}~xEF?FmH)K(xQY>V= z1f!I|Aao}T>`3J;q5!|9%4JFf-v9kp8OMwQ?=E1DQuc&e)&3Rf?se1`i@Lp+yZDrP z_)dnQ5|8Fk99?Aw4g23>-#wfP`(Bjo-SU$UT=4ZWAWf#p8L;@#oOul%*HeW*0V;(!a@xLfM=YC-o1LsdcRGCXEWg!ZlGVd&kjv}^ zIM)No3#I^!;w8tX7>P`aGE~$lXAx%{##3lJ1>M5&~&(HewoPYj+wU)g1>$XPk zY@`#X)=o^nnVi(%Plj75FUwuB^D+=F(9qnz9e-4NE}wG_aZmz`VcbC?-BR=(_VZa9Q_@)U=qfBOXt8O;rqg25xbP^-4)wb_} zY&Wr-e&RIZe&&|SUE#PWD5S^O4o-Z4Oig2BL+}@H*CX?CY}qf9q1>46v3y>BptAwC zaTJZuldJc()>!R``X--^3^KT$BIg(8cYiVl`u`XbxbyeV5OJqme#g1>r-*E;IjTq{ zD`>nZEW;SEPoL*2$NDQi;=mhvM7hit`j5-69JS?ex{9re)khnkH0P=~ISe$^dMwia zk1b~ZN4ZUJ-}n^arofP?*8(t~YD>8Aqkx1bW0mzVqUuU#i?|BruY%R3*hbOynI_Wt zGgS{U&*o-u5ny4L$d6=5&#f*=Rc#KmB!y2|!JtQJFkTH}jMKAreTgT_7fHx>jn*cS zGXuk^_m?wF%CpGu+T9+?*;VFi{7*8FvpkpVozeGMjs?eYGtBcT41s-!=avYyQCGcX z&A!Vi+iLL68FjlMmfM7C)4?~=cv-v zuZf#Ww~yJM^^ggvaI-|EP>zMGFRLa4&dr`{9!>>(g7x?!pN}aDm9G-r%ngV5zqRX& zom&!Bjfr?=HK&H^QoV2_FC5moyx2(K4E__ubq{Qh;)}b)Vs<*K=P%vU21e3?70VlX z&;DJk#`!0F+P^Pi(P@c2_NO{P*4l^N%E<$V)+m`ahG|KZHTH^ey`OKE>#+becZ^I3^h9bCoC(7@%zb z&Be7J`??kAXUqQW!@**ps>XB<>M^IMiRT;Ifsx~`TzC?yQYFk+*`o|oX^bF;PP1M&TYLXv34&Q9Ewc<9 zb{Z|wG-D1{T8vcBUkqIFCjJFI65js2DBtQj{O#&<`FFIwUp7X`c|#CFKUN1F0-I&M zx#iyTpl5P+r7{-lqxGC?)v)`@*ghzN_q9>SFaOxsEXGUW2)U~6tBkBmp?Z5VN*Vq( z=nHaZkcH?Vw^Q^T4$ZqX|4lBAI9HBXV?Vrbb_?JAK#D4-V1EYw+JizD`x(o0jO3PD z%o7X51Ciy}FV|AS)brK&78b1D+6mSOXgamlZ?cXNiy@GJkEDW0M=baWr(BH@a?Yd% zkbEI54zL;}3JHigEiy#NIAAQOfnhTz@=;UkYROQC+1T%LM6 zngSBM#T*Soy3O{5Y&d21dej=)_Vgr@j7|o0EidjcwU%SQ`=QQ6ch!su{S%+HRueqQ zwfO5&ayp`axpSSK+gkz61#^4d81??WVqF$4Yo1yO9=-swZ5{c?e3vpq-TF?`s_Z>I zrZ3f+jmGbDiEeJ*WfT!-+b)9)?j;=4Ujx#dU~TT=XjH{8$3}ov$JZ4#c@_wym-Z#T zFS_hUjZxd+oHur(Q!bi9-$RUl|8+a$`{v@lsL`!dvX%wJtP!3=#B@c|l@Z?h;+I#y zp?Zkapzb>cx{V6v&Wn&r(i9%-@|3htP6%XR%A*Ros;N=3Z^HJXaUNSOdYP6GheIhM z*-j5apn4UXJ{qP>?)Gz$pIuvnI%cL(6bZzP6C52uXd&Y_bI@2mc>B2J$jufm($<%Wd_)blt4*5 zHu|m}4D0A|({W}8+SKlmI4X4`7cbdP|4>iPOO9#lHMMg@K&Dc?dcY9$daw3@xeH`Jc_0#oo5)VK8R&8r>0sM5Ke`%8EWUc zQv*p&WdAfU7ho_xFJ1Y|w{6!sU#i&*WQ7{%I2ue(CsbE4fj)v&X%X0VN5qdDm1LNExq5M&a#1jg zgCM{>$sixi#Z}`bFfQqOmdZ}b**isKu=V-<5*OlzJ~Il5=18F-FEp(kEn=#q86j0 zO$UdF2tT&hh(}J30Hm1XEJE`HMN(lXEu8grbl_9=tfvM=KkbAF!tf4hNYv9nqEo6^ z#L8f5zNJM#NDYK+Py?7@TbJ9=hcMf9MJa~CA^%LWEEB7ygn~F4+28+jo4GuMAex~B zj~4kQ1tmgs0R>P;tvPtPtiE+XW7~4nDR04I2jhbWwRC)aO?D51^va1UBG{@g06{y_CZLD0bKdv_GV$JYw$1gc>s##gd5ATLCn#5VzBzUNExkF*Z!!}3wN~> z>o?+Re*R>+=*2_$KE952iPia}gRZ%EE{l`Wc}V&bcREHIF_VgF4+nSPT=;2h?1d@I z;y4dlF<&pC0&^?)0|&=?wP=Z?!yerukJyNPgqE6eCBABm6u7<%IUgZRb$Gb#WBDC2 z29nKHYTpt}nv@k4cUV7*AG!SYa_?vQp*wcS=8JHPA3ookZ;wf*6?*cN9dcXnj}FCS zh#N*kBb#Ujwawu*5&d_BDp7eX>5%J`I})Qd@?DE7F=Ty0)Gso&zp|OFygN7(zWES) z>A3{47?fL9q9|SVCP<$Bs7WdT%j{LZw%W1B#FWZQ;2@Y07wy zWHlB|G^i!Q&wtKFh>Y3eezIm@l3nR^)^Cpq$l@i}4fRgdyAbf03ua{57G`4&5zFWm zIqIg?qq;LOoAw}O+KuMsWFoh^o$v1zyO4boYM(Dk{8XQI4K{ z`>1!RNcO1jHw+;u!r*jw9u~I(pXPk;N1+<%_ErNn7ZrCbr~iC1vm^AF&HQJ5R&-iy z{;{i&vEZw`;r&fzq1Xw%ILe+-DAmWj1)*h5qWCyMJrApkZ7zOq0ej8dgbXYVnsWEW zh9=&|m9_JBPsG}cNnAe?2*b~4dJa&Xka1RcXp=^E1u^riZmOQK_+^@^IYVK6L+;}E z8QeRx{K|gB(Xd^Z5qYs_Fea`Qz3?uV9v6lD==w`cu5*Fa(GoBeXS?qP3&Q6ZFdx6Q z4^wbs7T%I zLp<4{c0F+nf1i!LV|dIZg`NX^O8wxFKdBaebNQE?5fA27?Vj^eW)HP*rnhpuLO$CM zieiZ=Z!34-6ZyN>Y?PZPVaYWm(0 zcEhdKI{SRXW33AK0#_301mI5a# zLCLdbS0d%_OFSkz9P=WyyzHg>*0_$=CuF!^#VoS!w`@61?;>6hsGh$vS!N3Kd`O7+ zL1)r9>%JIn2+oa!@Ts?+yezzaq%imS(|bJ)k6^--sSI$FST=n+c@`S?$OuM!c{R!8 z@>lNXJ}tn%YL&&nC0+L#Lm*P}rEogsrX56u?}7FfF-z--L|XYj@;qR2nmQ3uF)_;= z5df@^q8#n$0X#@{v<_zKvKPo1nZx{6sZxv$PHSY4M^0jalax#x9XFH7VoRp^7ms>H zA*IzNsGAd7t7h4qLvI4#v)Ld8+J0A}Re3e)mK% z{)L7nP*B3!6AcV$a11zpPx<0uUmXn-o2b+?uMD$!X(6`l{-A#*G%~^f52s=~%OAtC z201H0i&?|zl8x$f;3YFa8e`L;(9qDZweb;_H`N!|IX*pm#@*8{)eufu2w%*|bR!tt z2_oF+qaiqrqWr=-9~HE-%sSZV@wAG-xF373@vI%y}0!8v0)`LOYJM@)U7Fn#X8dlHG`WX>8v?NlYF^(^d};0i1LOubrez*Xe_f z(%ScJb^%qC^DJ=l=XTG=!l~->3l**PH|JRBU;cSD?{nt~jx2=j!hC{>m^W>W9h18u`0g!aaf zjsBc@+52EFe`bY-FVc=ZI2v9xt%EL7!x#EEU*?1RusHLIAH0TtIdlq$Emcz~uM$P~ zEF%aIvdQMH|Jf}l_Oto5P1|(l&9|UdHhDd}1t_5#vR*58#s?A=Y9(J!ZtZSlwVdvJ zaB;LLFO*fNIQTnAQ^Fren}`MO#D7MYbrpfi!YauPoU=m|5 z>a(#nEI2Qxqwwpu%K?=h#Bih6qJ%`GUn}f!2ef7jO{FbJ-rG1wNRw+N9dxi1t@VaL z;7Iy(mm>#VoFg1;JDhUzt(NYM#M!yPOhf2ce~{`%&idM z?*F88U0*zWLa+A$rNp*>&ynN>_jP)JG3pzw>FT@T%q1lHSR=`3OQhk!qqcVoF2DSQ45ng`D<_PkN5V;*w70Ee2QE|yKtE< zhv=#E1KS!r!>z&O)9ZLjy{ulpQPYpdha@06*3XAC#KMOpe5tD0e5c;_;1IrLAE@8& zSi29FI{d8UOQ~Nkw1{ud(ii4X57L`eY{oOTY;zSO}1 z;k&n8w@;jQ0JUzl{X!;VZls;etB~u6y_;ByR58WywF^!h(FUJMvAn5t;N_m6V}wy$ zgf;2v%FU#HTgTam__IHq=NA-$$7Kxx^ghDiT$7OCy~;q*&T^ZG{>uErL&w0Y_Yrat z`D!J97E!_Ah7t{yLt&MF81BEI=-(?+ox7#D+V%YRes&q!(%L%k} z*@C}lm0hz+u*io-^phf8ugD5SSet%eAgY~vYdt|;TW3Io!zu69P2@677)A{8mj1h9 zd*!adk^~%PTc=n20+hfiX&VcrTFoJtu8rtV*P#BnBD75p?s@8#95*-j{UIVKVO~u= z5#)8Ts|nP?j)Km(ChJY8paZw!-zyX|-gF{kz{Eh%om2Gzv65;5cT&Mh8;C$Cd zAgMj07wRIYSq^r+o$k2crr(t_dMx| z=vbn2zi4UC&JqR>hZil~Gl=JUjQwTSsaAzoo3>?(N&}H=e>sl-_CRx7Yvtl-W~9Xs z>xE%ZsCGtCcEvmx;a$EQCPRfY?CntF*hW+Ljs(ruaN>Ov?PwLs)p0kBqBQlBf?5#E zR7S6jHg^`Vi%7I2K{c-zdBz;A}CiG7A95^C8DJY(lG^PNbA<9u8^iqbRGWppGxD0JS# zZ-NjQEKbw+ckjP$1s|FRjXj0=IhVjvB^dz^=K9}AOeq`_*$g~WgWWFEIMegw&9 z!#8dI=dWAm=1&tBZTdg;qW{V#@_{D+JWUzAlJY9pvOg_QBN+-tKSZ&C5+s~s0iolm zOY}n#W7ki;$qys0c&xUerE>q2YX1!p{=Vk?9A3g26cC-%GTQendB@srq*E!d8mWon z$*=>?E%eKR@C z5V58gkL)L6y!0iICCoc1d0!XtTf2)0Fn5G_B@%Dz!9&uxWhp|&Qwq5EA`PtaZ*m=) z_)1V+*PdmF7m@o3b%adl?0x8DA+@V(^uw$l5zQmfyFD33xxHG+RkF&>>bP32BjVle z7xEP8_i*_%B3MiV2S>)ceUZH$f>ItA@RtvllMIarMJ<;#>VS#@7d`V7jPWfGO7jI7 z*eWfXf2Qp!=gzI?w=el==C_-ppOfF@6U0*8SkcNG;9ggQO+I3zAm2-!pFc<#Uk7s7 XNN#wif}p^6lEBGJD?=(?y$$*wxlGVg diff --git a/website/docs/assets/maya-multiverse_openpype_publishers.png b/website/docs/assets/maya-multiverse_openpype_publishers.png index b83fa5f59cd95ac8f6feab2e043fb9514ab257a7..bee6c79fe9802337520574fc619da0b63b62918b 100644 GIT binary patch literal 309158 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%0Dj1JpW~1Q|YqP!3 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�kJI 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

Nf;6b8`XXkU*mAAa2w5Kiw5OZ<+ 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%0A{^xbkk2>37iiz1t7NI7 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 literal 182542 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¼zTC%cnFn=R3lm5XW25oz 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

+jTLKMEVQn~<}sDNh&32k4p!94^-h8nk-?qrSH7 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

V^Fk7mW zA|$34-ocbzZ)vacMs`X~0T>mG(ZC`V5iRp#u?Y$aT5}zueGubly5gQRG}JX`PS7T~ zrf~G|=6v+*NP6m)P0H4EXL;|y2@zj&?d*Ub?Tk^iakb~T_kHH7)yBNsp(m7t1m}Za zcwSyKgvp!*iAWGYd`_0VfmnqFptkqYdy)w%nJ#sikfh6I++H0F+^P1EfEm?u@Xbzl zoWIW6Zp{txN1wV>{|z2Xa^TD00{few{#l7rE8GbVrMguXTN=WvC(>k74YA4FLjUK# zFdp9wh69D7ipti;h7|u8XOxb7<+HEgbknX%mUVW@b96ZAj171A^pyMMg#ZXzZ3#QQ zgP60OTb1AakaE(|qUBmqIzzQ7j3k1x+D1ihvB!SRuzGz^>JL1uJ3e>zhr3=Glkk?u=i;0jfwY1#Rbql(PMiSy zJ8p-0lL=!&hU*{P8qY`@d6$O|CBdTI&qGnRJ0S#a1Vm?`+0elGA@JepNyoszexRbo z`EJ%3H?O1YBG;Y0t?W6Ti&Pfg%@*69qVDpjQp}~#CeK^ag8jg>g30@O9m3__1R08^ zf#Z5>Bc3Drd=2pnVzh9IP;oc7c~0EO**_dew7+MB1OTE>Cn8>1ljm3*-ho7Cf3mocuSl<2)dp_! z{1-Jj=HAo0&LpbE65^qi{t&xkMf1bZ^5^Y^p5fx z!*Z?n?B9j$I6sQ&(>trp;NXbjsy)$}dwnZ1C?Q_$^t8Kc>9~`8;N@k{V|g78c}t{q z%S=LmyWL>fVMx35&O5{J0?^5eVU}2HFS%Mp=hd#0lG=+CpBV*Ie;TdQ9@}|YH@930 zRQ<^E2a2?3cGS=oxI2j_UxUUEf(u_ET?bG!&iYDczN^2M)9ClU-g9_z)1-olu5ORS z-Xw~e`h~uz0obC3SST2@m>OR?}yMYj7yb!qW>7??QnpP(BziT`QvHR@NK*a{Lzk(f&nnX;9^-C_ZaUICE*+H~(&yP4g9WXw8Qfp=J7+a?y)iB@i4)jicMpX(T4`Uvw<2=1Eql|PdQ)6p zdFB)uWpND#Mg015t0uo~JDwTJGtyc-yl9=f^*s$on|Hg+;&LI4Vfl6){(~!fRAOXr z2AZ{_6@&*ESek3d?WuWp54z_p%B|a&GsRocaFOTo?rhw=jQ>&Hag-|*>p2Pm^;%~K zDvgi?j_@7+YIfc!pK<$K!GFWYqTf+=c`eWAJUu)fWbgJjB;O^n2aUyv-F$pYm?`9S%!i;McB{ZbaGgk1Y`}JK$a<8Fa>D z2AR=W>#xBGX{b`EG~B|rbeW3uRhhx&fsG1>3FRGBRZ(rYrgmu799Z)<#^{uHZd8V? z*1Ata^E5|H89$05oh>p2ZO33oIGv0TeXkjh3cSAN#?Q%k?*#P1-cd)wXl_j2^jM9G zDxf1Pz9}|DJnj%e`X*?t7}L^>xO+l%u|j^Uni zZQufvWg45bdhC*rU#qU8vr;6;i&-OI33xNUeboa!*1M|ZXjsD>o@RnSn}_Y>pO23M zMn$=LFRy>?$jb8F1*>`yXH3Nw2uJ7eg{$FV4_3P_@}=4@LGNMY*QM0&s@UJLv9KT# z=Gc4cArmvefAm_Dpsgc!WVm7)LttBfhi#u5sY>N2B_2p&MdjxsLU$h;9E^A-_NlLs z1?tv$rZII`W5oF>qN9)|%#uEX-Z96*V5s!#CkBw)Dm`cSQwE5dT)z!G8{~%I7hxC$ zwWGVFn#oLCobCXm-V2ePjN#OY<3%&iv3UiWQ4kW~0QXvC_%Ea@){!YRWY`7JR^jpg z_?tCu>KllZkI&66J`QaiNH&Pe3VXE)KaMLoDH0yBdJw>vQ4tas{oY-96HZ*6`0z!t zhgxe^62^l#MmO5lg~#e`PP$! z?D6Fij_>|zt#x?^_9y6lBNo;-%<(2Dy$k{rN(Mj<-FELMMJhJKoIGn%Vg@8^k;zOU zn!d08ccm`>h$IOo9C!A`)4i`)HKQL!=P#&acmgyvmV4Ml!t7i6kIW>l`idcrL*#$Y z!G%xdHX=#kI8&7{Vjn7xndQ{xCK64`|Cm208CdxHCOUG_d?}pLI{%usZzlQN%cF>n9Xt&@ zt`jwO5bK8mTH%);%3v@v)zSsB)pj->k0ta{EIBzb`a8c*UqPHIihxJv>`4dQzZ$J7 z+giaup_u$=3{M{Af~mq}WZLY6Uw5Jdjw1_L3i-fhvsZ+;v?O&y_l26MR9ClHdpzXr zBIs2oExjo=R}pePAEp#Ik9>HQ4C$hN#>sW7MjCs4tu|6kF~IFW$$jd~A|bpjzTB&^JFmQO8hviVngGzaQ*w#4U`4g;tiic()sdUs`Po za-NapPZw1A0T$a}=}N_KZ+wp;xJ@)E`<~)`7_bDFhKKdIoqfr5ulBEXZJaFS*Rz{} zpf8VSD5(K)bFe4x`=u(NCGI;jZ3DgUn_e}9)^PQq1QDv{aqu~%G7&FQ-=^(Jg5Abg z&br(PYkdxnV2)XJm7IYnwA0JQ^1QPu6w$0DEft^SF#!#)xy8^~MbGVY4ac3IuHe8Z z8)W`2+ajE~p5ueV#saTzl*{GLNy^ce_d{fTRBROy7yf=vMR=OQCIn3PST&GeNoW_e_zj^aMI2qfar)?bK+x$ zDCn|&2I<{RXafA!tswddNw=MAEP>t17?kJ=I=aQ1r&#XHMi&^mkmblD#$#z*bK^ya z*zL$Qrmz&jkQWhCpl-_eLj^KVinEV8Y3|EOzQH=5U!bquf{_UF=fO|=FO6LAum!bw z=MMcPSNkV#)Ww_+;P9>Ko62F91)tblY~aUQD}NybzLRhNQv zekhEsC(p={kACBId1;Dci(b_mzy*LFyfwGs&CrhS+vM(Q0*x+eBA*C{pZRV?x7c{9 z70@TvWf#lwC{{Bdi5w3bRXqYiIUh<086X=acTafF{>-#jytVYzy$w9J%~6oJu%(|; z<}k9<3G2TC^KphOdGfjlFr!I{1y*s(AMniY|V9H zBP8v14zAA9q$KOI{ul32>fMJCVMVqwBAZDubP4t0<(r3JQIby>-q1-<4+W(2v~NnY z#EP5y@+0TOO4<02{>cgi3vq`7x~ zc#$h`Qx1pdOexS*9E<5?O{z-YmTguc$ULx-L62Kg+Y*SbCiT^ZZjiY@1pdhzv!ZsR zc$>CvTkJfBuXVX2Hcx*M7-#U{P9NqMO=Pwu)r5T1W=;0!>L&Ek=e<~9taZC6FMe}Q z9fZ#@Rek2{lSnnL+@$f?e0@5jkrWf_o}I0Qp`qw5DFJYx`T6@#JL4h> zcYOCOAS7-@Xgf07INqne=AL#}$)fU$eHF)Z4CRA!G&v=uK#sJQmKNPj8312pH*6VE zcKIPt2#bp|H7vFPfjE9B$jFv6Yk_yCE28r9T(DKP zxVA@*4Q2}E&Y0{uL$&71wPY@@u6U@as02$&=S+`gez#j>q)&C6+`ai%!0B_*G!9~Ec0Sec@;UgczjO_mzTTK;%3e4*N zE;Q3O>cjt_4Oai^-FqYA;AmpodcW;ZEJvEj9$Ne_4i}K@t6XA!iku`M1tfeC6db_= zg5sg240=IM7HdpSS%C)!uumP`?B6`@B4$lAG&Ql5 zy&GjKudYrl3dHvRyS@UVdfC7{!T||CI0;0PK(kzz(yrh;DCh^JM`?r=WM31z^&* zZ=V5wUvqP_)1AGoZ4SRb;{4*Gw(fNKSd)mT=)lm>)_4Ygc5d!$Q5j#x_q?f$sRLRj zCbRRilH%f!%}s59Y1luYsitWB4;Nsdz}}sdl$4W`v%IeEM}aJ}p{Y|#T5~gJ7(SN> zA0MB07(fHf^a2phjt%GyLrncZ&=1Ha&kwbD6`ifA|e8s#M0irZ+g1ALx+N#{0j?9Jix6y!WRW#vH+9WS~HbtVKVjlPW@&Z(8yFZdBJ1X^>X&Z66?0~sG-@$oUMv9||fRaI5s{o0!@koihN!t5{sLcO~O}e=)#26T33Sml;I8(@)`3g{ng=*;)YsQ*(qaOzrD4o76xv%H2C$sT zl1~^i+B%5>10I!zh6do&%s1IvWT7e!4GjTCedXf91H3?4Svsk!nR4W@)n-d^z&%1u zW&Xho9%!%-adC0L7*?+dkBt22=H>>p`A2;G)paEOvGat#>;Mc3e9;Y1lRuwudz5oN(y zj560=^~jr;9I@B1Ff$9~O?^Z|o9T~~Lsiz%(E*faN@bjZIklmI4alG!ii?FoLIM;! zUC;tF0W~!>E~i6zBvDboNp~PhePg5g9P9h!uZrT!#|u!GvbcPU3R@*V1Fj^emR6}W zb@ftU+yPC>UbZnEv!juw-_g^frC9e|=Fpc7_NnOQjMSKsp#Q>>Y%#Az&>8rl3RHbUj_&>GdIQ z*}UfFl&GjrA)!aL*d=@!^$}pnUg~9Kb9cGGTDaXor|RP1!nHjL%&-4m;*`n$Z&#yI z3s4{_dwv{*ojIZ*)d+2YNqiKgbTkm{Lz819f6pfQqi+@Lfa?$3$;nLqz^-n$yAOom zZ$_^4|JB)7#zpx=|1KyYjdV%FBHbk^h?EjacXzIobc3*>bS|NQbV{=zut=_eboWxi zQqtVV|NZ>#eR1F2^LppZnVmD|nK|F@%(>x6+|;3^v!`$P?ojrJR5$6BjuYi9R$~2- z!-(5@m3Bw%q)M&#yxHebG4v*|nGgZt@0&3EGMeO+v)A9+{>(9oGh>aWm?ZjL95B(BdE=yLf@p%&X(hvw} zLTU+`2Odt#@lx(^!>W=~6CqEuy<--I15z0AW?ca;_;+FiiGf3qLY~JFxCq-Pdd*az zYTc6Z<7Hf~t_pqmpH)4xXy!eUF)qz(-Z0=|P;mQco}49qr-i)>nRzGnzxfxz1C~*y zp^TeABJY^P@lS3cEjw@#y2%2=6n0H5<3ufEy02v}F_$$}+0kO{M_D3PW$SAQ!eBU6 zsxlib{yD?Ji%Yd(lVn+jD9{Ki7{(=2;~JdMg+3qD$L`>DT zHQ;wLtgN(BxFkp{e!*G|Uw{Z{jA$8AYnv6z$Azuk=nM{KbIY}jxzF4Z?7ojRrPHqy zrUcs4ea6q76-@Q|PqP3=8zAw2VsKKjSnYw)7yhe_4@ z&e#iUivCtO;$^+)aON#1T9ahjf7901rli-ZVkfjpB+O$%+lp46vVn2S|Ek9|kq%Uw-W+<08Ko{su);s0^P zy|7&kWa{TI+H+B!K<8PB$M_^iG!K->HTa?}nq+O4+~SK%ooUItpeNRArDF>_Rxwh0 zY^2@l7ds`F!}rz?EOF|=JPk#QD<%G@CvwmBv)R5lrC5L`tii(h`~YEQLe~jgT+>V* zq`PdQXSu#L^efK&=+7{xU)OTWpVwu|$t`=E=`{OR33a>oub{r@@u3J$QSGTLSbxX0 zz+Ewi{yvk@4q(lm%#X!V^PG9dob zqLF%VXLkc3uEkN!9i-waLeM=T-PN z{C>`tv8zVA!^5?~8Q7XDhG9l&PrRv37;#0@h<~Js2=Ww3(ejgud|SRH9J2cN<^qYK zqmxp~^K3_W$2ITN^>b30_A5zc6rlxpTGz-fe^^UVTU9Vh)AXiN(eg`gd@J` zu6xcEObnlm3GTkHu^G`jAB3#XAy&cE+nYbrHk3v=^;8Z=!G+VGb@Zlb0%a4jWkPez zJFUG<1h2O4)j1;_b|4+p^r+V=`h>IZ)6hNfn){O8!ET2{Kxrs(nSQwuRNDiOiQiH> z!p+*p^`cW%&#A>5se%-z>Rrz4SZt(@2$^Nb78M>}zOUI^RZUviIw=ObJbri0 z<+wA`Mz!R69Cu~Ut+J`Y9qHhL*fI!6*cj-BQsa3^wE7Tv-&M5E^!O;=o!TwrG*Pb^ ztb<2NE8@~?%AlB0pXgizI(k(WW9qN7u>!{kCc>(ntz3@_z^MJii6|Nj=!GAbgqK9r z@p0?jm3I=maHqxdn=^`#AVgvha>GeDEs_LN>U zuJ+~Me<}+Xc_e)$x9;s_?4N&(@}WhdaSQK)|iS8n7iD$PNuhR z*1VSa{3;&4WZl1J@AS0ZFN#EebGC?%VZOOgvN2BXj_8}tP31GWQ-$dB4$=>BaTaHj zYpC_$zTt4-uA5Zr%V0OR%bm;|-ZASxes>34-VH|F8k)zdRVU9+2@0RzTTPDdao;(~ zJUeQ;^~vl5NJ&q16Qj+ZV3!Oep6$SbOea0pHpgEfdZe{Ol9zuzEz7|Qut|z zwVB?_7U|FeNlN`O@SHS9ids!E@G4ca?v>YZQvAY+c!nWo>?qjfig5Lf*o;9~rvS`* zyK8O?`@~Ch`xu|g^{zi7z0Rj-=$Ace++Lvjp2v1%>ZKv$g%nrnH2S6RrhxY5`jOXy zq8AG_efv&DtevYGjqT>-=0sFr2Up%_^>e;s%LNxkXh`$tTjj6Rfe69(6~uNN6fNHU zlB|PIy-R=`xW8K{ela3-Jt`0xo>&W^MiW^Fik9BoG7Cp46H}F&1AxP?zb1$i?fxPq%Fv!7gJ)}93^lBz1bN>eE{Zy@|1^@Dr zO^^ml6V99yCn~0gHyDpE;rpH58HbLCM|5K`>2j9hkeTf0k7!2H*^og!dwApmqAll> z_B!u^FRRwmYU`G|8;B6HwdftBclK#kJvOfqHwDLKEn624v;jyfqXxDz za$m>Wy31rFb$q&|9#6ukym?f z#QwrD@%33QDg^1#Dv9yjZCeXvcemsZ6$?6C>%_=2z?|jmneDVBv_yvn;RKw*V?HhB zGE2v8>`R>Ia|6{bj*(tjJA5o4GQsh0{R64KYliH7MbsM$C9q{WZe4~RTI0|*g!hS zygu_AV*e=kKo!r*&Bt?}^!SwB9Vgd)Z|K7RcIRcSrk>5)!;!|z`*?+#njA-E85t9#HU>({ha zOo4;lc|_n_%xmSW%E$rrVOiU5dDusxy?e$~kD&fERTrX9s%Vn*>U7jr>9C?j8G>3? zin&{aFuzrgN}PJ4Y53@p9gCcTN;|_c{KF`b@sO2T-7(nl?yp9*%>ja8y<~NdVfc`H z`Z0a-%mV3Rp^gOo+ffOAmG2y!g>5dEG2ADPa_vnO79pD1)6zs6QDe}^dyyDT&puQK zu$-u|_PD+6cc%|^V$?n>bZmZHM{$zBiXs9YYrT||4as# z4|2+!@cc1Sq0g<1<^OHEHT~?~Oizd=H(FSGWllyK9Fff*jDO*(jnX41&hK;St}n!I07ImAS=U>yP~h zoy}Q{n@!>&%ufjUOfip+86(s0z-ot}_Q|oEyY6Wv8sGf)7Oz{4ioII7I)#{(geP=! zLLx`*eIrLi8^C-L{b~2tk-VC;)0^bfG>#sS357Yw_yl$EsH92`BI}&5av&~leLwL$ zlv3U@a=en0Kd2#P)tO4b)}L279@}Hl-G)Q|O{gc{)BYiRXhn1LP0yxq=qj#YBjJ=< z%sI-3l7sGPT7(33-`JRJPBoReto0+PS6!=TU^}Yx6yfeeg?J-Le_#<=s1ze+ha<4#&&*XW?Ki}0mr;A+uQoWJ^C@P?4kY&n%i_A2VcZ=6eq0R zC?4aVQWIdS)lUrkZnmZs3{Qk;K$bJP=z(L<8W7$v%rxTrq{wL`Wjq##t4qr3b2h@d`btKio>1z!jx0#h7fxeN zyfxsmoxpX$+A+&&t`au*id8UB?^@kDI`=C(WfYdp_HxFhe6m)3YR zEk=Od^KQgz^fhv$qK#6epQu6FK59?`HZ!PS%m>3D$fTdYWW;fz)u=>Nh{WtC3#JY! z*nCb+Qt~Q~pGZf7qIq6Ly28Dly?^gw+P*Wrm>Jwe4EK<@wvlKdavCJq{we91Qo0Z? zShr553TfM_mdEw1|A#g9ZLV#74v@Kud{{NihM=ZvTUoQ|B4J$q0%QPQyl|jj{FPeb zd1WG*lA>j#7tJi0MzD9@F#h#3OegTc)a@V_zdBRk0j-{>7rLpUq5>mrLmEyHS&hlx zN1CS3cOQD#(~e>oX%g<_7}I(o*_i#K^ z)pjUGre6U+&61s_4J9eGMg0sSmK&^DVyKZ0L2Y2C@!WmLN1dw2w=*0vAX$5^@})ac zl!wGgY37yKEm8@k-ir>_SrG(0!29}`ER_Z!k8Z&{g;E+AQ)<0QQC}s5z*6a`hUL_! zYeeg+=rUvHfb8KOUA`Ah?5Jr1%7uKSY;cp&X|QDaMt@*if#W)7Bi;W)Ecer|1VEUn zV3)Ixu0h4{=5X<}oMtWkZwrrXW|~!+n_!&h+r+teRs8UqTU&|_GGDm7f?IWdpf@zo&EAy;|<761YFf73uTw0MZ}K=QiIAu-E!(Z!ho9 z|AE?y|DcguUT-a9di6qpYy zBF|s)+%K1G?n@r}`?8la#t81GwQ5okc3>^NYNfAiG|O@cv0E{#6Ek<+cXXfOd4v7h*A}8> z&w3oBW(eWP<}35Ii8z;Q*R^`(=WHyjLXWeqFBT@^qpxyHmXgaEJC-D%B0wr18kp1dQzs#I(6!sBQQ( zup#iV5CV8zoi*ei^xqcPlyV8?5sI}vBH|3~Rv@^3m*MoZ)@v20T@O!2GTU|6<&)!& zMdSsFo8YT!$K#{DNya*q2`x8n?w68oQhA8p++GS_tkP1fj+q6Hxh!z{b=!oOmr{$7 zS#Xz!I+x`_2=Sld2?kn%ymKEwIfH$z|5mj&{Jrg1s^A$6`g+C_xc4x}xkG5|4#WIO z@24~5`Zj!qAQ^P=acZ?Wgu<=mqWLfAuHz4mpZ^{b{l<&5nc26Hq1Te$_%F}*&YPfE z%oVwF4BDI2xYPk-&y5+Wn+=uu8n+GM_Ok#PpPMV(yjwJ-%RC#qp1gZc%>2@G-FxQt z%WgZD+mcE@p%&Z)53`#zJ(kwL&FO31{he{lf{|O^vNH!*;1CFz({#uK?gcS|+yG*n zta-~fX#P5Utrf2>%a)Pgv}fdgn6W>#6Gq?C965*^y$XzP4N8*CU!fu4LNZks3R%f9S$En7KW^cG#6mX!4PX;+0zr zPaIHQc`<_aAyI-Hdh+&7GX~*CDB@~vV_P%^Xx*V+1iCMZYj4(CH#_1GiNYeZ``|V2 zfq^iIM_ud>5VNqT2ngxhb`{AuxR&;hUL`wNZ}~1gy1GTd14&Jx!G6gwSl}Xx!6>q< zAJ)&E9qMuD48}iiM=V~fH4sjng(Pm9%}O|uN`UU3oD%Z=C_8)>qwf^vv5W<| zp7)Rifr3xX{`3{TUG7`HyDjTw@kqQFzohQG-sg@L52Jozzpfp4wW``6GH+q}SKRMB z^-$C3Az#NKDd~uAA=Wv&c|RFl^-2-qgNA;gUh}0mzGuU6#rm+vV_ve`B zua_E{B+>a;hpvt-R~LP%%Z%UKKT;Vl!PeN!==~1*oatU4N(PpM?3 zs{S+4%D~^~2AFayak`~P-)c8MNfCZc+!2?~Ax9&da6jYYw|kj48y6x|e^SgJ6Z;3F zhsslMBXT13BfI%~Ol0su9G!1V1LOVKfIXryIFum|lZw7+Rrn^}hUi6_LoF)>(X(!i18+q>gQmS0)jneM9J< z6LNj5ug&4=AXmcsI9G&i2y;B&n+37!?3sC+@Sb{*e~&4(pa@RIbKO>SjQDa_xp&Hi zW>)S)0n#zkB0>h(ow(WcB2#Oi>`eKSA5EPdkM^7fSH83R(CqepvwpAL1b<{I%bFgX z&7m4TYo(Az9bQ3GyE2U1+PM(1e7F#UIN3kRb}K7W{7hXOhcX+3;$Pq4R3B2m_}c}l zNL}tK`Y0E7=1j@5oHt{8!IA|%P+kn$R^3(~`qf#ui(t7y)I;u0h_k zuy>~Lb!Cn1`VHr{BH1n_CErBcLTemP)LXgW`5D}5b~^4UnZ!Bwdx|Nx_wnUIKhkNB zRuAQVs3h(f+d%X(MrsUss6W!wGUX6y9v}u5+iJ^4Gi2dF<-@0BT}mlL7=HW^3yFEW z4{RKBHDBaE(v0(0SGpFuVRnBfn_2R~)SVQi<;PuyNeRxmNA>YAH>%@PlrhPJ#skA< z;~pG#hV414#>JxC9?OT=Nm2 zhQPP?Pz?J{28Ob={=O~2886qLYG>f&M;uHek1PmlQKUD!I4a`-X**V!8L{I^3L@0< zkg5{TX>I11h@>8G8R9JGLh$Grc`TUqTWmBntmGqFk|LbR26FRQ`O_p^DFs_fgg1T# zTyz52X-)W5`fPe43=e|81=}471`?pi0qrttBnfzD?0DJJbh-eH2i#PdcvgRbnf0oKY}Lc*HU;=u zv14?*JIF#TyM*H2#xQzfM`Jyq%ByV=H%if01RL-M5!T#jprAMin_E>$;Y}S+k5l6- zlin}MY^qx1?3*8&U$Ozv9Hs<4R0K&-Gwbq8gm+V^m}f{JdnB5zgk3eftc(lpKITBq zmp+ulWY=g^uqOp*RjTKqfo)so#3`{H`8_)klI^z={5JwVA)raI<3v0XK-4^1OdRtJ zpUFNzFSD|+WHAiv<=LRMq#lKXR6mm>7Eir4n11)OE}`!ORf)eHx@B`?gNX$9O9Q-W zFZM+d&^wkw=&VcL)HNkuR88RJu?Uh!R=i{)4vj`j)5ZW3m-T6B$*i;tuW6nzdjwm7 z#O{_Jfd7Avjz-TPL@yt{mek6T^u}(j0!rRv19+|Dw=DqdQ~+>1Cnu+slS=^sfuwOy zvuao&J6W!6WR#V*(YffR;^@ewS8bMP zArQN?v}7o!Qv6a>P+m#I(A=DnTsdrLQ;1ELn24x2P~jg&U0N1@{ilr>xESXchXhDO zWN3d64-&I#@vrOcX8bIlUe2x8FS^AHZT`5pkcf_n(UqzyDtgduK{7cx31FY<&VfMX zju&8ZMWEVv0%Gi)OwoEeFdO=AWTfoMMcNQwUsiE(D!z1XUi52{hX$4ZC<{MUJyyoQ zs&g?=5yg1!{^sH_tb6D@RgrnY;vtr)!QuDp36Jr20^rdu04BFl>;G03KRWtKf}Ut& zd%L9aV%Vy-Yt^G`)2jfWrEIn^$OY4Xpyd4lka{fZXUB!ao8}YQ)zel0dO$yVz2E*B zw5*K8$=UfG>(h%^urLL1iLju(Otdx&(Qq zP=GC$k_=C#AScf+Ed}#7c{^oAJBY*&rStz!gRw&8(y}r&^{6zSAzbVT3Ew~TS65em z9C8eJ&WYcj?_W;xymB|2t+9-7sWlik|DmEpGY|`GbSH$1oKk>_^@VkHUWJ-HE^3JF zdV6FsV8ie_`hR+&>hh(90q}XP*XCfu8^t~=!H1X_4NXl*iA((6`M2Qsy3O^Ea(8`z z{w07t)`L{izG1J1#W`>c+X)X24SmYamfkb}#bGR&@bTk6TY*2!0^mFWG^E@~fA(#_ zQ3XUqQoN3Rg|x}FwY9?l%1T3EZ6z2_u|R8ZV0l1;gY1dl3PwtLI`XO=#6*r~>Ef~v z{S24C;p5>51ww9>H$Pjbxcd^1a~1*!Mx=|PLNdU^o+n$doz}adH`iuB@EXDS^fHo{ zDfa(}5XHY0`Jd3l|35M9zj&t|{V$mQC*bFT7#jbzQc5Yv0CD^m3RwoVG(i71PTGS1 z8TX$Ek^g^orwYmk51Js?v7@4tQG0t)ReO#yA4{xg7K#NSK9~7UJ2KOd4So~6CQZee z={W8pyf?g^i+YchZR5g^Y^9}e9)jl=E(?q1y0FfBYUWKZ(Fh9#)6aivmrh*Y=dr&oM@A&ON$#KgQk4YD$hkyFaF_mdYV#Xm2wp=8<8GhEr{wl;sfF`oC zImaDH{;YjD?e;ps+INoWn~-AIQ3`Al_{GNUz^O4$@6uK21)i<5QbV$Mr?$2(@Y+=Qe0uUgo%CD5F6fMI47r>0XP5=M^ diff --git a/website/sidebars.js b/website/sidebars.js index 105afc30eb..b105e6ea93 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -19,7 +19,18 @@ module.exports = { items: [ "artist_hosts_hiero", "artist_hosts_nuke_tut", - "artist_hosts_maya", + { + type: "category", + label: "Maya", + items: [ + "artist_hosts_maya", + "artist_hosts_maya_multiverse", + "artist_hosts_maya_yeti", + "artist_hosts_maya_xgen", + "artist_hosts_maya_vray", + "artist_hosts_maya_redshift", + ], + }, "artist_hosts_blender", "artist_hosts_harmony", "artist_hosts_houdini", From 5c8e7fd3305dc67d339e49837d0a224961e274e3 Mon Sep 17 00:00:00 2001 From: pberto Date: Tue, 31 May 2022 14:36:06 +0900 Subject: [PATCH 0411/1227] docstring: addressing trailing whitespaces to placate the hound --- openpype/hosts/maya/plugins/publish/extract_multiverse_look.py | 1 - openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py | 2 +- .../hosts/maya/plugins/publish/extract_multiverse_usd_comp.py | 2 +- .../hosts/maya/plugins/publish/extract_multiverse_usd_over.py | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py index d59d03ee58..82e2b41929 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py @@ -31,7 +31,6 @@ class ExtractMultiverseLook(openpype.api.Extractor): Note: when layering the material assignment override on a loaded Compound, remember to set a matching attribute override with the namespace of the loaded compound in order for the material assignment to resolve. - """ label = "Extract Multiverse USD Look" diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py index 0671813c32..b1aaf9d9ba 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py @@ -21,7 +21,7 @@ class ExtractMultiverseUsd(openpype.api.Extractor): instancers, pfx, MASH, lights, cameras, joints, connected materials, shading networks etc. including many of their attributes. - Upon publish a .usd (or .usdz) asset file will be typically written. + Upon publish a .usd (or .usdz) asset file will be typically written. """ label = "Extract Multiverse USD Asset" diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py index 45d0cad368..c85b3b6664 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py @@ -20,7 +20,7 @@ class ExtractMultiverseUsdComposition(openpype.api.Extractor): - a single Compound node with more than one layer (in this case the "Write as Compound Layers" option should be set). - Upon publish a .usda composition file will be written. + Upon publish a .usda composition file will be written. """ label = "Extract Multiverse USD Composition" diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py index 8b14ac3388..4856f0cfdb 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py @@ -18,7 +18,7 @@ class ExtractMultiverseUsdOverride(openpype.api.Extractor): - a single Multiverse Compound node with any number of overrides (typically set in MEOW) - Upon publish a .usda override file will be written. + Upon publish a .usda override file will be written. """ label = "Extract Multiverse USD Override" From 45e0e39ea1e262044e1764f546943f4b436dc926 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 31 May 2022 11:39:03 +0200 Subject: [PATCH 0412/1227] :sparkles: add support for skeletalMesh and staticMesh to loaders --- openpype/hosts/unreal/plugins/load/load_rig.py | 2 +- openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_rig.py b/openpype/hosts/unreal/plugins/load/load_rig.py index c27bd23aaf..227c5c9292 100644 --- a/openpype/hosts/unreal/plugins/load/load_rig.py +++ b/openpype/hosts/unreal/plugins/load/load_rig.py @@ -14,7 +14,7 @@ import unreal # noqa class SkeletalMeshFBXLoader(plugin.Loader): """Load Unreal SkeletalMesh from FBX.""" - families = ["rig"] + families = ["rig", "skeletalMesh"] label = "Import FBX Skeletal Mesh" representations = ["fbx"] icon = "cube" diff --git a/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py b/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py index 282d249947..351c686095 100644 --- a/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py +++ b/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py @@ -14,7 +14,7 @@ import unreal # noqa class StaticMeshFBXLoader(plugin.Loader): """Load Unreal StaticMesh from FBX.""" - families = ["model", "unrealStaticMesh"] + families = ["model", "staticMesh"] label = "Import FBX Static Mesh" representations = ["fbx"] icon = "cube" From 9c1b1f11e97dfb7deff3c3b76a6ed9058771451b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 31 May 2022 13:52:29 +0200 Subject: [PATCH 0413/1227] updated windows oiio tool to v 2.3.10 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 47d678b5e8..6b98178aa8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -126,7 +126,7 @@ url = "https://distribute.openpype.io/thirdparty/ffmpeg-4.4-macos.tgz" hash = "95f43568338c275f80dc0cab1e1836a2e2270f856f0e7b204440d881dd74fbdb" [openpype.thirdparty.oiio.windows] -url = "https://distribute.openpype.io/thirdparty/oiio_tools-2.2.0-windows.zip" +url = "https://distribute.openpype.io/thirdparty/oiio_tools-2.3.10-windows.zip" hash = "fd2e00278e01e85dcee7b4a6969d1a16f13016ec16700fb0366dbb1b1f3c37ad" [openpype.thirdparty.oiio.linux] From 5464fd40850ae1d25c1b467149249bd5edaaae9e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 31 May 2022 13:58:11 +0200 Subject: [PATCH 0414/1227] OP-2787 - fix extractors could be run on a farm --- openpype/hosts/maya/plugins/publish/extract_animation.py | 3 +++ openpype/hosts/maya/plugins/publish/extract_pointcache.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py index 1ccc8f5cfe..8f2bc26d08 100644 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_animation.py @@ -16,11 +16,14 @@ class ExtractAnimation(openpype.api.Extractor): Positions and normals, uvs, creases are preserved, but nothing more, for plain and predictable point caches. + Plugin can run locally or remotely (on a farm - if instance is marked with + "farm" it will be skipped in local processing, but processed on farm) """ label = "Extract Animation" hosts = ["maya"] families = ["animation"] + targets = ["local", "remote"] def process(self, instance): if instance.data.get("farm"): diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index ff3d97ded1..5606ea9459 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -16,6 +16,8 @@ class ExtractAlembic(openpype.api.Extractor): Positions and normals, uvs, creases are preserved, but nothing more, for plain and predictable point caches. + Plugin can run locally or remotely (on a farm - if instance is marked with + "farm" it will be skipped in local processing, but processed on farm) """ label = "Extract Pointcache (Alembic)" @@ -23,6 +25,7 @@ class ExtractAlembic(openpype.api.Extractor): families = ["pointcache", "model", "vrayproxy"] + targets = ["local", "remote"] def process(self, instance): if instance.data.get("farm"): From 376ddc41329206173f2d8f4e9d568e0aef4cebc3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 31 May 2022 14:01:23 +0200 Subject: [PATCH 0415/1227] OP-2787 - added raising error for Deadline --- openpype/lib/remote_publish.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openpype/lib/remote_publish.py b/openpype/lib/remote_publish.py index da2497e1a5..d7884d0200 100644 --- a/openpype/lib/remote_publish.py +++ b/openpype/lib/remote_publish.py @@ -60,7 +60,7 @@ def start_webpublish_log(dbcon, batch_id, user): }).inserted_id -def publish(log, close_plugin_name=None): +def publish(log, close_plugin_name=None, raise_error=False): """Loops through all plugins, logs to console. Used for tests. Args: @@ -79,10 +79,15 @@ def publish(log, close_plugin_name=None): result["plugin"].label, record.msg)) if result["error"]: - log.error(error_format.format(**result)) + error_message = error_format.format(**result) + log.error(error_message) if close_plugin: # close host app explicitly after error context = pyblish.api.Context() close_plugin().process(context) + if raise_error: + # Fatal Error is because of Deadline + error_message = "Fatal Error: " + error_format.format(**result) + raise RuntimeError(error_message) def publish_and_log(dbcon, _id, log, close_plugin_name=None, batch_id=None): From 8de1cbf7320f792e0d6cf8e2709f918a9d8c4ddd Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 31 May 2022 14:15:05 +0200 Subject: [PATCH 0416/1227] OP-2787 - fixed resolution order --- openpype/hosts/maya/api/pipeline.py | 16 ++++++++-------- openpype/scripts/remote_publish.py | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 6fc93e864f..0261694be2 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -66,20 +66,20 @@ def install(): log.info("Installing callbacks ... ") register_event_callback("init", on_init) - # Callbacks below are not required for headless mode, the `init` however - # is important to load referenced Alembics correctly at rendertime. + if os.environ.get("HEADLESS_PUBLISH"): + # Maya launched on farm, lib.IS_HEADLESS might be triggered locally too + # target "farm" == rendering on farm, expects OPENPYPE_PUBLISH_DATA + # target "remote" == remote execution + print("Registering pyblish target: remote") + pyblish.api.register_target("remote") + return + if lib.IS_HEADLESS: log.info(("Running in headless mode, skipping Maya " "save/open/new callback installation..")) return - if os.environ.get("HEADLESS_PUBLISH"): - # Maya launched on farm, lib.IS_HEADLESS might be triggered locally too - print("Registering pyblish target: remote") - pyblish.api.register_target("remote") - return - print("Registering pyblish target: local") pyblish.api.register_target("local") diff --git a/openpype/scripts/remote_publish.py b/openpype/scripts/remote_publish.py index b54c8d931b..8e5c91d663 100644 --- a/openpype/scripts/remote_publish.py +++ b/openpype/scripts/remote_publish.py @@ -1,6 +1,7 @@ try: from openpype.api import Logger import openpype.lib.remote_publish + import pyblish.api except ImportError as exc: # Ensure Deadline fails by output an error that contains "Fatal Error:" raise ImportError("Fatal Error: %s" % exc) @@ -8,4 +9,4 @@ except ImportError as exc: if __name__ == "__main__": # Perform remote publish with thorough error checking log = Logger.get_logger(__name__) - openpype.lib.remote_publish.publish(log) + openpype.lib.remote_publish.publish(log, raise_error=True) From 2dd79b32e7ebbc3caaf863484806569bf62ab1f3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 31 May 2022 14:15:25 +0200 Subject: [PATCH 0417/1227] OP-2787 - removed unnecessary family --- .../deadline/plugins/publish/collect_publishable_instances.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/collect_publishable_instances.py b/openpype/modules/deadline/plugins/publish/collect_publishable_instances.py index 741a2a5af8..b00381b6cf 100644 --- a/openpype/modules/deadline/plugins/publish/collect_publishable_instances.py +++ b/openpype/modules/deadline/plugins/publish/collect_publishable_instances.py @@ -34,7 +34,6 @@ class CollectDeadlinePublishableInstances(pyblish.api.InstancePlugin): self.log.debug("Publish {}".format(subset_name)) instance.data["publish"] = True instance.data["farm"] = False - instance.data["families"].remove("deadline") else: self.log.debug("Skipping {}".format(subset_name)) instance.data["publish"] = False From b6e6b7ae69f92aa540a3af146249d8ba0db4fd93 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 31 May 2022 15:17:52 +0300 Subject: [PATCH 0418/1227] refactor cleanup --- openpype/plugins/publish/extract_jpeg_exr.py | 32 ++++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 88fa627183..8211c1a255 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -46,13 +46,9 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): if not profile: return - oiio_path = get_oiio_tools_path() + # Raise an exception when oiiotool is not available - if not os.path.exists(oiio_path): - KnownPublishError( - "OpenImageIO tool is not available on this machine." - ) self.log.info("subset {}".format(instance.data['subset'])) # skip crypto passes. @@ -167,18 +163,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "thumbnail": True, "tags": ["thumbnail"] } - # adding representation self.log.debug("Adding: {}".format(new_repre)) instance.data["representations"].append(new_repre) - elif repre["ext"] == "exr": - oiio_support = is_oiio_supported() - if oiio_support: - # TODO: Add resolution checking, possibly check for other - # places where things may break - oiio_tool_path = get_oiio_tools_path() - full_output_path = os.path.join(stagingdir, jpeg_file) - + def _get_filtered_repres(self, instance): filtered_repres = [] @@ -199,22 +187,26 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres.append(repre) return filtered_repres - - def create_thumbnail_oiio(self, src_path, dst_path): + def create_thumbnail_oiio(self, instance, src_path, dst_path): + oiio_path = get_oiio_tools_path() + if not os.path.exists(oiio_path): + KnownPublishError( + "OpenImageIO tool is not available on this machine." + ) args = [oiio_tool_path] oiio_cmd = [oiio_tool_path, - full_input_path, "-o", - full_output_path + src_path, "-o", + dst_path ] subprocess_exr = " ".join(oiio_cmd) self.log.info(f"running: {subprocess_exr}") run_subprocess(subprocess_exr, logger=self.log) # raise error if there is no ouptput - if not os.path.exists(full_input_path): + if not os.path.exists(src_path): self.log.error( ("File {} was not converted " - "by oiio tool!").format(full_input_path)) + "by oiio tool!").format(src_path)) raise AssertionError("OIIO tool conversion failed") failed_output = "oiiotool produced no output." output = run_subprocess(args) From d37815467a059cfa2ad2dbde6ce4025ec00def2d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 31 May 2022 14:20:50 +0200 Subject: [PATCH 0419/1227] OP-2787 - added extracted path to explicit cleanup --- openpype/hosts/maya/plugins/publish/extract_animation.py | 2 ++ openpype/hosts/maya/plugins/publish/extract_pointcache.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py index 8f2bc26d08..abe5ed3bf5 100644 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_animation.py @@ -95,4 +95,6 @@ class ExtractAnimation(openpype.api.Extractor): } instance.data["representations"].append(representation) + instance.context.data["cleanupFullPaths"].append(path) + self.log.info("Extracted {} to {}".format(instance, dirname)) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 5606ea9459..c4c8610ebb 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -98,4 +98,6 @@ class ExtractAlembic(openpype.api.Extractor): } instance.data["representations"].append(representation) + instance.context.data["cleanupFullPaths"].append(path) + self.log.info("Extracted {} to {}".format(instance, dirname)) From 0d7d43316b94685f78fb0a7e2e39153a98996b36 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 31 May 2022 14:44:37 +0200 Subject: [PATCH 0420/1227] OP-2787 - changed class to api.Integrator This plugin should run only locally, not no a farm. --- .../plugins/publish/submit_maya_remote_publish_deadline.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py index 196adc5906..c31052be07 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py @@ -5,11 +5,12 @@ from maya import cmds from openpype.pipeline import legacy_io, PublishXmlValidationError from openpype.settings import get_project_settings +import openpype.api import pyblish.api -class MayaSubmitRemotePublishDeadline(pyblish.api.InstancePlugin): +class MayaSubmitRemotePublishDeadline(openpype.api.Integrator): """Submit Maya scene to perform a local publish in Deadline. Publishing in Deadline can be helpful for scenes that publish very slow. From 55a69074c6b550b4e30d99b658b9ec664d30214b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 31 May 2022 14:46:05 +0200 Subject: [PATCH 0421/1227] OP-2787 - Hound --- openpype/scripts/remote_publish.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/scripts/remote_publish.py b/openpype/scripts/remote_publish.py index 8e5c91d663..d322f369d1 100644 --- a/openpype/scripts/remote_publish.py +++ b/openpype/scripts/remote_publish.py @@ -1,7 +1,6 @@ try: from openpype.api import Logger import openpype.lib.remote_publish - import pyblish.api except ImportError as exc: # Ensure Deadline fails by output an error that contains "Fatal Error:" raise ImportError("Fatal Error: %s" % exc) From 26332ef0c8e7eccc1022b95ef883c6850a907681 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 31 May 2022 15:00:15 +0200 Subject: [PATCH 0422/1227] fix file hash --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6b98178aa8..d398257f6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -127,7 +127,7 @@ hash = "95f43568338c275f80dc0cab1e1836a2e2270f856f0e7b204440d881dd74fbdb" [openpype.thirdparty.oiio.windows] url = "https://distribute.openpype.io/thirdparty/oiio_tools-2.3.10-windows.zip" -hash = "fd2e00278e01e85dcee7b4a6969d1a16f13016ec16700fb0366dbb1b1f3c37ad" +hash = "b9950f5d2fa3720b52b8be55bacf5f56d33f9e029d38ee86534995f3d8d253d2" [openpype.thirdparty.oiio.linux] url = "https://distribute.openpype.io/thirdparty/oiio_tools-2.2.12-linux.tgz" From 735942a6f53ca3a36ab1b60ebe462b25a5841abe Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 31 May 2022 16:44:18 +0200 Subject: [PATCH 0423/1227] Nuke: simplify duplicate node function --- openpype/hosts/nuke/api/lib.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 3871381451..7bb8c1e3d1 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2567,8 +2567,7 @@ def duplicate_node(node): reset_selection() # select required node for duplication - node_orig = nuke.toNode(node.name()) - node_orig.setSelected(True) + node.setSelected(True) # copy selected to clipboard nuke.nodeCopy("%clipboard%") @@ -2577,10 +2576,7 @@ def duplicate_node(node): reset_selection() # paste node and selection is on it only - nuke.nodePaste("%clipboard%") - - # assign to variable - dupli_node = nuke.selectedNode() + dupli_node = nuke.nodePaste("%clipboard%") # reset selection reset_selection() From 2de4044167d5940c13bd768ad29879aa9d007b47 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 31 May 2022 16:44:37 +0200 Subject: [PATCH 0424/1227] Nuke: remove callback always --- openpype/hosts/nuke/api/lib.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 7bb8c1e3d1..3062091ba0 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2384,6 +2384,8 @@ def process_workfile_builder(): env_value_to_bool, get_custom_workfile_template ) + # to avoid looping of the callback, remove it! + nuke.removeOnCreate(process_workfile_builder, nodeClass="Root") # get state from settings workfile_builder = get_current_project_settings()["nuke"].get( @@ -2439,9 +2441,6 @@ def process_workfile_builder(): if not openlv_on or not os.path.exists(last_workfile_path): return - # to avoid looping of the callback, remove it! - nuke.removeOnCreate(process_workfile_builder, nodeClass="Root") - log.info("Opening last workfile...") # open workfile open_file(last_workfile_path) From b073853a0b1d056a0976b5a3f983184bfb195d2e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 31 May 2022 18:58:13 +0200 Subject: [PATCH 0425/1227] Update openpype/settings/entities/schemas/projects_schema/schema_project_maya.json Co-authored-by: Milan Kolar --- .../entities/schemas/projects_schema/schema_project_maya.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index f9523b1baa..f7d92c385e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -25,7 +25,7 @@ { "type": "boolean", "key": "use_env_var_as_root", - "label": "Use env var placeholder in referenced url", + "label": "Use env var placeholder in referenced paths", "docstring": "Use ${} placeholder instead of physical value of root when storing into workfile metadata." }, { From 6530fbd918f795077f0a715827e101c97b4bc8ea Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 31 May 2022 18:58:28 +0200 Subject: [PATCH 0426/1227] Update openpype/settings/entities/schemas/projects_schema/schema_project_maya.json Co-authored-by: Milan Kolar --- .../entities/schemas/projects_schema/schema_project_maya.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index f7d92c385e..40e98b0333 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -26,7 +26,7 @@ "type": "boolean", "key": "use_env_var_as_root", "label": "Use env var placeholder in referenced paths", - "docstring": "Use ${} placeholder instead of physical value of root when storing into workfile metadata." + "docstring": "Use ${} placeholder instead of absolute value of a root in referenced filepaths." }, { "type": "boolean", From 5be6f27eb19c76e78ec197358930f2d3de417c4d Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 1 Jun 2022 04:12:29 +0000 Subject: [PATCH 0427/1227] [Automated] Bump version --- CHANGELOG.md | 29 +++++++++++++---------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a5e1f1067..6613985ccf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,28 @@ # Changelog -## [3.10.1-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.10.1-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...HEAD) **🚀 Enhancements** +- General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268) - TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250) -- Support for Unreal 5 [\#3122](https://github.com/pypeclub/OpenPype/pull/3122) +- Nuke: Change default icon path in settings [\#3247](https://github.com/pypeclub/OpenPype/pull/3247) **🐛 Bug fixes** +- Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) +- Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) - Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) - Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240) - Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) - Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) +- Flame: debugging [\#3224](https://github.com/pypeclub/OpenPype/pull/3224) + +**Merged pull requests:** + +- Nuke: add pointcache and animation to loader [\#3186](https://github.com/pypeclub/OpenPype/pull/3186) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -25,7 +33,6 @@ - General: OpenPype modules publish plugins are registered in host [\#3180](https://github.com/pypeclub/OpenPype/pull/3180) - General: Creator plugins from addons can be registered [\#3179](https://github.com/pypeclub/OpenPype/pull/3179) - Ftrack: Single image reviewable [\#3157](https://github.com/pypeclub/OpenPype/pull/3157) -- Nuke: Expose write attributes to settings [\#3123](https://github.com/pypeclub/OpenPype/pull/3123) **🚀 Enhancements** @@ -45,8 +52,6 @@ - Houdini: Add loader for alembic through Alembic Archive node [\#3140](https://github.com/pypeclub/OpenPype/pull/3140) - Publisher: UI Modifications and fixes [\#3139](https://github.com/pypeclub/OpenPype/pull/3139) - General: Simplified OP modules/addons import [\#3137](https://github.com/pypeclub/OpenPype/pull/3137) -- Terminal: Tweak coloring of TrayModuleManager logging enabled states [\#3133](https://github.com/pypeclub/OpenPype/pull/3133) -- General: Cleanup some Loader docstrings [\#3131](https://github.com/pypeclub/OpenPype/pull/3131) **🐛 Bug fixes** @@ -70,17 +75,18 @@ - General: Oiio conversion for ffmpeg checks for invalid characters [\#3166](https://github.com/pypeclub/OpenPype/pull/3166) - Fix for attaching render to subset [\#3164](https://github.com/pypeclub/OpenPype/pull/3164) - Harmony: fixed missing task name in render instance [\#3163](https://github.com/pypeclub/OpenPype/pull/3163) +- add silent audio to slate [\#3162](https://github.com/pypeclub/OpenPype/pull/3162) - Ftrack: Action delete old versions formatting works [\#3152](https://github.com/pypeclub/OpenPype/pull/3152) +- nuke: adding extract thumbnail settings [\#3148](https://github.com/pypeclub/OpenPype/pull/3148) - Deadline: fix the output directory [\#3144](https://github.com/pypeclub/OpenPype/pull/3144) - General: New Session schema [\#3141](https://github.com/pypeclub/OpenPype/pull/3141) - General: Missing version on headless mode crash properly [\#3136](https://github.com/pypeclub/OpenPype/pull/3136) - TVPaint: Composite layers in reversed order [\#3135](https://github.com/pypeclub/OpenPype/pull/3135) -- Nuke: fix anatomy imageio regex default [\#3119](https://github.com/pypeclub/OpenPype/pull/3119) +- TVPaint: Composite layers in reversed order [\#3134](https://github.com/pypeclub/OpenPype/pull/3134) **🔀 Refactored code** - Avalon repo removed from Jobs workflow [\#3193](https://github.com/pypeclub/OpenPype/pull/3193) -- General: Remove remaining imports from avalon [\#3130](https://github.com/pypeclub/OpenPype/pull/3130) **Merged pull requests:** @@ -128,7 +134,6 @@ **🐛 Bug fixes** - Ftrack: Action delete old versions formatting works [\#3154](https://github.com/pypeclub/OpenPype/pull/3154) -- nuke: adding extract thumbnail settings [\#3148](https://github.com/pypeclub/OpenPype/pull/3148) **Merged pull requests:** @@ -138,14 +143,6 @@ [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.5...3.9.6) -**🆕 New features** - -- Nuke: render instance with subset name filtered overrides \(3.9.x\) [\#3125](https://github.com/pypeclub/OpenPype/pull/3125) - -**🐛 Bug fixes** - -- TVPaint: Composite layers in reversed order [\#3134](https://github.com/pypeclub/OpenPype/pull/3134) - ## [3.9.5](https://github.com/pypeclub/OpenPype/tree/3.9.5) (2022-04-25) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.10.0-nightly.2...3.9.5) diff --git a/openpype/version.py b/openpype/version.py index 12f25cdcea..1d8ef28225 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.10.1-nightly.1" +__version__ = "3.10.1-nightly.2" diff --git a/pyproject.toml b/pyproject.toml index d398257f6b..27b32cf53b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.10.1-nightly.1" # OpenPype +version = "3.10.1-nightly.2" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From ca9c06df18bd7ff16ee7bae5ba7f4ef51ac383f7 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 1 Jun 2022 08:03:05 +0300 Subject: [PATCH 0428/1227] continue refactor --- openpype/plugins/publish/extract_jpeg_exr.py | 104 +++++++------------ 1 file changed, 37 insertions(+), 67 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 8211c1a255..4ffacf2600 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -13,6 +13,8 @@ from openpype.lib import ( filter_profiles, run_subprocess, path_to_subprocess_arg, + + execute, ) @@ -46,9 +48,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): if not profile: return - - # Raise an exception when oiiotool is not available - self.log.info("subset {}".format(instance.data['subset'])) # skip crypto passes. @@ -67,7 +66,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): return filtered_repres = self._get_filtered_repres(instance) - for repre in filtered_repres: # Presumably the following is not needed since we are being # explicit @@ -112,61 +110,42 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_file = filename + "jpg" full_output_path = os.path.join(stagingdir, jpeg_file) # If it's a movie file, use ffmpeg - if repre["ext"] == "mov": - self.log.info("output {}".format(full_output_path)) - ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") - ffmpeg_args = self.ffmpeg_args or {} + thumbnail_created = False + # Try to use FFMPEG if OIIO is not supported (for cases when oiiotool + # isn't available) + if not is_oiio_supported(): + thumbnail_created = self.create_thumbnail_ffmpeg(src_path, dst_path) + else: + # Check if the file can be read by OIIO + args = [ + oiiotool_path, "--info", "-i", src_path + ] + returncode = execute(args, silent=True) + # If the input can read by OIIO then use OIIO method for conversion otherwise use ffmpeg + if returncode == 0: + + thumbnail_created = self.create_thumbnail_oiio(src_path, dst_path) + else: + thumbnail_created = self.create_thumbnail_ffmpeg(src_path, dst_path) - jpeg_items = [] - jpeg_items.append(path_to_subprocess_arg(ffmpeg_path)) - # override file if already exists - jpeg_items.append("-y") - # flag for large file sizes - max_int = 2147483647 - jpeg_items.append("-analyzeduration {}".format(max_int)) - jpeg_items.append("-probesize {}".format(max_int)) - # use same input args like with mov - jpeg_items.extend(ffmpeg_args.get("input") or []) - # input file - jpeg_items.append("-i {}".format( - path_to_subprocess_arg(full_input_path) - )) - # output arguments from presets - jpeg_items.extend(ffmpeg_args.get("output") or []) - # we just want one frame from movie files - jpeg_items.append("-vframes 1") - # output file - jpeg_items.append(path_to_subprocess_arg(full_output_path)) - subprocess_command = " ".join(jpeg_items) + # Skip the rest of the process if the thumbnail wasn't created + if not thumbnail_created: - # run subprocess - self.log.debug("{}".format(subprocess_command)) - try: # temporary until oiiotool is supported cross platform - run_subprocess( - subprocess_command, shell=True, logger=self.log - ) - except RuntimeError as exp: - if "Compression" in str(exp): - self.log.debug( - "Unsupported compression on input files. Skipping!" - ) - return - self.log.warning("Conversion crashed", exc_info=True) - raise + return - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) - + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } + + # adding representation + self.log.debug("Adding: {}".format(new_repre)) + instance.data["representations"].append(new_repre) def _get_filtered_repres(self, instance): filtered_repres = [] @@ -188,11 +167,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): return filtered_repres def create_thumbnail_oiio(self, instance, src_path, dst_path): - oiio_path = get_oiio_tools_path() - if not os.path.exists(oiio_path): - KnownPublishError( - "OpenImageIO tool is not available on this machine." - ) + oiio_tool_path = get_oiio_tools_path() args = [oiio_tool_path] oiio_cmd = [oiio_tool_path, src_path, "-o", @@ -202,12 +177,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): self.log.info(f"running: {subprocess_exr}") run_subprocess(subprocess_exr, logger=self.log) - # raise error if there is no ouptput - if not os.path.exists(src_path): - self.log.error( - ("File {} was not converted " - "by oiio tool!").format(src_path)) - raise AssertionError("OIIO tool conversion failed") + # raise an error if there's no output failed_output = "oiiotool produced no output." output = run_subprocess(args) if failed_output in output: @@ -230,7 +200,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # will be thumbnail created def create_thumbnail_ffmpeg(self, src_path, dst_path): - self.log.info("output {}".format(full_output_path)) + self.log.info("output {}".format(dst_path)) ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") ffmpeg_args = self.ffmpeg_args or {} From ebfef2a1b869c07eee950952bc38b97717281854 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 1 Jun 2022 08:11:26 +0300 Subject: [PATCH 0429/1227] Cleanup --- openpype/plugins/publish/extract_jpeg_exr.py | 32 ++++++-------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 4ffacf2600..15f90262dc 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -115,19 +115,20 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # Try to use FFMPEG if OIIO is not supported (for cases when oiiotool # isn't available) if not is_oiio_supported(): - thumbnail_created = self.create_thumbnail_ffmpeg(src_path, dst_path) + thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) else: # Check if the file can be read by OIIO args = [ - oiiotool_path, "--info", "-i", src_path + oiio_tool_path, "--info", "-i", full_output_path ] returncode = execute(args, silent=True) - # If the input can read by OIIO then use OIIO method for conversion otherwise use ffmpeg + # If the input can read by OIIO then use OIIO method for + # conversion otherwise use ffmpeg if returncode == 0: - thumbnail_created = self.create_thumbnail_oiio(src_path, dst_path) + thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa else: - thumbnail_created = self.create_thumbnail_ffmpeg(src_path, dst_path) + thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa # Skip the rest of the process if the thumbnail wasn't created if not thumbnail_created: @@ -166,7 +167,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres.append(repre) return filtered_repres - def create_thumbnail_oiio(self, instance, src_path, dst_path): + def create_thumbnail_oiio(self, src_path, dst_path): oiio_tool_path = get_oiio_tools_path() args = [oiio_tool_path] oiio_cmd = [oiio_tool_path, @@ -183,21 +184,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): if failed_output in output: raise ValueError( "oiiotool processing failed. Args: {}".format(args)) # noqa - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) - - # Create only one representation with name 'thumbnail' - # TODO maybe handle way how to decide from which repre - # will be thumbnail created def create_thumbnail_ffmpeg(self, src_path, dst_path): self.log.info("output {}".format(dst_path)) @@ -217,14 +203,14 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.extend(ffmpeg_args.get("input") or []) # input file jpeg_items.append("-i {}".format( - path_to_subprocess_arg(full_input_path) + path_to_subprocess_arg(src_path) )) # output arguments from presets jpeg_items.extend(ffmpeg_args.get("output") or []) # we just want one frame from movie files jpeg_items.append("-vframes 1") # output file - jpeg_items.append(path_to_subprocess_arg(full_output_path)) + jpeg_items.append(path_to_subprocess_arg(src_path)) subprocess_command = " ".join(jpeg_items) # run subprocess From b720d46489cc88a2b08cb11964fad2b4d91af8e5 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 1 Jun 2022 08:31:20 +0300 Subject: [PATCH 0430/1227] continue refactoring --- openpype/plugins/publish/extract_jpeg_exr.py | 116 +++++-------------- 1 file changed, 29 insertions(+), 87 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 15f90262dc..8dc06bfe79 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -67,32 +67,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres = self._get_filtered_repres(instance) for repre in filtered_repres: - # Presumably the following is not needed since we are being - # explicit - # TODO: Test cases, cleanup. - # do_convert = should_convert_for_ffmpeg(full_input_path) - # # If result is None the requirement of conversion can't be - # # determined - # if do_convert is None: - # self.log.info(( - # "Can't determine if representation requires conversion." # noqa - # " Skipped." - # )) - # continue - # - # # Do conversion if needed - # # - change staging dir of source representation - # # - must be set back after output definitions processing - # convert_dir = None - # if do_convert: - # convert_dir = get_transcode_temp_directory() - # filename = os.path.basename(full_input_path) - # convert_input_paths_for_ffmpeg( - # [full_input_path], - # convert_dir, - # self.log - # ) - # full_input_path = os.path.join(convert_dir, filename) repre_files = repre["files"] if not isinstance(repre_files, (list, tuple)): input_file = repre_files @@ -111,38 +85,38 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): full_output_path = os.path.join(stagingdir, jpeg_file) # If it's a movie file, use ffmpeg - thumbnail_created = False - # Try to use FFMPEG if OIIO is not supported (for cases when oiiotool - # isn't available) - if not is_oiio_supported(): - thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) - else: - # Check if the file can be read by OIIO - args = [ - oiio_tool_path, "--info", "-i", full_output_path - ] - returncode = execute(args, silent=True) - # If the input can read by OIIO then use OIIO method for - # conversion otherwise use ffmpeg - if returncode == 0: - - thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa + thumbnail_created = False + # Try to use FFMPEG if OIIO is not supported (for cases when + # oiiotool isn't available) + if not is_oiio_supported(): + thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) else: - thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa + # Check if the file can be read by OIIO + args = [ + oiio_tool_path, "--info", "-i", full_output_path + ] + returncode = execute(args, silent=True) + # If the input can read by OIIO then use OIIO method for + # conversion otherwise use ffmpeg + if returncode == 0: + + thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa + else: + thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa - # Skip the rest of the process if the thumbnail wasn't created - if not thumbnail_created: + # Skip the rest of the process if the thumbnail wasn't created + if not thumbnail_created: - return + return - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } # adding representation self.log.debug("Adding: {}".format(new_repre)) @@ -169,7 +143,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): def create_thumbnail_oiio(self, src_path, dst_path): oiio_tool_path = get_oiio_tools_path() - args = [oiio_tool_path] oiio_cmd = [oiio_tool_path, src_path, "-o", dst_path @@ -178,13 +151,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): self.log.info(f"running: {subprocess_exr}") run_subprocess(subprocess_exr, logger=self.log) - # raise an error if there's no output - failed_output = "oiiotool produced no output." - output = run_subprocess(args) - if failed_output in output: - raise ValueError( - "oiiotool processing failed. Args: {}".format(args)) # noqa - def create_thumbnail_ffmpeg(self, src_path, dst_path): self.log.info("output {}".format(dst_path)) @@ -213,30 +179,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.append(path_to_subprocess_arg(src_path)) subprocess_command = " ".join(jpeg_items) - # run subprocess - self.log.debug("{}".format(subprocess_command)) - try: # temporary until oiiotool is supported cross platform - run_subprocess( + run_subprocess( subprocess_command, shell=True, logger=self.log ) - except RuntimeError as exp: - if "Compression" in str(exp): - self.log.debug( - "Unsupported compression on input files. Skipping!" - ) - return - self.log.warning("Conversion crashed", exc_info=True) - raise - - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } - - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) \ No newline at end of file From 84c073dd3c45515a055d36a8eee29bfe3cd749cd Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 1 Jun 2022 10:19:45 +0300 Subject: [PATCH 0431/1227] Further clean up and refactor. --- openpype/plugins/publish/extract_jpeg_exr.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 8dc06bfe79..a5a62c04ee 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -3,7 +3,6 @@ import os import pyblish.api from openpype.pipeline import ( legacy_io, - KnownPublishError ) from openpype.lib import ( get_ffmpeg_tool_path, @@ -83,15 +82,15 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filename += "." jpeg_file = filename + "jpg" full_output_path = os.path.join(stagingdir, jpeg_file) - # If it's a movie file, use ffmpeg thumbnail_created = False # Try to use FFMPEG if OIIO is not supported (for cases when # oiiotool isn't available) if not is_oiio_supported(): - thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) + thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa else: # Check if the file can be read by OIIO + oiio_tool_path = get_oiio_tools_path() args = [ oiio_tool_path, "--info", "-i", full_output_path ] @@ -99,9 +98,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # If the input can read by OIIO then use OIIO method for # conversion otherwise use ffmpeg if returncode == 0: - + self.log.info("Input can be read by OIIO, converting with oiiotool now.") thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa else: + self.log.info("converting with") thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa # Skip the rest of the process if the thumbnail wasn't created @@ -142,6 +142,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): return filtered_repres def create_thumbnail_oiio(self, src_path, dst_path): + self.log.info("outputting {}".format(dst_path)) oiio_tool_path = get_oiio_tools_path() oiio_cmd = [oiio_tool_path, src_path, "-o", @@ -152,7 +153,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): run_subprocess(subprocess_exr, logger=self.log) def create_thumbnail_ffmpeg(self, src_path, dst_path): - self.log.info("output {}".format(dst_path)) + self.log.info("outputting {}".format(dst_path)) ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") ffmpeg_args = self.ffmpeg_args or {} From 3b26f78335057810cd8cd9d23f1de3546524211a Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 1 Jun 2022 10:23:52 +0300 Subject: [PATCH 0432/1227] Revert "Add profiles handling in schema aand for extractor" This reverts commit 8453f9710f1fb305d917723eeee4cfea5c007062. --- openpype/plugins/publish/extract_jpeg_exr.py | 17 --------- .../defaults/project_settings/deadline.json | 2 +- .../defaults/project_settings/global.json | 12 +----- .../schemas/schema_global_publish.json | 38 ------------------- repos/avalon-core | 1 + 5 files changed, 3 insertions(+), 67 deletions(-) create mode 160000 repos/avalon-core diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index a5a62c04ee..4a1fea2043 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -1,15 +1,11 @@ import os import pyblish.api -from openpype.pipeline import ( - legacy_io, -) from openpype.lib import ( get_ffmpeg_tool_path, get_oiio_tools_path, is_oiio_supported, - filter_profiles, run_subprocess, path_to_subprocess_arg, @@ -34,19 +30,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): oiio_args = None def process(self, instance): - task_name = instance.data.get("task", legacy_io.Session["AVALON_TASK"]) - host_name = legacy_io.Session["AVALON_APP"] - family = instance.data["family"] - filtering_criteria = { - "hosts": host_name, - "families": family, - "tasks": task_name - } - profile = filter_profiles(self.profiles, filtering_criteria, - logger=self.log) - if not profile: - return - self.log.info("subset {}".format(instance.data['subset'])) # skip crypto passes. diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 5c5a14bf21..f0b2a7e555 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -98,4 +98,4 @@ } } } -} \ No newline at end of file +} diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 4ad56d8086..cedd0eed99 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -40,17 +40,7 @@ "-apply_trc gamma22" ], "output": [] - }, - "thumbnail_extraction_profiles": [ - { - "families": [ - "" - ], - "hosts": [], - "task_types": [], - "tasks": [] - } - ] + } }, "ExtractReview": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 4149c99348..a3cbf0cfcd 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -152,44 +152,6 @@ "label": "FFmpeg output arguments" } ] - }, - { - "type": "list", - "key": "thumbnail_extraction_profiles", - "label": "Thumbnail Extraction profiles", - "use_label_wrap": true, - "object_type": { - "type": "dict", - "children": [ - { - "type": "label", - "label": "" - }, - { - "key": "families", - "label": "Families", - "type": "list", - "object_type": "text" - }, - { - "type": "hosts-enum", - "key": "hosts", - "label": "Hosts", - "multiselection": true - }, - { - "key": "task_types", - "label": "Task types", - "type": "task-types-enum" - }, - { - "key": "tasks", - "label": "Task names", - "type": "list", - "object_type": "text" - } - ] - } } ] }, diff --git a/repos/avalon-core b/repos/avalon-core new file mode 160000 index 0000000000..2fa14cea6f --- /dev/null +++ b/repos/avalon-core @@ -0,0 +1 @@ +Subproject commit 2fa14cea6f6a9d86eec70bbb96860cbe4c75c8eb From c4468664970bd93bbd074d7424042d4adee20c8e Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 1 Jun 2022 10:25:56 +0300 Subject: [PATCH 0433/1227] remove unused variable --- openpype/plugins/publish/extract_jpeg_exr.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 4a1fea2043..daf7430a32 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -27,7 +27,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # presetable attribute ffmpeg_args = None - oiio_args = None def process(self, instance): self.log.info("subset {}".format(instance.data['subset'])) From a10bbbcc79b5a369d51bb4716164611edcb4dbfa Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Jun 2022 12:33:09 +0200 Subject: [PATCH 0434/1227] OP-3068 - better handling of legacy review subsets in Maya Multiple reviews from a Maya workfile are blocked by legacy subset names without variant. These names could be used in later process so we cannot replace them. Unique subset name validator was added, check for existing subset in DB too. --- .../maya/plugins/publish/collect_review.py | 17 +++++---- .../validate_review_subset_uniqueness.xml | 28 +++++++++++++++ .../validate_review_subset_uniqueness.py | 36 +++++++++++++++++++ 3 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 openpype/hosts/maya/plugins/publish/help/validate_review_subset_uniqueness.xml create mode 100644 openpype/hosts/maya/plugins/publish/validate_review_subset_uniqueness.py diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index 1af92c3bfc..e9e0d74c03 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -77,15 +77,14 @@ class CollectReview(pyblish.api.InstancePlugin): instance.data['remove'] = True self.log.debug('isntance data {}'.format(instance.data)) else: - if self.legacy: - instance.data['subset'] = task + 'Review' - else: - subset = "{}{}{}".format( - task, - instance.data["subset"][0].upper(), - instance.data["subset"][1:] - ) - instance.data['subset'] = subset + legacy_subset_name = task + 'Review' + asset_doc_id = instance.context.data['assetEntity']["_id"] + subsets = legacy_io.find({"type": "subset", + "name": legacy_subset_name, + "parent": asset_doc_id}).distinct("_id") + if len(list(subsets)) > 0: + self.log.debug("Existing subsets found, keep legacy name.") + instance.data['subset'] = legacy_subset_name instance.data['review_camera'] = camera instance.data['frameStartFtrack'] = \ diff --git a/openpype/hosts/maya/plugins/publish/help/validate_review_subset_uniqueness.xml b/openpype/hosts/maya/plugins/publish/help/validate_review_subset_uniqueness.xml new file mode 100644 index 0000000000..fd1bf4cbaa --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/help/validate_review_subset_uniqueness.xml @@ -0,0 +1,28 @@ + + + + Review subsets not unique + + ## Non unique subset name found + + Non unique subset names: '{non_unique}' + + ### __Detailed Info__ (optional) + + This might happen if you already published for this asset + review subset with legacy name {task}Review. + This legacy name limits possibility of publishing of multiple + reviews from a single workfile. Proper review subset name should + now + contain variant also (as 'Main', 'Default' etc.). That would + result in completely new subset though, so this situation must + be handled manually. + + ### How to repair? + + Legacy subsets must be removed from Openpype DB, please ask admin + to do that. Please provide them asset and subset names. + + + + \ No newline at end of file diff --git a/openpype/hosts/maya/plugins/publish/validate_review_subset_uniqueness.py b/openpype/hosts/maya/plugins/publish/validate_review_subset_uniqueness.py new file mode 100644 index 0000000000..d70096ee45 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_review_subset_uniqueness.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +import collections +import pyblish.api +import openpype.api +from openpype.pipeline import PublishXmlValidationError + + +class ValidateReviewSubsetUniqueness(pyblish.api.ContextPlugin): + """Validates that nodes has common root.""" + + order = openpype.api.ValidateContentsOrder + hosts = ["maya"] + families = ["review"] + label = "Validate Review Subset Unique" + + def process(self, context): + subset_names = [] + + for instance in context: + self.log.info("instance:: {}".format(instance.data)) + if instance.data.get('publish'): + subset_names.append(instance.data.get('subset')) + + non_unique = \ + [item + for item, count in collections.Counter(subset_names).items() + if count > 1] + msg = ("Instance subset names {} are not unique. ".format(non_unique) + + "Ask admin to remove subset from DB for multiple reviews.") + formatting_data = { + "non_unique": ",".join(non_unique) + } + + if non_unique: + raise PublishXmlValidationError(self, msg, + formatting_data=formatting_data) From 6a5fa89348bb822e1f2c8748fa867f19e2f70a8d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Jun 2022 14:27:55 +0200 Subject: [PATCH 0435/1227] Fix - change default of use_env_var_as_root True was there only for testing, false is more sane default. --- openpype/settings/defaults/project_settings/maya.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index a42f889e85..efd22e13c8 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -8,7 +8,7 @@ "yetiRig": "ma" }, "maya-dirmap": { - "use_env_var_as_root": true, + "use_env_var_as_root": false, "enabled": false, "paths": { "source-path": [], From 8b43c5e733117d528a164f45a6112b6d76a53e42 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 4 May 2022 12:16:19 +0200 Subject: [PATCH 0436/1227] add a tab in nuke project settings for gizmos --- openpype/hosts/nuke/startup/menu.py | 1 - .../defaults/project_settings/nuke.json | 22 ++++++++++ .../projects_schema/schema_project_nuke.json | 4 ++ .../schemas/schema_nuke_scriptsgizmo.json | 42 +++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsgizmo.json diff --git a/openpype/hosts/nuke/startup/menu.py b/openpype/hosts/nuke/startup/menu.py index 49edb22a89..eea2d940f8 100644 --- a/openpype/hosts/nuke/startup/menu.py +++ b/openpype/hosts/nuke/startup/menu.py @@ -31,7 +31,6 @@ nuke.addFilenameFilter(dirmap_file_name_filter) log.info('Automatic syncing of write file knob to script version') - def add_scripts_menu(): try: from scriptsmenu import launchfornuke diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index dc8ffcebff..a10b88464c 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -290,5 +290,27 @@ } ] }, + "gizmo": [ + { + "toolbar_menu_name": "FixStudio", + "toolbar_icon_path": "{QUAD_PLUGIN_PATH}/nuke/icons/fixstudio.png", + "gizmo_path": ["{QUAD_PLUGIN_PATH}/nuke/gizmos"], + "gizmo_definition": [ + { + "type": "menu", + "title": "3D", + "items": [ + { + "type": "action", + "command": "nuke.createNode('Camera_Smoother')", + "sourcetype": "python", + "title": "Camera_Smoother" + } + + ] + } + ] + } + ], "filters": {} } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json index 1ae4efd8ea..03d67a57ba 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json @@ -83,6 +83,10 @@ "type": "schema", "name": "schema_scriptsmenu" }, + { + "type": "schema", + "name": "schema_nuke_scriptsgizmo" + }, { "type": "dict", "collapsible": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsgizmo.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsgizmo.json new file mode 100644 index 0000000000..c1e67842ce --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsgizmo.json @@ -0,0 +1,42 @@ +{ + "type": "list", + "key": "gizmo", + "label": "Gizmo Menu", + "is_group": true, + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "text", + "key": "toolbar_menu_name", + "label": "Toolbar Menu Name" + }, + { + "type": "path", + "key": "toolbar_icon_path", + "label": "Toolbar Icon Path", + "multipath": false + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "Absolute path to gizmo folders." + }, + { + "type": "path", + "key": "gizmo_path", + "label": "Gizmo Path", + "multipath": true + }, + { + "type": "raw-json", + "key": "gizmo_definition", + "label": "Gizmo definition", + "is_list": true + } + ] + } +} \ No newline at end of file From 25518a1c42ff9958f9bb9763b0792dd28e8cc691 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 4 May 2022 17:01:32 +0200 Subject: [PATCH 0437/1227] generate toolbar menu from openpype project settings --- openpype/hosts/nuke/api/lib.py | 48 +++++++++++++++ openpype/hosts/nuke/startup/menu.py | 59 ++++++++++++++++++- .../defaults/project_settings/nuke.json | 9 ++- 3 files changed, 110 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index f40425eefc..a1ac50ae1a 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -30,6 +30,8 @@ from openpype.pipeline import ( legacy_io, ) +from . import gizmo_menu + from .workio import ( save_file, open_file @@ -2498,6 +2500,52 @@ def recreate_instance(origin_node, avalon_data=None): return new_node +def find_scripts_gizmo(title, parent): + """ + Check if the menu exists with the given title in the parent + + Args: + title (str): the title name of the scripts menu + + parent (QtWidgets.QMenuBar): the menubar to check + + Returns: + QtWidgets.QMenu or None + + """ + + menu = None + search = [i for i in parent.items() if + isinstance(i, gizmo_menu.GizmoMenu) + and i.title() == title] + + if search: + assert len(search) < 2, ("Multiple instances of menu '{}' " + "in toolbar".format(title)) + menu = search[0] + + return menu + + +def gizmo_creation(title="Gizmos", parent=None, objectName=None, icon=None): + try: + toolbar = find_scripts_gizmo(title, parent) + if not toolbar: + log.info("Attempting to build toolbar...") + object_name = objectName or title.lower() + toolbar = gizmo_menu.GizmoMenu( + title=title, + parent=parent, + objectName=object_name, + icon=icon + ) + except Exception as e: + log.error(e) + return + + return toolbar + + class NukeDirmap(HostDirmap): def __init__(self, host_name, project_settings, sync_module, file_name): """ diff --git a/openpype/hosts/nuke/startup/menu.py b/openpype/hosts/nuke/startup/menu.py index eea2d940f8..0f587fc62a 100644 --- a/openpype/hosts/nuke/startup/menu.py +++ b/openpype/hosts/nuke/startup/menu.py @@ -2,7 +2,7 @@ import nuke import os from openpype.api import Logger -from openpype.pipeline import install_host +from openpype.settings import get_project_settings from openpype.hosts.nuke import api from openpype.hosts.nuke.api.lib import ( on_script_load, @@ -31,6 +31,7 @@ nuke.addFilenameFilter(dirmap_file_name_filter) log.info('Automatic syncing of write file knob to script version') + def add_scripts_menu(): try: from scriptsmenu import launchfornuke @@ -58,3 +59,59 @@ def add_scripts_menu(): add_scripts_menu() + + +def add_scripts_gizmo(): + try: + from openpype.hosts.nuke.api import lib + except ImportError: + log.warning( + "Skipping studio.gizmo install, because " + "'scriptsgizmo' module seems unavailable." + ) + return + + for gizmo in project_settings["nuke"]["gizmo"]: + config = gizmo["gizmo_definition"] + toolbar_name = gizmo["toolbar_menu_name"] + gizmo_path = gizmo["gizmo_path"] + icon = gizmo['toolbar_icon_path'] + + if not any(gizmo_path): + log.warning("Skipping studio gizmo, no gizmo path found.") + return + + if not config: + log.warning("Skipping studio gizmo, no definition found.") + return + + try: + icon = icon.format(**os.environ) + except KeyError as e: + log.warning(f"This environment variable doesn't exist: {e}") + + for gizmo in gizmo_path: + try: + gizmo = gizmo.format(**os.environ) + gizmo_path.append(gizmo) + gizmo_path.pop(0) + except KeyError as e: + log.warning(f"This environment variable doesn't exist: {e}") + + nuke_toolbar = nuke.menu("Nodes") + toolbar = nuke_toolbar.addMenu(toolbar_name, icon=icon) + + # run the launcher for Nuke toolbar + studio_menu = lib.gizmo_creation( + title=toolbar_name, + parent=toolbar, + objectName=toolbar_name.lower().replace(" ", "_"), + icon=icon + ) + + # apply configuration + studio_menu.add_gizmo_path(gizmo_path) + studio_menu.build_from_configuration(toolbar, config) + + +add_scripts_gizmo() diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index a10b88464c..48bbbf0dcc 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -293,8 +293,8 @@ "gizmo": [ { "toolbar_menu_name": "FixStudio", - "toolbar_icon_path": "{QUAD_PLUGIN_PATH}/nuke/icons/fixstudio.png", - "gizmo_path": ["{QUAD_PLUGIN_PATH}/nuke/gizmos"], + "toolbar_icon_path": "openpype/modules/quad/nuke/icons/fixstudio.png", + "gizmo_path": ["openpype/modules/quad/nuke/gizmos/3D"], "gizmo_definition": [ { "type": "menu", @@ -302,11 +302,10 @@ "items": [ { "type": "action", - "command": "nuke.createNode('Camera_Smoother')", "sourcetype": "python", - "title": "Camera_Smoother" + "title": "Camera Smoother", + "command": "nuke.createNodes('Camera_Smoother)" } - ] } ] From af259d215e08d54cca7dd01754a03f8b0863aefe Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 9 May 2022 15:12:16 +0200 Subject: [PATCH 0438/1227] refactor default gizmo --- openpype/settings/defaults/project_settings/nuke.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 48bbbf0dcc..d9b443c958 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -293,8 +293,8 @@ "gizmo": [ { "toolbar_menu_name": "FixStudio", - "toolbar_icon_path": "openpype/modules/quad/nuke/icons/fixstudio.png", - "gizmo_path": ["openpype/modules/quad/nuke/gizmos/3D"], + "toolbar_icon_path": "path/to/nuke/icon.png", + "gizmo_path": ["path/to/nuke/gizmo"], "gizmo_definition": [ { "type": "menu", @@ -304,7 +304,7 @@ "type": "action", "sourcetype": "python", "title": "Camera Smoother", - "command": "nuke.createNodes('Camera_Smoother)" + "command": "nuke.createNode('Camera_Smoother')" } ] } From 5bc741d993b67a3e9ebfd74b5cc711caa56e06fb Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 9 May 2022 15:13:50 +0200 Subject: [PATCH 0439/1227] add gizmo_menu module --- openpype/hosts/nuke/api/gizmo_menu.py | 77 +++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 openpype/hosts/nuke/api/gizmo_menu.py diff --git a/openpype/hosts/nuke/api/gizmo_menu.py b/openpype/hosts/nuke/api/gizmo_menu.py new file mode 100644 index 0000000000..56532ed1dc --- /dev/null +++ b/openpype/hosts/nuke/api/gizmo_menu.py @@ -0,0 +1,77 @@ +import os +import logging +import nuke + +log = logging.getLogger(__name__) + + +class GizmoMenu(): + def __init__(self, *args, **kwargs): + self._script_actions = [] + + def build_from_configuration(self, parent, configuration): + for item in configuration: + assert isinstance(item, dict), "Configuration is wrong!" + + # skip items which have no `type` key + item_type = item.get('type', None) + if not item_type: + log.warning("Missing 'type' from configuration item") + continue + + if item_type == "action": + # filter out `type` from the item dict + config = {key: value for key, value in + item.items() if key != "type"} + + command = config['command'] + + if command.find('{pipe_path}') > -1: + command = command.format( + pipe_path=os.environ['QUAD_PLUGIN_PATH'] + ) + + icon = config.get('icon', None) + if icon: + try: + icon = icon.format(**os.environ) + except KeyError as e: + log.warning(f"This environment variable doesn't exist: {e}'") + + hotkey = config.get('hotkey', None) + + parent.addCommand( + config['title'], + command=command, + icon=icon, + shortcut=hotkey + ) + + # add separator + # Special behavior for separators + if item_type == "separator": + parent.addSeparator() + + # add submenu + # items should hold a collection of submenu items (dict) + elif item_type == "menu": + assert "items" in item, "Menu is missing 'items' key" + + icon = item.get('icon', None) + if icon: + try: + icon = icon.format(**os.environ) + except KeyError as e: + log.warning(f"This environment variable doesn't exist: {e}'") + menu = parent.addMenu(item['title'], icon=icon) + self.build_from_configuration(menu, item["items"]) + + def add_gizmo_path(self, gizmo_paths): + for gizmo_path in gizmo_paths: + if os.path.isdir(gizmo_path): + for folder in os.listdir(gizmo_path): + if os.path.isdir(os.path.join(gizmo_path, folder)): + nuke.pluginAddPath(os.path.join(gizmo_path, folder)) + nuke.pluginAddPath(gizmo_path) + else: + log.warning(f"This path doesn't exist: {gizmo_path}") From 94356faa9c16f359dca2e94a726e79f16253e1d3 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 11 May 2022 11:46:57 +0200 Subject: [PATCH 0440/1227] fix install_host import --- openpype/hosts/nuke/startup/menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/startup/menu.py b/openpype/hosts/nuke/startup/menu.py index 0f587fc62a..88c727aaa6 100644 --- a/openpype/hosts/nuke/startup/menu.py +++ b/openpype/hosts/nuke/startup/menu.py @@ -2,7 +2,7 @@ import nuke import os from openpype.api import Logger -from openpype.settings import get_project_settings +from openpype.pipeline import install_host from openpype.hosts.nuke import api from openpype.hosts.nuke.api.lib import ( on_script_load, From 0696d505c2cf9eb53c10afe2c2ffa6b73f3cbe7b Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 12 May 2022 11:12:08 +0200 Subject: [PATCH 0441/1227] set the default gizmo to a sticky note --- .../settings/defaults/project_settings/nuke.json | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index d9b443c958..06679ac314 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -292,21 +292,15 @@ }, "gizmo": [ { - "toolbar_menu_name": "FixStudio", + "toolbar_menu_name": "OpenPype Gizmo", "toolbar_icon_path": "path/to/nuke/icon.png", "gizmo_path": ["path/to/nuke/gizmo"], "gizmo_definition": [ { - "type": "menu", - "title": "3D", - "items": [ - { - "type": "action", - "sourcetype": "python", - "title": "Camera Smoother", - "command": "nuke.createNode('Camera_Smoother')" - } - ] + "type": "action", + "sourcetype": "python", + "title": "Gizmo Note", + "command": "nuke.nodes.StickyNote(label='You can create your own toolbar menu in the Nuke GizmoMenu of OpenPype')" } ] } From bd7243f8aa1dd64d3bad4ef822eb24e4ee70e4d4 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 12 May 2022 11:20:55 +0200 Subject: [PATCH 0442/1227] add the docs for the gizmo menu --- website/docs/admin_hosts_nuke.md | 4 ++++ website/docs/assets/nuke-admin_gizmomenu.png | Bin 0 -> 34803 bytes 2 files changed, 4 insertions(+) create mode 100644 website/docs/assets/nuke-admin_gizmomenu.png diff --git a/website/docs/admin_hosts_nuke.md b/website/docs/admin_hosts_nuke.md index 46f596a2dc..bab63223ce 100644 --- a/website/docs/admin_hosts_nuke.md +++ b/website/docs/admin_hosts_nuke.md @@ -12,3 +12,7 @@ You can add your custom tools menu into Nuke by extending definitions in **Nuke This is still work in progress. Menu definition will be handled more friendly with widgets and not raw json. ::: + +## Gizmo Menu +You can add your custom toolbar menu into Nuke by setting your gizmo path and extending definitions in **Nuke -> Gizmo Menu**. +![Custom menu definition](assets/nuke-admin_gizmomenu.png) diff --git a/website/docs/assets/nuke-admin_gizmomenu.png b/website/docs/assets/nuke-admin_gizmomenu.png new file mode 100644 index 0000000000000000000000000000000000000000..81e63b20418896173bf5f6417ce2515780e2bcdf GIT binary patch literal 34803 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 KXW7E9J^v4^Goq*f literal 0 HcmV?d00001 From 08e04911b3c100ca1dba8d1107a33cf689c98e79 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Thu, 12 May 2022 15:55:11 +0200 Subject: [PATCH 0443/1227] fix string format for python2 --- openpype/hosts/nuke/api/gizmo_menu.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/api/gizmo_menu.py b/openpype/hosts/nuke/api/gizmo_menu.py index 56532ed1dc..c1132792d0 100644 --- a/openpype/hosts/nuke/api/gizmo_menu.py +++ b/openpype/hosts/nuke/api/gizmo_menu.py @@ -36,7 +36,8 @@ class GizmoMenu(): try: icon = icon.format(**os.environ) except KeyError as e: - log.warning(f"This environment variable doesn't exist: {e}'") + log.warning("This environment variable doesn't exist: " + "{}".format(e)) hotkey = config.get('hotkey', None) @@ -62,7 +63,8 @@ class GizmoMenu(): try: icon = icon.format(**os.environ) except KeyError as e: - log.warning(f"This environment variable doesn't exist: {e}'") + log.warning("This environment variable doesn't exist: " + "{}".format(e)) menu = parent.addMenu(item['title'], icon=icon) self.build_from_configuration(menu, item["items"]) @@ -74,4 +76,4 @@ class GizmoMenu(): nuke.pluginAddPath(os.path.join(gizmo_path, folder)) nuke.pluginAddPath(gizmo_path) else: - log.warning(f"This path doesn't exist: {gizmo_path}") + log.warning("This path doesn't exist: {}".format(gizmo_path)) From eb590602c329b90d8b1e40aeda4ae04287e01a06 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Wed, 1 Jun 2022 17:31:19 +0200 Subject: [PATCH 0444/1227] make it works on nuke 12 --- openpype/hosts/nuke/api/gizmo_menu.py | 2 +- openpype/hosts/nuke/startup/menu.py | 11 +++++++++-- openpype/settings/defaults/project_settings/nuke.json | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/api/gizmo_menu.py b/openpype/hosts/nuke/api/gizmo_menu.py index c1132792d0..a541fd3ab1 100644 --- a/openpype/hosts/nuke/api/gizmo_menu.py +++ b/openpype/hosts/nuke/api/gizmo_menu.py @@ -24,7 +24,7 @@ class GizmoMenu(): config = {key: value for key, value in item.items() if key != "type"} - command = config['command'] + command = str(config['command']) if command.find('{pipe_path}') > -1: command = command.format( diff --git a/openpype/hosts/nuke/startup/menu.py b/openpype/hosts/nuke/startup/menu.py index 88c727aaa6..6c076fc87b 100644 --- a/openpype/hosts/nuke/startup/menu.py +++ b/openpype/hosts/nuke/startup/menu.py @@ -71,6 +71,9 @@ def add_scripts_gizmo(): ) return + # load configuration of custom menu + project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) + for gizmo in project_settings["nuke"]["gizmo"]: config = gizmo["gizmo_definition"] toolbar_name = gizmo["toolbar_menu_name"] @@ -88,7 +91,9 @@ def add_scripts_gizmo(): try: icon = icon.format(**os.environ) except KeyError as e: - log.warning(f"This environment variable doesn't exist: {e}") + log.warning( + "This environment variable doesn't exist: {}".format(e) + ) for gizmo in gizmo_path: try: @@ -96,7 +101,9 @@ def add_scripts_gizmo(): gizmo_path.append(gizmo) gizmo_path.pop(0) except KeyError as e: - log.warning(f"This environment variable doesn't exist: {e}") + log.warning( + "This environment variable doesn't exist: {}".format(e) + ) nuke_toolbar = nuke.menu("Nodes") toolbar = nuke_toolbar.addMenu(toolbar_name, icon=icon) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 06679ac314..6c6454de36 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -306,4 +306,4 @@ } ], "filters": {} -} \ No newline at end of file +} From 8f9c08549292a58b7beccf3ae8a9477b2aac1020 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Wed, 1 Jun 2022 20:40:28 +0200 Subject: [PATCH 0445/1227] beter loop check --- openpype/hosts/nuke/startup/menu.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/startup/menu.py b/openpype/hosts/nuke/startup/menu.py index 6c076fc87b..715bab8ea5 100644 --- a/openpype/hosts/nuke/startup/menu.py +++ b/openpype/hosts/nuke/startup/menu.py @@ -95,15 +95,21 @@ def add_scripts_gizmo(): "This environment variable doesn't exist: {}".format(e) ) + existing_gizmo_path = [] for gizmo in gizmo_path: try: gizmo = gizmo.format(**os.environ) - gizmo_path.append(gizmo) - gizmo_path.pop(0) except KeyError as e: log.warning( "This environment variable doesn't exist: {}".format(e) ) + continue + if not os.path.exists(gizmo): + log.warning( + "The source of gizmo `{}` does not exists".format(gizmo) + ) + continue + existing_gizmo_path.append(gizmo) nuke_toolbar = nuke.menu("Nodes") toolbar = nuke_toolbar.addMenu(toolbar_name, icon=icon) @@ -117,7 +123,7 @@ def add_scripts_gizmo(): ) # apply configuration - studio_menu.add_gizmo_path(gizmo_path) + studio_menu.add_gizmo_path(existing_gizmo_path) studio_menu.build_from_configuration(toolbar, config) From 0fcfdf7fa8250d400a7da772be12972b59c667fd Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Wed, 1 Jun 2022 20:44:10 +0200 Subject: [PATCH 0446/1227] remove studio operation --- openpype/hosts/nuke/api/gizmo_menu.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openpype/hosts/nuke/api/gizmo_menu.py b/openpype/hosts/nuke/api/gizmo_menu.py index a541fd3ab1..dd04f4a42e 100644 --- a/openpype/hosts/nuke/api/gizmo_menu.py +++ b/openpype/hosts/nuke/api/gizmo_menu.py @@ -26,11 +26,6 @@ class GizmoMenu(): command = str(config['command']) - if command.find('{pipe_path}') > -1: - command = command.format( - pipe_path=os.environ['QUAD_PLUGIN_PATH'] - ) - icon = config.get('icon', None) if icon: try: From 7d3b76cc0a1d9856bfd2a0479600d867412b133c Mon Sep 17 00:00:00 2001 From: DMO Date: Thu, 2 Jun 2022 09:50:53 +0900 Subject: [PATCH 0447/1227] Removed lib for hound. --- openpype/hosts/maya/plugins/create/create_multiverse_look.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_look.py b/openpype/hosts/maya/plugins/create/create_multiverse_look.py index 3fc834a700..f47c88a93b 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_look.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_look.py @@ -1,4 +1,4 @@ -from openpype.hosts.maya.api import plugin, lib +from openpype.hosts.maya.api import plugin class CreateMultiverseLook(plugin.Creator): From 85748589ef4cdbda6ad65c59c3130ad2901dc0b4 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 2 Jun 2022 11:18:02 +0200 Subject: [PATCH 0448/1227] :bug: skip empty attributes --- openpype/hosts/maya/plugins/publish/collect_look.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 323bede761..9b6d1d999c 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -601,6 +601,9 @@ class CollectLook(pyblish.api.InstancePlugin): source, computed_source)) + if not source: + self.log.info("source is empty, skipping...") + continue # We replace backslashes with forward slashes because V-Ray # can't handle the UDIM files with the backslashes in the # paths as the computed patterns From 73b492c1fff78f4ece0e63dfc9bb0c83189ebe47 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 2 Jun 2022 15:53:40 +0200 Subject: [PATCH 0449/1227] Flame: adding `OPENPYPE_TEMP_DIR` to extractor --- .../publish/extract_subset_resources.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 0bad3f7cfc..f3239af23e 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -1,5 +1,6 @@ import os import re +import tempfile from pprint import pformat from copy import deepcopy @@ -420,3 +421,28 @@ class ExtractSubsetResources(openpype.api.Extractor): "Path `{}` is containing more that one clip".format(path) ) return clips[0] + + def staging_dir(self, instance): + """Provide a temporary directory in which to store extracted files + + Upon calling this method the staging directory is stored inside + the instance.data['stagingDir'] + """ + staging_dir = instance.data.get('stagingDir', None) + openpype_temp_dir = os.getenv("OPENPYPE_TEMP_DIR") + + if not staging_dir: + if openpype_temp_dir and os.path.exists(openpype_temp_dir): + staging_dir = os.path.normpath( + tempfile.mkdtemp( + prefix="pyblish_tmp_", + dir=openpype_temp_dir + ) + ) + else: + staging_dir = os.path.normpath( + tempfile.mkdtemp(prefix="pyblish_tmp_") + ) + instance.data['stagingDir'] = staging_dir + + return staging_dir From 2339e41b4c32afcbd4eb5d1a6d8ea2cadd58f057 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 10:19:22 +0200 Subject: [PATCH 0450/1227] Fix - removed unnecessary initialization --- openpype/modules/sync_server/sync_server_module.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 5a1d8467ec..bd75172cc6 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -1026,9 +1026,6 @@ class SyncServerModule(OpenPypeModule, ITrayModule): """ self.server_init() - from .tray.app import SyncServerWindow - self.widget = SyncServerWindow(self) - def server_init(self): """Actual initialization of Sync Server.""" # import only in tray or Python3, because of Python2 hosts From 688400ad7b7065f6db5322dd480bfe352cc2f62d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 10:20:33 +0200 Subject: [PATCH 0451/1227] Fix - removed unnecessary first query setSortable forces refresh itself --- openpype/modules/sync_server/tray/models.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index 7241cc3472..d3d36cdfd4 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -523,10 +523,6 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): self.query = self.get_query() self.default_query = list(self.get_query()) - representations = self.dbcon.aggregate(pipeline=self.query, - allowDiskUse=True) - self.refresh(representations) - self.timer = QtCore.QTimer() self.timer.timeout.connect(self.tick) self.timer.start(self.REFRESH_SEC) @@ -1003,9 +999,6 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): self.sort_criteria = self.DEFAULT_SORT self.query = self.get_query() - representations = self.dbcon.aggregate(pipeline=self.query, - allowDiskUse=True) - self.refresh(representations) self.timer = QtCore.QTimer() self.timer.timeout.connect(self.tick) From 81a51696804915888daeeb42cf0b1d5f4c92e538 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 11:08:52 +0200 Subject: [PATCH 0452/1227] Fix - faster loop logic --- openpype/modules/sync_server/sync_server.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 22eed01ef3..808d703616 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -282,10 +282,9 @@ class SyncServerThread(threading.Thread): import time start_time = None self.module.set_sync_project_settings() # clean cache - for collection, preset in self.module.sync_project_settings.\ - items(): - if collection not in self.module.get_enabled_projects(): - continue + enabled_projects = self.module.get_enabled_projects() + for collection in enabled_projects: + preset = self.module.sync_project_settings["collection"] start_time = time.time() local_site, remote_site = self._working_sites(collection) From 3c285d859a454107278470be43d00c205a0233e1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 12:34:21 +0200 Subject: [PATCH 0453/1227] Fix - fixes Do creation of settings only in Thread as it is expensive operation. --- openpype/modules/sync_server/sync_server.py | 6 +++--- openpype/modules/sync_server/sync_server_module.py | 12 +++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 808d703616..356a75f99d 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -280,13 +280,13 @@ class SyncServerThread(threading.Thread): while self.is_running and not self.module.is_paused(): try: import time - start_time = None + start_time = time.time() self.module.set_sync_project_settings() # clean cache + collection = None enabled_projects = self.module.get_enabled_projects() for collection in enabled_projects: - preset = self.module.sync_project_settings["collection"] + preset = self.module.sync_project_settings[collection] - start_time = time.time() local_site, remote_site = self._working_sites(collection) if not all([local_site, remote_site]): continue diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index bd75172cc6..7f541d52e3 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -1029,15 +1029,10 @@ class SyncServerModule(OpenPypeModule, ITrayModule): def server_init(self): """Actual initialization of Sync Server.""" # import only in tray or Python3, because of Python2 hosts - from .sync_server import SyncServerThread - if not self.enabled: return - enabled_projects = self.get_enabled_projects() - if not enabled_projects: - self.enabled = False - return + from .sync_server import SyncServerThread self.lock = threading.Lock() @@ -1057,7 +1052,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): self.server_start() def server_start(self): - if self.sync_project_settings and self.enabled: + if self.enabled: self.sync_server_thread.start() else: log.info("No presets or active providers. " + @@ -1848,6 +1843,9 @@ class SyncServerModule(OpenPypeModule, ITrayModule): Returns: (int): in seconds """ + if not project_name: + return 60 + ld = self.sync_project_settings[project_name]["config"]["loop_delay"] return int(ld) From 96115fac6ed2addf4908e0aacaf9d3d2dc77ad12 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 12:36:18 +0200 Subject: [PATCH 0454/1227] Fix - remove reset of Settings Let only thread do it, expensive operation potentially. --- openpype/modules/sync_server/tray/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index d3d36cdfd4..6b309312a2 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -378,7 +378,6 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): project (str): name of project """ self._project = project - self.sync_server.set_sync_project_settings() # project might have been deactivated in the meantime if not self.sync_server.get_sync_project_setting(project): return From 7afa319b25ce94cb3cb64b1c050ad23eeb1cd873 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 3 Jun 2022 12:54:27 +0200 Subject: [PATCH 0455/1227] add subdir 'bin' when oiio path is prepared --- openpype/lib/vendor_bin_utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/lib/vendor_bin_utils.py b/openpype/lib/vendor_bin_utils.py index 23e28ea304..e5ab2872a0 100644 --- a/openpype/lib/vendor_bin_utils.py +++ b/openpype/lib/vendor_bin_utils.py @@ -116,7 +116,10 @@ def get_oiio_tools_path(tool="oiiotool"): tool (string): Tool name (oiiotool, maketx, ...). Default is "oiiotool". """ + oiio_dir = get_vendor_bin_path("oiio") + if platform.system().lower() == "linux": + oiio_dir = os.path.join(oiio_dir, "bin") return find_executable(os.path.join(oiio_dir, tool)) From 0034d7495cebc18f0cee2466d1109d69cf52a234 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 3 Jun 2022 12:14:23 +0200 Subject: [PATCH 0456/1227] Hiero: add support for task tags and collecting tags in general --- openpype/hosts/hiero/api/__init__.py | 2 ++ openpype/hosts/hiero/api/lib.py | 25 +++++++++++++++++++ .../collect_tag_tasks.py | 6 ++--- .../plugins/publish/precollect_instances.py | 5 +++- 4 files changed, 34 insertions(+), 4 deletions(-) rename openpype/hosts/hiero/plugins/{publish_old_workflow => publish}/collect_tag_tasks.py (91%) diff --git a/openpype/hosts/hiero/api/__init__.py b/openpype/hosts/hiero/api/__init__.py index fc2d017f04..781f846bbe 100644 --- a/openpype/hosts/hiero/api/__init__.py +++ b/openpype/hosts/hiero/api/__init__.py @@ -29,6 +29,7 @@ from .lib import ( get_current_sequence, get_timeline_selection, get_current_track, + get_track_item_tags, get_track_item_pype_tag, set_track_item_pype_tag, get_track_item_pype_data, @@ -83,6 +84,7 @@ __all__ = [ "get_current_sequence", "get_timeline_selection", "get_current_track", + "get_track_item_tags", "get_track_item_pype_tag", "set_track_item_pype_tag", "get_track_item_pype_data", diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 761a36bd0f..06dfd2f2ee 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -274,6 +274,31 @@ def _validate_all_atrributes( ]) +def get_track_item_tags(track_item): + """ + Get track item tags excluded openpype tag + + Attributes: + trackItem (hiero.core.TrackItem): hiero object + + Returns: + hiero.core.Tag: hierarchy, orig clip attributes + """ + returning_tag_data = [] + # get all tags from track item + _tags = track_item.tags() + if not _tags: + return [] + + # collect all tags which are not openpype tag + returning_tag_data.extend( + tag for tag in _tags + if tag.name() != self.pype_tag_name + ) + + return returning_tag_data + + def get_track_item_pype_tag(track_item): """ Get pype track item tag created by creator or loader plugin. diff --git a/openpype/hosts/hiero/plugins/publish_old_workflow/collect_tag_tasks.py b/openpype/hosts/hiero/plugins/publish/collect_tag_tasks.py similarity index 91% rename from openpype/hosts/hiero/plugins/publish_old_workflow/collect_tag_tasks.py rename to openpype/hosts/hiero/plugins/publish/collect_tag_tasks.py index 70891d5b45..27968060e1 100644 --- a/openpype/hosts/hiero/plugins/publish_old_workflow/collect_tag_tasks.py +++ b/openpype/hosts/hiero/plugins/publish/collect_tag_tasks.py @@ -4,16 +4,16 @@ from pyblish import api class CollectClipTagTasks(api.InstancePlugin): """Collect Tags from selected track items.""" - order = api.CollectorOrder + order = api.CollectorOrder - 0.077 label = "Collect Tag Tasks" hosts = ["hiero"] - families = ['clip'] + families = ["shot"] def process(self, instance): # gets tags tags = instance.data["tags"] - tasks = dict() + tasks = {} for tag in tags: t_metadata = dict(tag.metadata()) t_family = t_metadata.get("tag.family", "") diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index e54d050f0d..b891a37d9d 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -106,7 +106,10 @@ class PrecollectInstances(pyblish.api.ContextPlugin): # clip's effect "clipEffectItems": subtracks, - "clipAnnotations": annotations + "clipAnnotations": annotations, + + # add all additional tags + "tags": phiero.get_track_item_tags(track_item) }) # otio clip data From 3464a7c5d7bceb1174237aeecc25e9d12c178638 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 3 Jun 2022 13:48:41 +0200 Subject: [PATCH 0457/1227] global: hierarchy publishing only to active instances filter --- .../publish/integrate_hierarchy_ftrack.py | 47 +++++++++++-- openpype/plugins/publish/collect_hierarchy.py | 4 -- .../publish/extract_hierarchy_avalon.py | 70 +++++++++---------- 3 files changed, 76 insertions(+), 45 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index cf90c11b65..73398941eb 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -2,7 +2,7 @@ import sys import collections import six import pyblish.api - +from copy import deepcopy from openpype.pipeline import legacy_io # Copy of constant `openpype_modules.ftrack.lib.avalon_sync.CUST_ATTR_AUTO_SYNC` @@ -72,7 +72,8 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): if "hierarchyContext" not in self.context.data: return - hierarchy_context = self.context.data["hierarchyContext"] + hierarchy_context = self._get_active_assets(context) + self.log.debug("__ hierarchy_context: {}".format(hierarchy_context)) self.session = self.context.data["ftrackSession"] project_name = self.context.data["projectEntity"]["name"] @@ -86,15 +87,13 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): self.ft_project = None - input_data = hierarchy_context - # disable termporarily ftrack project's autosyncing if auto_sync_state: self.auto_sync_off(project) try: # import ftrack hierarchy - self.import_to_ftrack(input_data) + self.import_to_ftrack(hierarchy_context) except Exception: raise finally: @@ -355,3 +354,41 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): self.session.rollback() self.session._configure_locations() six.reraise(tp, value, tb) + + def _get_active_assets(self, context): + """ Returns only asset dictionary. + Usually the last part of deep dictionary which + is not having any children + """ + def get_pure_hierarchy_data(input_dict): + input_dict_copy = deepcopy(input_dict) + for key in input_dict.keys(): + self.log.debug("__ key: {}".format(key)) + # check if child key is available + if input_dict[key].get("childs"): + # loop deeper + input_dict_copy[ + key]["childs"] = get_pure_hierarchy_data( + input_dict[key]["childs"]) + elif key not in active_assets: + input_dict_copy.pop(key, None) + return input_dict_copy + + hierarchy_context = context.data["hierarchyContext"] + + active_assets = [] + # filter only the active publishing insatnces + for instance in context: + if instance.data.get("publish") is False: + continue + + if not instance.data.get("asset"): + continue + + active_assets.append(instance.data["asset"]) + + # remove duplicity in list + active_assets = list(set(active_assets)) + self.log.debug("__ active_assets: {}".format(active_assets)) + + return get_pure_hierarchy_data(hierarchy_context) diff --git a/openpype/plugins/publish/collect_hierarchy.py b/openpype/plugins/publish/collect_hierarchy.py index 8398a2815a..91d5162d62 100644 --- a/openpype/plugins/publish/collect_hierarchy.py +++ b/openpype/plugins/publish/collect_hierarchy.py @@ -33,10 +33,6 @@ class CollectHierarchy(pyblish.api.ContextPlugin): family = instance.data["family"] families = instance.data["families"] - # filter out all unepropriate instances - if not instance.data["publish"]: - continue - # exclude other families then self.families with intersection if not set(self.families).intersection(set(families + [family])): continue diff --git a/openpype/plugins/publish/extract_hierarchy_avalon.py b/openpype/plugins/publish/extract_hierarchy_avalon.py index 2f528d4469..1f7ce839ed 100644 --- a/openpype/plugins/publish/extract_hierarchy_avalon.py +++ b/openpype/plugins/publish/extract_hierarchy_avalon.py @@ -1,7 +1,5 @@ from copy import deepcopy - import pyblish.api - from openpype.pipeline import legacy_io @@ -17,33 +15,16 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): if "hierarchyContext" not in context.data: self.log.info("skipping IntegrateHierarchyToAvalon") return - hierarchy_context = deepcopy(context.data["hierarchyContext"]) if not legacy_io.Session: legacy_io.install() - active_assets = [] - # filter only the active publishing insatnces - for instance in context: - if instance.data.get("publish") is False: - continue - - if not instance.data.get("asset"): - continue - - active_assets.append(instance.data["asset"]) - - # remove duplicity in list - self.active_assets = list(set(active_assets)) - self.log.debug("__ self.active_assets: {}".format(self.active_assets)) - - hierarchy_context = self._get_assets(hierarchy_context) - + hierarchy_context = self._get_active_assets(context) self.log.debug("__ hierarchy_context: {}".format(hierarchy_context)) - input_data = context.data["hierarchyContext"] = hierarchy_context self.project = None - self.import_to_avalon(input_data) + self.import_to_avalon(hierarchy_context) + def import_to_avalon(self, input_data, parent=None): for name in input_data: @@ -183,23 +164,40 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): return legacy_io.find_one({"_id": entity_id}) - def _get_assets(self, input_dict): + def _get_active_assets(self, context): """ Returns only asset dictionary. Usually the last part of deep dictionary which is not having any children """ - input_dict_copy = deepcopy(input_dict) - - for key in input_dict.keys(): - self.log.debug("__ key: {}".format(key)) - # check if child key is available - if input_dict[key].get("childs"): - # loop deeper - input_dict_copy[key]["childs"] = self._get_assets( - input_dict[key]["childs"]) - else: - # filter out unwanted assets - if key not in self.active_assets: + def get_pure_hierarchy_data(input_dict): + input_dict_copy = deepcopy(input_dict) + for key in input_dict.keys(): + self.log.debug("__ key: {}".format(key)) + # check if child key is available + if input_dict[key].get("childs"): + # loop deeper + input_dict_copy[ + key]["childs"] = get_pure_hierarchy_data( + input_dict[key]["childs"]) + elif key not in active_assets: input_dict_copy.pop(key, None) + return input_dict_copy - return input_dict_copy + hierarchy_context = context.data["hierarchyContext"] + + active_assets = [] + # filter only the active publishing insatnces + for instance in context: + if instance.data.get("publish") is False: + continue + + if not instance.data.get("asset"): + continue + + active_assets.append(instance.data["asset"]) + + # remove duplicity in list + active_assets = list(set(active_assets)) + self.log.debug("__ active_assets: {}".format(active_assets)) + + return get_pure_hierarchy_data(hierarchy_context) From d86c71c15bc00d31c5459b3d75a28fcd70813696 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 13:23:51 +0200 Subject: [PATCH 0458/1227] Fix - added disabled icon to Site Queue --- .../sync_server/resources/disabled.png | Bin 0 -> 2368 bytes .../modules/sync_server/sync_server_module.py | 17 +++++++-- openpype/modules/sync_server/tray/widgets.py | 34 ++++++++++++------ openpype/tools/loader/model.py | 2 +- openpype/tools/loader/widgets.py | 14 ++++---- 5 files changed, 48 insertions(+), 19 deletions(-) create mode 100644 openpype/modules/sync_server/resources/disabled.png diff --git a/openpype/modules/sync_server/resources/disabled.png b/openpype/modules/sync_server/resources/disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..e036d7ef6a12a353551ddfc3e8a4e099c6506dd7 GIT binary patch literal 2368 zcmb_e2~ZPP7>*VZ@L-&JptjTPQmu-cJ+dU(EfJBRpoYW5gAP_+c3;R!k`38Kf-}}? zwXI5VMkdyC#wymMmU>MOoz_|qi=#}bPFwG)^{8UedNAtX^d*EyJ6_XiW;Xlwz3+Yh z_y6yEZ+B5{_PAbggW@zAO|Ps>^CWb~f>%rw`aiDhoQrO;-pqVOqv>52yt)LEdJooU zqW4I)Y3ek~L|%~HS|G}HsP((O2(8hir24%;D26I#heeXdgtzS7hhvgx!lx!%D2q28 zI;G6nK4_htZ4+h}3!I3jj>1y>JR)#I6<~h1%cJmq6CUE_Q9Ia8;#dfx7Mt+2U_fk| zB^OJVeGoHfX+oeV3Nvt8gB>^o2Q?C7D266!illV}Me!`nGe#`@!%;Y&=-?-r$A*KU zCll^eRWDDHWo2dBGM!fT6_GT@aU{i%3_~CUp_F@6;3qsvVh4j6DuPe)s*>!%f{ef} zm#QWlNo~);?TyfSlyIF;#YjK!lC+iz<`e>oLIme6^|?a9MS+Aa=!PCuL0CG1^*Uu$ zR-Ez^s3XJw2!Lv9u|#4#R*Tym38APNvrro0f;<+j*vh?-oCFoQ)F;4Vms*6^VM4mJ1 zj4Z7;5PE}7AoLEFC4imd2)&rhGLY37jSzOsH_Jk4aP@=p+joK}3y9;9GCg2)z;1L9 zMnOjtw1IUH00NGHc88Is05H-t*TI(Klh9@auFkAMRU%?E>h(HEB|8YwkjxMQU?BmF zMuDJcgWdpjlwPpwf{qWFh|iQ1w7livJ!FL?;g(CnLcZaF5Zn(YTnHKgig>tNdQ3f@ zD{^>#nG+(?rx>I?x*|K>?)0MW}YqG$8pT@d}vrbm?~5ZlLWtKmaz`NH81=2)md} z6Oa-OqR3KugC3c-T~lZ2oeWDOZ?_xvzeo>r3r^rEg6MQ5@&BI8^ZD|r$voP9|6ej> z@JwxY82NAWg~mFazi1&tUFc8`HY2YS^bmO=K@X~v54~=fpA$+nnyw?W%xSjFgQo+j z#eMVo9yr_BJaq3H#T88puo3~=xOSm)L;sV~h?_|<%bVg0x0!8G`w!({>(>Q-YgxB! z%J%I$UL4#lf8x%DXhHvr-BEVwdh=@wr!=0UYf~D(ZvFPbWlovfz4eut>i7f0h9vAT z%&&?5NS#2I?>9}a9upWexqfqQ=Yx5RN8LXLcoToso*=KRZtk&Q z&-%~urkI+qbbn6@46Hwo+3)9lQ#GJ;-sqg^1M3H0+R{6U-4gFVPS%~lzS4hr!Rj@d zG9~8vPf5#f^h~jKyS(D#Yi(zaFa2|0emyt6KIg=|yGJ)BXH;HvT{=GCudQiAk4gvc ztP#3@;D4*2bj{?v`*)WGKCjzt)fzKOJj$!Lf4$iH@b&rhb?>P{^Szl%i7(!deS7aM zzNT(;%PvkkVpGA2;=^|)aOd+1AhR#AZTa1q=XN!UtG86&n!S1Qi4(cwUglj+>*}kw z_eWoQv&fQq?|bERwfrnM z>C9Ms=!$h|YexhgCj3!b@GvemCN?%ErmBVMcW6QF3v+glZdz;el5Owqy%~SyllUg~ T){1$-Z=9@*Y;(<+!ufv#nIl6P literal 0 HcmV?d00001 diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 7f541d52e3..698b296a52 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -926,9 +926,22 @@ class SyncServerModule(OpenPypeModule, ITrayModule): return enabled_projects - def is_project_enabled(self, project_name): + def is_project_enabled(self, project_name, single=False): + """Checks if 'project_name' is enabled for syncing. + 'get_sync_project_setting' is potentially expensive operation (pulls + settings for all projects if cached version is not available), using + project_settings for specific project should be faster. + Args: + project_name (str) + single (bool): use 'get_project_settings' method + """ if self.enabled: - project_settings = self.get_sync_project_setting(project_name) + if single: + project_settings = get_project_settings(project_name) + project_settings = \ + self._parse_sync_settings_from_settings(project_settings) + else: + project_settings = self.get_sync_project_setting(project_name) if project_settings and project_settings.get("enabled"): return True return False diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 6aae9562cf..18de4d311d 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -122,11 +122,13 @@ class SyncProjectListWidget(QtWidgets.QWidget): self._model_reset = False selected_item = None - for project_name in self.sync_server.sync_project_settings.\ - keys(): + sync_settings = self.sync_server.sync_project_settings + for project_name in sync_settings.keys(): if self.sync_server.is_paused() or \ self.sync_server.is_project_paused(project_name): icon = self._get_icon("paused") + elif not sync_settings["enabled"]: + icon = self._get_icon("disabled") else: icon = self._get_icon("synced") @@ -578,10 +580,11 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): ) def __init__(self, sync_server, project=None, parent=None): + import time + log.info("SyncRepresentationSummaryWidget start") super(SyncRepresentationSummaryWidget, self).__init__(parent) self.sync_server = sync_server - self._selected_ids = set() # keep last selected _id txt_filter = QtWidgets.QLineEdit() @@ -600,8 +603,11 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): table_view = QtWidgets.QTableView() headers = [item[0] for item in self.default_widths] + start_time = time.time() model = SyncRepresentationSummaryModel(sync_server, headers, project, parent=self) + log.info("SyncRepresentationSummaryModel:: {}".format(time.time() - start_time)) + start_time = time.time() table_view.setModel(model) table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) table_view.setSelectionMode( @@ -625,7 +631,8 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): column = table_view.model().get_header_index("priority") priority_delegate = delegates.PriorityDelegate(self) table_view.setItemDelegateForColumn(column, priority_delegate) - + log.info("SyncRepresentationSummaryWidget.2:: {}".format(time.time() - start_time)) + start_time = time.time() layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addLayout(top_bar_layout) @@ -633,27 +640,35 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): self.table_view = table_view self.model = model - + log.info("SyncRepresentationSummaryWidget.3:: {}".format(time.time() - start_time)) + start_time = time.time() horizontal_header = HorizontalHeader(self) - + log.info("SyncRepresentationSummaryWidget.4:: {}".format(time.time() - start_time)) + start_time = time.time() table_view.setHorizontalHeader(horizontal_header) + log.info("SyncRepresentationSummaryWidget.4.1:: {}".format(time.time() - start_time)) + start_time = time.time() table_view.setSortingEnabled(True) - + log.info("SyncRepresentationSummaryWidget.5:: {}".format(time.time() - start_time)) + start_time = time.time() for column_name, width in self.default_widths: idx = model.get_header_index(column_name) table_view.setColumnWidth(idx, width) - + log.info("SyncRepresentationSummaryWidget.6:: {}".format(time.time() - start_time)) + start_time = time.time() table_view.doubleClicked.connect(self._double_clicked) self.txt_filter.textChanged.connect(lambda: model.set_word_filter( self.txt_filter.text())) table_view.customContextMenuRequested.connect(self._on_context_menu) - + log.info("SyncRepresentationSummaryWidget.7:: {}".format(time.time() - start_time)) + start_time = time.time() model.refresh_started.connect(self._save_scrollbar) model.refresh_finished.connect(self._set_scrollbar) model.modelReset.connect(self._set_selection) self.selection_model = self.table_view.selectionModel() self.selection_model.selectionChanged.connect(self._selection_changed) + log.info("SyncRepresentationSummaryWidget.end:: {}".format(time.time() - start_time)) def _prepare_menu(self, local_progress, remote_progress, is_multi, can_edit, status=None): @@ -963,7 +978,6 @@ class HorizontalHeader(QtWidgets.QHeaderView): super(HorizontalHeader, self).__init__(QtCore.Qt.Horizontal, parent) self._parent = parent self.checked_values = {} - self.setModel(self._parent.model) self.setSectionsClickable(True) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 6f39c428be..e6bef0a33a 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -89,7 +89,7 @@ class BaseRepresentationModel(object): self._last_manager_cache = now_time sync_server = self._modules_manager.modules_by_name["sync_server"] - if sync_server.is_project_enabled(project_name): + if sync_server.is_project_enabled(project_name, single=True): active_site = sync_server.get_active_site(project_name) active_provider = sync_server.get_provider_for_site( project_name, active_site) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 42fb62b632..5764085b6a 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -356,9 +356,10 @@ class SubsetWidget(QtWidgets.QWidget): enabled = False if project_name: self.model.reset_sync_server(project_name) - if self.model.sync_server: - enabled_proj = self.model.sync_server.get_enabled_projects() - enabled = project_name in enabled_proj + sync_server = self.model.sync_server + if sync_server: + enabled = sync_server.is_project_enabled(project_name, + single=True) lib.change_visibility(self.model, self.view, "repre_info", enabled) @@ -1228,9 +1229,10 @@ class RepresentationWidget(QtWidgets.QWidget): enabled = False if project_name: self.model.reset_sync_server(project_name) - if self.model.sync_server: - enabled_proj = self.model.sync_server.get_enabled_projects() - enabled = project_name in enabled_proj + sync_server = self.model.sync_server + if sync_server: + enabled = sync_server.is_project_enabled(project_name, + single=True) self.sync_server_enabled = enabled lib.change_visibility(self.model, self.tree_view, From 60bef01f6cd9a4f1233fcebf655254d52a8e3f04 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 14:18:58 +0200 Subject: [PATCH 0459/1227] Fix - removed unwanted logs --- openpype/modules/sync_server/tray/widgets.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 18de4d311d..049a3f0127 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -580,8 +580,6 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): ) def __init__(self, sync_server, project=None, parent=None): - import time - log.info("SyncRepresentationSummaryWidget start") super(SyncRepresentationSummaryWidget, self).__init__(parent) self.sync_server = sync_server @@ -603,11 +601,8 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): table_view = QtWidgets.QTableView() headers = [item[0] for item in self.default_widths] - start_time = time.time() model = SyncRepresentationSummaryModel(sync_server, headers, project, parent=self) - log.info("SyncRepresentationSummaryModel:: {}".format(time.time() - start_time)) - start_time = time.time() table_view.setModel(model) table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) table_view.setSelectionMode( @@ -631,8 +626,6 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): column = table_view.model().get_header_index("priority") priority_delegate = delegates.PriorityDelegate(self) table_view.setItemDelegateForColumn(column, priority_delegate) - log.info("SyncRepresentationSummaryWidget.2:: {}".format(time.time() - start_time)) - start_time = time.time() layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addLayout(top_bar_layout) @@ -640,35 +633,22 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): self.table_view = table_view self.model = model - log.info("SyncRepresentationSummaryWidget.3:: {}".format(time.time() - start_time)) - start_time = time.time() horizontal_header = HorizontalHeader(self) - log.info("SyncRepresentationSummaryWidget.4:: {}".format(time.time() - start_time)) - start_time = time.time() table_view.setHorizontalHeader(horizontal_header) - log.info("SyncRepresentationSummaryWidget.4.1:: {}".format(time.time() - start_time)) - start_time = time.time() table_view.setSortingEnabled(True) - log.info("SyncRepresentationSummaryWidget.5:: {}".format(time.time() - start_time)) - start_time = time.time() for column_name, width in self.default_widths: idx = model.get_header_index(column_name) table_view.setColumnWidth(idx, width) - log.info("SyncRepresentationSummaryWidget.6:: {}".format(time.time() - start_time)) - start_time = time.time() table_view.doubleClicked.connect(self._double_clicked) self.txt_filter.textChanged.connect(lambda: model.set_word_filter( self.txt_filter.text())) table_view.customContextMenuRequested.connect(self._on_context_menu) - log.info("SyncRepresentationSummaryWidget.7:: {}".format(time.time() - start_time)) - start_time = time.time() model.refresh_started.connect(self._save_scrollbar) model.refresh_finished.connect(self._set_scrollbar) model.modelReset.connect(self._set_selection) self.selection_model = self.table_view.selectionModel() self.selection_model.selectionChanged.connect(self._selection_changed) - log.info("SyncRepresentationSummaryWidget.end:: {}".format(time.time() - start_time)) def _prepare_menu(self, local_progress, remote_progress, is_multi, can_edit, status=None): From a9fdcd80aaee0db6dddc0e777e9dd021459f04c2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 14:44:11 +0200 Subject: [PATCH 0460/1227] OP-3231 - return only active projects in webpublisher ProjectsEndpoing --- .../webserver_service/webpublish_routes.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index e82ba7f2b8..70324fc39c 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -71,16 +71,12 @@ class ProjectsEndpoint(_RestApiEndpoint): """Returns list of dict with project info (id, name).""" async def get(self) -> Response: output = [] - for project_name in self.dbcon.database.collection_names(): - project_doc = self.dbcon.database[project_name].find_one({ - "type": "project" - }) - if project_doc: - ret_val = { - "id": project_doc["_id"], - "name": project_doc["name"] - } - output.append(ret_val) + for project_doc in self.dbcon.projects(): + ret_val = { + "id": project_doc["_id"], + "name": project_doc["name"] + } + output.append(ret_val) return Response( status=200, body=self.resource.encode(output), From 98504a205210471242f0c955d3f549561ec392df Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 3 Jun 2022 14:45:05 +0200 Subject: [PATCH 0461/1227] implemented action that can tranfer values of 1 hierarchical attribute to another --- .../action_translate_hierarchical_values.py | 331 ++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 openpype/modules/ftrack/event_handlers_server/action_translate_hierarchical_values.py diff --git a/openpype/modules/ftrack/event_handlers_server/action_translate_hierarchical_values.py b/openpype/modules/ftrack/event_handlers_server/action_translate_hierarchical_values.py new file mode 100644 index 0000000000..fd10005fad --- /dev/null +++ b/openpype/modules/ftrack/event_handlers_server/action_translate_hierarchical_values.py @@ -0,0 +1,331 @@ +import copy +import json +import collections + +import ftrack_api + +from openpype_modules.ftrack.lib import ( + ServerAction, + statics_icon, +) +from openpype_modules.ftrack.lib.avalon_sync import create_chunks + + +class TranslateHierarchicalValues(ServerAction): + """Transfer values across hierarhcical attributes. + + Aalso gives ability to convert types meanwhile. That is limited to + conversions between numbers and strings + - int <-> float + - in, float -> string + """ + + identifier = "translate.hierarchical.values" + label = "OpenPype Admin" + variant = "- Translate values between 2 custom attributes" + description = ( + "Move values from a hierarchical attribute to" + " second hierarchical attribute." + ) + icon = statics_icon("ftrack", "action_icons", "OpenPypeAdmin.svg") + + all_project_entities_query = ( + "select id, name, parent_id, link" + " from TypedContext where project_id is \"{}\"" + ) + cust_attr_query = ( + "select value, entity_id from CustomAttributeValue" + " where entity_id in ({}) and configuration_id is \"{}\"" + ) + settings_key = "clean_hierarchical_attr" + + def discover(self, session, entities, event): + """Show anywhere.""" + + return self.valid_roles(session, entities, event) + + def _selection_interface(self, session, event_values=None): + title = "Translate hierarchical values" + + attr_confs = session.query( + ( + "select id, key from CustomAttributeConfiguration" + " where is_hierarchical is true" + ) + ).all() + attr_items = [] + for attr_conf in attr_confs: + attr_items.append({ + "value": attr_conf["id"], + "label": attr_conf["key"] + }) + + if len(attr_items) < 2: + return { + "title": title, + "items": [{ + "type": "label", + "value": ( + "Didn't found custom attributes" + " that can be translated." + ) + }] + } + + attr_items = sorted(attr_items, key=lambda item: item["label"]) + items = [] + item_splitter = {"type": "label", "value": "---"} + items.append({ + "type": "label", + "value": ( + "

Please select source and destination" + " Custom attribute

" + ) + }) + items.append({ + "type": "label", + "value": ( + "WARNING: This will take affect for all projects!" + ) + }) + if event_values: + items.append({ + "type": "label", + "value": ( + "Note: Please select 2 different custom attributes." + ) + }) + + items.append(item_splitter) + + src_item = { + "type": "enumerator", + "label": "Source", + "name": "src_attr_id", + "data": copy.deepcopy(attr_items) + } + dst_item = { + "type": "enumerator", + "label": "Destination", + "name": "dst_attr_id", + "data": copy.deepcopy(attr_items) + } + delete_item = { + "type": "boolean", + "name": "delete_dst_attr_first", + "label": "Delete first", + "value": False + } + if event_values: + src_item["value"] = event_values["src_attr_id"] + dst_item["value"] = event_values["dst_attr_id"] + delete_item["value"] = event_values["delete_dst_attr_first"] + + items.append(src_item) + items.append(dst_item) + items.append(item_splitter) + items.append({ + "type": "label", + "value": ( + "WARNING: All values from destination" + " Custom Attribute will be removed if this is enabled." + ) + }) + items.append(delete_item) + + return { + "title": title, + "items": items + } + + def interface(self, session, entities, event): + if event["data"].get("values", {}): + return None + + return self._selection_interface(session) + + def launch(self, session, entities, event): + values = event["data"].get("values", {}) + if not values: + return None + src_attr_id = values["src_attr_id"] + dst_attr_id = values["dst_attr_id"] + delete_dst_values = values["delete_dst_attr_first"] + + if not src_attr_id or not dst_attr_id: + return { + "success": True, + "message": "Nothing to do" + } + + if src_attr_id == dst_attr_id: + return self._selection_interface(session, values) + + # Query custom attrbutes + src_conf = session.query(( + "select id from CustomAttributeConfiguration where id is {}" + ).format(src_attr_id)).one() + dst_conf = session.query(( + "select id from CustomAttributeConfiguration where id is {}" + ).format(dst_attr_id)).one() + src_type_name = src_conf["type"]["name"] + dst_type_name = dst_conf["type"]["name"] + # Limit conversion to + # - same type -> same type (there is no need to do conversion) + # - number -> number (int to float and back) + # - number -> str (any number can be converted to str) + src_type = None + dst_type = None + if src_type_name == "number" or src_type_name != dst_type_name: + src_type = self._get_attr_type(dst_conf) + dst_type = self._get_attr_type(dst_conf) + valid = False + # Can convert numbers + if src_type in (int, float) and dst_type in (int, float): + valid = True + # Can convert numbers to string + elif dst_type is str: + valid = True + + if not valid: + return { + "message": ( + "Don't know how to properly convert" + " custom attribute types {} > {}" + ).format(src_type_name, dst_type_name), + "success": False + } + + # Query source values + src_attr_values = session.query( + ( + "select value, entity_id" + " from CustomAttributeValue" + " where configuration_id is {}" + ).format(src_attr_id) + ).all() + + value_by_id = {} + failed_entity_ids = [] + for attr_value in src_attr_values: + entity_id = attr_value["entity_id"] + value = attr_value["value"] + if value is not None: + try: + if dst_type is not None: + value = dst_type(value) + value_by_id[entity_id] = value + except Exception: + failed_entity_ids.append(entity_id) + + if failed_entity_ids: + return { + "success": False, + "message": ( + "Couldn't convert some values to destination attribute" + ) + } + + + # Delete destination custom attributes first + if delete_dst_values: + self.log.info("Deleting destination custom attribute values first") + self._delete_custom_attribute_values(session, dst_attr_id) + + self._apply_values(session, value_by_id, dst_attr_id) + return True + + def _delete_custom_attribute_values(self, session, dst_attr_id): + dst_attr_values = session.query( + ( + "select configuration_id, entity_id" + " from CustomAttributeValue" + " where configuration_id is {}" + ).format(dst_attr_id) + ).all() + delete_operations = [] + for attr_value in dst_attr_values: + entity_id = attr_value["entity_id"] + configuration_id = attr_value["configuration_id"] + entity_key = collections.OrderedDict(( + ("configuration_id", configuration_id), + ("entity_id", entity_id) + )) + delete_operations.append( + ftrack_api.operation.DeleteEntityOperation( + "CustomAttributeValue", + entity_key + ) + ) + + if not delete_operations: + return + + for chunk in create_chunks(delete_operations, 500): + for operation in chunk: + session.recorded_operations.push(operation) + session.commit() + + def _apply_values(self, session, value_by_id, dst_attr_id): + dst_attr_values = session.query( + ( + "select configuration_id, entity_id" + " from CustomAttributeValue" + " where configuration_id is {}" + ).format(dst_attr_id) + ).all() + + dst_entity_ids_with_value = { + item["entity_id"] + for item in dst_attr_values + } + operations = [] + for entity_id, value in value_by_id.items(): + entity_key = collections.OrderedDict(( + ("configuration_id", dst_attr_id), + ("entity_id", entity_id) + )) + if entity_id in dst_entity_ids_with_value: + operations.append( + ftrack_api.operation.UpdateEntityOperation( + "CustomAttributeValue", + entity_key, + "value", + ftrack_api.symbol.NOT_SET, + value + ) + ) + else: + operations.append( + ftrack_api.operation.CreateEntityOperation( + "CustomAttributeValue", + entity_key, + {"value": value} + ) + ) + + if not operations: + return + + for chunk in create_chunks(operations, 500): + for operation in chunk: + session.recorded_operations.push(operation) + session.commit() + + def _get_attr_type(self, conf_def): + type_name = conf_def["type"]["name"] + if type_name == "text": + return str + + if type_name == "number": + config = json.loads(conf_def["config"]) + if config["isdecimal"]: + return float + return int + return None + + +def register(session): + '''Register plugin. Called when used as an plugin.''' + + TranslateHierarchicalValues(session).register() From 8e7358a33b9da4a97681a1dae4119e30b238a24a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 3 Jun 2022 14:51:21 +0200 Subject: [PATCH 0462/1227] changed translate to transfer --- .../action_translate_hierarchical_values.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_translate_hierarchical_values.py b/openpype/modules/ftrack/event_handlers_server/action_translate_hierarchical_values.py index fd10005fad..8cc6fa3a57 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_translate_hierarchical_values.py +++ b/openpype/modules/ftrack/event_handlers_server/action_translate_hierarchical_values.py @@ -11,7 +11,7 @@ from openpype_modules.ftrack.lib import ( from openpype_modules.ftrack.lib.avalon_sync import create_chunks -class TranslateHierarchicalValues(ServerAction): +class TransferHierarchicalValues(ServerAction): """Transfer values across hierarhcical attributes. Aalso gives ability to convert types meanwhile. That is limited to @@ -20,9 +20,9 @@ class TranslateHierarchicalValues(ServerAction): - in, float -> string """ - identifier = "translate.hierarchical.values" + identifier = "transfer.hierarchical.values" label = "OpenPype Admin" - variant = "- Translate values between 2 custom attributes" + variant = "- Transfer values between 2 custom attributes" description = ( "Move values from a hierarchical attribute to" " second hierarchical attribute." @@ -37,7 +37,7 @@ class TranslateHierarchicalValues(ServerAction): "select value, entity_id from CustomAttributeValue" " where entity_id in ({}) and configuration_id is \"{}\"" ) - settings_key = "clean_hierarchical_attr" + settings_key = "transfer_values_of_hierarchical_attributes" def discover(self, session, entities, event): """Show anywhere.""" @@ -45,7 +45,7 @@ class TranslateHierarchicalValues(ServerAction): return self.valid_roles(session, entities, event) def _selection_interface(self, session, event_values=None): - title = "Translate hierarchical values" + title = "Transfer hierarchical values" attr_confs = session.query( ( @@ -67,7 +67,7 @@ class TranslateHierarchicalValues(ServerAction): "type": "label", "value": ( "Didn't found custom attributes" - " that can be translated." + " that can be transfered." ) }] } @@ -328,4 +328,4 @@ class TranslateHierarchicalValues(ServerAction): def register(session): '''Register plugin. Called when used as an plugin.''' - TranslateHierarchicalValues(session).register() + TransferHierarchicalValues(session).register() From 3da8536f968c932811a7979680cc27980594c2fe Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 3 Jun 2022 14:51:32 +0200 Subject: [PATCH 0463/1227] added settings for new action --- .../defaults/project_settings/ftrack.json | 7 +++++++ .../schema_project_ftrack.json | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index f9d16d6476..9d59deea3d 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -109,6 +109,13 @@ "Omitted" ], "name_sorting": false + }, + "transfer_values_of_hierarchical_attributes": { + "enabled": true, + "role_list": [ + "Administrator", + "Project manager" + ] } }, "user_handlers": { 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 7db490b114..16cab49d5d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json @@ -369,6 +369,25 @@ "key": "name_sorting" } ] + }, + { + "type": "dict", + "key": "transfer_values_of_hierarchical_attributes", + "label": "Action to transfer hierarchical attribute values", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "role_list", + "label": "Roles", + "object_type": "text" + } + ] } ] }, From d37497df10d6883f3b154f076ce91e7163c9d89b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 3 Jun 2022 14:54:33 +0200 Subject: [PATCH 0464/1227] changed filename --- ...erarchical_values.py => action_tranfer_hierarchical_values.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename openpype/modules/ftrack/event_handlers_server/{action_translate_hierarchical_values.py => action_tranfer_hierarchical_values.py} (100%) diff --git a/openpype/modules/ftrack/event_handlers_server/action_translate_hierarchical_values.py b/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py similarity index 100% rename from openpype/modules/ftrack/event_handlers_server/action_translate_hierarchical_values.py rename to openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py From 078a47e32f39b02c4c6c5db8860c2c7a6de886b5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 3 Jun 2022 14:58:59 +0200 Subject: [PATCH 0465/1227] added some logs --- .../action_tranfer_hierarchical_values.py | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py b/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py index 8cc6fa3a57..9df3b67969 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py +++ b/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py @@ -153,12 +153,17 @@ class TransferHierarchicalValues(ServerAction): delete_dst_values = values["delete_dst_attr_first"] if not src_attr_id or not dst_attr_id: + self.log.info("Attributes were not filled. Nothing to do.") return { "success": True, "message": "Nothing to do" } if src_attr_id == dst_attr_id: + self.log.info(( + "Same attributes were selected {}, {}." + " Showing interface again." + ).format(src_attr_id, dst_attr_id)) return self._selection_interface(session, values) # Query custom attrbutes @@ -188,6 +193,10 @@ class TransferHierarchicalValues(ServerAction): valid = True if not valid: + self.log.info(( + "Don't know how to properly convert" + " custom attribute types {} > {}" + ).format(src_type_name, dst_type_name)) return { "message": ( "Don't know how to properly convert" @@ -205,20 +214,26 @@ class TransferHierarchicalValues(ServerAction): ).format(src_attr_id) ).all() - value_by_id = {} + self.log.debug("Queried source values.") failed_entity_ids = [] - for attr_value in src_attr_values: - entity_id = attr_value["entity_id"] - value = attr_value["value"] - if value is not None: - try: - if dst_type is not None: - value = dst_type(value) - value_by_id[entity_id] = value - except Exception: - failed_entity_ids.append(entity_id) + if dst_type is not None: + self.log.debug("Converting source values to desctination type") + value_by_id = {} + for attr_value in src_attr_values: + entity_id = attr_value["entity_id"] + value = attr_value["value"] + if value is not None: + try: + if dst_type is not None: + value = dst_type(value) + value_by_id[entity_id] = value + except Exception: + failed_entity_ids.append(entity_id) if failed_entity_ids: + self.log.info( + "Couldn't convert some values to destination attribute" + ) return { "success": False, "message": ( @@ -232,6 +247,7 @@ class TransferHierarchicalValues(ServerAction): self.log.info("Deleting destination custom attribute values first") self._delete_custom_attribute_values(session, dst_attr_id) + self.log.info("Applying source values on destination custom attribute") self._apply_values(session, value_by_id, dst_attr_id) return True From 7500097cc47b18f8d9422547d13500e89427dd0f Mon Sep 17 00:00:00 2001 From: murphy Date: Fri, 3 Jun 2022 14:54:53 +0200 Subject: [PATCH 0466/1227] added Royal Render and Multiverse updated Nuke Studio icon, Flame moved to integrations --- website/src/pages/index.js | 22 ++++++++++++++++------ website/static/img/app_flame.png | Bin 74845 -> 39096 bytes website/static/img/app_hiero.png | Bin 40175 -> 33079 bytes website/static/img/app_multiverse.png | Bin 0 -> 4814 bytes website/static/img/app_nuke.png | Bin 25887 -> 32869 bytes website/static/img/app_nukestudio.png | Bin 0 -> 37527 bytes website/static/img/app_royalrender.png | Bin 0 -> 11650 bytes 7 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 website/static/img/app_multiverse.png create mode 100644 website/static/img/app_nukestudio.png create mode 100644 website/static/img/app_royalrender.png diff --git a/website/src/pages/index.js b/website/src/pages/index.js index d9bbc3eaa0..f57fd1002a 100644 --- a/website/src/pages/index.js +++ b/website/src/pages/index.js @@ -299,6 +299,11 @@ function Home() { Maya
+ + + + Flame + @@ -306,7 +311,7 @@ function Home() { - + Nuke Studio @@ -374,12 +379,17 @@ function Home() { Deadline - + Muster + + + Royal Render + + Slack @@ -390,10 +400,10 @@ function Home() {

In development by us or OpenPype community.

- - - - Flame + + + + Multiverse diff --git a/website/static/img/app_flame.png b/website/static/img/app_flame.png index ba9b69e45fa73d3f9298e77ec4a167a38f7ecf7b..188153e573f64ae8f66d9a27092cb47b7049fc23 100644 GIT binary patch literal 39096 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 literal 40175 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 diff --git a/website/static/img/app_multiverse.png b/website/static/img/app_multiverse.png new file mode 100644 index 0000000000000000000000000000000000000000..c0d80e4f1bf6cec0d95451fe51172cd8e4a36333 GIT binary patch literal 4814 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 literal 0 HcmV?d00001 diff --git a/website/static/img/app_nuke.png b/website/static/img/app_nuke.png index 4b3797af7aea30fb37b43c7d94d91a249da013bf..1465da8ce823d72303087250d3fa74453c13aa83 100644 GIT binary patch literal 32869 zcmb??V|!&y*KKTcY;|ngws!2KW7~Ge>G+O~9e1aL9ox>1ZQJO)dCqS*AF9?>^{H0X zm~+$`bF3IO6+X2vm7FDF6fnB-8&I0^HY(Z(yS5*8|cWAS(g6GRhSEH9&Nc z({qP_K*suCgM`S=#fN|hL6Vme*YwFf&x6;+*1?M^8FD&j_9}~;^T#=Fv|q@=*I3ZR2-3e`(_gns# z_cq0Ul<$5?nIuQ^pGAuN|KHBDAwE7Hw@gpRfReTjIj3MTb5xPb@-C%j{0!R; z;M8aD;5r2FpguJ1p#G}TO#Z-SkW4&*OCwvz#vG+ZiCsS2085XtZy!GL$v=u)ekG

FeTI>>8KOkPC^ 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 literal 25887 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ⅆgv-9FWR6p~| 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 diff --git a/website/static/img/app_nukestudio.png b/website/static/img/app_nukestudio.png new file mode 100644 index 0000000000000000000000000000000000000000..dfc2ad5a9745a5010589f7292a8c16f1097294f6 GIT binary patch literal 37527 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 literal 0 HcmV?d00001 diff --git a/website/static/img/app_royalrender.png b/website/static/img/app_royalrender.png new file mode 100644 index 0000000000000000000000000000000000000000..0e49519227003bcc031b8267315266c7da9e3f9d GIT binary patch literal 11650 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 Date: Fri, 3 Jun 2022 15:04:20 +0200 Subject: [PATCH 0467/1227] hound fix --- .../event_handlers_server/action_tranfer_hierarchical_values.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py b/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py index 9df3b67969..d160b7200d 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py +++ b/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py @@ -241,7 +241,6 @@ class TransferHierarchicalValues(ServerAction): ) } - # Delete destination custom attributes first if delete_dst_values: self.log.info("Deleting destination custom attribute values first") From 12579501c16120fcea663e97db93f5dfb8dd012c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 3 Jun 2022 17:37:45 +0200 Subject: [PATCH 0468/1227] global: fixing color metrix scale argument --- openpype/plugins/publish/extract_review_slate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index a2cbc1b704..01a7b0f592 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -189,7 +189,6 @@ class ExtractReviewSlate(openpype.api.Extractor): # make sure colors are correct output_args.extend([ - "-vf", "scale=out_color_matrix=bt709", "-color_primaries", "bt709", "-color_trc", "bt709", "-colorspace", "bt709", @@ -230,6 +229,7 @@ class ExtractReviewSlate(openpype.api.Extractor): scaling_arg = ( "scale={0}x{1}:flags=lanczos" + ":out_color_matrix=bt709" ",pad={2}:{3}:{4}:{5}:black" ",setsar=1" ",fps={6}" From 248d3bd1a36630adc42d9ca090f73c6557498d1b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 3 Jun 2022 17:38:29 +0200 Subject: [PATCH 0469/1227] Global: removing duplicate timecode argument - this will be applied at the end in concating stage --- openpype/plugins/publish/extract_review_slate.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 01a7b0f592..cff71f67ac 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -173,7 +173,6 @@ class ExtractReviewSlate(openpype.api.Extractor): self.log.debug("Slate Timecode: `{}`".format( offset_timecode )) - input_args.extend(["-timecode", str(offset_timecode)]) if use_legacy_code: format_args = [] From d1ff95129f447b52c46aa158c24b2c5d9ecac325 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 4 Jun 2022 03:43:28 +0000 Subject: [PATCH 0470/1227] [Automated] Bump version --- CHANGELOG.md | 18 ++++++++---------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6613985ccf..50cb1d423e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,19 @@ # Changelog -## [3.10.1-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.10.1-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...HEAD) **🚀 Enhancements** - General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268) +- Maya: reference loaders could store placeholder in referenced url [\#3264](https://github.com/pypeclub/OpenPype/pull/3264) - TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250) - Nuke: Change default icon path in settings [\#3247](https://github.com/pypeclub/OpenPype/pull/3247) **🐛 Bug fixes** +- Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) - Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) - Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) - Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) @@ -19,9 +21,12 @@ - Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) - Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) - Flame: debugging [\#3224](https://github.com/pypeclub/OpenPype/pull/3224) +- Ftrack: Push hierarchical attributes action works [\#3210](https://github.com/pypeclub/OpenPype/pull/3210) +- add silent audio to slate [\#3162](https://github.com/pypeclub/OpenPype/pull/3162) **Merged pull requests:** +- Maya: better handling of legacy review subsets names [\#3269](https://github.com/pypeclub/OpenPype/pull/3269) - Nuke: add pointcache and animation to loader [\#3186](https://github.com/pypeclub/OpenPype/pull/3186) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -49,9 +54,6 @@ - General: Add 'dataclasses' to required python modules [\#3149](https://github.com/pypeclub/OpenPype/pull/3149) - Hooks: Tweak logging grammar [\#3147](https://github.com/pypeclub/OpenPype/pull/3147) - Nuke: settings for reformat node in CreateWriteRender node [\#3143](https://github.com/pypeclub/OpenPype/pull/3143) -- Houdini: Add loader for alembic through Alembic Archive node [\#3140](https://github.com/pypeclub/OpenPype/pull/3140) -- Publisher: UI Modifications and fixes [\#3139](https://github.com/pypeclub/OpenPype/pull/3139) -- General: Simplified OP modules/addons import [\#3137](https://github.com/pypeclub/OpenPype/pull/3137) **🐛 Bug fixes** @@ -64,7 +66,6 @@ - Hiero: debugging frame range and other 3.10 [\#3222](https://github.com/pypeclub/OpenPype/pull/3222) - Project Manager: Fix persistent editors on project change [\#3218](https://github.com/pypeclub/OpenPype/pull/3218) - Deadline: instance data overwrite fix [\#3214](https://github.com/pypeclub/OpenPype/pull/3214) -- Ftrack: Push hierarchical attributes action works [\#3210](https://github.com/pypeclub/OpenPype/pull/3210) - Standalone Publisher: Always create new representation for thumbnail [\#3203](https://github.com/pypeclub/OpenPype/pull/3203) - Photoshop: skip collector when automatic testing [\#3202](https://github.com/pypeclub/OpenPype/pull/3202) - Nuke: render/workfile version sync doesn't work on farm [\#3185](https://github.com/pypeclub/OpenPype/pull/3185) @@ -75,14 +76,9 @@ - General: Oiio conversion for ffmpeg checks for invalid characters [\#3166](https://github.com/pypeclub/OpenPype/pull/3166) - Fix for attaching render to subset [\#3164](https://github.com/pypeclub/OpenPype/pull/3164) - Harmony: fixed missing task name in render instance [\#3163](https://github.com/pypeclub/OpenPype/pull/3163) -- add silent audio to slate [\#3162](https://github.com/pypeclub/OpenPype/pull/3162) - Ftrack: Action delete old versions formatting works [\#3152](https://github.com/pypeclub/OpenPype/pull/3152) -- nuke: adding extract thumbnail settings [\#3148](https://github.com/pypeclub/OpenPype/pull/3148) - Deadline: fix the output directory [\#3144](https://github.com/pypeclub/OpenPype/pull/3144) - General: New Session schema [\#3141](https://github.com/pypeclub/OpenPype/pull/3141) -- General: Missing version on headless mode crash properly [\#3136](https://github.com/pypeclub/OpenPype/pull/3136) -- TVPaint: Composite layers in reversed order [\#3135](https://github.com/pypeclub/OpenPype/pull/3135) -- TVPaint: Composite layers in reversed order [\#3134](https://github.com/pypeclub/OpenPype/pull/3134) **🔀 Refactored code** @@ -93,6 +89,7 @@ - Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) - Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249) - Maya: added jpg to filter for Image Plane Loader [\#3223](https://github.com/pypeclub/OpenPype/pull/3223) +- Maya: added jpg to filter for Image Plane Loader [\#3221](https://github.com/pypeclub/OpenPype/pull/3221) - Webpublisher: replace space by underscore in subset names [\#3160](https://github.com/pypeclub/OpenPype/pull/3160) ## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19) @@ -134,6 +131,7 @@ **🐛 Bug fixes** - Ftrack: Action delete old versions formatting works [\#3154](https://github.com/pypeclub/OpenPype/pull/3154) +- nuke: adding extract thumbnail settings [\#3148](https://github.com/pypeclub/OpenPype/pull/3148) **Merged pull requests:** diff --git a/openpype/version.py b/openpype/version.py index 1d8ef28225..e5dfc2bb8f 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.10.1-nightly.2" +__version__ = "3.10.1-nightly.3" diff --git a/pyproject.toml b/pyproject.toml index 27b32cf53b..7a620aec8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.10.1-nightly.2" # OpenPype +version = "3.10.1-nightly.3" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 5526435e41554a685864218fd601dd276cfc0cdd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Sat, 4 Jun 2022 21:07:49 +0200 Subject: [PATCH 0471/1227] initial commit of client entities functions --- openpype/client/__init__.py | 59 +++ openpype/client/entities.py | 897 ++++++++++++++++++++++++++++++++++++ 2 files changed, 956 insertions(+) create mode 100644 openpype/client/__init__.py create mode 100644 openpype/client/entities.py diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py new file mode 100644 index 0000000000..861f828e67 --- /dev/null +++ b/openpype/client/__init__.py @@ -0,0 +1,59 @@ +from .entities import ( + get_projects, + get_project, + + get_asset, + get_assets, + get_asset_ids_with_subsets, + + get_subset, + get_subsets, + get_subset_families, + + get_version_by_name, + get_version, + get_versions, + get_last_versions, + get_last_version_for_subset, + get_hero_version, + get_hero_versions, + get_version_links, + + get_representation, + get_representation_by_name, + get_representations, + get_representations_parents, + + get_thumbnail, + get_thumbnail_id_from_source, +) + +__all__ = ( + "get_projects", + "get_project", + + "get_asset", + "get_assets", + "get_asset_ids_with_subsets", + + "get_subset", + "get_subsets", + "get_subset_families", + + "get_version_by_name", + "get_version", + "get_versions", + "get_last_versions", + "get_last_version_for_subset", + "get_hero_version", + "get_hero_versions", + "get_version_links", + + "get_representation", + "get_representation_by_name", + "get_representations", + "get_representations_parents", + + "get_thubmnail", + "get_thumbnail_id_from_source", +) diff --git a/openpype/client/entities.py b/openpype/client/entities.py new file mode 100644 index 0000000000..4b52f8cf2d --- /dev/null +++ b/openpype/client/entities.py @@ -0,0 +1,897 @@ +"""Unclear if these will have public functions like these. + +Goal is that most of functions here are called on (or with) an object +that has project name as a context (e.g. on 'ProjectEntity'?). + ++ We will need more specific functions doing wery specific queires really fast. +""" + +import os +import collections + +import six +from bson.objectid import ObjectId + +from openpype.lib.mongo import OpenPypeMongoConnection + + +def _get_project_connection(project_name=None): + db_name = os.environ.get("AVALON_DB") or "avalon" + mongodb = OpenPypeMongoConnection.get_mongo_client()[db_name] + if project_name: + return mongodb[project_name] + return mongodb + + +def _prepare_fields(fields): + if not fields: + return None + + output = { + field: True + for field in fields + } + if "_id" not in output: + output["_id"] = True + return output + + +def _convert_id(in_id): + if isinstance(in_id, six.string_types): + return ObjectId(in_id) + return in_id + + +def _convert_ids(in_ids): + _output = set() + for in_id in in_ids: + if in_id is not None: + _output.add(_convert_id(in_id)) + return list(_output) + + +def get_projects(active=True, inactive=False, fields=None): + mongodb = _get_project_connection() + for project_name in mongodb.collection_names(): + if project_name in ("system.indexes",): + continue + project_doc = get_project( + project_name, active=active, inactive=inactive, fields=fields + ) + if project_doc is not None: + yield project_doc + + +def get_project(project_name, active=True, inactive=False, fields=None): + # Skip if both are disabled + if not active and not inactive: + return None + + query_filter = {"type": "project"} + # Keep query untouched if both should be available + if active and inactive: + pass + + # Add filter to keep only active + elif active: + query_filter["$or"] = [ + {"data.active": {"$exists": False}}, + {"data.active": True}, + ] + + # Add filter to keep only inactive + elif inactive: + query_filter["$or"] = [ + {"data.active": {"$exists": False}}, + {"data.active": False}, + ] + + conn = _get_project_connection(project_name) + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_asset(project_name, asset_name=None, asset_id=None, fields=None): + query_filter = {"type": "asset"} + has_filter = False + if asset_name: + has_filter = True + query_filter["name"] = asset_name + + if asset_id: + has_filter = True + query_filter["_id"] = _convert_id(asset_id) + + # Avoid random asset quqery + if not has_filter: + return None + + conn = _get_project_connection(project_name) + + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_assets( + project_name, asset_ids=None, asset_names=None, archived=False, fields=None +): + asset_types = ["asset"] + if archived: + asset_types.append("archived_asset") + + if len(asset_types) == 1: + query_filter = {"type": asset_types[0]} + else: + query_filter = {"type": {"$in": asset_types}} + + if asset_ids is not None: + asset_ids = _convert_ids(asset_ids) + if not asset_ids: + return [] + query_filter["_id"] = {"$in": asset_ids} + + if asset_names is not None: + if not asset_names: + return [] + query_filter["name"] = {"$in": list(asset_names)} + + conn = _get_project_connection(project_name) + + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_asset_ids_with_subsets(project_name, asset_ids=None): + subset_query = { + "type": "subset" + } + if asset_ids is not None: + asset_ids = _convert_ids(asset_ids) + if not asset_ids: + return [] + subset_query["parent"] = {"$in": asset_ids} + + conn = _get_project_connection(project_name) + result = conn.aggregate([ + { + "$match": subset_query + }, + { + "$group": { + "_id": "$parent", + "count": {"$sum": 1} + } + } + ]) + asset_ids_with_subsets = [] + for item in result: + asset_id = item["_id"] + count = item["count"] + if count > 0: + asset_ids_with_subsets.append(asset_id) + return asset_ids_with_subsets + + +def get_subset( + project_name, + subset_id=None, + subset_name=None, + asset_id=None, + fields=None +): + """Single subset document by subset id or name and parent id. + + When subset id is defined it is not needed to add any other arguments but + subset name filter must be always combined with asset id (or subset id). + + Question: + This could be split into more functions? + + Args: + project_name (str): Name of project where to look for subset. + subset_id (ObjectId): Id of subset which should be found. + subset_name (str): Name of asset. Must be combined with 'asset_id' or + 'subset_id' arguments otherwise result is 'None'. + asset_id (ObjectId): Id of parent asset. Must be combined with + 'subset_name' or 'subset_id'. + fields (list): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If subset with specified filters was not found. + Dict: Subset document which can be reduced to specified 'fields'. + """ + + query_filters = {"type": "subset"} + has_valid_filters = False + if subset_id is not None: + query_filters["_id"] = _convert_id(asset_id) + has_valid_filters = True + + if subset_name is not None: + if asset_id is not None: + has_valid_filters = True + query_filters["name"] = subset_name + + if asset_id is not None: + query_filters["parent"] = _convert_id(asset_id) + + if not has_valid_filters: + return None + + conn = _get_project_connection(project_name) + return conn.find_one(query_filters, _prepare_fields(fields)) + + +def get_subsets( + project_name, + asset_ids=None, + subset_ids=None, + subset_names=None, + archived=False, + fields=None +): + subset_types = ["subset"] + if archived: + subset_types.append("archived_subset") + + if len(subset_types) == 1: + query_filter = {"type": subset_types[0]} + else: + query_filter = {"type": {"$in": subset_types}} + + if asset_ids is not None: + asset_ids = _convert_ids(asset_ids) + if not asset_ids: + return [] + query_filter["parent"] = {"$in": asset_ids} + + if subset_ids is not None: + subset_ids = _convert_ids(subset_ids) + if not subset_ids: + return [] + query_filter["_id"] = {"$in": subset_ids} + + if subset_names is not None: + if not subset_names: + return [] + query_filter["name"] = {"$in": list(subset_names)} + + conn = _get_project_connection(project_name) + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_subset_families(project_name, subset_ids=None): + subset_filter = { + "type": "subset" + } + if subset_ids is not None: + if not subset_ids: + return set() + subset_filter["_id"] = {"$in": list(subset_ids)} + + conn = _get_project_connection(project_name) + result = list(conn.aggregate([ + {"$match": subset_filter}, + {"$project": { + "family": {"$arrayElemAt": ["$data.families", 0]} + }}, + {"$group": { + "_id": "family_group", + "families": {"$addToSet": "$family"} + }} + ])) + if result: + return set(result[0]["families"]) + return set() + + +def get_version_by_name(project_name, subset_id, version, fields=None): + conn = _get_project_connection(project_name) + query_filter = { + "type": "version", + "parent": _convert_id(subset_id), + "name": version + } + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_version(project_name, version_id, fields=None): + if not version_id: + return None + conn = _get_project_connection(project_name) + query_filter = { + "type": {"$in": ["version", "hero_version"]}, + "_id": _convert_id(version_id) + } + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def _get_versions( + project_name, + subset_ids=None, + version_ids=None, + standard=True, + hero=False, + fields=None +): + version_types = [] + if standard: + version_types.append("version") + + if hero: + version_types.append("hero_version") + + if not version_types: + return [] + elif len(version_types) == 1: + query_filter = {"type": version_types[0]} + else: + query_filter = {"type": {"$in": version_types}} + + if subset_ids is not None: + subset_ids = _convert_ids(subset_ids) + if not subset_ids: + return [] + query_filter["parent"] = {"$in": subset_ids} + + if version_ids is not None: + version_ids = _convert_ids(version_ids) + if not version_ids: + return [] + query_filter["_id"] = {"$in": version_ids} + + conn = _get_project_connection(project_name) + + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_versions( + project_name, + subset_ids=None, + version_ids=None, + hero=False, + fields=None +): + return _get_versions( + project_name, + subset_ids, + version_ids, + standard=True, + hero=hero, + fields=fields + ) + + +def get_hero_version( + project_name, + subset_id=None, + version_id=None, + fields=None +): + if not subset_id and not version_id: + return None + + subset_ids = None + if subset_id is not None: + subset_ids = [subset_id] + + version_ids = None + if version_id is not None: + version_ids = [version_id] + + versions = list(_get_versions( + project_name, + subset_ids=subset_ids, + version_ids=version_ids, + standard=False, + hero=True, + fields=fields + )) + if versions: + return versions[0] + return None + + +def get_hero_versions( + project_name, + subset_ids=None, + version_ids=None, + fields=None +): + return _get_versions( + project_name, + subset_ids, + version_ids, + standard=False, + hero=True, + fields=fields + ) + + +def get_version_links(project_name, version_id, fields=None): + conn = _get_project_connection(project_name) + # Does make sense to look for hero versions? + query_filter = { + "type": "version", + "data.inputLinks.input": _convert_id(version_id) + } + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_last_versions(project_name, subset_ids, fields=None): + """Retrieve all latest versions for entered subset_ids. + + Args: + subset_ids (list): List of subset ids. + + Returns: + dict: Key is subset id and value is last version name. + """ + + subset_ids = _convert_ids(subset_ids) + if not subset_ids: + return {} + + _pipeline = [ + # Find all versions of those subsets + {"$match": { + "type": "version", + "parent": {"$in": subset_ids} + }}, + # Sorting versions all together + {"$sort": {"name": 1}}, + # Group them by "parent", but only take the last + {"$group": { + "_id": "$parent", + "_version_id": {"$last": "$_id"} + }} + ] + + conn = _get_project_connection(project_name) + version_ids = [ + doc["_version_id"] + for doc in conn.aggregate(_pipeline) + ] + version_docs = get_versions( + project_name, version_ids=version_ids, fields=fields + ) + + return { + version_doc["parent"]: version_doc + for version_doc in version_docs + } + + +def get_last_version_for_subset( + project_name, subset_id=None, subset_name=None, asset_id=None, fields=None +): + subset_doc = get_subset( + project_name, + subset_id=subset_id, + subset_name=subset_name, + asset_id=asset_id, + fields=["_id"] + ) + if not subset_doc: + return None + subset_id = subset_doc["_id"] + last_versions = get_last_versions( + project_name, subset_ids=[subset_id], fields=fields + ) + return last_versions.get(subset_id) + + +def get_representation( + project_name, + representation_id=None, + representation_name=None, + version_id=None, + fields=None +): + if not representation_id: + if not representation_name or not version_id: + return None + + repre_types = ["representation", "archived_representations"] + query_filter = { + "type": {"$in": repre_types} + } + if representation_id is not None: + query_filter["_id"] = _convert_id(representation_id) + + if representation_name is not None: + query_filter["name"] = representation_name + + if version_id is not None: + query_filter["parent"] = version_id + + conn = _get_project_connection(project_name) + + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_representation_by_name( + project_name, representation_name, version_id, fields=None +): + if not version_id or not representation_name: + return None + repre_types = ["representation", "archived_representations"] + query_filter = { + "type": {"$in": repre_types}, + "name": representation_name, + "parent": _convert_id(version_id) + } + + conn = _get_project_connection(project_name) + + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_representations( + project_name, + representation_ids=None, + representation_names=None, + version_ids=None, + extensions=None, + names_by_version_ids=None, + check_site_name=False, + archived=False, + fields=None +): + repre_types = ["representation"] + if archived: + repre_types.append("archived_representations") + if len(repre_types) == 1: + query_filter = {"type": repre_types[0]} + else: + query_filter = {"type": {"$in": repre_types}} + + if check_site_name: + query_filter["files.site.name"] = {"$exists": True} + + if representation_ids is not None: + representation_ids = _convert_ids(representation_ids) + if not representation_ids: + return [] + query_filter["_id"] = {"$in": representation_ids} + + if representation_names is not None: + if not representation_names: + return [] + query_filter["name"] = {"$in": list(representation_names)} + + if version_ids is not None: + version_ids = _convert_ids(version_ids) + if not version_ids: + return [] + query_filter["parent"] = {"$in": version_ids} + + if extensions is not None: + if not extensions: + return [] + query_filter["context.ext"] = {"$in": list(extensions)} + + if names_by_version_ids is not None: + or_query = [] + for version_id, names in names_by_version_ids.items(): + if version_id and names: + or_query.append({ + "parent": _convert_id(version_id), + "name": {"$in": list(names)} + }) + if not or_query: + return [] + query_filter["$or"] = or_query + + conn = _get_project_connection(project_name) + + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_representations_parents(project_name, representations): + repres_by_version_id = collections.defaultdict(list) + versions_by_version_id = {} + versions_by_subset_id = collections.defaultdict(list) + subsets_by_subset_id = {} + subsets_by_asset_id = collections.defaultdict(list) + for representation in representations: + repre_id = representation["_id"] + version_id = representation["parent"] + repres_by_version_id[version_id].append(representation) + + versions = get_versions( + project_name, version_ids=repres_by_version_id.keys() + ) + for version in versions: + version_id = version["_id"] + subset_id = version["parent"] + versions_by_version_id[version_id] = version + versions_by_subset_id[subset_id].append(version) + + subsets = get_subsets( + project_name, subset_ids=versions_by_subset_id.keys() + ) + for subset in subsets: + subset_id = subset["_id"] + asset_id = subset["parent"] + subsets_by_subset_id[subset_id] = subset + subsets_by_asset_id[asset_id].append(subset) + + assets = get_assets(project_name, asset_ids=subsets_by_asset_id.keys()) + assets_by_id = { + asset["_id"]: asset + for asset in assets + } + + project = get_project(project_name) + + output = {} + for version_id, representations in repres_by_version_id.items(): + asset = None + subset = None + version = versions_by_version_id.get(version_id) + if version: + subset_id = version["parent"] + subset = subsets_by_subset_id.get(subset_id) + if subset: + asset_id = subset["parent"] + asset = assets_by_id.get(asset_id) + + for representation in representations: + repre_id = representation["_id"] + output[repre_id] = (version, subset, asset, project) + return output + + +def get_thumbnail_id_from_source(project_name, src_type, src_id): + if not src_type or not src_id: + return None + + query_filter = {"_id": _convert_id(src_id)} + + conn = _get_project_connection(project_name) + src_doc = conn.find_one(query_filter, {"data.thumbnail_id"}) + if src_doc: + return src_doc.get("data", {}).get("thumbnail_id") + return None + + +def get_thumbnail(project_name, thumbnail_id, fields=None): + if not thumbnail_id: + return None + query_filter = {"type": "thumbnail", "_id": _convert_id(thumbnail_id)} + conn = _get_project_connection(project_name) + return conn.find(query_filter, _prepare_fields(fields)) + + +""" +Custom data storage: +- Webpublisher - jobs +- Ftrack - events + +openpype/tools/assetlinks/widgets.py +- SimpleLinkView + Query: + - get_versions + - get_subsets + - get_assets + - get_version_links + +openpype/tools/creator/window.py +- CreatorWindow + Query: + - get_asset + - get_subsets + +openpype/tools/launcher/models.py +- LauncherModel + Query: + - get_project + - get_assets + +openpype/tools/libraryloader/app.py +- LibraryLoaderWindow + Query: + - get_project + +openpype/tools/loader/app.py +- LoaderWindow + Query: + - get_project +- show + Query: + - get_projects + +openpype/tools/loader/model.py +- SubsetsModel + Query: + - get_assets + - get_subsets + - get_last_versions + - get_versions + - get_hero_versions + - get_version_by_name +- RepresentationModel + Query: + - get_representations + - sync server specific queries (separated into multiple functions?) + - NOT REPLACED + +openpype/tools/loader/widgets.py +- FamilyModel + Query: + - get_subset_families +- VersionTextEdit + Query: + - get_subset + - get_version +- SubsetWidget + Query: + - get_subsets + - get_representations +- RepresentationWidget + Query: + - get_subsets + - get_versions + - get_representations +- ThumbnailWidget + Query: + - get_thumbnail_id_from_source + - get_thumbnail + +openpype/tools/mayalookassigner/app.py +- MayaLookAssignerWindow + Query: + - get_last_version_for_subset + +openpype/tools/mayalookassigner/commands.py +- create_items_from_nodes + Query: + - get_asset + +openpype/tools/mayalookassigner/vray_proxies.py +- get_look_relationships + Query: + - get_representation_by_name +- load_look + Query: + - get_representation_by_name +- vrayproxy_assign_look + Query: + - get_last_version_for_subset + +openpype/tools/project_manager/project_manager/model.py +- HierarchyModel + Query: + - get_asset_ids_with_subsets + - get_project + - get_assets + +openpype/tools/project_manager/project_manager/view.py +- ProjectDocCache + Query: + - get_project + +openpype/tools/project_manager/project_manager/widgets.py +- CreateProjectDialog + Query: + - get_projects + +openpype/tools/publisher/widgets/create_dialog.py +- CreateDialog + Query: + - get_asset + - get_subsets + +openpype/tools/publisher/control.py +- AssetDocsCache + Query: + - get_assets + +openpype/tools/sceneinventory/model.py +- InventoryModel + Query: + - get_asset + - get_subset + - get_version + - get_last_version_for_subset + - get_representation + +openpype/tools/sceneinventory/switch_dialog.py +- SwitchAssetDialog + Query: + - get_asset + - get_assets + - get_subset + - get_subsets + - get_versions + - get_hero_versions + - get_last_versions + - get_representations + +openpype/tools/sceneinventory/view.py +- SceneInventoryView + Query: + - get_version + - get_versions + - get_hero_versions + - get_representation + - get_representations + +openpype/tools/standalonepublish/widgets/model_asset.py +- AssetModel + Query: + - get_assets + +openpype/tools/standalonepublish/widgets/widget_asset.py +- AssetWidget + Query: + - get_project + - get_asset + +openpype/tools/standalonepublish/widgets/widget_family.py +- FamilyWidget + Query: + - get_asset + - get_subset + - get_subsets + - get_last_version_for_subset + +openpype/tools/standalonepublish/app.py +- Window + Query: + - get_asset + +openpype/tools/texture_copy/app.py +- TextureCopy + Query: + - get_project + - get_asset + +openpype/tools/workfiles/files_widget.py +- FilesWidget + Query: + - get_asset + +openpype/tools/workfiles/model.py +- PublishFilesModel + Query: + - get_subsets + - get_versions + - get_representations + +openpype/tools/workfiles/save_as_dialog.py +- build_workfile_data + Query: + - get_project + - get_asset + +openpype/tools/workfiles/window.py +- Window + Query: + - get_asset + +openpype/tools/utils/assets_widget.py +- AssetModel + Query: + - get_project + - get_assets + +openpype/tools/utils/delegates.py +- VersionDelegate + Query: + - get_versions + - get_hero_versions + +openpype/tools/utils/lib.py +- GroupsConfig + Query: + - get_project +- FamilyConfigCache + Query: + - get_asset + +openpype/tools/utils/tasks_widget.py +- TasksModel + Query: + - get_project + - get_asset +""" From 7109d932ec2ed70a2042d2df7563de368293a88e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Sat, 4 Jun 2022 21:08:42 +0200 Subject: [PATCH 0472/1227] replace queries in tools --- openpype/tools/assetlinks/widgets.py | 159 +++-- openpype/tools/creator/window.py | 21 +- openpype/tools/launcher/models.py | 21 +- openpype/tools/libraryloader/app.py | 57 +- openpype/tools/loader/app.py | 17 +- openpype/tools/loader/model.py | 289 +++++---- openpype/tools/loader/widgets.py | 231 ++++--- openpype/tools/mayalookassigner/app.py | 21 +- openpype/tools/mayalookassigner/commands.py | 18 +- .../tools/mayalookassigner/vray_proxies.py | 77 +-- .../project_manager/project_manager/model.py | 42 +- .../project_manager/project_manager/view.py | 9 +- .../project_manager/widgets.py | 13 +- openpype/tools/publisher/control.py | 9 +- .../tools/publisher/widgets/create_dialog.py | 22 +- openpype/tools/sceneinventory/model.py | 38 +- .../tools/sceneinventory/switch_dialog.py | 569 ++++++++---------- openpype/tools/sceneinventory/view.py | 113 ++-- openpype/tools/standalonepublish/app.py | 18 +- .../standalonepublish/widgets/model_asset.py | 12 +- .../standalonepublish/widgets/widget_asset.py | 27 +- .../widgets/widget_family.py | 81 ++- openpype/tools/texture_copy/app.py | 28 +- openpype/tools/utils/assets_widget.py | 19 +- openpype/tools/utils/delegates.py | 40 +- openpype/tools/utils/lib.py | 14 +- openpype/tools/utils/tasks_widget.py | 13 +- openpype/tools/workfiles/files_widget.py | 5 +- openpype/tools/workfiles/model.py | 48 +- openpype/tools/workfiles/save_as_dialog.py | 30 +- openpype/tools/workfiles/window.py | 23 +- 31 files changed, 1010 insertions(+), 1074 deletions(-) diff --git a/openpype/tools/assetlinks/widgets.py b/openpype/tools/assetlinks/widgets.py index 22e8848a60..5ce2a835ef 100644 --- a/openpype/tools/assetlinks/widgets.py +++ b/openpype/tools/assetlinks/widgets.py @@ -1,10 +1,16 @@ +import collections +from openpype.client import ( + get_versions, + get_subsets, + get_assets, + get_version_links, +) from Qt import QtWidgets class SimpleLinkView(QtWidgets.QWidget): - - def __init__(self, dbcon, parent=None): + def __init__(self, dbcon, parent): super(SimpleLinkView, self).__init__(parent=parent) self.dbcon = dbcon @@ -24,6 +30,11 @@ class SimpleLinkView(QtWidgets.QWidget): self._in_view = in_view self._out_view = out_view + self._version_doc_to_process = None + + @property + def project_name(self): + return self.dbcon.current_project() def clear(self): self._in_view.clear() @@ -31,60 +42,114 @@ class SimpleLinkView(QtWidgets.QWidget): def set_version(self, version_doc): self.clear() - if not version_doc or not self.isVisible(): - return + self._version_doc_to_process = version_doc + if version_doc and self.isVisible(): + self._fill_values() - # inputs - # + def showEvent(self, event): + super(SimpleLinkView, self).showEvent(event) + self._fill_values() + + def _fill_values(self): + if self._version_doc_to_process is None: + return + version_doc = self._version_doc_to_process + self._version_doc_to_process = None + self._fill_inputs(version_doc) + self._fill_outputs(version_doc) + + def _fill_inputs(self, version_doc): + version_ids = set() for link in version_doc["data"].get("inputLinks", []): # Backwards compatibility for "input" key used as "id" if "id" not in link: link_id = link["input"] else: link_id = link["id"] - version = self.dbcon.find_one( - {"_id": link_id, "type": "version"}, - projection={"name": 1, "parent": 1} - ) - if not version: - continue - subset = self.dbcon.find_one( - {"_id": version["parent"], "type": "subset"}, - projection={"name": 1, "parent": 1} - ) - if not subset: - continue - asset = self.dbcon.find_one( - {"_id": subset["parent"], "type": "asset"}, - projection={"name": 1} - ) + version_ids.add(link_id) - self._in_view.addItem("{asset} {subset} v{version:0>3}".format( - asset=asset["name"], - subset=subset["name"], - version=version["name"], + version_docs = list(get_versions( + self.project_name, + version_ids=version_ids, + fields=["name", "parent"] + )) + + subset_docs = [] + versions_by_subset_id = collections.defaultdict(list) + if versions_by_subset_id: + for version_doc in version_docs: + subset_id = version_doc["parent"] + versions_by_subset_id[subset_id].append(version_doc) + + subset_docs = list(get_subsets( + self.project_name, + subset_ids=versions_by_subset_id.keys(), + fields=["_id", "name", "parent"] )) - # outputs - # - outputs = self.dbcon.find( - {"type": "version", "data.inputLinks.input": version_doc["_id"]}, - projection={"name": 1, "parent": 1} - ) - for version in outputs or []: - subset = self.dbcon.find_one( - {"_id": version["parent"], "type": "subset"}, - projection={"name": 1, "parent": 1} - ) - if not subset: - continue - asset = self.dbcon.find_one( - {"_id": subset["parent"], "type": "asset"}, - projection={"name": 1} - ) + asset_docs = [] + subsets_by_asset_id = collections.defaultdict(list) + if subset_docs: + for subset_doc in subset_docs: + asset_id = subset_doc["parent"] + subsets_by_asset_id[asset_id].append(subset_doc) - self._out_view.addItem("{asset} {subset} v{version:0>3}".format( - asset=asset["name"], - subset=subset["name"], - version=version["name"], + asset_docs = list(get_assets( + self.project_name, + asset_ids=subsets_by_asset_id.keys(), + fields=["_id", "name"] )) + + for asset_doc in asset_docs: + asset_id = asset_doc["_id"] + for subset_doc in subsets_by_asset_id[asset_id]: + subset_id = subset_doc["_id"] + for version_doc in versions_by_subset_id[subset_id]: + self._in_view.addItem("{} {} v{:0>3}".format( + asset_doc["name"], + subset_doc["name"], + version_doc["name"], + )) + + def _fill_outputs(self, version_doc): + version_docs = list(get_version_links( + self.project_name, + version_doc["_id"], + fields=["name", "parent"] + )) + subset_docs = [] + versions_by_subset_id = collections.defaultdict(list) + if versions_by_subset_id: + for version_doc in version_docs: + subset_id = version_doc["parent"] + versions_by_subset_id[subset_id].append(version_doc) + + subset_docs = list(get_subsets( + self.project_name, + subset_ids=versions_by_subset_id.keys(), + fields=["_id", "name", "parent"] + )) + + asset_docs = [] + subsets_by_asset_id = collections.defaultdict(list) + if subset_docs: + for subset_doc in subset_docs: + asset_id = subset_doc["parent"] + subsets_by_asset_id[asset_id].append(subset_doc) + + asset_docs = list(get_assets( + self.project_name, + asset_ids=subsets_by_asset_id.keys(), + fields=["_id", "name"] + )) + + for asset_doc in asset_docs: + asset_id = asset_doc["_id"] + for subset_doc in subsets_by_asset_id[asset_id]: + subset_id = subset_doc["_id"] + for version_doc in versions_by_subset_id[subset_id]: + self._out_view.addItem("{} {} v{:0>3}".format( + asset_doc["name"], + subset_doc["name"], + version_doc["name"], + )) diff --git a/openpype/tools/creator/window.py b/openpype/tools/creator/window.py index e0c329fb78..a85f47a060 100644 --- a/openpype/tools/creator/window.py +++ b/openpype/tools/creator/window.py @@ -4,6 +4,7 @@ import re from Qt import QtWidgets, QtCore +from openpype.client import get_asset, get_subsets from openpype import style from openpype.api import get_current_project_settings from openpype.tools.utils.lib import qt_app_context @@ -215,12 +216,12 @@ class CreatorWindow(QtWidgets.QDialog): self._set_valid_state(False) return + project_name = legacy_io.active_project() asset_doc = None if creator_plugin: # Get the asset from the database which match with the name - asset_doc = legacy_io.find_one( - {"name": asset_name, "type": "asset"}, - projection={"_id": 1} + asset_doc = get_asset( + project_name, asset_name=asset_name, fields=["_id"] ) # Get plugin @@ -235,7 +236,6 @@ class CreatorWindow(QtWidgets.QDialog): self._set_valid_state(False) return - project_name = legacy_io.Session["AVALON_PROJECT"] asset_id = asset_doc["_id"] task_name = legacy_io.Session["AVALON_TASK"] @@ -269,14 +269,13 @@ class CreatorWindow(QtWidgets.QDialog): self._subset_name_input.setText(subset_name) # Get all subsets of the current asset - subset_docs = legacy_io.find( - { - "type": "subset", - "parent": asset_id - }, - {"name": 1} + subset_docs = get_subsets( + project_name, asset_ids=[asset_id], fields=["name"] ) - existing_subset_names = set(subset_docs.distinct("name")) + existing_subset_names = { + subset_doc["name"] + for subset_doc in subset_docs + } existing_subset_names_low = set( _name.lower() for _name in existing_subset_names diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 13567e7916..307f591d96 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -9,6 +9,10 @@ import appdirs from Qt import QtCore, QtGui import qtawesome +from openpype.client import ( + get_project, + get_assets, +) from openpype.lib import JSONSettingRegistry from openpype.lib.applications import ( CUSTOM_LAUNCH_APP_GROUPS, @@ -81,13 +85,11 @@ class ActionModel(QtGui.QStandardItemModel): def get_application_actions(self): actions = [] - if not self.dbcon.Session.get("AVALON_PROJECT"): + if not self.dbcon.current_project(): return actions - project_doc = self.dbcon.find_one( - {"type": "project"}, - {"config.apps": True} - ) + project_name = self.dbcon.active_project() + project_doc = get_project(project_name, fields=["config.apps"]) if not project_doc: return actions @@ -448,7 +450,7 @@ class LauncherModel(QtCore.QObject): @property def project_name(self): """Current project name.""" - return self._dbcon.Session.get("AVALON_PROJECT") + return self._dbcon.current_project() @property def refreshing_assets(self): @@ -649,10 +651,9 @@ class LauncherModel(QtCore.QObject): self._asset_refresh_thread = None def _refresh_assets(self): - asset_docs = list(self._dbcon.find( - {"type": "asset"}, - self._asset_projection - )) + asset_docs = get_assets( + self._last_project_name, fields=list(self._asset_projection.keys()) + ) if not self._refreshing_assets: return self._refreshing_assets = False diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index 7fda6bd6f9..5f4d10d796 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -3,6 +3,7 @@ import sys from Qt import QtWidgets, QtCore, QtGui from openpype import style +from openpype.client import get_project from openpype.pipeline import AvalonMongoDB from openpype.tools.utils import lib as tools_lib from openpype.tools.loader.widgets import ( @@ -303,14 +304,26 @@ class LibraryLoaderWindow(QtWidgets.QDialog): families = self._subsets_widget.get_subsets_families() self._families_filter_view.set_enabled_families(families) - def set_context(self, context, refresh=True): - self.echo("Setting context: {}".format(context)) - lib.schedule( - lambda: self._set_context(context, refresh=refresh), - 50, channel="mongo" - ) - # ------------------------------ + def set_context(self, context, refresh=True): + """Set the selection in the interface using a context. + The context must contain `asset` data by name. + + Args: + context (dict): The context to apply. + Returns: + None + """ + + asset_name = context.get("asset", None) + if asset_name is None: + return + + if refresh: + self._refresh_assets() + + self._assets_widget.select_asset_by_name(asset_name) + def _on_family_filter_change(self, families): self._subsets_widget.set_family_filters(families) @@ -323,10 +336,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog): """Load assets from database""" if self.current_project is not None: # Ensure a project is loaded - project_doc = self.dbcon.find_one( - {"type": "project"}, - {"type": 1} - ) + project_doc = get_project(self.current_project, fields=["_id"]) assert project_doc, "This is a bug" self._families_filter_view.set_enabled_families(set()) @@ -371,7 +381,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog): # Clear the version information on asset change self._version_info_widget.set_version(None) - self._thumbnail_widget.set_thumbnail(asset_ids) + self._thumbnail_widget.set_thumbnail("asset", asset_ids) self.data["state"]["assetIds"] = asset_ids @@ -426,34 +436,17 @@ class LibraryLoaderWindow(QtWidgets.QDialog): version_doc["_id"] for version_doc in version_docs ] + src_type = "version" if not thumbnail_src_ids: + src_type = "asset" thumbnail_src_ids = self._assets_widget.get_selected_asset_ids() - self._thumbnail_widget.set_thumbnail(thumbnail_src_ids) + self._thumbnail_widget.set_thumbnail(src_type, thumbnail_src_ids) version_ids = [doc["_id"] for doc in version_docs or []] if self._repres_widget: self._repres_widget.set_version_ids(version_ids) - def _set_context(self, context, refresh=True): - """Set the selection in the interface using a context. - The context must contain `asset` data by name. - - Args: - context (dict): The context to apply. - Returns: - None - """ - - asset_name = context.get("asset", None) - if asset_name is None: - return - - if refresh: - self._refresh_assets() - - self._assets_widget.select_asset_by_name(asset_name) - def _on_message_timeout(self): self._message_label.setText("") diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index bb589c199d..1917f23c60 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -3,6 +3,7 @@ import traceback from Qt import QtWidgets, QtCore +from openpype.client import get_projects, get_project from openpype import style from openpype.lib import register_event_callback from openpype.pipeline import ( @@ -39,7 +40,7 @@ class LoaderWindow(QtWidgets.QDialog): def __init__(self, parent=None): super(LoaderWindow, self).__init__(parent) title = "Asset Loader 2.1" - project_name = legacy_io.Session.get("AVALON_PROJECT") + project_name = legacy_io.active_project() if project_name: title += " - {}".format(project_name) self.setWindowTitle(title) @@ -274,8 +275,9 @@ class LoaderWindow(QtWidgets.QDialog): """Load assets from database""" # Ensure a project is loaded - project = legacy_io.find_one({"type": "project"}, {"type": 1}) - assert project, "Project was not found! This is a bug" + project_name = legacy_io.active_project() + project_doc = get_project(project_name, fields=["_id"]) + assert project_doc, "Project was not found! This is a bug" self._assets_widget.refresh() self._assets_widget.setFocus() @@ -314,7 +316,7 @@ class LoaderWindow(QtWidgets.QDialog): ) # Clear the version information on asset change - self._thumbnail_widget.set_thumbnail(asset_ids) + self._thumbnail_widget.set_thumbnail("asset", asset_ids) self._version_info_widget.set_version(None) self.data["state"]["assetIds"] = asset_ids @@ -371,10 +373,12 @@ class LoaderWindow(QtWidgets.QDialog): version_doc["_id"] for version_doc in version_docs ] + source_type = "version" if not thumbnail_src_ids: + source_type = "asset" thumbnail_src_ids = self._assets_widget.get_selected_asset_ids() - self._thumbnail_widget.set_thumbnail(thumbnail_src_ids) + self._thumbnail_widget.set_thumbnail(source_type, thumbnail_src_ids) if self._repres_widget is not None: version_ids = [doc["_id"] for doc in version_docs] @@ -576,8 +580,7 @@ def show(debug=False, parent=None, use_context=False): legacy_io.install() any_project = next( - project for project in legacy_io.projects() - if project.get("active", True) is not False + project for project in get_projects(fields=["name"]) ) legacy_io.Session["AVALON_PROJECT"] = any_project["name"] diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 6f39c428be..e8e0480d9c 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -7,6 +7,15 @@ from uuid import uuid4 from Qt import QtCore, QtGui import qtawesome +from openpype.client import ( + get_assets, + get_subsets, + get_last_versions, + get_versions, + get_hero_versions, + get_version_by_name, + get_representations +) from openpype.pipeline import ( HeroVersionType, schema, @@ -56,7 +65,7 @@ class BaseRepresentationModel(object): remote_site = remote_provider = None if not project_name: - project_name = self.dbcon.Session.get("AVALON_PROJECT") + project_name = self.dbcon.active_project() else: self.dbcon.Session["AVALON_PROJECT"] = project_name @@ -254,57 +263,61 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): # because it also updates the information in other columns if index.column() == self.columns_index["version"]: item = index.internalPointer() - parent = item["_id"] + subset_id = item["_id"] if isinstance(value, HeroVersionType): - versions = list(self.dbcon.find({ - "type": {"$in": ["version", "hero_version"]}, - "parent": parent - }, sort=[("name", -1)])) - - version = None - last_version = None - for __version in versions: - if __version["type"] == "hero_version": - version = __version - elif last_version is None: - last_version = __version - - if version is not None and last_version is not None: - break - - _version = None - for __version in versions: - if __version["_id"] == version["version_id"]: - _version = __version - break - - version["data"] = _version["data"] - version["name"] = _version["name"] - version["is_from_latest"] = ( - last_version["_id"] == _version["_id"] - ) + version_doc = self._get_hero_version(subset_id) else: - version = self.dbcon.find_one({ - "name": value, - "type": "version", - "parent": parent - }) + project_name = self.dbcon.active_project() + version_doc = get_version_by_name( + project_name, subset_id, value + ) # update availability on active site when version changes - if self.sync_server.enabled and version: - query = self._repre_per_version_pipeline([version["_id"]], - self.active_site, - self.remote_site) + if self.sync_server.enabled and version_doc: + query = self._repre_per_version_pipeline( + [version_doc["_id"]], + self.active_site, + self.remote_site + ) docs = list(self.dbcon.aggregate(query)) if docs: repre = docs.pop() - version["data"].update(self._get_repre_dict(repre)) + version_doc["data"].update(self._get_repre_dict(repre)) - self.set_version(index, version) + self.set_version(index, version_doc) return super(SubsetsModel, self).setData(index, value, role) + def _get_hero_version(self, subset_id): + project_name = self.dbcon.active_project() + version_docs = get_versions( + project_name, subset_ids=[subset_id], hero=True + ) + standard_versions = [] + hero_version_doc = None + for version_doc in version_docs: + if version_doc["type"] == "hero_version": + hero_version_doc = version_doc + continue + standard_versions.append(version_doc) + + src_version_id = hero_version_doc["version_id"] + src_version = None + is_from_latest = True + for version_doc in reversed(sorted( + standard_versions, key=lambda item: item["name"] + )): + if version_doc["_id"] == src_version_id: + src_version = version_doc + break + is_from_latest = False + + hero_version_doc["data"] = src_version["data"] + hero_version_doc["name"] = src_version["name"] + hero_version_doc["is_from_latest"] = is_from_latest + return hero_version_doc + def set_version(self, index, version): """Update the version data of the given index. @@ -391,26 +404,25 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): item["repre_info"] = repre_info def _fetch(self): - asset_docs = self.dbcon.find( - { - "type": "asset", - "_id": {"$in": self._asset_ids} - }, - self.asset_doc_projection + project_name = self.dbcon.active_project() + asset_docs = get_assets( + project_name, + asset_ids=self._asset_ids, + fields=self.asset_doc_projection.keys() ) + asset_docs_by_id = { asset_doc["_id"]: asset_doc for asset_doc in asset_docs } subset_docs_by_id = {} - subset_docs = self.dbcon.find( - { - "type": "subset", - "parent": {"$in": self._asset_ids} - }, - self.subset_doc_projection + subset_docs = get_subsets( + project_name, + asset_ids=self._asset_ids, + fields=self.subset_doc_projection.keys() ) + subset_families = set() for subset_doc in subset_docs: if self._doc_fetching_stop: @@ -423,37 +435,13 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): subset_docs_by_id[subset_doc["_id"]] = subset_doc subset_ids = list(subset_docs_by_id.keys()) - _pipeline = [ - # Find all versions of those subsets - {"$match": { - "type": "version", - "parent": {"$in": subset_ids} - }}, - # Sorting versions all together - {"$sort": {"name": 1}}, - # Group them by "parent", but only take the last - {"$group": { - "_id": "$parent", - "_version_id": {"$last": "$_id"}, - "name": {"$last": "$name"}, - "type": {"$last": "$type"}, - "data": {"$last": "$data"}, - "locations": {"$last": "$locations"}, - "schema": {"$last": "$schema"} - }} - ] - last_versions_by_subset_id = dict() - for doc in self.dbcon.aggregate(_pipeline): - if self._doc_fetching_stop: - return - doc["parent"] = doc["_id"] - doc["_id"] = doc.pop("_version_id") - last_versions_by_subset_id[doc["parent"]] = doc + last_versions_by_subset_id = get_last_versions( + project_name, + subset_ids, + fields=["_id", "parent", "name", "type", "data", "schema"] + ) - hero_versions = self.dbcon.find({ - "type": "hero_version", - "parent": {"$in": subset_ids} - }) + hero_versions = get_hero_versions(project_name, subset_ids=subset_ids) missing_versions = [] for hero_version in hero_versions: version_id = hero_version["version_id"] @@ -462,10 +450,9 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): missing_versions_by_id = {} if missing_versions: - missing_version_docs = self.dbcon.find({ - "type": "version", - "_id": {"$in": missing_versions} - }) + missing_version_docs = get_versions( + project_name, version_ids=missing_versions + ) missing_versions_by_id = { missing_version_doc["_id"]: missing_version_doc for missing_version_doc in missing_version_docs @@ -488,23 +475,16 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): last_versions_by_subset_id[subset_id] = hero_version - self._doc_payload = { - "asset_docs_by_id": asset_docs_by_id, - "subset_docs_by_id": subset_docs_by_id, - "subset_families": subset_families, - "last_versions_by_subset_id": last_versions_by_subset_id - } - + repre_info = {} if self.sync_server.enabled: version_ids = set() for _subset_id, doc in last_versions_by_subset_id.items(): version_ids.add(doc["_id"]) - query = self._repre_per_version_pipeline(list(version_ids), - self.active_site, - self.remote_site) + query = self._repre_per_version_pipeline( + list(version_ids), self.active_site, self.remote_site + ) - repre_info = {} for doc in self.dbcon.aggregate(query): if self._doc_fetching_stop: return @@ -512,7 +492,13 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): doc["remote_provider"] = self.remote_provider repre_info[doc["_id"]] = doc - self._doc_payload["repre_info_by_version_id"] = repre_info + self._doc_payload = { + "asset_docs_by_id": asset_docs_by_id, + "subset_docs_by_id": subset_docs_by_id, + "subset_families": subset_families, + "last_versions_by_subset_id": last_versions_by_subset_id, + "repre_info_by_version_id": repre_info + } self.doc_fetched.emit() @@ -1062,6 +1048,16 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): "remote_site": "Remote" } + repre_projection = { + "_id": 1, + "name": 1, + "context.subset": 1, + "context.asset": 1, + "context.version": 1, + "context.representation": 1, + 'files.sites': 1 + } + def __init__(self, dbcon, header, version_ids): super(RepresentationModel, self).__init__() self.dbcon = dbcon @@ -1073,22 +1069,22 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): sync_server = active_site = remote_site = None active_provider = remote_provider = None - project = dbcon.Session["AVALON_PROJECT"] - if project: + project_name = dbcon.current_project() + if project_name: sync_server = manager.modules_by_name["sync_server"] - active_site = sync_server.get_active_site(project) - remote_site = sync_server.get_remote_site(project) + active_site = sync_server.get_active_site(project_name) + remote_site = sync_server.get_remote_site(project_name) # TODO refactor - active_provider = \ - sync_server.get_provider_for_site(project, - active_site) + active_provider = sync_server.get_provider_for_site( + project_name, active_site + ) if active_site == 'studio': active_provider = 'studio' - remote_provider = \ - sync_server.get_provider_for_site(project, - remote_site) + remote_provider = sync_server.get_provider_for_site( + project_name, remote_site + ) if remote_site == 'studio': remote_provider = 'studio' @@ -1127,8 +1123,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): if index.column() == self.Columns.index("name"): if item.get("isMerged"): return item["icon"] - else: - return self._icons["repre"] + return self._icons["repre"] active_index = self.Columns.index("active_site") remote_index = self.Columns.index("remote_site") @@ -1144,12 +1139,12 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): # site added, sync in progress progress_str = "not avail." if progress >= 0: - # progress == 0 for isMerged is unavailable if progress == 0 and item.get("isMerged"): progress_str = "not avail." else: - progress_str = "{}% {}".format(int(progress * 100), - label) + progress_str = "{}% {}".format( + int(progress * 100), label + ) return progress_str @@ -1192,7 +1187,6 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): if len(self.version_ids) > 1: group = repre_groups.get(doc["name"]) if not group: - group_item = Item() item_id = str(uuid4()) group_item.update({ @@ -1213,9 +1207,9 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): repre_groups_items[doc["name"]] = 0 group = group_item - progress = lib.get_progress_for_repre(doc, - self.active_site, - self.remote_site) + progress = lib.get_progress_for_repre( + doc, self.active_site, self.remote_site + ) active_site_icon = self._icons.get(self.active_provider) remote_site_icon = self._icons.get(self.remote_provider) @@ -1248,9 +1242,9 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): 'remote_site_progress': progress[self.remote_site] } if group: - group = self._sum_group_progress(doc["name"], group, - current_progress, - repre_groups_items) + group = self._sum_group_progress( + doc["name"], group, current_progress, repre_groups_items + ) self.add_child(item, group) @@ -1269,47 +1263,40 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): return self._items_by_id.get(item_id) def refresh(self): - docs = [] - session_project = self.dbcon.Session['AVALON_PROJECT'] - if not session_project: + project_name = self.dbcon.current_project() + if not project_name: return + repre_docs = [] if self.version_ids: # Simple find here for now, expected to receive lower number of # representations and logic could be in Python - docs = list(self.dbcon.find( - {"type": "representation", "parent": {"$in": self.version_ids}, - "files.sites.name": {"$exists": 1}}, self.projection())) - self._docs = docs + repre_docs = list(get_representations( + project_name, + version_ids=self.version_ids, + check_site_name=True, + fields=self.repre_projection.keys() + )) + + self._docs = repre_docs self.doc_fetched.emit() - @classmethod - def projection(cls): - return { - "_id": 1, - "name": 1, - "context.subset": 1, - "context.asset": 1, - "context.version": 1, - "context.representation": 1, - 'files.sites': 1 - } + def _sum_group_progress( + self, repre_name, group, current_item_progress, repre_groups_items + ): + """Update final group progress - def _sum_group_progress(self, repre_name, group, current_item_progress, - repre_groups_items): - """ - Update final group progress - Called after every item in group is added + Called after every item in group is added - Args: - repre_name(string) - group(dict): info about group of selected items - current_item_progress(dict): {'active_site_progress': XX, - 'remote_site_progress': YY} - repre_groups_items(dict) - Returns: - (dict): updated group info + Args: + repre_name(string) + group(dict): info about group of selected items + current_item_progress(dict): {'active_site_progress': XX, + 'remote_site_progress': YY} + repre_groups_items(dict) + Returns: + (dict): updated group info """ repre_groups_items[repre_name] += 1 diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 42fb62b632..6c7acc593d 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -7,6 +7,16 @@ import collections from Qt import QtWidgets, QtCore, QtGui +from openpype.client import ( + get_subset_families, + get_subset, + get_subsets, + get_version, + get_versions, + get_representations, + get_thumbnail_id_from_source, + get_thumbnail, +) from openpype.api import Anatomy from openpype.pipeline import HeroVersionType from openpype.pipeline.thumbnail import get_thumbnail_binary @@ -237,8 +247,7 @@ class SubsetWidget(QtWidgets.QWidget): self.model = model self.view = view - actual_project = dbcon.Session["AVALON_PROJECT"] - self.on_project_change(actual_project) + self.on_project_change(dbcon.current_project()) view.customContextMenuRequested.connect(self.on_context_menu) @@ -302,33 +311,23 @@ class SubsetWidget(QtWidgets.QWidget): item["version_document"] ) - subset_docs = list(self.dbcon.find( - { - "_id": {"$in": list(version_docs_by_subset_id.keys())}, - "type": "subset" - }, - { - "schema": 1, - "data.families": 1 - } + project_name = self.dbcon.active_project() + subset_docs = list(get_subsets( + project_name, + subset_ids=version_docs_by_subset_id.keys(), + fields=["schema", "data.families"] )) subset_docs_by_id = { subset_doc["_id"]: subset_doc for subset_doc in subset_docs } version_ids = list(version_docs_by_id.keys()) - repre_docs = self.dbcon.find( - # Query all representations for selected versions at once - { - "type": "representation", - "parent": {"$in": version_ids} - }, - # Query only name and parent from representation - { - "name": 1, - "parent": 1 - } + repre_docs = get_representations( + project_name, + version_ids=version_ids, + fields=["name", "parent"] ) + repre_docs_by_version_id = { version_id: [] for version_id in version_ids @@ -566,28 +565,42 @@ class SubsetWidget(QtWidgets.QWidget): # same representation available # Trigger - repre_ids = [] + project_name = self.dbcon.active_project() + subset_names_by_version_id = collections.defaultdict(set) for item in items: - representation = self.dbcon.find_one( - { - "type": "representation", - "name": representation_name, - "parent": item["version_document"]["_id"] - }, - {"_id": 1} - ) - if not representation: - self.echo("Subset '{}' has no representation '{}'".format( - item["subset"], representation_name - )) - continue - repre_ids.append(representation["_id"]) + version_id = item["version_document"]["_id"] + subset_names_by_version_id[version_id].add(item["subset"]) + + version_ids = set(subset_names_by_version_id.keys()) + repre_docs = get_representations( + project_name, + representation_names=[representation_name], + version_ids=version_ids, + fields=["_id", "parent"] + ) + + repre_ids = [] + for repre_doc in repre_docs: + repre_ids.append(repre_doc["_id"]) + + version_id = repre_doc["parent"] + if version_id not in version_ids: + version_ids.remove(version_id) + + for version_id in version_ids: + joined_subset_names = ", ".join([ + '"{}"'.format(subset) + for subset in subset_names_by_version_id[version_id] + ]) + self.echo("Subsets {} don't have representation '{}'".format( + joined_subset_names, representation_name + )) # get contexts only for selected menu option repre_contexts = get_repres_contexts(repre_ids, self.dbcon) - options = lib.get_options(action, loader, self, - list(repre_contexts.values())) - + options = lib.get_options( + action, loader, self, list(repre_contexts.values()) + ) error_info = _load_representations_by_loader( loader, repre_contexts, options=options ) @@ -661,27 +674,21 @@ class VersionTextEdit(QtWidgets.QTextEdit): print("Querying..") + project_name = self.dbcon.active_project() if not version_doc: - version_doc = self.dbcon.find_one({ - "_id": version_id, - "type": {"$in": ["version", "hero_version"]} - }) + version_doc = get_version(project_name, version_id=version_id) assert version_doc, "Not a valid version id" if version_doc["type"] == "hero_version": - _version_doc = self.dbcon.find_one({ - "_id": version_doc["version_id"], - "type": "version" - }) + _version_doc = get_version( + project_name, version_id=version_doc["version_id"] + ) version_doc["data"] = _version_doc["data"] version_doc["name"] = HeroVersionType( _version_doc["name"] ) - subset = self.dbcon.find_one({ - "_id": version_doc["parent"], - "type": "subset" - }) + subset = get_subset(project_name, subset_id=version_doc["parent"]) assert subset, "No valid subset parent for version" # Define readable creation timestamp @@ -752,7 +759,7 @@ class VersionTextEdit(QtWidgets.QTextEdit): if not source: return - project_name = self.dbcon.Session["AVALON_PROJECT"] + project_name = self.dbcon.current_project() if self._anatomy is None or self._anatomy.project_name != project_name: self._anatomy = Anatomy(project_name) @@ -833,24 +840,19 @@ class ThumbnailWidget(QtWidgets.QLabel): QtCore.Qt.SmoothTransformation ) - def set_thumbnail(self, doc_id=None): - if not doc_id: + def set_thumbnail(self, src_type, doc_ids): + if not doc_ids: self.set_pixmap() return - if isinstance(doc_id, (list, tuple)): - if len(doc_id) < 1: - self.set_pixmap() - return - doc_id = doc_id[0] + src_id = doc_ids[0] - doc = self.dbcon.find_one( - {"_id": doc_id}, - {"data.thumbnail_id"} + project_name = self.dbcon.active_project() + thumbnail_id = get_thumbnail_id_from_source( + project_name, + src_type, + src_id, ) - thumbnail_id = None - if doc: - thumbnail_id = doc.get("data", {}).get("thumbnail_id") if thumbnail_id == self.current_thumb_id: if self.current_thumbnail is None: self.set_pixmap() @@ -861,9 +863,7 @@ class ThumbnailWidget(QtWidgets.QLabel): self.set_pixmap() return - thumbnail_ent = self.dbcon.find_one( - {"type": "thumbnail", "_id": thumbnail_id} - ) + thumbnail_ent = get_thumbnail(project_name, thumbnail_id) if not thumbnail_ent: return @@ -917,21 +917,9 @@ class FamilyModel(QtGui.QStandardItemModel): def refresh(self): families = set() - if self.dbcon.Session.get("AVALON_PROJECT"): - result = list(self.dbcon.aggregate([ - {"$match": { - "type": "subset" - }}, - {"$project": { - "family": {"$arrayElemAt": ["$data.families", 0]} - }}, - {"$group": { - "_id": "family_group", - "families": {"$addToSet": "$family"} - }} - ])) - if result: - families = set(result[0]["families"]) + project_name = self.dbcon.current_project() + if project_name: + families = get_subset_families(project_name) root_item = self.invisibleRootItem() @@ -1213,8 +1201,8 @@ class RepresentationWidget(QtWidgets.QWidget): self.proxy_model = proxy_model self.sync_server_enabled = False - actual_project = dbcon.Session["AVALON_PROJECT"] - self.on_project_change(actual_project) + + self.on_project_change(dbcon.current_project()) self.model.refresh() @@ -1243,23 +1231,18 @@ class RepresentationWidget(QtWidgets.QWidget): for item in items: repre_ids.append(item["_id"]) - repre_docs = list(self.dbcon.find( - { - "type": "representation", - "_id": {"$in": repre_ids} - }, - { - "name": 1, - "parent": 1 - } - )) + project_name = self.dbcon.actual_project() + repre_docs = get_representations( + project_name, + representation_ids=repre_ids, + fields=["name", "parent"] + ) + version_ids = [ repre_doc["parent"] for repre_doc in repre_docs ] - version_docs = self.dbcon.find({ - "_id": {"$in": version_ids} - }) + version_docs = get_versions(project_name, version_ids=version_ids) version_docs_by_id = {} version_docs_by_subset_id = collections.defaultdict(list) @@ -1269,15 +1252,10 @@ class RepresentationWidget(QtWidgets.QWidget): version_docs_by_id[version_id] = version_doc version_docs_by_subset_id[subset_id].append(version_doc) - subset_docs = list(self.dbcon.find( - { - "_id": {"$in": list(version_docs_by_subset_id.keys())}, - "type": "subset" - }, - { - "schema": 1, - "data.families": 1 - } + subset_docs = list(get_subsets( + project_name, + subset_ids=version_docs_by_subset_id.keys(), + fields=["schema", "data.families"] )) subset_docs_by_id = { subset_doc["_id"]: subset_doc @@ -1446,13 +1424,12 @@ class RepresentationWidget(QtWidgets.QWidget): self._process_action(items, menu, point) def _process_action(self, items, menu, point): - """ - Show the context action menu and process selected + """Show the context action menu and process selected - Args: - items(dict): menu items - menu(OptionalMenu) - point(PointIndex) + Args: + items(dict): menu items + menu(OptionalMenu) + point(PointIndex) """ global_point = self.tree_view.mapToGlobal(point) action = menu.exec_(global_point) @@ -1468,21 +1445,23 @@ class RepresentationWidget(QtWidgets.QWidget): data_by_repre_id = {} selected_side = action_representation.get("selected_side") + is_sync_loader = tools_lib.is_sync_loader(loader) for item in items: - if tools_lib.is_sync_loader(loader): - site_name = "{}_site_name".format(selected_side) - data = { - "_id": item.get("_id"), - "site_name": item.get(site_name), - "project_name": self.dbcon.Session["AVALON_PROJECT"] - } + item_id = item.get("_id") + repre_ids.append(item_id) + if not is_sync_loader: + continue - if not data["site_name"]: - continue + site_name = "{}_site_name".format(selected_side) + data_site_name = item.get(site_name) + if not data_site_name: + continue - data_by_repre_id[data["_id"]] = data - - repre_ids.append(item.get("_id")) + data_by_repre_id[item_id] = { + "_id": item_id, + "site_name": data_site_name, + "project_name": self.dbcon.active_project() + } repre_contexts = get_repres_contexts(repre_ids, self.dbcon) options = lib.get_options(action, loader, self, diff --git a/openpype/tools/mayalookassigner/app.py b/openpype/tools/mayalookassigner/app.py index 1b6cad77a8..427edf8245 100644 --- a/openpype/tools/mayalookassigner/app.py +++ b/openpype/tools/mayalookassigner/app.py @@ -4,6 +4,7 @@ import logging from Qt import QtWidgets, QtCore +from openpype.client import get_last_version_for_subset from openpype import style from openpype.pipeline import legacy_io from openpype.tools.utils.lib import qt_app_context @@ -211,6 +212,7 @@ class MayaLookAssignerWindow(QtWidgets.QWidget): selection = self.assign_selected.isChecked() asset_nodes = self.asset_outliner.get_nodes(selection=selection) + project_name = legacy_io.active_project() start = time.time() for i, (asset, item) in enumerate(asset_nodes.items()): @@ -222,23 +224,20 @@ class MayaLookAssignerWindow(QtWidgets.QWidget): assign_look = next((subset for subset in item["looks"] if subset["name"] in looks), None) if not assign_look: - self.echo("{} No matching selected " - "look for {}".format(prefix, asset)) + self.echo( + "{} No matching selected look for {}".format(prefix, asset) + ) continue # Get the latest version of this asset's look subset - version = legacy_io.find_one( - { - "type": "version", - "parent": assign_look["_id"] - }, - sort=[("name", -1)] + version = get_last_version_for_subset( + project_name, subset_id=assign_look["_id"], fields=["_id"] ) subset_name = assign_look["name"] - self.echo("{} Assigning {} to {}\t".format(prefix, - subset_name, - asset)) + self.echo("{} Assigning {} to {}\t".format( + prefix, subset_name, asset + )) nodes = item["nodes"] if cmds.pluginInfo('vrayformaya', query=True, loaded=True): diff --git a/openpype/tools/mayalookassigner/commands.py b/openpype/tools/mayalookassigner/commands.py index d41d8ca5a2..a4fc1fab70 100644 --- a/openpype/tools/mayalookassigner/commands.py +++ b/openpype/tools/mayalookassigner/commands.py @@ -2,9 +2,9 @@ from collections import defaultdict import logging import os -from bson.objectid import ObjectId import maya.cmds as cmds +from openpype.client import get_asset from openpype.pipeline import ( legacy_io, remove_container, @@ -159,11 +159,9 @@ def create_items_from_nodes(nodes): log.warning("No id hashes") return asset_view_items + project_name = legacy_io.active_project() for _id, id_nodes in id_hashes.items(): - asset = legacy_io.find_one( - {"_id": ObjectId(_id)}, - projection={"name": True} - ) + asset = get_asset(project_name, asset_id=_id, fields=["name"]) # Skip if asset id is not found if not asset: @@ -180,10 +178,12 @@ def create_items_from_nodes(nodes): namespace = get_namespace_from_node(node) namespaces.add(namespace) - asset_view_items.append({"label": asset["name"], - "asset": asset, - "looks": looks, - "namespaces": namespaces}) + asset_view_items.append({ + "label": asset["name"], + "asset": asset, + "looks": looks, + "namespaces": namespaces + }) return asset_view_items diff --git a/openpype/tools/mayalookassigner/vray_proxies.py b/openpype/tools/mayalookassigner/vray_proxies.py index 3523b24bf3..b2ba21f944 100644 --- a/openpype/tools/mayalookassigner/vray_proxies.py +++ b/openpype/tools/mayalookassigner/vray_proxies.py @@ -6,11 +6,14 @@ import logging import json import six -from bson.objectid import ObjectId import alembic.Abc from maya import cmds +from openpype.client import ( + get_representation_by_name, + get_last_version_for_subset, +) from openpype.pipeline import ( legacy_io, load_container, @@ -155,13 +158,12 @@ def get_look_relationships(version_id): Returns: dict: Dictionary of relations. - """ - json_representation = legacy_io.find_one({ - "type": "representation", - "parent": version_id, - "name": "json" - }) + + project_name = legacy_io.active_project() + json_representation = get_representation_by_name( + project_name, representation_name="json", version_id=version_id + ) # Load relationships shader_relation = get_representation_path(json_representation) @@ -184,12 +186,12 @@ def load_look(version_id): list of shader nodes. """ + + project_name = legacy_io.active_project() # Get representations of shader file and relationships - look_representation = legacy_io.find_one({ - "type": "representation", - "parent": version_id, - "name": "ma" - }) + look_representation = get_representation_by_name( + project_name, representation_name="ma", version_id=version_id + ) # See if representation is already loaded, if so reuse it. host = registered_host() @@ -220,42 +222,6 @@ def load_look(version_id): return shader_nodes -def get_latest_version(asset_id, subset): - # type: (str, str) -> dict - """Get latest version of subset. - - Args: - asset_id (str): Asset ID - subset (str): Subset name. - - Returns: - Latest version - - Throws: - RuntimeError: When subset or version doesn't exist. - - """ - subset = legacy_io.find_one({ - "name": subset, - "parent": ObjectId(asset_id), - "type": "subset" - }) - if not subset: - raise RuntimeError("Subset does not exist: %s" % subset) - - version = legacy_io.find_one( - { - "type": "version", - "parent": subset["_id"] - }, - sort=[("name", -1)] - ) - if not version: - raise RuntimeError("Version does not exist.") - - return version - - def vrayproxy_assign_look(vrayproxy, subset="lookDefault"): # type: (str, str) -> None """Assign look to vray proxy. @@ -281,13 +247,20 @@ def vrayproxy_assign_look(vrayproxy, subset="lookDefault"): asset_id = node_id.split(":", 1)[0] node_ids_by_asset_id[asset_id].add(node_id) + project_name = legacy_io.active_project() for asset_id, node_ids in node_ids_by_asset_id.items(): # Get latest look version - try: - version = get_latest_version(asset_id, subset=subset) - except RuntimeError as exc: - print(exc) + version = get_last_version_for_subset( + project_name, + subset_name=subset, + asset_id=asset_id, + fields=["_id"] + ) + if not version: + print("Didn't find last version for subset name {}".format( + subset + )) continue relationships = get_look_relationships(version["_id"]) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 223cfa629d..c5bde5aaec 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -7,6 +7,11 @@ from pymongo import UpdateOne, DeleteOne from Qt import QtCore, QtGui +from openpype.client import ( + get_project, + get_assets, + get_asset_ids_with_subsets, +) from openpype.lib import ( CURRENT_DOC_SCHEMAS, PypeLogger, @@ -255,10 +260,11 @@ class HierarchyModel(QtCore.QAbstractItemModel): return # Find project'd document - project_doc = self.dbcon.database[project_name].find_one( - {"type": "project"}, - ProjectItem.query_projection + project_doc = get_project( + project_name, + fields=list(ProjectItem.query_projection.keys()) ) + # Skip if project document does not exist # - this shouldn't happen using only UI elements if not project_doc: @@ -269,9 +275,8 @@ class HierarchyModel(QtCore.QAbstractItemModel): self.add_item(project_item) # Query all assets of the project - asset_docs = self.dbcon.database[project_name].find( - {"type": "asset"}, - AssetItem.query_projection + asset_docs = get_assets( + project_name, fields=AssetItem.query_projection.keys() ) asset_docs_by_id = { asset_doc["_id"]: asset_doc @@ -282,31 +287,16 @@ class HierarchyModel(QtCore.QAbstractItemModel): # if asset item can be modified (name and hierarchy change) # - the same must be applied to all it's parents asset_ids = list(asset_docs_by_id.keys()) - result = [] + asset_ids_with_subsets = [] if asset_ids: - result = self.dbcon.database[project_name].aggregate([ - { - "$match": { - "type": "subset", - "parent": {"$in": asset_ids} - } - }, - { - "$group": { - "_id": "$parent", - "count": {"$sum": 1} - } - } - ]) + asset_ids_with_subsets = get_asset_ids_with_subsets( + project_name, asset_ids=asset_ids + ) asset_modifiable = { - asset_id: True + asset_id: asset_id not in asset_ids_with_subsets for asset_id in asset_docs_by_id.keys() } - for item in result: - asset_id = item["_id"] - count = item["count"] - asset_modifiable[asset_id] = count < 1 # Store assets by their visual parent to be able create their hierarchy asset_docs_by_parent_id = collections.defaultdict(list) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 4b5bc36aeb..e9d2874c54 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -3,6 +3,7 @@ from queue import Queue from Qt import QtWidgets, QtCore, QtGui +from openpype.client import get_project from .delegates import ( NumberDelegate, NameDelegate, @@ -47,12 +48,8 @@ class ProjectDocCache: def set_project(self, project_name): self.project_doc = None - if not project_name: - return - - self.project_doc = self.dbcon.database[project_name].find_one( - {"type": "project"} - ) + if project_name: + self.project_doc = get_project(project_name) class ToolsCache: diff --git a/openpype/tools/project_manager/project_manager/widgets.py b/openpype/tools/project_manager/project_manager/widgets.py index dc75b30bd7..371d1ba2ef 100644 --- a/openpype/tools/project_manager/project_manager/widgets.py +++ b/openpype/tools/project_manager/project_manager/widgets.py @@ -1,5 +1,6 @@ import re +from openpype.client import get_projects from .constants import ( NAME_ALLOWED_SYMBOLS, NAME_REGEX @@ -272,15 +273,9 @@ class CreateProjectDialog(QtWidgets.QDialog): def _get_existing_projects(self): project_names = set() project_codes = set() - for project_name in self.dbcon.database.collection_names(): - # Each collection will have exactly one project document - project_doc = self.dbcon.database[project_name].find_one( - {"type": "project"}, - {"name": 1, "data.code": 1} - ) - if not project_doc: - continue - + for project_doc in get_projects( + inactive=True, fields=["name", "data.code"] + ): project_name = project_doc.get("name") if not project_name: continue diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 2973d6a5bb..915fb7f32e 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -13,6 +13,7 @@ except Exception: import pyblish.api +from openpype.client import get_assets from openpype.pipeline import ( PublishValidationError, registered_host, @@ -116,10 +117,10 @@ class AssetDocsCache: def _query(self): if self._asset_docs is None: - asset_docs = list(self.dbcon.find( - {"type": "asset"}, - self.projection - )) + project_name = self.dbcon.active_project() + asset_docs = get_assets( + project_name, fields=self.projection.keys() + ) task_names_by_asset_name = {} for asset_doc in asset_docs: asset_name = asset_doc["name"] diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 9e357f3a56..d579831b21 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -9,6 +9,8 @@ try: except Exception: commonmark = None from Qt import QtWidgets, QtCore, QtGui + +from openpype.client import get_asset, get_subsets from openpype.lib import TaskNotSetError from openpype.pipeline.create import ( CreatorError, @@ -647,21 +649,19 @@ class CreateDialog(QtWidgets.QDialog): if asset_name is None: return - asset_doc = self.dbcon.find_one({ - "type": "asset", - "name": asset_name - }) + project_name = self.dbcon.active_project() + asset_doc = get_asset(project_name, asset_name=asset_name) self._asset_doc = asset_doc if asset_doc: - subset_docs = self.dbcon.find( - { - "type": "subset", - "parent": asset_doc["_id"] - }, - {"name": 1} + asset_id = asset_doc["_id"] + subset_docs = get_subsets( + project_name, asset_ids=[asset_id], fields=["name"] ) - self._subset_names = set(subset_docs.distinct("name")) + self._subset_names = { + subset_doc["name"] + for subset_doc in subset_docs + } if not asset_doc: self.subset_name_input.setText("< Asset is not set >") diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 8d72020c98..9cf69ed650 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -5,8 +5,14 @@ from collections import defaultdict from Qt import QtCore, QtGui import qtawesome -from bson.objectid import ObjectId +from openpype.client import ( + get_asset, + get_subset, + get_version, + get_last_version_for_subset, + get_representation, +) from openpype.pipeline import ( legacy_io, schema, @@ -55,7 +61,7 @@ class InventoryModel(TreeModel): if not self.sync_enabled: return - project_name = legacy_io.Session["AVALON_PROJECT"] + project_name = legacy_io.current_project() active_site = sync_server.get_active_site(project_name) remote_site = sync_server.get_remote_site(project_name) @@ -291,6 +297,9 @@ class InventoryModel(TreeModel): node.Item: root node which has children added based on the data """ + # NOTE: @iLLiCiTiT this need refactor + project_name = legacy_io.active_project() + self.beginResetModel() # Group by representation @@ -304,32 +313,36 @@ class InventoryModel(TreeModel): for repre_id, group_dict in sorted(grouped.items()): group_items = group_dict["items"] # Get parenthood per group - representation = legacy_io.find_one({"_id": ObjectId(repre_id)}) + representation = get_representation( + project_name, representation_id=repre_id + ) if not representation: not_found["representation"].append(group_items) not_found_ids.append(repre_id) continue - version = legacy_io.find_one({"_id": representation["parent"]}) + version = get_version( + project_name, version_id=representation["parent"] + ) if not version: not_found["version"].append(group_items) not_found_ids.append(repre_id) continue elif version["type"] == "hero_version": - _version = legacy_io.find_one({ - "_id": version["version_id"] - }) + _version = get_version( + project_name, version_id=version["version_id"] + ) version["name"] = HeroVersionType(_version["name"]) version["data"] = _version["data"] - subset = legacy_io.find_one({"_id": version["parent"]}) + subset = get_subset(project_name, subset_id=version["parent"]) if not subset: not_found["subset"].append(group_items) not_found_ids.append(repre_id) continue - asset = legacy_io.find_one({"_id": subset["parent"]}) + asset = get_asset(project_name, asset_id=subset["parent"]) if not asset: not_found["asset"].append(group_items) not_found_ids.append(repre_id) @@ -390,10 +403,9 @@ class InventoryModel(TreeModel): # Store the highest available version so the model can know # whether current version is currently up-to-date. - highest_version = legacy_io.find_one({ - "type": "version", - "parent": version["parent"] - }, sort=[("name", -1)]) + highest_version = get_last_version_for_subset( + project_name, subset_id=version["parent"] + ) # create the group header group_node = Item() diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index b2d770330f..b940c66a56 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -4,6 +4,16 @@ from Qt import QtWidgets, QtCore import qtawesome from bson.objectid import ObjectId +from openpype.client import ( + get_asset, + get_assets, + get_subset, + get_subsets, + get_versions, + get_hero_versions, + get_last_versions, + get_representations, +) from openpype.pipeline import legacy_io from openpype.pipeline.load import ( discover_loader_plugins, @@ -144,6 +154,9 @@ class SwitchAssetDialog(QtWidgets.QDialog): self._prepare_content_data() self.refresh(True) + def active_project(self): + return legacy_io.active_project() + def _prepare_content_data(self): repre_ids = set() content_loaders = set() @@ -151,10 +164,12 @@ class SwitchAssetDialog(QtWidgets.QDialog): repre_ids.add(ObjectId(item["representation"])) content_loaders.add(item["loader"]) - repres = list(legacy_io.find({ - "type": {"$in": ["representation", "archived_representation"]}, - "_id": {"$in": list(repre_ids)} - })) + project_name = self.active_project() + repres = get_representations( + project_name, + representation_ids=repre_ids, + archived=True + ) repres_by_id = {repre["_id"]: repre for repre in repres} # stash context values, works only for single representation @@ -179,10 +194,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): content_repres[repre_id] = repres_by_id[repre_id] version_ids.append(repre["parent"]) - versions = legacy_io.find({ - "type": {"$in": ["version", "hero_version"]}, - "_id": {"$in": list(set(version_ids))} - }) + versions = get_versions( + project_name, + version_ids=set(version_ids), + hero=True + ) content_versions = {} hero_version_ids = set() for version in versions: @@ -198,10 +214,9 @@ class SwitchAssetDialog(QtWidgets.QDialog): else: subset_ids.append(content_versions[version_id]["parent"]) - subsets = legacy_io.find({ - "type": {"$in": ["subset", "archived_subset"]}, - "_id": {"$in": subset_ids} - }) + subsets = get_subsets( + project_name, subset_ids=subset_ids, archived=True + ) subsets_by_id = {sub["_id"]: sub for sub in subsets} asset_ids = [] @@ -220,10 +235,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): asset_ids.append(subset["parent"]) content_subsets[subset_id] = subset - assets = legacy_io.find({ - "type": {"$in": ["asset", "archived_asset"]}, - "_id": {"$in": list(asset_ids)} - }) + assets = get_assets(project_name, asset_ids=asset_ids, archived=True) assets_by_id = {asset["_id"]: asset for asset in assets} missing_assets = [] @@ -472,9 +484,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): # Prepare asset document if asset is selected asset_doc = None if selected_asset: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": True} + asset_doc = get_asset( + self.active_project(), + asset_name=selected_asset, + fields=["_id"] ) if not asset_doc: return [] @@ -523,38 +536,35 @@ class SwitchAssetDialog(QtWidgets.QDialog): def _get_current_output_repre_ids_xxx( self, asset_doc, selected_subset, selected_repre ): - subset_doc = legacy_io.find_one( - { - "type": "subset", - "name": selected_subset, - "parent": asset_doc["_id"] - }, - {"_id": True} + project_name = self.active_project() + subset_doc = get_subset( + project_name, + subset_name=selected_subset, + asset_id=asset_doc["_id"], + fields=["_id"] ) + subset_id = subset_doc["_id"] last_versions_by_subset_id = self.find_last_versions([subset_id]) version_doc = last_versions_by_subset_id.get(subset_id) if not version_doc: return [] - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": version_doc["_id"], - "name": selected_repre - }, - {"_id": True} + repre_docs = get_representations( + project_name, + version_ids=[version_doc["_id"]], + representation_names=[selected_repre], + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_xxo(self, asset_doc, selected_subset): - subset_doc = legacy_io.find_one( - { - "type": "subset", - "parent": asset_doc["_id"], - "name": selected_subset - }, - {"_id": True} + project_name = self.active_project() + subset_doc = get_subset( + project_name, + asset_id=asset_doc["_id"], + subset_name=selected_subset, + fields=["_id"] ) if not subset_doc: return [] @@ -563,41 +573,51 @@ class SwitchAssetDialog(QtWidgets.QDialog): for repre_doc in self.content_repres.values(): repre_names.add(repre_doc["name"]) - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": subset_doc["_id"], - "name": {"$in": list(repre_names)} - }, - {"_id": True} + # TODO where to take version ids? + version_ids = [] + repre_docs = get_representations( + project_name, + representation_names=repre_names, + version_ids=version_ids, + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_xox(self, asset_doc, selected_repre): - susbet_names = set() + subset_names = set() for subset_doc in self.content_subsets.values(): - susbet_names.add(subset_doc["name"]) + subset_names.add(subset_doc["name"]) - subset_docs = legacy_io.find( - { - "type": "subset", - "name": {"$in": list(susbet_names)}, - "parent": asset_doc["_id"] - }, - {"_id": True} + project_name = self.active_project() + subset_docs = get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + subset_names=subset_names, + fields=["_id", "name"] ) - subset_ids = [subset_doc["_id"] for subset_doc in subset_docs] - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": {"$in": subset_ids}, - "name": selected_repre - }, - {"_id": True} + subset_name_by_id = { + subset_doc["_id"]: subset_doc["name"] + for subset_doc in subset_docs + } + subset_ids = list(subset_name_by_id.keys()) + last_versions_by_subset_id = self.find_last_versions(subset_ids) + last_version_id_by_subset_name = {} + for subset_id, last_version in last_versions_by_subset_id.items(): + subset_name = subset_name_by_id[subset_id] + last_version_id_by_subset_name[subset_name] = ( + last_version["_id"] + ) + + repre_docs = get_representations( + project_name, + version_ids=last_version_id_by_subset_name.values(), + representation_names=[selected_repre], + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_xoo(self, asset_doc): + project_name = self.active_project() repres_by_subset_name = collections.defaultdict(set) for repre_doc in self.content_repres.values(): repre_name = repre_doc["name"] @@ -606,13 +626,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): subset_name = subset_doc["name"] repres_by_subset_name[subset_name].add(repre_name) - subset_docs = list(legacy_io.find( - { - "type": "subset", - "parent": asset_doc["_id"], - "name": {"$in": list(repres_by_subset_name.keys())} - }, - {"_id": True, "name": True} + subset_docs = list(get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + subset_names=repres_by_subset_name.keys(), + fields=["_id", "name"] )) subset_name_by_id = { subset_doc["_id"]: subset_doc["name"] @@ -627,60 +645,59 @@ class SwitchAssetDialog(QtWidgets.QDialog): last_version["_id"] ) - repre_or_query = [] + repre_names_by_version_id = {} for subset_name, repre_names in repres_by_subset_name.items(): version_id = last_version_id_by_subset_name.get(subset_name) # This should not happen but why to crash? - if version_id is None: - continue - repre_or_query.append({ - "parent": version_id, - "name": {"$in": list(repre_names)} - }) - repre_docs = legacy_io.find( - {"$or": repre_or_query}, - {"_id": True} + if version_id is not None: + repre_names_by_version_id[version_id] = list(repre_names) + + repre_docs = get_representations( + project_name, + names_by_version_ids=repre_names_by_version_id, + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_oxx( self, selected_subset, selected_repre ): - subset_docs = list(legacy_io.find({ - "type": "subset", - "parent": {"$in": list(self.content_assets.keys())}, - "name": selected_subset - })) + project_name = self.active_project() + subset_docs = get_subsets( + project_name, + asset_ids=self.content_assets.keys(), + subset_names=[selected_subset], + fields=["_id"] + ) subset_ids = [subset_doc["_id"] for subset_doc in subset_docs] last_versions_by_subset_id = self.find_last_versions(subset_ids) last_version_ids = [ last_version["_id"] for last_version in last_versions_by_subset_id.values() ] - repre_docs = legacy_io.find({ - "type": "representation", - "parent": {"$in": last_version_ids}, - "name": selected_repre - }) - + repre_docs = get_representations( + project_name, + version_ids=last_version_ids, + representation_names=[selected_repre], + fields=["_id"] + ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_oxo(self, selected_subset): - subset_docs = list(legacy_io.find( - { - "type": "subset", - "parent": {"$in": list(self.content_assets.keys())}, - "name": selected_subset - }, - {"_id": True, "parent": True} - )) - if not subset_docs: - return list() - + project_name = self.active_project() + subset_docs = get_subsets( + project_name, + asset_ids=self.content_assets.keys(), + subset_names=[selected_subset], + fields=["_id", "parent"] + ) subset_docs_by_id = { subset_doc["_id"]: subset_doc for subset_doc in subset_docs } + if not subset_docs: + return list() + last_versions_by_subset_id = self.find_last_versions( subset_docs_by_id.keys() ) @@ -702,56 +719,44 @@ class SwitchAssetDialog(QtWidgets.QDialog): asset_id = asset_doc["_id"] repre_names_by_asset_id[asset_id].add(repre_name) - repre_or_query = [] + repre_names_by_version_id = {} for last_version_id, subset_id in subset_id_by_version_id.items(): subset_doc = subset_docs_by_id[subset_id] asset_id = subset_doc["parent"] repre_names = repre_names_by_asset_id.get(asset_id) if not repre_names: continue - repre_or_query.append({ - "parent": last_version_id, - "name": {"$in": list(repre_names)} - }) - repre_docs = legacy_io.find( - { - "type": "representation", - "$or": repre_or_query - }, - {"_id": True} - ) + repre_names_by_version_id[last_version_id] = repre_names + repre_docs = get_representations( + project_name, + names_by_version_ids=repre_names_by_version_id, + fields=["_id"] + ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_oox(self, selected_repre): - repre_docs = legacy_io.find( - { - "name": selected_repre, - "parent": {"$in": list(self.content_versions.keys())} - }, - {"_id": True} + project_name = self.active_project() + repre_docs = get_representations( + project_name, + representation_names=[selected_repre], + version_ids=self.content_versions.keys(), + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_asset_box_values(self): - asset_docs = legacy_io.find( - {"type": "asset"}, - {"_id": 1, "name": 1} - ) + project_name = self.active_project() + asset_docs = get_assets(project_name, fields=["_id", "name"]) asset_names_by_id = { asset_doc["_id"]: asset_doc["name"] for asset_doc in asset_docs } - subsets = legacy_io.find( - { - "type": "subset", - "parent": {"$in": list(asset_names_by_id.keys())} - }, - { - "parent": 1 - } + subsets = get_subsets( + project_name, + asset_ids=asset_names_by_id.keys(), + fields=["parent"] ) - filtered_assets = [] for subset in subsets: asset_name = asset_names_by_id[subset["parent"]] @@ -760,25 +765,20 @@ class SwitchAssetDialog(QtWidgets.QDialog): return sorted(filtered_assets) def _get_subset_box_values(self): + project_name = self.active_project() selected_asset = self._assets_box.get_valid_value() if selected_asset: - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": selected_asset - }) + asset_doc = get_asset( + project_name, asset_name=selected_asset, fields=["_id"] + ) asset_ids = [asset_doc["_id"]] else: asset_ids = list(self.content_assets.keys()) - subsets = legacy_io.find( - { - "type": "subset", - "parent": {"$in": asset_ids} - }, - { - "parent": 1, - "name": 1 - } + subsets = get_subsets( + project_name, + asset_ids=asset_ids, + fields=["parent", "name"] ) subset_names_by_parent_id = collections.defaultdict(set) @@ -800,6 +800,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): def _representations_box_values(self): # NOTE hero versions are not used because it is expected that # hero version has same representations as latests + project_name = self.active_project() selected_asset = self._assets_box.currentText() selected_subset = self._subsets_box.currentText() @@ -807,16 +808,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [ ] [ ] [?] if not selected_asset and not selected_subset: # Find all representations of selection's subsets - possible_repres = list(legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(self.content_versions.keys())} - }, - { - "parent": 1, - "name": 1 - } - )) + possible_repres = get_representations( + project_name, + version_ids=self.content_versions.keys(), + fields=["parent", "name"] + ) possible_repres_by_parent = collections.defaultdict(set) for repre in possible_repres: @@ -836,29 +832,23 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [x] [?] if selected_asset and selected_subset: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + asset_doc = get_asset( + project_name, asset_name=selected_asset, fields=["_id"] ) - subset_doc = legacy_io.find_one( - { - "type": "subset", - "name": selected_subset, - "parent": asset_doc["_id"] - }, - {"_id": 1} + subset_doc = get_subset( + project_name, + asset_id=asset_doc["_id"], + subset_name=selected_subset, + fields=["_id"] ) + subset_id = subset_doc["_id"] last_versions_by_subset_id = self.find_last_versions([subset_id]) version_doc = last_versions_by_subset_id.get(subset_id) - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": version_doc["_id"] - }, - { - "name": 1 - } + repre_docs = get_representations( + project_name, + version_ids=[version_doc["_id"]], + fields=["name"] ) return [ repre_doc["name"] @@ -868,9 +858,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [ ] [?] # If asset only is selected if selected_asset: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + asset_doc = get_asset( + project_name, asset_name=selected_asset, fields=["_id"] ) if not asset_doc: return list() @@ -879,13 +868,12 @@ class SwitchAssetDialog(QtWidgets.QDialog): subset_names = set() for subset_doc in self.content_subsets.values(): subset_names.add(subset_doc["name"]) - subset_docs = legacy_io.find( - { - "type": "subset", - "parent": asset_doc["_id"], - "name": {"$in": list(subset_names)} - }, - {"_id": 1} + + subset_docs = get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + subset_names=subset_names, + fields=["_id"] ) subset_ids = [ subset_doc["_id"] @@ -903,15 +891,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): if not subset_id_by_version_id: return list() - repre_docs = list(legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(subset_id_by_version_id.keys())} - }, - { - "name": 1, - "parent": 1 - } + repre_docs = list(get_representations( + project_name, + version_ids=subset_id_by_version_id.keys(), + fields=["name", "parent"] )) if not repre_docs: return list() @@ -933,13 +916,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): return list(available_repres) # [ ] [x] [?] - subset_docs = list(legacy_io.find( - { - "type": "subset", - "parent": {"$in": list(self.content_assets.keys())}, - "name": selected_subset - }, - {"_id": 1, "parent": 1} + subset_docs = list(get_subsets( + project_name, + asset_ids=self.content_assets.keys(), + subset_names=[selected_subset], + fields=["_id", "parent"] )) if not subset_docs: return list() @@ -960,16 +941,13 @@ class SwitchAssetDialog(QtWidgets.QDialog): if not subset_id_by_version_id: return list() - repre_docs = list(legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(subset_id_by_version_id.keys())} - }, - { - "name": 1, - "parent": 1 - } - )) + repre_docs = list( + get_representations( + project_name, + subset_ids=subset_id_by_version_id.keys(), + fields=["name", "parent"] + ) + ) if not repre_docs: return list() @@ -1016,14 +994,14 @@ class SwitchAssetDialog(QtWidgets.QDialog): return # [x] [ ] [?] - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + project_name = self.active_project() + asset_doc = get_asset( + project_name, asset_name=selected_asset, fields=["_id"] ) - subset_docs = legacy_io.find( - {"type": "subset", "parent": asset_doc["_id"]}, - {"name": 1} + subset_docs = get_subsets( + project_name, asset_ids=[asset_doc["_id"]], fields=["name"] ) + subset_names = set( subset_doc["name"] for subset_doc in subset_docs @@ -1035,27 +1013,12 @@ class SwitchAssetDialog(QtWidgets.QDialog): break def find_last_versions(self, subset_ids): - _pipeline = [ - # Find all versions of those subsets - {"$match": { - "type": "version", - "parent": {"$in": list(subset_ids)} - }}, - # Sorting versions all together - {"$sort": {"name": 1}}, - # Group them by "parent", but only take the last - {"$group": { - "_id": "$parent", - "_version_id": {"$last": "$_id"}, - "type": {"$last": "$type"} - }} - ] - last_versions_by_subset_id = dict() - for doc in legacy_io.aggregate(_pipeline): - doc["parent"] = doc["_id"] - doc["_id"] = doc.pop("_version_id") - last_versions_by_subset_id[doc["parent"]] = doc - return last_versions_by_subset_id + project_name = self.active_project() + return get_last_versions( + project_name, + subset_ids=subset_ids, + fields=["_id", "parent", "type"] + ) def _is_repre_ok(self, validation_state): selected_asset = self._assets_box.get_valid_value() @@ -1078,33 +1041,28 @@ class SwitchAssetDialog(QtWidgets.QDialog): return # [x] [x] [ ] + project_name = self.active_project() if selected_asset is not None and selected_subset is not None: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + asset_doc = get_asset( + project_name, asset_name=selected_asset, fields=["_id"] ) - subset_doc = legacy_io.find_one( - { - "type": "subset", - "parent": asset_doc["_id"], - "name": selected_subset - }, - {"_id": 1} + subset_doc = get_subset( + project_name, + asset_id=asset_doc["_id"], + subset_name=selected_subset, + fields=["_id"] ) - last_versions_by_subset_id = self.find_last_versions( - [subset_doc["_id"]] - ) - last_version = last_versions_by_subset_id.get(subset_doc["_id"]) + subset_id = subset_doc["_id"] + last_versions_by_subset_id = self.find_last_versions([subset_id]) + last_version = last_versions_by_subset_id.get(subset_id) if not last_version: validation_state.repre_ok = False return - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": last_version["_id"] - }, - {"name": 1} + repre_docs = get_representations( + project_name, + version_ids=[last_version["_id"]], + fields=["name"] ) repre_names = set( @@ -1119,16 +1077,13 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [ ] [ ] if selected_asset is not None: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + asset_doc = get_asset( + project_name, asset_name=selected_asset, fields=["_id"] ) - subset_docs = list(legacy_io.find( - { - "type": "subset", - "parent": asset_doc["_id"] - }, - {"_id": 1, "name": 1} + subset_docs = list(get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + fields=["_id", "name"] )) subset_name_by_id = {} @@ -1145,15 +1100,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): version_id = last_version["_id"] subset_id_by_version_id[version_id] = subset_id - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(subset_id_by_version_id.keys())} - }, - { - "name": 1, - "parent": 1 - } + repre_docs = get_representations( + project_name, + subset_ids=subset_id_by_version_id.keys(), + fields=["name", "parent"] ) repres_by_subset_name = {} for repre_doc in repre_docs: @@ -1176,15 +1126,12 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [ ] [x] [ ] # Subset documents - subset_docs = legacy_io.find( - { - "type": "subset", - "parent": {"$in": list(self.content_assets.keys())}, - "name": selected_subset - }, - {"_id": 1, "name": 1, "parent": 1} + subset_docs = get_subsets( + project_name, + asset_ids=self.content_assets.keys(), + subset_names=[selected_subset], + fields=["_id", "name", "parent"] ) - subset_docs_by_id = {} for subset_doc in subset_docs: subset_docs_by_id[subset_doc["_id"]] = subset_doc @@ -1197,15 +1144,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): version_id = last_version["_id"] subset_id_by_version_id[version_id] = subset_id - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(subset_id_by_version_id.keys())} - }, - { - "name": 1, - "parent": 1 - } + repre_docs = get_representations( + project_name, + version_ids=subset_id_by_version_id.keys(), + fields=["name", "parent"] ) repres_by_asset_id = {} for repre_doc in repre_docs: @@ -1245,11 +1187,9 @@ class SwitchAssetDialog(QtWidgets.QDialog): selected_subset = self._subsets_box.get_valid_value() selected_representation = self._representations_box.get_valid_value() + project_name = self.active_project() if selected_asset: - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": selected_asset - }) + asset_doc = get_asset(project_name, asset_name=selected_asset) asset_docs_by_id = {asset_doc["_id"]: asset_doc} else: asset_docs_by_id = self.content_assets @@ -1259,16 +1199,15 @@ class SwitchAssetDialog(QtWidgets.QDialog): for asset_doc in asset_docs_by_id.values() } - asset_ids = list(asset_docs_by_id.keys()) - - subset_query = { - "type": "subset", - "parent": {"$in": asset_ids} - } + subset_names = None if selected_subset: - subset_query["name"] = selected_subset + subset_names = [selected_subset] - subset_docs = list(legacy_io.find(subset_query)) + subset_docs = list(get_subsets( + project_name, + subset_names=subset_names, + asset_ids=asset_docs_by_id.keys() + )) subset_ids = [] subset_docs_by_parent_and_name = collections.defaultdict(dict) for subset in subset_docs: @@ -1278,15 +1217,14 @@ class SwitchAssetDialog(QtWidgets.QDialog): subset_docs_by_parent_and_name[parent_id][name] = subset # versions - version_docs = list(legacy_io.find({ - "type": "version", - "parent": {"$in": subset_ids} - }, sort=[("name", -1)])) + _version_docs = get_versions(project_name, subset_ids=subset_ids) + version_docs = list(reversed( + sorted(_version_docs, key=lambda item: item["name"]) + )) - hero_version_docs = list(legacy_io.find({ - "type": "hero_version", - "parent": {"$in": subset_ids} - })) + hero_version_docs = list(get_hero_versions( + project_name, subset_ids=subset_ids + )) version_ids = list() @@ -1303,10 +1241,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): parent_id = hero_version_doc["parent"] hero_version_docs_by_parent_id[parent_id] = hero_version_doc - repre_docs = legacy_io.find({ - "type": "representation", - "parent": {"$in": version_ids} - }) + repre_docs = get_representations(project_name, version_ids=version_ids) repre_docs_by_parent_id_by_name = collections.defaultdict(dict) for repre_doc in repre_docs: parent_id = repre_doc["parent"] diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 448e3f4e6f..6a95ccb57b 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -6,6 +6,13 @@ from Qt import QtWidgets, QtCore import qtawesome from bson.objectid import ObjectId +from openpype.client import ( + get_version, + get_versions, + get_hero_versions, + get_representation, + get_representations, +) from openpype import style from openpype.pipeline import ( legacy_io, @@ -83,12 +90,9 @@ class SceneInventoryView(QtWidgets.QTreeView): if item_id not in repre_ids: repre_ids.append(item_id) - repre_docs = legacy_io.find( - { - "type": "representation", - "_id": {"$in": repre_ids} - }, - {"parent": 1} + project_name = legacy_io.active_project() + repre_docs = get_representations( + project_name, representaion_ids=repre_ids, fields=["parent"] ) version_ids = [] @@ -97,10 +101,9 @@ class SceneInventoryView(QtWidgets.QTreeView): if version_id not in version_ids: version_ids.append(version_id) - loaded_versions = legacy_io.find({ - "_id": {"$in": version_ids}, - "type": {"$in": ["version", "hero_version"]} - }) + loaded_versions = get_versions( + project_name, version_ids=version_ids, hero=True + ) loaded_hero_versions = [] versions_by_parent_id = collections.defaultdict(list) @@ -114,10 +117,9 @@ class SceneInventoryView(QtWidgets.QTreeView): if parent_id not in version_parents: version_parents.append(parent_id) - all_versions = legacy_io.find({ - "type": {"$in": ["hero_version", "version"]}, - "parent": {"$in": version_parents} - }) + all_versions = get_versions( + project_name, subset_ids=version_parents, hero=True + ) hero_versions = [] versions = [] for version in all_versions: @@ -150,12 +152,10 @@ class SceneInventoryView(QtWidgets.QTreeView): if item_id not in repre_ids: repre_ids.append(item_id) - repre_docs = legacy_io.find( - { - "type": "representation", - "_id": {"$in": repre_ids} - }, - {"parent": 1} + repre_docs = get_representations( + project_name, + representation_ids=repre_ids, + fields=["parent"] ) version_ids = [] @@ -165,13 +165,13 @@ class SceneInventoryView(QtWidgets.QTreeView): version_id_by_repre_id[repre_doc["_id"]] = version_id if version_id not in version_ids: version_ids.append(version_id) - hero_versions = legacy_io.find( - { - "_id": {"$in": version_ids}, - "type": "hero_version" - }, - {"version_id": 1} + + hero_versions = get_hero_versions( + project_name, + version_ids=version_ids, + fields=["version_id"] ) + version_ids = set() for hero_version in hero_versions: version_id = hero_version["version_id"] @@ -183,12 +183,10 @@ class SceneInventoryView(QtWidgets.QTreeView): if current_version_id == hero_version_id: version_id_by_repre_id[_repre_id] = version_id - version_docs = legacy_io.find( - { - "_id": {"$in": list(version_ids)}, - "type": "version" - }, - {"name": 1} + version_docs = get_versions( + project_name, + version_ids=version_ids, + fields=["name"] ) version_name_by_id = {} for version_doc in version_docs: @@ -370,10 +368,9 @@ class SceneInventoryView(QtWidgets.QTreeView): active_site = self.sync_server.get_active_site(project_name) remote_site = self.sync_server.get_remote_site(project_name) - repre_docs = legacy_io.find({ - "type": "representation", - "_id": {"$in": repre_ids} - }) + repre_docs = get_representations( + project_name, representation_ids=repre_ids + ) repre_docs_by_id = { repre_doc["_id"]: repre_doc for repre_doc in repre_docs @@ -658,25 +655,37 @@ class SceneInventoryView(QtWidgets.QTreeView): active = items[-1] + project_name = legacy_io.active_project() # Get available versions for active representation representation_id = ObjectId(active["representation"]) - representation = legacy_io.find_one({"_id": representation_id}) - version = legacy_io.find_one({ - "_id": representation["parent"] - }) - versions = list(legacy_io.find( - { - "parent": version["parent"], - "type": "version" - }, - sort=[("name", 1)] + repre_doc = get_representation( + project_name, + representation_id=representation_id, + fields=["parent"] + ) + + repre_version_doc = get_version( + project_name, + version_id=repre_doc["parent"], + fields=["parent"] + ) + + version_docs = get_versions( + project_name, + subset_ids=[repre_version_doc["parent"]], + hero=True + ) + hero_version = None + standard_versions = [] + for version_doc in version_docs: + if version_doc["type"] == "hero_version": + hero_version = version_doc + else: + standard_versions.append(version_doc) + versions = list(reversed( + sorted(standard_versions, key=lambda item: item["name"]) )) - - hero_version = legacy_io.find_one({ - "parent": version["parent"], - "type": "hero_version" - }) if hero_version: _version_id = hero_version["version_id"] for _version in versions: @@ -703,7 +712,7 @@ class SceneInventoryView(QtWidgets.QTreeView): all_versions = [] if hero_version: all_versions.append(hero_version) - all_versions.extend(reversed(versions)) + all_versions.extend(versions) if current_item: index = all_versions.index(current_item) diff --git a/openpype/tools/standalonepublish/app.py b/openpype/tools/standalonepublish/app.py index 1ad5cd119e..4831db038c 100644 --- a/openpype/tools/standalonepublish/app.py +++ b/openpype/tools/standalonepublish/app.py @@ -6,6 +6,8 @@ import signal from bson.objectid import ObjectId from Qt import QtWidgets, QtCore, QtGui +from openpype.client import get_asset + from .widgets import ( AssetWidget, FamilyWidget, ComponentsWidget, ShadowWidget ) @@ -126,17 +128,6 @@ class Window(QtWidgets.QDialog): if event: super().resizeEvent(event) - def get_avalon_parent(self, entity): - ''' Avalon DB entities helper - get all parents (exclude project). - ''' - parent_id = entity['data']['visualParent'] - parents = [] - if parent_id is not None: - parent = self.db.find_one({'_id': parent_id}) - parents.extend(self.get_avalon_parent(parent)) - parents.append(parent['name']) - return parents - def on_project_change(self, project_name): self.widget_family.refresh() @@ -152,7 +143,10 @@ class Window(QtWidgets.QDialog): ] if len(selected) == 1: self.valid_parent = True - asset = self.db.find_one({"_id": selected[0], "type": "asset"}) + project_name = self.db.active_project() + asset = get_asset( + project_name, asset_id=selected[0], fields=["name"] + ) self.widget_family.change_asset(asset['name']) else: self.valid_parent = False diff --git a/openpype/tools/standalonepublish/widgets/model_asset.py b/openpype/tools/standalonepublish/widgets/model_asset.py index 02e9073555..abfc0a2145 100644 --- a/openpype/tools/standalonepublish/widgets/model_asset.py +++ b/openpype/tools/standalonepublish/widgets/model_asset.py @@ -4,6 +4,7 @@ import collections from Qt import QtCore, QtGui import qtawesome +from openpype.client import get_assets from openpype.style import ( get_default_entity_icon_color, get_deprecated_entity_font_color, @@ -104,17 +105,18 @@ class AssetModel(TreeModel): def refresh(self): """Refresh the data for the model.""" + project_name = self.dbcon.active_project() self.clear() - if ( - self.dbcon.active_project() is None or - self.dbcon.active_project() == '' - ): + if not project_name: return self.beginResetModel() # Get all assets in current project sorted by name - db_assets = self.dbcon.find({"type": "asset"}).sort("name", 1) + asset_docs = get_assets(project_name) + db_assets = list( + sorted(asset_docs, key=lambda item: item["name"]) + ) # Group the assets by their visual parent's id assets_by_parent = collections.defaultdict(list) diff --git a/openpype/tools/standalonepublish/widgets/widget_asset.py b/openpype/tools/standalonepublish/widgets/widget_asset.py index 8b43cd7cf8..0b5802ed9e 100644 --- a/openpype/tools/standalonepublish/widgets/widget_asset.py +++ b/openpype/tools/standalonepublish/widgets/widget_asset.py @@ -2,6 +2,10 @@ import contextlib from Qt import QtWidgets, QtCore import qtawesome +from openpype.client import ( + get_project, + get_asset, +) from openpype.tools.utils import PlaceholderLineEdit from openpype.style import get_default_tools_icon_color @@ -218,7 +222,8 @@ class AssetWidget(QtWidgets.QWidget): self.view = view def collect_data(self): - project = self.dbcon.find_one({'type': 'project'}) + project_name = self.dbcon.active_project() + project = get_project(project_name, fields=["name"]) asset = self.get_active_asset() try: @@ -241,9 +246,16 @@ class AssetWidget(QtWidgets.QWidget): return ent_parents output = [] - if entity.get('data', {}).get('visualParent', None) is None: + parent_asset_id = entity.get('data', {}).get('visualParent', None) + if parent_asset_id is None: return output - parent = self.dbcon.find_one({'_id': entity['data']['visualParent']}) + + project_name = self.dbcon.active_project() + parent = get_asset( + project_name, + asset_id=parent_asset_id, + fields=["name", "data.visualParent"] + ) output.append(parent['name']) output.extend(self.get_parents(parent)) return output @@ -349,9 +361,10 @@ class AssetWidget(QtWidgets.QWidget): tasks = [] selected = self.get_selected_assets() if len(selected) == 1: - asset = self.dbcon.find_one({ - "_id": selected[0], "type": "asset" - }) + project_name = self.dbcon.active_project() + asset = get_asset( + project_name, asset_id=selected[0], fields=["data.tasks"] + ) if asset: tasks = asset.get('data', {}).get('tasks', []) self.task_model.set_tasks(tasks) @@ -400,7 +413,7 @@ class AssetWidget(QtWidgets.QWidget): # Select mode = selection_model.Select | selection_model.Rows - for index in lib.iter_model_rows( + for index in _iter_model_rows( self.proxy, column=0, include_root=False ): # stop iteration if there are no assets to process diff --git a/openpype/tools/standalonepublish/widgets/widget_family.py b/openpype/tools/standalonepublish/widgets/widget_family.py index 08cd45bbf2..ed9f405f38 100644 --- a/openpype/tools/standalonepublish/widgets/widget_family.py +++ b/openpype/tools/standalonepublish/widgets/widget_family.py @@ -1,14 +1,21 @@ import re from Qt import QtWidgets, QtCore -from . import HelpRole, FamilyRole, ExistsRole, PluginRole, PluginKeyRole -from . import FamilyDescriptionWidget +from openpype.client import ( + get_asset, + get_subset, + get_subsets, + get_last_version_for_subset, +) from openpype.api import get_project_settings from openpype.pipeline import LegacyCreator from openpype.lib import TaskNotSetError from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS +from . import HelpRole, FamilyRole, ExistsRole, PluginRole, PluginKeyRole +from . import FamilyDescriptionWidget + class FamilyWidget(QtWidgets.QWidget): @@ -180,12 +187,9 @@ class FamilyWidget(QtWidgets.QWidget): asset_doc = None if asset_name != self.NOT_SELECTED: # Get the assets from the database which match with the name - asset_doc = self.dbcon.find_one( - { - "type": "asset", - "name": asset_name - }, - {"_id": 1} + project_name = self.dbcon.active_project() + asset_doc = get_asset( + project_name, asset_name=asset_name, fields=["_id"] ) # Get plugin and family @@ -200,14 +204,13 @@ class FamilyWidget(QtWidgets.QWidget): return # Get the asset from the database which match with the name - asset_doc = self.dbcon.find_one( - {"name": asset_name, "type": "asset"}, - projection={"_id": 1} + project_name = self.dbcon.active_project() + asset_doc = get_asset( + project_name, asset_name=asset_name, fields=["_id"] ) # Get plugin plugin = item.data(PluginRole) if asset_doc and plugin: - project_name = self.dbcon.Session["AVALON_PROJECT"] asset_id = asset_doc["_id"] task_name = self.dbcon.Session["AVALON_TASK"] @@ -231,14 +234,14 @@ class FamilyWidget(QtWidgets.QWidget): self.input_result.setText("Select task please") # Get all subsets of the current asset - subset_docs = self.dbcon.find( - { - "type": "subset", - "parent": asset_id - }, - {"name": 1} + subset_docs = get_subsets( + project_name, asset_ids=[asset_id], fields=["name"] ) - existing_subset_names = set(subset_docs.distinct("name")) + + existing_subset_names = { + subset_doc["name"] + for subset_doc in subset_docs + } # Defaults to dropdown defaults = [] @@ -296,47 +299,37 @@ class FamilyWidget(QtWidgets.QWidget): if not auto_version: return + project_name = self.dbcon.active_project() asset_name = self.asset_name subset_name = self.input_result.text() version = 1 asset_doc = None subset_doc = None - versions = None if ( asset_name != self.NOT_SELECTED and subset_name.strip() != '' ): - asset_doc = self.dbcon.find_one( - { - 'type': 'asset', - 'name': asset_name - }, - {"_id": 1} + asset_doc = get_asset( + project_name, asset_name=asset_name, fields=["_id"] ) if asset_doc: - subset_doc = self.dbcon.find_one( - { - 'type': 'subset', - 'parent': asset_doc['_id'], - 'name': subset_name - }, - {"_id": 1} + subset_doc = get_subset( + project_name, + subset_name=subset_name, + asset_id=asset_doc['_id'], + fields=["_id"] ) if subset_doc: - versions = self.dbcon.find( - { - 'type': 'version', - 'parent': subset_doc['_id'] - }, - {"name": 1} - ).distinct("name") - - if versions: - versions = sorted(versions) - version = int(versions[-1]) + 1 + last_version = get_last_version_for_subset( + project_name, + subset_id=subset_doc["_id"], + fields=["name"] + ) + if last_version: + version = last_version["name"] + 1 self.version_spinbox.setValue(version) diff --git a/openpype/tools/texture_copy/app.py b/openpype/tools/texture_copy/app.py index fd8d6dc02e..8703f075d3 100644 --- a/openpype/tools/texture_copy/app.py +++ b/openpype/tools/texture_copy/app.py @@ -4,6 +4,7 @@ import click import speedcopy +from openpype.client import get_project, get_asset from openpype.lib import Terminal from openpype.api import Anatomy from openpype.pipeline import legacy_io @@ -29,20 +30,6 @@ class TextureCopy: if os.path.splitext(x)[1].lower() in texture_extensions) return textures - def _get_project(self, project_name): - project = legacy_io.find_one({ - 'type': 'project', - 'name': project_name - }) - return project - - def _get_asset(self, asset_name): - asset = legacy_io.find_one({ - 'type': 'asset', - 'name': asset_name - }) - return asset - def _get_destination_path(self, asset, project): project_name = project["name"] hierarchy = "" @@ -88,11 +75,12 @@ class TextureCopy: t.echo("!!! {}".format(e)) exit(1) - def process(self, asset, project, path): + def process(self, asset_name, project_name, path): """ Process all textures found in path and copy them to asset under project. """ + t.echo(">>> Looking for textures ...") textures = self._get_textures(path) if len(textures) < 1: @@ -101,14 +89,14 @@ class TextureCopy: else: t.echo(">>> Found {} textures ...".format(len(textures))) - project = self._get_project(project) + project = get_project(project_name) if not project: - t.echo("!!! Project name [ {} ] not found.".format(project)) + t.echo("!!! Project name [ {} ] not found.".format(project_name)) exit(1) - asset = self._get_asset(asset) - if not project: - t.echo("!!! Asset [ {} ] not found in project".format(asset)) + asset = get_asset(project_name, asset_name=asset_name) + if not asset: + t.echo("!!! Asset [ {} ] not found in project".format(asset_name)) exit(1) t.echo((">>> Project [ {} ] and " "asset [ {} ] seems to be OK ...").format(project['name'], diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py index 82bdcd63a2..772946e9e1 100644 --- a/openpype/tools/utils/assets_widget.py +++ b/openpype/tools/utils/assets_widget.py @@ -5,6 +5,10 @@ import Qt from Qt import QtWidgets, QtCore, QtGui import qtawesome +from openpype.client import ( + get_project, + get_assets, +) from openpype.style import ( get_objected_colors, get_default_tools_icon_color, @@ -525,21 +529,18 @@ class AssetModel(QtGui.QStandardItemModel): self._doc_fetched.emit() def _fetch_asset_docs(self): - if not self.dbcon.Session.get("AVALON_PROJECT"): + project_name = self.dbcon.current_project() + if not project_name: return [] - project_doc = self.dbcon.find_one( - {"type": "project"}, - {"_id": True} - ) + project_doc = get_project(project_name, fields=["_id"]) if not project_doc: return [] # Get all assets sorted by name - return list(self.dbcon.find( - {"type": "asset"}, - self._asset_projection - )) + return list( + get_assets(project_name, fields=self._asset_projection.keys()) + ) def _stop_fetch_thread(self): self._refreshing = False diff --git a/openpype/tools/utils/delegates.py b/openpype/tools/utils/delegates.py index 71f817a1d7..d6c2d69e76 100644 --- a/openpype/tools/utils/delegates.py +++ b/openpype/tools/utils/delegates.py @@ -6,15 +6,19 @@ import numbers import Qt from Qt import QtWidgets, QtGui, QtCore -from openpype.pipeline import HeroVersionType -from .models import TreeModel -from . import lib - if Qt.__binding__ == "PySide": from PySide.QtGui import QStyleOptionViewItemV4 elif Qt.__binding__ == "PyQt4": from PyQt4.QtGui import QStyleOptionViewItemV4 +from openpype.client import ( + get_versions, + get_hero_versions, +) +from openpype.pipeline import HeroVersionType +from .models import TreeModel +from . import lib + log = logging.getLogger(__name__) @@ -114,26 +118,24 @@ class VersionDelegate(QtWidgets.QStyledItemDelegate): "Version is not integer" ) + project_name = self.dbcon.active_project() # Add all available versions to the editor parent_id = item["version_document"]["parent"] - version_docs = list(self.dbcon.find( - { - "type": "version", - "parent": parent_id - }, - sort=[("name", 1)] + version_docs = list(sorted( + get_versions(project_name, subset_ids=[parent_id]), + key=lambda item: item["name"] )) - hero_version_doc = self.dbcon.find_one( - { - "type": "hero_version", - "parent": parent_id - }, { - "name": 1, - "data.tags": 1, - "version_id": 1 - } + hero_versions = list( + get_hero_versions( + project_name, + subset_ids=[parent_id], + fields=["name", "data.tags", "version_id"] + ) ) + hero_version_doc = None + if hero_versions: + hero_version_doc = hero_versions[0] doc_for_hero_version = None diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 20fea6600b..72ebfcc063 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -6,6 +6,10 @@ import collections from Qt import QtWidgets, QtCore, QtGui import qtawesome +from openpype.client import ( + get_project, + get_asset, +) from openpype.style import ( get_default_entity_icon_color, get_objected_colors, @@ -430,9 +434,8 @@ class FamilyConfigCache: database = getattr(self.dbcon, "database", None) if database is None: database = self.dbcon._database - asset_doc = database[project_name].find_one( - {"type": "asset", "name": asset_name}, - {"data.tasks": True} + asset_doc = get_asset( + project_name, asset_name=asset_name, fields=["data.tasks"] ) or {} tasks_info = asset_doc.get("data", {}).get("tasks") or {} task_type = tasks_info.get(task_name, {}).get("type") @@ -500,10 +503,7 @@ class GroupsConfig: project_name = self.dbcon.Session.get("AVALON_PROJECT") if project_name: # Get pre-defined group name and appearance from project config - project_doc = self.dbcon.find_one( - {"type": "project"}, - projection={"config.groups": True} - ) + project_doc = get_project(project_name, fields=["config.groups"]) if project_doc: group_configs = project_doc["config"].get("groups") or [] diff --git a/openpype/tools/utils/tasks_widget.py b/openpype/tools/utils/tasks_widget.py index eab183d5f3..fcbec318f5 100644 --- a/openpype/tools/utils/tasks_widget.py +++ b/openpype/tools/utils/tasks_widget.py @@ -1,6 +1,10 @@ from Qt import QtWidgets, QtCore, QtGui import qtawesome +from openpype.client import ( + get_project, + get_asset, +) from openpype.style import get_disabled_entity_icon_color from openpype.tools.utils.lib import get_task_icon @@ -47,7 +51,8 @@ class TasksModel(QtGui.QStandardItemModel): # Get the project configured icons from database project_doc = {} if self._context_is_valid(): - project_doc = self.dbcon.find_one({"type": "project"}) + project_name = self.dbcon.active_project() + project_doc = get_project(project_name) self._loaded_project_name = self._get_current_project() self._project_doc = project_doc @@ -71,9 +76,9 @@ class TasksModel(QtGui.QStandardItemModel): def set_asset_id(self, asset_id): asset_doc = None if self._context_is_valid(): - asset_doc = self.dbcon.find_one( - {"_id": asset_id}, - {"data.tasks": True} + project_name = self._get_current_project() + asset_doc = get_asset( + project_name, asset_id=asset_id, fields=["data.tasks"] ) self._set_asset(asset_doc) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 977111b71b..36b9a055d8 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -5,6 +5,7 @@ import shutil import Qt from Qt import QtWidgets, QtCore +from openpype.client import get_asset from openpype.tools.utils import PlaceholderLineEdit from openpype.tools.utils.delegates import PrettyTimeDelegate from openpype.lib import ( @@ -384,7 +385,9 @@ class FilesWidget(QtWidgets.QWidget): return None if self._asset_doc is None: - self._asset_doc = legacy_io.find_one({"_id": self._asset_id}) + project_name = legacy_io.active_project() + self._asset_doc = get_asset(project_name, asset_id=self._asset_id) + return self._asset_doc def _get_session(self): diff --git a/openpype/tools/workfiles/model.py b/openpype/tools/workfiles/model.py index 8f9dd8c6ba..d5b7cef339 100644 --- a/openpype/tools/workfiles/model.py +++ b/openpype/tools/workfiles/model.py @@ -4,6 +4,11 @@ import logging from Qt import QtCore, QtGui import qtawesome +from openpype.client import ( + get_subsets, + get_versions, + get_representations, +) from openpype.style import ( get_default_entity_icon_color, get_disabled_entity_icon_color, @@ -215,6 +220,7 @@ class PublishFilesModel(QtGui.QStandardItemModel): self._dbcon = dbcon self._anatomy = anatomy + self._file_extensions = extensions self._invalid_context_item = None @@ -234,6 +240,10 @@ class PublishFilesModel(QtGui.QStandardItemModel): self._asset_id = None self._task_name = None + @property + def project_name(self): + return self._dbcon.Session["AVALON_PROJECT"] + def _set_item_invalid(self, item): item.setFlags(QtCore.Qt.NoItemFlags) item.setData(self._invalid_icon, QtCore.Qt.DecorationRole) @@ -285,15 +295,11 @@ class PublishFilesModel(QtGui.QStandardItemModel): def _get_workfie_representations(self): output = [] # Get subset docs of asset - subset_docs = self._dbcon.find( - { - "type": "subset", - "parent": self._asset_id - }, - { - "_id": True, - "name": True - } + subset_docs = get_subsets( + self.project_name, + asset_ids=[self._asset_id], + fields=["_id", "name"] + ) subset_ids = [subset_doc["_id"] for subset_doc in subset_docs] @@ -301,17 +307,12 @@ class PublishFilesModel(QtGui.QStandardItemModel): return output # Get version docs of subsets with their families - version_docs = self._dbcon.find( - { - "type": "version", - "parent": {"$in": subset_ids} - }, - { - "_id": True, - "data.families": True, - "parent": True - } + version_docs = get_versions( + self.project_name, + subset_ids=subset_ids, + fields=["_id", "parent", "data.families"] ) + # Filter versions if they contain 'workfile' family filtered_versions = [] for version_doc in version_docs: @@ -327,13 +328,10 @@ class PublishFilesModel(QtGui.QStandardItemModel): # Query representations of filtered versions and add filter for # extension extensions = [ext.replace(".", "") for ext in self._file_extensions] - repre_docs = self._dbcon.find( - { - "type": "representation", - "parent": {"$in": version_ids}, - "context.ext": {"$in": extensions} - } + repre_docs = get_representations( + self.project_name, version_ids, extensions ) + # Filter queried representations by task name if task is set filtered_repre_docs = [] for repre_doc in repre_docs: diff --git a/openpype/tools/workfiles/save_as_dialog.py b/openpype/tools/workfiles/save_as_dialog.py index 3e97d6c938..1fbcbfeb22 100644 --- a/openpype/tools/workfiles/save_as_dialog.py +++ b/openpype/tools/workfiles/save_as_dialog.py @@ -5,6 +5,10 @@ import logging from Qt import QtWidgets, QtCore +from openpype.client import ( + get_project, + get_asset, +) from openpype.lib import ( get_last_workfile_with_version, get_workdir_data, @@ -22,29 +26,19 @@ def build_workfile_data(session): """Get the data required for workfile formatting from avalon `session`""" # Set work file data for template formatting + project_name = session["AVALON_PROJECT"] asset_name = session["AVALON_ASSET"] task_name = session["AVALON_TASK"] host_name = session["AVALON_APP"] - project_doc = legacy_io.find_one( - {"type": "project"}, - { - "name": True, - "data.code": True, - "config.tasks": True, - } + project_doc = get_project( + project_name, fields=["name", "data.code", "config.tasks"] + ) + asset_doc = get_asset( + project_name, + asset_name=asset_name, + fields=["name", "data.tasks", "data.parents"] ) - asset_doc = legacy_io.find_one( - { - "type": "asset", - "name": asset_name - }, - { - "name": True, - "data.tasks": True, - "data.parents": True - } - ) data = get_workdir_data(project_doc, asset_doc, task_name, host_name) data.update({ "version": 1, diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index 02a22af26c..45d8d41d16 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -2,6 +2,7 @@ import os import datetime from Qt import QtCore, QtWidgets +from openpype.client import get_asset from openpype import style from openpype.lib import ( get_workfile_doc, @@ -223,6 +224,10 @@ class Window(QtWidgets.QMainWindow): self._first_show = True self._context_to_set = None + @property + def project_name(self): + return legacy_io.Session["AVALON_PROJECT"] + def showEvent(self, event): super(Window, self).showEvent(event) if self._first_show: @@ -296,7 +301,8 @@ class Window(QtWidgets.QMainWindow): if not workfile_doc: workdir, filename = os.path.split(filepath) asset_id = self.assets_widget.get_selected_asset_id() - asset_doc = legacy_io.find_one({"_id": asset_id}) + project_name = legacy_io.active_project() + asset_doc = get_asset(project_name, asset_id=asset_id) task_name = self.tasks_widget.get_selected_task_name() create_workfile_doc( asset_doc, task_name, filename, workdir, legacy_io @@ -322,14 +328,13 @@ class Window(QtWidgets.QMainWindow): self._context_to_set, context = None, self._context_to_set if "asset" in context: - asset_doc = legacy_io.find_one( - { - "name": context["asset"], - "type": "asset" - }, - {"_id": 1} - ) or {} - asset_id = asset_doc.get("_id") + asset_doc = get_asset( + self.project_name, context["asset"], fields=["_id"] + ) + + asset_id = None + if asset_doc: + asset_id = asset_doc["_id"] # Select the asset self.assets_widget.select_asset(asset_id) self.tasks_widget.set_asset_id(asset_id) From 8bb97b9bbc9f14315aea37de033fa59758c5185a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 6 Jun 2022 11:06:03 +0200 Subject: [PATCH 0473/1227] Fix - correct project settings selected --- openpype/modules/sync_server/tray/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 049a3f0127..e41910fa4f 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -127,7 +127,7 @@ class SyncProjectListWidget(QtWidgets.QWidget): if self.sync_server.is_paused() or \ self.sync_server.is_project_paused(project_name): icon = self._get_icon("paused") - elif not sync_settings["enabled"]: + elif not sync_settings[project_name]["enabled"]: icon = self._get_icon("disabled") else: icon = self._get_icon("synced") From b3098a4a1ed2f06c622392870f6fad4f7d122e29 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Mon, 6 Jun 2022 12:12:10 +0300 Subject: [PATCH 0474/1227] Fix path bug causing output path to equal input path. --- repos/avalon-core | 1 - 1 file changed, 1 deletion(-) delete mode 160000 repos/avalon-core diff --git a/repos/avalon-core b/repos/avalon-core deleted file mode 160000 index 2fa14cea6f..0000000000 --- a/repos/avalon-core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2fa14cea6f6a9d86eec70bbb96860cbe4c75c8eb From a922b93202cc8ec898981109f260141bb0707ef2 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Mon, 6 Jun 2022 12:12:30 +0300 Subject: [PATCH 0475/1227] Fix path bug. --- openpype/plugins/publish/extract_jpeg_exr.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index daf7430a32..f474714780 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -159,9 +159,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # we just want one frame from movie files jpeg_items.append("-vframes 1") # output file - jpeg_items.append(path_to_subprocess_arg(src_path)) + jpeg_items.append(path_to_subprocess_arg(dst_path)) subprocess_command = " ".join(jpeg_items) - run_subprocess( subprocess_command, shell=True, logger=self.log ) From 7b16c6837b1c02defd73fcba984909d085cce61e Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 6 Jun 2022 17:08:37 +0200 Subject: [PATCH 0476/1227] refacto code to have simpler menu --- openpype/hosts/nuke/api/gizmo_menu.py | 90 ++++++++------ openpype/hosts/nuke/api/lib.py | 109 ++++++++++------ openpype/hosts/nuke/startup/menu.py | 70 +---------- .../defaults/project_settings/nuke.json | 8 +- .../schemas/schema_nuke_scriptsgizmo.json | 117 +++++++++++++++--- 5 files changed, 222 insertions(+), 172 deletions(-) diff --git a/openpype/hosts/nuke/api/gizmo_menu.py b/openpype/hosts/nuke/api/gizmo_menu.py index dd04f4a42e..7f8121372c 100644 --- a/openpype/hosts/nuke/api/gizmo_menu.py +++ b/openpype/hosts/nuke/api/gizmo_menu.py @@ -1,67 +1,75 @@ import os -import logging +import re import nuke -log = logging.getLogger(__name__) +from openpype.api import Logger + +log = Logger.get_logger(__name__) class GizmoMenu(): - def __init__(self, *args, **kwargs): + def __init__(self, title, icon=None): + + self.toolbar = self._create_toolbar_menu( + title, + icon=icon + ) + self._script_actions = [] - def build_from_configuration(self, parent, configuration): + def _create_toolbar_menu(self, name, icon=None): + nuke_node_menu = nuke.menu("Nodes") + return nuke_node_menu.addMenu( + name, + icon=icon + ) + + def _make_menu_path(self, path, icon=None): + parent = self.toolbar + for folder in re.split(r"/|\\",path): + if not folder: + continue + existing_menu = parent.findItem(folder) + if existing_menu: + parent = existing_menu + else: + parent = parent.addMenu(folder, icon=icon) + + return parent + + def build_from_configuration(self, configuration): for item in configuration: assert isinstance(item, dict), "Configuration is wrong!" - # skip items which have no `type` key - item_type = item.get('type', None) - if not item_type: - log.warning("Missing 'type' from configuration item") - continue + # Construct parent path else parent is toolbar + parent = self.toolbar + gizmo_toolbar_path = item.get("gizmo_toolbar_path") + if gizmo_toolbar_path: + parent = self._make_menu_path(gizmo_toolbar_path) - if item_type == "action": - # filter out `type` from the item dict - config = {key: value for key, value in - item.items() if key != "type"} - - command = str(config['command']) - - icon = config.get('icon', None) - if icon: - try: - icon = icon.format(**os.environ) - except KeyError as e: - log.warning("This environment variable doesn't exist: " - "{}".format(e)) - - hotkey = config.get('hotkey', None) + item_type = item.get("sourcetype") + if item_type == ("python" or "file"): parent.addCommand( - config['title'], - command=command, - icon=icon, - shortcut=hotkey + item['title'], + command=str(item["command"]), + icon=item.get("icon"), + shortcut=item.get('hotkey') ) # add separator # Special behavior for separators - if item_type == "separator": + elif item_type == "separator": parent.addSeparator() # add submenu # items should hold a collection of submenu items (dict) elif item_type == "menu": - assert "items" in item, "Menu is missing 'items' key" - - icon = item.get('icon', None) - if icon: - try: - icon = icon.format(**os.environ) - except KeyError as e: - log.warning("This environment variable doesn't exist: " - "{}".format(e)) - menu = parent.addMenu(item['title'], icon=icon) - self.build_from_configuration(menu, item["items"]) + # assert "items" in item, "Menu is missing 'items' key" + parent.addMenu( + item['title'], + icon=item.get('icon') + ) def add_gizmo_path(self, gizmo_paths): for gizmo_path in gizmo_paths: diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index a1ac50ae1a..335e7190a0 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2500,50 +2500,77 @@ def recreate_instance(origin_node, avalon_data=None): return new_node -def find_scripts_gizmo(title, parent): - """ - Check if the menu exists with the given title in the parent - - Args: - title (str): the title name of the scripts menu - - parent (QtWidgets.QMenuBar): the menubar to check - - Returns: - QtWidgets.QMenu or None - - """ - - menu = None - search = [i for i in parent.items() if - isinstance(i, gizmo_menu.GizmoMenu) - and i.title() == title] - - if search: - assert len(search) < 2, ("Multiple instances of menu '{}' " - "in toolbar".format(title)) - menu = search[0] - - return menu - - -def gizmo_creation(title="Gizmos", parent=None, objectName=None, icon=None): +def add_scripts_gizmo(): try: - toolbar = find_scripts_gizmo(title, parent) - if not toolbar: - log.info("Attempting to build toolbar...") - object_name = objectName or title.lower() - toolbar = gizmo_menu.GizmoMenu( - title=title, - parent=parent, - objectName=object_name, - icon=icon - ) - except Exception as e: - log.error(e) + from openpype.hosts.nuke.api import lib + except ImportError: + log.warning( + "Skipping studio.gizmo install, because " + "'scriptsgizmo' module seems unavailable." + ) return - return toolbar + # load configuration of custom menu + project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) + platform_name = platform.system().lower() + + for gizmo_settings in project_settings["nuke"]["gizmo"]: + gizmo_list_definition = gizmo_settings["gizmo_definition"] + print(1, gizmo_list_definition) + toolbar_name = gizmo_settings["toolbar_menu_name"] + # gizmo_toolbar_path = gizmo_settings["gizmo_toolbar_path"] + gizmo_source_dir = gizmo_settings.get( + "gizmo_source_dir", {}).get(platform_name) + toolbar_icon_path = gizmo_settings.get( + "toolbar_icon_path", {}).get(platform_name) + + if not gizmo_source_dir: + log.debug("Skipping studio gizmo `{}`, no gizmo path found.".format( + toolbar_name + )) + return + + if not gizmo_list_definition: + log.debug("Skipping studio gizmo `{}`, no definition found.".format( + toolbar_name + )) + return + + if toolbar_icon_path: + try: + toolbar_icon_path = toolbar_icon_path.format(**os.environ) + except KeyError as e: + log.error( + "This environment variable doesn't exist: {}".format(e) + ) + + existing_gizmo_path = [] + for source_dir in gizmo_source_dir: + try: + resolve_source_dir = source_dir.format(**os.environ) + except KeyError as e: + log.error( + "This environment variable doesn't exist: {}".format(e) + ) + continue + if not os.path.exists(resolve_source_dir): + log.warning( + "The source of gizmo `{}` does not exists".format( + resolve_source_dir + ) + ) + continue + existing_gizmo_path.append(resolve_source_dir) + + # run the launcher for Nuke toolbar + toolbar_menu = gizmo_menu.GizmoMenu( + title=toolbar_name, + icon=toolbar_icon_path + ) + + # apply configuration + toolbar_menu.add_gizmo_path(existing_gizmo_path) + toolbar_menu.build_from_configuration(gizmo_list_definition) class NukeDirmap(HostDirmap): diff --git a/openpype/hosts/nuke/startup/menu.py b/openpype/hosts/nuke/startup/menu.py index 715bab8ea5..1461d41385 100644 --- a/openpype/hosts/nuke/startup/menu.py +++ b/openpype/hosts/nuke/startup/menu.py @@ -8,7 +8,8 @@ from openpype.hosts.nuke.api.lib import ( on_script_load, check_inventory_versions, WorkfileSettings, - dirmap_file_name_filter + dirmap_file_name_filter, + add_scripts_gizmo ) from openpype.settings import get_project_settings @@ -60,71 +61,4 @@ def add_scripts_menu(): add_scripts_menu() - -def add_scripts_gizmo(): - try: - from openpype.hosts.nuke.api import lib - except ImportError: - log.warning( - "Skipping studio.gizmo install, because " - "'scriptsgizmo' module seems unavailable." - ) - return - - # load configuration of custom menu - project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) - - for gizmo in project_settings["nuke"]["gizmo"]: - config = gizmo["gizmo_definition"] - toolbar_name = gizmo["toolbar_menu_name"] - gizmo_path = gizmo["gizmo_path"] - icon = gizmo['toolbar_icon_path'] - - if not any(gizmo_path): - log.warning("Skipping studio gizmo, no gizmo path found.") - return - - if not config: - log.warning("Skipping studio gizmo, no definition found.") - return - - try: - icon = icon.format(**os.environ) - except KeyError as e: - log.warning( - "This environment variable doesn't exist: {}".format(e) - ) - - existing_gizmo_path = [] - for gizmo in gizmo_path: - try: - gizmo = gizmo.format(**os.environ) - except KeyError as e: - log.warning( - "This environment variable doesn't exist: {}".format(e) - ) - continue - if not os.path.exists(gizmo): - log.warning( - "The source of gizmo `{}` does not exists".format(gizmo) - ) - continue - existing_gizmo_path.append(gizmo) - - nuke_toolbar = nuke.menu("Nodes") - toolbar = nuke_toolbar.addMenu(toolbar_name, icon=icon) - - # run the launcher for Nuke toolbar - studio_menu = lib.gizmo_creation( - title=toolbar_name, - parent=toolbar, - objectName=toolbar_name.lower().replace(" ", "_"), - icon=icon - ) - - # apply configuration - studio_menu.add_gizmo_path(existing_gizmo_path) - studio_menu.build_from_configuration(toolbar, config) - - add_scripts_gizmo() diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 6c6454de36..63978ad1be 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -293,8 +293,12 @@ "gizmo": [ { "toolbar_menu_name": "OpenPype Gizmo", - "toolbar_icon_path": "path/to/nuke/icon.png", - "gizmo_path": ["path/to/nuke/gizmo"], + "gizmo_path": { + "windows": [], + "darwin": [], + "linux": [] + }, + "toolbar_icon_path": {}, "gizmo_definition": [ { "type": "action", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsgizmo.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsgizmo.json index c1e67842ce..80fda56175 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsgizmo.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsgizmo.json @@ -14,28 +14,105 @@ }, { "type": "path", - "key": "toolbar_icon_path", - "label": "Toolbar Icon Path", - "multipath": false + "key": "gizmo_source_dir", + "label": "Gizmo directory path", + "multipath": true, + "multiplatform": true }, { - "type": "splitter" - }, - { - "type": "label", - "label": "Absolute path to gizmo folders." - }, - { - "type": "path", - "key": "gizmo_path", - "label": "Gizmo Path", - "multipath": true - }, - { - "type": "raw-json", - "key": "gizmo_definition", - "label": "Gizmo definition", - "is_list": true + "type": "collapsible-wrap", + "label": "Options", + "collapsible": true, + "collapsed": true, + "children": [ + { + "type": "path", + "key": "toolbar_icon_path", + "label": "Toolbar Icon Path", + "multipath": false, + "multiplatform": true + }, + { + "type": "splitter" + }, + { + "type": "list", + "key": "gizmo_definition", + "label": "Gizmo definitions", + "use_label_wrap": true, + "object_type": { + "type": "dict-conditional", + "enum_key": "sourcetype", + "enum_label": "Type of usage", + "enum_children": [ + { + "key": "python", + "label": "Python", + "children": [ + { + "type": "text", + "key": "title", + "label": "Title" + }, + { + "type": "text", + "key": "gizmo_toolbar_path", + "label": "Toolbar path" + }, + { + "type": "text", + "key": "command", + "label": "Python command" + }, + { + "type": "text", + "key": "shortcut", + "label": "Hotkey" + } + ] + }, + { + "key": "file", + "label": "File", + "children": [ + { + "type": "text", + "key": "title", + "label": "Title" + }, + { + "type": "text", + "key": "gizmo_toolbar_path", + "label": "Toolbar path" + }, + { + "type": "text", + "key": "file_name", + "label": "Gizmo file name" + }, + { + "type": "text", + "key": "shortcut", + "label": "Hotkey" + } + + ] + }, + { + "key": "separator", + "label": "Separator", + "children": [ + { + "type": "text", + "key": "gizmo_toolbar_path", + "label": "Toolbar path" + } + ] + } + ] + } + } + ] } ] } From b77cb4ba1a367e5974342e670241d19137fb2a3e Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 6 Jun 2022 19:17:34 +0200 Subject: [PATCH 0477/1227] Add global menu from settings --- openpype/hosts/nuke/api/gizmo_menu.py | 52 +++---- openpype/hosts/nuke/api/lib.py | 1 - .../defaults/project_settings/nuke.json | 13 +- .../schemas/schema_nuke_scriptsgizmo.json | 133 +++++++++--------- 4 files changed, 106 insertions(+), 93 deletions(-) diff --git a/openpype/hosts/nuke/api/gizmo_menu.py b/openpype/hosts/nuke/api/gizmo_menu.py index 7f8121372c..42b5812360 100644 --- a/openpype/hosts/nuke/api/gizmo_menu.py +++ b/openpype/hosts/nuke/api/gizmo_menu.py @@ -38,38 +38,42 @@ class GizmoMenu(): return parent def build_from_configuration(self, configuration): - for item in configuration: - assert isinstance(item, dict), "Configuration is wrong!" - + for menu in configuration: # Construct parent path else parent is toolbar parent = self.toolbar - gizmo_toolbar_path = item.get("gizmo_toolbar_path") + gizmo_toolbar_path = menu.get("gizmo_toolbar_path") if gizmo_toolbar_path: parent = self._make_menu_path(gizmo_toolbar_path) - item_type = item.get("sourcetype") + for item in menu["sub_gizmo_list"]: + assert isinstance(item, dict), "Configuration is wrong!" - if item_type == ("python" or "file"): - parent.addCommand( - item['title'], - command=str(item["command"]), - icon=item.get("icon"), - shortcut=item.get('hotkey') - ) + if not item.get("title"): + continue - # add separator - # Special behavior for separators - elif item_type == "separator": - parent.addSeparator() + item_type = item.get("sourcetype") - # add submenu - # items should hold a collection of submenu items (dict) - elif item_type == "menu": - # assert "items" in item, "Menu is missing 'items' key" - parent.addMenu( - item['title'], - icon=item.get('icon') - ) + if item_type == ("python" or "file"): + parent.addCommand( + item["title"], + command=str(item["command"]), + icon=item.get("icon"), + shortcut=item.get("hotkey") + ) + + # add separator + # Special behavior for separators + elif item_type == "separator": + parent.addSeparator() + + # add submenu + # items should hold a collection of submenu items (dict) + elif item_type == "menu": + # assert "items" in item, "Menu is missing 'items' key" + parent.addMenu( + item['title'], + icon=item.get('icon') + ) def add_gizmo_path(self, gizmo_paths): for gizmo_path in gizmo_paths: diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 335e7190a0..0d766c8459 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2516,7 +2516,6 @@ def add_scripts_gizmo(): for gizmo_settings in project_settings["nuke"]["gizmo"]: gizmo_list_definition = gizmo_settings["gizmo_definition"] - print(1, gizmo_list_definition) toolbar_name = gizmo_settings["toolbar_menu_name"] # gizmo_toolbar_path = gizmo_settings["gizmo_toolbar_path"] gizmo_source_dir = gizmo_settings.get( diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 63978ad1be..c609a0927a 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -301,10 +301,15 @@ "toolbar_icon_path": {}, "gizmo_definition": [ { - "type": "action", - "sourcetype": "python", - "title": "Gizmo Note", - "command": "nuke.nodes.StickyNote(label='You can create your own toolbar menu in the Nuke GizmoMenu of OpenPype')" + "gizmo_toolbar_path": "/path/to/menu", + "sub_gizmo_list": [ + { + "sourcetype": "python", + "title": "Gizmo Note", + "command": "nuke.nodes.StickyNote(label='You can create your own toolbar menu in the Nuke GizmoMenu of OpenPype')", + "shortcut": "" + } + ] } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsgizmo.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsgizmo.json index 80fda56175..abe14970c5 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsgizmo.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsgizmo.json @@ -41,73 +41,78 @@ "label": "Gizmo definitions", "use_label_wrap": true, "object_type": { - "type": "dict-conditional", - "enum_key": "sourcetype", - "enum_label": "Type of usage", - "enum_children": [ + "type": "dict", + "children": [ { - "key": "python", - "label": "Python", - "children": [ - { - "type": "text", - "key": "title", - "label": "Title" - }, - { - "type": "text", - "key": "gizmo_toolbar_path", - "label": "Toolbar path" - }, - { - "type": "text", - "key": "command", - "label": "Python command" - }, - { - "type": "text", - "key": "shortcut", - "label": "Hotkey" - } - ] + "type": "text", + "key": "gizmo_toolbar_path", + "label": "Gizmo Menu Path" }, { - "key": "file", - "label": "File", - "children": [ - { - "type": "text", - "key": "title", - "label": "Title" - }, - { - "type": "text", - "key": "gizmo_toolbar_path", - "label": "Toolbar path" - }, - { - "type": "text", - "key": "file_name", - "label": "Gizmo file name" - }, - { - "type": "text", - "key": "shortcut", - "label": "Hotkey" - } - - ] - }, - { - "key": "separator", - "label": "Separator", - "children": [ - { - "type": "text", - "key": "gizmo_toolbar_path", - "label": "Toolbar path" - } - ] + "type": "list", + "key": "sub_gizmo_list", + "label": "Sub Gizmo List", + "use_label_wrap": true, + "object_type": { + "type": "dict-conditional", + "enum_key": "sourcetype", + "enum_label": "Type of usage", + "enum_children": [ + { + "key": "python", + "label": "Python", + "children": [ + { + "type": "text", + "key": "title", + "label": "Title" + }, + { + "type": "text", + "key": "command", + "label": "Python command" + }, + { + "type": "text", + "key": "shortcut", + "label": "Hotkey" + } + ] + }, + { + "key": "file", + "label": "File", + "children": [ + { + "type": "text", + "key": "title", + "label": "Title" + }, + { + "type": "text", + "key": "file_name", + "label": "Gizmo file name" + }, + { + "type": "text", + "key": "shortcut", + "label": "Hotkey" + } + ] + }, + { + "key": "separator", + "label": "Separator", + "children": [ + { + "type": "text", + "key": "gizmo_toolbar_path", + "label": "Toolbar path" + } + ] + } + ] + } } ] } From 25e9ee617e798e14cffb6e2090e1a4dbdb88214c Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 6 Jun 2022 21:00:24 +0200 Subject: [PATCH 0478/1227] linter correction --- openpype/hosts/nuke/api/gizmo_menu.py | 2 +- openpype/hosts/nuke/api/lib.py | 20 ++++++-------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/nuke/api/gizmo_menu.py b/openpype/hosts/nuke/api/gizmo_menu.py index 42b5812360..0f1a3e03fc 100644 --- a/openpype/hosts/nuke/api/gizmo_menu.py +++ b/openpype/hosts/nuke/api/gizmo_menu.py @@ -26,7 +26,7 @@ class GizmoMenu(): def _make_menu_path(self, path, icon=None): parent = self.toolbar - for folder in re.split(r"/|\\",path): + for folder in re.split(r"/|\\", path): if not folder: continue existing_menu = parent.findItem(folder) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 0d766c8459..2c5989309b 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2501,14 +2501,6 @@ def recreate_instance(origin_node, avalon_data=None): def add_scripts_gizmo(): - try: - from openpype.hosts.nuke.api import lib - except ImportError: - log.warning( - "Skipping studio.gizmo install, because " - "'scriptsgizmo' module seems unavailable." - ) - return # load configuration of custom menu project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) @@ -2524,15 +2516,15 @@ def add_scripts_gizmo(): "toolbar_icon_path", {}).get(platform_name) if not gizmo_source_dir: - log.debug("Skipping studio gizmo `{}`, no gizmo path found.".format( - toolbar_name - )) + log.debug("Skipping studio gizmo `{}`, " + "no gizmo path found.".format(toolbar_name) + ) return if not gizmo_list_definition: - log.debug("Skipping studio gizmo `{}`, no definition found.".format( - toolbar_name - )) + log.debug("Skipping studio gizmo `{}`, " + "no definition found.".format(toolbar_name) + ) return if toolbar_icon_path: From 2f945b9a7b3ce04fa3a37eb3eafac5cdc3efe942 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 7 Jun 2022 11:06:10 +0200 Subject: [PATCH 0479/1227] Added checkbox to filter only enabled projects Default is true, is not persistent between opening of dialog. --- openpype/modules/sync_server/tray/app.py | 36 +++++++++++++++----- openpype/modules/sync_server/tray/widgets.py | 3 ++ 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/openpype/modules/sync_server/tray/app.py b/openpype/modules/sync_server/tray/app.py index fc8558bdbc..4aa3d430fa 100644 --- a/openpype/modules/sync_server/tray/app.py +++ b/openpype/modules/sync_server/tray/app.py @@ -46,6 +46,14 @@ class SyncServerWindow(QtWidgets.QDialog): left_column_layout.addWidget(self.pause_btn) + checkbox = QtWidgets.QCheckBox("Show only enabled", self) + checkbox.setStyleSheet("QCheckBox{spacing: 5px;" + "padding:5px 5px 5px 5px;}") + checkbox.setChecked(True) + self.show_only_enabled_chk = checkbox + + left_column_layout.addWidget(self.show_only_enabled_chk) + repres = SyncRepresentationSummaryWidget( sync_server, project=self.projects.current_project, @@ -86,8 +94,23 @@ class SyncServerWindow(QtWidgets.QDialog): repres.message_generated.connect(self._update_message) self.projects.message_generated.connect(self._update_message) + self.show_only_enabled_chk.stateChanged.connect( + self._on_enabled_change + ) + self.representationWidget = repres + def showEvent(self, event): + self.representationWidget.model.set_project( + self.projects.current_project) + self.projects.refresh() + self._set_running(True) + super().showEvent(event) + + def closeEvent(self, event): + self._set_running(False) + super().closeEvent(event) + def _on_project_change(self): if self.projects.current_project is None: return @@ -103,16 +126,11 @@ class SyncServerWindow(QtWidgets.QDialog): self.projects.refresh() return - def showEvent(self, event): - self.representationWidget.model.set_project( - self.projects.current_project) + def _on_enabled_change(self): + """Called when enabled projects only checkbox is toggled.""" + self.projects.show_only_enabled = \ + self.show_only_enabled_chk.isChecked() self.projects.refresh() - self._set_running(True) - super().showEvent(event) - - def closeEvent(self, event): - self._set_running(False) - super().closeEvent(event) def _set_running(self, running): self.representationWidget.model.is_running = running diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index e41910fa4f..88ed2c1d37 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -47,6 +47,7 @@ class SyncProjectListWidget(QtWidgets.QWidget): message_generated = QtCore.Signal(str) refresh_msec = 10000 + show_only_enabled = True def __init__(self, sync_server, parent): super(SyncProjectListWidget, self).__init__(parent) @@ -128,6 +129,8 @@ class SyncProjectListWidget(QtWidgets.QWidget): self.sync_server.is_project_paused(project_name): icon = self._get_icon("paused") elif not sync_settings[project_name]["enabled"]: + if self.show_only_enabled: + continue icon = self._get_icon("disabled") else: icon = self._get_icon("synced") From e2d6d903e99120048d92cb7fbabe42b111df373c Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Tue, 7 Jun 2022 11:23:45 +0000 Subject: [PATCH 0480/1227] docs: update README.md [skip ci] --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b6966adbc4..b8c04f8b49 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-26-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-27-orange.svg?style=flat-square)](#contributors-) OpenPype ==== @@ -328,6 +328,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Malthaldar

💻
Sven Neve

💻
zafrs

💻 +
Félix David

💻 📖 From 884fce81ce10c6f31e8738b1e7c6f178787ba9df Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Tue, 7 Jun 2022 11:23:46 +0000 Subject: [PATCH 0481/1227] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index a3b85cae68..b30f3b2499 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -309,7 +309,18 @@ "contributions": [ "code" ] + }, + { + "login": "Tilix4", + "name": "Félix David", + "avatar_url": "https://avatars.githubusercontent.com/u/22875539?v=4", + "profile": "http://felixdavid.com/", + "contributions": [ + "code", + "doc" + ] } ], - "contributorsPerLine": 7 -} \ No newline at end of file + "contributorsPerLine": 7, + "skipCi": true +} From 9babdb7832c9cc6644e0dc4ff8f3d024b068fd52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 7 Jun 2022 14:18:48 +0200 Subject: [PATCH 0482/1227] :recycle: limit the scope of file attribute skipping filter --- openpype/hosts/maya/plugins/publish/collect_look.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 9b6d1d999c..60af5238d0 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -601,8 +601,10 @@ class CollectLook(pyblish.api.InstancePlugin): source, computed_source)) - if not source: - self.log.info("source is empty, skipping...") + # renderman allows nodes to have filename attribute empty while + # you can have another incoming connection from different node. + if not source and cmds.nodeType(node).lower().startswith("pxr"): + self.log.info("Renderman: source is empty, skipping...") continue # We replace backslashes with forward slashes because V-Ray # can't handle the UDIM files with the backslashes in the From 71cabc76c4386bff5c8d89ab0afcc6d5d15c61ab Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 7 Jun 2022 15:13:08 +0200 Subject: [PATCH 0483/1227] Handle when no projects are shown or selected --- openpype/modules/sync_server/tray/app.py | 1 + openpype/modules/sync_server/tray/models.py | 33 +++++++++++++------- openpype/modules/sync_server/tray/widgets.py | 6 ++-- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/openpype/modules/sync_server/tray/app.py b/openpype/modules/sync_server/tray/app.py index 4aa3d430fa..dee3bf0ecc 100644 --- a/openpype/modules/sync_server/tray/app.py +++ b/openpype/modules/sync_server/tray/app.py @@ -131,6 +131,7 @@ class SyncServerWindow(QtWidgets.QDialog): self.projects.show_only_enabled = \ self.show_only_enabled_chk.isChecked() self.projects.refresh() + self.representationWidget.model.set_project(None) def _set_running(self, running): self.representationWidget.model.is_running = running diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index 6b309312a2..c49edeafb9 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -52,7 +52,8 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): All queries should go through this (because of collection). """ - return self.sync_server.connection.database[self.project] + if self.project: + return self.sync_server.connection.database[self.project] @property def project(self): @@ -150,6 +151,9 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): @property def can_edit(self): """Returns true if some site is user local site, eg. could edit""" + if not self.project: + return False + return get_local_site_id() in (self.active_site, self.remote_site) def get_column(self, index): @@ -190,7 +194,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): actually queried (scrolled a couple of times to list more than single page of records) """ - if self.is_editing or not self.is_running: + if self.is_editing or not self.is_running or not self.project: return self.refresh_started.emit() self.beginResetModel() @@ -232,6 +236,9 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): more records in DB than loaded. """ log.debug("fetchMore") + if not self.dbcon: + return + items_to_fetch = min(self._total_records - self._rec_loaded, self.PAGE_SIZE) self.query = self.get_query(self._rec_loaded) @@ -286,9 +293,10 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): # replace('False', 'false').\ # replace('True', 'true').replace('None', 'null')) - representations = self.dbcon.aggregate(pipeline=self.query, - allowDiskUse=True) - self.refresh(representations) + if self.dbcon: + representations = self.dbcon.aggregate(pipeline=self.query, + allowDiskUse=True) + self.refresh(representations) def set_word_filter(self, word_filter): """ @@ -380,6 +388,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): self._project = project # project might have been deactivated in the meantime if not self.sync_server.get_sync_project_setting(project): + self._data = {} return self.active_site = self.sync_server.get_active_site(self.project) @@ -508,21 +517,23 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): self._word_filter = None - if not self._project or self._project == lib.DUMMY_PROJECT: - return - self.sync_server = sync_server # TODO think about admin mode + self.sort_criteria = self.DEFAULT_SORT + + self.timer = QtCore.QTimer() + if not self._project or self._project == lib.DUMMY_PROJECT: + self.active_site = sync_server.DEFAULT_SITE + self.remote_site = sync_server.DEFAULT_SITE + return + # this is for regular user, always only single local and single remote self.active_site = self.sync_server.get_active_site(self.project) self.remote_site = self.sync_server.get_remote_site(self.project) - self.sort_criteria = self.DEFAULT_SORT - self.query = self.get_query() self.default_query = list(self.get_query()) - self.timer = QtCore.QTimer() self.timer.timeout.connect(self.tick) self.timer.start(self.REFRESH_SEC) diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 88ed2c1d37..89ab12ea4d 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -144,12 +144,12 @@ class SyncProjectListWidget(QtWidgets.QWidget): if self.current_project == project_name: selected_item = item + if model.item(0) is None: + return + if selected_item: selected_index = model.indexFromItem(selected_item) - if len(self.sync_server.sync_project_settings.keys()) == 0: - model.appendRow(QtGui.QStandardItem(lib.DUMMY_PROJECT)) - if not self.current_project: self.current_project = model.item(0).data(QtCore.Qt.DisplayRole) From 9829410932e18f85f4e899a2af045e65aa1d12ab Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 15:16:39 +0200 Subject: [PATCH 0484/1227] implemented get_representation_parents for single representation --- openpype/client/__init__.py | 2 ++ openpype/client/entities.py | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 861f828e67..e4c01fc0cc 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -22,6 +22,7 @@ from .entities import ( get_representation, get_representation_by_name, get_representations, + get_representation_parents, get_representations_parents, get_thumbnail, @@ -52,6 +53,7 @@ __all__ = ( "get_representation", "get_representation_by_name", "get_representations", + "get_representation_parents", "get_representations_parents", "get_thubmnail", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 4b52f8cf2d..e9b820dd1a 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -641,6 +641,15 @@ def get_representations_parents(project_name, representations): return output +def get_representation_parents(project_name, representation): + if not representation: + return None + + repre_id = representation["_id"] + parents_by_repre_id = get_representations(project_name, [representation]) + return parents_by_repre_id.get(repre_id) + + def get_thumbnail_id_from_source(project_name, src_type, src_id): if not src_type or not src_id: return None From f760c9d767c46a0ee433e83890cc15b4565f412f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 7 Jun 2022 15:28:11 +0200 Subject: [PATCH 0485/1227] :recycle: more explicit renderman check --- openpype/hosts/maya/plugins/publish/collect_look.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 60af5238d0..4a34cfdea2 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -603,7 +603,14 @@ class CollectLook(pyblish.api.InstancePlugin): # renderman allows nodes to have filename attribute empty while # you can have another incoming connection from different node. - if not source and cmds.nodeType(node).lower().startswith("pxr"): + pxr_nodes = set() + if cmds.pluginInfo("RenderMan_for_Maya", query=True, loaded=True): + pxr_nodes = set( + cmds.pluginInfo("RenderMan_for_Maya", + query=True, + dependNode=True) + ) + if not source and cmds.nodeType(node) in pxr_nodes: self.log.info("Renderman: source is empty, skipping...") continue # We replace backslashes with forward slashes because V-Ray From dcac9e08a6aae510f024389870a212d0aae1596e Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 7 Jun 2022 15:53:08 +0200 Subject: [PATCH 0486/1227] :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 0d38c76f5402cd99e5a70385667ff1a5f0885eb7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 16:28:53 +0200 Subject: [PATCH 0487/1227] separated functions to query asset by name and id --- openpype/client/__init__.py | 8 ++-- openpype/client/entities.py | 43 ++++++++++++++----- openpype/tools/creator/window.py | 6 +-- openpype/tools/mayalookassigner/commands.py | 4 +- .../tools/publisher/widgets/create_dialog.py | 4 +- openpype/tools/sceneinventory/model.py | 4 +- .../tools/sceneinventory/switch_dialog.py | 32 +++++++------- openpype/tools/sceneinventory/view.py | 4 +- openpype/tools/standalonepublish/app.py | 6 +-- .../standalonepublish/widgets/widget_asset.py | 10 ++--- .../widgets/widget_family.py | 14 +++--- openpype/tools/texture_copy/app.py | 4 +- openpype/tools/utils/lib.py | 6 +-- openpype/tools/utils/tasks_widget.py | 6 +-- openpype/tools/workfiles/files_widget.py | 4 +- openpype/tools/workfiles/save_as_dialog.py | 6 +-- openpype/tools/workfiles/window.py | 6 +-- 17 files changed, 94 insertions(+), 73 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index e4c01fc0cc..2ba2a3693e 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -2,7 +2,8 @@ from .entities import ( get_projects, get_project, - get_asset, + get_asset_by_id, + get_asset_by_name, get_assets, get_asset_ids_with_subsets, @@ -33,7 +34,8 @@ __all__ = ( "get_projects", "get_project", - "get_asset", + "get_asset_by_id", + "get_asset_by_name", "get_assets", "get_asset_ids_with_subsets", @@ -56,6 +58,6 @@ __all__ = ( "get_representation_parents", "get_representations_parents", - "get_thubmnail", + "get_thumbnail", "get_thumbnail_id_from_source", ) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index e9b820dd1a..97553f30fe 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -90,23 +90,44 @@ def get_project(project_name, active=True, inactive=False, fields=None): return conn.find_one(query_filter, _prepare_fields(fields)) -def get_asset(project_name, asset_name=None, asset_id=None, fields=None): - query_filter = {"type": "asset"} - has_filter = False - if asset_name: - has_filter = True - query_filter["name"] = asset_name +def get_asset_by_id(project_name, asset_id, fields=None): + """Receive asset data by it's id. - if asset_id: - has_filter = True - query_filter["_id"] = _convert_id(asset_id) + Args: + project_name (str): Name of project where to look for subset. + asset_id (str|ObjectId): Asset's id. - # Avoid random asset quqery - if not has_filter: + Returns: + dict: Asset entity data. + None: Asset was not found by id. + """ + + asset_id = _convert_id(asset_id) + if not asset_id: return None + query_filter = {"type": "asset", "_id": asset_id} conn = _get_project_connection(project_name) + return conn.find_one(query_filter, _prepare_fields(fields)) + +def get_asset_by_name(project_name, asset_name, fields=None): + """Receive asset data by it's name. + + Args: + project_name (str): Name of project where to look for subset. + asset_name (str): Asset's name. + + Returns: + dict: Asset entity data. + None: Asset was not found by name. + """ + + if not asset_name: + return None + + query_filter = {"type": "asset", "name": asset_name} + conn = _get_project_connection(project_name) return conn.find_one(query_filter, _prepare_fields(fields)) diff --git a/openpype/tools/creator/window.py b/openpype/tools/creator/window.py index a85f47a060..a3937d6a40 100644 --- a/openpype/tools/creator/window.py +++ b/openpype/tools/creator/window.py @@ -4,7 +4,7 @@ import re from Qt import QtWidgets, QtCore -from openpype.client import get_asset, get_subsets +from openpype.client import get_asset_by_name, get_subsets from openpype import style from openpype.api import get_current_project_settings from openpype.tools.utils.lib import qt_app_context @@ -220,8 +220,8 @@ class CreatorWindow(QtWidgets.QDialog): asset_doc = None if creator_plugin: # Get the asset from the database which match with the name - asset_doc = get_asset( - project_name, asset_name=asset_name, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["_id"] ) # Get plugin diff --git a/openpype/tools/mayalookassigner/commands.py b/openpype/tools/mayalookassigner/commands.py index a4fc1fab70..2e7a51efde 100644 --- a/openpype/tools/mayalookassigner/commands.py +++ b/openpype/tools/mayalookassigner/commands.py @@ -4,7 +4,7 @@ import os import maya.cmds as cmds -from openpype.client import get_asset +from openpype.client import get_asset_by_id from openpype.pipeline import ( legacy_io, remove_container, @@ -161,7 +161,7 @@ def create_items_from_nodes(nodes): project_name = legacy_io.active_project() for _id, id_nodes in id_hashes.items(): - asset = get_asset(project_name, asset_id=_id, fields=["name"]) + asset = get_asset_by_id(project_name, _id, fields=["name"]) # Skip if asset id is not found if not asset: diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index d579831b21..53bbef8b75 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -10,7 +10,7 @@ except Exception: commonmark = None from Qt import QtWidgets, QtCore, QtGui -from openpype.client import get_asset, get_subsets +from openpype.client import get_asset_by_name, get_subsets from openpype.lib import TaskNotSetError from openpype.pipeline.create import ( CreatorError, @@ -650,7 +650,7 @@ class CreateDialog(QtWidgets.QDialog): return project_name = self.dbcon.active_project() - asset_doc = get_asset(project_name, asset_name=asset_name) + asset_doc = get_asset_by_name(project_name, asset_name) self._asset_doc = asset_doc if asset_doc: diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 9cf69ed650..6d813af45b 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -7,7 +7,7 @@ from Qt import QtCore, QtGui import qtawesome from openpype.client import ( - get_asset, + get_asset_by_id, get_subset, get_version, get_last_version_for_subset, @@ -342,7 +342,7 @@ class InventoryModel(TreeModel): not_found_ids.append(repre_id) continue - asset = get_asset(project_name, asset_id=subset["parent"]) + asset = get_asset_by_id(project_name, subset["parent"]) if not asset: not_found["asset"].append(group_items) not_found_ids.append(repre_id) diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index b940c66a56..6d6294af6f 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -5,7 +5,7 @@ import qtawesome from bson.objectid import ObjectId from openpype.client import ( - get_asset, + get_asset_by_name, get_assets, get_subset, get_subsets, @@ -484,9 +484,9 @@ class SwitchAssetDialog(QtWidgets.QDialog): # Prepare asset document if asset is selected asset_doc = None if selected_asset: - asset_doc = get_asset( + asset_doc = get_asset_by_name( self.active_project(), - asset_name=selected_asset, + selected_asset, fields=["_id"] ) if not asset_doc: @@ -768,8 +768,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): project_name = self.active_project() selected_asset = self._assets_box.get_valid_value() if selected_asset: - asset_doc = get_asset( - project_name, asset_name=selected_asset, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) asset_ids = [asset_doc["_id"]] else: @@ -832,8 +832,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [x] [?] if selected_asset and selected_subset: - asset_doc = get_asset( - project_name, asset_name=selected_asset, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) subset_doc = get_subset( project_name, @@ -858,8 +858,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [ ] [?] # If asset only is selected if selected_asset: - asset_doc = get_asset( - project_name, asset_name=selected_asset, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) if not asset_doc: return list() @@ -995,8 +995,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [ ] [?] project_name = self.active_project() - asset_doc = get_asset( - project_name, asset_name=selected_asset, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) subset_docs = get_subsets( project_name, asset_ids=[asset_doc["_id"]], fields=["name"] @@ -1043,8 +1043,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [x] [ ] project_name = self.active_project() if selected_asset is not None and selected_subset is not None: - asset_doc = get_asset( - project_name, asset_name=selected_asset, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) subset_doc = get_subset( project_name, @@ -1077,8 +1077,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [ ] [ ] if selected_asset is not None: - asset_doc = get_asset( - project_name, asset_name=selected_asset, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) subset_docs = list(get_subsets( project_name, @@ -1189,7 +1189,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): project_name = self.active_project() if selected_asset: - asset_doc = get_asset(project_name, asset_name=selected_asset) + asset_doc = get_asset_by_name(project_name, selected_asset) asset_docs_by_id = {asset_doc["_id"]: asset_doc} else: asset_docs_by_id = self.content_assets diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 6a95ccb57b..04e0f67a15 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -657,11 +657,9 @@ class SceneInventoryView(QtWidgets.QTreeView): project_name = legacy_io.active_project() # Get available versions for active representation - representation_id = ObjectId(active["representation"]) - repre_doc = get_representation( project_name, - representation_id=representation_id, + representation_id=active["representation"], fields=["parent"] ) diff --git a/openpype/tools/standalonepublish/app.py b/openpype/tools/standalonepublish/app.py index 4831db038c..3ceeb3ad48 100644 --- a/openpype/tools/standalonepublish/app.py +++ b/openpype/tools/standalonepublish/app.py @@ -6,7 +6,7 @@ import signal from bson.objectid import ObjectId from Qt import QtWidgets, QtCore, QtGui -from openpype.client import get_asset +from openpype.client import get_asset_by_id from .widgets import ( AssetWidget, FamilyWidget, ComponentsWidget, ShadowWidget @@ -144,8 +144,8 @@ class Window(QtWidgets.QDialog): if len(selected) == 1: self.valid_parent = True project_name = self.db.active_project() - asset = get_asset( - project_name, asset_id=selected[0], fields=["name"] + asset = get_asset_by_id( + project_name, selected[0], fields=["name"] ) self.widget_family.change_asset(asset['name']) else: diff --git a/openpype/tools/standalonepublish/widgets/widget_asset.py b/openpype/tools/standalonepublish/widgets/widget_asset.py index 0b5802ed9e..73114f7960 100644 --- a/openpype/tools/standalonepublish/widgets/widget_asset.py +++ b/openpype/tools/standalonepublish/widgets/widget_asset.py @@ -4,7 +4,7 @@ import qtawesome from openpype.client import ( get_project, - get_asset, + get_asset_by_id, ) from openpype.tools.utils import PlaceholderLineEdit @@ -251,9 +251,9 @@ class AssetWidget(QtWidgets.QWidget): return output project_name = self.dbcon.active_project() - parent = get_asset( + parent = get_asset_by_id( project_name, - asset_id=parent_asset_id, + parent_asset_id, fields=["name", "data.visualParent"] ) output.append(parent['name']) @@ -362,8 +362,8 @@ class AssetWidget(QtWidgets.QWidget): selected = self.get_selected_assets() if len(selected) == 1: project_name = self.dbcon.active_project() - asset = get_asset( - project_name, asset_id=selected[0], fields=["data.tasks"] + asset = get_asset_by_id( + project_name, selected[0], fields=["data.tasks"] ) if asset: tasks = asset.get('data', {}).get('tasks', []) diff --git a/openpype/tools/standalonepublish/widgets/widget_family.py b/openpype/tools/standalonepublish/widgets/widget_family.py index ed9f405f38..fa157d37f1 100644 --- a/openpype/tools/standalonepublish/widgets/widget_family.py +++ b/openpype/tools/standalonepublish/widgets/widget_family.py @@ -3,7 +3,7 @@ import re from Qt import QtWidgets, QtCore from openpype.client import ( - get_asset, + get_asset_by_name, get_subset, get_subsets, get_last_version_for_subset, @@ -188,8 +188,8 @@ class FamilyWidget(QtWidgets.QWidget): if asset_name != self.NOT_SELECTED: # Get the assets from the database which match with the name project_name = self.dbcon.active_project() - asset_doc = get_asset( - project_name, asset_name=asset_name, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["_id"] ) # Get plugin and family @@ -205,8 +205,8 @@ class FamilyWidget(QtWidgets.QWidget): # Get the asset from the database which match with the name project_name = self.dbcon.active_project() - asset_doc = get_asset( - project_name, asset_name=asset_name, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["_id"] ) # Get plugin plugin = item.data(PluginRole) @@ -310,8 +310,8 @@ class FamilyWidget(QtWidgets.QWidget): asset_name != self.NOT_SELECTED and subset_name.strip() != '' ): - asset_doc = get_asset( - project_name, asset_name=asset_name, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["_id"] ) if asset_doc: diff --git a/openpype/tools/texture_copy/app.py b/openpype/tools/texture_copy/app.py index 8703f075d3..746a72b3ec 100644 --- a/openpype/tools/texture_copy/app.py +++ b/openpype/tools/texture_copy/app.py @@ -4,7 +4,7 @@ import click import speedcopy -from openpype.client import get_project, get_asset +from openpype.client import get_project, get_asset_by_name from openpype.lib import Terminal from openpype.api import Anatomy from openpype.pipeline import legacy_io @@ -94,7 +94,7 @@ class TextureCopy: t.echo("!!! Project name [ {} ] not found.".format(project_name)) exit(1) - asset = get_asset(project_name, asset_name=asset_name) + asset = get_asset_by_name(project_name, asset_name) if not asset: t.echo("!!! Asset [ {} ] not found in project".format(asset_name)) exit(1) diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 72ebfcc063..ea1362945f 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -8,7 +8,7 @@ import qtawesome from openpype.client import ( get_project, - get_asset, + get_asset_by_name, ) from openpype.style import ( get_default_entity_icon_color, @@ -434,8 +434,8 @@ class FamilyConfigCache: database = getattr(self.dbcon, "database", None) if database is None: database = self.dbcon._database - asset_doc = get_asset( - project_name, asset_name=asset_name, fields=["data.tasks"] + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["data.tasks"] ) or {} tasks_info = asset_doc.get("data", {}).get("tasks") or {} task_type = tasks_info.get(task_name, {}).get("type") diff --git a/openpype/tools/utils/tasks_widget.py b/openpype/tools/utils/tasks_widget.py index fcbec318f5..0353f3dd2f 100644 --- a/openpype/tools/utils/tasks_widget.py +++ b/openpype/tools/utils/tasks_widget.py @@ -3,7 +3,7 @@ import qtawesome from openpype.client import ( get_project, - get_asset, + get_asset_by_id, ) from openpype.style import get_disabled_entity_icon_color from openpype.tools.utils.lib import get_task_icon @@ -77,8 +77,8 @@ class TasksModel(QtGui.QStandardItemModel): asset_doc = None if self._context_is_valid(): project_name = self._get_current_project() - asset_doc = get_asset( - project_name, asset_id=asset_id, fields=["data.tasks"] + asset_doc = get_asset_by_id( + project_name, asset_id, fields=["data.tasks"] ) self._set_asset(asset_doc) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 36b9a055d8..68fe8301c9 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -5,7 +5,7 @@ import shutil import Qt from Qt import QtWidgets, QtCore -from openpype.client import get_asset +from openpype.client import get_asset_by_id from openpype.tools.utils import PlaceholderLineEdit from openpype.tools.utils.delegates import PrettyTimeDelegate from openpype.lib import ( @@ -386,7 +386,7 @@ class FilesWidget(QtWidgets.QWidget): if self._asset_doc is None: project_name = legacy_io.active_project() - self._asset_doc = get_asset(project_name, asset_id=self._asset_id) + self._asset_doc = get_asset_by_id(project_name, self._asset_id) return self._asset_doc diff --git a/openpype/tools/workfiles/save_as_dialog.py b/openpype/tools/workfiles/save_as_dialog.py index 1fbcbfeb22..b62fd2c889 100644 --- a/openpype/tools/workfiles/save_as_dialog.py +++ b/openpype/tools/workfiles/save_as_dialog.py @@ -7,7 +7,7 @@ from Qt import QtWidgets, QtCore from openpype.client import ( get_project, - get_asset, + get_asset_by_name, ) from openpype.lib import ( get_last_workfile_with_version, @@ -33,9 +33,9 @@ def build_workfile_data(session): project_doc = get_project( project_name, fields=["name", "data.code", "config.tasks"] ) - asset_doc = get_asset( + asset_doc = get_asset_by_name( project_name, - asset_name=asset_name, + asset_name, fields=["name", "data.tasks", "data.parents"] ) diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index 45d8d41d16..9f4cea2f8a 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -2,7 +2,7 @@ import os import datetime from Qt import QtCore, QtWidgets -from openpype.client import get_asset +from openpype.client import get_asset_by_id, get_asset_by_name from openpype import style from openpype.lib import ( get_workfile_doc, @@ -302,7 +302,7 @@ class Window(QtWidgets.QMainWindow): workdir, filename = os.path.split(filepath) asset_id = self.assets_widget.get_selected_asset_id() project_name = legacy_io.active_project() - asset_doc = get_asset(project_name, asset_id=asset_id) + asset_doc = get_asset_by_id(project_name, asset_id) task_name = self.tasks_widget.get_selected_task_name() create_workfile_doc( asset_doc, task_name, filename, workdir, legacy_io @@ -328,7 +328,7 @@ class Window(QtWidgets.QMainWindow): self._context_to_set, context = None, self._context_to_set if "asset" in context: - asset_doc = get_asset( + asset_doc = get_asset_by_name( self.project_name, context["asset"], fields=["_id"] ) From 20ecefa4aae7918fb6a0dbdd5d3de141249eb69d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 16:43:07 +0200 Subject: [PATCH 0488/1227] added docstrings for asset queries --- openpype/client/entities.py | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 97553f30fe..16dc3d76a9 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -94,8 +94,10 @@ def get_asset_by_id(project_name, asset_id, fields=None): """Receive asset data by it's id. Args: - project_name (str): Name of project where to look for subset. + project_name (str): Name of project where to look for queried entities. asset_id (str|ObjectId): Asset's id. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. Returns: dict: Asset entity data. @@ -115,8 +117,10 @@ def get_asset_by_name(project_name, asset_name, fields=None): """Receive asset data by it's name. Args: - project_name (str): Name of project where to look for subset. + project_name (str): Name of project where to look for queried entities. asset_name (str): Asset's name. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. Returns: dict: Asset entity data. @@ -134,6 +138,25 @@ def get_asset_by_name(project_name, asset_name, fields=None): def get_assets( project_name, asset_ids=None, asset_names=None, archived=False, fields=None ): + """Assets for specified project by passed filters. + + Passed filters (ids and names) are always combined so all conditions must + match. + + To receive all assets from project just keep filters empty. + + Args: + project_name (str): Name of project where to look for queried entities. + asset_ids (list[str, ObjectId]): Asset ids that should be found. + asset_names (list[str]): Name assets that should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Query cursor as iterable which returns asset documents matching + passed filters. + """ + asset_types = ["asset"] if archived: asset_types.append("archived_asset") @@ -160,6 +183,16 @@ def get_assets( def get_asset_ids_with_subsets(project_name, asset_ids=None): + """Find out which assets have existing subsets. + + Args: + project_name (str): Name of project where to look for queried entities. + asset_ids (list[str|ObjectId]): Look only for entered asset ids. + + Returns: + List[ObjectId]: Asset ids that have existing subsets. + """ + subset_query = { "type": "subset" } From 9456dac8b92862120f580bfbd59327b83a78fd4c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 16:44:26 +0200 Subject: [PATCH 0489/1227] separated function get_subset into 2 separated functions --- openpype/client/__init__.py | 6 +- openpype/client/entities.py | 73 ++++++++++--------- openpype/tools/loader/widgets.py | 4 +- openpype/tools/sceneinventory/model.py | 4 +- .../tools/sceneinventory/switch_dialog.py | 26 +++---- .../widgets/widget_family.py | 8 +- 6 files changed, 63 insertions(+), 58 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 2ba2a3693e..34257cf3dc 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -7,7 +7,8 @@ from .entities import ( get_assets, get_asset_ids_with_subsets, - get_subset, + get_subset_by_id, + get_subset_by_name, get_subsets, get_subset_families, @@ -39,7 +40,8 @@ __all__ = ( "get_assets", "get_asset_ids_with_subsets", - "get_subset", + "get_subset_by_id", + "get_subset_by_name", "get_subsets", "get_subset_families", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 16dc3d76a9..07f6bb2e65 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -223,29 +223,13 @@ def get_asset_ids_with_subsets(project_name, asset_ids=None): return asset_ids_with_subsets -def get_subset( - project_name, - subset_id=None, - subset_name=None, - asset_id=None, - fields=None -): - """Single subset document by subset id or name and parent id. - - When subset id is defined it is not needed to add any other arguments but - subset name filter must be always combined with asset id (or subset id). - - Question: - This could be split into more functions? +def get_subset_by_id(project_name, subset_id, fields=None): + """Single subset document by it's id. Args: - project_name (str): Name of project where to look for subset. + project_name (str): Name of project where to look for queried entities. subset_id (ObjectId): Id of subset which should be found. - subset_name (str): Name of asset. Must be combined with 'asset_id' or - 'subset_id' arguments otherwise result is 'None'. - asset_id (ObjectId): Id of parent asset. Must be combined with - 'subset_name' or 'subset_id'. - fields (list): Fields that should be returned. All fields are + fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -253,23 +237,42 @@ def get_subset( Dict: Subset document which can be reduced to specified 'fields'. """ - query_filters = {"type": "subset"} - has_valid_filters = False - if subset_id is not None: - query_filters["_id"] = _convert_id(asset_id) - has_valid_filters = True - - if subset_name is not None: - if asset_id is not None: - has_valid_filters = True - query_filters["name"] = subset_name - - if asset_id is not None: - query_filters["parent"] = _convert_id(asset_id) - - if not has_valid_filters: + subset_id = _convert_id(subset_id) + if not subset_id: return None + query_filters = {"type": "subset", "_id": subset_id} + conn = _get_project_connection(project_name) + return conn.find_one(query_filters, _prepare_fields(fields)) + + +def get_subset_by_name(project_name, subset_name, asset_id, fields=None): + """Single subset document by subset name and it's version id. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_name (str): Name of subset. + asset_id (str|ObjectId): Id of parent asset. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If subset with specified filters was not found. + Dict: Subset document which can be reduced to specified 'fields'. + """ + + if not subset_name: + return None + + asset_id = _convert_id(asset_id) + if not asset_id: + return None + + query_filters = { + "type": "subset", + "name": subset_name, + "parent": asset_id + } conn = _get_project_connection(project_name) return conn.find_one(query_filters, _prepare_fields(fields)) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 6c7acc593d..4a9a911f93 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -9,7 +9,7 @@ from Qt import QtWidgets, QtCore, QtGui from openpype.client import ( get_subset_families, - get_subset, + get_subset_by_id, get_subsets, get_version, get_versions, @@ -688,7 +688,7 @@ class VersionTextEdit(QtWidgets.QTextEdit): _version_doc["name"] ) - subset = get_subset(project_name, subset_id=version_doc["parent"]) + subset = get_subset_by_id(project_name, version_doc["parent"]) assert subset, "No valid subset parent for version" # Define readable creation timestamp diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 6d813af45b..b36f7f4cea 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -8,7 +8,7 @@ import qtawesome from openpype.client import ( get_asset_by_id, - get_subset, + get_subset_by_id, get_version, get_last_version_for_subset, get_representation, @@ -336,7 +336,7 @@ class InventoryModel(TreeModel): version["name"] = HeroVersionType(_version["name"]) version["data"] = _version["data"] - subset = get_subset(project_name, subset_id=version["parent"]) + subset = get_subset_by_id(project_name, version["parent"]) if not subset: not_found["subset"].append(group_items) not_found_ids.append(repre_id) diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index 6d6294af6f..92ef2b3553 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -7,7 +7,7 @@ from bson.objectid import ObjectId from openpype.client import ( get_asset_by_name, get_assets, - get_subset, + get_subset_by_name, get_subsets, get_versions, get_hero_versions, @@ -537,10 +537,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): self, asset_doc, selected_subset, selected_repre ): project_name = self.active_project() - subset_doc = get_subset( + subset_doc = get_subset_by_name( project_name, - subset_name=selected_subset, - asset_id=asset_doc["_id"], + selected_subset, + asset_doc["_id"], fields=["_id"] ) @@ -560,10 +560,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): def _get_current_output_repre_ids_xxo(self, asset_doc, selected_subset): project_name = self.active_project() - subset_doc = get_subset( + subset_doc = get_subset_by_name( project_name, - asset_id=asset_doc["_id"], - subset_name=selected_subset, + selected_subset, + asset_doc["_id"], fields=["_id"] ) if not subset_doc: @@ -835,10 +835,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): asset_doc = get_asset_by_name( project_name, selected_asset, fields=["_id"] ) - subset_doc = get_subset( + subset_doc = get_subset_by_name( project_name, - asset_id=asset_doc["_id"], - subset_name=selected_subset, + selected_subset, + asset_doc["_id"], fields=["_id"] ) @@ -1046,10 +1046,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): asset_doc = get_asset_by_name( project_name, selected_asset, fields=["_id"] ) - subset_doc = get_subset( + subset_doc = get_subset_by_name( project_name, - asset_id=asset_doc["_id"], - subset_name=selected_subset, + selected_subset, + asset_doc["_id"], fields=["_id"] ) subset_id = subset_doc["_id"] diff --git a/openpype/tools/standalonepublish/widgets/widget_family.py b/openpype/tools/standalonepublish/widgets/widget_family.py index fa157d37f1..2f00cfe7bb 100644 --- a/openpype/tools/standalonepublish/widgets/widget_family.py +++ b/openpype/tools/standalonepublish/widgets/widget_family.py @@ -4,7 +4,7 @@ from Qt import QtWidgets, QtCore from openpype.client import ( get_asset_by_name, - get_subset, + get_subset_by_name, get_subsets, get_last_version_for_subset, ) @@ -315,10 +315,10 @@ class FamilyWidget(QtWidgets.QWidget): ) if asset_doc: - subset_doc = get_subset( + subset_doc = get_subset_by_name( project_name, - subset_name=subset_name, - asset_id=asset_doc['_id'], + subset_name, + asset_doc['_id'], fields=["_id"] ) From 0d5cc529d502cdf2a421be4f7db27dcbd54eb9ff Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 17:18:21 +0200 Subject: [PATCH 0490/1227] renamed 'get_version' to 'get_version_by_id' --- openpype/client/__init__.py | 4 +- openpype/client/entities.py | 107 ++++++++++++++++++++++--- openpype/tools/loader/widgets.py | 8 +- openpype/tools/sceneinventory/model.py | 10 +-- openpype/tools/sceneinventory/view.py | 6 +- 5 files changed, 108 insertions(+), 27 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 34257cf3dc..e8e3a81d5d 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -12,8 +12,8 @@ from .entities import ( get_subsets, get_subset_families, + get_version_by_id, get_version_by_name, - get_version, get_versions, get_last_versions, get_last_version_for_subset, @@ -45,8 +45,8 @@ __all__ = ( "get_subsets", "get_subset_families", + "get_version_by_id", "get_version_by_name", - "get_version", "get_versions", "get_last_versions", "get_last_version_for_subset", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 07f6bb2e65..1a45be6e93 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -224,7 +224,7 @@ def get_asset_ids_with_subsets(project_name, asset_ids=None): def get_subset_by_id(project_name, subset_id, fields=None): - """Single subset document by it's id. + """Single subset entity data by it's id. Args: project_name (str): Name of project where to look for queried entities. @@ -247,7 +247,7 @@ def get_subset_by_id(project_name, subset_id, fields=None): def get_subset_by_name(project_name, subset_name, asset_id, fields=None): - """Single subset document by subset name and it's version id. + """Single subset entity data by it's name and it's version id. Args: project_name (str): Name of project where to look for queried entities. @@ -279,12 +279,31 @@ def get_subset_by_name(project_name, subset_name, asset_id, fields=None): def get_subsets( project_name, - asset_ids=None, subset_ids=None, subset_names=None, + asset_ids=None, archived=False, fields=None ): + """Subset entities data from one project filtered by entered filters. + + Filters are additive (all conditions must pass to return subset). + + Args: + project_name (str): Name of project where to look for queried entities. + subset_ids (list[str, ObjectId]): Subset ids that should be queried. + Filter ignored if 'None' is passed. + subset_names (list[str]): Subset names that should be queried. + Filter ignored if 'None' is passed. + asset_ids (list[str, ObjectId]): Asset ids under which should look for + the subsets. Filter ignored if 'None' is passed. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Iterable cursor yielding all matching subsets. + """ + subset_types = ["subset"] if archived: subset_types.append("archived_subset") @@ -316,6 +335,17 @@ def get_subsets( def get_subset_families(project_name, subset_ids=None): + """Set of main families of subsets. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_ids (list[str, ObjectId]): Subset ids that should be queried. + All subsets from project are used if 'None' is passed. + + Returns: + set[str]: Main families of matching subsets. + """ + subset_filter = { "type": "subset" } @@ -340,23 +370,56 @@ def get_subset_families(project_name, subset_ids=None): return set() -def get_version_by_name(project_name, subset_id, version, fields=None): - conn = _get_project_connection(project_name) +def get_version_by_id(project_name, version_id, fields=None): + """Single version entity data by it's id. + + Args: + project_name (str): Name of project where to look for queried entities. + version_id (ObjectId): Id of version which should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If version with specified filters was not found. + Dict: Version document which can be reduced to specified 'fields'. + """ + + version_id = _convert_id(version_id) + if not version_id: + return None + query_filter = { - "type": "version", - "parent": _convert_id(subset_id), - "name": version + "type": {"$in": ["version", "hero_version"]}, + "_id": version_id } + conn = _get_project_connection(project_name) return conn.find_one(query_filter, _prepare_fields(fields)) -def get_version(project_name, version_id, fields=None): - if not version_id: +def get_version_by_name(project_name, version, subset_id, fields=None): + """Single version entity data by it's name and subset id. + + Args: + project_name (str): Name of project where to look for queried entities. + version (int): name of version entity (it's version). + subset_id (ObjectId): Id of version which should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If version with specified filters was not found. + Dict: Version document which can be reduced to specified 'fields'. + """ + + subset_id = _convert_id(subset_id) + if not subset_id: return None + conn = _get_project_connection(project_name) query_filter = { - "type": {"$in": ["version", "hero_version"]}, - "_id": _convert_id(version_id) + "type": "version", + "parent": subset_id, + "name": version } return conn.find_one(query_filter, _prepare_fields(fields)) @@ -402,11 +465,29 @@ def _get_versions( def get_versions( project_name, - subset_ids=None, version_ids=None, + subset_ids=None, hero=False, fields=None ): + """Version entities data from one project filtered by entered filters. + + Filters are additive (all conditions must pass to return subset). + + Args: + project_name (str): Name of project where to look for queried entities. + version_ids (list[str, ObjectId]): Version ids that will be queried. + Filter ignored if 'None' is passed. + subset_ids (list[str]): Subset ids that will be queried. + Filter ignored if 'None' is passed. + hero (bool): Look also for hero versions. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Iterable cursor yielding all matching versions. + """ + return _get_versions( project_name, subset_ids, diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 4a9a911f93..921708922e 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -11,7 +11,7 @@ from openpype.client import ( get_subset_families, get_subset_by_id, get_subsets, - get_version, + get_version_by_id, get_versions, get_representations, get_thumbnail_id_from_source, @@ -676,12 +676,12 @@ class VersionTextEdit(QtWidgets.QTextEdit): project_name = self.dbcon.active_project() if not version_doc: - version_doc = get_version(project_name, version_id=version_id) + version_doc = get_version_by_id(project_name, version_id) assert version_doc, "Not a valid version id" if version_doc["type"] == "hero_version": - _version_doc = get_version( - project_name, version_id=version_doc["version_id"] + _version_doc = get_version_by_id( + project_name, version_doc["version_id"] ) version_doc["data"] = _version_doc["data"] version_doc["name"] = HeroVersionType( diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index b36f7f4cea..a5d856fe72 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -9,7 +9,7 @@ import qtawesome from openpype.client import ( get_asset_by_id, get_subset_by_id, - get_version, + get_version_by_id, get_last_version_for_subset, get_representation, ) @@ -321,8 +321,8 @@ class InventoryModel(TreeModel): not_found_ids.append(repre_id) continue - version = get_version( - project_name, version_id=representation["parent"] + version = get_version_by_id( + project_name, representation["parent"] ) if not version: not_found["version"].append(group_items) @@ -330,8 +330,8 @@ class InventoryModel(TreeModel): continue elif version["type"] == "hero_version": - _version = get_version( - project_name, version_id=version["version_id"] + _version = get_version_by_id( + project_name, version["version_id"] ) version["name"] = HeroVersionType(_version["name"]) version["data"] = _version["data"] diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 04e0f67a15..d1ff91535f 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -7,7 +7,7 @@ import qtawesome from bson.objectid import ObjectId from openpype.client import ( - get_version, + get_version_by_id, get_versions, get_hero_versions, get_representation, @@ -663,9 +663,9 @@ class SceneInventoryView(QtWidgets.QTreeView): fields=["parent"] ) - repre_version_doc = get_version( + repre_version_doc = get_version_by_id( project_name, - version_id=repre_doc["parent"], + repre_doc["parent"], fields=["parent"] ) From 0f95a66359c693382af236e4d695764ecbfb7f14 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 17:43:44 +0200 Subject: [PATCH 0491/1227] renamed get_version_links to get_output_link_versions --- openpype/client/__init__.py | 4 ++-- openpype/client/entities.py | 26 ++++++++++++++++++++++++-- openpype/tools/assetlinks/widgets.py | 4 ++-- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index e8e3a81d5d..7230a7c153 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -19,7 +19,7 @@ from .entities import ( get_last_version_for_subset, get_hero_version, get_hero_versions, - get_version_links, + get_output_link_versions, get_representation, get_representation_by_name, @@ -52,7 +52,7 @@ __all__ = ( "get_last_version_for_subset", "get_hero_version", "get_hero_versions", - "get_version_links", + "get_output_link_versions", "get_representation", "get_representation_by_name", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 1a45be6e93..0eb0367452 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -544,12 +544,34 @@ def get_hero_versions( ) -def get_version_links(project_name, version_id, fields=None): +def get_output_link_versions(project_name, version_id, fields=None): + """Versions where passed version was used as input. + + Question: + Not 100% sure about the usage of the function so the name and docstring + maybe does not match what it does? + + Args: + project_name (str): Name of project where to look for queried entities. + version_id (str|ObjectId): Version id which can be used as input link + for other versions. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor|list: Iterable cursor yielding versions that are used as input + links for passed version. + """ + + version_id = _convert_id(version_id) + if not version_id: + return [] + conn = _get_project_connection(project_name) # Does make sense to look for hero versions? query_filter = { "type": "version", - "data.inputLinks.input": _convert_id(version_id) + "data.inputLinks.input": version_id } return conn.find(query_filter, _prepare_fields(fields)) diff --git a/openpype/tools/assetlinks/widgets.py b/openpype/tools/assetlinks/widgets.py index 5ce2a835ef..3078585ed1 100644 --- a/openpype/tools/assetlinks/widgets.py +++ b/openpype/tools/assetlinks/widgets.py @@ -3,7 +3,7 @@ from openpype.client import ( get_versions, get_subsets, get_assets, - get_version_links, + get_output_link_versions, ) from Qt import QtWidgets @@ -112,7 +112,7 @@ class SimpleLinkView(QtWidgets.QWidget): )) def _fill_outputs(self, version_doc): - version_docs = list(get_version_links( + version_docs = list(get_output_link_versions( self.project_name, version_doc["_id"], fields=["name", "parent"] From 675da63f8ccb862797c298ba9acd6a57b7f7e1e6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:04:04 +0200 Subject: [PATCH 0492/1227] split 'get_hero_version' to 'get_hero_version_by_id' and 'get_hero_version_by_subset_id' --- openpype/client/__init__.py | 10 +++-- openpype/client/entities.py | 79 +++++++++++++++++++++++++++++-------- 2 files changed, 68 insertions(+), 21 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 7230a7c153..4f8b948c93 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -15,10 +15,11 @@ from .entities import ( get_version_by_id, get_version_by_name, get_versions, + get_hero_version_by_id, + get_hero_version_by_subset_id, + get_hero_versions, get_last_versions, get_last_version_for_subset, - get_hero_version, - get_hero_versions, get_output_link_versions, get_representation, @@ -48,10 +49,11 @@ __all__ = ( "get_version_by_id", "get_version_by_name", "get_versions", + "get_hero_version_by_id", + "get_hero_version_by_subset_id", + "get_hero_versions", "get_last_versions", "get_last_version_for_subset", - "get_hero_version", - "get_hero_versions", "get_output_link_versions", "get_representation", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 0eb0367452..bf033e7c81 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -498,27 +498,57 @@ def get_versions( ) -def get_hero_version( - project_name, - subset_id=None, - version_id=None, - fields=None -): - if not subset_id and not version_id: +def get_hero_version_by_subset_id(project_name, subset_id, fields=None): + """Hero version by subset id. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_id (str|ObjectId): Subset id under which is hero version. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If hero version for passed subset id does not exists. + Dict: Hero version entity data. + """ + + subset_id = _convert_id(subset_id) + if not subset_id: return None - subset_ids = None - if subset_id is not None: - subset_ids = [subset_id] - - version_ids = None - if version_id is not None: - version_ids = [version_id] - versions = list(_get_versions( project_name, - subset_ids=subset_ids, - version_ids=version_ids, + subset_ids=[subset_id], + standard=False, + hero=True, + fields=fields + )) + if versions: + return versions[0] + return None + + +def get_hero_version_by_id(project_name, version_id, fields=None): + """Hero version by it's id. + + Args: + project_name (str): Name of project where to look for queried entities. + version_id (str|ObjectId): Hero version id. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If hero version with passed id was not found. + Dict: Hero version entity data. + """ + + version_id = _convert_id(version_id) + if not version_id: + return None + + versions = list(_get_versions( + project_name, + version_ids=[version_id], standard=False, hero=True, fields=fields @@ -534,6 +564,21 @@ def get_hero_versions( version_ids=None, fields=None ): + """Hero version entities data from one project filtered by entered filters. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_ids (list[str|ObjectId]): Subset ids for which should look for + hero versions. Filter ignored if 'None' is passed. + version_ids (list[str|ObjectId]): Hero version ids. Filter ignored if + 'None' is passed. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor|list: Iterable yielding hero versions matching passed filters. + """ + return _get_versions( project_name, subset_ids, From ba6ef6d2ae035361d0a7282983555990c322cc7c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:23:44 +0200 Subject: [PATCH 0493/1227] split 'get_last_version_for_subset' into 'get_last_version_by_subset_id' and 'get_last_version_by_subset_name' --- openpype/client/__init__.py | 6 +- openpype/client/entities.py | 81 ++++++++++++------- openpype/tools/mayalookassigner/app.py | 6 +- .../tools/mayalookassigner/vray_proxies.py | 4 +- openpype/tools/sceneinventory/model.py | 6 +- .../widgets/widget_family.py | 6 +- 6 files changed, 65 insertions(+), 44 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 4f8b948c93..2ef32d6a83 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -19,7 +19,8 @@ from .entities import ( get_hero_version_by_subset_id, get_hero_versions, get_last_versions, - get_last_version_for_subset, + get_last_version_by_subset_id, + get_last_version_by_subset_name, get_output_link_versions, get_representation, @@ -53,7 +54,8 @@ __all__ = ( "get_hero_version_by_subset_id", "get_hero_versions", "get_last_versions", - "get_last_version_for_subset", + "get_last_version_by_subset_id", + "get_last_version_by_subset_name", "get_output_link_versions", "get_representation", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index bf033e7c81..232d9aebcc 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -228,7 +228,7 @@ def get_subset_by_id(project_name, subset_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. - subset_id (ObjectId): Id of subset which should be found. + subset_id (str|ObjectId): Id of subset which should be found. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -375,7 +375,7 @@ def get_version_by_id(project_name, version_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. - version_id (ObjectId): Id of version which should be found. + version_id (str|ObjectId): Id of version which should be found. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -402,7 +402,7 @@ def get_version_by_name(project_name, version, subset_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. version (int): name of version entity (it's version). - subset_id (ObjectId): Id of version which should be found. + subset_id (str|ObjectId): Id of version which should be found. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -622,13 +622,13 @@ def get_output_link_versions(project_name, version_id, fields=None): def get_last_versions(project_name, subset_ids, fields=None): - """Retrieve all latest versions for entered subset_ids. + """Latest versions for entered subset_ids. Args: subset_ids (list): List of subset ids. Returns: - dict: Key is subset id and value is last version name. + dict[ObjectId, int]: Key is subset id and value is last version name. """ subset_ids = _convert_ids(subset_ids) @@ -665,35 +665,60 @@ def get_last_versions(project_name, subset_ids, fields=None): } -def get_last_version_for_subset( - project_name, subset_id=None, subset_name=None, asset_id=None, fields=None -): - subset_doc = get_subset( - project_name, - subset_id=subset_id, - subset_name=subset_name, - asset_id=asset_id, - fields=["_id"] - ) - if not subset_doc: +def get_last_version_by_subset_id(project_name, subset_id, fields=None): + """Last version for passed subset id. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_id (str|ObjectId): Id of version which should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If version with specified filters was not found. + Dict: Version document which can be reduced to specified 'fields'. + """ + + subset_id = _convert_id(subset_id) + if not subset_id: return None - subset_id = subset_doc["_id"] + last_versions = get_last_versions( project_name, subset_ids=[subset_id], fields=fields ) return last_versions.get(subset_id) -def get_representation( - project_name, - representation_id=None, - representation_name=None, - version_id=None, - fields=None +def get_last_version_by_subset_name( + project_name, subset_name, asset_id, fields=None ): + """Last version for passed subset name under asset id. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_name (str): Name of subset. + asset_id (str|ObjectId): Asset id which is parnt of passed subset name. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If version with specified filters was not found. + Dict: Version document which can be reduced to specified 'fields'. + """ + + subset_doc = get_subset_by_name( + project_name, subset_name, asset_id, fields=["_id"] + ) + if not subset_doc: + return None + return get_last_version_by_subset_id( + project_name, subset_doc["_id"], fields=fields + ) + + +def get_representation(project_name, representation_id, fields=None): if not representation_id: - if not representation_name or not version_id: - return None + return None repre_types = ["representation", "archived_representations"] query_filter = { @@ -702,12 +727,6 @@ def get_representation( if representation_id is not None: query_filter["_id"] = _convert_id(representation_id) - if representation_name is not None: - query_filter["name"] = representation_name - - if version_id is not None: - query_filter["parent"] = version_id - conn = _get_project_connection(project_name) return conn.find_one(query_filter, _prepare_fields(fields)) diff --git a/openpype/tools/mayalookassigner/app.py b/openpype/tools/mayalookassigner/app.py index 427edf8245..5665acea42 100644 --- a/openpype/tools/mayalookassigner/app.py +++ b/openpype/tools/mayalookassigner/app.py @@ -4,7 +4,7 @@ import logging from Qt import QtWidgets, QtCore -from openpype.client import get_last_version_for_subset +from openpype.client import get_last_version_by_subset_id from openpype import style from openpype.pipeline import legacy_io from openpype.tools.utils.lib import qt_app_context @@ -230,8 +230,8 @@ class MayaLookAssignerWindow(QtWidgets.QWidget): continue # Get the latest version of this asset's look subset - version = get_last_version_for_subset( - project_name, subset_id=assign_look["_id"], fields=["_id"] + version = get_last_version_by_subset_id( + project_name, assign_look["_id"], fields=["_id"] ) subset_name = assign_look["name"] diff --git a/openpype/tools/mayalookassigner/vray_proxies.py b/openpype/tools/mayalookassigner/vray_proxies.py index b2ba21f944..889396e555 100644 --- a/openpype/tools/mayalookassigner/vray_proxies.py +++ b/openpype/tools/mayalookassigner/vray_proxies.py @@ -12,7 +12,7 @@ from maya import cmds from openpype.client import ( get_representation_by_name, - get_last_version_for_subset, + get_last_version_by_subset_name, ) from openpype.pipeline import ( legacy_io, @@ -251,7 +251,7 @@ def vrayproxy_assign_look(vrayproxy, subset="lookDefault"): for asset_id, node_ids in node_ids_by_asset_id.items(): # Get latest look version - version = get_last_version_for_subset( + version = get_last_version_by_subset_name( project_name, subset_name=subset, asset_id=asset_id, diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index a5d856fe72..8c49933e80 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -10,7 +10,7 @@ from openpype.client import ( get_asset_by_id, get_subset_by_id, get_version_by_id, - get_last_version_for_subset, + get_last_version_by_subset_id, get_representation, ) from openpype.pipeline import ( @@ -403,8 +403,8 @@ class InventoryModel(TreeModel): # Store the highest available version so the model can know # whether current version is currently up-to-date. - highest_version = get_last_version_for_subset( - project_name, subset_id=version["parent"] + highest_version = get_last_version_by_subset_id( + project_name, version["parent"] ) # create the group header diff --git a/openpype/tools/standalonepublish/widgets/widget_family.py b/openpype/tools/standalonepublish/widgets/widget_family.py index 2f00cfe7bb..1736be84ab 100644 --- a/openpype/tools/standalonepublish/widgets/widget_family.py +++ b/openpype/tools/standalonepublish/widgets/widget_family.py @@ -6,7 +6,7 @@ from openpype.client import ( get_asset_by_name, get_subset_by_name, get_subsets, - get_last_version_for_subset, + get_last_version_by_subset_id, ) from openpype.api import get_project_settings from openpype.pipeline import LegacyCreator @@ -323,9 +323,9 @@ class FamilyWidget(QtWidgets.QWidget): ) if subset_doc: - last_version = get_last_version_for_subset( + last_version = get_last_version_by_subset_id( project_name, - subset_id=subset_doc["_id"], + subset_doc["_id"], fields=["name"] ) if last_version: From cde47eefa8201fb6fc7b922427436ec2f3bd0202 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:27:54 +0200 Subject: [PATCH 0494/1227] renamed 'get_representation' to 'get_representation_by_id' --- openpype/client/__init__.py | 4 +-- openpype/client/entities.py | 35 +++++++++++++++++++++++--- openpype/tools/sceneinventory/model.py | 6 ++--- openpype/tools/sceneinventory/view.py | 6 ++--- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 2ef32d6a83..2fa7730839 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -23,7 +23,7 @@ from .entities import ( get_last_version_by_subset_name, get_output_link_versions, - get_representation, + get_representation_by_id, get_representation_by_name, get_representations, get_representation_parents, @@ -58,7 +58,7 @@ __all__ = ( "get_last_version_by_subset_name", "get_output_link_versions", - "get_representation", + "get_representation_by_id", "get_representation_by_name", "get_representations", "get_representation_parents", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 232d9aebcc..7abb25e380 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -716,7 +716,21 @@ def get_last_version_by_subset_name( ) -def get_representation(project_name, representation_id, fields=None): +def get_representation_by_id(project_name, representation_id, fields=None): + """Representation entity data by it's id. + + Args: + project_name (str): Name of project where to look for queried entities. + representation_id (str|ObjectId): Representation id. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If representation with specified filters was not found. + Dict: Representation entity data which can be reduced + to specified 'fields'. + """ + if not representation_id: return None @@ -735,17 +749,32 @@ def get_representation(project_name, representation_id, fields=None): def get_representation_by_name( project_name, representation_name, version_id, fields=None ): + """Representation entity data by it's name and it's version id. + + Args: + project_name (str): Name of project where to look for queried entities. + representation_name (str): Representation name. + version_id (str|ObjectId): Id of parent version entity. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If representation with specified filters was not found. + Dict: Representation entity data which can be reduced + to specified 'fields'. + """ + + version_id = _convert_id(version_id) if not version_id or not representation_name: return None repre_types = ["representation", "archived_representations"] query_filter = { "type": {"$in": repre_types}, "name": representation_name, - "parent": _convert_id(version_id) + "parent": version_id } conn = _get_project_connection(project_name) - return conn.find_one(query_filter, _prepare_fields(fields)) diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 8c49933e80..117bdfcba1 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -11,7 +11,7 @@ from openpype.client import ( get_subset_by_id, get_version_by_id, get_last_version_by_subset_id, - get_representation, + get_representation_by_id, ) from openpype.pipeline import ( legacy_io, @@ -313,8 +313,8 @@ class InventoryModel(TreeModel): for repre_id, group_dict in sorted(grouped.items()): group_items = group_dict["items"] # Get parenthood per group - representation = get_representation( - project_name, representation_id=repre_id + representation = get_representation_by_id( + project_name, repre_id ) if not representation: not_found["representation"].append(group_items) diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index d1ff91535f..8164c48a5d 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -10,7 +10,7 @@ from openpype.client import ( get_version_by_id, get_versions, get_hero_versions, - get_representation, + get_representation_by_id, get_representations, ) from openpype import style @@ -657,9 +657,9 @@ class SceneInventoryView(QtWidgets.QTreeView): project_name = legacy_io.active_project() # Get available versions for active representation - repre_doc = get_representation( + repre_doc = get_representation_by_id( project_name, - representation_id=active["representation"], + active["representation"], fields=["parent"] ) From aa91db6883485f33ba7bd2c74e2250ec5ebd906d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:36:54 +0200 Subject: [PATCH 0495/1227] added docstring for get_representations --- openpype/client/entities.py | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 7abb25e380..6c0170ea40 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -147,7 +147,7 @@ def get_assets( Args: project_name (str): Name of project where to look for queried entities. - asset_ids (list[str, ObjectId]): Asset ids that should be found. + asset_ids (list[str|ObjectId]): Asset ids that should be found. asset_names (list[str]): Name assets that should be found. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -291,11 +291,11 @@ def get_subsets( Args: project_name (str): Name of project where to look for queried entities. - subset_ids (list[str, ObjectId]): Subset ids that should be queried. + subset_ids (list[str|ObjectId]): Subset ids that should be queried. Filter ignored if 'None' is passed. subset_names (list[str]): Subset names that should be queried. Filter ignored if 'None' is passed. - asset_ids (list[str, ObjectId]): Asset ids under which should look for + asset_ids (list[str|ObjectId]): Asset ids under which should look for the subsets. Filter ignored if 'None' is passed. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -339,7 +339,7 @@ def get_subset_families(project_name, subset_ids=None): Args: project_name (str): Name of project where to look for queried entities. - subset_ids (list[str, ObjectId]): Subset ids that should be queried. + subset_ids (list[str|ObjectId]): Subset ids that should be queried. All subsets from project are used if 'None' is passed. Returns: @@ -476,7 +476,7 @@ def get_versions( Args: project_name (str): Name of project where to look for queried entities. - version_ids (list[str, ObjectId]): Version ids that will be queried. + version_ids (list[str|ObjectId]): Version ids that will be queried. Filter ignored if 'None' is passed. subset_ids (list[str]): Subset ids that will be queried. Filter ignored if 'None' is passed. @@ -789,6 +789,32 @@ def get_representations( archived=False, fields=None ): + """Representaion entities data from one project filtered by filters. + + Filters are additive (all conditions must pass to return subset). + + Args: + project_name (str): Name of project where to look for queried entities. + representation_ids (list[str|ObjectId]): Representation ids used as + filter. Filter ignored if 'None' is passed. + representation_names (list[str]): Representations names used as filter. + Filter ignored if 'None' is passed. + version_ids (list[str]): Subset ids used as parent filter. Filter + ignored if 'None' is passed. + extensions (list[str]): Filter by extension of main representation + file (without dot). + names_by_version_ids (dict[ObjectId, list[str]]): Complex filtering + using version ids and list of names under the version. + check_site_name (bool): Filter only representation that have existing + site name. + archived (bool): Output will also contain archived representations. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Iterable cursor yielding all matching representations. + """ + repre_types = ["representation"] if archived: repre_types.append("archived_representations") From 7d13ba2706d0cdc11f6b6358bafc432c87522d04 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:37:09 +0200 Subject: [PATCH 0496/1227] added missing functions to legacy_io and mongodb --- openpype/pipeline/legacy_io.py | 9 +++++++++ openpype/pipeline/mongodb.py | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/openpype/pipeline/legacy_io.py b/openpype/pipeline/legacy_io.py index c8e7e79600..9359e3057b 100644 --- a/openpype/pipeline/legacy_io.py +++ b/openpype/pipeline/legacy_io.py @@ -144,3 +144,12 @@ def parenthood(*args, **kwargs): @requires_install def bulk_write(*args, **kwargs): return _connection_object.bulk_write(*args, **kwargs) + + +@requires_install +def active_project(*args, **kwargs): + return _connection_object.active_project(*args, **kwargs) + + +def current_project(*args, **kwargs): + return Session.get("AVALON_PROJECT") diff --git a/openpype/pipeline/mongodb.py b/openpype/pipeline/mongodb.py index 565e26b966..dab5bb9e13 100644 --- a/openpype/pipeline/mongodb.py +++ b/openpype/pipeline/mongodb.py @@ -199,6 +199,10 @@ class AvalonMongoDB: """Return the name of the active project""" return self.Session["AVALON_PROJECT"] + def current_project(self): + """Currently set project in Session without triggering installation.""" + return self.Session.get("AVALON_PROJECT") + @requires_install @auto_reconnect def projects(self, projection=None, only_active=True): From 0ce3045ad0bf399389cdbc7605762ecfe7b638f2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:47:42 +0200 Subject: [PATCH 0497/1227] added missing docstrings --- openpype/client/__init__.py | 2 + openpype/client/entities.py | 88 ++++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 2fa7730839..16b1dcf321 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -30,6 +30,7 @@ from .entities import ( get_representations_parents, get_thumbnail, + get_thumbnails, get_thumbnail_id_from_source, ) @@ -65,5 +66,6 @@ __all__ = ( "get_representations_parents", "get_thumbnail", + "get_thumbnails", "get_thumbnail_id_from_source", ) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 6c0170ea40..c50ae32d7f 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -866,6 +866,20 @@ def get_representations( def get_representations_parents(project_name, representations): + """Prepare parents of representation entities. + + Each item of returned dictionary contains version, subset, asset + and project in that order. + + Args: + project_name (str): Name of project where to look for queried entities. + representations (list[dict]): Representation entities with at least + '_id' and 'parent' keys. + + Returns: + dict[ObjectId, tuple]: Parents by representation id. + """ + repres_by_version_id = collections.defaultdict(list) versions_by_version_id = {} versions_by_subset_id = collections.defaultdict(list) @@ -921,15 +935,43 @@ def get_representations_parents(project_name, representations): def get_representation_parents(project_name, representation): + """Prepare parents of representation entity. + + Each item of returned dictionary contains version, subset, asset + and project in that order. + + Args: + project_name (str): Name of project where to look for queried entities. + representation (dict): Representation entities with at least + '_id' and 'parent' keys. + + Returns: + dict[ObjectId, tuple]: Parents by representation id. + """ + if not representation: return None repre_id = representation["_id"] - parents_by_repre_id = get_representations(project_name, [representation]) + parents_by_repre_id = get_representations_parents( + project_name, [representation] + ) return parents_by_repre_id.get(repre_id) def get_thumbnail_id_from_source(project_name, src_type, src_id): + """Receive thumbnail id from source entity. + + Args: + project_name (str): Name of project where to look for queried entities. + src_type (str): Type of source entity ('asset', 'version'). + src_id (str|objectId): Id of source entity. + + Returns: + ObjectId: Thumbnail id assigned to entity. + None: If Source entity does not have any thumbnail id assigned. + """ + if not src_type or not src_id: return None @@ -942,12 +984,54 @@ def get_thumbnail_id_from_source(project_name, src_type, src_id): return None +def get_thumbnails(project_name, thumbnail_ids, fields=None): + """Receive thumbnails entity data. + + Thumbnail entity can be used to receive binary content of thumbnail based + on it's content and ThumbnailResolvers. + + Args: + project_name (str): Name of project where to look for queried entities. + thumbnail_ids (list[str|ObjectId]): Ids of thumbnail entities. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + cursor: Cursor of queried documents. + """ + + if thumbnail_ids: + thumbnail_ids = _convert_ids(thumbnail_ids) + + if not thumbnail_ids: + return [] + query_filter = { + "type": "thumbnail", + "_id": {"$in": thumbnail_ids} + } + conn = _get_project_connection(project_name) + return conn.find(query_filter, _prepare_fields(fields)) + + def get_thumbnail(project_name, thumbnail_id, fields=None): + """Receive thumbnail entity data. + + Args: + project_name (str): Name of project where to look for queried entities. + thumbnail_id (str|ObjectId): Id of thumbnail entity. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If thumbnail with specified id was not found. + Dict: Thumbnail entity data which can be reduced to specified 'fields'. + """ + if not thumbnail_id: return None query_filter = {"type": "thumbnail", "_id": _convert_id(thumbnail_id)} conn = _get_project_connection(project_name) - return conn.find(query_filter, _prepare_fields(fields)) + return conn.find_one(query_filter, _prepare_fields(fields)) """ From 125af17c2244f902447048c803d649f657f16f38 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:47:47 +0200 Subject: [PATCH 0498/1227] updated usages --- openpype/client/entities.py | 57 +++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index c50ae32d7f..2459ea3e92 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1045,12 +1045,12 @@ openpype/tools/assetlinks/widgets.py - get_versions - get_subsets - get_assets - - get_version_links + - get_output_link_versions openpype/tools/creator/window.py - CreatorWindow Query: - - get_asset + - get_asset_by_name - get_subsets openpype/tools/launcher/models.py @@ -1093,12 +1093,14 @@ openpype/tools/loader/widgets.py - get_subset_families - VersionTextEdit Query: - - get_subset - - get_version + - get_subset_by_id + - get_version_by_id - SubsetWidget Query: - get_subsets - get_representations + Update: + - Subset groups (combination of asset id and subset names) - RepresentationWidget Query: - get_subsets @@ -1112,12 +1114,12 @@ openpype/tools/loader/widgets.py openpype/tools/mayalookassigner/app.py - MayaLookAssignerWindow Query: - - get_last_version_for_subset + - get_last_version_by_subset_id openpype/tools/mayalookassigner/commands.py - create_items_from_nodes Query: - - get_asset + - get_asset_by_id openpype/tools/mayalookassigner/vray_proxies.py - get_look_relationships @@ -1128,7 +1130,7 @@ openpype/tools/mayalookassigner/vray_proxies.py - get_representation_by_name - vrayproxy_assign_look Query: - - get_last_version_for_subset + - get_last_version_by_subset_name openpype/tools/project_manager/project_manager/model.py - HierarchyModel @@ -1150,7 +1152,7 @@ openpype/tools/project_manager/project_manager/widgets.py openpype/tools/publisher/widgets/create_dialog.py - CreateDialog Query: - - get_asset + - get_asset_by_name - get_subsets openpype/tools/publisher/control.py @@ -1161,18 +1163,18 @@ openpype/tools/publisher/control.py openpype/tools/sceneinventory/model.py - InventoryModel Query: - - get_asset - - get_subset - - get_version - - get_last_version_for_subset + - get_asset_by_id + - get_subset_by_id + - get_version_by_id + - get_last_version_by_subset_id - get_representation openpype/tools/sceneinventory/switch_dialog.py - SwitchAssetDialog Query: - - get_asset + - get_asset_by_name - get_assets - - get_subset + - get_subset_by_name - get_subsets - get_versions - get_hero_versions @@ -1182,10 +1184,10 @@ openpype/tools/sceneinventory/switch_dialog.py openpype/tools/sceneinventory/view.py - SceneInventoryView Query: - - get_version + - get_version_by_id - get_versions - get_hero_versions - - get_representation + - get_representation_by_id - get_representations openpype/tools/standalonepublish/widgets/model_asset.py @@ -1197,31 +1199,31 @@ openpype/tools/standalonepublish/widgets/widget_asset.py - AssetWidget Query: - get_project - - get_asset + - get_asset_by_id openpype/tools/standalonepublish/widgets/widget_family.py - FamilyWidget Query: - - get_asset - - get_subset + - get_asset_by_name + - get_subset_by_name - get_subsets - - get_last_version_for_subset + - get_last_version_by_subset_id openpype/tools/standalonepublish/app.py - Window Query: - - get_asset + - get_asset_by_id openpype/tools/texture_copy/app.py - TextureCopy Query: - get_project - - get_asset + - get_asset_by_name openpype/tools/workfiles/files_widget.py - FilesWidget Query: - - get_asset + - get_asset_by_id openpype/tools/workfiles/model.py - PublishFilesModel @@ -1234,12 +1236,13 @@ openpype/tools/workfiles/save_as_dialog.py - build_workfile_data Query: - get_project - - get_asset + - get_asset_by_name openpype/tools/workfiles/window.py - Window Query: - - get_asset + - get_asset_by_id + - get_asset_by_name openpype/tools/utils/assets_widget.py - AssetModel @@ -1259,11 +1262,11 @@ openpype/tools/utils/lib.py - get_project - FamilyConfigCache Query: - - get_asset + - get_asset_by_name openpype/tools/utils/tasks_widget.py - TasksModel Query: - get_project - - get_asset + - get_asset_by_id """ From 1133dedb545efe81aaf5febc5819d7bd3ab6a3ab Mon Sep 17 00:00:00 2001 From: "felix.wang" Date: Tue, 7 Jun 2022 16:07:54 -0700 Subject: [PATCH 0499/1227] Support Maya instances in pointCache extraction. --- .../hosts/maya/plugins/publish/extract_pointcache.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index c4c8610ebb..4aaf223403 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -65,7 +65,7 @@ class ExtractAlembic(openpype.api.Extractor): "writeColorSets": writeColorSets, "writeFaceSets": writeFaceSets, "uvWrite": True, - "selection": True, + "selection": False, "worldSpace": instance.data.get("worldSpace", True) } @@ -80,12 +80,10 @@ class ExtractAlembic(openpype.api.Extractor): options["writeUVSets"] = True with suspended_refresh(): - with maintained_selection(): - cmds.select(nodes, noExpand=True) - extract_alembic(file=path, - startFrame=start, - endFrame=end, - **options) + extract_alembic(file=path, + startFrame=start, + endFrame=end, + **options) if "representations" not in instance.data: instance.data["representations"] = [] From 1819c66e7bed0b3e9af91cf1d408bd91f8adb7b2 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 8 Jun 2022 03:51:00 +0000 Subject: [PATCH 0500/1227] [Automated] Bump version --- CHANGELOG.md | 42 +++++++++++++----------------------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 15 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50cb1d423e..1d7798cb48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,28 @@ # Changelog -## [3.10.1-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.11.0-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...HEAD) +### 📖 Documentation + +- doc: adding royal render and multiverse to the web site [\#3285](https://github.com/pypeclub/OpenPype/pull/3285) + **🚀 Enhancements** - General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268) +- Unreal: add support for skeletalMesh and staticMesh to loaders [\#3267](https://github.com/pypeclub/OpenPype/pull/3267) - Maya: reference loaders could store placeholder in referenced url [\#3264](https://github.com/pypeclub/OpenPype/pull/3264) - TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250) - Nuke: Change default icon path in settings [\#3247](https://github.com/pypeclub/OpenPype/pull/3247) **🐛 Bug fixes** +- Global: extract review slate issues [\#3286](https://github.com/pypeclub/OpenPype/pull/3286) - Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) +- Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) +- General: Fix Oiio tool path resolving [\#3278](https://github.com/pypeclub/OpenPype/pull/3278) +- Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) - Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) - Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) - Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) @@ -21,13 +30,14 @@ - Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) - Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) - Flame: debugging [\#3224](https://github.com/pypeclub/OpenPype/pull/3224) -- Ftrack: Push hierarchical attributes action works [\#3210](https://github.com/pypeclub/OpenPype/pull/3210) - add silent audio to slate [\#3162](https://github.com/pypeclub/OpenPype/pull/3162) **Merged pull requests:** - Maya: better handling of legacy review subsets names [\#3269](https://github.com/pypeclub/OpenPype/pull/3269) +- Deadline: publishing of animation and pointcache on a farm [\#3225](https://github.com/pypeclub/OpenPype/pull/3225) - Nuke: add pointcache and animation to loader [\#3186](https://github.com/pypeclub/OpenPype/pull/3186) +- Add a gizmo menu to nuke [\#3172](https://github.com/pypeclub/OpenPype/pull/3172) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -37,7 +47,6 @@ - General: OpenPype modules publish plugins are registered in host [\#3180](https://github.com/pypeclub/OpenPype/pull/3180) - General: Creator plugins from addons can be registered [\#3179](https://github.com/pypeclub/OpenPype/pull/3179) -- Ftrack: Single image reviewable [\#3157](https://github.com/pypeclub/OpenPype/pull/3157) **🚀 Enhancements** @@ -50,10 +59,6 @@ - Maya: added clean\_import option to Import loader [\#3181](https://github.com/pypeclub/OpenPype/pull/3181) - Add the scripts menu definition to nuke [\#3168](https://github.com/pypeclub/OpenPype/pull/3168) - Maya: add maya 2023 to default applications [\#3167](https://github.com/pypeclub/OpenPype/pull/3167) -- Compressed bgeo publishing in SAP and Houdini loader [\#3153](https://github.com/pypeclub/OpenPype/pull/3153) -- General: Add 'dataclasses' to required python modules [\#3149](https://github.com/pypeclub/OpenPype/pull/3149) -- Hooks: Tweak logging grammar [\#3147](https://github.com/pypeclub/OpenPype/pull/3147) -- Nuke: settings for reformat node in CreateWriteRender node [\#3143](https://github.com/pypeclub/OpenPype/pull/3143) **🐛 Bug fixes** @@ -66,6 +71,7 @@ - Hiero: debugging frame range and other 3.10 [\#3222](https://github.com/pypeclub/OpenPype/pull/3222) - Project Manager: Fix persistent editors on project change [\#3218](https://github.com/pypeclub/OpenPype/pull/3218) - Deadline: instance data overwrite fix [\#3214](https://github.com/pypeclub/OpenPype/pull/3214) +- Ftrack: Push hierarchical attributes action works [\#3210](https://github.com/pypeclub/OpenPype/pull/3210) - Standalone Publisher: Always create new representation for thumbnail [\#3203](https://github.com/pypeclub/OpenPype/pull/3203) - Photoshop: skip collector when automatic testing [\#3202](https://github.com/pypeclub/OpenPype/pull/3202) - Nuke: render/workfile version sync doesn't work on farm [\#3185](https://github.com/pypeclub/OpenPype/pull/3185) @@ -76,9 +82,6 @@ - General: Oiio conversion for ffmpeg checks for invalid characters [\#3166](https://github.com/pypeclub/OpenPype/pull/3166) - Fix for attaching render to subset [\#3164](https://github.com/pypeclub/OpenPype/pull/3164) - Harmony: fixed missing task name in render instance [\#3163](https://github.com/pypeclub/OpenPype/pull/3163) -- Ftrack: Action delete old versions formatting works [\#3152](https://github.com/pypeclub/OpenPype/pull/3152) -- Deadline: fix the output directory [\#3144](https://github.com/pypeclub/OpenPype/pull/3144) -- General: New Session schema [\#3141](https://github.com/pypeclub/OpenPype/pull/3141) **🔀 Refactored code** @@ -89,7 +92,6 @@ - Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) - Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249) - Maya: added jpg to filter for Image Plane Loader [\#3223](https://github.com/pypeclub/OpenPype/pull/3223) -- Maya: added jpg to filter for Image Plane Loader [\#3221](https://github.com/pypeclub/OpenPype/pull/3221) - Webpublisher: replace space by underscore in subset names [\#3160](https://github.com/pypeclub/OpenPype/pull/3160) ## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19) @@ -119,24 +121,6 @@ [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.6...3.9.7) -**🆕 New features** - -- Ftrack: Single image reviewable [\#3158](https://github.com/pypeclub/OpenPype/pull/3158) - -**🚀 Enhancements** - -- Deadline output dir issue to 3.9x [\#3155](https://github.com/pypeclub/OpenPype/pull/3155) -- nuke: removing redundant code from startup [\#3142](https://github.com/pypeclub/OpenPype/pull/3142) - -**🐛 Bug fixes** - -- Ftrack: Action delete old versions formatting works [\#3154](https://github.com/pypeclub/OpenPype/pull/3154) -- nuke: adding extract thumbnail settings [\#3148](https://github.com/pypeclub/OpenPype/pull/3148) - -**Merged pull requests:** - -- Webpublisher: replace space by underscore in subset names [\#3159](https://github.com/pypeclub/OpenPype/pull/3159) - ## [3.9.6](https://github.com/pypeclub/OpenPype/tree/3.9.6) (2022-05-03) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.5...3.9.6) diff --git a/openpype/version.py b/openpype/version.py index e5dfc2bb8f..4c78a6e0a1 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.10.1-nightly.3" +__version__ = "3.11.0-nightly.1" diff --git a/pyproject.toml b/pyproject.toml index 5649ff2073..362f6a62d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.10.1-nightly.3" # OpenPype +version = "3.11.0-nightly.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 24cb024dd9d27e5c68fcd6f7d9097297031d7845 Mon Sep 17 00:00:00 2001 From: Felix Wang Date: Wed, 8 Jun 2022 00:45:15 -0700 Subject: [PATCH 0501/1227] Maya: Look assigner UI improvements (#3208) Co-authored-by: felix.wang --- openpype/hosts/maya/api/lib.py | 5 ++++- openpype/tools/utils/host_tools.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 088304ab05..bce03a648b 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1737,8 +1737,11 @@ def apply_shaders(relationships, shadernodes, nodes): log.warning("No nodes found for shading engine " "'{0}'".format(id_shading_engines[0])) continue + try: + cmds.sets(filtered_nodes, forceElement=id_shading_engines[0]) + except RuntimeError as rte: + log.error("Error during shader assignment: {}".format(rte)) - cmds.sets(filtered_nodes, forceElement=id_shading_engines[0]) # endregion apply_attributes(attributes, nodes_by_id) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index d8f4570120..9dbbe25fda 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -105,6 +105,7 @@ class HostToolsHelper: loader_tool.show() loader_tool.raise_() loader_tool.activateWindow() + loader_tool.showNormal() if use_context is None: use_context = False @@ -180,6 +181,7 @@ class HostToolsHelper: # Pull window to the front. scene_inventory_tool.raise_() scene_inventory_tool.activateWindow() + scene_inventory_tool.showNormal() def get_library_loader_tool(self, parent): """Create, cache and return library loader tool window.""" @@ -200,8 +202,10 @@ class HostToolsHelper: library_loader_tool.show() library_loader_tool.raise_() library_loader_tool.activateWindow() + library_loader_tool.showNormal() library_loader_tool.refresh() + def show_publish(self, parent=None): """Try showing the most desirable publish GUI @@ -243,6 +247,11 @@ class HostToolsHelper: look_assigner_tool = self.get_look_assigner_tool(parent) look_assigner_tool.show() + # Pull window to the front. + look_assigner_tool.raise_() + look_assigner_tool.activateWindow() + look_assigner_tool.showNormal() + def get_experimental_tools_dialog(self, parent=None): """Dialog of experimental tools. @@ -270,6 +279,7 @@ class HostToolsHelper: dialog.show() dialog.raise_() dialog.activateWindow() + dialog.showNormal() def get_tool_by_name(self, tool_name, parent=None, *args, **kwargs): """Show tool by it's name. From 964504eb7706eaf2f0ac9013f31a72192c840845 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Wed, 8 Jun 2022 10:23:43 +0200 Subject: [PATCH 0502/1227] add app key to documentation --- website/docs/admin_settings_project_anatomy.md | 1 + 1 file changed, 1 insertion(+) diff --git a/website/docs/admin_settings_project_anatomy.md b/website/docs/admin_settings_project_anatomy.md index b98819cd8a..6e0b49f152 100644 --- a/website/docs/admin_settings_project_anatomy.md +++ b/website/docs/admin_settings_project_anatomy.md @@ -67,6 +67,7 @@ We have a few required anatomy templates for OpenPype to work properly, however | `ext` | File extension | | `representation` | Representation name | | `frame` | Frame number for sequence files. | +| `app` | Application Name | | `output` | | | `comment` | | From 10bf3a33e1c90104b5f9cfe709b46607a5c98807 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 8 Jun 2022 11:45:12 +0200 Subject: [PATCH 0503/1227] Nuke: no need to add Nuke host to filter It is actually adding review even not needed --- openpype/settings/defaults/project_settings/deadline.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 5c5a14bf21..a6e7b4a94a 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -83,9 +83,6 @@ "maya": [ ".*([Bb]eauty).*" ], - "nuke": [ - ".*" - ], "aftereffects": [ ".*" ], From 016baf166b1646c06cb8b1c7b40d6ccb9a5be2e1 Mon Sep 17 00:00:00 2001 From: DMO Date: Wed, 8 Jun 2022 18:45:31 +0900 Subject: [PATCH 0504/1227] Renaming `usdX` families to `mvUsdX`, that is `usd`->`mvUsd`, `usdComposition`->`mvUsdComposition`, `usdOverride`->`usdOverride`, specifically within multiverse files. I have left the `usd` family in `integrate_new.py` because that is being used in a bunch of different places (eg: houdini's host integration), and have just added `mvUsd` as a new family. --- .../hosts/maya/plugins/create/create_multiverse_usd.py | 4 ++-- .../maya/plugins/create/create_multiverse_usd_comp.py | 4 ++-- .../maya/plugins/create/create_multiverse_usd_over.py | 4 ++-- openpype/hosts/maya/plugins/load/load_multiverse_usd.py | 2 +- .../hosts/maya/plugins/publish/extract_multiverse_usd.py | 2 +- .../maya/plugins/publish/extract_multiverse_usd_comp.py | 2 +- .../maya/plugins/publish/extract_multiverse_usd_over.py | 2 +- openpype/plugins/publish/integrate_new.py | 7 ++++--- 8 files changed, 14 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py index a82a73cbdb..034714d51b 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py @@ -4,9 +4,9 @@ from openpype.hosts.maya.api import plugin, lib class CreateMultiverseUsd(plugin.Creator): """Create Multiverse USD Asset""" - name = "usdMain" + name = "mvUsdMain" label = "Multiverse USD Asset" - family = "usd" + family = "mvUsd" icon = "cubes" def __init__(self, *args, **kwargs): diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py index 9d00ad1cfa..ed466a8068 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py @@ -4,9 +4,9 @@ from openpype.hosts.maya.api import plugin, lib class CreateMultiverseUsdComp(plugin.Creator): """Create Multiverse USD Composition""" - name = "usdCompositionMain" + name = "mvUsdCompositionMain" label = "Multiverse USD Composition" - family = "usdComposition" + family = "mvUsdComposition" icon = "cubes" def __init__(self, *args, **kwargs): diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py index 9477cd7fed..06e22df295 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py @@ -4,9 +4,9 @@ from openpype.hosts.maya.api import plugin, lib class CreateMultiverseUsdOver(plugin.Creator): """Create Multiverse USD Override""" - name = "usdOverrideMain" + name = "mvUsdOverrideMain" label = "Multiverse USD Override" - family = "usdOverride" + family = "mvUsdOverride" icon = "cubes" def __init__(self, *args, **kwargs): diff --git a/openpype/hosts/maya/plugins/load/load_multiverse_usd.py b/openpype/hosts/maya/plugins/load/load_multiverse_usd.py index ed0ffc8ef1..3350dc6ac9 100644 --- a/openpype/hosts/maya/plugins/load/load_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/load/load_multiverse_usd.py @@ -16,7 +16,7 @@ from openpype.hosts.maya.api.pipeline import containerise class MultiverseUsdLoader(load.LoaderPlugin): """Read USD data in a Multiverse Compound""" - families = ["model", "usd", "usdComposition", "usdOverride", + families = ["model", "mvUsd", "mvUsdComposition", "mvUsdOverride", "pointcache", "animation"] representations = ["usd", "usda", "usdc", "usdz", "abc"] diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py index b1aaf9d9ba..3654be7b34 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py @@ -26,7 +26,7 @@ class ExtractMultiverseUsd(openpype.api.Extractor): label = "Extract Multiverse USD Asset" hosts = ["maya"] - families = ["usd"] + families = ["mvUsd"] scene_type = "usd" file_formats = ["usd", "usda", "usdz"] diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py index c85b3b6664..ad9303657f 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py @@ -25,7 +25,7 @@ class ExtractMultiverseUsdComposition(openpype.api.Extractor): label = "Extract Multiverse USD Composition" hosts = ["maya"] - families = ["usdComposition"] + families = ["mvUsdComposition"] scene_type = "usd" # Order of `fileFormat` must match create_multiverse_usd_comp.py file_formats = ["usda", "usd"] diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py index 4856f0cfdb..d44e3878b8 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py @@ -23,7 +23,7 @@ class ExtractMultiverseUsdOverride(openpype.api.Extractor): label = "Extract Multiverse USD Override" hosts = ["maya"] - families = ["usdOverride"] + families = ["mvUsdOverride"] scene_type = "usd" # Order of `fileFormat` must match create_multiverse_usd_over.py file_formats = ["usda", "usd"] diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 4712c2e6bb..59b80f5cc1 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -106,12 +106,13 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "effect", "xgen", "hda", - "mvLook", "usd", "staticMesh", "skeletalMesh", - "usdComposition", - "usdOverride", + "mvLook", + "mvUsd", + "mvUsdComposition", + "mvUsdOverride", "simpleUnrealTexture" ] exclude_families = ["clip", "render.farm"] From 082fb1d29dfe151809c8a63767a9fc941462a23e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 8 Jun 2022 12:09:13 +0200 Subject: [PATCH 0505/1227] :sparkles: settings for camera content validator --- .../publish/validate_camera_contents.py | 3 +++ .../defaults/project_settings/maya.json | 5 ++++ .../schemas/schema_maya_publish.json | 24 +++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/validate_camera_contents.py b/openpype/hosts/maya/plugins/publish/validate_camera_contents.py index 20af8d2315..38cf65f006 100644 --- a/openpype/hosts/maya/plugins/publish/validate_camera_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_camera_contents.py @@ -20,6 +20,7 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): hosts = ['maya'] label = 'Camera Contents' actions = [openpype.hosts.maya.api.action.SelectInvalidAction] + validate_shapes = True @classmethod def get_invalid(cls, instance): @@ -49,6 +50,8 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): raise RuntimeError("No cameras found in empty instance.") + if not cls.validate_shapes: + return # non-camera shapes valid_shapes = cmds.ls(shapes, type=('camera', 'locator'), long=True) shapes = set(shapes) - set(valid_shapes) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index efd22e13c8..b39beeb305 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -413,6 +413,11 @@ "optional": true, "active": true }, + "ValidateCameraContents": { + "enabled": true, + "optional": true, + "validate_shapes": true + }, "ExtractPlayblast": { "capture_preset": { "Codec": { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 9877b5ff0d..dca0e85acb 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -570,6 +570,30 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "ValidateCameraContents", + "label": "Validate Camera Content", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "validate_shapes", + "label": "Validate presence of shapes" + } + ] + }, { "type": "splitter" }, From eba5691a2db74da0ff994299d9c252f03dcea021 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 13:23:14 +0200 Subject: [PATCH 0506/1227] use width for width and height for height in maya render --- openpype/hosts/maya/plugins/publish/collect_render.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index fbd2e81279..8b911a867d 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -340,10 +340,10 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "expectedFiles": full_exp_files, "publishRenderMetadataFolder": common_publish_meta_path, "resolutionWidth": lib.get_attr_in_layer( - "defaultResolution.height", layer=layer_name + "defaultResolution.width", layer=layer_name ), "resolutionHeight": lib.get_attr_in_layer( - "defaultResolution.width", layer=layer_name + "defaultResolution.height", layer=layer_name ), "pixelAspect": lib.get_attr_in_layer( "defaultResolution.pixelAspect", layer=layer_name From a45795526698b9a668df876a01ce002a5a05024f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 13:33:26 +0200 Subject: [PATCH 0507/1227] add missing default settings for nuke gizmo --- .../defaults/project_settings/nuke.json | 54 ++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index c609a0927a..16348bec85 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -27,6 +27,34 @@ } ] }, + "gizmo": [ + { + "toolbar_menu_name": "OpenPype Gizmo", + "gizmo_source_dir": { + "windows": [], + "darwin": [], + "linux": [] + }, + "toolbar_icon_path": { + "windows": "", + "darwin": "", + "linux": "" + }, + "gizmo_definition": [ + { + "gizmo_toolbar_path": "/path/to/menu", + "sub_gizmo_list": [ + { + "sourcetype": "python", + "title": "Gizmo Note", + "command": "nuke.nodes.StickyNote(label='You can create your own toolbar menu in the Nuke GizmoMenu of OpenPype')", + "shortcut": "" + } + ] + } + ] + } + ], "create": { "CreateWriteRender": { "fpath_template": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}", @@ -290,29 +318,5 @@ } ] }, - "gizmo": [ - { - "toolbar_menu_name": "OpenPype Gizmo", - "gizmo_path": { - "windows": [], - "darwin": [], - "linux": [] - }, - "toolbar_icon_path": {}, - "gizmo_definition": [ - { - "gizmo_toolbar_path": "/path/to/menu", - "sub_gizmo_list": [ - { - "sourcetype": "python", - "title": "Gizmo Note", - "command": "nuke.nodes.StickyNote(label='You can create your own toolbar menu in the Nuke GizmoMenu of OpenPype')", - "shortcut": "" - } - ] - } - ] - } - ], "filters": {} -} +} \ No newline at end of file From 56a3dae94b39ce90e5afc701ae8efb3aa130896e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 8 Jun 2022 14:46:16 +0200 Subject: [PATCH 0508/1227] Fix - added local targets to install host By default when host is started, it should register targets to 'local', only if specific env var is set (OPENPYPE_REMOTE_PUBLISH) it should be 'remote'. This means farm is using host to collect/validate/extract and publish on behalf of artist. --- openpype/hosts/maya/api/pipeline.py | 2 +- .../publish/submit_maya_remote_publish_deadline.py | 2 +- openpype/pipeline/context_tools.py | 8 ++++++++ openpype/plugin.py | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 0261694be2..836445a970 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -66,7 +66,7 @@ def install(): log.info("Installing callbacks ... ") register_event_callback("init", on_init) - if os.environ.get("HEADLESS_PUBLISH"): + if os.environ.get("OPENPYPE_REMOTE_PUBLISH"): # Maya launched on farm, lib.IS_HEADLESS might be triggered locally too # target "farm" == rendering on farm, expects OPENPYPE_PUBLISH_DATA # target "remote" == remote execution diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py index c31052be07..4f82818d6d 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py @@ -115,7 +115,7 @@ class MayaSubmitRemotePublishDeadline(openpype.api.Integrator): environment["OPENPYPE_REMOTE_JOB"] = "1" environment["OPENPYPE_USERNAME"] = instance.context.data["user"] environment["OPENPYPE_PUBLISH_SUBSET"] = instance.data["subset"] - environment["HEADLESS_PUBLISH"] = "1" + environment["OPENPYPE_REMOTE_PUBLISH"] = "1" payload["JobInfo"].update({ "EnvironmentKeyValue%d" % index: "{key}={value}".format( diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index e849f5b0d1..c6e09cfba1 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -104,6 +104,14 @@ def install_host(host): MessageHandler.emit = modified_emit + if os.environ.get("OPENPYPE_REMOTE_PUBLISH"): + # target "farm" == rendering on farm, expects OPENPYPE_PUBLISH_DATA + # target "remote" == remote execution, installs host + print("Registering pyblish target: remote") + pyblish.api.register_target("remote") + else: + pyblish.api.register_target("local") + install_openpype_plugins() diff --git a/openpype/plugin.py b/openpype/plugin.py index f1ee626ffb..6637ad1d8b 100644 --- a/openpype/plugin.py +++ b/openpype/plugin.py @@ -23,7 +23,7 @@ class Integrator(InstancePlugin): Wraps pyblish instance plugin. Targets set to "local" which means all integrators should run on "local" publishes, by default. - "farm" targets could be used for integrators that should run on a farm. + "remote" targets could be used for integrators that should run externally. """ targets = ["local"] From 860dc3fc90ae78a044bbbf52e47560e1d7cdd7ea Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 14:57:12 +0200 Subject: [PATCH 0509/1227] use temp file instead of clipboard for node duplication --- openpype/hosts/nuke/api/lib.py | 44 +++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 3062091ba0..7329fc859b 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3,6 +3,8 @@ from pprint import pformat import re import six import platform +import time +import tempfile import contextlib from collections import OrderedDict @@ -2562,20 +2564,50 @@ class DirmapCache: return cls._sync_module +@contextlib.contextmanager +def _duplicate_node_temp(): + """Create a temp file where node is pasted during duplication. + + This is to avoid using clipboard for node duplication. + """ + + duplicate_node_temp_path = os.path.join( + tempfile.gettempdir(), + "openpype_nuke_duplicate_temp_{}".format(os.getpid()) + ) + + # This can happen only if 'duplicate_node' would be + if os.path.exists(duplicate_node_temp_path): + log.warning(( + "Temp file for node duplication already exists." + " Trying to remove {}" + ).format(duplicate_node_temp_path)) + os.remove(duplicate_node_temp_path) + + try: + # Yield the path where node can be copied + yield duplicate_node_temp_path + + finally: + # Remove the file at the end + os.remove(duplicate_node_temp_path) + + def duplicate_node(node): reset_selection() # select required node for duplication node.setSelected(True) - # copy selected to clipboard - nuke.nodeCopy("%clipboard%") + with _duplicate_node_temp() as filepath: + # copy selected to temp filepath + nuke.nodeCopy(filepath) - # reset selection - reset_selection() + # reset selection + reset_selection() - # paste node and selection is on it only - dupli_node = nuke.nodePaste("%clipboard%") + # paste node and selection is on it only + dupli_node = nuke.nodePaste(filepath) # reset selection reset_selection() From 1f8be47064249bb69ca764a70e18f81d2cb3af49 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 14:57:42 +0200 Subject: [PATCH 0510/1227] remove unused import --- openpype/hosts/nuke/api/lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 7329fc859b..4391742f43 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3,7 +3,6 @@ from pprint import pformat import re import six import platform -import time import tempfile import contextlib from collections import OrderedDict From a63299d57e8856408ec9a2eeb6fe60ebb57483e2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 8 Jun 2022 15:00:54 +0200 Subject: [PATCH 0511/1227] Removed explicit targets registering install_host registers targets implicitly for all hosts --- openpype/hosts/maya/api/pipeline.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 836445a970..d9276ddf4a 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -66,23 +66,12 @@ def install(): log.info("Installing callbacks ... ") register_event_callback("init", on_init) - if os.environ.get("OPENPYPE_REMOTE_PUBLISH"): - # Maya launched on farm, lib.IS_HEADLESS might be triggered locally too - # target "farm" == rendering on farm, expects OPENPYPE_PUBLISH_DATA - # target "remote" == remote execution - print("Registering pyblish target: remote") - pyblish.api.register_target("remote") - return - if lib.IS_HEADLESS: log.info(("Running in headless mode, skipping Maya " "save/open/new callback installation..")) return - print("Registering pyblish target: local") - pyblish.api.register_target("local") - _set_project() _register_callbacks() From 5f3395683ac38bbb01d16880a1e063b0f9f39554 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 16:02:08 +0200 Subject: [PATCH 0512/1227] added 'requests' and its dependencies into python 2 vendor --- .../python/python_2/chardet/__init__.py | 83 + .../python/python_2/chardet/big5freq.py | 386 + .../python/python_2/chardet/big5prober.py | 47 + .../python_2/chardet/chardistribution.py | 233 + .../python_2/chardet/charsetgroupprober.py | 107 + .../python/python_2/chardet/charsetprober.py | 145 + .../python/python_2/chardet/cli/__init__.py | 1 + .../python/python_2/chardet/cli/chardetect.py | 84 + .../python_2/chardet/codingstatemachine.py | 88 + .../vendor/python/python_2/chardet/compat.py | 36 + .../python/python_2/chardet/cp949prober.py | 49 + .../vendor/python/python_2/chardet/enums.py | 76 + .../python/python_2/chardet/escprober.py | 101 + .../vendor/python/python_2/chardet/escsm.py | 246 + .../python/python_2/chardet/eucjpprober.py | 92 + .../python/python_2/chardet/euckrfreq.py | 195 + .../python/python_2/chardet/euckrprober.py | 47 + .../python/python_2/chardet/euctwfreq.py | 387 + .../python/python_2/chardet/euctwprober.py | 46 + .../python/python_2/chardet/gb2312freq.py | 283 + .../python/python_2/chardet/gb2312prober.py | 46 + .../python/python_2/chardet/hebrewprober.py | 292 + .../vendor/python/python_2/chardet/jisfreq.py | 325 + .../vendor/python/python_2/chardet/jpcntx.py | 233 + .../python_2/chardet/langbulgarianmodel.py | 4650 +++++++++ .../python/python_2/chardet/langgreekmodel.py | 4398 +++++++++ .../python_2/chardet/langhebrewmodel.py | 4383 +++++++++ .../python_2/chardet/langhungarianmodel.py | 4650 +++++++++ .../python_2/chardet/langrussianmodel.py | 5718 +++++++++++ .../python/python_2/chardet/langthaimodel.py | 4383 +++++++++ .../python_2/chardet/langturkishmodel.py | 4383 +++++++++ .../python/python_2/chardet/latin1prober.py | 145 + .../python_2/chardet/mbcharsetprober.py | 91 + .../python_2/chardet/mbcsgroupprober.py | 54 + .../vendor/python/python_2/chardet/mbcssm.py | 572 ++ .../python_2/chardet/metadata/__init__.py | 0 .../python_2/chardet/metadata/languages.py | 310 + .../python_2/chardet/sbcharsetprober.py | 145 + .../python_2/chardet/sbcsgroupprober.py | 83 + .../python/python_2/chardet/sjisprober.py | 92 + .../python_2/chardet/universaldetector.py | 286 + .../python/python_2/chardet/utf8prober.py | 82 + .../vendor/python/python_2/chardet/version.py | 9 + .../vendor/python/python_2/idna/__init__.py | 2 + openpype/vendor/python/python_2/idna/codec.py | 118 + .../vendor/python/python_2/idna/compat.py | 12 + openpype/vendor/python/python_2/idna/core.py | 400 + .../vendor/python/python_2/idna/idnadata.py | 2050 ++++ .../vendor/python/python_2/idna/intranges.py | 53 + .../python/python_2/idna/package_data.py | 2 + .../vendor/python/python_2/idna/uts46data.py | 8357 +++++++++++++++++ .../python/python_2/requests/__init__.py | 152 + .../python/python_2/requests/__version__.py | 14 + .../python_2/requests/_internal_utils.py | 42 + .../python/python_2/requests/adapters.py | 538 ++ .../vendor/python/python_2/requests/api.py | 159 + .../vendor/python/python_2/requests/auth.py | 305 + .../vendor/python/python_2/requests/certs.py | 18 + .../vendor/python/python_2/requests/compat.py | 81 + .../python/python_2/requests/cookies.py | 549 ++ .../python/python_2/requests/exceptions.py | 133 + .../vendor/python/python_2/requests/help.py | 135 + .../vendor/python/python_2/requests/hooks.py | 34 + .../vendor/python/python_2/requests/models.py | 973 ++ .../python/python_2/requests/packages.py | 26 + .../python/python_2/requests/sessions.py | 771 ++ .../python/python_2/requests/status_codes.py | 123 + .../python/python_2/requests/structures.py | 105 + .../vendor/python/python_2/requests/utils.py | 1060 +++ .../python/python_2/urllib3/__init__.py | 85 + .../python/python_2/urllib3/_collections.py | 337 + .../python/python_2/urllib3/_version.py | 2 + .../python/python_2/urllib3/connection.py | 567 ++ .../python/python_2/urllib3/connectionpool.py | 1108 +++ .../python_2/urllib3/contrib/__init__.py | 0 .../urllib3/contrib/_appengine_environ.py | 36 + .../contrib/_securetransport/__init__.py | 0 .../contrib/_securetransport/bindings.py | 519 + .../contrib/_securetransport/low_level.py | 397 + .../python_2/urllib3/contrib/appengine.py | 314 + .../python_2/urllib3/contrib/ntlmpool.py | 130 + .../python_2/urllib3/contrib/pyopenssl.py | 511 + .../urllib3/contrib/securetransport.py | 922 ++ .../python/python_2/urllib3/contrib/socks.py | 216 + .../python/python_2/urllib3/exceptions.py | 323 + .../vendor/python/python_2/urllib3/fields.py | 274 + .../python/python_2/urllib3/filepost.py | 98 + .../python_2/urllib3/packages/__init__.py | 0 .../urllib3/packages/backports/__init__.py | 0 .../urllib3/packages/backports/makefile.py | 51 + .../python/python_2/urllib3/packages/six.py | 1077 +++ .../python/python_2/urllib3/poolmanager.py | 537 ++ .../vendor/python/python_2/urllib3/request.py | 170 + .../python/python_2/urllib3/response.py | 824 ++ .../python/python_2/urllib3/util/__init__.py | 49 + .../python_2/urllib3/util/connection.py | 149 + .../python/python_2/urllib3/util/proxy.py | 57 + .../python/python_2/urllib3/util/queue.py | 22 + .../python/python_2/urllib3/util/request.py | 146 + .../python/python_2/urllib3/util/response.py | 107 + .../python/python_2/urllib3/util/retry.py | 620 ++ .../python/python_2/urllib3/util/ssl_.py | 495 + .../urllib3/util/ssl_match_hostname.py | 159 + .../python_2/urllib3/util/ssltransport.py | 221 + .../python/python_2/urllib3/util/timeout.py | 268 + .../python/python_2/urllib3/util/url.py | 432 + .../python/python_2/urllib3/util/wait.py | 153 + 107 files changed, 65650 insertions(+) create mode 100644 openpype/vendor/python/python_2/chardet/__init__.py create mode 100644 openpype/vendor/python/python_2/chardet/big5freq.py create mode 100644 openpype/vendor/python/python_2/chardet/big5prober.py create mode 100644 openpype/vendor/python/python_2/chardet/chardistribution.py create mode 100644 openpype/vendor/python/python_2/chardet/charsetgroupprober.py create mode 100644 openpype/vendor/python/python_2/chardet/charsetprober.py create mode 100644 openpype/vendor/python/python_2/chardet/cli/__init__.py create mode 100644 openpype/vendor/python/python_2/chardet/cli/chardetect.py create mode 100644 openpype/vendor/python/python_2/chardet/codingstatemachine.py create mode 100644 openpype/vendor/python/python_2/chardet/compat.py create mode 100644 openpype/vendor/python/python_2/chardet/cp949prober.py create mode 100644 openpype/vendor/python/python_2/chardet/enums.py create mode 100644 openpype/vendor/python/python_2/chardet/escprober.py create mode 100644 openpype/vendor/python/python_2/chardet/escsm.py create mode 100644 openpype/vendor/python/python_2/chardet/eucjpprober.py create mode 100644 openpype/vendor/python/python_2/chardet/euckrfreq.py create mode 100644 openpype/vendor/python/python_2/chardet/euckrprober.py create mode 100644 openpype/vendor/python/python_2/chardet/euctwfreq.py create mode 100644 openpype/vendor/python/python_2/chardet/euctwprober.py create mode 100644 openpype/vendor/python/python_2/chardet/gb2312freq.py create mode 100644 openpype/vendor/python/python_2/chardet/gb2312prober.py create mode 100644 openpype/vendor/python/python_2/chardet/hebrewprober.py create mode 100644 openpype/vendor/python/python_2/chardet/jisfreq.py create mode 100644 openpype/vendor/python/python_2/chardet/jpcntx.py create mode 100644 openpype/vendor/python/python_2/chardet/langbulgarianmodel.py create mode 100644 openpype/vendor/python/python_2/chardet/langgreekmodel.py create mode 100644 openpype/vendor/python/python_2/chardet/langhebrewmodel.py create mode 100644 openpype/vendor/python/python_2/chardet/langhungarianmodel.py create mode 100644 openpype/vendor/python/python_2/chardet/langrussianmodel.py create mode 100644 openpype/vendor/python/python_2/chardet/langthaimodel.py create mode 100644 openpype/vendor/python/python_2/chardet/langturkishmodel.py create mode 100644 openpype/vendor/python/python_2/chardet/latin1prober.py create mode 100644 openpype/vendor/python/python_2/chardet/mbcharsetprober.py create mode 100644 openpype/vendor/python/python_2/chardet/mbcsgroupprober.py create mode 100644 openpype/vendor/python/python_2/chardet/mbcssm.py create mode 100644 openpype/vendor/python/python_2/chardet/metadata/__init__.py create mode 100644 openpype/vendor/python/python_2/chardet/metadata/languages.py create mode 100644 openpype/vendor/python/python_2/chardet/sbcharsetprober.py create mode 100644 openpype/vendor/python/python_2/chardet/sbcsgroupprober.py create mode 100644 openpype/vendor/python/python_2/chardet/sjisprober.py create mode 100644 openpype/vendor/python/python_2/chardet/universaldetector.py create mode 100644 openpype/vendor/python/python_2/chardet/utf8prober.py create mode 100644 openpype/vendor/python/python_2/chardet/version.py create mode 100644 openpype/vendor/python/python_2/idna/__init__.py create mode 100644 openpype/vendor/python/python_2/idna/codec.py create mode 100644 openpype/vendor/python/python_2/idna/compat.py create mode 100644 openpype/vendor/python/python_2/idna/core.py create mode 100644 openpype/vendor/python/python_2/idna/idnadata.py create mode 100644 openpype/vendor/python/python_2/idna/intranges.py create mode 100644 openpype/vendor/python/python_2/idna/package_data.py create mode 100644 openpype/vendor/python/python_2/idna/uts46data.py create mode 100644 openpype/vendor/python/python_2/requests/__init__.py create mode 100644 openpype/vendor/python/python_2/requests/__version__.py create mode 100644 openpype/vendor/python/python_2/requests/_internal_utils.py create mode 100644 openpype/vendor/python/python_2/requests/adapters.py create mode 100644 openpype/vendor/python/python_2/requests/api.py create mode 100644 openpype/vendor/python/python_2/requests/auth.py create mode 100644 openpype/vendor/python/python_2/requests/certs.py create mode 100644 openpype/vendor/python/python_2/requests/compat.py create mode 100644 openpype/vendor/python/python_2/requests/cookies.py create mode 100644 openpype/vendor/python/python_2/requests/exceptions.py create mode 100644 openpype/vendor/python/python_2/requests/help.py create mode 100644 openpype/vendor/python/python_2/requests/hooks.py create mode 100644 openpype/vendor/python/python_2/requests/models.py create mode 100644 openpype/vendor/python/python_2/requests/packages.py create mode 100644 openpype/vendor/python/python_2/requests/sessions.py create mode 100644 openpype/vendor/python/python_2/requests/status_codes.py create mode 100644 openpype/vendor/python/python_2/requests/structures.py create mode 100644 openpype/vendor/python/python_2/requests/utils.py create mode 100644 openpype/vendor/python/python_2/urllib3/__init__.py create mode 100644 openpype/vendor/python/python_2/urllib3/_collections.py create mode 100644 openpype/vendor/python/python_2/urllib3/_version.py create mode 100644 openpype/vendor/python/python_2/urllib3/connection.py create mode 100644 openpype/vendor/python/python_2/urllib3/connectionpool.py create mode 100644 openpype/vendor/python/python_2/urllib3/contrib/__init__.py create mode 100644 openpype/vendor/python/python_2/urllib3/contrib/_appengine_environ.py create mode 100644 openpype/vendor/python/python_2/urllib3/contrib/_securetransport/__init__.py create mode 100644 openpype/vendor/python/python_2/urllib3/contrib/_securetransport/bindings.py create mode 100644 openpype/vendor/python/python_2/urllib3/contrib/_securetransport/low_level.py create mode 100644 openpype/vendor/python/python_2/urllib3/contrib/appengine.py create mode 100644 openpype/vendor/python/python_2/urllib3/contrib/ntlmpool.py create mode 100644 openpype/vendor/python/python_2/urllib3/contrib/pyopenssl.py create mode 100644 openpype/vendor/python/python_2/urllib3/contrib/securetransport.py create mode 100644 openpype/vendor/python/python_2/urllib3/contrib/socks.py create mode 100644 openpype/vendor/python/python_2/urllib3/exceptions.py create mode 100644 openpype/vendor/python/python_2/urllib3/fields.py create mode 100644 openpype/vendor/python/python_2/urllib3/filepost.py create mode 100644 openpype/vendor/python/python_2/urllib3/packages/__init__.py create mode 100644 openpype/vendor/python/python_2/urllib3/packages/backports/__init__.py create mode 100644 openpype/vendor/python/python_2/urllib3/packages/backports/makefile.py create mode 100644 openpype/vendor/python/python_2/urllib3/packages/six.py create mode 100644 openpype/vendor/python/python_2/urllib3/poolmanager.py create mode 100644 openpype/vendor/python/python_2/urllib3/request.py create mode 100644 openpype/vendor/python/python_2/urllib3/response.py create mode 100644 openpype/vendor/python/python_2/urllib3/util/__init__.py create mode 100644 openpype/vendor/python/python_2/urllib3/util/connection.py create mode 100644 openpype/vendor/python/python_2/urllib3/util/proxy.py create mode 100644 openpype/vendor/python/python_2/urllib3/util/queue.py create mode 100644 openpype/vendor/python/python_2/urllib3/util/request.py create mode 100644 openpype/vendor/python/python_2/urllib3/util/response.py create mode 100644 openpype/vendor/python/python_2/urllib3/util/retry.py create mode 100644 openpype/vendor/python/python_2/urllib3/util/ssl_.py create mode 100644 openpype/vendor/python/python_2/urllib3/util/ssl_match_hostname.py create mode 100644 openpype/vendor/python/python_2/urllib3/util/ssltransport.py create mode 100644 openpype/vendor/python/python_2/urllib3/util/timeout.py create mode 100644 openpype/vendor/python/python_2/urllib3/util/url.py create mode 100644 openpype/vendor/python/python_2/urllib3/util/wait.py diff --git a/openpype/vendor/python/python_2/chardet/__init__.py b/openpype/vendor/python/python_2/chardet/__init__.py new file mode 100644 index 0000000000..80ad2546d7 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/__init__.py @@ -0,0 +1,83 @@ +######################## BEGIN LICENSE BLOCK ######################## +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + + +from .universaldetector import UniversalDetector +from .enums import InputState +from .version import __version__, VERSION + + +__all__ = ['UniversalDetector', 'detect', 'detect_all', '__version__', 'VERSION'] + + +def detect(byte_str): + """ + Detect the encoding of the given byte string. + + :param byte_str: The byte sequence to examine. + :type byte_str: ``bytes`` or ``bytearray`` + """ + if not isinstance(byte_str, bytearray): + if not isinstance(byte_str, bytes): + raise TypeError('Expected object of type bytes or bytearray, got: ' + '{}'.format(type(byte_str))) + else: + byte_str = bytearray(byte_str) + detector = UniversalDetector() + detector.feed(byte_str) + return detector.close() + + +def detect_all(byte_str): + """ + Detect all the possible encodings of the given byte string. + + :param byte_str: The byte sequence to examine. + :type byte_str: ``bytes`` or ``bytearray`` + """ + if not isinstance(byte_str, bytearray): + if not isinstance(byte_str, bytes): + raise TypeError('Expected object of type bytes or bytearray, got: ' + '{}'.format(type(byte_str))) + else: + byte_str = bytearray(byte_str) + + detector = UniversalDetector() + detector.feed(byte_str) + detector.close() + + if detector._input_state == InputState.HIGH_BYTE: + results = [] + for prober in detector._charset_probers: + if prober.get_confidence() > detector.MINIMUM_THRESHOLD: + charset_name = prober.charset_name + lower_charset_name = prober.charset_name.lower() + # Use Windows encoding name instead of ISO-8859 if we saw any + # extra Windows-specific bytes + if lower_charset_name.startswith('iso-8859'): + if detector._has_win_bytes: + charset_name = detector.ISO_WIN_MAP.get(lower_charset_name, + charset_name) + results.append({ + 'encoding': charset_name, + 'confidence': prober.get_confidence(), + 'language': prober.language, + }) + if len(results) > 0: + return sorted(results, key=lambda result: -result['confidence']) + + return [detector.result] diff --git a/openpype/vendor/python/python_2/chardet/big5freq.py b/openpype/vendor/python/python_2/chardet/big5freq.py new file mode 100644 index 0000000000..38f32517aa --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/big5freq.py @@ -0,0 +1,386 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# Big5 frequency table +# by Taiwan's Mandarin Promotion Council +# +# +# 128 --> 0.42261 +# 256 --> 0.57851 +# 512 --> 0.74851 +# 1024 --> 0.89384 +# 2048 --> 0.97583 +# +# Ideal Distribution Ratio = 0.74851/(1-0.74851) =2.98 +# Random Distribution Ration = 512/(5401-512)=0.105 +# +# Typical Distribution Ratio about 25% of Ideal one, still much higher than RDR + +BIG5_TYPICAL_DISTRIBUTION_RATIO = 0.75 + +#Char to FreqOrder table +BIG5_TABLE_SIZE = 5376 + +BIG5_CHAR_TO_FREQ_ORDER = ( + 1,1801,1506, 255,1431, 198, 9, 82, 6,5008, 177, 202,3681,1256,2821, 110, # 16 +3814, 33,3274, 261, 76, 44,2114, 16,2946,2187,1176, 659,3971, 26,3451,2653, # 32 +1198,3972,3350,4202, 410,2215, 302, 590, 361,1964, 8, 204, 58,4510,5009,1932, # 48 + 63,5010,5011, 317,1614, 75, 222, 159,4203,2417,1480,5012,3555,3091, 224,2822, # 64 +3682, 3, 10,3973,1471, 29,2787,1135,2866,1940, 873, 130,3275,1123, 312,5013, # 80 +4511,2052, 507, 252, 682,5014, 142,1915, 124, 206,2947, 34,3556,3204, 64, 604, # 96 +5015,2501,1977,1978, 155,1991, 645, 641,1606,5016,3452, 337, 72, 406,5017, 80, # 112 + 630, 238,3205,1509, 263, 939,1092,2654, 756,1440,1094,3453, 449, 69,2987, 591, # 128 + 179,2096, 471, 115,2035,1844, 60, 50,2988, 134, 806,1869, 734,2036,3454, 180, # 144 + 995,1607, 156, 537,2907, 688,5018, 319,1305, 779,2145, 514,2379, 298,4512, 359, # 160 +2502, 90,2716,1338, 663, 11, 906,1099,2553, 20,2441, 182, 532,1716,5019, 732, # 176 +1376,4204,1311,1420,3206, 25,2317,1056, 113, 399, 382,1950, 242,3455,2474, 529, # 192 +3276, 475,1447,3683,5020, 117, 21, 656, 810,1297,2300,2334,3557,5021, 126,4205, # 208 + 706, 456, 150, 613,4513, 71,1118,2037,4206, 145,3092, 85, 835, 486,2115,1246, # 224 +1426, 428, 727,1285,1015, 800, 106, 623, 303,1281,5022,2128,2359, 347,3815, 221, # 240 +3558,3135,5023,1956,1153,4207, 83, 296,1199,3093, 192, 624, 93,5024, 822,1898, # 256 +2823,3136, 795,2065, 991,1554,1542,1592, 27, 43,2867, 859, 139,1456, 860,4514, # 272 + 437, 712,3974, 164,2397,3137, 695, 211,3037,2097, 195,3975,1608,3559,3560,3684, # 288 +3976, 234, 811,2989,2098,3977,2233,1441,3561,1615,2380, 668,2077,1638, 305, 228, # 304 +1664,4515, 467, 415,5025, 262,2099,1593, 239, 108, 300, 200,1033, 512,1247,2078, # 320 +5026,5027,2176,3207,3685,2682, 593, 845,1062,3277, 88,1723,2038,3978,1951, 212, # 336 + 266, 152, 149, 468,1899,4208,4516, 77, 187,5028,3038, 37, 5,2990,5029,3979, # 352 +5030,5031, 39,2524,4517,2908,3208,2079, 55, 148, 74,4518, 545, 483,1474,1029, # 368 +1665, 217,1870,1531,3138,1104,2655,4209, 24, 172,3562, 900,3980,3563,3564,4519, # 384 + 32,1408,2824,1312, 329, 487,2360,2251,2717, 784,2683, 4,3039,3351,1427,1789, # 400 + 188, 109, 499,5032,3686,1717,1790, 888,1217,3040,4520,5033,3565,5034,3352,1520, # 416 +3687,3981, 196,1034, 775,5035,5036, 929,1816, 249, 439, 38,5037,1063,5038, 794, # 432 +3982,1435,2301, 46, 178,3278,2066,5039,2381,5040, 214,1709,4521, 804, 35, 707, # 448 + 324,3688,1601,2554, 140, 459,4210,5041,5042,1365, 839, 272, 978,2262,2580,3456, # 464 +2129,1363,3689,1423, 697, 100,3094, 48, 70,1231, 495,3139,2196,5043,1294,5044, # 480 +2080, 462, 586,1042,3279, 853, 256, 988, 185,2382,3457,1698, 434,1084,5045,3458, # 496 + 314,2625,2788,4522,2335,2336, 569,2285, 637,1817,2525, 757,1162,1879,1616,3459, # 512 + 287,1577,2116, 768,4523,1671,2868,3566,2526,1321,3816, 909,2418,5046,4211, 933, # 528 +3817,4212,2053,2361,1222,4524, 765,2419,1322, 786,4525,5047,1920,1462,1677,2909, # 544 +1699,5048,4526,1424,2442,3140,3690,2600,3353,1775,1941,3460,3983,4213, 309,1369, # 560 +1130,2825, 364,2234,1653,1299,3984,3567,3985,3986,2656, 525,1085,3041, 902,2001, # 576 +1475, 964,4527, 421,1845,1415,1057,2286, 940,1364,3141, 376,4528,4529,1381, 7, # 592 +2527, 983,2383, 336,1710,2684,1846, 321,3461, 559,1131,3042,2752,1809,1132,1313, # 608 + 265,1481,1858,5049, 352,1203,2826,3280, 167,1089, 420,2827, 776, 792,1724,3568, # 624 +4214,2443,3281,5050,4215,5051, 446, 229, 333,2753, 901,3818,1200,1557,4530,2657, # 640 +1921, 395,2754,2685,3819,4216,1836, 125, 916,3209,2626,4531,5052,5053,3820,5054, # 656 +5055,5056,4532,3142,3691,1133,2555,1757,3462,1510,2318,1409,3569,5057,2146, 438, # 672 +2601,2910,2384,3354,1068, 958,3043, 461, 311,2869,2686,4217,1916,3210,4218,1979, # 688 + 383, 750,2755,2627,4219, 274, 539, 385,1278,1442,5058,1154,1965, 384, 561, 210, # 704 + 98,1295,2556,3570,5059,1711,2420,1482,3463,3987,2911,1257, 129,5060,3821, 642, # 720 + 523,2789,2790,2658,5061, 141,2235,1333, 68, 176, 441, 876, 907,4220, 603,2602, # 736 + 710, 171,3464, 404, 549, 18,3143,2398,1410,3692,1666,5062,3571,4533,2912,4534, # 752 +5063,2991, 368,5064, 146, 366, 99, 871,3693,1543, 748, 807,1586,1185, 22,2263, # 768 + 379,3822,3211,5065,3212, 505,1942,2628,1992,1382,2319,5066, 380,2362, 218, 702, # 784 +1818,1248,3465,3044,3572,3355,3282,5067,2992,3694, 930,3283,3823,5068, 59,5069, # 800 + 585, 601,4221, 497,3466,1112,1314,4535,1802,5070,1223,1472,2177,5071, 749,1837, # 816 + 690,1900,3824,1773,3988,1476, 429,1043,1791,2236,2117, 917,4222, 447,1086,1629, # 832 +5072, 556,5073,5074,2021,1654, 844,1090, 105, 550, 966,1758,2828,1008,1783, 686, # 848 +1095,5075,2287, 793,1602,5076,3573,2603,4536,4223,2948,2302,4537,3825, 980,2503, # 864 + 544, 353, 527,4538, 908,2687,2913,5077, 381,2629,1943,1348,5078,1341,1252, 560, # 880 +3095,5079,3467,2870,5080,2054, 973, 886,2081, 143,4539,5081,5082, 157,3989, 496, # 896 +4224, 57, 840, 540,2039,4540,4541,3468,2118,1445, 970,2264,1748,1966,2082,4225, # 912 +3144,1234,1776,3284,2829,3695, 773,1206,2130,1066,2040,1326,3990,1738,1725,4226, # 928 + 279,3145, 51,1544,2604, 423,1578,2131,2067, 173,4542,1880,5083,5084,1583, 264, # 944 + 610,3696,4543,2444, 280, 154,5085,5086,5087,1739, 338,1282,3096, 693,2871,1411, # 960 +1074,3826,2445,5088,4544,5089,5090,1240, 952,2399,5091,2914,1538,2688, 685,1483, # 976 +4227,2475,1436, 953,4228,2055,4545, 671,2400, 79,4229,2446,3285, 608, 567,2689, # 992 +3469,4230,4231,1691, 393,1261,1792,2401,5092,4546,5093,5094,5095,5096,1383,1672, # 1008 +3827,3213,1464, 522,1119, 661,1150, 216, 675,4547,3991,1432,3574, 609,4548,2690, # 1024 +2402,5097,5098,5099,4232,3045, 0,5100,2476, 315, 231,2447, 301,3356,4549,2385, # 1040 +5101, 233,4233,3697,1819,4550,4551,5102, 96,1777,1315,2083,5103, 257,5104,1810, # 1056 +3698,2718,1139,1820,4234,2022,1124,2164,2791,1778,2659,5105,3097, 363,1655,3214, # 1072 +5106,2993,5107,5108,5109,3992,1567,3993, 718, 103,3215, 849,1443, 341,3357,2949, # 1088 +1484,5110,1712, 127, 67, 339,4235,2403, 679,1412, 821,5111,5112, 834, 738, 351, # 1104 +2994,2147, 846, 235,1497,1881, 418,1993,3828,2719, 186,1100,2148,2756,3575,1545, # 1120 +1355,2950,2872,1377, 583,3994,4236,2581,2995,5113,1298,3699,1078,2557,3700,2363, # 1136 + 78,3829,3830, 267,1289,2100,2002,1594,4237, 348, 369,1274,2197,2178,1838,4552, # 1152 +1821,2830,3701,2757,2288,2003,4553,2951,2758, 144,3358, 882,4554,3995,2759,3470, # 1168 +4555,2915,5114,4238,1726, 320,5115,3996,3046, 788,2996,5116,2831,1774,1327,2873, # 1184 +3997,2832,5117,1306,4556,2004,1700,3831,3576,2364,2660, 787,2023, 506, 824,3702, # 1200 + 534, 323,4557,1044,3359,2024,1901, 946,3471,5118,1779,1500,1678,5119,1882,4558, # 1216 + 165, 243,4559,3703,2528, 123, 683,4239, 764,4560, 36,3998,1793, 589,2916, 816, # 1232 + 626,1667,3047,2237,1639,1555,1622,3832,3999,5120,4000,2874,1370,1228,1933, 891, # 1248 +2084,2917, 304,4240,5121, 292,2997,2720,3577, 691,2101,4241,1115,4561, 118, 662, # 1264 +5122, 611,1156, 854,2386,1316,2875, 2, 386, 515,2918,5123,5124,3286, 868,2238, # 1280 +1486, 855,2661, 785,2216,3048,5125,1040,3216,3578,5126,3146, 448,5127,1525,5128, # 1296 +2165,4562,5129,3833,5130,4242,2833,3579,3147, 503, 818,4001,3148,1568, 814, 676, # 1312 +1444, 306,1749,5131,3834,1416,1030, 197,1428, 805,2834,1501,4563,5132,5133,5134, # 1328 +1994,5135,4564,5136,5137,2198, 13,2792,3704,2998,3149,1229,1917,5138,3835,2132, # 1344 +5139,4243,4565,2404,3580,5140,2217,1511,1727,1120,5141,5142, 646,3836,2448, 307, # 1360 +5143,5144,1595,3217,5145,5146,5147,3705,1113,1356,4002,1465,2529,2530,5148, 519, # 1376 +5149, 128,2133, 92,2289,1980,5150,4003,1512, 342,3150,2199,5151,2793,2218,1981, # 1392 +3360,4244, 290,1656,1317, 789, 827,2365,5152,3837,4566, 562, 581,4004,5153, 401, # 1408 +4567,2252, 94,4568,5154,1399,2794,5155,1463,2025,4569,3218,1944,5156, 828,1105, # 1424 +4245,1262,1394,5157,4246, 605,4570,5158,1784,2876,5159,2835, 819,2102, 578,2200, # 1440 +2952,5160,1502, 436,3287,4247,3288,2836,4005,2919,3472,3473,5161,2721,2320,5162, # 1456 +5163,2337,2068, 23,4571, 193, 826,3838,2103, 699,1630,4248,3098, 390,1794,1064, # 1472 +3581,5164,1579,3099,3100,1400,5165,4249,1839,1640,2877,5166,4572,4573, 137,4250, # 1488 + 598,3101,1967, 780, 104, 974,2953,5167, 278, 899, 253, 402, 572, 504, 493,1339, # 1504 +5168,4006,1275,4574,2582,2558,5169,3706,3049,3102,2253, 565,1334,2722, 863, 41, # 1520 +5170,5171,4575,5172,1657,2338, 19, 463,2760,4251, 606,5173,2999,3289,1087,2085, # 1536 +1323,2662,3000,5174,1631,1623,1750,4252,2691,5175,2878, 791,2723,2663,2339, 232, # 1552 +2421,5176,3001,1498,5177,2664,2630, 755,1366,3707,3290,3151,2026,1609, 119,1918, # 1568 +3474, 862,1026,4253,5178,4007,3839,4576,4008,4577,2265,1952,2477,5179,1125, 817, # 1584 +4254,4255,4009,1513,1766,2041,1487,4256,3050,3291,2837,3840,3152,5180,5181,1507, # 1600 +5182,2692, 733, 40,1632,1106,2879, 345,4257, 841,2531, 230,4578,3002,1847,3292, # 1616 +3475,5183,1263, 986,3476,5184, 735, 879, 254,1137, 857, 622,1300,1180,1388,1562, # 1632 +4010,4011,2954, 967,2761,2665,1349, 592,2134,1692,3361,3003,1995,4258,1679,4012, # 1648 +1902,2188,5185, 739,3708,2724,1296,1290,5186,4259,2201,2202,1922,1563,2605,2559, # 1664 +1871,2762,3004,5187, 435,5188, 343,1108, 596, 17,1751,4579,2239,3477,3709,5189, # 1680 +4580, 294,3582,2955,1693, 477, 979, 281,2042,3583, 643,2043,3710,2631,2795,2266, # 1696 +1031,2340,2135,2303,3584,4581, 367,1249,2560,5190,3585,5191,4582,1283,3362,2005, # 1712 + 240,1762,3363,4583,4584, 836,1069,3153, 474,5192,2149,2532, 268,3586,5193,3219, # 1728 +1521,1284,5194,1658,1546,4260,5195,3587,3588,5196,4261,3364,2693,1685,4262, 961, # 1744 +1673,2632, 190,2006,2203,3841,4585,4586,5197, 570,2504,3711,1490,5198,4587,2633, # 1760 +3293,1957,4588, 584,1514, 396,1045,1945,5199,4589,1968,2449,5200,5201,4590,4013, # 1776 + 619,5202,3154,3294, 215,2007,2796,2561,3220,4591,3221,4592, 763,4263,3842,4593, # 1792 +5203,5204,1958,1767,2956,3365,3712,1174, 452,1477,4594,3366,3155,5205,2838,1253, # 1808 +2387,2189,1091,2290,4264, 492,5206, 638,1169,1825,2136,1752,4014, 648, 926,1021, # 1824 +1324,4595, 520,4596, 997, 847,1007, 892,4597,3843,2267,1872,3713,2405,1785,4598, # 1840 +1953,2957,3103,3222,1728,4265,2044,3714,4599,2008,1701,3156,1551, 30,2268,4266, # 1856 +5207,2027,4600,3589,5208, 501,5209,4267, 594,3478,2166,1822,3590,3479,3591,3223, # 1872 + 829,2839,4268,5210,1680,3157,1225,4269,5211,3295,4601,4270,3158,2341,5212,4602, # 1888 +4271,5213,4015,4016,5214,1848,2388,2606,3367,5215,4603, 374,4017, 652,4272,4273, # 1904 + 375,1140, 798,5216,5217,5218,2366,4604,2269, 546,1659, 138,3051,2450,4605,5219, # 1920 +2254, 612,1849, 910, 796,3844,1740,1371, 825,3845,3846,5220,2920,2562,5221, 692, # 1936 + 444,3052,2634, 801,4606,4274,5222,1491, 244,1053,3053,4275,4276, 340,5223,4018, # 1952 +1041,3005, 293,1168, 87,1357,5224,1539, 959,5225,2240, 721, 694,4277,3847, 219, # 1968 +1478, 644,1417,3368,2666,1413,1401,1335,1389,4019,5226,5227,3006,2367,3159,1826, # 1984 + 730,1515, 184,2840, 66,4607,5228,1660,2958, 246,3369, 378,1457, 226,3480, 975, # 2000 +4020,2959,1264,3592, 674, 696,5229, 163,5230,1141,2422,2167, 713,3593,3370,4608, # 2016 +4021,5231,5232,1186, 15,5233,1079,1070,5234,1522,3224,3594, 276,1050,2725, 758, # 2032 +1126, 653,2960,3296,5235,2342, 889,3595,4022,3104,3007, 903,1250,4609,4023,3481, # 2048 +3596,1342,1681,1718, 766,3297, 286, 89,2961,3715,5236,1713,5237,2607,3371,3008, # 2064 +5238,2962,2219,3225,2880,5239,4610,2505,2533, 181, 387,1075,4024, 731,2190,3372, # 2080 +5240,3298, 310, 313,3482,2304, 770,4278, 54,3054, 189,4611,3105,3848,4025,5241, # 2096 +1230,1617,1850, 355,3597,4279,4612,3373, 111,4280,3716,1350,3160,3483,3055,4281, # 2112 +2150,3299,3598,5242,2797,4026,4027,3009, 722,2009,5243,1071, 247,1207,2343,2478, # 2128 +1378,4613,2010, 864,1437,1214,4614, 373,3849,1142,2220, 667,4615, 442,2763,2563, # 2144 +3850,4028,1969,4282,3300,1840, 837, 170,1107, 934,1336,1883,5244,5245,2119,4283, # 2160 +2841, 743,1569,5246,4616,4284, 582,2389,1418,3484,5247,1803,5248, 357,1395,1729, # 2176 +3717,3301,2423,1564,2241,5249,3106,3851,1633,4617,1114,2086,4285,1532,5250, 482, # 2192 +2451,4618,5251,5252,1492, 833,1466,5253,2726,3599,1641,2842,5254,1526,1272,3718, # 2208 +4286,1686,1795, 416,2564,1903,1954,1804,5255,3852,2798,3853,1159,2321,5256,2881, # 2224 +4619,1610,1584,3056,2424,2764, 443,3302,1163,3161,5257,5258,4029,5259,4287,2506, # 2240 +3057,4620,4030,3162,2104,1647,3600,2011,1873,4288,5260,4289, 431,3485,5261, 250, # 2256 + 97, 81,4290,5262,1648,1851,1558, 160, 848,5263, 866, 740,1694,5264,2204,2843, # 2272 +3226,4291,4621,3719,1687, 950,2479, 426, 469,3227,3720,3721,4031,5265,5266,1188, # 2288 + 424,1996, 861,3601,4292,3854,2205,2694, 168,1235,3602,4293,5267,2087,1674,4622, # 2304 +3374,3303, 220,2565,1009,5268,3855, 670,3010, 332,1208, 717,5269,5270,3603,2452, # 2320 +4032,3375,5271, 513,5272,1209,2882,3376,3163,4623,1080,5273,5274,5275,5276,2534, # 2336 +3722,3604, 815,1587,4033,4034,5277,3605,3486,3856,1254,4624,1328,3058,1390,4035, # 2352 +1741,4036,3857,4037,5278, 236,3858,2453,3304,5279,5280,3723,3859,1273,3860,4625, # 2368 +5281, 308,5282,4626, 245,4627,1852,2480,1307,2583, 430, 715,2137,2454,5283, 270, # 2384 + 199,2883,4038,5284,3606,2727,1753, 761,1754, 725,1661,1841,4628,3487,3724,5285, # 2400 +5286, 587, 14,3305, 227,2608, 326, 480,2270, 943,2765,3607, 291, 650,1884,5287, # 2416 +1702,1226, 102,1547, 62,3488, 904,4629,3489,1164,4294,5288,5289,1224,1548,2766, # 2432 + 391, 498,1493,5290,1386,1419,5291,2056,1177,4630, 813, 880,1081,2368, 566,1145, # 2448 +4631,2291,1001,1035,2566,2609,2242, 394,1286,5292,5293,2069,5294, 86,1494,1730, # 2464 +4039, 491,1588, 745, 897,2963, 843,3377,4040,2767,2884,3306,1768, 998,2221,2070, # 2480 + 397,1827,1195,1970,3725,3011,3378, 284,5295,3861,2507,2138,2120,1904,5296,4041, # 2496 +2151,4042,4295,1036,3490,1905, 114,2567,4296, 209,1527,5297,5298,2964,2844,2635, # 2512 +2390,2728,3164, 812,2568,5299,3307,5300,1559, 737,1885,3726,1210, 885, 28,2695, # 2528 +3608,3862,5301,4297,1004,1780,4632,5302, 346,1982,2222,2696,4633,3863,1742, 797, # 2544 +1642,4043,1934,1072,1384,2152, 896,4044,3308,3727,3228,2885,3609,5303,2569,1959, # 2560 +4634,2455,1786,5304,5305,5306,4045,4298,1005,1308,3728,4299,2729,4635,4636,1528, # 2576 +2610, 161,1178,4300,1983, 987,4637,1101,4301, 631,4046,1157,3229,2425,1343,1241, # 2592 +1016,2243,2570, 372, 877,2344,2508,1160, 555,1935, 911,4047,5307, 466,1170, 169, # 2608 +1051,2921,2697,3729,2481,3012,1182,2012,2571,1251,2636,5308, 992,2345,3491,1540, # 2624 +2730,1201,2071,2406,1997,2482,5309,4638, 528,1923,2191,1503,1874,1570,2369,3379, # 2640 +3309,5310, 557,1073,5311,1828,3492,2088,2271,3165,3059,3107, 767,3108,2799,4639, # 2656 +1006,4302,4640,2346,1267,2179,3730,3230, 778,4048,3231,2731,1597,2667,5312,4641, # 2672 +5313,3493,5314,5315,5316,3310,2698,1433,3311, 131, 95,1504,4049, 723,4303,3166, # 2688 +1842,3610,2768,2192,4050,2028,2105,3731,5317,3013,4051,1218,5318,3380,3232,4052, # 2704 +4304,2584, 248,1634,3864, 912,5319,2845,3732,3060,3865, 654, 53,5320,3014,5321, # 2720 +1688,4642, 777,3494,1032,4053,1425,5322, 191, 820,2121,2846, 971,4643, 931,3233, # 2736 + 135, 664, 783,3866,1998, 772,2922,1936,4054,3867,4644,2923,3234, 282,2732, 640, # 2752 +1372,3495,1127, 922, 325,3381,5323,5324, 711,2045,5325,5326,4055,2223,2800,1937, # 2768 +4056,3382,2224,2255,3868,2305,5327,4645,3869,1258,3312,4057,3235,2139,2965,4058, # 2784 +4059,5328,2225, 258,3236,4646, 101,1227,5329,3313,1755,5330,1391,3314,5331,2924, # 2800 +2057, 893,5332,5333,5334,1402,4305,2347,5335,5336,3237,3611,5337,5338, 878,1325, # 2816 +1781,2801,4647, 259,1385,2585, 744,1183,2272,4648,5339,4060,2509,5340, 684,1024, # 2832 +4306,5341, 472,3612,3496,1165,3315,4061,4062, 322,2153, 881, 455,1695,1152,1340, # 2848 + 660, 554,2154,4649,1058,4650,4307, 830,1065,3383,4063,4651,1924,5342,1703,1919, # 2864 +5343, 932,2273, 122,5344,4652, 947, 677,5345,3870,2637, 297,1906,1925,2274,4653, # 2880 +2322,3316,5346,5347,4308,5348,4309, 84,4310, 112, 989,5349, 547,1059,4064, 701, # 2896 +3613,1019,5350,4311,5351,3497, 942, 639, 457,2306,2456, 993,2966, 407, 851, 494, # 2912 +4654,3384, 927,5352,1237,5353,2426,3385, 573,4312, 680, 921,2925,1279,1875, 285, # 2928 + 790,1448,1984, 719,2168,5354,5355,4655,4065,4066,1649,5356,1541, 563,5357,1077, # 2944 +5358,3386,3061,3498, 511,3015,4067,4068,3733,4069,1268,2572,3387,3238,4656,4657, # 2960 +5359, 535,1048,1276,1189,2926,2029,3167,1438,1373,2847,2967,1134,2013,5360,4313, # 2976 +1238,2586,3109,1259,5361, 700,5362,2968,3168,3734,4314,5363,4315,1146,1876,1907, # 2992 +4658,2611,4070, 781,2427, 132,1589, 203, 147, 273,2802,2407, 898,1787,2155,4071, # 3008 +4072,5364,3871,2803,5365,5366,4659,4660,5367,3239,5368,1635,3872, 965,5369,1805, # 3024 +2699,1516,3614,1121,1082,1329,3317,4073,1449,3873, 65,1128,2848,2927,2769,1590, # 3040 +3874,5370,5371, 12,2668, 45, 976,2587,3169,4661, 517,2535,1013,1037,3240,5372, # 3056 +3875,2849,5373,3876,5374,3499,5375,2612, 614,1999,2323,3877,3110,2733,2638,5376, # 3072 +2588,4316, 599,1269,5377,1811,3735,5378,2700,3111, 759,1060, 489,1806,3388,3318, # 3088 +1358,5379,5380,2391,1387,1215,2639,2256, 490,5381,5382,4317,1759,2392,2348,5383, # 3104 +4662,3878,1908,4074,2640,1807,3241,4663,3500,3319,2770,2349, 874,5384,5385,3501, # 3120 +3736,1859, 91,2928,3737,3062,3879,4664,5386,3170,4075,2669,5387,3502,1202,1403, # 3136 +3880,2969,2536,1517,2510,4665,3503,2511,5388,4666,5389,2701,1886,1495,1731,4076, # 3152 +2370,4667,5390,2030,5391,5392,4077,2702,1216, 237,2589,4318,2324,4078,3881,4668, # 3168 +4669,2703,3615,3504, 445,4670,5393,5394,5395,5396,2771, 61,4079,3738,1823,4080, # 3184 +5397, 687,2046, 935, 925, 405,2670, 703,1096,1860,2734,4671,4081,1877,1367,2704, # 3200 +3389, 918,2106,1782,2483, 334,3320,1611,1093,4672, 564,3171,3505,3739,3390, 945, # 3216 +2641,2058,4673,5398,1926, 872,4319,5399,3506,2705,3112, 349,4320,3740,4082,4674, # 3232 +3882,4321,3741,2156,4083,4675,4676,4322,4677,2408,2047, 782,4084, 400, 251,4323, # 3248 +1624,5400,5401, 277,3742, 299,1265, 476,1191,3883,2122,4324,4325,1109, 205,5402, # 3264 +2590,1000,2157,3616,1861,5403,5404,5405,4678,5406,4679,2573, 107,2484,2158,4085, # 3280 +3507,3172,5407,1533, 541,1301, 158, 753,4326,2886,3617,5408,1696, 370,1088,4327, # 3296 +4680,3618, 579, 327, 440, 162,2244, 269,1938,1374,3508, 968,3063, 56,1396,3113, # 3312 +2107,3321,3391,5409,1927,2159,4681,3016,5410,3619,5411,5412,3743,4682,2485,5413, # 3328 +2804,5414,1650,4683,5415,2613,5416,5417,4086,2671,3392,1149,3393,4087,3884,4088, # 3344 +5418,1076, 49,5419, 951,3242,3322,3323, 450,2850, 920,5420,1812,2805,2371,4328, # 3360 +1909,1138,2372,3885,3509,5421,3243,4684,1910,1147,1518,2428,4685,3886,5422,4686, # 3376 +2393,2614, 260,1796,3244,5423,5424,3887,3324, 708,5425,3620,1704,5426,3621,1351, # 3392 +1618,3394,3017,1887, 944,4329,3395,4330,3064,3396,4331,5427,3744, 422, 413,1714, # 3408 +3325, 500,2059,2350,4332,2486,5428,1344,1911, 954,5429,1668,5430,5431,4089,2409, # 3424 +4333,3622,3888,4334,5432,2307,1318,2512,3114, 133,3115,2887,4687, 629, 31,2851, # 3440 +2706,3889,4688, 850, 949,4689,4090,2970,1732,2089,4335,1496,1853,5433,4091, 620, # 3456 +3245, 981,1242,3745,3397,1619,3746,1643,3326,2140,2457,1971,1719,3510,2169,5434, # 3472 +3246,5435,5436,3398,1829,5437,1277,4690,1565,2048,5438,1636,3623,3116,5439, 869, # 3488 +2852, 655,3890,3891,3117,4092,3018,3892,1310,3624,4691,5440,5441,5442,1733, 558, # 3504 +4692,3747, 335,1549,3065,1756,4336,3748,1946,3511,1830,1291,1192, 470,2735,2108, # 3520 +2806, 913,1054,4093,5443,1027,5444,3066,4094,4693, 982,2672,3399,3173,3512,3247, # 3536 +3248,1947,2807,5445, 571,4694,5446,1831,5447,3625,2591,1523,2429,5448,2090, 984, # 3552 +4695,3749,1960,5449,3750, 852, 923,2808,3513,3751, 969,1519, 999,2049,2325,1705, # 3568 +5450,3118, 615,1662, 151, 597,4095,2410,2326,1049, 275,4696,3752,4337, 568,3753, # 3584 +3626,2487,4338,3754,5451,2430,2275, 409,3249,5452,1566,2888,3514,1002, 769,2853, # 3600 + 194,2091,3174,3755,2226,3327,4339, 628,1505,5453,5454,1763,2180,3019,4096, 521, # 3616 +1161,2592,1788,2206,2411,4697,4097,1625,4340,4341, 412, 42,3119, 464,5455,2642, # 3632 +4698,3400,1760,1571,2889,3515,2537,1219,2207,3893,2643,2141,2373,4699,4700,3328, # 3648 +1651,3401,3627,5456,5457,3628,2488,3516,5458,3756,5459,5460,2276,2092, 460,5461, # 3664 +4701,5462,3020, 962, 588,3629, 289,3250,2644,1116, 52,5463,3067,1797,5464,5465, # 3680 +5466,1467,5467,1598,1143,3757,4342,1985,1734,1067,4702,1280,3402, 465,4703,1572, # 3696 + 510,5468,1928,2245,1813,1644,3630,5469,4704,3758,5470,5471,2673,1573,1534,5472, # 3712 +5473, 536,1808,1761,3517,3894,3175,2645,5474,5475,5476,4705,3518,2929,1912,2809, # 3728 +5477,3329,1122, 377,3251,5478, 360,5479,5480,4343,1529, 551,5481,2060,3759,1769, # 3744 +2431,5482,2930,4344,3330,3120,2327,2109,2031,4706,1404, 136,1468,1479, 672,1171, # 3760 +3252,2308, 271,3176,5483,2772,5484,2050, 678,2736, 865,1948,4707,5485,2014,4098, # 3776 +2971,5486,2737,2227,1397,3068,3760,4708,4709,1735,2931,3403,3631,5487,3895, 509, # 3792 +2854,2458,2890,3896,5488,5489,3177,3178,4710,4345,2538,4711,2309,1166,1010, 552, # 3808 + 681,1888,5490,5491,2972,2973,4099,1287,1596,1862,3179, 358, 453, 736, 175, 478, # 3824 +1117, 905,1167,1097,5492,1854,1530,5493,1706,5494,2181,3519,2292,3761,3520,3632, # 3840 +4346,2093,4347,5495,3404,1193,2489,4348,1458,2193,2208,1863,1889,1421,3331,2932, # 3856 +3069,2182,3521, 595,2123,5496,4100,5497,5498,4349,1707,2646, 223,3762,1359, 751, # 3872 +3121, 183,3522,5499,2810,3021, 419,2374, 633, 704,3897,2394, 241,5500,5501,5502, # 3888 + 838,3022,3763,2277,2773,2459,3898,1939,2051,4101,1309,3122,2246,1181,5503,1136, # 3904 +2209,3899,2375,1446,4350,2310,4712,5504,5505,4351,1055,2615, 484,3764,5506,4102, # 3920 + 625,4352,2278,3405,1499,4353,4103,5507,4104,4354,3253,2279,2280,3523,5508,5509, # 3936 +2774, 808,2616,3765,3406,4105,4355,3123,2539, 526,3407,3900,4356, 955,5510,1620, # 3952 +4357,2647,2432,5511,1429,3766,1669,1832, 994, 928,5512,3633,1260,5513,5514,5515, # 3968 +1949,2293, 741,2933,1626,4358,2738,2460, 867,1184, 362,3408,1392,5516,5517,4106, # 3984 +4359,1770,1736,3254,2934,4713,4714,1929,2707,1459,1158,5518,3070,3409,2891,1292, # 4000 +1930,2513,2855,3767,1986,1187,2072,2015,2617,4360,5519,2574,2514,2170,3768,2490, # 4016 +3332,5520,3769,4715,5521,5522, 666,1003,3023,1022,3634,4361,5523,4716,1814,2257, # 4032 + 574,3901,1603, 295,1535, 705,3902,4362, 283, 858, 417,5524,5525,3255,4717,4718, # 4048 +3071,1220,1890,1046,2281,2461,4107,1393,1599, 689,2575, 388,4363,5526,2491, 802, # 4064 +5527,2811,3903,2061,1405,2258,5528,4719,3904,2110,1052,1345,3256,1585,5529, 809, # 4080 +5530,5531,5532, 575,2739,3524, 956,1552,1469,1144,2328,5533,2329,1560,2462,3635, # 4096 +3257,4108, 616,2210,4364,3180,2183,2294,5534,1833,5535,3525,4720,5536,1319,3770, # 4112 +3771,1211,3636,1023,3258,1293,2812,5537,5538,5539,3905, 607,2311,3906, 762,2892, # 4128 +1439,4365,1360,4721,1485,3072,5540,4722,1038,4366,1450,2062,2648,4367,1379,4723, # 4144 +2593,5541,5542,4368,1352,1414,2330,2935,1172,5543,5544,3907,3908,4724,1798,1451, # 4160 +5545,5546,5547,5548,2936,4109,4110,2492,2351, 411,4111,4112,3637,3333,3124,4725, # 4176 +1561,2674,1452,4113,1375,5549,5550, 47,2974, 316,5551,1406,1591,2937,3181,5552, # 4192 +1025,2142,3125,3182, 354,2740, 884,2228,4369,2412, 508,3772, 726,3638, 996,2433, # 4208 +3639, 729,5553, 392,2194,1453,4114,4726,3773,5554,5555,2463,3640,2618,1675,2813, # 4224 + 919,2352,2975,2353,1270,4727,4115, 73,5556,5557, 647,5558,3259,2856,2259,1550, # 4240 +1346,3024,5559,1332, 883,3526,5560,5561,5562,5563,3334,2775,5564,1212, 831,1347, # 4256 +4370,4728,2331,3909,1864,3073, 720,3910,4729,4730,3911,5565,4371,5566,5567,4731, # 4272 +5568,5569,1799,4732,3774,2619,4733,3641,1645,2376,4734,5570,2938, 669,2211,2675, # 4288 +2434,5571,2893,5572,5573,1028,3260,5574,4372,2413,5575,2260,1353,5576,5577,4735, # 4304 +3183, 518,5578,4116,5579,4373,1961,5580,2143,4374,5581,5582,3025,2354,2355,3912, # 4320 + 516,1834,1454,4117,2708,4375,4736,2229,2620,1972,1129,3642,5583,2776,5584,2976, # 4336 +1422, 577,1470,3026,1524,3410,5585,5586, 432,4376,3074,3527,5587,2594,1455,2515, # 4352 +2230,1973,1175,5588,1020,2741,4118,3528,4737,5589,2742,5590,1743,1361,3075,3529, # 4368 +2649,4119,4377,4738,2295, 895, 924,4378,2171, 331,2247,3076, 166,1627,3077,1098, # 4384 +5591,1232,2894,2231,3411,4739, 657, 403,1196,2377, 542,3775,3412,1600,4379,3530, # 4400 +5592,4740,2777,3261, 576, 530,1362,4741,4742,2540,2676,3776,4120,5593, 842,3913, # 4416 +5594,2814,2032,1014,4121, 213,2709,3413, 665, 621,4380,5595,3777,2939,2435,5596, # 4432 +2436,3335,3643,3414,4743,4381,2541,4382,4744,3644,1682,4383,3531,1380,5597, 724, # 4448 +2282, 600,1670,5598,1337,1233,4745,3126,2248,5599,1621,4746,5600, 651,4384,5601, # 4464 +1612,4385,2621,5602,2857,5603,2743,2312,3078,5604, 716,2464,3079, 174,1255,2710, # 4480 +4122,3645, 548,1320,1398, 728,4123,1574,5605,1891,1197,3080,4124,5606,3081,3082, # 4496 +3778,3646,3779, 747,5607, 635,4386,4747,5608,5609,5610,4387,5611,5612,4748,5613, # 4512 +3415,4749,2437, 451,5614,3780,2542,2073,4388,2744,4389,4125,5615,1764,4750,5616, # 4528 +4390, 350,4751,2283,2395,2493,5617,4391,4126,2249,1434,4127, 488,4752, 458,4392, # 4544 +4128,3781, 771,1330,2396,3914,2576,3184,2160,2414,1553,2677,3185,4393,5618,2494, # 4560 +2895,2622,1720,2711,4394,3416,4753,5619,2543,4395,5620,3262,4396,2778,5621,2016, # 4576 +2745,5622,1155,1017,3782,3915,5623,3336,2313, 201,1865,4397,1430,5624,4129,5625, # 4592 +5626,5627,5628,5629,4398,1604,5630, 414,1866, 371,2595,4754,4755,3532,2017,3127, # 4608 +4756,1708, 960,4399, 887, 389,2172,1536,1663,1721,5631,2232,4130,2356,2940,1580, # 4624 +5632,5633,1744,4757,2544,4758,4759,5634,4760,5635,2074,5636,4761,3647,3417,2896, # 4640 +4400,5637,4401,2650,3418,2815, 673,2712,2465, 709,3533,4131,3648,4402,5638,1148, # 4656 + 502, 634,5639,5640,1204,4762,3649,1575,4763,2623,3783,5641,3784,3128, 948,3263, # 4672 + 121,1745,3916,1110,5642,4403,3083,2516,3027,4132,3785,1151,1771,3917,1488,4133, # 4688 +1987,5643,2438,3534,5644,5645,2094,5646,4404,3918,1213,1407,2816, 531,2746,2545, # 4704 +3264,1011,1537,4764,2779,4405,3129,1061,5647,3786,3787,1867,2897,5648,2018, 120, # 4720 +4406,4407,2063,3650,3265,2314,3919,2678,3419,1955,4765,4134,5649,3535,1047,2713, # 4736 +1266,5650,1368,4766,2858, 649,3420,3920,2546,2747,1102,2859,2679,5651,5652,2000, # 4752 +5653,1111,3651,2977,5654,2495,3921,3652,2817,1855,3421,3788,5655,5656,3422,2415, # 4768 +2898,3337,3266,3653,5657,2577,5658,3654,2818,4135,1460, 856,5659,3655,5660,2899, # 4784 +2978,5661,2900,3922,5662,4408, 632,2517, 875,3923,1697,3924,2296,5663,5664,4767, # 4800 +3028,1239, 580,4768,4409,5665, 914, 936,2075,1190,4136,1039,2124,5666,5667,5668, # 4816 +5669,3423,1473,5670,1354,4410,3925,4769,2173,3084,4137, 915,3338,4411,4412,3339, # 4832 +1605,1835,5671,2748, 398,3656,4413,3926,4138, 328,1913,2860,4139,3927,1331,4414, # 4848 +3029, 937,4415,5672,3657,4140,4141,3424,2161,4770,3425, 524, 742, 538,3085,1012, # 4864 +5673,5674,3928,2466,5675, 658,1103, 225,3929,5676,5677,4771,5678,4772,5679,3267, # 4880 +1243,5680,4142, 963,2250,4773,5681,2714,3658,3186,5682,5683,2596,2332,5684,4774, # 4896 +5685,5686,5687,3536, 957,3426,2547,2033,1931,2941,2467, 870,2019,3659,1746,2780, # 4912 +2781,2439,2468,5688,3930,5689,3789,3130,3790,3537,3427,3791,5690,1179,3086,5691, # 4928 +3187,2378,4416,3792,2548,3188,3131,2749,4143,5692,3428,1556,2549,2297, 977,2901, # 4944 +2034,4144,1205,3429,5693,1765,3430,3189,2125,1271, 714,1689,4775,3538,5694,2333, # 4960 +3931, 533,4417,3660,2184, 617,5695,2469,3340,3539,2315,5696,5697,3190,5698,5699, # 4976 +3932,1988, 618, 427,2651,3540,3431,5700,5701,1244,1690,5702,2819,4418,4776,5703, # 4992 +3541,4777,5704,2284,1576, 473,3661,4419,3432, 972,5705,3662,5706,3087,5707,5708, # 5008 +4778,4779,5709,3793,4145,4146,5710, 153,4780, 356,5711,1892,2902,4420,2144, 408, # 5024 + 803,2357,5712,3933,5713,4421,1646,2578,2518,4781,4782,3934,5714,3935,4422,5715, # 5040 +2416,3433, 752,5716,5717,1962,3341,2979,5718, 746,3030,2470,4783,4423,3794, 698, # 5056 +4784,1893,4424,3663,2550,4785,3664,3936,5719,3191,3434,5720,1824,1302,4147,2715, # 5072 +3937,1974,4425,5721,4426,3192, 823,1303,1288,1236,2861,3542,4148,3435, 774,3938, # 5088 +5722,1581,4786,1304,2862,3939,4787,5723,2440,2162,1083,3268,4427,4149,4428, 344, # 5104 +1173, 288,2316, 454,1683,5724,5725,1461,4788,4150,2597,5726,5727,4789, 985, 894, # 5120 +5728,3436,3193,5729,1914,2942,3795,1989,5730,2111,1975,5731,4151,5732,2579,1194, # 5136 + 425,5733,4790,3194,1245,3796,4429,5734,5735,2863,5736, 636,4791,1856,3940, 760, # 5152 +1800,5737,4430,2212,1508,4792,4152,1894,1684,2298,5738,5739,4793,4431,4432,2213, # 5168 + 479,5740,5741, 832,5742,4153,2496,5743,2980,2497,3797, 990,3132, 627,1815,2652, # 5184 +4433,1582,4434,2126,2112,3543,4794,5744, 799,4435,3195,5745,4795,2113,1737,3031, # 5200 +1018, 543, 754,4436,3342,1676,4796,4797,4154,4798,1489,5746,3544,5747,2624,2903, # 5216 +4155,5748,5749,2981,5750,5751,5752,5753,3196,4799,4800,2185,1722,5754,3269,3270, # 5232 +1843,3665,1715, 481, 365,1976,1857,5755,5756,1963,2498,4801,5757,2127,3666,3271, # 5248 + 433,1895,2064,2076,5758, 602,2750,5759,5760,5761,5762,5763,3032,1628,3437,5764, # 5264 +3197,4802,4156,2904,4803,2519,5765,2551,2782,5766,5767,5768,3343,4804,2905,5769, # 5280 +4805,5770,2864,4806,4807,1221,2982,4157,2520,5771,5772,5773,1868,1990,5774,5775, # 5296 +5776,1896,5777,5778,4808,1897,4158, 318,5779,2095,4159,4437,5780,5781, 485,5782, # 5312 + 938,3941, 553,2680, 116,5783,3942,3667,5784,3545,2681,2783,3438,3344,2820,5785, # 5328 +3668,2943,4160,1747,2944,2983,5786,5787, 207,5788,4809,5789,4810,2521,5790,3033, # 5344 + 890,3669,3943,5791,1878,3798,3439,5792,2186,2358,3440,1652,5793,5794,5795, 941, # 5360 +2299, 208,3546,4161,2020, 330,4438,3944,2906,2499,3799,4439,4811,5796,5797,5798, # 5376 +) + diff --git a/openpype/vendor/python/python_2/chardet/big5prober.py b/openpype/vendor/python/python_2/chardet/big5prober.py new file mode 100644 index 0000000000..98f9970122 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/big5prober.py @@ -0,0 +1,47 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import Big5DistributionAnalysis +from .mbcssm import BIG5_SM_MODEL + + +class Big5Prober(MultiByteCharSetProber): + def __init__(self): + super(Big5Prober, self).__init__() + self.coding_sm = CodingStateMachine(BIG5_SM_MODEL) + self.distribution_analyzer = Big5DistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "Big5" + + @property + def language(self): + return "Chinese" diff --git a/openpype/vendor/python/python_2/chardet/chardistribution.py b/openpype/vendor/python/python_2/chardet/chardistribution.py new file mode 100644 index 0000000000..c0395f4a45 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/chardistribution.py @@ -0,0 +1,233 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .euctwfreq import (EUCTW_CHAR_TO_FREQ_ORDER, EUCTW_TABLE_SIZE, + EUCTW_TYPICAL_DISTRIBUTION_RATIO) +from .euckrfreq import (EUCKR_CHAR_TO_FREQ_ORDER, EUCKR_TABLE_SIZE, + EUCKR_TYPICAL_DISTRIBUTION_RATIO) +from .gb2312freq import (GB2312_CHAR_TO_FREQ_ORDER, GB2312_TABLE_SIZE, + GB2312_TYPICAL_DISTRIBUTION_RATIO) +from .big5freq import (BIG5_CHAR_TO_FREQ_ORDER, BIG5_TABLE_SIZE, + BIG5_TYPICAL_DISTRIBUTION_RATIO) +from .jisfreq import (JIS_CHAR_TO_FREQ_ORDER, JIS_TABLE_SIZE, + JIS_TYPICAL_DISTRIBUTION_RATIO) + + +class CharDistributionAnalysis(object): + ENOUGH_DATA_THRESHOLD = 1024 + SURE_YES = 0.99 + SURE_NO = 0.01 + MINIMUM_DATA_THRESHOLD = 3 + + def __init__(self): + # Mapping table to get frequency order from char order (get from + # GetOrder()) + self._char_to_freq_order = None + self._table_size = None # Size of above table + # This is a constant value which varies from language to language, + # used in calculating confidence. See + # http://www.mozilla.org/projects/intl/UniversalCharsetDetection.html + # for further detail. + self.typical_distribution_ratio = None + self._done = None + self._total_chars = None + self._freq_chars = None + self.reset() + + def reset(self): + """reset analyser, clear any state""" + # If this flag is set to True, detection is done and conclusion has + # been made + self._done = False + self._total_chars = 0 # Total characters encountered + # The number of characters whose frequency order is less than 512 + self._freq_chars = 0 + + def feed(self, char, char_len): + """feed a character with known length""" + if char_len == 2: + # we only care about 2-bytes character in our distribution analysis + order = self.get_order(char) + else: + order = -1 + if order >= 0: + self._total_chars += 1 + # order is valid + if order < self._table_size: + if 512 > self._char_to_freq_order[order]: + self._freq_chars += 1 + + def get_confidence(self): + """return confidence based on existing data""" + # if we didn't receive any character in our consideration range, + # return negative answer + if self._total_chars <= 0 or self._freq_chars <= self.MINIMUM_DATA_THRESHOLD: + return self.SURE_NO + + if self._total_chars != self._freq_chars: + r = (self._freq_chars / ((self._total_chars - self._freq_chars) + * self.typical_distribution_ratio)) + if r < self.SURE_YES: + return r + + # normalize confidence (we don't want to be 100% sure) + return self.SURE_YES + + def got_enough_data(self): + # It is not necessary to receive all data to draw conclusion. + # For charset detection, certain amount of data is enough + return self._total_chars > self.ENOUGH_DATA_THRESHOLD + + def get_order(self, byte_str): + # We do not handle characters based on the original encoding string, + # but convert this encoding string to a number, here called order. + # This allows multiple encodings of a language to share one frequency + # table. + return -1 + + +class EUCTWDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(EUCTWDistributionAnalysis, self).__init__() + self._char_to_freq_order = EUCTW_CHAR_TO_FREQ_ORDER + self._table_size = EUCTW_TABLE_SIZE + self.typical_distribution_ratio = EUCTW_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str): + # for euc-TW encoding, we are interested + # first byte range: 0xc4 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char = byte_str[0] + if first_char >= 0xC4: + return 94 * (first_char - 0xC4) + byte_str[1] - 0xA1 + else: + return -1 + + +class EUCKRDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(EUCKRDistributionAnalysis, self).__init__() + self._char_to_freq_order = EUCKR_CHAR_TO_FREQ_ORDER + self._table_size = EUCKR_TABLE_SIZE + self.typical_distribution_ratio = EUCKR_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str): + # for euc-KR encoding, we are interested + # first byte range: 0xb0 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char = byte_str[0] + if first_char >= 0xB0: + return 94 * (first_char - 0xB0) + byte_str[1] - 0xA1 + else: + return -1 + + +class GB2312DistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(GB2312DistributionAnalysis, self).__init__() + self._char_to_freq_order = GB2312_CHAR_TO_FREQ_ORDER + self._table_size = GB2312_TABLE_SIZE + self.typical_distribution_ratio = GB2312_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str): + # for GB2312 encoding, we are interested + # first byte range: 0xb0 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char, second_char = byte_str[0], byte_str[1] + if (first_char >= 0xB0) and (second_char >= 0xA1): + return 94 * (first_char - 0xB0) + second_char - 0xA1 + else: + return -1 + + +class Big5DistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(Big5DistributionAnalysis, self).__init__() + self._char_to_freq_order = BIG5_CHAR_TO_FREQ_ORDER + self._table_size = BIG5_TABLE_SIZE + self.typical_distribution_ratio = BIG5_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str): + # for big5 encoding, we are interested + # first byte range: 0xa4 -- 0xfe + # second byte range: 0x40 -- 0x7e , 0xa1 -- 0xfe + # no validation needed here. State machine has done that + first_char, second_char = byte_str[0], byte_str[1] + if first_char >= 0xA4: + if second_char >= 0xA1: + return 157 * (first_char - 0xA4) + second_char - 0xA1 + 63 + else: + return 157 * (first_char - 0xA4) + second_char - 0x40 + else: + return -1 + + +class SJISDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(SJISDistributionAnalysis, self).__init__() + self._char_to_freq_order = JIS_CHAR_TO_FREQ_ORDER + self._table_size = JIS_TABLE_SIZE + self.typical_distribution_ratio = JIS_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str): + # for sjis encoding, we are interested + # first byte range: 0x81 -- 0x9f , 0xe0 -- 0xfe + # second byte range: 0x40 -- 0x7e, 0x81 -- oxfe + # no validation needed here. State machine has done that + first_char, second_char = byte_str[0], byte_str[1] + if (first_char >= 0x81) and (first_char <= 0x9F): + order = 188 * (first_char - 0x81) + elif (first_char >= 0xE0) and (first_char <= 0xEF): + order = 188 * (first_char - 0xE0 + 31) + else: + return -1 + order = order + second_char - 0x40 + if second_char > 0x7F: + order = -1 + return order + + +class EUCJPDistributionAnalysis(CharDistributionAnalysis): + def __init__(self): + super(EUCJPDistributionAnalysis, self).__init__() + self._char_to_freq_order = JIS_CHAR_TO_FREQ_ORDER + self._table_size = JIS_TABLE_SIZE + self.typical_distribution_ratio = JIS_TYPICAL_DISTRIBUTION_RATIO + + def get_order(self, byte_str): + # for euc-JP encoding, we are interested + # first byte range: 0xa0 -- 0xfe + # second byte range: 0xa1 -- 0xfe + # no validation needed here. State machine has done that + char = byte_str[0] + if char >= 0xA0: + return 94 * (char - 0xA1) + byte_str[1] - 0xa1 + else: + return -1 diff --git a/openpype/vendor/python/python_2/chardet/charsetgroupprober.py b/openpype/vendor/python/python_2/chardet/charsetgroupprober.py new file mode 100644 index 0000000000..5812cef0b5 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/charsetgroupprober.py @@ -0,0 +1,107 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .enums import ProbingState +from .charsetprober import CharSetProber + + +class CharSetGroupProber(CharSetProber): + def __init__(self, lang_filter=None): + super(CharSetGroupProber, self).__init__(lang_filter=lang_filter) + self._active_num = 0 + self.probers = [] + self._best_guess_prober = None + + def reset(self): + super(CharSetGroupProber, self).reset() + self._active_num = 0 + for prober in self.probers: + if prober: + prober.reset() + prober.active = True + self._active_num += 1 + self._best_guess_prober = None + + @property + def charset_name(self): + if not self._best_guess_prober: + self.get_confidence() + if not self._best_guess_prober: + return None + return self._best_guess_prober.charset_name + + @property + def language(self): + if not self._best_guess_prober: + self.get_confidence() + if not self._best_guess_prober: + return None + return self._best_guess_prober.language + + def feed(self, byte_str): + for prober in self.probers: + if not prober: + continue + if not prober.active: + continue + state = prober.feed(byte_str) + if not state: + continue + if state == ProbingState.FOUND_IT: + self._best_guess_prober = prober + self._state = ProbingState.FOUND_IT + return self.state + elif state == ProbingState.NOT_ME: + prober.active = False + self._active_num -= 1 + if self._active_num <= 0: + self._state = ProbingState.NOT_ME + return self.state + return self.state + + def get_confidence(self): + state = self.state + if state == ProbingState.FOUND_IT: + return 0.99 + elif state == ProbingState.NOT_ME: + return 0.01 + best_conf = 0.0 + self._best_guess_prober = None + for prober in self.probers: + if not prober: + continue + if not prober.active: + self.logger.debug('%s not active', prober.charset_name) + continue + conf = prober.get_confidence() + self.logger.debug('%s %s confidence = %s', prober.charset_name, prober.language, conf) + if best_conf < conf: + best_conf = conf + self._best_guess_prober = prober + if not self._best_guess_prober: + return 0.0 + return best_conf diff --git a/openpype/vendor/python/python_2/chardet/charsetprober.py b/openpype/vendor/python/python_2/chardet/charsetprober.py new file mode 100644 index 0000000000..eac4e59865 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/charsetprober.py @@ -0,0 +1,145 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +import logging +import re + +from .enums import ProbingState + + +class CharSetProber(object): + + SHORTCUT_THRESHOLD = 0.95 + + def __init__(self, lang_filter=None): + self._state = None + self.lang_filter = lang_filter + self.logger = logging.getLogger(__name__) + + def reset(self): + self._state = ProbingState.DETECTING + + @property + def charset_name(self): + return None + + def feed(self, buf): + pass + + @property + def state(self): + return self._state + + def get_confidence(self): + return 0.0 + + @staticmethod + def filter_high_byte_only(buf): + buf = re.sub(b'([\x00-\x7F])+', b' ', buf) + return buf + + @staticmethod + def filter_international_words(buf): + """ + We define three types of bytes: + alphabet: english alphabets [a-zA-Z] + international: international characters [\x80-\xFF] + marker: everything else [^a-zA-Z\x80-\xFF] + + The input buffer can be thought to contain a series of words delimited + by markers. This function works to filter all words that contain at + least one international character. All contiguous sequences of markers + are replaced by a single space ascii character. + + This filter applies to all scripts which do not use English characters. + """ + filtered = bytearray() + + # This regex expression filters out only words that have at-least one + # international character. The word may include one marker character at + # the end. + words = re.findall(b'[a-zA-Z]*[\x80-\xFF]+[a-zA-Z]*[^a-zA-Z\x80-\xFF]?', + buf) + + for word in words: + filtered.extend(word[:-1]) + + # If the last character in the word is a marker, replace it with a + # space as markers shouldn't affect our analysis (they are used + # similarly across all languages and may thus have similar + # frequencies). + last_char = word[-1:] + if not last_char.isalpha() and last_char < b'\x80': + last_char = b' ' + filtered.extend(last_char) + + return filtered + + @staticmethod + def filter_with_english_letters(buf): + """ + Returns a copy of ``buf`` that retains only the sequences of English + alphabet and high byte characters that are not between <> characters. + Also retains English alphabet and high byte characters immediately + before occurrences of >. + + This filter can be applied to all scripts which contain both English + characters and extended ASCII characters, but is currently only used by + ``Latin1Prober``. + """ + filtered = bytearray() + in_tag = False + prev = 0 + + for curr in range(len(buf)): + # Slice here to get bytes instead of an int with Python 3 + buf_char = buf[curr:curr + 1] + # Check if we're coming out of or entering an HTML tag + if buf_char == b'>': + in_tag = False + elif buf_char == b'<': + in_tag = True + + # If current character is not extended-ASCII and not alphabetic... + if buf_char < b'\x80' and not buf_char.isalpha(): + # ...and we're not in a tag + if curr > prev and not in_tag: + # Keep everything after last non-extended-ASCII, + # non-alphabetic character + filtered.extend(buf[prev:curr]) + # Output a space to delimit stretch we kept + filtered.extend(b' ') + prev = curr + 1 + + # If we're not in a tag... + if not in_tag: + # Keep everything after last non-extended-ASCII, non-alphabetic + # character + filtered.extend(buf[prev:]) + + return filtered diff --git a/openpype/vendor/python/python_2/chardet/cli/__init__.py b/openpype/vendor/python/python_2/chardet/cli/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/cli/__init__.py @@ -0,0 +1 @@ + diff --git a/openpype/vendor/python/python_2/chardet/cli/chardetect.py b/openpype/vendor/python/python_2/chardet/cli/chardetect.py new file mode 100644 index 0000000000..e1d8cd69ac --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/cli/chardetect.py @@ -0,0 +1,84 @@ +""" +Script which takes one or more file paths and reports on their detected +encodings + +Example:: + + % chardetect somefile someotherfile + somefile: windows-1252 with confidence 0.5 + someotherfile: ascii with confidence 1.0 + +If no paths are provided, it takes its input from stdin. + +""" + +from __future__ import absolute_import, print_function, unicode_literals + +import argparse +import sys + +from chardet import __version__ +from chardet.compat import PY2 +from chardet.universaldetector import UniversalDetector + + +def description_of(lines, name='stdin'): + """ + Return a string describing the probable encoding of a file or + list of strings. + + :param lines: The lines to get the encoding of. + :type lines: Iterable of bytes + :param name: Name of file or collection of lines + :type name: str + """ + u = UniversalDetector() + for line in lines: + line = bytearray(line) + u.feed(line) + # shortcut out of the loop to save reading further - particularly useful if we read a BOM. + if u.done: + break + u.close() + result = u.result + if PY2: + name = name.decode(sys.getfilesystemencoding(), 'ignore') + if result['encoding']: + return '{}: {} with confidence {}'.format(name, result['encoding'], + result['confidence']) + else: + return '{}: no result'.format(name) + + +def main(argv=None): + """ + Handles command line arguments and gets things started. + + :param argv: List of arguments, as if specified on the command-line. + If None, ``sys.argv[1:]`` is used instead. + :type argv: list of str + """ + # Get command line arguments + parser = argparse.ArgumentParser( + description="Takes one or more file paths and reports their detected \ + encodings") + parser.add_argument('input', + help='File whose encoding we would like to determine. \ + (default: stdin)', + type=argparse.FileType('rb'), nargs='*', + default=[sys.stdin if PY2 else sys.stdin.buffer]) + parser.add_argument('--version', action='version', + version='%(prog)s {}'.format(__version__)) + args = parser.parse_args(argv) + + for f in args.input: + if f.isatty(): + print("You are running chardetect interactively. Press " + + "CTRL-D twice at the start of a blank line to signal the " + + "end of your input. If you want help, run chardetect " + + "--help\n", file=sys.stderr) + print(description_of(f, f.name)) + + +if __name__ == '__main__': + main() diff --git a/openpype/vendor/python/python_2/chardet/codingstatemachine.py b/openpype/vendor/python/python_2/chardet/codingstatemachine.py new file mode 100644 index 0000000000..68fba44f14 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/codingstatemachine.py @@ -0,0 +1,88 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +import logging + +from .enums import MachineState + + +class CodingStateMachine(object): + """ + A state machine to verify a byte sequence for a particular encoding. For + each byte the detector receives, it will feed that byte to every active + state machine available, one byte at a time. The state machine changes its + state based on its previous state and the byte it receives. There are 3 + states in a state machine that are of interest to an auto-detector: + + START state: This is the state to start with, or a legal byte sequence + (i.e. a valid code point) for character has been identified. + + ME state: This indicates that the state machine identified a byte sequence + that is specific to the charset it is designed for and that + there is no other possible encoding which can contain this byte + sequence. This will to lead to an immediate positive answer for + the detector. + + ERROR state: This indicates the state machine identified an illegal byte + sequence for that encoding. This will lead to an immediate + negative answer for this encoding. Detector will exclude this + encoding from consideration from here on. + """ + def __init__(self, sm): + self._model = sm + self._curr_byte_pos = 0 + self._curr_char_len = 0 + self._curr_state = None + self.logger = logging.getLogger(__name__) + self.reset() + + def reset(self): + self._curr_state = MachineState.START + + def next_state(self, c): + # for each byte we get its class + # if it is first byte, we also get byte length + byte_class = self._model['class_table'][c] + if self._curr_state == MachineState.START: + self._curr_byte_pos = 0 + self._curr_char_len = self._model['char_len_table'][byte_class] + # from byte's class and state_table, we get its next state + curr_state = (self._curr_state * self._model['class_factor'] + + byte_class) + self._curr_state = self._model['state_table'][curr_state] + self._curr_byte_pos += 1 + return self._curr_state + + def get_current_charlen(self): + return self._curr_char_len + + def get_coding_state_machine(self): + return self._model['name'] + + @property + def language(self): + return self._model['language'] diff --git a/openpype/vendor/python/python_2/chardet/compat.py b/openpype/vendor/python/python_2/chardet/compat.py new file mode 100644 index 0000000000..8941572b3e --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/compat.py @@ -0,0 +1,36 @@ +######################## BEGIN LICENSE BLOCK ######################## +# Contributor(s): +# Dan Blanchard +# Ian Cordasco +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +import sys + + +if sys.version_info < (3, 0): + PY2 = True + PY3 = False + string_types = (str, unicode) + text_type = unicode + iteritems = dict.iteritems +else: + PY2 = False + PY3 = True + string_types = (bytes, str) + text_type = str + iteritems = dict.items diff --git a/openpype/vendor/python/python_2/chardet/cp949prober.py b/openpype/vendor/python/python_2/chardet/cp949prober.py new file mode 100644 index 0000000000..efd793abca --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/cp949prober.py @@ -0,0 +1,49 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .chardistribution import EUCKRDistributionAnalysis +from .codingstatemachine import CodingStateMachine +from .mbcharsetprober import MultiByteCharSetProber +from .mbcssm import CP949_SM_MODEL + + +class CP949Prober(MultiByteCharSetProber): + def __init__(self): + super(CP949Prober, self).__init__() + self.coding_sm = CodingStateMachine(CP949_SM_MODEL) + # NOTE: CP949 is a superset of EUC-KR, so the distribution should be + # not different. + self.distribution_analyzer = EUCKRDistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "CP949" + + @property + def language(self): + return "Korean" diff --git a/openpype/vendor/python/python_2/chardet/enums.py b/openpype/vendor/python/python_2/chardet/enums.py new file mode 100644 index 0000000000..0451207225 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/enums.py @@ -0,0 +1,76 @@ +""" +All of the Enums that are used throughout the chardet package. + +:author: Dan Blanchard (dan.blanchard@gmail.com) +""" + + +class InputState(object): + """ + This enum represents the different states a universal detector can be in. + """ + PURE_ASCII = 0 + ESC_ASCII = 1 + HIGH_BYTE = 2 + + +class LanguageFilter(object): + """ + This enum represents the different language filters we can apply to a + ``UniversalDetector``. + """ + CHINESE_SIMPLIFIED = 0x01 + CHINESE_TRADITIONAL = 0x02 + JAPANESE = 0x04 + KOREAN = 0x08 + NON_CJK = 0x10 + ALL = 0x1F + CHINESE = CHINESE_SIMPLIFIED | CHINESE_TRADITIONAL + CJK = CHINESE | JAPANESE | KOREAN + + +class ProbingState(object): + """ + This enum represents the different states a prober can be in. + """ + DETECTING = 0 + FOUND_IT = 1 + NOT_ME = 2 + + +class MachineState(object): + """ + This enum represents the different states a state machine can be in. + """ + START = 0 + ERROR = 1 + ITS_ME = 2 + + +class SequenceLikelihood(object): + """ + This enum represents the likelihood of a character following the previous one. + """ + NEGATIVE = 0 + UNLIKELY = 1 + LIKELY = 2 + POSITIVE = 3 + + @classmethod + def get_num_categories(cls): + """:returns: The number of likelihood categories in the enum.""" + return 4 + + +class CharacterCategory(object): + """ + This enum represents the different categories language models for + ``SingleByteCharsetProber`` put characters into. + + Anything less than CONTROL is considered a letter. + """ + UNDEFINED = 255 + LINE_BREAK = 254 + SYMBOL = 253 + DIGIT = 252 + CONTROL = 251 diff --git a/openpype/vendor/python/python_2/chardet/escprober.py b/openpype/vendor/python/python_2/chardet/escprober.py new file mode 100644 index 0000000000..c70493f2b1 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/escprober.py @@ -0,0 +1,101 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetprober import CharSetProber +from .codingstatemachine import CodingStateMachine +from .enums import LanguageFilter, ProbingState, MachineState +from .escsm import (HZ_SM_MODEL, ISO2022CN_SM_MODEL, ISO2022JP_SM_MODEL, + ISO2022KR_SM_MODEL) + + +class EscCharSetProber(CharSetProber): + """ + This CharSetProber uses a "code scheme" approach for detecting encodings, + whereby easily recognizable escape or shift sequences are relied on to + identify these encodings. + """ + + def __init__(self, lang_filter=None): + super(EscCharSetProber, self).__init__(lang_filter=lang_filter) + self.coding_sm = [] + if self.lang_filter & LanguageFilter.CHINESE_SIMPLIFIED: + self.coding_sm.append(CodingStateMachine(HZ_SM_MODEL)) + self.coding_sm.append(CodingStateMachine(ISO2022CN_SM_MODEL)) + if self.lang_filter & LanguageFilter.JAPANESE: + self.coding_sm.append(CodingStateMachine(ISO2022JP_SM_MODEL)) + if self.lang_filter & LanguageFilter.KOREAN: + self.coding_sm.append(CodingStateMachine(ISO2022KR_SM_MODEL)) + self.active_sm_count = None + self._detected_charset = None + self._detected_language = None + self._state = None + self.reset() + + def reset(self): + super(EscCharSetProber, self).reset() + for coding_sm in self.coding_sm: + if not coding_sm: + continue + coding_sm.active = True + coding_sm.reset() + self.active_sm_count = len(self.coding_sm) + self._detected_charset = None + self._detected_language = None + + @property + def charset_name(self): + return self._detected_charset + + @property + def language(self): + return self._detected_language + + def get_confidence(self): + if self._detected_charset: + return 0.99 + else: + return 0.00 + + def feed(self, byte_str): + for c in byte_str: + for coding_sm in self.coding_sm: + if not coding_sm or not coding_sm.active: + continue + coding_state = coding_sm.next_state(c) + if coding_state == MachineState.ERROR: + coding_sm.active = False + self.active_sm_count -= 1 + if self.active_sm_count <= 0: + self._state = ProbingState.NOT_ME + return self.state + elif coding_state == MachineState.ITS_ME: + self._state = ProbingState.FOUND_IT + self._detected_charset = coding_sm.get_coding_state_machine() + self._detected_language = coding_sm.language + return self.state + + return self.state diff --git a/openpype/vendor/python/python_2/chardet/escsm.py b/openpype/vendor/python/python_2/chardet/escsm.py new file mode 100644 index 0000000000..0069523a04 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/escsm.py @@ -0,0 +1,246 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .enums import MachineState + +HZ_CLS = ( +1,0,0,0,0,0,0,0, # 00 - 07 +0,0,0,0,0,0,0,0, # 08 - 0f +0,0,0,0,0,0,0,0, # 10 - 17 +0,0,0,1,0,0,0,0, # 18 - 1f +0,0,0,0,0,0,0,0, # 20 - 27 +0,0,0,0,0,0,0,0, # 28 - 2f +0,0,0,0,0,0,0,0, # 30 - 37 +0,0,0,0,0,0,0,0, # 38 - 3f +0,0,0,0,0,0,0,0, # 40 - 47 +0,0,0,0,0,0,0,0, # 48 - 4f +0,0,0,0,0,0,0,0, # 50 - 57 +0,0,0,0,0,0,0,0, # 58 - 5f +0,0,0,0,0,0,0,0, # 60 - 67 +0,0,0,0,0,0,0,0, # 68 - 6f +0,0,0,0,0,0,0,0, # 70 - 77 +0,0,0,4,0,5,2,0, # 78 - 7f +1,1,1,1,1,1,1,1, # 80 - 87 +1,1,1,1,1,1,1,1, # 88 - 8f +1,1,1,1,1,1,1,1, # 90 - 97 +1,1,1,1,1,1,1,1, # 98 - 9f +1,1,1,1,1,1,1,1, # a0 - a7 +1,1,1,1,1,1,1,1, # a8 - af +1,1,1,1,1,1,1,1, # b0 - b7 +1,1,1,1,1,1,1,1, # b8 - bf +1,1,1,1,1,1,1,1, # c0 - c7 +1,1,1,1,1,1,1,1, # c8 - cf +1,1,1,1,1,1,1,1, # d0 - d7 +1,1,1,1,1,1,1,1, # d8 - df +1,1,1,1,1,1,1,1, # e0 - e7 +1,1,1,1,1,1,1,1, # e8 - ef +1,1,1,1,1,1,1,1, # f0 - f7 +1,1,1,1,1,1,1,1, # f8 - ff +) + +HZ_ST = ( +MachineState.START,MachineState.ERROR, 3,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,# 00-07 +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 08-0f +MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START, 4,MachineState.ERROR,# 10-17 + 5,MachineState.ERROR, 6,MachineState.ERROR, 5, 5, 4,MachineState.ERROR,# 18-1f + 4,MachineState.ERROR, 4, 4, 4,MachineState.ERROR, 4,MachineState.ERROR,# 20-27 + 4,MachineState.ITS_ME,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 28-2f +) + +HZ_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0) + +HZ_SM_MODEL = {'class_table': HZ_CLS, + 'class_factor': 6, + 'state_table': HZ_ST, + 'char_len_table': HZ_CHAR_LEN_TABLE, + 'name': "HZ-GB-2312", + 'language': 'Chinese'} + +ISO2022CN_CLS = ( +2,0,0,0,0,0,0,0, # 00 - 07 +0,0,0,0,0,0,0,0, # 08 - 0f +0,0,0,0,0,0,0,0, # 10 - 17 +0,0,0,1,0,0,0,0, # 18 - 1f +0,0,0,0,0,0,0,0, # 20 - 27 +0,3,0,0,0,0,0,0, # 28 - 2f +0,0,0,0,0,0,0,0, # 30 - 37 +0,0,0,0,0,0,0,0, # 38 - 3f +0,0,0,4,0,0,0,0, # 40 - 47 +0,0,0,0,0,0,0,0, # 48 - 4f +0,0,0,0,0,0,0,0, # 50 - 57 +0,0,0,0,0,0,0,0, # 58 - 5f +0,0,0,0,0,0,0,0, # 60 - 67 +0,0,0,0,0,0,0,0, # 68 - 6f +0,0,0,0,0,0,0,0, # 70 - 77 +0,0,0,0,0,0,0,0, # 78 - 7f +2,2,2,2,2,2,2,2, # 80 - 87 +2,2,2,2,2,2,2,2, # 88 - 8f +2,2,2,2,2,2,2,2, # 90 - 97 +2,2,2,2,2,2,2,2, # 98 - 9f +2,2,2,2,2,2,2,2, # a0 - a7 +2,2,2,2,2,2,2,2, # a8 - af +2,2,2,2,2,2,2,2, # b0 - b7 +2,2,2,2,2,2,2,2, # b8 - bf +2,2,2,2,2,2,2,2, # c0 - c7 +2,2,2,2,2,2,2,2, # c8 - cf +2,2,2,2,2,2,2,2, # d0 - d7 +2,2,2,2,2,2,2,2, # d8 - df +2,2,2,2,2,2,2,2, # e0 - e7 +2,2,2,2,2,2,2,2, # e8 - ef +2,2,2,2,2,2,2,2, # f0 - f7 +2,2,2,2,2,2,2,2, # f8 - ff +) + +ISO2022CN_ST = ( +MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 00-07 +MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 08-0f +MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 10-17 +MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 4,MachineState.ERROR,# 18-1f +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 20-27 + 5, 6,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 28-2f +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 30-37 +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.START,# 38-3f +) + +ISO2022CN_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0, 0, 0, 0) + +ISO2022CN_SM_MODEL = {'class_table': ISO2022CN_CLS, + 'class_factor': 9, + 'state_table': ISO2022CN_ST, + 'char_len_table': ISO2022CN_CHAR_LEN_TABLE, + 'name': "ISO-2022-CN", + 'language': 'Chinese'} + +ISO2022JP_CLS = ( +2,0,0,0,0,0,0,0, # 00 - 07 +0,0,0,0,0,0,2,2, # 08 - 0f +0,0,0,0,0,0,0,0, # 10 - 17 +0,0,0,1,0,0,0,0, # 18 - 1f +0,0,0,0,7,0,0,0, # 20 - 27 +3,0,0,0,0,0,0,0, # 28 - 2f +0,0,0,0,0,0,0,0, # 30 - 37 +0,0,0,0,0,0,0,0, # 38 - 3f +6,0,4,0,8,0,0,0, # 40 - 47 +0,9,5,0,0,0,0,0, # 48 - 4f +0,0,0,0,0,0,0,0, # 50 - 57 +0,0,0,0,0,0,0,0, # 58 - 5f +0,0,0,0,0,0,0,0, # 60 - 67 +0,0,0,0,0,0,0,0, # 68 - 6f +0,0,0,0,0,0,0,0, # 70 - 77 +0,0,0,0,0,0,0,0, # 78 - 7f +2,2,2,2,2,2,2,2, # 80 - 87 +2,2,2,2,2,2,2,2, # 88 - 8f +2,2,2,2,2,2,2,2, # 90 - 97 +2,2,2,2,2,2,2,2, # 98 - 9f +2,2,2,2,2,2,2,2, # a0 - a7 +2,2,2,2,2,2,2,2, # a8 - af +2,2,2,2,2,2,2,2, # b0 - b7 +2,2,2,2,2,2,2,2, # b8 - bf +2,2,2,2,2,2,2,2, # c0 - c7 +2,2,2,2,2,2,2,2, # c8 - cf +2,2,2,2,2,2,2,2, # d0 - d7 +2,2,2,2,2,2,2,2, # d8 - df +2,2,2,2,2,2,2,2, # e0 - e7 +2,2,2,2,2,2,2,2, # e8 - ef +2,2,2,2,2,2,2,2, # f0 - f7 +2,2,2,2,2,2,2,2, # f8 - ff +) + +ISO2022JP_ST = ( +MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 00-07 +MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 08-0f +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 10-17 +MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,# 18-1f +MachineState.ERROR, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 4,MachineState.ERROR,MachineState.ERROR,# 20-27 +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 6,MachineState.ITS_ME,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,# 28-2f +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,# 30-37 +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 38-3f +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.START,MachineState.START,# 40-47 +) + +ISO2022JP_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + +ISO2022JP_SM_MODEL = {'class_table': ISO2022JP_CLS, + 'class_factor': 10, + 'state_table': ISO2022JP_ST, + 'char_len_table': ISO2022JP_CHAR_LEN_TABLE, + 'name': "ISO-2022-JP", + 'language': 'Japanese'} + +ISO2022KR_CLS = ( +2,0,0,0,0,0,0,0, # 00 - 07 +0,0,0,0,0,0,0,0, # 08 - 0f +0,0,0,0,0,0,0,0, # 10 - 17 +0,0,0,1,0,0,0,0, # 18 - 1f +0,0,0,0,3,0,0,0, # 20 - 27 +0,4,0,0,0,0,0,0, # 28 - 2f +0,0,0,0,0,0,0,0, # 30 - 37 +0,0,0,0,0,0,0,0, # 38 - 3f +0,0,0,5,0,0,0,0, # 40 - 47 +0,0,0,0,0,0,0,0, # 48 - 4f +0,0,0,0,0,0,0,0, # 50 - 57 +0,0,0,0,0,0,0,0, # 58 - 5f +0,0,0,0,0,0,0,0, # 60 - 67 +0,0,0,0,0,0,0,0, # 68 - 6f +0,0,0,0,0,0,0,0, # 70 - 77 +0,0,0,0,0,0,0,0, # 78 - 7f +2,2,2,2,2,2,2,2, # 80 - 87 +2,2,2,2,2,2,2,2, # 88 - 8f +2,2,2,2,2,2,2,2, # 90 - 97 +2,2,2,2,2,2,2,2, # 98 - 9f +2,2,2,2,2,2,2,2, # a0 - a7 +2,2,2,2,2,2,2,2, # a8 - af +2,2,2,2,2,2,2,2, # b0 - b7 +2,2,2,2,2,2,2,2, # b8 - bf +2,2,2,2,2,2,2,2, # c0 - c7 +2,2,2,2,2,2,2,2, # c8 - cf +2,2,2,2,2,2,2,2, # d0 - d7 +2,2,2,2,2,2,2,2, # d8 - df +2,2,2,2,2,2,2,2, # e0 - e7 +2,2,2,2,2,2,2,2, # e8 - ef +2,2,2,2,2,2,2,2, # f0 - f7 +2,2,2,2,2,2,2,2, # f8 - ff +) + +ISO2022KR_ST = ( +MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,# 00-07 +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 08-0f +MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 4,MachineState.ERROR,MachineState.ERROR,# 10-17 +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 18-1f +MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 20-27 +) + +ISO2022KR_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0) + +ISO2022KR_SM_MODEL = {'class_table': ISO2022KR_CLS, + 'class_factor': 6, + 'state_table': ISO2022KR_ST, + 'char_len_table': ISO2022KR_CHAR_LEN_TABLE, + 'name': "ISO-2022-KR", + 'language': 'Korean'} + + diff --git a/openpype/vendor/python/python_2/chardet/eucjpprober.py b/openpype/vendor/python/python_2/chardet/eucjpprober.py new file mode 100644 index 0000000000..20ce8f7d15 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/eucjpprober.py @@ -0,0 +1,92 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .enums import ProbingState, MachineState +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import EUCJPDistributionAnalysis +from .jpcntx import EUCJPContextAnalysis +from .mbcssm import EUCJP_SM_MODEL + + +class EUCJPProber(MultiByteCharSetProber): + def __init__(self): + super(EUCJPProber, self).__init__() + self.coding_sm = CodingStateMachine(EUCJP_SM_MODEL) + self.distribution_analyzer = EUCJPDistributionAnalysis() + self.context_analyzer = EUCJPContextAnalysis() + self.reset() + + def reset(self): + super(EUCJPProber, self).reset() + self.context_analyzer.reset() + + @property + def charset_name(self): + return "EUC-JP" + + @property + def language(self): + return "Japanese" + + def feed(self, byte_str): + for i in range(len(byte_str)): + # PY3K: byte_str is a byte array, so byte_str[i] is an int, not a byte + coding_state = self.coding_sm.next_state(byte_str[i]) + if coding_state == MachineState.ERROR: + self.logger.debug('%s %s prober hit error at byte %s', + self.charset_name, self.language, i) + self._state = ProbingState.NOT_ME + break + elif coding_state == MachineState.ITS_ME: + self._state = ProbingState.FOUND_IT + break + elif coding_state == MachineState.START: + char_len = self.coding_sm.get_current_charlen() + if i == 0: + self._last_char[1] = byte_str[0] + self.context_analyzer.feed(self._last_char, char_len) + self.distribution_analyzer.feed(self._last_char, char_len) + else: + self.context_analyzer.feed(byte_str[i - 1:i + 1], + char_len) + self.distribution_analyzer.feed(byte_str[i - 1:i + 1], + char_len) + + self._last_char[0] = byte_str[-1] + + if self.state == ProbingState.DETECTING: + if (self.context_analyzer.got_enough_data() and + (self.get_confidence() > self.SHORTCUT_THRESHOLD)): + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self): + context_conf = self.context_analyzer.get_confidence() + distrib_conf = self.distribution_analyzer.get_confidence() + return max(context_conf, distrib_conf) diff --git a/openpype/vendor/python/python_2/chardet/euckrfreq.py b/openpype/vendor/python/python_2/chardet/euckrfreq.py new file mode 100644 index 0000000000..b68078cb96 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/euckrfreq.py @@ -0,0 +1,195 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# Sampling from about 20M text materials include literature and computer technology + +# 128 --> 0.79 +# 256 --> 0.92 +# 512 --> 0.986 +# 1024 --> 0.99944 +# 2048 --> 0.99999 +# +# Idea Distribution Ratio = 0.98653 / (1-0.98653) = 73.24 +# Random Distribution Ration = 512 / (2350-512) = 0.279. +# +# Typical Distribution Ratio + +EUCKR_TYPICAL_DISTRIBUTION_RATIO = 6.0 + +EUCKR_TABLE_SIZE = 2352 + +# Char to FreqOrder table , +EUCKR_CHAR_TO_FREQ_ORDER = ( + 13, 130, 120,1396, 481,1719,1720, 328, 609, 212,1721, 707, 400, 299,1722, 87, +1397,1723, 104, 536,1117,1203,1724,1267, 685,1268, 508,1725,1726,1727,1728,1398, +1399,1729,1730,1731, 141, 621, 326,1057, 368,1732, 267, 488, 20,1733,1269,1734, + 945,1400,1735, 47, 904,1270,1736,1737, 773, 248,1738, 409, 313, 786, 429,1739, + 116, 987, 813,1401, 683, 75,1204, 145,1740,1741,1742,1743, 16, 847, 667, 622, + 708,1744,1745,1746, 966, 787, 304, 129,1747, 60, 820, 123, 676,1748,1749,1750, +1751, 617,1752, 626,1753,1754,1755,1756, 653,1757,1758,1759,1760,1761,1762, 856, + 344,1763,1764,1765,1766, 89, 401, 418, 806, 905, 848,1767,1768,1769, 946,1205, + 709,1770,1118,1771, 241,1772,1773,1774,1271,1775, 569,1776, 999,1777,1778,1779, +1780, 337, 751,1058, 28, 628, 254,1781, 177, 906, 270, 349, 891,1079,1782, 19, +1783, 379,1784, 315,1785, 629, 754,1402, 559,1786, 636, 203,1206,1787, 710, 567, +1788, 935, 814,1789,1790,1207, 766, 528,1791,1792,1208,1793,1794,1795,1796,1797, +1403,1798,1799, 533,1059,1404,1405,1156,1406, 936, 884,1080,1800, 351,1801,1802, +1803,1804,1805, 801,1806,1807,1808,1119,1809,1157, 714, 474,1407,1810, 298, 899, + 885,1811,1120, 802,1158,1812, 892,1813,1814,1408, 659,1815,1816,1121,1817,1818, +1819,1820,1821,1822, 319,1823, 594, 545,1824, 815, 937,1209,1825,1826, 573,1409, +1022,1827,1210,1828,1829,1830,1831,1832,1833, 556, 722, 807,1122,1060,1834, 697, +1835, 900, 557, 715,1836,1410, 540,1411, 752,1159, 294, 597,1211, 976, 803, 770, +1412,1837,1838, 39, 794,1413, 358,1839, 371, 925,1840, 453, 661, 788, 531, 723, + 544,1023,1081, 869, 91,1841, 392, 430, 790, 602,1414, 677,1082, 457,1415,1416, +1842,1843, 475, 327,1024,1417, 795, 121,1844, 733, 403,1418,1845,1846,1847, 300, + 119, 711,1212, 627,1848,1272, 207,1849,1850, 796,1213, 382,1851, 519,1852,1083, + 893,1853,1854,1855, 367, 809, 487, 671,1856, 663,1857,1858, 956, 471, 306, 857, +1859,1860,1160,1084,1861,1862,1863,1864,1865,1061,1866,1867,1868,1869,1870,1871, + 282, 96, 574,1872, 502,1085,1873,1214,1874, 907,1875,1876, 827, 977,1419,1420, +1421, 268,1877,1422,1878,1879,1880, 308,1881, 2, 537,1882,1883,1215,1884,1885, + 127, 791,1886,1273,1423,1887, 34, 336, 404, 643,1888, 571, 654, 894, 840,1889, + 0, 886,1274, 122, 575, 260, 908, 938,1890,1275, 410, 316,1891,1892, 100,1893, +1894,1123, 48,1161,1124,1025,1895, 633, 901,1276,1896,1897, 115, 816,1898, 317, +1899, 694,1900, 909, 734,1424, 572, 866,1425, 691, 85, 524,1010, 543, 394, 841, +1901,1902,1903,1026,1904,1905,1906,1907,1908,1909, 30, 451, 651, 988, 310,1910, +1911,1426, 810,1216, 93,1912,1913,1277,1217,1914, 858, 759, 45, 58, 181, 610, + 269,1915,1916, 131,1062, 551, 443,1000, 821,1427, 957, 895,1086,1917,1918, 375, +1919, 359,1920, 687,1921, 822,1922, 293,1923,1924, 40, 662, 118, 692, 29, 939, + 887, 640, 482, 174,1925, 69,1162, 728,1428, 910,1926,1278,1218,1279, 386, 870, + 217, 854,1163, 823,1927,1928,1929,1930, 834,1931, 78,1932, 859,1933,1063,1934, +1935,1936,1937, 438,1164, 208, 595,1938,1939,1940,1941,1219,1125,1942, 280, 888, +1429,1430,1220,1431,1943,1944,1945,1946,1947,1280, 150, 510,1432,1948,1949,1950, +1951,1952,1953,1954,1011,1087,1955,1433,1043,1956, 881,1957, 614, 958,1064,1065, +1221,1958, 638,1001, 860, 967, 896,1434, 989, 492, 553,1281,1165,1959,1282,1002, +1283,1222,1960,1961,1962,1963, 36, 383, 228, 753, 247, 454,1964, 876, 678,1965, +1966,1284, 126, 464, 490, 835, 136, 672, 529, 940,1088,1435, 473,1967,1968, 467, + 50, 390, 227, 587, 279, 378, 598, 792, 968, 240, 151, 160, 849, 882,1126,1285, + 639,1044, 133, 140, 288, 360, 811, 563,1027, 561, 142, 523,1969,1970,1971, 7, + 103, 296, 439, 407, 506, 634, 990,1972,1973,1974,1975, 645,1976,1977,1978,1979, +1980,1981, 236,1982,1436,1983,1984,1089, 192, 828, 618, 518,1166, 333,1127,1985, + 818,1223,1986,1987,1988,1989,1990,1991,1992,1993, 342,1128,1286, 746, 842,1994, +1995, 560, 223,1287, 98, 8, 189, 650, 978,1288,1996,1437,1997, 17, 345, 250, + 423, 277, 234, 512, 226, 97, 289, 42, 167,1998, 201,1999,2000, 843, 836, 824, + 532, 338, 783,1090, 182, 576, 436,1438,1439, 527, 500,2001, 947, 889,2002,2003, +2004,2005, 262, 600, 314, 447,2006, 547,2007, 693, 738,1129,2008, 71,1440, 745, + 619, 688,2009, 829,2010,2011, 147,2012, 33, 948,2013,2014, 74, 224,2015, 61, + 191, 918, 399, 637,2016,1028,1130, 257, 902,2017,2018,2019,2020,2021,2022,2023, +2024,2025,2026, 837,2027,2028,2029,2030, 179, 874, 591, 52, 724, 246,2031,2032, +2033,2034,1167, 969,2035,1289, 630, 605, 911,1091,1168,2036,2037,2038,1441, 912, +2039, 623,2040,2041, 253,1169,1290,2042,1442, 146, 620, 611, 577, 433,2043,1224, + 719,1170, 959, 440, 437, 534, 84, 388, 480,1131, 159, 220, 198, 679,2044,1012, + 819,1066,1443, 113,1225, 194, 318,1003,1029,2045,2046,2047,2048,1067,2049,2050, +2051,2052,2053, 59, 913, 112,2054, 632,2055, 455, 144, 739,1291,2056, 273, 681, + 499,2057, 448,2058,2059, 760,2060,2061, 970, 384, 169, 245,1132,2062,2063, 414, +1444,2064,2065, 41, 235,2066, 157, 252, 877, 568, 919, 789, 580,2067, 725,2068, +2069,1292,2070,2071,1445,2072,1446,2073,2074, 55, 588, 66,1447, 271,1092,2075, +1226,2076, 960,1013, 372,2077,2078,2079,2080,2081,1293,2082,2083,2084,2085, 850, +2086,2087,2088,2089,2090, 186,2091,1068, 180,2092,2093,2094, 109,1227, 522, 606, +2095, 867,1448,1093, 991,1171, 926, 353,1133,2096, 581,2097,2098,2099,1294,1449, +1450,2100, 596,1172,1014,1228,2101,1451,1295,1173,1229,2102,2103,1296,1134,1452, + 949,1135,2104,2105,1094,1453,1454,1455,2106,1095,2107,2108,2109,2110,2111,2112, +2113,2114,2115,2116,2117, 804,2118,2119,1230,1231, 805,1456, 405,1136,2120,2121, +2122,2123,2124, 720, 701,1297, 992,1457, 927,1004,2125,2126,2127,2128,2129,2130, + 22, 417,2131, 303,2132, 385,2133, 971, 520, 513,2134,1174, 73,1096, 231, 274, + 962,1458, 673,2135,1459,2136, 152,1137,2137,2138,2139,2140,1005,1138,1460,1139, +2141,2142,2143,2144, 11, 374, 844,2145, 154,1232, 46,1461,2146, 838, 830, 721, +1233, 106,2147, 90, 428, 462, 578, 566,1175, 352,2148,2149, 538,1234, 124,1298, +2150,1462, 761, 565,2151, 686,2152, 649,2153, 72, 173,2154, 460, 415,2155,1463, +2156,1235, 305,2157,2158,2159,2160,2161,2162, 579,2163,2164,2165,2166,2167, 747, +2168,2169,2170,2171,1464, 669,2172,2173,2174,2175,2176,1465,2177, 23, 530, 285, +2178, 335, 729,2179, 397,2180,2181,2182,1030,2183,2184, 698,2185,2186, 325,2187, +2188, 369,2189, 799,1097,1015, 348,2190,1069, 680,2191, 851,1466,2192,2193, 10, +2194, 613, 424,2195, 979, 108, 449, 589, 27, 172, 81,1031, 80, 774, 281, 350, +1032, 525, 301, 582,1176,2196, 674,1045,2197,2198,1467, 730, 762,2199,2200,2201, +2202,1468,2203, 993,2204,2205, 266,1070, 963,1140,2206,2207,2208, 664,1098, 972, +2209,2210,2211,1177,1469,1470, 871,2212,2213,2214,2215,2216,1471,2217,2218,2219, +2220,2221,2222,2223,2224,2225,2226,2227,1472,1236,2228,2229,2230,2231,2232,2233, +2234,2235,1299,2236,2237, 200,2238, 477, 373,2239,2240, 731, 825, 777,2241,2242, +2243, 521, 486, 548,2244,2245,2246,1473,1300, 53, 549, 137, 875, 76, 158,2247, +1301,1474, 469, 396,1016, 278, 712,2248, 321, 442, 503, 767, 744, 941,1237,1178, +1475,2249, 82, 178,1141,1179, 973,2250,1302,2251, 297,2252,2253, 570,2254,2255, +2256, 18, 450, 206,2257, 290, 292,1142,2258, 511, 162, 99, 346, 164, 735,2259, +1476,1477, 4, 554, 343, 798,1099,2260,1100,2261, 43, 171,1303, 139, 215,2262, +2263, 717, 775,2264,1033, 322, 216,2265, 831,2266, 149,2267,1304,2268,2269, 702, +1238, 135, 845, 347, 309,2270, 484,2271, 878, 655, 238,1006,1478,2272, 67,2273, + 295,2274,2275, 461,2276, 478, 942, 412,2277,1034,2278,2279,2280, 265,2281, 541, +2282,2283,2284,2285,2286, 70, 852,1071,2287,2288,2289,2290, 21, 56, 509, 117, + 432,2291,2292, 331, 980, 552,1101, 148, 284, 105, 393,1180,1239, 755,2293, 187, +2294,1046,1479,2295, 340,2296, 63,1047, 230,2297,2298,1305, 763,1306, 101, 800, + 808, 494,2299,2300,2301, 903,2302, 37,1072, 14, 5,2303, 79, 675,2304, 312, +2305,2306,2307,2308,2309,1480, 6,1307,2310,2311,2312, 1, 470, 35, 24, 229, +2313, 695, 210, 86, 778, 15, 784, 592, 779, 32, 77, 855, 964,2314, 259,2315, + 501, 380,2316,2317, 83, 981, 153, 689,1308,1481,1482,1483,2318,2319, 716,1484, +2320,2321,2322,2323,2324,2325,1485,2326,2327, 128, 57, 68, 261,1048, 211, 170, +1240, 31,2328, 51, 435, 742,2329,2330,2331, 635,2332, 264, 456,2333,2334,2335, + 425,2336,1486, 143, 507, 263, 943,2337, 363, 920,1487, 256,1488,1102, 243, 601, +1489,2338,2339,2340,2341,2342,2343,2344, 861,2345,2346,2347,2348,2349,2350, 395, +2351,1490,1491, 62, 535, 166, 225,2352,2353, 668, 419,1241, 138, 604, 928,2354, +1181,2355,1492,1493,2356,2357,2358,1143,2359, 696,2360, 387, 307,1309, 682, 476, +2361,2362, 332, 12, 222, 156,2363, 232,2364, 641, 276, 656, 517,1494,1495,1035, + 416, 736,1496,2365,1017, 586,2366,2367,2368,1497,2369, 242,2370,2371,2372,1498, +2373, 965, 713,2374,2375,2376,2377, 740, 982,1499, 944,1500,1007,2378,2379,1310, +1501,2380,2381,2382, 785, 329,2383,2384,1502,2385,2386,2387, 932,2388,1503,2389, +2390,2391,2392,1242,2393,2394,2395,2396,2397, 994, 950,2398,2399,2400,2401,1504, +1311,2402,2403,2404,2405,1049, 749,2406,2407, 853, 718,1144,1312,2408,1182,1505, +2409,2410, 255, 516, 479, 564, 550, 214,1506,1507,1313, 413, 239, 444, 339,1145, +1036,1508,1509,1314,1037,1510,1315,2411,1511,2412,2413,2414, 176, 703, 497, 624, + 593, 921, 302,2415, 341, 165,1103,1512,2416,1513,2417,2418,2419, 376,2420, 700, +2421,2422,2423, 258, 768,1316,2424,1183,2425, 995, 608,2426,2427,2428,2429, 221, +2430,2431,2432,2433,2434,2435,2436,2437, 195, 323, 726, 188, 897, 983,1317, 377, + 644,1050, 879,2438, 452,2439,2440,2441,2442,2443,2444, 914,2445,2446,2447,2448, + 915, 489,2449,1514,1184,2450,2451, 515, 64, 427, 495,2452, 583,2453, 483, 485, +1038, 562, 213,1515, 748, 666,2454,2455,2456,2457, 334,2458, 780, 996,1008, 705, +1243,2459,2460,2461,2462,2463, 114,2464, 493,1146, 366, 163,1516, 961,1104,2465, + 291,2466,1318,1105,2467,1517, 365,2468, 355, 951,1244,2469,1319,2470, 631,2471, +2472, 218,1320, 364, 320, 756,1518,1519,1321,1520,1322,2473,2474,2475,2476, 997, +2477,2478,2479,2480, 665,1185,2481, 916,1521,2482,2483,2484, 584, 684,2485,2486, + 797,2487,1051,1186,2488,2489,2490,1522,2491,2492, 370,2493,1039,1187, 65,2494, + 434, 205, 463,1188,2495, 125, 812, 391, 402, 826, 699, 286, 398, 155, 781, 771, + 585,2496, 590, 505,1073,2497, 599, 244, 219, 917,1018, 952, 646,1523,2498,1323, +2499,2500, 49, 984, 354, 741,2501, 625,2502,1324,2503,1019, 190, 357, 757, 491, + 95, 782, 868,2504,2505,2506,2507,2508,2509, 134,1524,1074, 422,1525, 898,2510, + 161,2511,2512,2513,2514, 769,2515,1526,2516,2517, 411,1325,2518, 472,1527,2519, +2520,2521,2522,2523,2524, 985,2525,2526,2527,2528,2529,2530, 764,2531,1245,2532, +2533, 25, 204, 311,2534, 496,2535,1052,2536,2537,2538,2539,2540,2541,2542, 199, + 704, 504, 468, 758, 657,1528, 196, 44, 839,1246, 272, 750,2543, 765, 862,2544, +2545,1326,2546, 132, 615, 933,2547, 732,2548,2549,2550,1189,1529,2551, 283,1247, +1053, 607, 929,2552,2553,2554, 930, 183, 872, 616,1040,1147,2555,1148,1020, 441, + 249,1075,2556,2557,2558, 466, 743,2559,2560,2561, 92, 514, 426, 420, 526,2562, +2563,2564,2565,2566,2567,2568, 185,2569,2570,2571,2572, 776,1530, 658,2573, 362, +2574, 361, 922,1076, 793,2575,2576,2577,2578,2579,2580,1531, 251,2581,2582,2583, +2584,1532, 54, 612, 237,1327,2585,2586, 275, 408, 647, 111,2587,1533,1106, 465, + 3, 458, 9, 38,2588, 107, 110, 890, 209, 26, 737, 498,2589,1534,2590, 431, + 202, 88,1535, 356, 287,1107, 660,1149,2591, 381,1536, 986,1150, 445,1248,1151, + 974,2592,2593, 846,2594, 446, 953, 184,1249,1250, 727,2595, 923, 193, 883,2596, +2597,2598, 102, 324, 539, 817,2599, 421,1041,2600, 832,2601, 94, 175, 197, 406, +2602, 459,2603,2604,2605,2606,2607, 330, 555,2608,2609,2610, 706,1108, 389,2611, +2612,2613,2614, 233,2615, 833, 558, 931, 954,1251,2616,2617,1537, 546,2618,2619, +1009,2620,2621,2622,1538, 690,1328,2623, 955,2624,1539,2625,2626, 772,2627,2628, +2629,2630,2631, 924, 648, 863, 603,2632,2633, 934,1540, 864, 865,2634, 642,1042, + 670,1190,2635,2636,2637,2638, 168,2639, 652, 873, 542,1054,1541,2640,2641,2642, # 512, 256 +) + diff --git a/openpype/vendor/python/python_2/chardet/euckrprober.py b/openpype/vendor/python/python_2/chardet/euckrprober.py new file mode 100644 index 0000000000..345a060d02 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/euckrprober.py @@ -0,0 +1,47 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import EUCKRDistributionAnalysis +from .mbcssm import EUCKR_SM_MODEL + + +class EUCKRProber(MultiByteCharSetProber): + def __init__(self): + super(EUCKRProber, self).__init__() + self.coding_sm = CodingStateMachine(EUCKR_SM_MODEL) + self.distribution_analyzer = EUCKRDistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "EUC-KR" + + @property + def language(self): + return "Korean" diff --git a/openpype/vendor/python/python_2/chardet/euctwfreq.py b/openpype/vendor/python/python_2/chardet/euctwfreq.py new file mode 100644 index 0000000000..ed7a995a3a --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/euctwfreq.py @@ -0,0 +1,387 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# EUCTW frequency table +# Converted from big5 work +# by Taiwan's Mandarin Promotion Council +# + +# 128 --> 0.42261 +# 256 --> 0.57851 +# 512 --> 0.74851 +# 1024 --> 0.89384 +# 2048 --> 0.97583 +# +# Idea Distribution Ratio = 0.74851/(1-0.74851) =2.98 +# Random Distribution Ration = 512/(5401-512)=0.105 +# +# Typical Distribution Ratio about 25% of Ideal one, still much higher than RDR + +EUCTW_TYPICAL_DISTRIBUTION_RATIO = 0.75 + +# Char to FreqOrder table , +EUCTW_TABLE_SIZE = 5376 + +EUCTW_CHAR_TO_FREQ_ORDER = ( + 1,1800,1506, 255,1431, 198, 9, 82, 6,7310, 177, 202,3615,1256,2808, 110, # 2742 +3735, 33,3241, 261, 76, 44,2113, 16,2931,2184,1176, 659,3868, 26,3404,2643, # 2758 +1198,3869,3313,4060, 410,2211, 302, 590, 361,1963, 8, 204, 58,4296,7311,1931, # 2774 + 63,7312,7313, 317,1614, 75, 222, 159,4061,2412,1480,7314,3500,3068, 224,2809, # 2790 +3616, 3, 10,3870,1471, 29,2774,1135,2852,1939, 873, 130,3242,1123, 312,7315, # 2806 +4297,2051, 507, 252, 682,7316, 142,1914, 124, 206,2932, 34,3501,3173, 64, 604, # 2822 +7317,2494,1976,1977, 155,1990, 645, 641,1606,7318,3405, 337, 72, 406,7319, 80, # 2838 + 630, 238,3174,1509, 263, 939,1092,2644, 756,1440,1094,3406, 449, 69,2969, 591, # 2854 + 179,2095, 471, 115,2034,1843, 60, 50,2970, 134, 806,1868, 734,2035,3407, 180, # 2870 + 995,1607, 156, 537,2893, 688,7320, 319,1305, 779,2144, 514,2374, 298,4298, 359, # 2886 +2495, 90,2707,1338, 663, 11, 906,1099,2545, 20,2436, 182, 532,1716,7321, 732, # 2902 +1376,4062,1311,1420,3175, 25,2312,1056, 113, 399, 382,1949, 242,3408,2467, 529, # 2918 +3243, 475,1447,3617,7322, 117, 21, 656, 810,1297,2295,2329,3502,7323, 126,4063, # 2934 + 706, 456, 150, 613,4299, 71,1118,2036,4064, 145,3069, 85, 835, 486,2114,1246, # 2950 +1426, 428, 727,1285,1015, 800, 106, 623, 303,1281,7324,2127,2354, 347,3736, 221, # 2966 +3503,3110,7325,1955,1153,4065, 83, 296,1199,3070, 192, 624, 93,7326, 822,1897, # 2982 +2810,3111, 795,2064, 991,1554,1542,1592, 27, 43,2853, 859, 139,1456, 860,4300, # 2998 + 437, 712,3871, 164,2392,3112, 695, 211,3017,2096, 195,3872,1608,3504,3505,3618, # 3014 +3873, 234, 811,2971,2097,3874,2229,1441,3506,1615,2375, 668,2076,1638, 305, 228, # 3030 +1664,4301, 467, 415,7327, 262,2098,1593, 239, 108, 300, 200,1033, 512,1247,2077, # 3046 +7328,7329,2173,3176,3619,2673, 593, 845,1062,3244, 88,1723,2037,3875,1950, 212, # 3062 + 266, 152, 149, 468,1898,4066,4302, 77, 187,7330,3018, 37, 5,2972,7331,3876, # 3078 +7332,7333, 39,2517,4303,2894,3177,2078, 55, 148, 74,4304, 545, 483,1474,1029, # 3094 +1665, 217,1869,1531,3113,1104,2645,4067, 24, 172,3507, 900,3877,3508,3509,4305, # 3110 + 32,1408,2811,1312, 329, 487,2355,2247,2708, 784,2674, 4,3019,3314,1427,1788, # 3126 + 188, 109, 499,7334,3620,1717,1789, 888,1217,3020,4306,7335,3510,7336,3315,1520, # 3142 +3621,3878, 196,1034, 775,7337,7338, 929,1815, 249, 439, 38,7339,1063,7340, 794, # 3158 +3879,1435,2296, 46, 178,3245,2065,7341,2376,7342, 214,1709,4307, 804, 35, 707, # 3174 + 324,3622,1601,2546, 140, 459,4068,7343,7344,1365, 839, 272, 978,2257,2572,3409, # 3190 +2128,1363,3623,1423, 697, 100,3071, 48, 70,1231, 495,3114,2193,7345,1294,7346, # 3206 +2079, 462, 586,1042,3246, 853, 256, 988, 185,2377,3410,1698, 434,1084,7347,3411, # 3222 + 314,2615,2775,4308,2330,2331, 569,2280, 637,1816,2518, 757,1162,1878,1616,3412, # 3238 + 287,1577,2115, 768,4309,1671,2854,3511,2519,1321,3737, 909,2413,7348,4069, 933, # 3254 +3738,7349,2052,2356,1222,4310, 765,2414,1322, 786,4311,7350,1919,1462,1677,2895, # 3270 +1699,7351,4312,1424,2437,3115,3624,2590,3316,1774,1940,3413,3880,4070, 309,1369, # 3286 +1130,2812, 364,2230,1653,1299,3881,3512,3882,3883,2646, 525,1085,3021, 902,2000, # 3302 +1475, 964,4313, 421,1844,1415,1057,2281, 940,1364,3116, 376,4314,4315,1381, 7, # 3318 +2520, 983,2378, 336,1710,2675,1845, 321,3414, 559,1131,3022,2742,1808,1132,1313, # 3334 + 265,1481,1857,7352, 352,1203,2813,3247, 167,1089, 420,2814, 776, 792,1724,3513, # 3350 +4071,2438,3248,7353,4072,7354, 446, 229, 333,2743, 901,3739,1200,1557,4316,2647, # 3366 +1920, 395,2744,2676,3740,4073,1835, 125, 916,3178,2616,4317,7355,7356,3741,7357, # 3382 +7358,7359,4318,3117,3625,1133,2547,1757,3415,1510,2313,1409,3514,7360,2145, 438, # 3398 +2591,2896,2379,3317,1068, 958,3023, 461, 311,2855,2677,4074,1915,3179,4075,1978, # 3414 + 383, 750,2745,2617,4076, 274, 539, 385,1278,1442,7361,1154,1964, 384, 561, 210, # 3430 + 98,1295,2548,3515,7362,1711,2415,1482,3416,3884,2897,1257, 129,7363,3742, 642, # 3446 + 523,2776,2777,2648,7364, 141,2231,1333, 68, 176, 441, 876, 907,4077, 603,2592, # 3462 + 710, 171,3417, 404, 549, 18,3118,2393,1410,3626,1666,7365,3516,4319,2898,4320, # 3478 +7366,2973, 368,7367, 146, 366, 99, 871,3627,1543, 748, 807,1586,1185, 22,2258, # 3494 + 379,3743,3180,7368,3181, 505,1941,2618,1991,1382,2314,7369, 380,2357, 218, 702, # 3510 +1817,1248,3418,3024,3517,3318,3249,7370,2974,3628, 930,3250,3744,7371, 59,7372, # 3526 + 585, 601,4078, 497,3419,1112,1314,4321,1801,7373,1223,1472,2174,7374, 749,1836, # 3542 + 690,1899,3745,1772,3885,1476, 429,1043,1790,2232,2116, 917,4079, 447,1086,1629, # 3558 +7375, 556,7376,7377,2020,1654, 844,1090, 105, 550, 966,1758,2815,1008,1782, 686, # 3574 +1095,7378,2282, 793,1602,7379,3518,2593,4322,4080,2933,2297,4323,3746, 980,2496, # 3590 + 544, 353, 527,4324, 908,2678,2899,7380, 381,2619,1942,1348,7381,1341,1252, 560, # 3606 +3072,7382,3420,2856,7383,2053, 973, 886,2080, 143,4325,7384,7385, 157,3886, 496, # 3622 +4081, 57, 840, 540,2038,4326,4327,3421,2117,1445, 970,2259,1748,1965,2081,4082, # 3638 +3119,1234,1775,3251,2816,3629, 773,1206,2129,1066,2039,1326,3887,1738,1725,4083, # 3654 + 279,3120, 51,1544,2594, 423,1578,2130,2066, 173,4328,1879,7386,7387,1583, 264, # 3670 + 610,3630,4329,2439, 280, 154,7388,7389,7390,1739, 338,1282,3073, 693,2857,1411, # 3686 +1074,3747,2440,7391,4330,7392,7393,1240, 952,2394,7394,2900,1538,2679, 685,1483, # 3702 +4084,2468,1436, 953,4085,2054,4331, 671,2395, 79,4086,2441,3252, 608, 567,2680, # 3718 +3422,4087,4088,1691, 393,1261,1791,2396,7395,4332,7396,7397,7398,7399,1383,1672, # 3734 +3748,3182,1464, 522,1119, 661,1150, 216, 675,4333,3888,1432,3519, 609,4334,2681, # 3750 +2397,7400,7401,7402,4089,3025, 0,7403,2469, 315, 231,2442, 301,3319,4335,2380, # 3766 +7404, 233,4090,3631,1818,4336,4337,7405, 96,1776,1315,2082,7406, 257,7407,1809, # 3782 +3632,2709,1139,1819,4091,2021,1124,2163,2778,1777,2649,7408,3074, 363,1655,3183, # 3798 +7409,2975,7410,7411,7412,3889,1567,3890, 718, 103,3184, 849,1443, 341,3320,2934, # 3814 +1484,7413,1712, 127, 67, 339,4092,2398, 679,1412, 821,7414,7415, 834, 738, 351, # 3830 +2976,2146, 846, 235,1497,1880, 418,1992,3749,2710, 186,1100,2147,2746,3520,1545, # 3846 +1355,2935,2858,1377, 583,3891,4093,2573,2977,7416,1298,3633,1078,2549,3634,2358, # 3862 + 78,3750,3751, 267,1289,2099,2001,1594,4094, 348, 369,1274,2194,2175,1837,4338, # 3878 +1820,2817,3635,2747,2283,2002,4339,2936,2748, 144,3321, 882,4340,3892,2749,3423, # 3894 +4341,2901,7417,4095,1726, 320,7418,3893,3026, 788,2978,7419,2818,1773,1327,2859, # 3910 +3894,2819,7420,1306,4342,2003,1700,3752,3521,2359,2650, 787,2022, 506, 824,3636, # 3926 + 534, 323,4343,1044,3322,2023,1900, 946,3424,7421,1778,1500,1678,7422,1881,4344, # 3942 + 165, 243,4345,3637,2521, 123, 683,4096, 764,4346, 36,3895,1792, 589,2902, 816, # 3958 + 626,1667,3027,2233,1639,1555,1622,3753,3896,7423,3897,2860,1370,1228,1932, 891, # 3974 +2083,2903, 304,4097,7424, 292,2979,2711,3522, 691,2100,4098,1115,4347, 118, 662, # 3990 +7425, 611,1156, 854,2381,1316,2861, 2, 386, 515,2904,7426,7427,3253, 868,2234, # 4006 +1486, 855,2651, 785,2212,3028,7428,1040,3185,3523,7429,3121, 448,7430,1525,7431, # 4022 +2164,4348,7432,3754,7433,4099,2820,3524,3122, 503, 818,3898,3123,1568, 814, 676, # 4038 +1444, 306,1749,7434,3755,1416,1030, 197,1428, 805,2821,1501,4349,7435,7436,7437, # 4054 +1993,7438,4350,7439,7440,2195, 13,2779,3638,2980,3124,1229,1916,7441,3756,2131, # 4070 +7442,4100,4351,2399,3525,7443,2213,1511,1727,1120,7444,7445, 646,3757,2443, 307, # 4086 +7446,7447,1595,3186,7448,7449,7450,3639,1113,1356,3899,1465,2522,2523,7451, 519, # 4102 +7452, 128,2132, 92,2284,1979,7453,3900,1512, 342,3125,2196,7454,2780,2214,1980, # 4118 +3323,7455, 290,1656,1317, 789, 827,2360,7456,3758,4352, 562, 581,3901,7457, 401, # 4134 +4353,2248, 94,4354,1399,2781,7458,1463,2024,4355,3187,1943,7459, 828,1105,4101, # 4150 +1262,1394,7460,4102, 605,4356,7461,1783,2862,7462,2822, 819,2101, 578,2197,2937, # 4166 +7463,1502, 436,3254,4103,3255,2823,3902,2905,3425,3426,7464,2712,2315,7465,7466, # 4182 +2332,2067, 23,4357, 193, 826,3759,2102, 699,1630,4104,3075, 390,1793,1064,3526, # 4198 +7467,1579,3076,3077,1400,7468,4105,1838,1640,2863,7469,4358,4359, 137,4106, 598, # 4214 +3078,1966, 780, 104, 974,2938,7470, 278, 899, 253, 402, 572, 504, 493,1339,7471, # 4230 +3903,1275,4360,2574,2550,7472,3640,3029,3079,2249, 565,1334,2713, 863, 41,7473, # 4246 +7474,4361,7475,1657,2333, 19, 463,2750,4107, 606,7476,2981,3256,1087,2084,1323, # 4262 +2652,2982,7477,1631,1623,1750,4108,2682,7478,2864, 791,2714,2653,2334, 232,2416, # 4278 +7479,2983,1498,7480,2654,2620, 755,1366,3641,3257,3126,2025,1609, 119,1917,3427, # 4294 + 862,1026,4109,7481,3904,3760,4362,3905,4363,2260,1951,2470,7482,1125, 817,4110, # 4310 +4111,3906,1513,1766,2040,1487,4112,3030,3258,2824,3761,3127,7483,7484,1507,7485, # 4326 +2683, 733, 40,1632,1106,2865, 345,4113, 841,2524, 230,4364,2984,1846,3259,3428, # 4342 +7486,1263, 986,3429,7487, 735, 879, 254,1137, 857, 622,1300,1180,1388,1562,3907, # 4358 +3908,2939, 967,2751,2655,1349, 592,2133,1692,3324,2985,1994,4114,1679,3909,1901, # 4374 +2185,7488, 739,3642,2715,1296,1290,7489,4115,2198,2199,1921,1563,2595,2551,1870, # 4390 +2752,2986,7490, 435,7491, 343,1108, 596, 17,1751,4365,2235,3430,3643,7492,4366, # 4406 + 294,3527,2940,1693, 477, 979, 281,2041,3528, 643,2042,3644,2621,2782,2261,1031, # 4422 +2335,2134,2298,3529,4367, 367,1249,2552,7493,3530,7494,4368,1283,3325,2004, 240, # 4438 +1762,3326,4369,4370, 836,1069,3128, 474,7495,2148,2525, 268,3531,7496,3188,1521, # 4454 +1284,7497,1658,1546,4116,7498,3532,3533,7499,4117,3327,2684,1685,4118, 961,1673, # 4470 +2622, 190,2005,2200,3762,4371,4372,7500, 570,2497,3645,1490,7501,4373,2623,3260, # 4486 +1956,4374, 584,1514, 396,1045,1944,7502,4375,1967,2444,7503,7504,4376,3910, 619, # 4502 +7505,3129,3261, 215,2006,2783,2553,3189,4377,3190,4378, 763,4119,3763,4379,7506, # 4518 +7507,1957,1767,2941,3328,3646,1174, 452,1477,4380,3329,3130,7508,2825,1253,2382, # 4534 +2186,1091,2285,4120, 492,7509, 638,1169,1824,2135,1752,3911, 648, 926,1021,1324, # 4550 +4381, 520,4382, 997, 847,1007, 892,4383,3764,2262,1871,3647,7510,2400,1784,4384, # 4566 +1952,2942,3080,3191,1728,4121,2043,3648,4385,2007,1701,3131,1551, 30,2263,4122, # 4582 +7511,2026,4386,3534,7512, 501,7513,4123, 594,3431,2165,1821,3535,3432,3536,3192, # 4598 + 829,2826,4124,7514,1680,3132,1225,4125,7515,3262,4387,4126,3133,2336,7516,4388, # 4614 +4127,7517,3912,3913,7518,1847,2383,2596,3330,7519,4389, 374,3914, 652,4128,4129, # 4630 + 375,1140, 798,7520,7521,7522,2361,4390,2264, 546,1659, 138,3031,2445,4391,7523, # 4646 +2250, 612,1848, 910, 796,3765,1740,1371, 825,3766,3767,7524,2906,2554,7525, 692, # 4662 + 444,3032,2624, 801,4392,4130,7526,1491, 244,1053,3033,4131,4132, 340,7527,3915, # 4678 +1041,2987, 293,1168, 87,1357,7528,1539, 959,7529,2236, 721, 694,4133,3768, 219, # 4694 +1478, 644,1417,3331,2656,1413,1401,1335,1389,3916,7530,7531,2988,2362,3134,1825, # 4710 + 730,1515, 184,2827, 66,4393,7532,1660,2943, 246,3332, 378,1457, 226,3433, 975, # 4726 +3917,2944,1264,3537, 674, 696,7533, 163,7534,1141,2417,2166, 713,3538,3333,4394, # 4742 +3918,7535,7536,1186, 15,7537,1079,1070,7538,1522,3193,3539, 276,1050,2716, 758, # 4758 +1126, 653,2945,3263,7539,2337, 889,3540,3919,3081,2989, 903,1250,4395,3920,3434, # 4774 +3541,1342,1681,1718, 766,3264, 286, 89,2946,3649,7540,1713,7541,2597,3334,2990, # 4790 +7542,2947,2215,3194,2866,7543,4396,2498,2526, 181, 387,1075,3921, 731,2187,3335, # 4806 +7544,3265, 310, 313,3435,2299, 770,4134, 54,3034, 189,4397,3082,3769,3922,7545, # 4822 +1230,1617,1849, 355,3542,4135,4398,3336, 111,4136,3650,1350,3135,3436,3035,4137, # 4838 +2149,3266,3543,7546,2784,3923,3924,2991, 722,2008,7547,1071, 247,1207,2338,2471, # 4854 +1378,4399,2009, 864,1437,1214,4400, 373,3770,1142,2216, 667,4401, 442,2753,2555, # 4870 +3771,3925,1968,4138,3267,1839, 837, 170,1107, 934,1336,1882,7548,7549,2118,4139, # 4886 +2828, 743,1569,7550,4402,4140, 582,2384,1418,3437,7551,1802,7552, 357,1395,1729, # 4902 +3651,3268,2418,1564,2237,7553,3083,3772,1633,4403,1114,2085,4141,1532,7554, 482, # 4918 +2446,4404,7555,7556,1492, 833,1466,7557,2717,3544,1641,2829,7558,1526,1272,3652, # 4934 +4142,1686,1794, 416,2556,1902,1953,1803,7559,3773,2785,3774,1159,2316,7560,2867, # 4950 +4405,1610,1584,3036,2419,2754, 443,3269,1163,3136,7561,7562,3926,7563,4143,2499, # 4966 +3037,4406,3927,3137,2103,1647,3545,2010,1872,4144,7564,4145, 431,3438,7565, 250, # 4982 + 97, 81,4146,7566,1648,1850,1558, 160, 848,7567, 866, 740,1694,7568,2201,2830, # 4998 +3195,4147,4407,3653,1687, 950,2472, 426, 469,3196,3654,3655,3928,7569,7570,1188, # 5014 + 424,1995, 861,3546,4148,3775,2202,2685, 168,1235,3547,4149,7571,2086,1674,4408, # 5030 +3337,3270, 220,2557,1009,7572,3776, 670,2992, 332,1208, 717,7573,7574,3548,2447, # 5046 +3929,3338,7575, 513,7576,1209,2868,3339,3138,4409,1080,7577,7578,7579,7580,2527, # 5062 +3656,3549, 815,1587,3930,3931,7581,3550,3439,3777,1254,4410,1328,3038,1390,3932, # 5078 +1741,3933,3778,3934,7582, 236,3779,2448,3271,7583,7584,3657,3780,1273,3781,4411, # 5094 +7585, 308,7586,4412, 245,4413,1851,2473,1307,2575, 430, 715,2136,2449,7587, 270, # 5110 + 199,2869,3935,7588,3551,2718,1753, 761,1754, 725,1661,1840,4414,3440,3658,7589, # 5126 +7590, 587, 14,3272, 227,2598, 326, 480,2265, 943,2755,3552, 291, 650,1883,7591, # 5142 +1702,1226, 102,1547, 62,3441, 904,4415,3442,1164,4150,7592,7593,1224,1548,2756, # 5158 + 391, 498,1493,7594,1386,1419,7595,2055,1177,4416, 813, 880,1081,2363, 566,1145, # 5174 +4417,2286,1001,1035,2558,2599,2238, 394,1286,7596,7597,2068,7598, 86,1494,1730, # 5190 +3936, 491,1588, 745, 897,2948, 843,3340,3937,2757,2870,3273,1768, 998,2217,2069, # 5206 + 397,1826,1195,1969,3659,2993,3341, 284,7599,3782,2500,2137,2119,1903,7600,3938, # 5222 +2150,3939,4151,1036,3443,1904, 114,2559,4152, 209,1527,7601,7602,2949,2831,2625, # 5238 +2385,2719,3139, 812,2560,7603,3274,7604,1559, 737,1884,3660,1210, 885, 28,2686, # 5254 +3553,3783,7605,4153,1004,1779,4418,7606, 346,1981,2218,2687,4419,3784,1742, 797, # 5270 +1642,3940,1933,1072,1384,2151, 896,3941,3275,3661,3197,2871,3554,7607,2561,1958, # 5286 +4420,2450,1785,7608,7609,7610,3942,4154,1005,1308,3662,4155,2720,4421,4422,1528, # 5302 +2600, 161,1178,4156,1982, 987,4423,1101,4157, 631,3943,1157,3198,2420,1343,1241, # 5318 +1016,2239,2562, 372, 877,2339,2501,1160, 555,1934, 911,3944,7611, 466,1170, 169, # 5334 +1051,2907,2688,3663,2474,2994,1182,2011,2563,1251,2626,7612, 992,2340,3444,1540, # 5350 +2721,1201,2070,2401,1996,2475,7613,4424, 528,1922,2188,1503,1873,1570,2364,3342, # 5366 +3276,7614, 557,1073,7615,1827,3445,2087,2266,3140,3039,3084, 767,3085,2786,4425, # 5382 +1006,4158,4426,2341,1267,2176,3664,3199, 778,3945,3200,2722,1597,2657,7616,4427, # 5398 +7617,3446,7618,7619,7620,3277,2689,1433,3278, 131, 95,1504,3946, 723,4159,3141, # 5414 +1841,3555,2758,2189,3947,2027,2104,3665,7621,2995,3948,1218,7622,3343,3201,3949, # 5430 +4160,2576, 248,1634,3785, 912,7623,2832,3666,3040,3786, 654, 53,7624,2996,7625, # 5446 +1688,4428, 777,3447,1032,3950,1425,7626, 191, 820,2120,2833, 971,4429, 931,3202, # 5462 + 135, 664, 783,3787,1997, 772,2908,1935,3951,3788,4430,2909,3203, 282,2723, 640, # 5478 +1372,3448,1127, 922, 325,3344,7627,7628, 711,2044,7629,7630,3952,2219,2787,1936, # 5494 +3953,3345,2220,2251,3789,2300,7631,4431,3790,1258,3279,3954,3204,2138,2950,3955, # 5510 +3956,7632,2221, 258,3205,4432, 101,1227,7633,3280,1755,7634,1391,3281,7635,2910, # 5526 +2056, 893,7636,7637,7638,1402,4161,2342,7639,7640,3206,3556,7641,7642, 878,1325, # 5542 +1780,2788,4433, 259,1385,2577, 744,1183,2267,4434,7643,3957,2502,7644, 684,1024, # 5558 +4162,7645, 472,3557,3449,1165,3282,3958,3959, 322,2152, 881, 455,1695,1152,1340, # 5574 + 660, 554,2153,4435,1058,4436,4163, 830,1065,3346,3960,4437,1923,7646,1703,1918, # 5590 +7647, 932,2268, 122,7648,4438, 947, 677,7649,3791,2627, 297,1905,1924,2269,4439, # 5606 +2317,3283,7650,7651,4164,7652,4165, 84,4166, 112, 989,7653, 547,1059,3961, 701, # 5622 +3558,1019,7654,4167,7655,3450, 942, 639, 457,2301,2451, 993,2951, 407, 851, 494, # 5638 +4440,3347, 927,7656,1237,7657,2421,3348, 573,4168, 680, 921,2911,1279,1874, 285, # 5654 + 790,1448,1983, 719,2167,7658,7659,4441,3962,3963,1649,7660,1541, 563,7661,1077, # 5670 +7662,3349,3041,3451, 511,2997,3964,3965,3667,3966,1268,2564,3350,3207,4442,4443, # 5686 +7663, 535,1048,1276,1189,2912,2028,3142,1438,1373,2834,2952,1134,2012,7664,4169, # 5702 +1238,2578,3086,1259,7665, 700,7666,2953,3143,3668,4170,7667,4171,1146,1875,1906, # 5718 +4444,2601,3967, 781,2422, 132,1589, 203, 147, 273,2789,2402, 898,1786,2154,3968, # 5734 +3969,7668,3792,2790,7669,7670,4445,4446,7671,3208,7672,1635,3793, 965,7673,1804, # 5750 +2690,1516,3559,1121,1082,1329,3284,3970,1449,3794, 65,1128,2835,2913,2759,1590, # 5766 +3795,7674,7675, 12,2658, 45, 976,2579,3144,4447, 517,2528,1013,1037,3209,7676, # 5782 +3796,2836,7677,3797,7678,3452,7679,2602, 614,1998,2318,3798,3087,2724,2628,7680, # 5798 +2580,4172, 599,1269,7681,1810,3669,7682,2691,3088, 759,1060, 489,1805,3351,3285, # 5814 +1358,7683,7684,2386,1387,1215,2629,2252, 490,7685,7686,4173,1759,2387,2343,7687, # 5830 +4448,3799,1907,3971,2630,1806,3210,4449,3453,3286,2760,2344, 874,7688,7689,3454, # 5846 +3670,1858, 91,2914,3671,3042,3800,4450,7690,3145,3972,2659,7691,3455,1202,1403, # 5862 +3801,2954,2529,1517,2503,4451,3456,2504,7692,4452,7693,2692,1885,1495,1731,3973, # 5878 +2365,4453,7694,2029,7695,7696,3974,2693,1216, 237,2581,4174,2319,3975,3802,4454, # 5894 +4455,2694,3560,3457, 445,4456,7697,7698,7699,7700,2761, 61,3976,3672,1822,3977, # 5910 +7701, 687,2045, 935, 925, 405,2660, 703,1096,1859,2725,4457,3978,1876,1367,2695, # 5926 +3352, 918,2105,1781,2476, 334,3287,1611,1093,4458, 564,3146,3458,3673,3353, 945, # 5942 +2631,2057,4459,7702,1925, 872,4175,7703,3459,2696,3089, 349,4176,3674,3979,4460, # 5958 +3803,4177,3675,2155,3980,4461,4462,4178,4463,2403,2046, 782,3981, 400, 251,4179, # 5974 +1624,7704,7705, 277,3676, 299,1265, 476,1191,3804,2121,4180,4181,1109, 205,7706, # 5990 +2582,1000,2156,3561,1860,7707,7708,7709,4464,7710,4465,2565, 107,2477,2157,3982, # 6006 +3460,3147,7711,1533, 541,1301, 158, 753,4182,2872,3562,7712,1696, 370,1088,4183, # 6022 +4466,3563, 579, 327, 440, 162,2240, 269,1937,1374,3461, 968,3043, 56,1396,3090, # 6038 +2106,3288,3354,7713,1926,2158,4467,2998,7714,3564,7715,7716,3677,4468,2478,7717, # 6054 +2791,7718,1650,4469,7719,2603,7720,7721,3983,2661,3355,1149,3356,3984,3805,3985, # 6070 +7722,1076, 49,7723, 951,3211,3289,3290, 450,2837, 920,7724,1811,2792,2366,4184, # 6086 +1908,1138,2367,3806,3462,7725,3212,4470,1909,1147,1518,2423,4471,3807,7726,4472, # 6102 +2388,2604, 260,1795,3213,7727,7728,3808,3291, 708,7729,3565,1704,7730,3566,1351, # 6118 +1618,3357,2999,1886, 944,4185,3358,4186,3044,3359,4187,7731,3678, 422, 413,1714, # 6134 +3292, 500,2058,2345,4188,2479,7732,1344,1910, 954,7733,1668,7734,7735,3986,2404, # 6150 +4189,3567,3809,4190,7736,2302,1318,2505,3091, 133,3092,2873,4473, 629, 31,2838, # 6166 +2697,3810,4474, 850, 949,4475,3987,2955,1732,2088,4191,1496,1852,7737,3988, 620, # 6182 +3214, 981,1242,3679,3360,1619,3680,1643,3293,2139,2452,1970,1719,3463,2168,7738, # 6198 +3215,7739,7740,3361,1828,7741,1277,4476,1565,2047,7742,1636,3568,3093,7743, 869, # 6214 +2839, 655,3811,3812,3094,3989,3000,3813,1310,3569,4477,7744,7745,7746,1733, 558, # 6230 +4478,3681, 335,1549,3045,1756,4192,3682,1945,3464,1829,1291,1192, 470,2726,2107, # 6246 +2793, 913,1054,3990,7747,1027,7748,3046,3991,4479, 982,2662,3362,3148,3465,3216, # 6262 +3217,1946,2794,7749, 571,4480,7750,1830,7751,3570,2583,1523,2424,7752,2089, 984, # 6278 +4481,3683,1959,7753,3684, 852, 923,2795,3466,3685, 969,1519, 999,2048,2320,1705, # 6294 +7754,3095, 615,1662, 151, 597,3992,2405,2321,1049, 275,4482,3686,4193, 568,3687, # 6310 +3571,2480,4194,3688,7755,2425,2270, 409,3218,7756,1566,2874,3467,1002, 769,2840, # 6326 + 194,2090,3149,3689,2222,3294,4195, 628,1505,7757,7758,1763,2177,3001,3993, 521, # 6342 +1161,2584,1787,2203,2406,4483,3994,1625,4196,4197, 412, 42,3096, 464,7759,2632, # 6358 +4484,3363,1760,1571,2875,3468,2530,1219,2204,3814,2633,2140,2368,4485,4486,3295, # 6374 +1651,3364,3572,7760,7761,3573,2481,3469,7762,3690,7763,7764,2271,2091, 460,7765, # 6390 +4487,7766,3002, 962, 588,3574, 289,3219,2634,1116, 52,7767,3047,1796,7768,7769, # 6406 +7770,1467,7771,1598,1143,3691,4198,1984,1734,1067,4488,1280,3365, 465,4489,1572, # 6422 + 510,7772,1927,2241,1812,1644,3575,7773,4490,3692,7774,7775,2663,1573,1534,7776, # 6438 +7777,4199, 536,1807,1761,3470,3815,3150,2635,7778,7779,7780,4491,3471,2915,1911, # 6454 +2796,7781,3296,1122, 377,3220,7782, 360,7783,7784,4200,1529, 551,7785,2059,3693, # 6470 +1769,2426,7786,2916,4201,3297,3097,2322,2108,2030,4492,1404, 136,1468,1479, 672, # 6486 +1171,3221,2303, 271,3151,7787,2762,7788,2049, 678,2727, 865,1947,4493,7789,2013, # 6502 +3995,2956,7790,2728,2223,1397,3048,3694,4494,4495,1735,2917,3366,3576,7791,3816, # 6518 + 509,2841,2453,2876,3817,7792,7793,3152,3153,4496,4202,2531,4497,2304,1166,1010, # 6534 + 552, 681,1887,7794,7795,2957,2958,3996,1287,1596,1861,3154, 358, 453, 736, 175, # 6550 + 478,1117, 905,1167,1097,7796,1853,1530,7797,1706,7798,2178,3472,2287,3695,3473, # 6566 +3577,4203,2092,4204,7799,3367,1193,2482,4205,1458,2190,2205,1862,1888,1421,3298, # 6582 +2918,3049,2179,3474, 595,2122,7800,3997,7801,7802,4206,1707,2636, 223,3696,1359, # 6598 + 751,3098, 183,3475,7803,2797,3003, 419,2369, 633, 704,3818,2389, 241,7804,7805, # 6614 +7806, 838,3004,3697,2272,2763,2454,3819,1938,2050,3998,1309,3099,2242,1181,7807, # 6630 +1136,2206,3820,2370,1446,4207,2305,4498,7808,7809,4208,1055,2605, 484,3698,7810, # 6646 +3999, 625,4209,2273,3368,1499,4210,4000,7811,4001,4211,3222,2274,2275,3476,7812, # 6662 +7813,2764, 808,2606,3699,3369,4002,4212,3100,2532, 526,3370,3821,4213, 955,7814, # 6678 +1620,4214,2637,2427,7815,1429,3700,1669,1831, 994, 928,7816,3578,1260,7817,7818, # 6694 +7819,1948,2288, 741,2919,1626,4215,2729,2455, 867,1184, 362,3371,1392,7820,7821, # 6710 +4003,4216,1770,1736,3223,2920,4499,4500,1928,2698,1459,1158,7822,3050,3372,2877, # 6726 +1292,1929,2506,2842,3701,1985,1187,2071,2014,2607,4217,7823,2566,2507,2169,3702, # 6742 +2483,3299,7824,3703,4501,7825,7826, 666,1003,3005,1022,3579,4218,7827,4502,1813, # 6758 +2253, 574,3822,1603, 295,1535, 705,3823,4219, 283, 858, 417,7828,7829,3224,4503, # 6774 +4504,3051,1220,1889,1046,2276,2456,4004,1393,1599, 689,2567, 388,4220,7830,2484, # 6790 + 802,7831,2798,3824,2060,1405,2254,7832,4505,3825,2109,1052,1345,3225,1585,7833, # 6806 + 809,7834,7835,7836, 575,2730,3477, 956,1552,1469,1144,2323,7837,2324,1560,2457, # 6822 +3580,3226,4005, 616,2207,3155,2180,2289,7838,1832,7839,3478,4506,7840,1319,3704, # 6838 +3705,1211,3581,1023,3227,1293,2799,7841,7842,7843,3826, 607,2306,3827, 762,2878, # 6854 +1439,4221,1360,7844,1485,3052,7845,4507,1038,4222,1450,2061,2638,4223,1379,4508, # 6870 +2585,7846,7847,4224,1352,1414,2325,2921,1172,7848,7849,3828,3829,7850,1797,1451, # 6886 +7851,7852,7853,7854,2922,4006,4007,2485,2346, 411,4008,4009,3582,3300,3101,4509, # 6902 +1561,2664,1452,4010,1375,7855,7856, 47,2959, 316,7857,1406,1591,2923,3156,7858, # 6918 +1025,2141,3102,3157, 354,2731, 884,2224,4225,2407, 508,3706, 726,3583, 996,2428, # 6934 +3584, 729,7859, 392,2191,1453,4011,4510,3707,7860,7861,2458,3585,2608,1675,2800, # 6950 + 919,2347,2960,2348,1270,4511,4012, 73,7862,7863, 647,7864,3228,2843,2255,1550, # 6966 +1346,3006,7865,1332, 883,3479,7866,7867,7868,7869,3301,2765,7870,1212, 831,1347, # 6982 +4226,4512,2326,3830,1863,3053, 720,3831,4513,4514,3832,7871,4227,7872,7873,4515, # 6998 +7874,7875,1798,4516,3708,2609,4517,3586,1645,2371,7876,7877,2924, 669,2208,2665, # 7014 +2429,7878,2879,7879,7880,1028,3229,7881,4228,2408,7882,2256,1353,7883,7884,4518, # 7030 +3158, 518,7885,4013,7886,4229,1960,7887,2142,4230,7888,7889,3007,2349,2350,3833, # 7046 + 516,1833,1454,4014,2699,4231,4519,2225,2610,1971,1129,3587,7890,2766,7891,2961, # 7062 +1422, 577,1470,3008,1524,3373,7892,7893, 432,4232,3054,3480,7894,2586,1455,2508, # 7078 +2226,1972,1175,7895,1020,2732,4015,3481,4520,7896,2733,7897,1743,1361,3055,3482, # 7094 +2639,4016,4233,4521,2290, 895, 924,4234,2170, 331,2243,3056, 166,1627,3057,1098, # 7110 +7898,1232,2880,2227,3374,4522, 657, 403,1196,2372, 542,3709,3375,1600,4235,3483, # 7126 +7899,4523,2767,3230, 576, 530,1362,7900,4524,2533,2666,3710,4017,7901, 842,3834, # 7142 +7902,2801,2031,1014,4018, 213,2700,3376, 665, 621,4236,7903,3711,2925,2430,7904, # 7158 +2431,3302,3588,3377,7905,4237,2534,4238,4525,3589,1682,4239,3484,1380,7906, 724, # 7174 +2277, 600,1670,7907,1337,1233,4526,3103,2244,7908,1621,4527,7909, 651,4240,7910, # 7190 +1612,4241,2611,7911,2844,7912,2734,2307,3058,7913, 716,2459,3059, 174,1255,2701, # 7206 +4019,3590, 548,1320,1398, 728,4020,1574,7914,1890,1197,3060,4021,7915,3061,3062, # 7222 +3712,3591,3713, 747,7916, 635,4242,4528,7917,7918,7919,4243,7920,7921,4529,7922, # 7238 +3378,4530,2432, 451,7923,3714,2535,2072,4244,2735,4245,4022,7924,1764,4531,7925, # 7254 +4246, 350,7926,2278,2390,2486,7927,4247,4023,2245,1434,4024, 488,4532, 458,4248, # 7270 +4025,3715, 771,1330,2391,3835,2568,3159,2159,2409,1553,2667,3160,4249,7928,2487, # 7286 +2881,2612,1720,2702,4250,3379,4533,7929,2536,4251,7930,3231,4252,2768,7931,2015, # 7302 +2736,7932,1155,1017,3716,3836,7933,3303,2308, 201,1864,4253,1430,7934,4026,7935, # 7318 +7936,7937,7938,7939,4254,1604,7940, 414,1865, 371,2587,4534,4535,3485,2016,3104, # 7334 +4536,1708, 960,4255, 887, 389,2171,1536,1663,1721,7941,2228,4027,2351,2926,1580, # 7350 +7942,7943,7944,1744,7945,2537,4537,4538,7946,4539,7947,2073,7948,7949,3592,3380, # 7366 +2882,4256,7950,4257,2640,3381,2802, 673,2703,2460, 709,3486,4028,3593,4258,7951, # 7382 +1148, 502, 634,7952,7953,1204,4540,3594,1575,4541,2613,3717,7954,3718,3105, 948, # 7398 +3232, 121,1745,3837,1110,7955,4259,3063,2509,3009,4029,3719,1151,1771,3838,1488, # 7414 +4030,1986,7956,2433,3487,7957,7958,2093,7959,4260,3839,1213,1407,2803, 531,2737, # 7430 +2538,3233,1011,1537,7960,2769,4261,3106,1061,7961,3720,3721,1866,2883,7962,2017, # 7446 + 120,4262,4263,2062,3595,3234,2309,3840,2668,3382,1954,4542,7963,7964,3488,1047, # 7462 +2704,1266,7965,1368,4543,2845, 649,3383,3841,2539,2738,1102,2846,2669,7966,7967, # 7478 +1999,7968,1111,3596,2962,7969,2488,3842,3597,2804,1854,3384,3722,7970,7971,3385, # 7494 +2410,2884,3304,3235,3598,7972,2569,7973,3599,2805,4031,1460, 856,7974,3600,7975, # 7510 +2885,2963,7976,2886,3843,7977,4264, 632,2510, 875,3844,1697,3845,2291,7978,7979, # 7526 +4544,3010,1239, 580,4545,4265,7980, 914, 936,2074,1190,4032,1039,2123,7981,7982, # 7542 +7983,3386,1473,7984,1354,4266,3846,7985,2172,3064,4033, 915,3305,4267,4268,3306, # 7558 +1605,1834,7986,2739, 398,3601,4269,3847,4034, 328,1912,2847,4035,3848,1331,4270, # 7574 +3011, 937,4271,7987,3602,4036,4037,3387,2160,4546,3388, 524, 742, 538,3065,1012, # 7590 +7988,7989,3849,2461,7990, 658,1103, 225,3850,7991,7992,4547,7993,4548,7994,3236, # 7606 +1243,7995,4038, 963,2246,4549,7996,2705,3603,3161,7997,7998,2588,2327,7999,4550, # 7622 +8000,8001,8002,3489,3307, 957,3389,2540,2032,1930,2927,2462, 870,2018,3604,1746, # 7638 +2770,2771,2434,2463,8003,3851,8004,3723,3107,3724,3490,3390,3725,8005,1179,3066, # 7654 +8006,3162,2373,4272,3726,2541,3163,3108,2740,4039,8007,3391,1556,2542,2292, 977, # 7670 +2887,2033,4040,1205,3392,8008,1765,3393,3164,2124,1271,1689, 714,4551,3491,8009, # 7686 +2328,3852, 533,4273,3605,2181, 617,8010,2464,3308,3492,2310,8011,8012,3165,8013, # 7702 +8014,3853,1987, 618, 427,2641,3493,3394,8015,8016,1244,1690,8017,2806,4274,4552, # 7718 +8018,3494,8019,8020,2279,1576, 473,3606,4275,3395, 972,8021,3607,8022,3067,8023, # 7734 +8024,4553,4554,8025,3727,4041,4042,8026, 153,4555, 356,8027,1891,2888,4276,2143, # 7750 + 408, 803,2352,8028,3854,8029,4277,1646,2570,2511,4556,4557,3855,8030,3856,4278, # 7766 +8031,2411,3396, 752,8032,8033,1961,2964,8034, 746,3012,2465,8035,4279,3728, 698, # 7782 +4558,1892,4280,3608,2543,4559,3609,3857,8036,3166,3397,8037,1823,1302,4043,2706, # 7798 +3858,1973,4281,8038,4282,3167, 823,1303,1288,1236,2848,3495,4044,3398, 774,3859, # 7814 +8039,1581,4560,1304,2849,3860,4561,8040,2435,2161,1083,3237,4283,4045,4284, 344, # 7830 +1173, 288,2311, 454,1683,8041,8042,1461,4562,4046,2589,8043,8044,4563, 985, 894, # 7846 +8045,3399,3168,8046,1913,2928,3729,1988,8047,2110,1974,8048,4047,8049,2571,1194, # 7862 + 425,8050,4564,3169,1245,3730,4285,8051,8052,2850,8053, 636,4565,1855,3861, 760, # 7878 +1799,8054,4286,2209,1508,4566,4048,1893,1684,2293,8055,8056,8057,4287,4288,2210, # 7894 + 479,8058,8059, 832,8060,4049,2489,8061,2965,2490,3731, 990,3109, 627,1814,2642, # 7910 +4289,1582,4290,2125,2111,3496,4567,8062, 799,4291,3170,8063,4568,2112,1737,3013, # 7926 +1018, 543, 754,4292,3309,1676,4569,4570,4050,8064,1489,8065,3497,8066,2614,2889, # 7942 +4051,8067,8068,2966,8069,8070,8071,8072,3171,4571,4572,2182,1722,8073,3238,3239, # 7958 +1842,3610,1715, 481, 365,1975,1856,8074,8075,1962,2491,4573,8076,2126,3611,3240, # 7974 + 433,1894,2063,2075,8077, 602,2741,8078,8079,8080,8081,8082,3014,1628,3400,8083, # 7990 +3172,4574,4052,2890,4575,2512,8084,2544,2772,8085,8086,8087,3310,4576,2891,8088, # 8006 +4577,8089,2851,4578,4579,1221,2967,4053,2513,8090,8091,8092,1867,1989,8093,8094, # 8022 +8095,1895,8096,8097,4580,1896,4054, 318,8098,2094,4055,4293,8099,8100, 485,8101, # 8038 + 938,3862, 553,2670, 116,8102,3863,3612,8103,3498,2671,2773,3401,3311,2807,8104, # 8054 +3613,2929,4056,1747,2930,2968,8105,8106, 207,8107,8108,2672,4581,2514,8109,3015, # 8070 + 890,3614,3864,8110,1877,3732,3402,8111,2183,2353,3403,1652,8112,8113,8114, 941, # 8086 +2294, 208,3499,4057,2019, 330,4294,3865,2892,2492,3733,4295,8115,8116,8117,8118, # 8102 +) + diff --git a/openpype/vendor/python/python_2/chardet/euctwprober.py b/openpype/vendor/python/python_2/chardet/euctwprober.py new file mode 100644 index 0000000000..35669cc4dd --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/euctwprober.py @@ -0,0 +1,46 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import EUCTWDistributionAnalysis +from .mbcssm import EUCTW_SM_MODEL + +class EUCTWProber(MultiByteCharSetProber): + def __init__(self): + super(EUCTWProber, self).__init__() + self.coding_sm = CodingStateMachine(EUCTW_SM_MODEL) + self.distribution_analyzer = EUCTWDistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "EUC-TW" + + @property + def language(self): + return "Taiwan" diff --git a/openpype/vendor/python/python_2/chardet/gb2312freq.py b/openpype/vendor/python/python_2/chardet/gb2312freq.py new file mode 100644 index 0000000000..697837bd9a --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/gb2312freq.py @@ -0,0 +1,283 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# GB2312 most frequently used character table +# +# Char to FreqOrder table , from hz6763 + +# 512 --> 0.79 -- 0.79 +# 1024 --> 0.92 -- 0.13 +# 2048 --> 0.98 -- 0.06 +# 6768 --> 1.00 -- 0.02 +# +# Ideal Distribution Ratio = 0.79135/(1-0.79135) = 3.79 +# Random Distribution Ration = 512 / (3755 - 512) = 0.157 +# +# Typical Distribution Ratio about 25% of Ideal one, still much higher that RDR + +GB2312_TYPICAL_DISTRIBUTION_RATIO = 0.9 + +GB2312_TABLE_SIZE = 3760 + +GB2312_CHAR_TO_FREQ_ORDER = ( +1671, 749,1443,2364,3924,3807,2330,3921,1704,3463,2691,1511,1515, 572,3191,2205, +2361, 224,2558, 479,1711, 963,3162, 440,4060,1905,2966,2947,3580,2647,3961,3842, +2204, 869,4207, 970,2678,5626,2944,2956,1479,4048, 514,3595, 588,1346,2820,3409, + 249,4088,1746,1873,2047,1774, 581,1813, 358,1174,3590,1014,1561,4844,2245, 670, +1636,3112, 889,1286, 953, 556,2327,3060,1290,3141, 613, 185,3477,1367, 850,3820, +1715,2428,2642,2303,2732,3041,2562,2648,3566,3946,1349, 388,3098,2091,1360,3585, + 152,1687,1539, 738,1559, 59,1232,2925,2267,1388,1249,1741,1679,2960, 151,1566, +1125,1352,4271, 924,4296, 385,3166,4459, 310,1245,2850, 70,3285,2729,3534,3575, +2398,3298,3466,1960,2265, 217,3647, 864,1909,2084,4401,2773,1010,3269,5152, 853, +3051,3121,1244,4251,1895, 364,1499,1540,2313,1180,3655,2268, 562, 715,2417,3061, + 544, 336,3768,2380,1752,4075, 950, 280,2425,4382, 183,2759,3272, 333,4297,2155, +1688,2356,1444,1039,4540, 736,1177,3349,2443,2368,2144,2225, 565, 196,1482,3406, + 927,1335,4147, 692, 878,1311,1653,3911,3622,1378,4200,1840,2969,3149,2126,1816, +2534,1546,2393,2760, 737,2494, 13, 447, 245,2747, 38,2765,2129,2589,1079, 606, + 360, 471,3755,2890, 404, 848, 699,1785,1236, 370,2221,1023,3746,2074,2026,2023, +2388,1581,2119, 812,1141,3091,2536,1519, 804,2053, 406,1596,1090, 784, 548,4414, +1806,2264,2936,1100, 343,4114,5096, 622,3358, 743,3668,1510,1626,5020,3567,2513, +3195,4115,5627,2489,2991, 24,2065,2697,1087,2719, 48,1634, 315, 68, 985,2052, + 198,2239,1347,1107,1439, 597,2366,2172, 871,3307, 919,2487,2790,1867, 236,2570, +1413,3794, 906,3365,3381,1701,1982,1818,1524,2924,1205, 616,2586,2072,2004, 575, + 253,3099, 32,1365,1182, 197,1714,2454,1201, 554,3388,3224,2748, 756,2587, 250, +2567,1507,1517,3529,1922,2761,2337,3416,1961,1677,2452,2238,3153, 615, 911,1506, +1474,2495,1265,1906,2749,3756,3280,2161, 898,2714,1759,3450,2243,2444, 563, 26, +3286,2266,3769,3344,2707,3677, 611,1402, 531,1028,2871,4548,1375, 261,2948, 835, +1190,4134, 353, 840,2684,1900,3082,1435,2109,1207,1674, 329,1872,2781,4055,2686, +2104, 608,3318,2423,2957,2768,1108,3739,3512,3271,3985,2203,1771,3520,1418,2054, +1681,1153, 225,1627,2929, 162,2050,2511,3687,1954, 124,1859,2431,1684,3032,2894, + 585,4805,3969,2869,2704,2088,2032,2095,3656,2635,4362,2209, 256, 518,2042,2105, +3777,3657, 643,2298,1148,1779, 190, 989,3544, 414, 11,2135,2063,2979,1471, 403, +3678, 126, 770,1563, 671,2499,3216,2877, 600,1179, 307,2805,4937,1268,1297,2694, + 252,4032,1448,1494,1331,1394, 127,2256, 222,1647,1035,1481,3056,1915,1048, 873, +3651, 210, 33,1608,2516, 200,1520, 415, 102, 0,3389,1287, 817, 91,3299,2940, + 836,1814, 549,2197,1396,1669,2987,3582,2297,2848,4528,1070, 687, 20,1819, 121, +1552,1364,1461,1968,2617,3540,2824,2083, 177, 948,4938,2291, 110,4549,2066, 648, +3359,1755,2110,2114,4642,4845,1693,3937,3308,1257,1869,2123, 208,1804,3159,2992, +2531,2549,3361,2418,1350,2347,2800,2568,1291,2036,2680, 72, 842,1990, 212,1233, +1154,1586, 75,2027,3410,4900,1823,1337,2710,2676, 728,2810,1522,3026,4995, 157, + 755,1050,4022, 710, 785,1936,2194,2085,1406,2777,2400, 150,1250,4049,1206, 807, +1910, 534, 529,3309,1721,1660, 274, 39,2827, 661,2670,1578, 925,3248,3815,1094, +4278,4901,4252, 41,1150,3747,2572,2227,4501,3658,4902,3813,3357,3617,2884,2258, + 887, 538,4187,3199,1294,2439,3042,2329,2343,2497,1255, 107, 543,1527, 521,3478, +3568, 194,5062, 15, 961,3870,1241,1192,2664, 66,5215,3260,2111,1295,1127,2152, +3805,4135, 901,1164,1976, 398,1278, 530,1460, 748, 904,1054,1966,1426, 53,2909, + 509, 523,2279,1534, 536,1019, 239,1685, 460,2353, 673,1065,2401,3600,4298,2272, +1272,2363, 284,1753,3679,4064,1695, 81, 815,2677,2757,2731,1386, 859, 500,4221, +2190,2566, 757,1006,2519,2068,1166,1455, 337,2654,3203,1863,1682,1914,3025,1252, +1409,1366, 847, 714,2834,2038,3209, 964,2970,1901, 885,2553,1078,1756,3049, 301, +1572,3326, 688,2130,1996,2429,1805,1648,2930,3421,2750,3652,3088, 262,1158,1254, + 389,1641,1812, 526,1719, 923,2073,1073,1902, 468, 489,4625,1140, 857,2375,3070, +3319,2863, 380, 116,1328,2693,1161,2244, 273,1212,1884,2769,3011,1775,1142, 461, +3066,1200,2147,2212, 790, 702,2695,4222,1601,1058, 434,2338,5153,3640, 67,2360, +4099,2502, 618,3472,1329, 416,1132, 830,2782,1807,2653,3211,3510,1662, 192,2124, + 296,3979,1739,1611,3684, 23, 118, 324, 446,1239,1225, 293,2520,3814,3795,2535, +3116, 17,1074, 467,2692,2201, 387,2922, 45,1326,3055,1645,3659,2817, 958, 243, +1903,2320,1339,2825,1784,3289, 356, 576, 865,2315,2381,3377,3916,1088,3122,1713, +1655, 935, 628,4689,1034,1327, 441, 800, 720, 894,1979,2183,1528,5289,2702,1071, +4046,3572,2399,1571,3281, 79, 761,1103, 327, 134, 758,1899,1371,1615, 879, 442, + 215,2605,2579, 173,2048,2485,1057,2975,3317,1097,2253,3801,4263,1403,1650,2946, + 814,4968,3487,1548,2644,1567,1285, 2, 295,2636, 97, 946,3576, 832, 141,4257, +3273, 760,3821,3521,3156,2607, 949,1024,1733,1516,1803,1920,2125,2283,2665,3180, +1501,2064,3560,2171,1592, 803,3518,1416, 732,3897,4258,1363,1362,2458, 119,1427, + 602,1525,2608,1605,1639,3175, 694,3064, 10, 465, 76,2000,4846,4208, 444,3781, +1619,3353,2206,1273,3796, 740,2483, 320,1723,2377,3660,2619,1359,1137,1762,1724, +2345,2842,1850,1862, 912, 821,1866, 612,2625,1735,2573,3369,1093, 844, 89, 937, + 930,1424,3564,2413,2972,1004,3046,3019,2011, 711,3171,1452,4178, 428, 801,1943, + 432, 445,2811, 206,4136,1472, 730, 349, 73, 397,2802,2547, 998,1637,1167, 789, + 396,3217, 154,1218, 716,1120,1780,2819,4826,1931,3334,3762,2139,1215,2627, 552, +3664,3628,3232,1405,2383,3111,1356,2652,3577,3320,3101,1703, 640,1045,1370,1246, +4996, 371,1575,2436,1621,2210, 984,4033,1734,2638, 16,4529, 663,2755,3255,1451, +3917,2257,1253,1955,2234,1263,2951, 214,1229, 617, 485, 359,1831,1969, 473,2310, + 750,2058, 165, 80,2864,2419, 361,4344,2416,2479,1134, 796,3726,1266,2943, 860, +2715, 938, 390,2734,1313,1384, 248, 202, 877,1064,2854, 522,3907, 279,1602, 297, +2357, 395,3740, 137,2075, 944,4089,2584,1267,3802, 62,1533,2285, 178, 176, 780, +2440, 201,3707, 590, 478,1560,4354,2117,1075, 30, 74,4643,4004,1635,1441,2745, + 776,2596, 238,1077,1692,1912,2844, 605, 499,1742,3947, 241,3053, 980,1749, 936, +2640,4511,2582, 515,1543,2162,5322,2892,2993, 890,2148,1924, 665,1827,3581,1032, + 968,3163, 339,1044,1896, 270, 583,1791,1720,4367,1194,3488,3669, 43,2523,1657, + 163,2167, 290,1209,1622,3378, 550, 634,2508,2510, 695,2634,2384,2512,1476,1414, + 220,1469,2341,2138,2852,3183,2900,4939,2865,3502,1211,3680, 854,3227,1299,2976, +3172, 186,2998,1459, 443,1067,3251,1495, 321,1932,3054, 909, 753,1410,1828, 436, +2441,1119,1587,3164,2186,1258, 227, 231,1425,1890,3200,3942, 247, 959, 725,5254, +2741, 577,2158,2079, 929, 120, 174, 838,2813, 591,1115, 417,2024, 40,3240,1536, +1037, 291,4151,2354, 632,1298,2406,2500,3535,1825,1846,3451, 205,1171, 345,4238, + 18,1163, 811, 685,2208,1217, 425,1312,1508,1175,4308,2552,1033, 587,1381,3059, +2984,3482, 340,1316,4023,3972, 792,3176, 519, 777,4690, 918, 933,4130,2981,3741, + 90,3360,2911,2200,5184,4550, 609,3079,2030, 272,3379,2736, 363,3881,1130,1447, + 286, 779, 357,1169,3350,3137,1630,1220,2687,2391, 747,1277,3688,2618,2682,2601, +1156,3196,5290,4034,3102,1689,3596,3128, 874, 219,2783, 798, 508,1843,2461, 269, +1658,1776,1392,1913,2983,3287,2866,2159,2372, 829,4076, 46,4253,2873,1889,1894, + 915,1834,1631,2181,2318, 298, 664,2818,3555,2735, 954,3228,3117, 527,3511,2173, + 681,2712,3033,2247,2346,3467,1652, 155,2164,3382, 113,1994, 450, 899, 494, 994, +1237,2958,1875,2336,1926,3727, 545,1577,1550, 633,3473, 204,1305,3072,2410,1956, +2471, 707,2134, 841,2195,2196,2663,3843,1026,4940, 990,3252,4997, 368,1092, 437, +3212,3258,1933,1829, 675,2977,2893, 412, 943,3723,4644,3294,3283,2230,2373,5154, +2389,2241,2661,2323,1404,2524, 593, 787, 677,3008,1275,2059, 438,2709,2609,2240, +2269,2246,1446, 36,1568,1373,3892,1574,2301,1456,3962, 693,2276,5216,2035,1143, +2720,1919,1797,1811,2763,4137,2597,1830,1699,1488,1198,2090, 424,1694, 312,3634, +3390,4179,3335,2252,1214, 561,1059,3243,2295,2561, 975,5155,2321,2751,3772, 472, +1537,3282,3398,1047,2077,2348,2878,1323,3340,3076, 690,2906, 51, 369, 170,3541, +1060,2187,2688,3670,2541,1083,1683, 928,3918, 459, 109,4427, 599,3744,4286, 143, +2101,2730,2490, 82,1588,3036,2121, 281,1860, 477,4035,1238,2812,3020,2716,3312, +1530,2188,2055,1317, 843, 636,1808,1173,3495, 649, 181,1002, 147,3641,1159,2414, +3750,2289,2795, 813,3123,2610,1136,4368, 5,3391,4541,2174, 420, 429,1728, 754, +1228,2115,2219, 347,2223,2733, 735,1518,3003,2355,3134,1764,3948,3329,1888,2424, +1001,1234,1972,3321,3363,1672,1021,1450,1584, 226, 765, 655,2526,3404,3244,2302, +3665, 731, 594,2184, 319,1576, 621, 658,2656,4299,2099,3864,1279,2071,2598,2739, + 795,3086,3699,3908,1707,2352,2402,1382,3136,2475,1465,4847,3496,3865,1085,3004, +2591,1084, 213,2287,1963,3565,2250, 822, 793,4574,3187,1772,1789,3050, 595,1484, +1959,2770,1080,2650, 456, 422,2996, 940,3322,4328,4345,3092,2742, 965,2784, 739, +4124, 952,1358,2498,2949,2565, 332,2698,2378, 660,2260,2473,4194,3856,2919, 535, +1260,2651,1208,1428,1300,1949,1303,2942, 433,2455,2450,1251,1946, 614,1269, 641, +1306,1810,2737,3078,2912, 564,2365,1419,1415,1497,4460,2367,2185,1379,3005,1307, +3218,2175,1897,3063, 682,1157,4040,4005,1712,1160,1941,1399, 394, 402,2952,1573, +1151,2986,2404, 862, 299,2033,1489,3006, 346, 171,2886,3401,1726,2932, 168,2533, + 47,2507,1030,3735,1145,3370,1395,1318,1579,3609,4560,2857,4116,1457,2529,1965, + 504,1036,2690,2988,2405, 745,5871, 849,2397,2056,3081, 863,2359,3857,2096, 99, +1397,1769,2300,4428,1643,3455,1978,1757,3718,1440, 35,4879,3742,1296,4228,2280, + 160,5063,1599,2013, 166, 520,3479,1646,3345,3012, 490,1937,1545,1264,2182,2505, +1096,1188,1369,1436,2421,1667,2792,2460,1270,2122, 727,3167,2143, 806,1706,1012, +1800,3037, 960,2218,1882, 805, 139,2456,1139,1521, 851,1052,3093,3089, 342,2039, + 744,5097,1468,1502,1585,2087, 223, 939, 326,2140,2577, 892,2481,1623,4077, 982, +3708, 135,2131, 87,2503,3114,2326,1106, 876,1616, 547,2997,2831,2093,3441,4530, +4314, 9,3256,4229,4148, 659,1462,1986,1710,2046,2913,2231,4090,4880,5255,3392, +3274,1368,3689,4645,1477, 705,3384,3635,1068,1529,2941,1458,3782,1509, 100,1656, +2548, 718,2339, 408,1590,2780,3548,1838,4117,3719,1345,3530, 717,3442,2778,3220, +2898,1892,4590,3614,3371,2043,1998,1224,3483, 891, 635, 584,2559,3355, 733,1766, +1729,1172,3789,1891,2307, 781,2982,2271,1957,1580,5773,2633,2005,4195,3097,1535, +3213,1189,1934,5693,3262, 586,3118,1324,1598, 517,1564,2217,1868,1893,4445,3728, +2703,3139,1526,1787,1992,3882,2875,1549,1199,1056,2224,1904,2711,5098,4287, 338, +1993,3129,3489,2689,1809,2815,1997, 957,1855,3898,2550,3275,3057,1105,1319, 627, +1505,1911,1883,3526, 698,3629,3456,1833,1431, 746, 77,1261,2017,2296,1977,1885, + 125,1334,1600, 525,1798,1109,2222,1470,1945, 559,2236,1186,3443,2476,1929,1411, +2411,3135,1777,3372,2621,1841,1613,3229, 668,1430,1839,2643,2916, 195,1989,2671, +2358,1387, 629,3205,2293,5256,4439, 123,1310, 888,1879,4300,3021,3605,1003,1162, +3192,2910,2010, 140,2395,2859, 55,1082,2012,2901, 662, 419,2081,1438, 680,2774, +4654,3912,1620,1731,1625,5035,4065,2328, 512,1344, 802,5443,2163,2311,2537, 524, +3399, 98,1155,2103,1918,2606,3925,2816,1393,2465,1504,3773,2177,3963,1478,4346, + 180,1113,4655,3461,2028,1698, 833,2696,1235,1322,1594,4408,3623,3013,3225,2040, +3022, 541,2881, 607,3632,2029,1665,1219, 639,1385,1686,1099,2803,3231,1938,3188, +2858, 427, 676,2772,1168,2025, 454,3253,2486,3556, 230,1950, 580, 791,1991,1280, +1086,1974,2034, 630, 257,3338,2788,4903,1017, 86,4790, 966,2789,1995,1696,1131, + 259,3095,4188,1308, 179,1463,5257, 289,4107,1248, 42,3413,1725,2288, 896,1947, + 774,4474,4254, 604,3430,4264, 392,2514,2588, 452, 237,1408,3018, 988,4531,1970, +3034,3310, 540,2370,1562,1288,2990, 502,4765,1147, 4,1853,2708, 207, 294,2814, +4078,2902,2509, 684, 34,3105,3532,2551, 644, 709,2801,2344, 573,1727,3573,3557, +2021,1081,3100,4315,2100,3681, 199,2263,1837,2385, 146,3484,1195,2776,3949, 997, +1939,3973,1008,1091,1202,1962,1847,1149,4209,5444,1076, 493, 117,5400,2521, 972, +1490,2934,1796,4542,2374,1512,2933,2657, 413,2888,1135,2762,2314,2156,1355,2369, + 766,2007,2527,2170,3124,2491,2593,2632,4757,2437, 234,3125,3591,1898,1750,1376, +1942,3468,3138, 570,2127,2145,3276,4131, 962, 132,1445,4196, 19, 941,3624,3480, +3366,1973,1374,4461,3431,2629, 283,2415,2275, 808,2887,3620,2112,2563,1353,3610, + 955,1089,3103,1053, 96, 88,4097, 823,3808,1583, 399, 292,4091,3313, 421,1128, + 642,4006, 903,2539,1877,2082, 596, 29,4066,1790, 722,2157, 130, 995,1569, 769, +1485, 464, 513,2213, 288,1923,1101,2453,4316, 133, 486,2445, 50, 625, 487,2207, + 57, 423, 481,2962, 159,3729,1558, 491, 303, 482, 501, 240,2837, 112,3648,2392, +1783, 362, 8,3433,3422, 610,2793,3277,1390,1284,1654, 21,3823, 734, 367, 623, + 193, 287, 374,1009,1483, 816, 476, 313,2255,2340,1262,2150,2899,1146,2581, 782, +2116,1659,2018,1880, 255,3586,3314,1110,2867,2137,2564, 986,2767,5185,2006, 650, + 158, 926, 762, 881,3157,2717,2362,3587, 306,3690,3245,1542,3077,2427,1691,2478, +2118,2985,3490,2438, 539,2305, 983, 129,1754, 355,4201,2386, 827,2923, 104,1773, +2838,2771, 411,2905,3919, 376, 767, 122,1114, 828,2422,1817,3506, 266,3460,1007, +1609,4998, 945,2612,4429,2274, 726,1247,1964,2914,2199,2070,4002,4108, 657,3323, +1422, 579, 455,2764,4737,1222,2895,1670, 824,1223,1487,2525, 558, 861,3080, 598, +2659,2515,1967, 752,2583,2376,2214,4180, 977, 704,2464,4999,2622,4109,1210,2961, + 819,1541, 142,2284, 44, 418, 457,1126,3730,4347,4626,1644,1876,3671,1864, 302, +1063,5694, 624, 723,1984,3745,1314,1676,2488,1610,1449,3558,3569,2166,2098, 409, +1011,2325,3704,2306, 818,1732,1383,1824,1844,3757, 999,2705,3497,1216,1423,2683, +2426,2954,2501,2726,2229,1475,2554,5064,1971,1794,1666,2014,1343, 783, 724, 191, +2434,1354,2220,5065,1763,2752,2472,4152, 131, 175,2885,3434, 92,1466,4920,2616, +3871,3872,3866, 128,1551,1632, 669,1854,3682,4691,4125,1230, 188,2973,3290,1302, +1213, 560,3266, 917, 763,3909,3249,1760, 868,1958, 764,1782,2097, 145,2277,3774, +4462, 64,1491,3062, 971,2132,3606,2442, 221,1226,1617, 218, 323,1185,3207,3147, + 571, 619,1473,1005,1744,2281, 449,1887,2396,3685, 275, 375,3816,1743,3844,3731, + 845,1983,2350,4210,1377, 773, 967,3499,3052,3743,2725,4007,1697,1022,3943,1464, +3264,2855,2722,1952,1029,2839,2467, 84,4383,2215, 820,1391,2015,2448,3672, 377, +1948,2168, 797,2545,3536,2578,2645, 94,2874,1678, 405,1259,3071, 771, 546,1315, + 470,1243,3083, 895,2468, 981, 969,2037, 846,4181, 653,1276,2928, 14,2594, 557, +3007,2474, 156, 902,1338,1740,2574, 537,2518, 973,2282,2216,2433,1928, 138,2903, +1293,2631,1612, 646,3457, 839,2935, 111, 496,2191,2847, 589,3186, 149,3994,2060, +4031,2641,4067,3145,1870, 37,3597,2136,1025,2051,3009,3383,3549,1121,1016,3261, +1301, 251,2446,2599,2153, 872,3246, 637, 334,3705, 831, 884, 921,3065,3140,4092, +2198,1944, 246,2964, 108,2045,1152,1921,2308,1031, 203,3173,4170,1907,3890, 810, +1401,2003,1690, 506, 647,1242,2828,1761,1649,3208,2249,1589,3709,2931,5156,1708, + 498, 666,2613, 834,3817,1231, 184,2851,1124, 883,3197,2261,3710,1765,1553,2658, +1178,2639,2351, 93,1193, 942,2538,2141,4402, 235,1821, 870,1591,2192,1709,1871, +3341,1618,4126,2595,2334, 603, 651, 69, 701, 268,2662,3411,2555,1380,1606, 503, + 448, 254,2371,2646, 574,1187,2309,1770, 322,2235,1292,1801, 305, 566,1133, 229, +2067,2057, 706, 167, 483,2002,2672,3295,1820,3561,3067, 316, 378,2746,3452,1112, + 136,1981, 507,1651,2917,1117, 285,4591, 182,2580,3522,1304, 335,3303,1835,2504, +1795,1792,2248, 674,1018,2106,2449,1857,2292,2845, 976,3047,1781,2600,2727,1389, +1281, 52,3152, 153, 265,3950, 672,3485,3951,4463, 430,1183, 365, 278,2169, 27, +1407,1336,2304, 209,1340,1730,2202,1852,2403,2883, 979,1737,1062, 631,2829,2542, +3876,2592, 825,2086,2226,3048,3625, 352,1417,3724, 542, 991, 431,1351,3938,1861, +2294, 826,1361,2927,3142,3503,1738, 463,2462,2723, 582,1916,1595,2808, 400,3845, +3891,2868,3621,2254, 58,2492,1123, 910,2160,2614,1372,1603,1196,1072,3385,1700, +3267,1980, 696, 480,2430, 920, 799,1570,2920,1951,2041,4047,2540,1321,4223,2469, +3562,2228,1271,2602, 401,2833,3351,2575,5157, 907,2312,1256, 410, 263,3507,1582, + 996, 678,1849,2316,1480, 908,3545,2237, 703,2322, 667,1826,2849,1531,2604,2999, +2407,3146,2151,2630,1786,3711, 469,3542, 497,3899,2409, 858, 837,4446,3393,1274, + 786, 620,1845,2001,3311, 484, 308,3367,1204,1815,3691,2332,1532,2557,1842,2020, +2724,1927,2333,4440, 567, 22,1673,2728,4475,1987,1858,1144,1597, 101,1832,3601, + 12, 974,3783,4391, 951,1412, 1,3720, 453,4608,4041, 528,1041,1027,3230,2628, +1129, 875,1051,3291,1203,2262,1069,2860,2799,2149,2615,3278, 144,1758,3040, 31, + 475,1680, 366,2685,3184, 311,1642,4008,2466,5036,1593,1493,2809, 216,1420,1668, + 233, 304,2128,3284, 232,1429,1768,1040,2008,3407,2740,2967,2543, 242,2133, 778, +1565,2022,2620, 505,2189,2756,1098,2273, 372,1614, 708, 553,2846,2094,2278, 169, +3626,2835,4161, 228,2674,3165, 809,1454,1309, 466,1705,1095, 900,3423, 880,2667, +3751,5258,2317,3109,2571,4317,2766,1503,1342, 866,4447,1118, 63,2076, 314,1881, +1348,1061, 172, 978,3515,1747, 532, 511,3970, 6, 601, 905,2699,3300,1751, 276, +1467,3725,2668, 65,4239,2544,2779,2556,1604, 578,2451,1802, 992,2331,2624,1320, +3446, 713,1513,1013, 103,2786,2447,1661, 886,1702, 916, 654,3574,2031,1556, 751, +2178,2821,2179,1498,1538,2176, 271, 914,2251,2080,1325, 638,1953,2937,3877,2432, +2754, 95,3265,1716, 260,1227,4083, 775, 106,1357,3254, 426,1607, 555,2480, 772, +1985, 244,2546, 474, 495,1046,2611,1851,2061, 71,2089,1675,2590, 742,3758,2843, +3222,1433, 267,2180,2576,2826,2233,2092,3913,2435, 956,1745,3075, 856,2113,1116, + 451, 3,1988,2896,1398, 993,2463,1878,2049,1341,2718,2721,2870,2108, 712,2904, +4363,2753,2324, 277,2872,2349,2649, 384, 987, 435, 691,3000, 922, 164,3939, 652, +1500,1184,4153,2482,3373,2165,4848,2335,3775,3508,3154,2806,2830,1554,2102,1664, +2530,1434,2408, 893,1547,2623,3447,2832,2242,2532,3169,2856,3223,2078, 49,3770, +3469, 462, 318, 656,2259,3250,3069, 679,1629,2758, 344,1138,1104,3120,1836,1283, +3115,2154,1437,4448, 934, 759,1999, 794,2862,1038, 533,2560,1722,2342, 855,2626, +1197,1663,4476,3127, 85,4240,2528, 25,1111,1181,3673, 407,3470,4561,2679,2713, + 768,1925,2841,3986,1544,1165, 932, 373,1240,2146,1930,2673, 721,4766, 354,4333, + 391,2963, 187, 61,3364,1442,1102, 330,1940,1767, 341,3809,4118, 393,2496,2062, +2211, 105, 331, 300, 439, 913,1332, 626, 379,3304,1557, 328, 689,3952, 309,1555, + 931, 317,2517,3027, 325, 569, 686,2107,3084, 60,1042,1333,2794, 264,3177,4014, +1628, 258,3712, 7,4464,1176,1043,1778, 683, 114,1975, 78,1492, 383,1886, 510, + 386, 645,5291,2891,2069,3305,4138,3867,2939,2603,2493,1935,1066,1848,3588,1015, +1282,1289,4609, 697,1453,3044,2666,3611,1856,2412, 54, 719,1330, 568,3778,2459, +1748, 788, 492, 551,1191,1000, 488,3394,3763, 282,1799, 348,2016,1523,3155,2390, +1049, 382,2019,1788,1170, 729,2968,3523, 897,3926,2785,2938,3292, 350,2319,3238, +1718,1717,2655,3453,3143,4465, 161,2889,2980,2009,1421, 56,1908,1640,2387,2232, +1917,1874,2477,4921, 148, 83,3438, 592,4245,2882,1822,1055, 741, 115,1496,1624, + 381,1638,4592,1020, 516,3214, 458, 947,4575,1432, 211,1514,2926,1865,2142, 189, + 852,1221,1400,1486, 882,2299,4036, 351, 28,1122, 700,6479,6480,6481,6482,6483, #last 512 +) + diff --git a/openpype/vendor/python/python_2/chardet/gb2312prober.py b/openpype/vendor/python/python_2/chardet/gb2312prober.py new file mode 100644 index 0000000000..8446d2dd95 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/gb2312prober.py @@ -0,0 +1,46 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import GB2312DistributionAnalysis +from .mbcssm import GB2312_SM_MODEL + +class GB2312Prober(MultiByteCharSetProber): + def __init__(self): + super(GB2312Prober, self).__init__() + self.coding_sm = CodingStateMachine(GB2312_SM_MODEL) + self.distribution_analyzer = GB2312DistributionAnalysis() + self.reset() + + @property + def charset_name(self): + return "GB2312" + + @property + def language(self): + return "Chinese" diff --git a/openpype/vendor/python/python_2/chardet/hebrewprober.py b/openpype/vendor/python/python_2/chardet/hebrewprober.py new file mode 100644 index 0000000000..b0e1bf4926 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/hebrewprober.py @@ -0,0 +1,292 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Shy Shalom +# Portions created by the Initial Developer are Copyright (C) 2005 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetprober import CharSetProber +from .enums import ProbingState + +# This prober doesn't actually recognize a language or a charset. +# It is a helper prober for the use of the Hebrew model probers + +### General ideas of the Hebrew charset recognition ### +# +# Four main charsets exist in Hebrew: +# "ISO-8859-8" - Visual Hebrew +# "windows-1255" - Logical Hebrew +# "ISO-8859-8-I" - Logical Hebrew +# "x-mac-hebrew" - ?? Logical Hebrew ?? +# +# Both "ISO" charsets use a completely identical set of code points, whereas +# "windows-1255" and "x-mac-hebrew" are two different proper supersets of +# these code points. windows-1255 defines additional characters in the range +# 0x80-0x9F as some misc punctuation marks as well as some Hebrew-specific +# diacritics and additional 'Yiddish' ligature letters in the range 0xc0-0xd6. +# x-mac-hebrew defines similar additional code points but with a different +# mapping. +# +# As far as an average Hebrew text with no diacritics is concerned, all four +# charsets are identical with respect to code points. Meaning that for the +# main Hebrew alphabet, all four map the same values to all 27 Hebrew letters +# (including final letters). +# +# The dominant difference between these charsets is their directionality. +# "Visual" directionality means that the text is ordered as if the renderer is +# not aware of a BIDI rendering algorithm. The renderer sees the text and +# draws it from left to right. The text itself when ordered naturally is read +# backwards. A buffer of Visual Hebrew generally looks like so: +# "[last word of first line spelled backwards] [whole line ordered backwards +# and spelled backwards] [first word of first line spelled backwards] +# [end of line] [last word of second line] ... etc' " +# adding punctuation marks, numbers and English text to visual text is +# naturally also "visual" and from left to right. +# +# "Logical" directionality means the text is ordered "naturally" according to +# the order it is read. It is the responsibility of the renderer to display +# the text from right to left. A BIDI algorithm is used to place general +# punctuation marks, numbers and English text in the text. +# +# Texts in x-mac-hebrew are almost impossible to find on the Internet. From +# what little evidence I could find, it seems that its general directionality +# is Logical. +# +# To sum up all of the above, the Hebrew probing mechanism knows about two +# charsets: +# Visual Hebrew - "ISO-8859-8" - backwards text - Words and sentences are +# backwards while line order is natural. For charset recognition purposes +# the line order is unimportant (In fact, for this implementation, even +# word order is unimportant). +# Logical Hebrew - "windows-1255" - normal, naturally ordered text. +# +# "ISO-8859-8-I" is a subset of windows-1255 and doesn't need to be +# specifically identified. +# "x-mac-hebrew" is also identified as windows-1255. A text in x-mac-hebrew +# that contain special punctuation marks or diacritics is displayed with +# some unconverted characters showing as question marks. This problem might +# be corrected using another model prober for x-mac-hebrew. Due to the fact +# that x-mac-hebrew texts are so rare, writing another model prober isn't +# worth the effort and performance hit. +# +#### The Prober #### +# +# The prober is divided between two SBCharSetProbers and a HebrewProber, +# all of which are managed, created, fed data, inquired and deleted by the +# SBCSGroupProber. The two SBCharSetProbers identify that the text is in +# fact some kind of Hebrew, Logical or Visual. The final decision about which +# one is it is made by the HebrewProber by combining final-letter scores +# with the scores of the two SBCharSetProbers to produce a final answer. +# +# The SBCSGroupProber is responsible for stripping the original text of HTML +# tags, English characters, numbers, low-ASCII punctuation characters, spaces +# and new lines. It reduces any sequence of such characters to a single space. +# The buffer fed to each prober in the SBCS group prober is pure text in +# high-ASCII. +# The two SBCharSetProbers (model probers) share the same language model: +# Win1255Model. +# The first SBCharSetProber uses the model normally as any other +# SBCharSetProber does, to recognize windows-1255, upon which this model was +# built. The second SBCharSetProber is told to make the pair-of-letter +# lookup in the language model backwards. This in practice exactly simulates +# a visual Hebrew model using the windows-1255 logical Hebrew model. +# +# The HebrewProber is not using any language model. All it does is look for +# final-letter evidence suggesting the text is either logical Hebrew or visual +# Hebrew. Disjointed from the model probers, the results of the HebrewProber +# alone are meaningless. HebrewProber always returns 0.00 as confidence +# since it never identifies a charset by itself. Instead, the pointer to the +# HebrewProber is passed to the model probers as a helper "Name Prober". +# When the Group prober receives a positive identification from any prober, +# it asks for the name of the charset identified. If the prober queried is a +# Hebrew model prober, the model prober forwards the call to the +# HebrewProber to make the final decision. In the HebrewProber, the +# decision is made according to the final-letters scores maintained and Both +# model probers scores. The answer is returned in the form of the name of the +# charset identified, either "windows-1255" or "ISO-8859-8". + +class HebrewProber(CharSetProber): + # windows-1255 / ISO-8859-8 code points of interest + FINAL_KAF = 0xea + NORMAL_KAF = 0xeb + FINAL_MEM = 0xed + NORMAL_MEM = 0xee + FINAL_NUN = 0xef + NORMAL_NUN = 0xf0 + FINAL_PE = 0xf3 + NORMAL_PE = 0xf4 + FINAL_TSADI = 0xf5 + NORMAL_TSADI = 0xf6 + + # Minimum Visual vs Logical final letter score difference. + # If the difference is below this, don't rely solely on the final letter score + # distance. + MIN_FINAL_CHAR_DISTANCE = 5 + + # Minimum Visual vs Logical model score difference. + # If the difference is below this, don't rely at all on the model score + # distance. + MIN_MODEL_DISTANCE = 0.01 + + VISUAL_HEBREW_NAME = "ISO-8859-8" + LOGICAL_HEBREW_NAME = "windows-1255" + + def __init__(self): + super(HebrewProber, self).__init__() + self._final_char_logical_score = None + self._final_char_visual_score = None + self._prev = None + self._before_prev = None + self._logical_prober = None + self._visual_prober = None + self.reset() + + def reset(self): + self._final_char_logical_score = 0 + self._final_char_visual_score = 0 + # The two last characters seen in the previous buffer, + # mPrev and mBeforePrev are initialized to space in order to simulate + # a word delimiter at the beginning of the data + self._prev = ' ' + self._before_prev = ' ' + # These probers are owned by the group prober. + + def set_model_probers(self, logicalProber, visualProber): + self._logical_prober = logicalProber + self._visual_prober = visualProber + + def is_final(self, c): + return c in [self.FINAL_KAF, self.FINAL_MEM, self.FINAL_NUN, + self.FINAL_PE, self.FINAL_TSADI] + + def is_non_final(self, c): + # The normal Tsadi is not a good Non-Final letter due to words like + # 'lechotet' (to chat) containing an apostrophe after the tsadi. This + # apostrophe is converted to a space in FilterWithoutEnglishLetters + # causing the Non-Final tsadi to appear at an end of a word even + # though this is not the case in the original text. + # The letters Pe and Kaf rarely display a related behavior of not being + # a good Non-Final letter. Words like 'Pop', 'Winamp' and 'Mubarak' + # for example legally end with a Non-Final Pe or Kaf. However, the + # benefit of these letters as Non-Final letters outweighs the damage + # since these words are quite rare. + return c in [self.NORMAL_KAF, self.NORMAL_MEM, + self.NORMAL_NUN, self.NORMAL_PE] + + def feed(self, byte_str): + # Final letter analysis for logical-visual decision. + # Look for evidence that the received buffer is either logical Hebrew + # or visual Hebrew. + # The following cases are checked: + # 1) A word longer than 1 letter, ending with a final letter. This is + # an indication that the text is laid out "naturally" since the + # final letter really appears at the end. +1 for logical score. + # 2) A word longer than 1 letter, ending with a Non-Final letter. In + # normal Hebrew, words ending with Kaf, Mem, Nun, Pe or Tsadi, + # should not end with the Non-Final form of that letter. Exceptions + # to this rule are mentioned above in isNonFinal(). This is an + # indication that the text is laid out backwards. +1 for visual + # score + # 3) A word longer than 1 letter, starting with a final letter. Final + # letters should not appear at the beginning of a word. This is an + # indication that the text is laid out backwards. +1 for visual + # score. + # + # The visual score and logical score are accumulated throughout the + # text and are finally checked against each other in GetCharSetName(). + # No checking for final letters in the middle of words is done since + # that case is not an indication for either Logical or Visual text. + # + # We automatically filter out all 7-bit characters (replace them with + # spaces) so the word boundary detection works properly. [MAP] + + if self.state == ProbingState.NOT_ME: + # Both model probers say it's not them. No reason to continue. + return ProbingState.NOT_ME + + byte_str = self.filter_high_byte_only(byte_str) + + for cur in byte_str: + if cur == ' ': + # We stand on a space - a word just ended + if self._before_prev != ' ': + # next-to-last char was not a space so self._prev is not a + # 1 letter word + if self.is_final(self._prev): + # case (1) [-2:not space][-1:final letter][cur:space] + self._final_char_logical_score += 1 + elif self.is_non_final(self._prev): + # case (2) [-2:not space][-1:Non-Final letter][ + # cur:space] + self._final_char_visual_score += 1 + else: + # Not standing on a space + if ((self._before_prev == ' ') and + (self.is_final(self._prev)) and (cur != ' ')): + # case (3) [-2:space][-1:final letter][cur:not space] + self._final_char_visual_score += 1 + self._before_prev = self._prev + self._prev = cur + + # Forever detecting, till the end or until both model probers return + # ProbingState.NOT_ME (handled above) + return ProbingState.DETECTING + + @property + def charset_name(self): + # Make the decision: is it Logical or Visual? + # If the final letter score distance is dominant enough, rely on it. + finalsub = self._final_char_logical_score - self._final_char_visual_score + if finalsub >= self.MIN_FINAL_CHAR_DISTANCE: + return self.LOGICAL_HEBREW_NAME + if finalsub <= -self.MIN_FINAL_CHAR_DISTANCE: + return self.VISUAL_HEBREW_NAME + + # It's not dominant enough, try to rely on the model scores instead. + modelsub = (self._logical_prober.get_confidence() + - self._visual_prober.get_confidence()) + if modelsub > self.MIN_MODEL_DISTANCE: + return self.LOGICAL_HEBREW_NAME + if modelsub < -self.MIN_MODEL_DISTANCE: + return self.VISUAL_HEBREW_NAME + + # Still no good, back to final letter distance, maybe it'll save the + # day. + if finalsub < 0.0: + return self.VISUAL_HEBREW_NAME + + # (finalsub > 0 - Logical) or (don't know what to do) default to + # Logical. + return self.LOGICAL_HEBREW_NAME + + @property + def language(self): + return 'Hebrew' + + @property + def state(self): + # Remain active as long as any of the model probers are active. + if (self._logical_prober.state == ProbingState.NOT_ME) and \ + (self._visual_prober.state == ProbingState.NOT_ME): + return ProbingState.NOT_ME + return ProbingState.DETECTING diff --git a/openpype/vendor/python/python_2/chardet/jisfreq.py b/openpype/vendor/python/python_2/chardet/jisfreq.py new file mode 100644 index 0000000000..83fc082b54 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/jisfreq.py @@ -0,0 +1,325 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +# Sampling from about 20M text materials include literature and computer technology +# +# Japanese frequency table, applied to both S-JIS and EUC-JP +# They are sorted in order. + +# 128 --> 0.77094 +# 256 --> 0.85710 +# 512 --> 0.92635 +# 1024 --> 0.97130 +# 2048 --> 0.99431 +# +# Ideal Distribution Ratio = 0.92635 / (1-0.92635) = 12.58 +# Random Distribution Ration = 512 / (2965+62+83+86-512) = 0.191 +# +# Typical Distribution Ratio, 25% of IDR + +JIS_TYPICAL_DISTRIBUTION_RATIO = 3.0 + +# Char to FreqOrder table , +JIS_TABLE_SIZE = 4368 + +JIS_CHAR_TO_FREQ_ORDER = ( + 40, 1, 6, 182, 152, 180, 295,2127, 285, 381,3295,4304,3068,4606,3165,3510, # 16 +3511,1822,2785,4607,1193,2226,5070,4608, 171,2996,1247, 18, 179,5071, 856,1661, # 32 +1262,5072, 619, 127,3431,3512,3230,1899,1700, 232, 228,1294,1298, 284, 283,2041, # 48 +2042,1061,1062, 48, 49, 44, 45, 433, 434,1040,1041, 996, 787,2997,1255,4305, # 64 +2108,4609,1684,1648,5073,5074,5075,5076,5077,5078,3687,5079,4610,5080,3927,3928, # 80 +5081,3296,3432, 290,2285,1471,2187,5082,2580,2825,1303,2140,1739,1445,2691,3375, # 96 +1691,3297,4306,4307,4611, 452,3376,1182,2713,3688,3069,4308,5083,5084,5085,5086, # 112 +5087,5088,5089,5090,5091,5092,5093,5094,5095,5096,5097,5098,5099,5100,5101,5102, # 128 +5103,5104,5105,5106,5107,5108,5109,5110,5111,5112,4097,5113,5114,5115,5116,5117, # 144 +5118,5119,5120,5121,5122,5123,5124,5125,5126,5127,5128,5129,5130,5131,5132,5133, # 160 +5134,5135,5136,5137,5138,5139,5140,5141,5142,5143,5144,5145,5146,5147,5148,5149, # 176 +5150,5151,5152,4612,5153,5154,5155,5156,5157,5158,5159,5160,5161,5162,5163,5164, # 192 +5165,5166,5167,5168,5169,5170,5171,5172,5173,5174,5175,1472, 598, 618, 820,1205, # 208 +1309,1412,1858,1307,1692,5176,5177,5178,5179,5180,5181,5182,1142,1452,1234,1172, # 224 +1875,2043,2149,1793,1382,2973, 925,2404,1067,1241, 960,1377,2935,1491, 919,1217, # 240 +1865,2030,1406,1499,2749,4098,5183,5184,5185,5186,5187,5188,2561,4099,3117,1804, # 256 +2049,3689,4309,3513,1663,5189,3166,3118,3298,1587,1561,3433,5190,3119,1625,2998, # 272 +3299,4613,1766,3690,2786,4614,5191,5192,5193,5194,2161, 26,3377, 2,3929, 20, # 288 +3691, 47,4100, 50, 17, 16, 35, 268, 27, 243, 42, 155, 24, 154, 29, 184, # 304 + 4, 91, 14, 92, 53, 396, 33, 289, 9, 37, 64, 620, 21, 39, 321, 5, # 320 + 12, 11, 52, 13, 3, 208, 138, 0, 7, 60, 526, 141, 151,1069, 181, 275, # 336 +1591, 83, 132,1475, 126, 331, 829, 15, 69, 160, 59, 22, 157, 55,1079, 312, # 352 + 109, 38, 23, 25, 10, 19, 79,5195, 61, 382,1124, 8, 30,5196,5197,5198, # 368 +5199,5200,5201,5202,5203,5204,5205,5206, 89, 62, 74, 34,2416, 112, 139, 196, # 384 + 271, 149, 84, 607, 131, 765, 46, 88, 153, 683, 76, 874, 101, 258, 57, 80, # 400 + 32, 364, 121,1508, 169,1547, 68, 235, 145,2999, 41, 360,3027, 70, 63, 31, # 416 + 43, 259, 262,1383, 99, 533, 194, 66, 93, 846, 217, 192, 56, 106, 58, 565, # 432 + 280, 272, 311, 256, 146, 82, 308, 71, 100, 128, 214, 655, 110, 261, 104,1140, # 448 + 54, 51, 36, 87, 67,3070, 185,2618,2936,2020, 28,1066,2390,2059,5207,5208, # 464 +5209,5210,5211,5212,5213,5214,5215,5216,4615,5217,5218,5219,5220,5221,5222,5223, # 480 +5224,5225,5226,5227,5228,5229,5230,5231,5232,5233,5234,5235,5236,3514,5237,5238, # 496 +5239,5240,5241,5242,5243,5244,2297,2031,4616,4310,3692,5245,3071,5246,3598,5247, # 512 +4617,3231,3515,5248,4101,4311,4618,3808,4312,4102,5249,4103,4104,3599,5250,5251, # 528 +5252,5253,5254,5255,5256,5257,5258,5259,5260,5261,5262,5263,5264,5265,5266,5267, # 544 +5268,5269,5270,5271,5272,5273,5274,5275,5276,5277,5278,5279,5280,5281,5282,5283, # 560 +5284,5285,5286,5287,5288,5289,5290,5291,5292,5293,5294,5295,5296,5297,5298,5299, # 576 +5300,5301,5302,5303,5304,5305,5306,5307,5308,5309,5310,5311,5312,5313,5314,5315, # 592 +5316,5317,5318,5319,5320,5321,5322,5323,5324,5325,5326,5327,5328,5329,5330,5331, # 608 +5332,5333,5334,5335,5336,5337,5338,5339,5340,5341,5342,5343,5344,5345,5346,5347, # 624 +5348,5349,5350,5351,5352,5353,5354,5355,5356,5357,5358,5359,5360,5361,5362,5363, # 640 +5364,5365,5366,5367,5368,5369,5370,5371,5372,5373,5374,5375,5376,5377,5378,5379, # 656 +5380,5381, 363, 642,2787,2878,2788,2789,2316,3232,2317,3434,2011, 165,1942,3930, # 672 +3931,3932,3933,5382,4619,5383,4620,5384,5385,5386,5387,5388,5389,5390,5391,5392, # 688 +5393,5394,5395,5396,5397,5398,5399,5400,5401,5402,5403,5404,5405,5406,5407,5408, # 704 +5409,5410,5411,5412,5413,5414,5415,5416,5417,5418,5419,5420,5421,5422,5423,5424, # 720 +5425,5426,5427,5428,5429,5430,5431,5432,5433,5434,5435,5436,5437,5438,5439,5440, # 736 +5441,5442,5443,5444,5445,5446,5447,5448,5449,5450,5451,5452,5453,5454,5455,5456, # 752 +5457,5458,5459,5460,5461,5462,5463,5464,5465,5466,5467,5468,5469,5470,5471,5472, # 768 +5473,5474,5475,5476,5477,5478,5479,5480,5481,5482,5483,5484,5485,5486,5487,5488, # 784 +5489,5490,5491,5492,5493,5494,5495,5496,5497,5498,5499,5500,5501,5502,5503,5504, # 800 +5505,5506,5507,5508,5509,5510,5511,5512,5513,5514,5515,5516,5517,5518,5519,5520, # 816 +5521,5522,5523,5524,5525,5526,5527,5528,5529,5530,5531,5532,5533,5534,5535,5536, # 832 +5537,5538,5539,5540,5541,5542,5543,5544,5545,5546,5547,5548,5549,5550,5551,5552, # 848 +5553,5554,5555,5556,5557,5558,5559,5560,5561,5562,5563,5564,5565,5566,5567,5568, # 864 +5569,5570,5571,5572,5573,5574,5575,5576,5577,5578,5579,5580,5581,5582,5583,5584, # 880 +5585,5586,5587,5588,5589,5590,5591,5592,5593,5594,5595,5596,5597,5598,5599,5600, # 896 +5601,5602,5603,5604,5605,5606,5607,5608,5609,5610,5611,5612,5613,5614,5615,5616, # 912 +5617,5618,5619,5620,5621,5622,5623,5624,5625,5626,5627,5628,5629,5630,5631,5632, # 928 +5633,5634,5635,5636,5637,5638,5639,5640,5641,5642,5643,5644,5645,5646,5647,5648, # 944 +5649,5650,5651,5652,5653,5654,5655,5656,5657,5658,5659,5660,5661,5662,5663,5664, # 960 +5665,5666,5667,5668,5669,5670,5671,5672,5673,5674,5675,5676,5677,5678,5679,5680, # 976 +5681,5682,5683,5684,5685,5686,5687,5688,5689,5690,5691,5692,5693,5694,5695,5696, # 992 +5697,5698,5699,5700,5701,5702,5703,5704,5705,5706,5707,5708,5709,5710,5711,5712, # 1008 +5713,5714,5715,5716,5717,5718,5719,5720,5721,5722,5723,5724,5725,5726,5727,5728, # 1024 +5729,5730,5731,5732,5733,5734,5735,5736,5737,5738,5739,5740,5741,5742,5743,5744, # 1040 +5745,5746,5747,5748,5749,5750,5751,5752,5753,5754,5755,5756,5757,5758,5759,5760, # 1056 +5761,5762,5763,5764,5765,5766,5767,5768,5769,5770,5771,5772,5773,5774,5775,5776, # 1072 +5777,5778,5779,5780,5781,5782,5783,5784,5785,5786,5787,5788,5789,5790,5791,5792, # 1088 +5793,5794,5795,5796,5797,5798,5799,5800,5801,5802,5803,5804,5805,5806,5807,5808, # 1104 +5809,5810,5811,5812,5813,5814,5815,5816,5817,5818,5819,5820,5821,5822,5823,5824, # 1120 +5825,5826,5827,5828,5829,5830,5831,5832,5833,5834,5835,5836,5837,5838,5839,5840, # 1136 +5841,5842,5843,5844,5845,5846,5847,5848,5849,5850,5851,5852,5853,5854,5855,5856, # 1152 +5857,5858,5859,5860,5861,5862,5863,5864,5865,5866,5867,5868,5869,5870,5871,5872, # 1168 +5873,5874,5875,5876,5877,5878,5879,5880,5881,5882,5883,5884,5885,5886,5887,5888, # 1184 +5889,5890,5891,5892,5893,5894,5895,5896,5897,5898,5899,5900,5901,5902,5903,5904, # 1200 +5905,5906,5907,5908,5909,5910,5911,5912,5913,5914,5915,5916,5917,5918,5919,5920, # 1216 +5921,5922,5923,5924,5925,5926,5927,5928,5929,5930,5931,5932,5933,5934,5935,5936, # 1232 +5937,5938,5939,5940,5941,5942,5943,5944,5945,5946,5947,5948,5949,5950,5951,5952, # 1248 +5953,5954,5955,5956,5957,5958,5959,5960,5961,5962,5963,5964,5965,5966,5967,5968, # 1264 +5969,5970,5971,5972,5973,5974,5975,5976,5977,5978,5979,5980,5981,5982,5983,5984, # 1280 +5985,5986,5987,5988,5989,5990,5991,5992,5993,5994,5995,5996,5997,5998,5999,6000, # 1296 +6001,6002,6003,6004,6005,6006,6007,6008,6009,6010,6011,6012,6013,6014,6015,6016, # 1312 +6017,6018,6019,6020,6021,6022,6023,6024,6025,6026,6027,6028,6029,6030,6031,6032, # 1328 +6033,6034,6035,6036,6037,6038,6039,6040,6041,6042,6043,6044,6045,6046,6047,6048, # 1344 +6049,6050,6051,6052,6053,6054,6055,6056,6057,6058,6059,6060,6061,6062,6063,6064, # 1360 +6065,6066,6067,6068,6069,6070,6071,6072,6073,6074,6075,6076,6077,6078,6079,6080, # 1376 +6081,6082,6083,6084,6085,6086,6087,6088,6089,6090,6091,6092,6093,6094,6095,6096, # 1392 +6097,6098,6099,6100,6101,6102,6103,6104,6105,6106,6107,6108,6109,6110,6111,6112, # 1408 +6113,6114,2044,2060,4621, 997,1235, 473,1186,4622, 920,3378,6115,6116, 379,1108, # 1424 +4313,2657,2735,3934,6117,3809, 636,3233, 573,1026,3693,3435,2974,3300,2298,4105, # 1440 + 854,2937,2463, 393,2581,2417, 539, 752,1280,2750,2480, 140,1161, 440, 708,1569, # 1456 + 665,2497,1746,1291,1523,3000, 164,1603, 847,1331, 537,1997, 486, 508,1693,2418, # 1472 +1970,2227, 878,1220, 299,1030, 969, 652,2751, 624,1137,3301,2619, 65,3302,2045, # 1488 +1761,1859,3120,1930,3694,3516, 663,1767, 852, 835,3695, 269, 767,2826,2339,1305, # 1504 + 896,1150, 770,1616,6118, 506,1502,2075,1012,2519, 775,2520,2975,2340,2938,4314, # 1520 +3028,2086,1224,1943,2286,6119,3072,4315,2240,1273,1987,3935,1557, 175, 597, 985, # 1536 +3517,2419,2521,1416,3029, 585, 938,1931,1007,1052,1932,1685,6120,3379,4316,4623, # 1552 + 804, 599,3121,1333,2128,2539,1159,1554,2032,3810, 687,2033,2904, 952, 675,1467, # 1568 +3436,6121,2241,1096,1786,2440,1543,1924, 980,1813,2228, 781,2692,1879, 728,1918, # 1584 +3696,4624, 548,1950,4625,1809,1088,1356,3303,2522,1944, 502, 972, 373, 513,2827, # 1600 + 586,2377,2391,1003,1976,1631,6122,2464,1084, 648,1776,4626,2141, 324, 962,2012, # 1616 +2177,2076,1384, 742,2178,1448,1173,1810, 222, 102, 301, 445, 125,2420, 662,2498, # 1632 + 277, 200,1476,1165,1068, 224,2562,1378,1446, 450,1880, 659, 791, 582,4627,2939, # 1648 +3936,1516,1274, 555,2099,3697,1020,1389,1526,3380,1762,1723,1787,2229, 412,2114, # 1664 +1900,2392,3518, 512,2597, 427,1925,2341,3122,1653,1686,2465,2499, 697, 330, 273, # 1680 + 380,2162, 951, 832, 780, 991,1301,3073, 965,2270,3519, 668,2523,2636,1286, 535, # 1696 +1407, 518, 671, 957,2658,2378, 267, 611,2197,3030,6123, 248,2299, 967,1799,2356, # 1712 + 850,1418,3437,1876,1256,1480,2828,1718,6124,6125,1755,1664,2405,6126,4628,2879, # 1728 +2829, 499,2179, 676,4629, 557,2329,2214,2090, 325,3234, 464, 811,3001, 992,2342, # 1744 +2481,1232,1469, 303,2242, 466,1070,2163, 603,1777,2091,4630,2752,4631,2714, 322, # 1760 +2659,1964,1768, 481,2188,1463,2330,2857,3600,2092,3031,2421,4632,2318,2070,1849, # 1776 +2598,4633,1302,2254,1668,1701,2422,3811,2905,3032,3123,2046,4106,1763,1694,4634, # 1792 +1604, 943,1724,1454, 917, 868,2215,1169,2940, 552,1145,1800,1228,1823,1955, 316, # 1808 +1080,2510, 361,1807,2830,4107,2660,3381,1346,1423,1134,4108,6127, 541,1263,1229, # 1824 +1148,2540, 545, 465,1833,2880,3438,1901,3074,2482, 816,3937, 713,1788,2500, 122, # 1840 +1575, 195,1451,2501,1111,6128, 859, 374,1225,2243,2483,4317, 390,1033,3439,3075, # 1856 +2524,1687, 266, 793,1440,2599, 946, 779, 802, 507, 897,1081, 528,2189,1292, 711, # 1872 +1866,1725,1167,1640, 753, 398,2661,1053, 246, 348,4318, 137,1024,3440,1600,2077, # 1888 +2129, 825,4319, 698, 238, 521, 187,2300,1157,2423,1641,1605,1464,1610,1097,2541, # 1904 +1260,1436, 759,2255,1814,2150, 705,3235, 409,2563,3304, 561,3033,2005,2564, 726, # 1920 +1956,2343,3698,4109, 949,3812,3813,3520,1669, 653,1379,2525, 881,2198, 632,2256, # 1936 +1027, 778,1074, 733,1957, 514,1481,2466, 554,2180, 702,3938,1606,1017,1398,6129, # 1952 +1380,3521, 921, 993,1313, 594, 449,1489,1617,1166, 768,1426,1360, 495,1794,3601, # 1968 +1177,3602,1170,4320,2344, 476, 425,3167,4635,3168,1424, 401,2662,1171,3382,1998, # 1984 +1089,4110, 477,3169, 474,6130,1909, 596,2831,1842, 494, 693,1051,1028,1207,3076, # 2000 + 606,2115, 727,2790,1473,1115, 743,3522, 630, 805,1532,4321,2021, 366,1057, 838, # 2016 + 684,1114,2142,4322,2050,1492,1892,1808,2271,3814,2424,1971,1447,1373,3305,1090, # 2032 +1536,3939,3523,3306,1455,2199, 336, 369,2331,1035, 584,2393, 902, 718,2600,6131, # 2048 +2753, 463,2151,1149,1611,2467, 715,1308,3124,1268, 343,1413,3236,1517,1347,2663, # 2064 +2093,3940,2022,1131,1553,2100,2941,1427,3441,2942,1323,2484,6132,1980, 872,2368, # 2080 +2441,2943, 320,2369,2116,1082, 679,1933,3941,2791,3815, 625,1143,2023, 422,2200, # 2096 +3816,6133, 730,1695, 356,2257,1626,2301,2858,2637,1627,1778, 937, 883,2906,2693, # 2112 +3002,1769,1086, 400,1063,1325,3307,2792,4111,3077, 456,2345,1046, 747,6134,1524, # 2128 + 884,1094,3383,1474,2164,1059, 974,1688,2181,2258,1047, 345,1665,1187, 358, 875, # 2144 +3170, 305, 660,3524,2190,1334,1135,3171,1540,1649,2542,1527, 927, 968,2793, 885, # 2160 +1972,1850, 482, 500,2638,1218,1109,1085,2543,1654,2034, 876, 78,2287,1482,1277, # 2176 + 861,1675,1083,1779, 724,2754, 454, 397,1132,1612,2332, 893, 672,1237, 257,2259, # 2192 +2370, 135,3384, 337,2244, 547, 352, 340, 709,2485,1400, 788,1138,2511, 540, 772, # 2208 +1682,2260,2272,2544,2013,1843,1902,4636,1999,1562,2288,4637,2201,1403,1533, 407, # 2224 + 576,3308,1254,2071, 978,3385, 170, 136,1201,3125,2664,3172,2394, 213, 912, 873, # 2240 +3603,1713,2202, 699,3604,3699, 813,3442, 493, 531,1054, 468,2907,1483, 304, 281, # 2256 +4112,1726,1252,2094, 339,2319,2130,2639, 756,1563,2944, 748, 571,2976,1588,2425, # 2272 +2715,1851,1460,2426,1528,1392,1973,3237, 288,3309, 685,3386, 296, 892,2716,2216, # 2288 +1570,2245, 722,1747,2217, 905,3238,1103,6135,1893,1441,1965, 251,1805,2371,3700, # 2304 +2601,1919,1078, 75,2182,1509,1592,1270,2640,4638,2152,6136,3310,3817, 524, 706, # 2320 +1075, 292,3818,1756,2602, 317, 98,3173,3605,3525,1844,2218,3819,2502, 814, 567, # 2336 + 385,2908,1534,6137, 534,1642,3239, 797,6138,1670,1529, 953,4323, 188,1071, 538, # 2352 + 178, 729,3240,2109,1226,1374,2000,2357,2977, 731,2468,1116,2014,2051,6139,1261, # 2368 +1593, 803,2859,2736,3443, 556, 682, 823,1541,6140,1369,2289,1706,2794, 845, 462, # 2384 +2603,2665,1361, 387, 162,2358,1740, 739,1770,1720,1304,1401,3241,1049, 627,1571, # 2400 +2427,3526,1877,3942,1852,1500, 431,1910,1503, 677, 297,2795, 286,1433,1038,1198, # 2416 +2290,1133,1596,4113,4639,2469,1510,1484,3943,6141,2442, 108, 712,4640,2372, 866, # 2432 +3701,2755,3242,1348, 834,1945,1408,3527,2395,3243,1811, 824, 994,1179,2110,1548, # 2448 +1453, 790,3003, 690,4324,4325,2832,2909,3820,1860,3821, 225,1748, 310, 346,1780, # 2464 +2470, 821,1993,2717,2796, 828, 877,3528,2860,2471,1702,2165,2910,2486,1789, 453, # 2480 + 359,2291,1676, 73,1164,1461,1127,3311, 421, 604, 314,1037, 589, 116,2487, 737, # 2496 + 837,1180, 111, 244, 735,6142,2261,1861,1362, 986, 523, 418, 581,2666,3822, 103, # 2512 + 855, 503,1414,1867,2488,1091, 657,1597, 979, 605,1316,4641,1021,2443,2078,2001, # 2528 +1209, 96, 587,2166,1032, 260,1072,2153, 173, 94, 226,3244, 819,2006,4642,4114, # 2544 +2203, 231,1744, 782, 97,2667, 786,3387, 887, 391, 442,2219,4326,1425,6143,2694, # 2560 + 633,1544,1202, 483,2015, 592,2052,1958,2472,1655, 419, 129,4327,3444,3312,1714, # 2576 +1257,3078,4328,1518,1098, 865,1310,1019,1885,1512,1734, 469,2444, 148, 773, 436, # 2592 +1815,1868,1128,1055,4329,1245,2756,3445,2154,1934,1039,4643, 579,1238, 932,2320, # 2608 + 353, 205, 801, 115,2428, 944,2321,1881, 399,2565,1211, 678, 766,3944, 335,2101, # 2624 +1459,1781,1402,3945,2737,2131,1010, 844, 981,1326,1013, 550,1816,1545,2620,1335, # 2640 +1008, 371,2881, 936,1419,1613,3529,1456,1395,2273,1834,2604,1317,2738,2503, 416, # 2656 +1643,4330, 806,1126, 229, 591,3946,1314,1981,1576,1837,1666, 347,1790, 977,3313, # 2672 + 764,2861,1853, 688,2429,1920,1462, 77, 595, 415,2002,3034, 798,1192,4115,6144, # 2688 +2978,4331,3035,2695,2582,2072,2566, 430,2430,1727, 842,1396,3947,3702, 613, 377, # 2704 + 278, 236,1417,3388,3314,3174, 757,1869, 107,3530,6145,1194, 623,2262, 207,1253, # 2720 +2167,3446,3948, 492,1117,1935, 536,1838,2757,1246,4332, 696,2095,2406,1393,1572, # 2736 +3175,1782, 583, 190, 253,1390,2230, 830,3126,3389, 934,3245,1703,1749,2979,1870, # 2752 +2545,1656,2204, 869,2346,4116,3176,1817, 496,1764,4644, 942,1504, 404,1903,1122, # 2768 +1580,3606,2945,1022, 515, 372,1735, 955,2431,3036,6146,2797,1110,2302,2798, 617, # 2784 +6147, 441, 762,1771,3447,3607,3608,1904, 840,3037, 86, 939,1385, 572,1370,2445, # 2800 +1336, 114,3703, 898, 294, 203,3315, 703,1583,2274, 429, 961,4333,1854,1951,3390, # 2816 +2373,3704,4334,1318,1381, 966,1911,2322,1006,1155, 309, 989, 458,2718,1795,1372, # 2832 +1203, 252,1689,1363,3177, 517,1936, 168,1490, 562, 193,3823,1042,4117,1835, 551, # 2848 + 470,4645, 395, 489,3448,1871,1465,2583,2641, 417,1493, 279,1295, 511,1236,1119, # 2864 + 72,1231,1982,1812,3004, 871,1564, 984,3449,1667,2696,2096,4646,2347,2833,1673, # 2880 +3609, 695,3246,2668, 807,1183,4647, 890, 388,2333,1801,1457,2911,1765,1477,1031, # 2896 +3316,3317,1278,3391,2799,2292,2526, 163,3450,4335,2669,1404,1802,6148,2323,2407, # 2912 +1584,1728,1494,1824,1269, 298, 909,3318,1034,1632, 375, 776,1683,2061, 291, 210, # 2928 +1123, 809,1249,1002,2642,3038, 206,1011,2132, 144, 975, 882,1565, 342, 667, 754, # 2944 +1442,2143,1299,2303,2062, 447, 626,2205,1221,2739,2912,1144,1214,2206,2584, 760, # 2960 +1715, 614, 950,1281,2670,2621, 810, 577,1287,2546,4648, 242,2168, 250,2643, 691, # 2976 + 123,2644, 647, 313,1029, 689,1357,2946,1650, 216, 771,1339,1306, 808,2063, 549, # 2992 + 913,1371,2913,2914,6149,1466,1092,1174,1196,1311,2605,2396,1783,1796,3079, 406, # 3008 +2671,2117,3949,4649, 487,1825,2220,6150,2915, 448,2348,1073,6151,2397,1707, 130, # 3024 + 900,1598, 329, 176,1959,2527,1620,6152,2275,4336,3319,1983,2191,3705,3610,2155, # 3040 +3706,1912,1513,1614,6153,1988, 646, 392,2304,1589,3320,3039,1826,1239,1352,1340, # 3056 +2916, 505,2567,1709,1437,2408,2547, 906,6154,2672, 384,1458,1594,1100,1329, 710, # 3072 + 423,3531,2064,2231,2622,1989,2673,1087,1882, 333, 841,3005,1296,2882,2379, 580, # 3088 +1937,1827,1293,2585, 601, 574, 249,1772,4118,2079,1120, 645, 901,1176,1690, 795, # 3104 +2207, 478,1434, 516,1190,1530, 761,2080, 930,1264, 355, 435,1552, 644,1791, 987, # 3120 + 220,1364,1163,1121,1538, 306,2169,1327,1222, 546,2645, 218, 241, 610,1704,3321, # 3136 +1984,1839,1966,2528, 451,6155,2586,3707,2568, 907,3178, 254,2947, 186,1845,4650, # 3152 + 745, 432,1757, 428,1633, 888,2246,2221,2489,3611,2118,1258,1265, 956,3127,1784, # 3168 +4337,2490, 319, 510, 119, 457,3612, 274,2035,2007,4651,1409,3128, 970,2758, 590, # 3184 +2800, 661,2247,4652,2008,3950,1420,1549,3080,3322,3951,1651,1375,2111, 485,2491, # 3200 +1429,1156,6156,2548,2183,1495, 831,1840,2529,2446, 501,1657, 307,1894,3247,1341, # 3216 + 666, 899,2156,1539,2549,1559, 886, 349,2208,3081,2305,1736,3824,2170,2759,1014, # 3232 +1913,1386, 542,1397,2948, 490, 368, 716, 362, 159, 282,2569,1129,1658,1288,1750, # 3248 +2674, 276, 649,2016, 751,1496, 658,1818,1284,1862,2209,2087,2512,3451, 622,2834, # 3264 + 376, 117,1060,2053,1208,1721,1101,1443, 247,1250,3179,1792,3952,2760,2398,3953, # 3280 +6157,2144,3708, 446,2432,1151,2570,3452,2447,2761,2835,1210,2448,3082, 424,2222, # 3296 +1251,2449,2119,2836, 504,1581,4338, 602, 817, 857,3825,2349,2306, 357,3826,1470, # 3312 +1883,2883, 255, 958, 929,2917,3248, 302,4653,1050,1271,1751,2307,1952,1430,2697, # 3328 +2719,2359, 354,3180, 777, 158,2036,4339,1659,4340,4654,2308,2949,2248,1146,2232, # 3344 +3532,2720,1696,2623,3827,6158,3129,1550,2698,1485,1297,1428, 637, 931,2721,2145, # 3360 + 914,2550,2587, 81,2450, 612, 827,2646,1242,4655,1118,2884, 472,1855,3181,3533, # 3376 +3534, 569,1353,2699,1244,1758,2588,4119,2009,2762,2171,3709,1312,1531,6159,1152, # 3392 +1938, 134,1830, 471,3710,2276,1112,1535,3323,3453,3535, 982,1337,2950, 488, 826, # 3408 + 674,1058,1628,4120,2017, 522,2399, 211, 568,1367,3454, 350, 293,1872,1139,3249, # 3424 +1399,1946,3006,1300,2360,3324, 588, 736,6160,2606, 744, 669,3536,3828,6161,1358, # 3440 + 199, 723, 848, 933, 851,1939,1505,1514,1338,1618,1831,4656,1634,3613, 443,2740, # 3456 +3829, 717,1947, 491,1914,6162,2551,1542,4121,1025,6163,1099,1223, 198,3040,2722, # 3472 + 370, 410,1905,2589, 998,1248,3182,2380, 519,1449,4122,1710, 947, 928,1153,4341, # 3488 +2277, 344,2624,1511, 615, 105, 161,1212,1076,1960,3130,2054,1926,1175,1906,2473, # 3504 + 414,1873,2801,6164,2309, 315,1319,3325, 318,2018,2146,2157, 963, 631, 223,4342, # 3520 +4343,2675, 479,3711,1197,2625,3712,2676,2361,6165,4344,4123,6166,2451,3183,1886, # 3536 +2184,1674,1330,1711,1635,1506, 799, 219,3250,3083,3954,1677,3713,3326,2081,3614, # 3552 +1652,2073,4657,1147,3041,1752, 643,1961, 147,1974,3955,6167,1716,2037, 918,3007, # 3568 +1994, 120,1537, 118, 609,3184,4345, 740,3455,1219, 332,1615,3830,6168,1621,2980, # 3584 +1582, 783, 212, 553,2350,3714,1349,2433,2082,4124, 889,6169,2310,1275,1410, 973, # 3600 + 166,1320,3456,1797,1215,3185,2885,1846,2590,2763,4658, 629, 822,3008, 763, 940, # 3616 +1990,2862, 439,2409,1566,1240,1622, 926,1282,1907,2764, 654,2210,1607, 327,1130, # 3632 +3956,1678,1623,6170,2434,2192, 686, 608,3831,3715, 903,3957,3042,6171,2741,1522, # 3648 +1915,1105,1555,2552,1359, 323,3251,4346,3457, 738,1354,2553,2311,2334,1828,2003, # 3664 +3832,1753,2351,1227,6172,1887,4125,1478,6173,2410,1874,1712,1847, 520,1204,2607, # 3680 + 264,4659, 836,2677,2102, 600,4660,3833,2278,3084,6174,4347,3615,1342, 640, 532, # 3696 + 543,2608,1888,2400,2591,1009,4348,1497, 341,1737,3616,2723,1394, 529,3252,1321, # 3712 + 983,4661,1515,2120, 971,2592, 924, 287,1662,3186,4349,2700,4350,1519, 908,1948, # 3728 +2452, 156, 796,1629,1486,2223,2055, 694,4126,1259,1036,3392,1213,2249,2742,1889, # 3744 +1230,3958,1015, 910, 408, 559,3617,4662, 746, 725, 935,4663,3959,3009,1289, 563, # 3760 + 867,4664,3960,1567,2981,2038,2626, 988,2263,2381,4351, 143,2374, 704,1895,6175, # 3776 +1188,3716,2088, 673,3085,2362,4352, 484,1608,1921,2765,2918, 215, 904,3618,3537, # 3792 + 894, 509, 976,3043,2701,3961,4353,2837,2982, 498,6176,6177,1102,3538,1332,3393, # 3808 +1487,1636,1637, 233, 245,3962, 383, 650, 995,3044, 460,1520,1206,2352, 749,3327, # 3824 + 530, 700, 389,1438,1560,1773,3963,2264, 719,2951,2724,3834, 870,1832,1644,1000, # 3840 + 839,2474,3717, 197,1630,3394, 365,2886,3964,1285,2133, 734, 922, 818,1106, 732, # 3856 + 480,2083,1774,3458, 923,2279,1350, 221,3086, 85,2233,2234,3835,1585,3010,2147, # 3872 +1387,1705,2382,1619,2475, 133, 239,2802,1991,1016,2084,2383, 411,2838,1113, 651, # 3888 +1985,1160,3328, 990,1863,3087,1048,1276,2647, 265,2627,1599,3253,2056, 150, 638, # 3904 +2019, 656, 853, 326,1479, 680,1439,4354,1001,1759, 413,3459,3395,2492,1431, 459, # 3920 +4355,1125,3329,2265,1953,1450,2065,2863, 849, 351,2678,3131,3254,3255,1104,1577, # 3936 + 227,1351,1645,2453,2193,1421,2887, 812,2121, 634, 95,2435, 201,2312,4665,1646, # 3952 +1671,2743,1601,2554,2702,2648,2280,1315,1366,2089,3132,1573,3718,3965,1729,1189, # 3968 + 328,2679,1077,1940,1136, 558,1283, 964,1195, 621,2074,1199,1743,3460,3619,1896, # 3984 +1916,1890,3836,2952,1154,2112,1064, 862, 378,3011,2066,2113,2803,1568,2839,6178, # 4000 +3088,2919,1941,1660,2004,1992,2194, 142, 707,1590,1708,1624,1922,1023,1836,1233, # 4016 +1004,2313, 789, 741,3620,6179,1609,2411,1200,4127,3719,3720,4666,2057,3721, 593, # 4032 +2840, 367,2920,1878,6180,3461,1521, 628,1168, 692,2211,2649, 300, 720,2067,2571, # 4048 +2953,3396, 959,2504,3966,3539,3462,1977, 701,6181, 954,1043, 800, 681, 183,3722, # 4064 +1803,1730,3540,4128,2103, 815,2314, 174, 467, 230,2454,1093,2134, 755,3541,3397, # 4080 +1141,1162,6182,1738,2039, 270,3256,2513,1005,1647,2185,3837, 858,1679,1897,1719, # 4096 +2954,2324,1806, 402, 670, 167,4129,1498,2158,2104, 750,6183, 915, 189,1680,1551, # 4112 + 455,4356,1501,2455, 405,1095,2955, 338,1586,1266,1819, 570, 641,1324, 237,1556, # 4128 +2650,1388,3723,6184,1368,2384,1343,1978,3089,2436, 879,3724, 792,1191, 758,3012, # 4144 +1411,2135,1322,4357, 240,4667,1848,3725,1574,6185, 420,3045,1546,1391, 714,4358, # 4160 +1967, 941,1864, 863, 664, 426, 560,1731,2680,1785,2864,1949,2363, 403,3330,1415, # 4176 +1279,2136,1697,2335, 204, 721,2097,3838, 90,6186,2085,2505, 191,3967, 124,2148, # 4192 +1376,1798,1178,1107,1898,1405, 860,4359,1243,1272,2375,2983,1558,2456,1638, 113, # 4208 +3621, 578,1923,2609, 880, 386,4130, 784,2186,2266,1422,2956,2172,1722, 497, 263, # 4224 +2514,1267,2412,2610, 177,2703,3542, 774,1927,1344, 616,1432,1595,1018, 172,4360, # 4240 +2325, 911,4361, 438,1468,3622, 794,3968,2024,2173,1681,1829,2957, 945, 895,3090, # 4256 + 575,2212,2476, 475,2401,2681, 785,2744,1745,2293,2555,1975,3133,2865, 394,4668, # 4272 +3839, 635,4131, 639, 202,1507,2195,2766,1345,1435,2572,3726,1908,1184,1181,2457, # 4288 +3727,3134,4362, 843,2611, 437, 916,4669, 234, 769,1884,3046,3047,3623, 833,6187, # 4304 +1639,2250,2402,1355,1185,2010,2047, 999, 525,1732,1290,1488,2612, 948,1578,3728, # 4320 +2413,2477,1216,2725,2159, 334,3840,1328,3624,2921,1525,4132, 564,1056, 891,4363, # 4336 +1444,1698,2385,2251,3729,1365,2281,2235,1717,6188, 864,3841,2515, 444, 527,2767, # 4352 +2922,3625, 544, 461,6189, 566, 209,2437,3398,2098,1065,2068,3331,3626,3257,2137, # 4368 #last 512 +) + + diff --git a/openpype/vendor/python/python_2/chardet/jpcntx.py b/openpype/vendor/python/python_2/chardet/jpcntx.py new file mode 100644 index 0000000000..20044e4bc8 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/jpcntx.py @@ -0,0 +1,233 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Communicator client code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + + +# This is hiragana 2-char sequence table, the number in each cell represents its frequency category +jp2CharContext = ( +(0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1), +(2,4,0,4,0,3,0,4,0,3,4,4,4,2,4,3,3,4,3,2,3,3,4,2,3,3,3,2,4,1,4,3,3,1,5,4,3,4,3,4,3,5,3,0,3,5,4,2,0,3,1,0,3,3,0,3,3,0,1,1,0,4,3,0,3,3,0,4,0,2,0,3,5,5,5,5,4,0,4,1,0,3,4), +(0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2), +(0,4,0,5,0,5,0,4,0,4,5,4,4,3,5,3,5,1,5,3,4,3,4,4,3,4,3,3,4,3,5,4,4,3,5,5,3,5,5,5,3,5,5,3,4,5,5,3,1,3,2,0,3,4,0,4,2,0,4,2,1,5,3,2,3,5,0,4,0,2,0,5,4,4,5,4,5,0,4,0,0,4,4), +(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), +(0,3,0,4,0,3,0,3,0,4,5,4,3,3,3,3,4,3,5,4,4,3,5,4,4,3,4,3,4,4,4,4,5,3,4,4,3,4,5,5,4,5,5,1,4,5,4,3,0,3,3,1,3,3,0,4,4,0,3,3,1,5,3,3,3,5,0,4,0,3,0,4,4,3,4,3,3,0,4,1,1,3,4), +(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), +(0,4,0,3,0,3,0,4,0,3,4,4,3,2,2,1,2,1,3,1,3,3,3,3,3,4,3,1,3,3,5,3,3,0,4,3,0,5,4,3,3,5,4,4,3,4,4,5,0,1,2,0,1,2,0,2,2,0,1,0,0,5,2,2,1,4,0,3,0,1,0,4,4,3,5,4,3,0,2,1,0,4,3), +(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), +(0,3,0,5,0,4,0,2,1,4,4,2,4,1,4,2,4,2,4,3,3,3,4,3,3,3,3,1,4,2,3,3,3,1,4,4,1,1,1,4,3,3,2,0,2,4,3,2,0,3,3,0,3,1,1,0,0,0,3,3,0,4,2,2,3,4,0,4,0,3,0,4,4,5,3,4,4,0,3,0,0,1,4), +(1,4,0,4,0,4,0,4,0,3,5,4,4,3,4,3,5,4,3,3,4,3,5,4,4,4,4,3,4,2,4,3,3,1,5,4,3,2,4,5,4,5,5,4,4,5,4,4,0,3,2,2,3,3,0,4,3,1,3,2,1,4,3,3,4,5,0,3,0,2,0,4,5,5,4,5,4,0,4,0,0,5,4), +(0,5,0,5,0,4,0,3,0,4,4,3,4,3,3,3,4,0,4,4,4,3,4,3,4,3,3,1,4,2,4,3,4,0,5,4,1,4,5,4,4,5,3,2,4,3,4,3,2,4,1,3,3,3,2,3,2,0,4,3,3,4,3,3,3,4,0,4,0,3,0,4,5,4,4,4,3,0,4,1,0,1,3), +(0,3,1,4,0,3,0,2,0,3,4,4,3,1,4,2,3,3,4,3,4,3,4,3,4,4,3,2,3,1,5,4,4,1,4,4,3,5,4,4,3,5,5,4,3,4,4,3,1,2,3,1,2,2,0,3,2,0,3,1,0,5,3,3,3,4,3,3,3,3,4,4,4,4,5,4,2,0,3,3,2,4,3), +(0,2,0,3,0,1,0,1,0,0,3,2,0,0,2,0,1,0,2,1,3,3,3,1,2,3,1,0,1,0,4,2,1,1,3,3,0,4,3,3,1,4,3,3,0,3,3,2,0,0,0,0,1,0,0,2,0,0,0,0,0,4,1,0,2,3,2,2,2,1,3,3,3,4,4,3,2,0,3,1,0,3,3), +(0,4,0,4,0,3,0,3,0,4,4,4,3,3,3,3,3,3,4,3,4,2,4,3,4,3,3,2,4,3,4,5,4,1,4,5,3,5,4,5,3,5,4,0,3,5,5,3,1,3,3,2,2,3,0,3,4,1,3,3,2,4,3,3,3,4,0,4,0,3,0,4,5,4,4,5,3,0,4,1,0,3,4), +(0,2,0,3,0,3,0,0,0,2,2,2,1,0,1,0,0,0,3,0,3,0,3,0,1,3,1,0,3,1,3,3,3,1,3,3,3,0,1,3,1,3,4,0,0,3,1,1,0,3,2,0,0,0,0,1,3,0,1,0,0,3,3,2,0,3,0,0,0,0,0,3,4,3,4,3,3,0,3,0,0,2,3), +(2,3,0,3,0,2,0,1,0,3,3,4,3,1,3,1,1,1,3,1,4,3,4,3,3,3,0,0,3,1,5,4,3,1,4,3,2,5,5,4,4,4,4,3,3,4,4,4,0,2,1,1,3,2,0,1,2,0,0,1,0,4,1,3,3,3,0,3,0,1,0,4,4,4,5,5,3,0,2,0,0,4,4), +(0,2,0,1,0,3,1,3,0,2,3,3,3,0,3,1,0,0,3,0,3,2,3,1,3,2,1,1,0,0,4,2,1,0,2,3,1,4,3,2,0,4,4,3,1,3,1,3,0,1,0,0,1,0,0,0,1,0,0,0,0,4,1,1,1,2,0,3,0,0,0,3,4,2,4,3,2,0,1,0,0,3,3), +(0,1,0,4,0,5,0,4,0,2,4,4,2,3,3,2,3,3,5,3,3,3,4,3,4,2,3,0,4,3,3,3,4,1,4,3,2,1,5,5,3,4,5,1,3,5,4,2,0,3,3,0,1,3,0,4,2,0,1,3,1,4,3,3,3,3,0,3,0,1,0,3,4,4,4,5,5,0,3,0,1,4,5), +(0,2,0,3,0,3,0,0,0,2,3,1,3,0,4,0,1,1,3,0,3,4,3,2,3,1,0,3,3,2,3,1,3,0,2,3,0,2,1,4,1,2,2,0,0,3,3,0,0,2,0,0,0,1,0,0,0,0,2,2,0,3,2,1,3,3,0,2,0,2,0,0,3,3,1,2,4,0,3,0,2,2,3), +(2,4,0,5,0,4,0,4,0,2,4,4,4,3,4,3,3,3,1,2,4,3,4,3,4,4,5,0,3,3,3,3,2,0,4,3,1,4,3,4,1,4,4,3,3,4,4,3,1,2,3,0,4,2,0,4,1,0,3,3,0,4,3,3,3,4,0,4,0,2,0,3,5,3,4,5,2,0,3,0,0,4,5), +(0,3,0,4,0,1,0,1,0,1,3,2,2,1,3,0,3,0,2,0,2,0,3,0,2,0,0,0,1,0,1,1,0,0,3,1,0,0,0,4,0,3,1,0,2,1,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,4,2,2,3,1,0,3,0,0,0,1,4,4,4,3,0,0,4,0,0,1,4), +(1,4,1,5,0,3,0,3,0,4,5,4,4,3,5,3,3,4,4,3,4,1,3,3,3,3,2,1,4,1,5,4,3,1,4,4,3,5,4,4,3,5,4,3,3,4,4,4,0,3,3,1,2,3,0,3,1,0,3,3,0,5,4,4,4,4,4,4,3,3,5,4,4,3,3,5,4,0,3,2,0,4,4), +(0,2,0,3,0,1,0,0,0,1,3,3,3,2,4,1,3,0,3,1,3,0,2,2,1,1,0,0,2,0,4,3,1,0,4,3,0,4,4,4,1,4,3,1,1,3,3,1,0,2,0,0,1,3,0,0,0,0,2,0,0,4,3,2,4,3,5,4,3,3,3,4,3,3,4,3,3,0,2,1,0,3,3), +(0,2,0,4,0,3,0,2,0,2,5,5,3,4,4,4,4,1,4,3,3,0,4,3,4,3,1,3,3,2,4,3,0,3,4,3,0,3,4,4,2,4,4,0,4,5,3,3,2,2,1,1,1,2,0,1,5,0,3,3,2,4,3,3,3,4,0,3,0,2,0,4,4,3,5,5,0,0,3,0,2,3,3), +(0,3,0,4,0,3,0,1,0,3,4,3,3,1,3,3,3,0,3,1,3,0,4,3,3,1,1,0,3,0,3,3,0,0,4,4,0,1,5,4,3,3,5,0,3,3,4,3,0,2,0,1,1,1,0,1,3,0,1,2,1,3,3,2,3,3,0,3,0,1,0,1,3,3,4,4,1,0,1,2,2,1,3), +(0,1,0,4,0,4,0,3,0,1,3,3,3,2,3,1,1,0,3,0,3,3,4,3,2,4,2,0,1,0,4,3,2,0,4,3,0,5,3,3,2,4,4,4,3,3,3,4,0,1,3,0,0,1,0,0,1,0,0,0,0,4,2,3,3,3,0,3,0,0,0,4,4,4,5,3,2,0,3,3,0,3,5), +(0,2,0,3,0,0,0,3,0,1,3,0,2,0,0,0,1,0,3,1,1,3,3,0,0,3,0,0,3,0,2,3,1,0,3,1,0,3,3,2,0,4,2,2,0,2,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,2,1,2,0,1,0,1,0,0,0,1,3,1,2,0,0,0,1,0,0,1,4), +(0,3,0,3,0,5,0,1,0,2,4,3,1,3,3,2,1,1,5,2,1,0,5,1,2,0,0,0,3,3,2,2,3,2,4,3,0,0,3,3,1,3,3,0,2,5,3,4,0,3,3,0,1,2,0,2,2,0,3,2,0,2,2,3,3,3,0,2,0,1,0,3,4,4,2,5,4,0,3,0,0,3,5), +(0,3,0,3,0,3,0,1,0,3,3,3,3,0,3,0,2,0,2,1,1,0,2,0,1,0,0,0,2,1,0,0,1,0,3,2,0,0,3,3,1,2,3,1,0,3,3,0,0,1,0,0,0,0,0,2,0,0,0,0,0,2,3,1,2,3,0,3,0,1,0,3,2,1,0,4,3,0,1,1,0,3,3), +(0,4,0,5,0,3,0,3,0,4,5,5,4,3,5,3,4,3,5,3,3,2,5,3,4,4,4,3,4,3,4,5,5,3,4,4,3,4,4,5,4,4,4,3,4,5,5,4,2,3,4,2,3,4,0,3,3,1,4,3,2,4,3,3,5,5,0,3,0,3,0,5,5,5,5,4,4,0,4,0,1,4,4), +(0,4,0,4,0,3,0,3,0,3,5,4,4,2,3,2,5,1,3,2,5,1,4,2,3,2,3,3,4,3,3,3,3,2,5,4,1,3,3,5,3,4,4,0,4,4,3,1,1,3,1,0,2,3,0,2,3,0,3,0,0,4,3,1,3,4,0,3,0,2,0,4,4,4,3,4,5,0,4,0,0,3,4), +(0,3,0,3,0,3,1,2,0,3,4,4,3,3,3,0,2,2,4,3,3,1,3,3,3,1,1,0,3,1,4,3,2,3,4,4,2,4,4,4,3,4,4,3,2,4,4,3,1,3,3,1,3,3,0,4,1,0,2,2,1,4,3,2,3,3,5,4,3,3,5,4,4,3,3,0,4,0,3,2,2,4,4), +(0,2,0,1,0,0,0,0,0,1,2,1,3,0,0,0,0,0,2,0,1,2,1,0,0,1,0,0,0,0,3,0,0,1,0,1,1,3,1,0,0,0,1,1,0,1,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0,1,1,2,2,0,3,4,0,0,0,1,1,0,0,1,0,0,0,0,0,1,1), +(0,1,0,0,0,1,0,0,0,0,4,0,4,1,4,0,3,0,4,0,3,0,4,0,3,0,3,0,4,1,5,1,4,0,0,3,0,5,0,5,2,0,1,0,0,0,2,1,4,0,1,3,0,0,3,0,0,3,1,1,4,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0), +(1,4,0,5,0,3,0,2,0,3,5,4,4,3,4,3,5,3,4,3,3,0,4,3,3,3,3,3,3,2,4,4,3,1,3,4,4,5,4,4,3,4,4,1,3,5,4,3,3,3,1,2,2,3,3,1,3,1,3,3,3,5,3,3,4,5,0,3,0,3,0,3,4,3,4,4,3,0,3,0,2,4,3), +(0,1,0,4,0,0,0,0,0,1,4,0,4,1,4,2,4,0,3,0,1,0,1,0,0,0,0,0,2,0,3,1,1,1,0,3,0,0,0,1,2,1,0,0,1,1,1,1,0,1,0,0,0,1,0,0,3,0,0,0,0,3,2,0,2,2,0,1,0,0,0,2,3,2,3,3,0,0,0,0,2,1,0), +(0,5,1,5,0,3,0,3,0,5,4,4,5,1,5,3,3,0,4,3,4,3,5,3,4,3,3,2,4,3,4,3,3,0,3,3,1,4,4,3,4,4,4,3,4,5,5,3,2,3,1,1,3,3,1,3,1,1,3,3,2,4,5,3,3,5,0,4,0,3,0,4,4,3,5,3,3,0,3,4,0,4,3), +(0,5,0,5,0,3,0,2,0,4,4,3,5,2,4,3,3,3,4,4,4,3,5,3,5,3,3,1,4,0,4,3,3,0,3,3,0,4,4,4,4,5,4,3,3,5,5,3,2,3,1,2,3,2,0,1,0,0,3,2,2,4,4,3,1,5,0,4,0,3,0,4,3,1,3,2,1,0,3,3,0,3,3), +(0,4,0,5,0,5,0,4,0,4,5,5,5,3,4,3,3,2,5,4,4,3,5,3,5,3,4,0,4,3,4,4,3,2,4,4,3,4,5,4,4,5,5,0,3,5,5,4,1,3,3,2,3,3,1,3,1,0,4,3,1,4,4,3,4,5,0,4,0,2,0,4,3,4,4,3,3,0,4,0,0,5,5), +(0,4,0,4,0,5,0,1,1,3,3,4,4,3,4,1,3,0,5,1,3,0,3,1,3,1,1,0,3,0,3,3,4,0,4,3,0,4,4,4,3,4,4,0,3,5,4,1,0,3,0,0,2,3,0,3,1,0,3,1,0,3,2,1,3,5,0,3,0,1,0,3,2,3,3,4,4,0,2,2,0,4,4), +(2,4,0,5,0,4,0,3,0,4,5,5,4,3,5,3,5,3,5,3,5,2,5,3,4,3,3,4,3,4,5,3,2,1,5,4,3,2,3,4,5,3,4,1,2,5,4,3,0,3,3,0,3,2,0,2,3,0,4,1,0,3,4,3,3,5,0,3,0,1,0,4,5,5,5,4,3,0,4,2,0,3,5), +(0,5,0,4,0,4,0,2,0,5,4,3,4,3,4,3,3,3,4,3,4,2,5,3,5,3,4,1,4,3,4,4,4,0,3,5,0,4,4,4,4,5,3,1,3,4,5,3,3,3,3,3,3,3,0,2,2,0,3,3,2,4,3,3,3,5,3,4,1,3,3,5,3,2,0,0,0,0,4,3,1,3,3), +(0,1,0,3,0,3,0,1,0,1,3,3,3,2,3,3,3,0,3,0,0,0,3,1,3,0,0,0,2,2,2,3,0,0,3,2,0,1,2,4,1,3,3,0,0,3,3,3,0,1,0,0,2,1,0,0,3,0,3,1,0,3,0,0,1,3,0,2,0,1,0,3,3,1,3,3,0,0,1,1,0,3,3), +(0,2,0,3,0,2,1,4,0,2,2,3,1,1,3,1,1,0,2,0,3,1,2,3,1,3,0,0,1,0,4,3,2,3,3,3,1,4,2,3,3,3,3,1,0,3,1,4,0,1,1,0,1,2,0,1,1,0,1,1,0,3,1,3,2,2,0,1,0,0,0,2,3,3,3,1,0,0,0,0,0,2,3), +(0,5,0,4,0,5,0,2,0,4,5,5,3,3,4,3,3,1,5,4,4,2,4,4,4,3,4,2,4,3,5,5,4,3,3,4,3,3,5,5,4,5,5,1,3,4,5,3,1,4,3,1,3,3,0,3,3,1,4,3,1,4,5,3,3,5,0,4,0,3,0,5,3,3,1,4,3,0,4,0,1,5,3), +(0,5,0,5,0,4,0,2,0,4,4,3,4,3,3,3,3,3,5,4,4,4,4,4,4,5,3,3,5,2,4,4,4,3,4,4,3,3,4,4,5,5,3,3,4,3,4,3,3,4,3,3,3,3,1,2,2,1,4,3,3,5,4,4,3,4,0,4,0,3,0,4,4,4,4,4,1,0,4,2,0,2,4), +(0,4,0,4,0,3,0,1,0,3,5,2,3,0,3,0,2,1,4,2,3,3,4,1,4,3,3,2,4,1,3,3,3,0,3,3,0,0,3,3,3,5,3,3,3,3,3,2,0,2,0,0,2,0,0,2,0,0,1,0,0,3,1,2,2,3,0,3,0,2,0,4,4,3,3,4,1,0,3,0,0,2,4), +(0,0,0,4,0,0,0,0,0,0,1,0,1,0,2,0,0,0,0,0,1,0,2,0,1,0,0,0,0,0,3,1,3,0,3,2,0,0,0,1,0,3,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,4,0,2,0,0,0,0,0,0,2), +(0,2,1,3,0,2,0,2,0,3,3,3,3,1,3,1,3,3,3,3,3,3,4,2,2,1,2,1,4,0,4,3,1,3,3,3,2,4,3,5,4,3,3,3,3,3,3,3,0,1,3,0,2,0,0,1,0,0,1,0,0,4,2,0,2,3,0,3,3,0,3,3,4,2,3,1,4,0,1,2,0,2,3), +(0,3,0,3,0,1,0,3,0,2,3,3,3,0,3,1,2,0,3,3,2,3,3,2,3,2,3,1,3,0,4,3,2,0,3,3,1,4,3,3,2,3,4,3,1,3,3,1,1,0,1,1,0,1,0,1,0,1,0,0,0,4,1,1,0,3,0,3,1,0,2,3,3,3,3,3,1,0,0,2,0,3,3), +(0,0,0,0,0,0,0,0,0,0,3,0,2,0,3,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,3,0,3,0,3,1,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,2,0,2,3,0,0,0,0,0,0,0,0,3), +(0,2,0,3,1,3,0,3,0,2,3,3,3,1,3,1,3,1,3,1,3,3,3,1,3,0,2,3,1,1,4,3,3,2,3,3,1,2,2,4,1,3,3,0,1,4,2,3,0,1,3,0,3,0,0,1,3,0,2,0,0,3,3,2,1,3,0,3,0,2,0,3,4,4,4,3,1,0,3,0,0,3,3), +(0,2,0,1,0,2,0,0,0,1,3,2,2,1,3,0,1,1,3,0,3,2,3,1,2,0,2,0,1,1,3,3,3,0,3,3,1,1,2,3,2,3,3,1,2,3,2,0,0,1,0,0,0,0,0,0,3,0,1,0,0,2,1,2,1,3,0,3,0,0,0,3,4,4,4,3,2,0,2,0,0,2,4), +(0,0,0,1,0,1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,2,2,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,3,1,0,0,0,0,0,0,0,3), +(0,3,0,3,0,2,0,3,0,3,3,3,2,3,2,2,2,0,3,1,3,3,3,2,3,3,0,0,3,0,3,2,2,0,2,3,1,4,3,4,3,3,2,3,1,5,4,4,0,3,1,2,1,3,0,3,1,1,2,0,2,3,1,3,1,3,0,3,0,1,0,3,3,4,4,2,1,0,2,1,0,2,4), +(0,1,0,3,0,1,0,2,0,1,4,2,5,1,4,0,2,0,2,1,3,1,4,0,2,1,0,0,2,1,4,1,1,0,3,3,0,5,1,3,2,3,3,1,0,3,2,3,0,1,0,0,0,0,0,0,1,0,0,0,0,4,0,1,0,3,0,2,0,1,0,3,3,3,4,3,3,0,0,0,0,2,3), +(0,0,0,1,0,0,0,0,0,0,2,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,1,0,0,0,0,0,3), +(0,1,0,3,0,4,0,3,0,2,4,3,1,0,3,2,2,1,3,1,2,2,3,1,1,1,2,1,3,0,1,2,0,1,3,2,1,3,0,5,5,1,0,0,1,3,2,1,0,3,0,0,1,0,0,0,0,0,3,4,0,1,1,1,3,2,0,2,0,1,0,2,3,3,1,2,3,0,1,0,1,0,4), +(0,0,0,1,0,3,0,3,0,2,2,1,0,0,4,0,3,0,3,1,3,0,3,0,3,0,1,0,3,0,3,1,3,0,3,3,0,0,1,2,1,1,1,0,1,2,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,2,2,1,2,0,0,2,0,0,0,0,2,3,3,3,3,0,0,0,0,1,4), +(0,0,0,3,0,3,0,0,0,0,3,1,1,0,3,0,1,0,2,0,1,0,0,0,0,0,0,0,1,0,3,0,2,0,2,3,0,0,2,2,3,1,2,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,2,0,0,0,0,2,3), +(2,4,0,5,0,5,0,4,0,3,4,3,3,3,4,3,3,3,4,3,4,4,5,4,5,5,5,2,3,0,5,5,4,1,5,4,3,1,5,4,3,4,4,3,3,4,3,3,0,3,2,0,2,3,0,3,0,0,3,3,0,5,3,2,3,3,0,3,0,3,0,3,4,5,4,5,3,0,4,3,0,3,4), +(0,3,0,3,0,3,0,3,0,3,3,4,3,2,3,2,3,0,4,3,3,3,3,3,3,3,3,0,3,2,4,3,3,1,3,4,3,4,4,4,3,4,4,3,2,4,4,1,0,2,0,0,1,1,0,2,0,0,3,1,0,5,3,2,1,3,0,3,0,1,2,4,3,2,4,3,3,0,3,2,0,4,4), +(0,3,0,3,0,1,0,0,0,1,4,3,3,2,3,1,3,1,4,2,3,2,4,2,3,4,3,0,2,2,3,3,3,0,3,3,3,0,3,4,1,3,3,0,3,4,3,3,0,1,1,0,1,0,0,0,4,0,3,0,0,3,1,2,1,3,0,4,0,1,0,4,3,3,4,3,3,0,2,0,0,3,3), +(0,3,0,4,0,1,0,3,0,3,4,3,3,0,3,3,3,1,3,1,3,3,4,3,3,3,0,0,3,1,5,3,3,1,3,3,2,5,4,3,3,4,5,3,2,5,3,4,0,1,0,0,0,0,0,2,0,0,1,1,0,4,2,2,1,3,0,3,0,2,0,4,4,3,5,3,2,0,1,1,0,3,4), +(0,5,0,4,0,5,0,2,0,4,4,3,3,2,3,3,3,1,4,3,4,1,5,3,4,3,4,0,4,2,4,3,4,1,5,4,0,4,4,4,4,5,4,1,3,5,4,2,1,4,1,1,3,2,0,3,1,0,3,2,1,4,3,3,3,4,0,4,0,3,0,4,4,4,3,3,3,0,4,2,0,3,4), +(1,4,0,4,0,3,0,1,0,3,3,3,1,1,3,3,2,2,3,3,1,0,3,2,2,1,2,0,3,1,2,1,2,0,3,2,0,2,2,3,3,4,3,0,3,3,1,2,0,1,1,3,1,2,0,0,3,0,1,1,0,3,2,2,3,3,0,3,0,0,0,2,3,3,4,3,3,0,1,0,0,1,4), +(0,4,0,4,0,4,0,0,0,3,4,4,3,1,4,2,3,2,3,3,3,1,4,3,4,0,3,0,4,2,3,3,2,2,5,4,2,1,3,4,3,4,3,1,3,3,4,2,0,2,1,0,3,3,0,0,2,0,3,1,0,4,4,3,4,3,0,4,0,1,0,2,4,4,4,4,4,0,3,2,0,3,3), +(0,0,0,1,0,4,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,3,2,0,0,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,2), +(0,2,0,3,0,4,0,4,0,1,3,3,3,0,4,0,2,1,2,1,1,1,2,0,3,1,1,0,1,0,3,1,0,0,3,3,2,0,1,1,0,0,0,0,0,1,0,2,0,2,2,0,3,1,0,0,1,0,1,1,0,1,2,0,3,0,0,0,0,1,0,0,3,3,4,3,1,0,1,0,3,0,2), +(0,0,0,3,0,5,0,0,0,0,1,0,2,0,3,1,0,1,3,0,0,0,2,0,0,0,1,0,0,0,1,1,0,0,4,0,0,0,2,3,0,1,4,1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,1,0,0,0,0,0,0,0,2,0,0,3,0,0,0,0,0,3), +(0,2,0,5,0,5,0,1,0,2,4,3,3,2,5,1,3,2,3,3,3,0,4,1,2,0,3,0,4,0,2,2,1,1,5,3,0,0,1,4,2,3,2,0,3,3,3,2,0,2,4,1,1,2,0,1,1,0,3,1,0,1,3,1,2,3,0,2,0,0,0,1,3,5,4,4,4,0,3,0,0,1,3), +(0,4,0,5,0,4,0,4,0,4,5,4,3,3,4,3,3,3,4,3,4,4,5,3,4,5,4,2,4,2,3,4,3,1,4,4,1,3,5,4,4,5,5,4,4,5,5,5,2,3,3,1,4,3,1,3,3,0,3,3,1,4,3,4,4,4,0,3,0,4,0,3,3,4,4,5,0,0,4,3,0,4,5), +(0,4,0,4,0,3,0,3,0,3,4,4,4,3,3,2,4,3,4,3,4,3,5,3,4,3,2,1,4,2,4,4,3,1,3,4,2,4,5,5,3,4,5,4,1,5,4,3,0,3,2,2,3,2,1,3,1,0,3,3,3,5,3,3,3,5,4,4,2,3,3,4,3,3,3,2,1,0,3,2,1,4,3), +(0,4,0,5,0,4,0,3,0,3,5,5,3,2,4,3,4,0,5,4,4,1,4,4,4,3,3,3,4,3,5,5,2,3,3,4,1,2,5,5,3,5,5,2,3,5,5,4,0,3,2,0,3,3,1,1,5,1,4,1,0,4,3,2,3,5,0,4,0,3,0,5,4,3,4,3,0,0,4,1,0,4,4), +(1,3,0,4,0,2,0,2,0,2,5,5,3,3,3,3,3,0,4,2,3,4,4,4,3,4,0,0,3,4,5,4,3,3,3,3,2,5,5,4,5,5,5,4,3,5,5,5,1,3,1,0,1,0,0,3,2,0,4,2,0,5,2,3,2,4,1,3,0,3,0,4,5,4,5,4,3,0,4,2,0,5,4), +(0,3,0,4,0,5,0,3,0,3,4,4,3,2,3,2,3,3,3,3,3,2,4,3,3,2,2,0,3,3,3,3,3,1,3,3,3,0,4,4,3,4,4,1,1,4,4,2,0,3,1,0,1,1,0,4,1,0,2,3,1,3,3,1,3,4,0,3,0,1,0,3,1,3,0,0,1,0,2,0,0,4,4), +(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), +(0,3,0,3,0,2,0,3,0,1,5,4,3,3,3,1,4,2,1,2,3,4,4,2,4,4,5,0,3,1,4,3,4,0,4,3,3,3,2,3,2,5,3,4,3,2,2,3,0,0,3,0,2,1,0,1,2,0,0,0,0,2,1,1,3,1,0,2,0,4,0,3,4,4,4,5,2,0,2,0,0,1,3), +(0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,1,0,0,1,1,0,0,0,4,2,1,1,0,1,0,3,2,0,0,3,1,1,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,1,0,0,0,2,0,0,0,1,4,0,4,2,1,0,0,0,0,0,1), +(0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0,1,0,1,0,0,0,0,3,1,0,0,0,2,0,2,1,0,0,1,2,1,0,1,1,0,0,3,0,0,0,0,0,0,0,0,0,0,0,1,3,1,0,0,0,0,0,1,0,0,2,1,0,0,0,0,0,0,0,0,2), +(0,4,0,4,0,4,0,3,0,4,4,3,4,2,4,3,2,0,4,4,4,3,5,3,5,3,3,2,4,2,4,3,4,3,1,4,0,2,3,4,4,4,3,3,3,4,4,4,3,4,1,3,4,3,2,1,2,1,3,3,3,4,4,3,3,5,0,4,0,3,0,4,3,3,3,2,1,0,3,0,0,3,3), +(0,4,0,3,0,3,0,3,0,3,5,5,3,3,3,3,4,3,4,3,3,3,4,4,4,3,3,3,3,4,3,5,3,3,1,3,2,4,5,5,5,5,4,3,4,5,5,3,2,2,3,3,3,3,2,3,3,1,2,3,2,4,3,3,3,4,0,4,0,2,0,4,3,2,2,1,2,0,3,0,0,4,1), +) + +class JapaneseContextAnalysis(object): + NUM_OF_CATEGORY = 6 + DONT_KNOW = -1 + ENOUGH_REL_THRESHOLD = 100 + MAX_REL_THRESHOLD = 1000 + MINIMUM_DATA_THRESHOLD = 4 + + def __init__(self): + self._total_rel = None + self._rel_sample = None + self._need_to_skip_char_num = None + self._last_char_order = None + self._done = None + self.reset() + + def reset(self): + self._total_rel = 0 # total sequence received + # category counters, each integer counts sequence in its category + self._rel_sample = [0] * self.NUM_OF_CATEGORY + # if last byte in current buffer is not the last byte of a character, + # we need to know how many bytes to skip in next buffer + self._need_to_skip_char_num = 0 + self._last_char_order = -1 # The order of previous char + # If this flag is set to True, detection is done and conclusion has + # been made + self._done = False + + def feed(self, byte_str, num_bytes): + if self._done: + return + + # The buffer we got is byte oriented, and a character may span in more than one + # buffers. In case the last one or two byte in last buffer is not + # complete, we record how many byte needed to complete that character + # and skip these bytes here. We can choose to record those bytes as + # well and analyse the character once it is complete, but since a + # character will not make much difference, by simply skipping + # this character will simply our logic and improve performance. + i = self._need_to_skip_char_num + while i < num_bytes: + order, char_len = self.get_order(byte_str[i:i + 2]) + i += char_len + if i > num_bytes: + self._need_to_skip_char_num = i - num_bytes + self._last_char_order = -1 + else: + if (order != -1) and (self._last_char_order != -1): + self._total_rel += 1 + if self._total_rel > self.MAX_REL_THRESHOLD: + self._done = True + break + self._rel_sample[jp2CharContext[self._last_char_order][order]] += 1 + self._last_char_order = order + + def got_enough_data(self): + return self._total_rel > self.ENOUGH_REL_THRESHOLD + + def get_confidence(self): + # This is just one way to calculate confidence. It works well for me. + if self._total_rel > self.MINIMUM_DATA_THRESHOLD: + return (self._total_rel - self._rel_sample[0]) / self._total_rel + else: + return self.DONT_KNOW + + def get_order(self, byte_str): + return -1, 1 + +class SJISContextAnalysis(JapaneseContextAnalysis): + def __init__(self): + super(SJISContextAnalysis, self).__init__() + self._charset_name = "SHIFT_JIS" + + @property + def charset_name(self): + return self._charset_name + + def get_order(self, byte_str): + if not byte_str: + return -1, 1 + # find out current char's byte length + first_char = byte_str[0] + if (0x81 <= first_char <= 0x9F) or (0xE0 <= first_char <= 0xFC): + char_len = 2 + if (first_char == 0x87) or (0xFA <= first_char <= 0xFC): + self._charset_name = "CP932" + else: + char_len = 1 + + # return its order if it is hiragana + if len(byte_str) > 1: + second_char = byte_str[1] + if (first_char == 202) and (0x9F <= second_char <= 0xF1): + return second_char - 0x9F, char_len + + return -1, char_len + +class EUCJPContextAnalysis(JapaneseContextAnalysis): + def get_order(self, byte_str): + if not byte_str: + return -1, 1 + # find out current char's byte length + first_char = byte_str[0] + if (first_char == 0x8E) or (0xA1 <= first_char <= 0xFE): + char_len = 2 + elif first_char == 0x8F: + char_len = 3 + else: + char_len = 1 + + # return its order if it is hiragana + if len(byte_str) > 1: + second_char = byte_str[1] + if (first_char == 0xA4) and (0xA1 <= second_char <= 0xF3): + return second_char - 0xA1, char_len + + return -1, char_len + + diff --git a/openpype/vendor/python/python_2/chardet/langbulgarianmodel.py b/openpype/vendor/python/python_2/chardet/langbulgarianmodel.py new file mode 100644 index 0000000000..561bfd9051 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/langbulgarianmodel.py @@ -0,0 +1,4650 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from chardet.sbcharsetprober import SingleByteCharSetModel + + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +BULGARIAN_LANG_MODEL = { + 63: { # 'e' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 1, # 'б' + 9: 1, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 1, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 0, # 'и' + 26: 1, # 'й' + 12: 1, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 1, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 1, # 'с' + 5: 1, # 'т' + 19: 0, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 45: { # '\xad' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 0, # 'Л' + 38: 1, # 'М' + 36: 0, # 'Н' + 41: 1, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 1, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 1, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 0, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 0, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 0, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 31: { # 'А' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 1, # 'А' + 32: 1, # 'Б' + 35: 2, # 'В' + 43: 1, # 'Г' + 37: 2, # 'Д' + 44: 2, # 'Е' + 55: 1, # 'Ж' + 47: 2, # 'З' + 40: 1, # 'И' + 59: 1, # 'Й' + 33: 1, # 'К' + 46: 2, # 'Л' + 38: 1, # 'М' + 36: 2, # 'Н' + 41: 1, # 'О' + 30: 2, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 1, # 'У' + 48: 2, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 2, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 1, # 'а' + 18: 2, # 'б' + 9: 2, # 'в' + 20: 2, # 'г' + 11: 2, # 'д' + 3: 1, # 'е' + 23: 1, # 'ж' + 15: 2, # 'з' + 2: 0, # 'и' + 26: 2, # 'й' + 12: 2, # 'к' + 10: 3, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 0, # 'о' + 13: 2, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 1, # 'у' + 29: 2, # 'ф' + 25: 1, # 'х' + 22: 1, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 32: { # 'Б' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 2, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 2, # 'Д' + 44: 1, # 'Е' + 55: 1, # 'Ж' + 47: 2, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 2, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 1, # 'У' + 48: 2, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 0, # 'Ш' + 57: 1, # 'Щ' + 61: 2, # 'Ъ' + 60: 1, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 2, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 1, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 35: { # 'В' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 2, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 1, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 2, # 'Ф' + 49: 0, # 'Х' + 53: 1, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 2, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 2, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 2, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 2, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 43: { # 'Г' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 2, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 0, # 'П' + 39: 1, # 'Р' + 28: 1, # 'С' + 34: 0, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 1, # 'Щ' + 61: 1, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 1, # 'б' + 9: 1, # 'в' + 20: 0, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 2, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 1, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 37: { # 'Д' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 2, # 'В' + 43: 1, # 'Г' + 37: 2, # 'Д' + 44: 2, # 'Е' + 55: 2, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 2, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 2, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 2, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 44: { # 'Е' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 1, # 'Б' + 35: 2, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 1, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 1, # 'Й' + 33: 2, # 'К' + 46: 2, # 'Л' + 38: 1, # 'М' + 36: 2, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 1, # 'У' + 48: 2, # 'Ф' + 49: 1, # 'Х' + 53: 2, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 1, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 0, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 2, # 'д' + 3: 0, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 0, # 'и' + 26: 1, # 'й' + 12: 2, # 'к' + 10: 2, # 'л' + 14: 2, # 'м' + 6: 2, # 'н' + 4: 0, # 'о' + 13: 1, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 1, # 'т' + 19: 1, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 55: { # 'Ж' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 1, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 47: { # 'З' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 2, # 'Н' + 41: 1, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 1, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 2, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 1, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 1, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 40: { # 'И' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 1, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 2, # 'Е' + 55: 1, # 'Ж' + 47: 2, # 'З' + 40: 1, # 'И' + 59: 1, # 'Й' + 33: 2, # 'К' + 46: 2, # 'Л' + 38: 2, # 'М' + 36: 2, # 'Н' + 41: 1, # 'О' + 30: 1, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 0, # 'У' + 48: 1, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 1, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 2, # 'Я' + 1: 1, # 'а' + 18: 1, # 'б' + 9: 3, # 'в' + 20: 2, # 'г' + 11: 1, # 'д' + 3: 1, # 'е' + 23: 0, # 'ж' + 15: 3, # 'з' + 2: 0, # 'и' + 26: 1, # 'й' + 12: 1, # 'к' + 10: 2, # 'л' + 14: 2, # 'м' + 6: 2, # 'н' + 4: 0, # 'о' + 13: 1, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 0, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 1, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 59: { # 'Й' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 1, # 'С' + 34: 1, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 1, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 0, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 1, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 0, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 2, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 33: { # 'К' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 0, # 'М' + 36: 2, # 'Н' + 41: 2, # 'О' + 30: 2, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 1, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 1, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 2, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 3, # 'р' + 8: 1, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 46: { # 'Л' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 2, # 'Г' + 37: 1, # 'Д' + 44: 2, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 0, # 'Р' + 28: 1, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 1, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 1, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 2, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 38: { # 'М' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 2, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 1, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 2, # 'л' + 14: 0, # 'м' + 6: 2, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 36: { # 'Н' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 2, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 2, # 'Д' + 44: 2, # 'Е' + 55: 1, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 1, # 'Й' + 33: 2, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 1, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 1, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 2, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 41: { # 'О' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 1, # 'Б' + 35: 2, # 'В' + 43: 1, # 'Г' + 37: 2, # 'Д' + 44: 1, # 'Е' + 55: 1, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 1, # 'Й' + 33: 2, # 'К' + 46: 2, # 'Л' + 38: 2, # 'М' + 36: 2, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 1, # 'Х' + 53: 0, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 1, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 1, # 'а' + 18: 2, # 'б' + 9: 2, # 'в' + 20: 2, # 'г' + 11: 1, # 'д' + 3: 1, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 0, # 'и' + 26: 1, # 'й' + 12: 2, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 0, # 'о' + 13: 2, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 3, # 'т' + 19: 1, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 1, # 'ц' + 21: 2, # 'ч' + 27: 0, # 'ш' + 24: 2, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 30: { # 'П' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 2, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 2, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 3, # 'л' + 14: 0, # 'м' + 6: 1, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 3, # 'р' + 8: 1, # 'с' + 5: 1, # 'т' + 19: 2, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 39: { # 'Р' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 2, # 'Г' + 37: 2, # 'Д' + 44: 2, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 0, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 2, # 'П' + 39: 1, # 'Р' + 28: 1, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 1, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 1, # 'с' + 5: 0, # 'т' + 19: 3, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 28: { # 'С' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 3, # 'А' + 32: 2, # 'Б' + 35: 2, # 'В' + 43: 1, # 'Г' + 37: 2, # 'Д' + 44: 2, # 'Е' + 55: 1, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 2, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 2, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 2, # 'к' + 10: 3, # 'л' + 14: 2, # 'м' + 6: 1, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 2, # 'р' + 8: 0, # 'с' + 5: 3, # 'т' + 19: 2, # 'у' + 29: 2, # 'ф' + 25: 1, # 'х' + 22: 1, # 'ц' + 21: 1, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 34: { # 'Т' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 2, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 2, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 2, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 1, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 1, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 1, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 3, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 51: { # 'У' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 1, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 2, # 'Е' + 55: 1, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 0, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 1, # 'С' + 34: 2, # 'Т' + 51: 0, # 'У' + 48: 1, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 1, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 2, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 2, # 'и' + 26: 1, # 'й' + 12: 2, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 2, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 2, # 'с' + 5: 1, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 2, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 48: { # 'Ф' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 2, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 2, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 2, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 49: { # 'Х' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 1, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 1, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 0, # 'н' + 4: 2, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 53: { # 'Ц' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 2, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 0, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 2, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 1, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 50: { # 'Ч' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 1, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 2, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 1, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 54: { # 'Ш' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 2, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 1, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 57: { # 'Щ' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 1, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 1, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 1, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 61: { # 'Ъ' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 0, # 'Е' + 55: 1, # 'Ж' + 47: 1, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 2, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 0, # 'О' + 30: 1, # 'П' + 39: 2, # 'Р' + 28: 1, # 'С' + 34: 1, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 1, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 0, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 0, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 1, # 'л' + 14: 0, # 'м' + 6: 1, # 'н' + 4: 0, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 60: { # 'Ю' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 1, # 'Б' + 35: 0, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 0, # 'Е' + 55: 1, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 1, # 'Р' + 28: 1, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 1, # 'б' + 9: 1, # 'в' + 20: 2, # 'г' + 11: 1, # 'д' + 3: 0, # 'е' + 23: 2, # 'ж' + 15: 1, # 'з' + 2: 1, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 0, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 1, # 'с' + 5: 1, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 56: { # 'Я' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 1, # 'С' + 34: 2, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 1, # 'б' + 9: 1, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 0, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 1, # 'и' + 26: 1, # 'й' + 12: 1, # 'к' + 10: 1, # 'л' + 14: 2, # 'м' + 6: 2, # 'н' + 4: 0, # 'о' + 13: 2, # 'п' + 7: 1, # 'р' + 8: 1, # 'с' + 5: 1, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 1: { # 'а' + 63: 1, # 'e' + 45: 1, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 1, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 3, # 'з' + 2: 3, # 'и' + 26: 3, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 2, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 3, # 'ф' + 25: 3, # 'х' + 22: 3, # 'ц' + 21: 3, # 'ч' + 27: 3, # 'ш' + 24: 3, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 18: { # 'б' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 3, # 'в' + 20: 1, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 3, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 1, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 0, # 'т' + 19: 3, # 'у' + 29: 0, # 'ф' + 25: 2, # 'х' + 22: 1, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 3, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 9: { # 'в' + 63: 1, # 'e' + 45: 1, # '\xad' + 31: 0, # 'А' + 32: 1, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 0, # 'в' + 20: 2, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 3, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 2, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 3, # 'ч' + 27: 2, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 20: { # 'г' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 3, # 'л' + 14: 1, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 1, # 'п' + 7: 3, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 3, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 11: { # 'д' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 2, # 'б' + 9: 3, # 'в' + 20: 2, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 2, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 1, # 'т' + 19: 3, # 'у' + 29: 1, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 3: { # 'е' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 2, # 'е' + 23: 3, # 'ж' + 15: 3, # 'з' + 2: 2, # 'и' + 26: 3, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 2, # 'у' + 29: 3, # 'ф' + 25: 3, # 'х' + 22: 3, # 'ц' + 21: 3, # 'ч' + 27: 3, # 'ш' + 24: 3, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 23: { # 'ж' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 2, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 3, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 1, # 'с' + 5: 1, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 1, # 'ц' + 21: 1, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 15: { # 'з' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 1, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 2, # 'ш' + 24: 1, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 2: { # 'и' + 63: 1, # 'e' + 45: 1, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 1, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 1, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 3, # 'з' + 2: 3, # 'и' + 26: 3, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 2, # 'у' + 29: 3, # 'ф' + 25: 3, # 'х' + 22: 3, # 'ц' + 21: 3, # 'ч' + 27: 3, # 'ш' + 24: 3, # 'щ' + 17: 2, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 26: { # 'й' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 1, # 'а' + 18: 2, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 2, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 2, # 'з' + 2: 1, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 2, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 2, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 1, # 'у' + 29: 2, # 'ф' + 25: 1, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 12: { # 'к' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 3, # 'в' + 20: 2, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 2, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 3, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 1, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 3, # 'ц' + 21: 2, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 10: { # 'л' + 63: 1, # 'e' + 45: 1, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 1, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 2, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 1, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 2, # 'п' + 7: 2, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 2, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 2, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 2, # 'ь' + 42: 3, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 14: { # 'м' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 2, # 'к' + 10: 3, # 'л' + 14: 1, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 1, # 'т' + 19: 3, # 'у' + 29: 2, # 'ф' + 25: 1, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 2, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 6: { # 'н' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 1, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 2, # 'б' + 9: 2, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 2, # 'ж' + 15: 2, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 1, # 'п' + 7: 2, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 3, # 'ф' + 25: 2, # 'х' + 22: 3, # 'ц' + 21: 3, # 'ч' + 27: 2, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 2, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 4: { # 'о' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 3, # 'з' + 2: 3, # 'и' + 26: 3, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 2, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 2, # 'у' + 29: 3, # 'ф' + 25: 3, # 'х' + 22: 3, # 'ц' + 21: 3, # 'ч' + 27: 3, # 'ш' + 24: 3, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 13: { # 'п' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 1, # 'й' + 12: 2, # 'к' + 10: 3, # 'л' + 14: 1, # 'м' + 6: 2, # 'н' + 4: 3, # 'о' + 13: 1, # 'п' + 7: 3, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 3, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 7: { # 'р' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 2, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 2, # 'п' + 7: 1, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 2, # 'ф' + 25: 3, # 'х' + 22: 3, # 'ц' + 21: 2, # 'ч' + 27: 3, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 8: { # 'с' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 2, # 'б' + 9: 3, # 'в' + 20: 2, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 1, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 2, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 2, # 'ш' + 24: 0, # 'щ' + 17: 3, # 'ъ' + 52: 2, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 5: { # 'т' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 2, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 2, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 1, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 2, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 19: { # 'у' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 2, # 'е' + 23: 3, # 'ж' + 15: 3, # 'з' + 2: 2, # 'и' + 26: 2, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 2, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 1, # 'у' + 29: 2, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 3, # 'ч' + 27: 3, # 'ш' + 24: 2, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 29: { # 'ф' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 1, # 'в' + 20: 1, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 2, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 2, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 25: { # 'х' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 3, # 'в' + 20: 0, # 'г' + 11: 1, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 2, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 1, # 'п' + 7: 3, # 'р' + 8: 1, # 'с' + 5: 2, # 'т' + 19: 3, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 22: { # 'ц' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 2, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 1, # 'с' + 5: 1, # 'т' + 19: 2, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 1, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 0, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 21: { # 'ч' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 3, # 'в' + 20: 1, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 2, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 0, # 'с' + 5: 2, # 'т' + 19: 3, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 27: { # 'ш' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 2, # 'в' + 20: 0, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 3, # 'н' + 4: 2, # 'о' + 13: 2, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 1, # 'т' + 19: 2, # 'у' + 29: 1, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 24: { # 'щ' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 1, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 2, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 2, # 'т' + 19: 3, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 1, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 17: { # 'ъ' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 1, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 2, # 'е' + 23: 3, # 'ж' + 15: 3, # 'з' + 2: 1, # 'и' + 26: 2, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 1, # 'у' + 29: 1, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 3, # 'ч' + 27: 2, # 'ш' + 24: 3, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 2, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 52: { # 'ь' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 1, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 0, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 1, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 1, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 1, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 42: { # 'ю' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 1, # 'а' + 18: 2, # 'б' + 9: 1, # 'в' + 20: 2, # 'г' + 11: 2, # 'д' + 3: 1, # 'е' + 23: 2, # 'ж' + 15: 2, # 'з' + 2: 1, # 'и' + 26: 1, # 'й' + 12: 2, # 'к' + 10: 2, # 'л' + 14: 2, # 'м' + 6: 2, # 'н' + 4: 1, # 'о' + 13: 1, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 1, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 2, # 'ц' + 21: 3, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 16: { # 'я' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 2, # 'г' + 11: 3, # 'д' + 3: 2, # 'е' + 23: 1, # 'ж' + 15: 2, # 'з' + 2: 1, # 'и' + 26: 2, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 1, # 'о' + 13: 2, # 'п' + 7: 2, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 1, # 'у' + 29: 1, # 'ф' + 25: 3, # 'х' + 22: 2, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 2, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 58: { # 'є' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 0, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 0, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 0, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 62: { # '№' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 0, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 0, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 0, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, +} + +# 255: Undefined characters that did not exist in training text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 +# 251: Control characters + +# Character Mapping Table(s): +ISO_8859_5_BULGARIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 77, # 'A' + 66: 90, # 'B' + 67: 99, # 'C' + 68: 100, # 'D' + 69: 72, # 'E' + 70: 109, # 'F' + 71: 107, # 'G' + 72: 101, # 'H' + 73: 79, # 'I' + 74: 185, # 'J' + 75: 81, # 'K' + 76: 102, # 'L' + 77: 76, # 'M' + 78: 94, # 'N' + 79: 82, # 'O' + 80: 110, # 'P' + 81: 186, # 'Q' + 82: 108, # 'R' + 83: 91, # 'S' + 84: 74, # 'T' + 85: 119, # 'U' + 86: 84, # 'V' + 87: 96, # 'W' + 88: 111, # 'X' + 89: 187, # 'Y' + 90: 115, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 65, # 'a' + 98: 69, # 'b' + 99: 70, # 'c' + 100: 66, # 'd' + 101: 63, # 'e' + 102: 68, # 'f' + 103: 112, # 'g' + 104: 103, # 'h' + 105: 92, # 'i' + 106: 194, # 'j' + 107: 104, # 'k' + 108: 95, # 'l' + 109: 86, # 'm' + 110: 87, # 'n' + 111: 71, # 'o' + 112: 116, # 'p' + 113: 195, # 'q' + 114: 85, # 'r' + 115: 93, # 's' + 116: 97, # 't' + 117: 113, # 'u' + 118: 196, # 'v' + 119: 197, # 'w' + 120: 198, # 'x' + 121: 199, # 'y' + 122: 200, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 194, # '\x80' + 129: 195, # '\x81' + 130: 196, # '\x82' + 131: 197, # '\x83' + 132: 198, # '\x84' + 133: 199, # '\x85' + 134: 200, # '\x86' + 135: 201, # '\x87' + 136: 202, # '\x88' + 137: 203, # '\x89' + 138: 204, # '\x8a' + 139: 205, # '\x8b' + 140: 206, # '\x8c' + 141: 207, # '\x8d' + 142: 208, # '\x8e' + 143: 209, # '\x8f' + 144: 210, # '\x90' + 145: 211, # '\x91' + 146: 212, # '\x92' + 147: 213, # '\x93' + 148: 214, # '\x94' + 149: 215, # '\x95' + 150: 216, # '\x96' + 151: 217, # '\x97' + 152: 218, # '\x98' + 153: 219, # '\x99' + 154: 220, # '\x9a' + 155: 221, # '\x9b' + 156: 222, # '\x9c' + 157: 223, # '\x9d' + 158: 224, # '\x9e' + 159: 225, # '\x9f' + 160: 81, # '\xa0' + 161: 226, # 'Ё' + 162: 227, # 'Ђ' + 163: 228, # 'Ѓ' + 164: 229, # 'Є' + 165: 230, # 'Ѕ' + 166: 105, # 'І' + 167: 231, # 'Ї' + 168: 232, # 'Ј' + 169: 233, # 'Љ' + 170: 234, # 'Њ' + 171: 235, # 'Ћ' + 172: 236, # 'Ќ' + 173: 45, # '\xad' + 174: 237, # 'Ў' + 175: 238, # 'Џ' + 176: 31, # 'А' + 177: 32, # 'Б' + 178: 35, # 'В' + 179: 43, # 'Г' + 180: 37, # 'Д' + 181: 44, # 'Е' + 182: 55, # 'Ж' + 183: 47, # 'З' + 184: 40, # 'И' + 185: 59, # 'Й' + 186: 33, # 'К' + 187: 46, # 'Л' + 188: 38, # 'М' + 189: 36, # 'Н' + 190: 41, # 'О' + 191: 30, # 'П' + 192: 39, # 'Р' + 193: 28, # 'С' + 194: 34, # 'Т' + 195: 51, # 'У' + 196: 48, # 'Ф' + 197: 49, # 'Х' + 198: 53, # 'Ц' + 199: 50, # 'Ч' + 200: 54, # 'Ш' + 201: 57, # 'Щ' + 202: 61, # 'Ъ' + 203: 239, # 'Ы' + 204: 67, # 'Ь' + 205: 240, # 'Э' + 206: 60, # 'Ю' + 207: 56, # 'Я' + 208: 1, # 'а' + 209: 18, # 'б' + 210: 9, # 'в' + 211: 20, # 'г' + 212: 11, # 'д' + 213: 3, # 'е' + 214: 23, # 'ж' + 215: 15, # 'з' + 216: 2, # 'и' + 217: 26, # 'й' + 218: 12, # 'к' + 219: 10, # 'л' + 220: 14, # 'м' + 221: 6, # 'н' + 222: 4, # 'о' + 223: 13, # 'п' + 224: 7, # 'р' + 225: 8, # 'с' + 226: 5, # 'т' + 227: 19, # 'у' + 228: 29, # 'ф' + 229: 25, # 'х' + 230: 22, # 'ц' + 231: 21, # 'ч' + 232: 27, # 'ш' + 233: 24, # 'щ' + 234: 17, # 'ъ' + 235: 75, # 'ы' + 236: 52, # 'ь' + 237: 241, # 'э' + 238: 42, # 'ю' + 239: 16, # 'я' + 240: 62, # '№' + 241: 242, # 'ё' + 242: 243, # 'ђ' + 243: 244, # 'ѓ' + 244: 58, # 'є' + 245: 245, # 'ѕ' + 246: 98, # 'і' + 247: 246, # 'ї' + 248: 247, # 'ј' + 249: 248, # 'љ' + 250: 249, # 'њ' + 251: 250, # 'ћ' + 252: 251, # 'ќ' + 253: 91, # '§' + 254: 252, # 'ў' + 255: 253, # 'џ' +} + +ISO_8859_5_BULGARIAN_MODEL = SingleByteCharSetModel(charset_name='ISO-8859-5', + language='Bulgarian', + char_to_order_map=ISO_8859_5_BULGARIAN_CHAR_TO_ORDER, + language_model=BULGARIAN_LANG_MODEL, + typical_positive_ratio=0.969392, + keep_ascii_letters=False, + alphabet='АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЮЯабвгдежзийклмнопрстуфхцчшщъьюя') + +WINDOWS_1251_BULGARIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 77, # 'A' + 66: 90, # 'B' + 67: 99, # 'C' + 68: 100, # 'D' + 69: 72, # 'E' + 70: 109, # 'F' + 71: 107, # 'G' + 72: 101, # 'H' + 73: 79, # 'I' + 74: 185, # 'J' + 75: 81, # 'K' + 76: 102, # 'L' + 77: 76, # 'M' + 78: 94, # 'N' + 79: 82, # 'O' + 80: 110, # 'P' + 81: 186, # 'Q' + 82: 108, # 'R' + 83: 91, # 'S' + 84: 74, # 'T' + 85: 119, # 'U' + 86: 84, # 'V' + 87: 96, # 'W' + 88: 111, # 'X' + 89: 187, # 'Y' + 90: 115, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 65, # 'a' + 98: 69, # 'b' + 99: 70, # 'c' + 100: 66, # 'd' + 101: 63, # 'e' + 102: 68, # 'f' + 103: 112, # 'g' + 104: 103, # 'h' + 105: 92, # 'i' + 106: 194, # 'j' + 107: 104, # 'k' + 108: 95, # 'l' + 109: 86, # 'm' + 110: 87, # 'n' + 111: 71, # 'o' + 112: 116, # 'p' + 113: 195, # 'q' + 114: 85, # 'r' + 115: 93, # 's' + 116: 97, # 't' + 117: 113, # 'u' + 118: 196, # 'v' + 119: 197, # 'w' + 120: 198, # 'x' + 121: 199, # 'y' + 122: 200, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 206, # 'Ђ' + 129: 207, # 'Ѓ' + 130: 208, # '‚' + 131: 209, # 'ѓ' + 132: 210, # '„' + 133: 211, # '…' + 134: 212, # '†' + 135: 213, # '‡' + 136: 120, # '€' + 137: 214, # '‰' + 138: 215, # 'Љ' + 139: 216, # '‹' + 140: 217, # 'Њ' + 141: 218, # 'Ќ' + 142: 219, # 'Ћ' + 143: 220, # 'Џ' + 144: 221, # 'ђ' + 145: 78, # '‘' + 146: 64, # '’' + 147: 83, # '“' + 148: 121, # '”' + 149: 98, # '•' + 150: 117, # '–' + 151: 105, # '—' + 152: 222, # None + 153: 223, # '™' + 154: 224, # 'љ' + 155: 225, # '›' + 156: 226, # 'њ' + 157: 227, # 'ќ' + 158: 228, # 'ћ' + 159: 229, # 'џ' + 160: 88, # '\xa0' + 161: 230, # 'Ў' + 162: 231, # 'ў' + 163: 232, # 'Ј' + 164: 233, # '¤' + 165: 122, # 'Ґ' + 166: 89, # '¦' + 167: 106, # '§' + 168: 234, # 'Ё' + 169: 235, # '©' + 170: 236, # 'Є' + 171: 237, # '«' + 172: 238, # '¬' + 173: 45, # '\xad' + 174: 239, # '®' + 175: 240, # 'Ї' + 176: 73, # '°' + 177: 80, # '±' + 178: 118, # 'І' + 179: 114, # 'і' + 180: 241, # 'ґ' + 181: 242, # 'µ' + 182: 243, # '¶' + 183: 244, # '·' + 184: 245, # 'ё' + 185: 62, # '№' + 186: 58, # 'є' + 187: 246, # '»' + 188: 247, # 'ј' + 189: 248, # 'Ѕ' + 190: 249, # 'ѕ' + 191: 250, # 'ї' + 192: 31, # 'А' + 193: 32, # 'Б' + 194: 35, # 'В' + 195: 43, # 'Г' + 196: 37, # 'Д' + 197: 44, # 'Е' + 198: 55, # 'Ж' + 199: 47, # 'З' + 200: 40, # 'И' + 201: 59, # 'Й' + 202: 33, # 'К' + 203: 46, # 'Л' + 204: 38, # 'М' + 205: 36, # 'Н' + 206: 41, # 'О' + 207: 30, # 'П' + 208: 39, # 'Р' + 209: 28, # 'С' + 210: 34, # 'Т' + 211: 51, # 'У' + 212: 48, # 'Ф' + 213: 49, # 'Х' + 214: 53, # 'Ц' + 215: 50, # 'Ч' + 216: 54, # 'Ш' + 217: 57, # 'Щ' + 218: 61, # 'Ъ' + 219: 251, # 'Ы' + 220: 67, # 'Ь' + 221: 252, # 'Э' + 222: 60, # 'Ю' + 223: 56, # 'Я' + 224: 1, # 'а' + 225: 18, # 'б' + 226: 9, # 'в' + 227: 20, # 'г' + 228: 11, # 'д' + 229: 3, # 'е' + 230: 23, # 'ж' + 231: 15, # 'з' + 232: 2, # 'и' + 233: 26, # 'й' + 234: 12, # 'к' + 235: 10, # 'л' + 236: 14, # 'м' + 237: 6, # 'н' + 238: 4, # 'о' + 239: 13, # 'п' + 240: 7, # 'р' + 241: 8, # 'с' + 242: 5, # 'т' + 243: 19, # 'у' + 244: 29, # 'ф' + 245: 25, # 'х' + 246: 22, # 'ц' + 247: 21, # 'ч' + 248: 27, # 'ш' + 249: 24, # 'щ' + 250: 17, # 'ъ' + 251: 75, # 'ы' + 252: 52, # 'ь' + 253: 253, # 'э' + 254: 42, # 'ю' + 255: 16, # 'я' +} + +WINDOWS_1251_BULGARIAN_MODEL = SingleByteCharSetModel(charset_name='windows-1251', + language='Bulgarian', + char_to_order_map=WINDOWS_1251_BULGARIAN_CHAR_TO_ORDER, + language_model=BULGARIAN_LANG_MODEL, + typical_positive_ratio=0.969392, + keep_ascii_letters=False, + alphabet='АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЮЯабвгдежзийклмнопрстуфхцчшщъьюя') + diff --git a/openpype/vendor/python/python_2/chardet/langgreekmodel.py b/openpype/vendor/python/python_2/chardet/langgreekmodel.py new file mode 100644 index 0000000000..02b94de655 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/langgreekmodel.py @@ -0,0 +1,4398 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from chardet.sbcharsetprober import SingleByteCharSetModel + + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +GREEK_LANG_MODEL = { + 60: { # 'e' + 60: 2, # 'e' + 55: 1, # 'o' + 58: 2, # 't' + 36: 1, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 1, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 55: { # 'o' + 60: 0, # 'e' + 55: 2, # 'o' + 58: 2, # 't' + 36: 1, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 1, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 1, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 58: { # 't' + 60: 2, # 'e' + 55: 1, # 'o' + 58: 1, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 1, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 36: { # '·' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 61: { # 'Ά' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 1, # 'γ' + 21: 2, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 1, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 46: { # 'Έ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 2, # 'β' + 20: 2, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 2, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 0, # 'ο' + 9: 2, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 1, # 'σ' + 2: 2, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 3, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 54: { # 'Ό' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 2, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 2, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 2, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 31: { # 'Α' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 2, # 'Β' + 43: 2, # 'Γ' + 41: 1, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 2, # 'Θ' + 47: 2, # 'Ι' + 44: 2, # 'Κ' + 53: 2, # 'Λ' + 38: 2, # 'Μ' + 49: 2, # 'Ν' + 59: 1, # 'Ξ' + 39: 0, # 'Ο' + 35: 2, # 'Π' + 48: 2, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 2, # 'Υ' + 56: 2, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 2, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 1, # 'θ' + 5: 0, # 'ι' + 11: 2, # 'κ' + 16: 3, # 'λ' + 10: 2, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 0, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 2, # 'ς' + 7: 2, # 'σ' + 2: 0, # 'τ' + 12: 3, # 'υ' + 28: 2, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 2, # 'ύ' + 27: 0, # 'ώ' + }, + 51: { # 'Β' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 1, # 'Ε' + 40: 1, # 'Η' + 52: 0, # 'Θ' + 47: 1, # 'Ι' + 44: 0, # 'Κ' + 53: 1, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 2, # 'ή' + 15: 0, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 43: { # 'Γ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 1, # 'Α' + 51: 0, # 'Β' + 43: 2, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 1, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 1, # 'Κ' + 53: 1, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 1, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 2, # 'Υ' + 56: 0, # 'Φ' + 50: 1, # 'Χ' + 57: 2, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 41: { # 'Δ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 2, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 2, # 'ή' + 15: 2, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 1, # 'ό' + 26: 2, # 'ύ' + 27: 2, # 'ώ' + }, + 34: { # 'Ε' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 2, # 'Γ' + 41: 2, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 2, # 'Κ' + 53: 2, # 'Λ' + 38: 2, # 'Μ' + 49: 2, # 'Ν' + 59: 1, # 'Ξ' + 39: 0, # 'Ο' + 35: 2, # 'Π' + 48: 2, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 2, # 'Υ' + 56: 0, # 'Φ' + 50: 2, # 'Χ' + 57: 2, # 'Ω' + 17: 3, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 3, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 3, # 'γ' + 21: 2, # 'δ' + 3: 1, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 1, # 'θ' + 5: 2, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 2, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 0, # 'ο' + 9: 3, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 2, # 'σ' + 2: 2, # 'τ' + 12: 2, # 'υ' + 28: 2, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 1, # 'ύ' + 27: 0, # 'ώ' + }, + 40: { # 'Η' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 1, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 2, # 'Θ' + 47: 0, # 'Ι' + 44: 2, # 'Κ' + 53: 0, # 'Λ' + 38: 2, # 'Μ' + 49: 2, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 2, # 'Π' + 48: 2, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 1, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 1, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 1, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 52: { # 'Θ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 1, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 1, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 2, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 2, # 'ύ' + 27: 0, # 'ώ' + }, + 47: { # 'Ι' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 1, # 'Β' + 43: 1, # 'Γ' + 41: 2, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 2, # 'Κ' + 53: 2, # 'Λ' + 38: 2, # 'Μ' + 49: 2, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 0, # 'Υ' + 56: 2, # 'Φ' + 50: 0, # 'Χ' + 57: 2, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 2, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 1, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 2, # 'σ' + 2: 1, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 1, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 44: { # 'Κ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 1, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 1, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 0, # 'Σ' + 33: 1, # 'Τ' + 45: 2, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 1, # 'Ω' + 17: 3, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 2, # 'ό' + 26: 2, # 'ύ' + 27: 2, # 'ώ' + }, + 53: { # 'Λ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 2, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 2, # 'Σ' + 33: 0, # 'Τ' + 45: 2, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 2, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 0, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 1, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 2, # 'ό' + 26: 2, # 'ύ' + 27: 0, # 'ώ' + }, + 38: { # 'Μ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 2, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 2, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 2, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 2, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 3, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 2, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 49: { # 'Ν' + 60: 2, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 2, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 2, # 'Ω' + 17: 0, # 'ά' + 18: 2, # 'έ' + 22: 0, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 1, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 1, # 'ω' + 19: 2, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 59: { # 'Ξ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 1, # 'Ε' + 40: 1, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 1, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 2, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 39: { # 'Ο' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 1, # 'Β' + 43: 2, # 'Γ' + 41: 2, # 'Δ' + 34: 2, # 'Ε' + 40: 1, # 'Η' + 52: 2, # 'Θ' + 47: 2, # 'Ι' + 44: 2, # 'Κ' + 53: 2, # 'Λ' + 38: 2, # 'Μ' + 49: 2, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 2, # 'Π' + 48: 2, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 2, # 'Υ' + 56: 2, # 'Φ' + 50: 2, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 2, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 2, # 'κ' + 16: 2, # 'λ' + 10: 2, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 2, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 2, # 'τ' + 12: 2, # 'υ' + 28: 1, # 'φ' + 23: 1, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 2, # 'ύ' + 27: 0, # 'ώ' + }, + 35: { # 'Π' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 2, # 'Λ' + 38: 1, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 0, # 'Σ' + 33: 1, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 1, # 'Χ' + 57: 2, # 'Ω' + 17: 2, # 'ά' + 18: 1, # 'έ' + 22: 1, # 'ή' + 15: 2, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 2, # 'ό' + 26: 0, # 'ύ' + 27: 3, # 'ώ' + }, + 48: { # 'Ρ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 1, # 'Γ' + 41: 1, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 2, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 0, # 'Σ' + 33: 1, # 'Τ' + 45: 1, # 'Υ' + 56: 0, # 'Φ' + 50: 1, # 'Χ' + 57: 1, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 2, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 1, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 2, # 'ύ' + 27: 0, # 'ώ' + }, + 37: { # 'Σ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 1, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 2, # 'Κ' + 53: 0, # 'Λ' + 38: 2, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 2, # 'Υ' + 56: 0, # 'Φ' + 50: 2, # 'Χ' + 57: 2, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 2, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 2, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 2, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 2, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 2, # 'ύ' + 27: 2, # 'ώ' + }, + 33: { # 'Τ' + 60: 0, # 'e' + 55: 1, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 2, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 0, # 'Σ' + 33: 1, # 'Τ' + 45: 1, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 2, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 0, # 'ή' + 15: 2, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 2, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 2, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 2, # 'ό' + 26: 2, # 'ύ' + 27: 3, # 'ώ' + }, + 45: { # 'Υ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 2, # 'Γ' + 41: 0, # 'Δ' + 34: 1, # 'Ε' + 40: 2, # 'Η' + 52: 2, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 1, # 'Λ' + 38: 2, # 'Μ' + 49: 2, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 2, # 'Π' + 48: 1, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 1, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 3, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 56: { # 'Φ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 1, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 1, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 2, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 1, # 'ύ' + 27: 1, # 'ώ' + }, + 50: { # 'Χ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 1, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 1, # 'Ν' + 59: 0, # 'Ξ' + 39: 1, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 1, # 'Χ' + 57: 1, # 'Ω' + 17: 2, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 2, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 57: { # 'Ω' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 1, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 1, # 'Λ' + 38: 0, # 'Μ' + 49: 2, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 2, # 'ρ' + 14: 2, # 'ς' + 7: 2, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 1, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 17: { # 'ά' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 3, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 3, # 'ε' + 32: 3, # 'ζ' + 13: 0, # 'η' + 25: 3, # 'θ' + 5: 2, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 0, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 3, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 18: { # 'έ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 3, # 'α' + 29: 2, # 'β' + 20: 3, # 'γ' + 21: 2, # 'δ' + 3: 3, # 'ε' + 32: 2, # 'ζ' + 13: 0, # 'η' + 25: 3, # 'θ' + 5: 0, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 3, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 22: { # 'ή' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 1, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 3, # 'θ' + 5: 0, # 'ι' + 11: 3, # 'κ' + 16: 2, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 0, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 15: { # 'ί' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 3, # 'α' + 29: 2, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 3, # 'ε' + 32: 3, # 'ζ' + 13: 3, # 'η' + 25: 3, # 'θ' + 5: 0, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 1, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 3, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 1: { # 'α' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 2, # 'έ' + 22: 0, # 'ή' + 15: 3, # 'ί' + 1: 0, # 'α' + 29: 3, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 2, # 'ε' + 32: 3, # 'ζ' + 13: 1, # 'η' + 25: 3, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 2, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 0, # 'ω' + 19: 2, # 'ό' + 26: 2, # 'ύ' + 27: 0, # 'ώ' + }, + 29: { # 'β' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 2, # 'έ' + 22: 3, # 'ή' + 15: 2, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 2, # 'γ' + 21: 2, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 3, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 2, # 'ό' + 26: 2, # 'ύ' + 27: 2, # 'ώ' + }, + 20: { # 'γ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 3, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 3, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 2, # 'ύ' + 27: 3, # 'ώ' + }, + 21: { # 'δ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 3: { # 'ε' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 3, # 'ί' + 1: 2, # 'α' + 29: 3, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 2, # 'ε' + 32: 2, # 'ζ' + 13: 0, # 'η' + 25: 3, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 2, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 3, # 'ω' + 19: 2, # 'ό' + 26: 3, # 'ύ' + 27: 2, # 'ώ' + }, + 32: { # 'ζ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 2, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 1, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 2, # 'ό' + 26: 0, # 'ύ' + 27: 2, # 'ώ' + }, + 13: { # 'η' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 3, # 'γ' + 21: 2, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 3, # 'θ' + 5: 0, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 0, # 'ο' + 9: 2, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 25: { # 'θ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 2, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 1, # 'λ' + 10: 3, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 5: { # 'ι' + 60: 0, # 'e' + 55: 1, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 1, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 0, # 'ί' + 1: 3, # 'α' + 29: 3, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 3, # 'ε' + 32: 2, # 'ζ' + 13: 3, # 'η' + 25: 3, # 'θ' + 5: 0, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 0, # 'ύ' + 27: 3, # 'ώ' + }, + 11: { # 'κ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 3, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 2, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 2, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 2, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 16: { # 'λ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 1, # 'β' + 20: 2, # 'γ' + 21: 1, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 2, # 'θ' + 5: 3, # 'ι' + 11: 2, # 'κ' + 16: 3, # 'λ' + 10: 2, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 2, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 10: { # 'μ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 1, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 3, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 3, # 'φ' + 23: 0, # 'χ' + 42: 2, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 2, # 'ύ' + 27: 2, # 'ώ' + }, + 6: { # 'ν' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 3, # 'δ' + 3: 3, # 'ε' + 32: 2, # 'ζ' + 13: 3, # 'η' + 25: 3, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 1, # 'λ' + 10: 0, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 30: { # 'ξ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 2, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 2, # 'ό' + 26: 3, # 'ύ' + 27: 1, # 'ώ' + }, + 4: { # 'ο' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 2, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 2, # 'α' + 29: 3, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 3, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 2, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 2, # 'ω' + 19: 1, # 'ό' + 26: 3, # 'ύ' + 27: 2, # 'ώ' + }, + 9: { # 'π' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 3, # 'λ' + 10: 0, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 2, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 2, # 'ύ' + 27: 3, # 'ώ' + }, + 8: { # 'ρ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 2, # 'β' + 20: 3, # 'γ' + 21: 2, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 3, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 1, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 3, # 'ο' + 9: 2, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 2, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 14: { # 'ς' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 7: { # 'σ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 3, # 'β' + 20: 0, # 'γ' + 21: 2, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 3, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 2, # 'λ' + 10: 3, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 2, # 'ώ' + }, + 2: { # 'τ' + 60: 0, # 'e' + 55: 2, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 2, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 2, # 'κ' + 16: 2, # 'λ' + 10: 3, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 2, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 12: { # 'υ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 3, # 'ή' + 15: 2, # 'ί' + 1: 3, # 'α' + 29: 2, # 'β' + 20: 3, # 'γ' + 21: 2, # 'δ' + 3: 2, # 'ε' + 32: 2, # 'ζ' + 13: 2, # 'η' + 25: 3, # 'θ' + 5: 2, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 2, # 'ω' + 19: 2, # 'ό' + 26: 0, # 'ύ' + 27: 2, # 'ώ' + }, + 28: { # 'φ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 2, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 1, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 1, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 2, # 'ύ' + 27: 2, # 'ώ' + }, + 23: { # 'χ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 2, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 2, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 2, # 'μ' + 6: 3, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 42: { # 'ψ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 1, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 2, # 'τ' + 12: 1, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 24: { # 'ω' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 1, # 'ά' + 18: 0, # 'έ' + 22: 2, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 2, # 'β' + 20: 3, # 'γ' + 21: 2, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 3, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 19: { # 'ό' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 3, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 1, # 'ε' + 32: 2, # 'ζ' + 13: 2, # 'η' + 25: 2, # 'θ' + 5: 2, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 1, # 'ξ' + 4: 2, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 26: { # 'ύ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 2, # 'α' + 29: 2, # 'β' + 20: 2, # 'γ' + 21: 1, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 3, # 'θ' + 5: 0, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 2, # 'χ' + 42: 2, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 27: { # 'ώ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 1, # 'β' + 20: 0, # 'γ' + 21: 3, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 1, # 'η' + 25: 2, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 1, # 'ξ' + 4: 0, # 'ο' + 9: 2, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 1, # 'φ' + 23: 1, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, +} + +# 255: Undefined characters that did not exist in training text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 +# 251: Control characters + +# Character Mapping Table(s): +WINDOWS_1253_GREEK_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 82, # 'A' + 66: 100, # 'B' + 67: 104, # 'C' + 68: 94, # 'D' + 69: 98, # 'E' + 70: 101, # 'F' + 71: 116, # 'G' + 72: 102, # 'H' + 73: 111, # 'I' + 74: 187, # 'J' + 75: 117, # 'K' + 76: 92, # 'L' + 77: 88, # 'M' + 78: 113, # 'N' + 79: 85, # 'O' + 80: 79, # 'P' + 81: 118, # 'Q' + 82: 105, # 'R' + 83: 83, # 'S' + 84: 67, # 'T' + 85: 114, # 'U' + 86: 119, # 'V' + 87: 95, # 'W' + 88: 99, # 'X' + 89: 109, # 'Y' + 90: 188, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 72, # 'a' + 98: 70, # 'b' + 99: 80, # 'c' + 100: 81, # 'd' + 101: 60, # 'e' + 102: 96, # 'f' + 103: 93, # 'g' + 104: 89, # 'h' + 105: 68, # 'i' + 106: 120, # 'j' + 107: 97, # 'k' + 108: 77, # 'l' + 109: 86, # 'm' + 110: 69, # 'n' + 111: 55, # 'o' + 112: 78, # 'p' + 113: 115, # 'q' + 114: 65, # 'r' + 115: 66, # 's' + 116: 58, # 't' + 117: 76, # 'u' + 118: 106, # 'v' + 119: 103, # 'w' + 120: 87, # 'x' + 121: 107, # 'y' + 122: 112, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 255, # '€' + 129: 255, # None + 130: 255, # '‚' + 131: 255, # 'ƒ' + 132: 255, # '„' + 133: 255, # '…' + 134: 255, # '†' + 135: 255, # '‡' + 136: 255, # None + 137: 255, # '‰' + 138: 255, # None + 139: 255, # '‹' + 140: 255, # None + 141: 255, # None + 142: 255, # None + 143: 255, # None + 144: 255, # None + 145: 255, # '‘' + 146: 255, # '’' + 147: 255, # '“' + 148: 255, # '”' + 149: 255, # '•' + 150: 255, # '–' + 151: 255, # '—' + 152: 255, # None + 153: 255, # '™' + 154: 255, # None + 155: 255, # '›' + 156: 255, # None + 157: 255, # None + 158: 255, # None + 159: 255, # None + 160: 253, # '\xa0' + 161: 233, # '΅' + 162: 61, # 'Ά' + 163: 253, # '£' + 164: 253, # '¤' + 165: 253, # '¥' + 166: 253, # '¦' + 167: 253, # '§' + 168: 253, # '¨' + 169: 253, # '©' + 170: 253, # None + 171: 253, # '«' + 172: 253, # '¬' + 173: 74, # '\xad' + 174: 253, # '®' + 175: 253, # '―' + 176: 253, # '°' + 177: 253, # '±' + 178: 253, # '²' + 179: 253, # '³' + 180: 247, # '΄' + 181: 253, # 'µ' + 182: 253, # '¶' + 183: 36, # '·' + 184: 46, # 'Έ' + 185: 71, # 'Ή' + 186: 73, # 'Ί' + 187: 253, # '»' + 188: 54, # 'Ό' + 189: 253, # '½' + 190: 108, # 'Ύ' + 191: 123, # 'Ώ' + 192: 110, # 'ΐ' + 193: 31, # 'Α' + 194: 51, # 'Β' + 195: 43, # 'Γ' + 196: 41, # 'Δ' + 197: 34, # 'Ε' + 198: 91, # 'Ζ' + 199: 40, # 'Η' + 200: 52, # 'Θ' + 201: 47, # 'Ι' + 202: 44, # 'Κ' + 203: 53, # 'Λ' + 204: 38, # 'Μ' + 205: 49, # 'Ν' + 206: 59, # 'Ξ' + 207: 39, # 'Ο' + 208: 35, # 'Π' + 209: 48, # 'Ρ' + 210: 250, # None + 211: 37, # 'Σ' + 212: 33, # 'Τ' + 213: 45, # 'Υ' + 214: 56, # 'Φ' + 215: 50, # 'Χ' + 216: 84, # 'Ψ' + 217: 57, # 'Ω' + 218: 120, # 'Ϊ' + 219: 121, # 'Ϋ' + 220: 17, # 'ά' + 221: 18, # 'έ' + 222: 22, # 'ή' + 223: 15, # 'ί' + 224: 124, # 'ΰ' + 225: 1, # 'α' + 226: 29, # 'β' + 227: 20, # 'γ' + 228: 21, # 'δ' + 229: 3, # 'ε' + 230: 32, # 'ζ' + 231: 13, # 'η' + 232: 25, # 'θ' + 233: 5, # 'ι' + 234: 11, # 'κ' + 235: 16, # 'λ' + 236: 10, # 'μ' + 237: 6, # 'ν' + 238: 30, # 'ξ' + 239: 4, # 'ο' + 240: 9, # 'π' + 241: 8, # 'ρ' + 242: 14, # 'ς' + 243: 7, # 'σ' + 244: 2, # 'τ' + 245: 12, # 'υ' + 246: 28, # 'φ' + 247: 23, # 'χ' + 248: 42, # 'ψ' + 249: 24, # 'ω' + 250: 64, # 'ϊ' + 251: 75, # 'ϋ' + 252: 19, # 'ό' + 253: 26, # 'ύ' + 254: 27, # 'ώ' + 255: 253, # None +} + +WINDOWS_1253_GREEK_MODEL = SingleByteCharSetModel(charset_name='windows-1253', + language='Greek', + char_to_order_map=WINDOWS_1253_GREEK_CHAR_TO_ORDER, + language_model=GREEK_LANG_MODEL, + typical_positive_ratio=0.982851, + keep_ascii_letters=False, + alphabet='ΆΈΉΊΌΎΏΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩάέήίαβγδεζηθικλμνξοπρςστυφχψωόύώ') + +ISO_8859_7_GREEK_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 82, # 'A' + 66: 100, # 'B' + 67: 104, # 'C' + 68: 94, # 'D' + 69: 98, # 'E' + 70: 101, # 'F' + 71: 116, # 'G' + 72: 102, # 'H' + 73: 111, # 'I' + 74: 187, # 'J' + 75: 117, # 'K' + 76: 92, # 'L' + 77: 88, # 'M' + 78: 113, # 'N' + 79: 85, # 'O' + 80: 79, # 'P' + 81: 118, # 'Q' + 82: 105, # 'R' + 83: 83, # 'S' + 84: 67, # 'T' + 85: 114, # 'U' + 86: 119, # 'V' + 87: 95, # 'W' + 88: 99, # 'X' + 89: 109, # 'Y' + 90: 188, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 72, # 'a' + 98: 70, # 'b' + 99: 80, # 'c' + 100: 81, # 'd' + 101: 60, # 'e' + 102: 96, # 'f' + 103: 93, # 'g' + 104: 89, # 'h' + 105: 68, # 'i' + 106: 120, # 'j' + 107: 97, # 'k' + 108: 77, # 'l' + 109: 86, # 'm' + 110: 69, # 'n' + 111: 55, # 'o' + 112: 78, # 'p' + 113: 115, # 'q' + 114: 65, # 'r' + 115: 66, # 's' + 116: 58, # 't' + 117: 76, # 'u' + 118: 106, # 'v' + 119: 103, # 'w' + 120: 87, # 'x' + 121: 107, # 'y' + 122: 112, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 255, # '\x80' + 129: 255, # '\x81' + 130: 255, # '\x82' + 131: 255, # '\x83' + 132: 255, # '\x84' + 133: 255, # '\x85' + 134: 255, # '\x86' + 135: 255, # '\x87' + 136: 255, # '\x88' + 137: 255, # '\x89' + 138: 255, # '\x8a' + 139: 255, # '\x8b' + 140: 255, # '\x8c' + 141: 255, # '\x8d' + 142: 255, # '\x8e' + 143: 255, # '\x8f' + 144: 255, # '\x90' + 145: 255, # '\x91' + 146: 255, # '\x92' + 147: 255, # '\x93' + 148: 255, # '\x94' + 149: 255, # '\x95' + 150: 255, # '\x96' + 151: 255, # '\x97' + 152: 255, # '\x98' + 153: 255, # '\x99' + 154: 255, # '\x9a' + 155: 255, # '\x9b' + 156: 255, # '\x9c' + 157: 255, # '\x9d' + 158: 255, # '\x9e' + 159: 255, # '\x9f' + 160: 253, # '\xa0' + 161: 233, # '‘' + 162: 90, # '’' + 163: 253, # '£' + 164: 253, # '€' + 165: 253, # '₯' + 166: 253, # '¦' + 167: 253, # '§' + 168: 253, # '¨' + 169: 253, # '©' + 170: 253, # 'ͺ' + 171: 253, # '«' + 172: 253, # '¬' + 173: 74, # '\xad' + 174: 253, # None + 175: 253, # '―' + 176: 253, # '°' + 177: 253, # '±' + 178: 253, # '²' + 179: 253, # '³' + 180: 247, # '΄' + 181: 248, # '΅' + 182: 61, # 'Ά' + 183: 36, # '·' + 184: 46, # 'Έ' + 185: 71, # 'Ή' + 186: 73, # 'Ί' + 187: 253, # '»' + 188: 54, # 'Ό' + 189: 253, # '½' + 190: 108, # 'Ύ' + 191: 123, # 'Ώ' + 192: 110, # 'ΐ' + 193: 31, # 'Α' + 194: 51, # 'Β' + 195: 43, # 'Γ' + 196: 41, # 'Δ' + 197: 34, # 'Ε' + 198: 91, # 'Ζ' + 199: 40, # 'Η' + 200: 52, # 'Θ' + 201: 47, # 'Ι' + 202: 44, # 'Κ' + 203: 53, # 'Λ' + 204: 38, # 'Μ' + 205: 49, # 'Ν' + 206: 59, # 'Ξ' + 207: 39, # 'Ο' + 208: 35, # 'Π' + 209: 48, # 'Ρ' + 210: 250, # None + 211: 37, # 'Σ' + 212: 33, # 'Τ' + 213: 45, # 'Υ' + 214: 56, # 'Φ' + 215: 50, # 'Χ' + 216: 84, # 'Ψ' + 217: 57, # 'Ω' + 218: 120, # 'Ϊ' + 219: 121, # 'Ϋ' + 220: 17, # 'ά' + 221: 18, # 'έ' + 222: 22, # 'ή' + 223: 15, # 'ί' + 224: 124, # 'ΰ' + 225: 1, # 'α' + 226: 29, # 'β' + 227: 20, # 'γ' + 228: 21, # 'δ' + 229: 3, # 'ε' + 230: 32, # 'ζ' + 231: 13, # 'η' + 232: 25, # 'θ' + 233: 5, # 'ι' + 234: 11, # 'κ' + 235: 16, # 'λ' + 236: 10, # 'μ' + 237: 6, # 'ν' + 238: 30, # 'ξ' + 239: 4, # 'ο' + 240: 9, # 'π' + 241: 8, # 'ρ' + 242: 14, # 'ς' + 243: 7, # 'σ' + 244: 2, # 'τ' + 245: 12, # 'υ' + 246: 28, # 'φ' + 247: 23, # 'χ' + 248: 42, # 'ψ' + 249: 24, # 'ω' + 250: 64, # 'ϊ' + 251: 75, # 'ϋ' + 252: 19, # 'ό' + 253: 26, # 'ύ' + 254: 27, # 'ώ' + 255: 253, # None +} + +ISO_8859_7_GREEK_MODEL = SingleByteCharSetModel(charset_name='ISO-8859-7', + language='Greek', + char_to_order_map=ISO_8859_7_GREEK_CHAR_TO_ORDER, + language_model=GREEK_LANG_MODEL, + typical_positive_ratio=0.982851, + keep_ascii_letters=False, + alphabet='ΆΈΉΊΌΎΏΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩάέήίαβγδεζηθικλμνξοπρςστυφχψωόύώ') + diff --git a/openpype/vendor/python/python_2/chardet/langhebrewmodel.py b/openpype/vendor/python/python_2/chardet/langhebrewmodel.py new file mode 100644 index 0000000000..40fd674c4a --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/langhebrewmodel.py @@ -0,0 +1,4383 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from chardet.sbcharsetprober import SingleByteCharSetModel + + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +HEBREW_LANG_MODEL = { + 50: { # 'a' + 50: 0, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 2, # 'l' + 54: 2, # 'n' + 49: 0, # 'o' + 51: 2, # 'r' + 43: 1, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 1, # 'ק' + 7: 0, # 'ר' + 10: 1, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 60: { # 'c' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 0, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 0, # 'n' + 49: 1, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 61: { # 'd' + 50: 1, # 'a' + 60: 0, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 1, # 'n' + 49: 2, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 0, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 1, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 42: { # 'e' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 2, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 2, # 'l' + 54: 2, # 'n' + 49: 1, # 'o' + 51: 2, # 'r' + 43: 2, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 1, # '–' + 52: 2, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 53: { # 'i' + 50: 1, # 'a' + 60: 2, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 0, # 'i' + 56: 1, # 'l' + 54: 2, # 'n' + 49: 2, # 'o' + 51: 1, # 'r' + 43: 2, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 56: { # 'l' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 2, # 'e' + 53: 2, # 'i' + 56: 2, # 'l' + 54: 1, # 'n' + 49: 1, # 'o' + 51: 0, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 54: { # 'n' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 1, # 'n' + 49: 1, # 'o' + 51: 0, # 'r' + 43: 1, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 2, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 49: { # 'o' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 2, # 'n' + 49: 1, # 'o' + 51: 2, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 51: { # 'r' + 50: 2, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 2, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 1, # 'n' + 49: 2, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 2, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 43: { # 's' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 0, # 'd' + 42: 2, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 1, # 'n' + 49: 1, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 2, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 44: { # 't' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 0, # 'd' + 42: 2, # 'e' + 53: 2, # 'i' + 56: 1, # 'l' + 54: 0, # 'n' + 49: 1, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 1, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 2, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 63: { # 'u' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 1, # 'n' + 49: 0, # 'o' + 51: 1, # 'r' + 43: 2, # 's' + 44: 1, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 34: { # '\xa0' + 50: 1, # 'a' + 60: 0, # 'c' + 61: 1, # 'd' + 42: 0, # 'e' + 53: 1, # 'i' + 56: 0, # 'l' + 54: 1, # 'n' + 49: 1, # 'o' + 51: 0, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 0, # 'u' + 34: 2, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 1, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 2, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 2, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 1, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 55: { # '´' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 1, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 2, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 1, # 'ן' + 12: 1, # 'נ' + 19: 1, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 48: { # '¼' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 1, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 39: { # '½' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 57: { # '¾' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 30: { # 'ְ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 1, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 1, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 2, # 'ג' + 16: 2, # 'ד' + 3: 2, # 'ה' + 2: 2, # 'ו' + 24: 2, # 'ז' + 14: 2, # 'ח' + 22: 2, # 'ט' + 1: 2, # 'י' + 25: 2, # 'ך' + 15: 2, # 'כ' + 4: 2, # 'ל' + 11: 1, # 'ם' + 6: 2, # 'מ' + 23: 0, # 'ן' + 12: 2, # 'נ' + 19: 2, # 'ס' + 13: 2, # 'ע' + 26: 0, # 'ף' + 18: 2, # 'פ' + 27: 0, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 59: { # 'ֱ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 1, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 1, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 2, # 'ל' + 11: 0, # 'ם' + 6: 2, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 41: { # 'ֲ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 2, # 'ב' + 20: 1, # 'ג' + 16: 2, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 1, # 'י' + 25: 1, # 'ך' + 15: 1, # 'כ' + 4: 2, # 'ל' + 11: 0, # 'ם' + 6: 2, # 'מ' + 23: 0, # 'ן' + 12: 2, # 'נ' + 19: 1, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 2, # 'צ' + 17: 1, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 33: { # 'ִ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 1, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 1, # 'ִ' + 37: 0, # 'ֵ' + 36: 1, # 'ֶ' + 31: 0, # 'ַ' + 29: 1, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 1, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 2, # 'ב' + 20: 2, # 'ג' + 16: 2, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 2, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 2, # 'כ' + 4: 2, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 2, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 2, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 37: { # 'ֵ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 1, # 'ֶ' + 31: 1, # 'ַ' + 29: 1, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 1, # 'ג' + 16: 2, # 'ד' + 3: 2, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 2, # 'ח' + 22: 1, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 1, # 'כ' + 4: 2, # 'ל' + 11: 2, # 'ם' + 6: 1, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 1, # 'ס' + 13: 2, # 'ע' + 26: 1, # 'ף' + 18: 1, # 'פ' + 27: 1, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 36: { # 'ֶ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 1, # 'ֶ' + 31: 1, # 'ַ' + 29: 1, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 1, # 'ג' + 16: 2, # 'ד' + 3: 2, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 2, # 'ח' + 22: 1, # 'ט' + 1: 2, # 'י' + 25: 2, # 'ך' + 15: 1, # 'כ' + 4: 2, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 2, # 'ס' + 13: 1, # 'ע' + 26: 1, # 'ף' + 18: 1, # 'פ' + 27: 2, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 31: { # 'ַ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 1, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 1, # 'ֶ' + 31: 0, # 'ַ' + 29: 2, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 2, # 'ג' + 16: 2, # 'ד' + 3: 2, # 'ה' + 2: 1, # 'ו' + 24: 2, # 'ז' + 14: 2, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 2, # 'כ' + 4: 2, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 2, # 'ס' + 13: 2, # 'ע' + 26: 2, # 'ף' + 18: 2, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 29: { # 'ָ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 1, # 'ַ' + 29: 2, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 1, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 2, # 'ג' + 16: 2, # 'ד' + 3: 3, # 'ה' + 2: 2, # 'ו' + 24: 2, # 'ז' + 14: 2, # 'ח' + 22: 1, # 'ט' + 1: 2, # 'י' + 25: 2, # 'ך' + 15: 2, # 'כ' + 4: 2, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 1, # 'ס' + 13: 2, # 'ע' + 26: 1, # 'ף' + 18: 2, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 35: { # 'ֹ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 1, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 1, # 'ג' + 16: 2, # 'ד' + 3: 2, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 1, # 'י' + 25: 1, # 'ך' + 15: 2, # 'כ' + 4: 2, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 2, # 'ס' + 13: 2, # 'ע' + 26: 1, # 'ף' + 18: 2, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 62: { # 'ֻ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 1, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 2, # 'ל' + 11: 1, # 'ם' + 6: 1, # 'מ' + 23: 1, # 'ן' + 12: 1, # 'נ' + 19: 1, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 28: { # 'ּ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 3, # 'ְ' + 59: 0, # 'ֱ' + 41: 1, # 'ֲ' + 33: 3, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 3, # 'ַ' + 29: 3, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 0, # 'ּ' + 38: 2, # 'ׁ' + 45: 1, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 1, # 'ג' + 16: 2, # 'ד' + 3: 1, # 'ה' + 2: 2, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 2, # 'י' + 25: 2, # 'ך' + 15: 2, # 'כ' + 4: 2, # 'ל' + 11: 1, # 'ם' + 6: 2, # 'מ' + 23: 1, # 'ן' + 12: 2, # 'נ' + 19: 1, # 'ס' + 13: 2, # 'ע' + 26: 1, # 'ף' + 18: 1, # 'פ' + 27: 1, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 38: { # 'ׁ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 2, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 45: { # 'ׂ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 1, # 'ֵ' + 36: 2, # 'ֶ' + 31: 1, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 1, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 2, # 'ו' + 24: 0, # 'ז' + 14: 1, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 1, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 0, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 0, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 9: { # 'א' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 2, # 'ֱ' + 41: 2, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 2, # 'ע' + 26: 3, # 'ף' + 18: 3, # 'פ' + 27: 1, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 8: { # 'ב' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 1, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 3, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 1, # 'ף' + 18: 3, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 1, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 20: { # 'ג' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 2, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 1, # 'ִ' + 37: 1, # 'ֵ' + 36: 1, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 0, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 3, # 'ב' + 20: 2, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 2, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 1, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 2, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 2, # 'פ' + 27: 1, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 16: { # 'ד' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 1, # 'ז' + 14: 2, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 2, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 2, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 0, # 'ץ' + 21: 2, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 3: { # 'ה' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 1, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 1, # 'ְ' + 59: 1, # 'ֱ' + 41: 2, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 3, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 0, # 'ף' + 18: 3, # 'פ' + 27: 1, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 1, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 2: { # 'ו' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 1, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 1, # 'ֵ' + 36: 1, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 3, # 'ֹ' + 62: 0, # 'ֻ' + 28: 3, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 3, # 'ף' + 18: 3, # 'פ' + 27: 3, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 1, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 24: { # 'ז' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 1, # 'ֲ' + 33: 1, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 2, # 'ב' + 20: 2, # 'ג' + 16: 2, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 2, # 'ז' + 14: 2, # 'ח' + 22: 1, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 1, # 'ס' + 13: 2, # 'ע' + 26: 1, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 2, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 1, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 14: { # 'ח' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 1, # 'ֱ' + 41: 2, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 3, # 'ב' + 20: 2, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 2, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 2, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 1, # 'ע' + 26: 2, # 'ף' + 18: 2, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 22: { # 'ט' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 1, # 'ֵ' + 36: 1, # 'ֶ' + 31: 2, # 'ַ' + 29: 1, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 1, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 1, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 2, # 'ז' + 14: 3, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 2, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 2, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 3, # 'ר' + 10: 2, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 1: { # 'י' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 1, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 3, # 'ף' + 18: 3, # 'פ' + 27: 3, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 1, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 25: { # 'ך' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 2, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 1, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 1, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 1, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 15: { # 'כ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 3, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 2, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 2, # 'ע' + 26: 3, # 'ף' + 18: 3, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 4: { # 'ל' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 3, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 1, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 11: { # 'ם' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 1, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 0, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 1, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 6: { # 'מ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 0, # 'ף' + 18: 3, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 23: { # 'ן' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 1, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 0, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 1, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 1, # 'ס' + 13: 1, # 'ע' + 26: 1, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 1, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 1, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 12: { # 'נ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 19: { # 'ס' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 1, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 1, # 'ָ' + 35: 1, # 'ֹ' + 62: 2, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 1, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 2, # 'ס' + 13: 3, # 'ע' + 26: 3, # 'ף' + 18: 3, # 'פ' + 27: 0, # 'ץ' + 21: 2, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 1, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 13: { # 'ע' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 1, # 'ְ' + 59: 1, # 'ֱ' + 41: 2, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 1, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 2, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 2, # 'ע' + 26: 1, # 'ף' + 18: 2, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 26: { # 'ף' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 1, # 'ו' + 24: 0, # 'ז' + 14: 1, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 1, # 'ס' + 13: 0, # 'ע' + 26: 1, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 1, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 18: { # 'פ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 1, # 'ֵ' + 36: 2, # 'ֶ' + 31: 1, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 2, # 'ב' + 20: 3, # 'ג' + 16: 2, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 2, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 2, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 27: { # 'ץ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 1, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 0, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 21: { # 'צ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 1, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 2, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 1, # 'ז' + 14: 3, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 1, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 1, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 2, # 'ץ' + 21: 2, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 0, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 17: { # 'ק' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 1, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 2, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 2, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 1, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 2, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 7: { # 'ר' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 2, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 1, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 3, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 10: { # 'ש' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 1, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 1, # 'ִ' + 37: 1, # 'ֵ' + 36: 1, # 'ֶ' + 31: 1, # 'ַ' + 29: 1, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 3, # 'ׁ' + 45: 2, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 2, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 2, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 5: { # 'ת' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 1, # '¼' + 39: 1, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 2, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 2, # 'ז' + 14: 3, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 2, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 1, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 32: { # '–' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 1, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 1, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 0, # 'ז' + 14: 1, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 1, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 1, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 52: { # '’' + 50: 1, # 'a' + 60: 0, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 1, # 'r' + 43: 2, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 1, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 47: { # '“' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 1, # 'n' + 49: 1, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 1, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 1, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 46: { # '”' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 1, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 1, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 58: { # '†' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 2, # '†' + 40: 0, # '…' + }, + 40: { # '…' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 0, # 'l' + 54: 1, # 'n' + 49: 0, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, +} + +# 255: Undefined characters that did not exist in training text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 +# 251: Control characters + +# Character Mapping Table(s): +WINDOWS_1255_HEBREW_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 69, # 'A' + 66: 91, # 'B' + 67: 79, # 'C' + 68: 80, # 'D' + 69: 92, # 'E' + 70: 89, # 'F' + 71: 97, # 'G' + 72: 90, # 'H' + 73: 68, # 'I' + 74: 111, # 'J' + 75: 112, # 'K' + 76: 82, # 'L' + 77: 73, # 'M' + 78: 95, # 'N' + 79: 85, # 'O' + 80: 78, # 'P' + 81: 121, # 'Q' + 82: 86, # 'R' + 83: 71, # 'S' + 84: 67, # 'T' + 85: 102, # 'U' + 86: 107, # 'V' + 87: 84, # 'W' + 88: 114, # 'X' + 89: 103, # 'Y' + 90: 115, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 50, # 'a' + 98: 74, # 'b' + 99: 60, # 'c' + 100: 61, # 'd' + 101: 42, # 'e' + 102: 76, # 'f' + 103: 70, # 'g' + 104: 64, # 'h' + 105: 53, # 'i' + 106: 105, # 'j' + 107: 93, # 'k' + 108: 56, # 'l' + 109: 65, # 'm' + 110: 54, # 'n' + 111: 49, # 'o' + 112: 66, # 'p' + 113: 110, # 'q' + 114: 51, # 'r' + 115: 43, # 's' + 116: 44, # 't' + 117: 63, # 'u' + 118: 81, # 'v' + 119: 77, # 'w' + 120: 98, # 'x' + 121: 75, # 'y' + 122: 108, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 124, # '€' + 129: 202, # None + 130: 203, # '‚' + 131: 204, # 'ƒ' + 132: 205, # '„' + 133: 40, # '…' + 134: 58, # '†' + 135: 206, # '‡' + 136: 207, # 'ˆ' + 137: 208, # '‰' + 138: 209, # None + 139: 210, # '‹' + 140: 211, # None + 141: 212, # None + 142: 213, # None + 143: 214, # None + 144: 215, # None + 145: 83, # '‘' + 146: 52, # '’' + 147: 47, # '“' + 148: 46, # '”' + 149: 72, # '•' + 150: 32, # '–' + 151: 94, # '—' + 152: 216, # '˜' + 153: 113, # '™' + 154: 217, # None + 155: 109, # '›' + 156: 218, # None + 157: 219, # None + 158: 220, # None + 159: 221, # None + 160: 34, # '\xa0' + 161: 116, # '¡' + 162: 222, # '¢' + 163: 118, # '£' + 164: 100, # '₪' + 165: 223, # '¥' + 166: 224, # '¦' + 167: 117, # '§' + 168: 119, # '¨' + 169: 104, # '©' + 170: 125, # '×' + 171: 225, # '«' + 172: 226, # '¬' + 173: 87, # '\xad' + 174: 99, # '®' + 175: 227, # '¯' + 176: 106, # '°' + 177: 122, # '±' + 178: 123, # '²' + 179: 228, # '³' + 180: 55, # '´' + 181: 229, # 'µ' + 182: 230, # '¶' + 183: 101, # '·' + 184: 231, # '¸' + 185: 232, # '¹' + 186: 120, # '÷' + 187: 233, # '»' + 188: 48, # '¼' + 189: 39, # '½' + 190: 57, # '¾' + 191: 234, # '¿' + 192: 30, # 'ְ' + 193: 59, # 'ֱ' + 194: 41, # 'ֲ' + 195: 88, # 'ֳ' + 196: 33, # 'ִ' + 197: 37, # 'ֵ' + 198: 36, # 'ֶ' + 199: 31, # 'ַ' + 200: 29, # 'ָ' + 201: 35, # 'ֹ' + 202: 235, # None + 203: 62, # 'ֻ' + 204: 28, # 'ּ' + 205: 236, # 'ֽ' + 206: 126, # '־' + 207: 237, # 'ֿ' + 208: 238, # '׀' + 209: 38, # 'ׁ' + 210: 45, # 'ׂ' + 211: 239, # '׃' + 212: 240, # 'װ' + 213: 241, # 'ױ' + 214: 242, # 'ײ' + 215: 243, # '׳' + 216: 127, # '״' + 217: 244, # None + 218: 245, # None + 219: 246, # None + 220: 247, # None + 221: 248, # None + 222: 249, # None + 223: 250, # None + 224: 9, # 'א' + 225: 8, # 'ב' + 226: 20, # 'ג' + 227: 16, # 'ד' + 228: 3, # 'ה' + 229: 2, # 'ו' + 230: 24, # 'ז' + 231: 14, # 'ח' + 232: 22, # 'ט' + 233: 1, # 'י' + 234: 25, # 'ך' + 235: 15, # 'כ' + 236: 4, # 'ל' + 237: 11, # 'ם' + 238: 6, # 'מ' + 239: 23, # 'ן' + 240: 12, # 'נ' + 241: 19, # 'ס' + 242: 13, # 'ע' + 243: 26, # 'ף' + 244: 18, # 'פ' + 245: 27, # 'ץ' + 246: 21, # 'צ' + 247: 17, # 'ק' + 248: 7, # 'ר' + 249: 10, # 'ש' + 250: 5, # 'ת' + 251: 251, # None + 252: 252, # None + 253: 128, # '\u200e' + 254: 96, # '\u200f' + 255: 253, # None +} + +WINDOWS_1255_HEBREW_MODEL = SingleByteCharSetModel(charset_name='windows-1255', + language='Hebrew', + char_to_order_map=WINDOWS_1255_HEBREW_CHAR_TO_ORDER, + language_model=HEBREW_LANG_MODEL, + typical_positive_ratio=0.984004, + keep_ascii_letters=False, + alphabet='אבגדהוזחטיךכלםמןנסעףפץצקרשתװױײ') + diff --git a/openpype/vendor/python/python_2/chardet/langhungarianmodel.py b/openpype/vendor/python/python_2/chardet/langhungarianmodel.py new file mode 100644 index 0000000000..24a097f520 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/langhungarianmodel.py @@ -0,0 +1,4650 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from chardet.sbcharsetprober import SingleByteCharSetModel + + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +HUNGARIAN_LANG_MODEL = { + 28: { # 'A' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 2, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 2, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 2, # 'K' + 41: 2, # 'L' + 34: 1, # 'M' + 35: 2, # 'N' + 47: 1, # 'O' + 46: 2, # 'P' + 43: 2, # 'R' + 33: 2, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 2, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 1, # 'j' + 7: 2, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 2, # 'n' + 8: 0, # 'o' + 23: 2, # 'p' + 10: 2, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 1, # 'u' + 19: 1, # 'v' + 62: 1, # 'x' + 16: 0, # 'y' + 11: 3, # 'z' + 51: 1, # 'Á' + 44: 0, # 'É' + 61: 1, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 40: { # 'B' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 0, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 3, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 2, # 'i' + 22: 1, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 3, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 54: { # 'C' + 28: 1, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 0, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 2, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 0, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 1, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 3, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 1, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 45: { # 'D' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 0, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 0, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 3, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 1, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 1, # 'o' + 23: 0, # 'p' + 10: 2, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 2, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 1, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 0, # 'ű' + }, + 32: { # 'E' + 28: 1, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 2, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 2, # 'K' + 41: 2, # 'L' + 34: 2, # 'M' + 35: 2, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 2, # 'R' + 33: 2, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 1, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 3, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 2, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 2, # 's' + 3: 1, # 't' + 21: 2, # 'u' + 19: 1, # 'v' + 62: 1, # 'x' + 16: 0, # 'y' + 11: 3, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 0, # 'Ú' + 63: 1, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 1, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 50: { # 'F' + 28: 1, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 0, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 0, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 0, # 'V' + 55: 1, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 1, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 2, # 'i' + 22: 1, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 2, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 0, # 'Ú' + 63: 1, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 2, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 49: { # 'G' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 2, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 1, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 2, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 2, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 0, # 'ű' + }, + 38: { # 'H' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 0, # 'D' + 32: 1, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 1, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 1, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 1, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 0, # 'V' + 55: 1, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 2, # 'i' + 22: 1, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 0, # 'n' + 8: 3, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 2, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 2, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 1, # 'é' + 30: 2, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 39: { # 'I' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 2, # 'K' + 41: 2, # 'L' + 34: 1, # 'M' + 35: 2, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 2, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 2, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 0, # 'e' + 27: 1, # 'f' + 12: 2, # 'g' + 20: 1, # 'h' + 9: 0, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 1, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 2, # 's' + 3: 2, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 53: { # 'J' + 28: 2, # 'A' + 40: 0, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 1, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 1, # 'o' + 23: 0, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 2, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 2, # 'ó' + 24: 2, # 'ö' + 31: 1, # 'ú' + 29: 0, # 'ü' + 42: 1, # 'ő' + 56: 0, # 'ű' + }, + 36: { # 'K' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 0, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 1, # 'f' + 12: 0, # 'g' + 20: 1, # 'h' + 9: 3, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 2, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 2, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 2, # 'ö' + 31: 1, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 0, # 'ű' + }, + 41: { # 'L' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 2, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 3, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 2, # 'i' + 22: 1, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 2, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 0, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 34: { # 'M' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 0, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 3, # 'a' + 18: 0, # 'b' + 26: 1, # 'c' + 17: 0, # 'd' + 1: 3, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 3, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 3, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 2, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 1, # 'ű' + }, + 35: { # 'N' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 2, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 2, # 'Y' + 52: 1, # 'Z' + 2: 3, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 3, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 2, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 2, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 1, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 1, # 'ő' + 56: 0, # 'ű' + }, + 47: { # 'O' + 28: 1, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 2, # 'K' + 41: 2, # 'L' + 34: 2, # 'M' + 35: 2, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 2, # 'R' + 33: 2, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 1, # 'j' + 7: 2, # 'k' + 6: 2, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 1, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 1, # 's' + 3: 2, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 1, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 1, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 46: { # 'P' + 28: 1, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 0, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 2, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 1, # 'f' + 12: 0, # 'g' + 20: 1, # 'h' + 9: 2, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 2, # 'r' + 5: 1, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 0, # 'Ú' + 63: 1, # 'Ü' + 14: 3, # 'á' + 15: 2, # 'é' + 30: 0, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 0, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 0, # 'ű' + }, + 43: { # 'R' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 2, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 1, # 'h' + 9: 2, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 2, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 2, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 33: { # 'S' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 2, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 3, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 1, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 1, # 'h' + 9: 2, # 'i' + 22: 0, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 1, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 1, # 't' + 21: 1, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 3, # 'z' + 51: 2, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 37: { # 'T' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 1, # 'P' + 43: 2, # 'R' + 33: 1, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 1, # 'h' + 9: 2, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 0, # 't' + 21: 2, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 1, # 'z' + 51: 2, # 'Á' + 44: 2, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 2, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 57: { # 'U' + 28: 1, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 2, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 1, # 'e' + 27: 0, # 'f' + 12: 2, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 1, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 48: { # 'V' + 28: 2, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 0, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 2, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 2, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 0, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 0, # 'ó' + 24: 1, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 55: { # 'Y' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 2, # 'Z' + 2: 1, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 1, # 'd' + 1: 1, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 1, # 'o' + 23: 1, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 52: { # 'Z' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 0, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 2, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 1, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 1, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 1, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 8: 1, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 2, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 2: { # 'a' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 2, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 2, # 'o' + 23: 3, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 1, # 'x' + 16: 2, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 18: { # 'b' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 2, # 'k' + 6: 2, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 2, # 's' + 3: 1, # 't' + 21: 3, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 3, # 'ó' + 24: 2, # 'ö' + 31: 2, # 'ú' + 29: 2, # 'ü' + 42: 2, # 'ő' + 56: 1, # 'ű' + }, + 26: { # 'c' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 1, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 1, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 1, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 1, # 'j' + 7: 2, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 3, # 's' + 3: 2, # 't' + 21: 2, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 2, # 'á' + 15: 2, # 'é' + 30: 2, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 17: { # 'd' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 2, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 2, # 'k' + 6: 1, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 2, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 3, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 2, # 'ú' + 29: 2, # 'ü' + 42: 2, # 'ő' + 56: 1, # 'ű' + }, + 1: { # 'e' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 2, # 'e' + 27: 3, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 2, # 'o' + 23: 3, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 2, # 'u' + 19: 3, # 'v' + 62: 2, # 'x' + 16: 2, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 27: { # 'f' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 3, # 'o' + 23: 0, # 'p' + 10: 3, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 2, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 3, # 'ö' + 31: 1, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 12: { # 'g' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 2, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 2, # 'k' + 6: 3, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 3, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 3, # 'ó' + 24: 2, # 'ö' + 31: 2, # 'ú' + 29: 2, # 'ü' + 42: 2, # 'ő' + 56: 1, # 'ű' + }, + 20: { # 'h' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 0, # 'd' + 1: 3, # 'e' + 27: 0, # 'f' + 12: 1, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 3, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 2, # 's' + 3: 1, # 't' + 21: 3, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 2, # 'y' + 11: 0, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 3, # 'í' + 25: 2, # 'ó' + 24: 2, # 'ö' + 31: 2, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 9: { # 'i' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 3, # 'e' + 27: 3, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 2, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 2, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 1, # 'x' + 16: 1, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 3, # 'ó' + 24: 1, # 'ö' + 31: 2, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 1, # 'ű' + }, + 22: { # 'j' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 2, # 'b' + 26: 1, # 'c' + 17: 3, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 2, # 'h' + 9: 1, # 'i' + 22: 2, # 'j' + 7: 2, # 'k' + 6: 2, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 2, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 1, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 3, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 7: { # 'k' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 1, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 2, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 3, # 'í' + 25: 2, # 'ó' + 24: 3, # 'ö' + 31: 1, # 'ú' + 29: 3, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 6: { # 'l' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 1, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 1, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 2, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 3, # 'e' + 27: 3, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 2, # 'p' + 10: 2, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 3, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 3, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 2, # 'ú' + 29: 2, # 'ü' + 42: 3, # 'ő' + 56: 1, # 'ű' + }, + 13: { # 'm' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 1, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 8: 3, # 'o' + 23: 3, # 'p' + 10: 2, # 'r' + 5: 2, # 's' + 3: 2, # 't' + 21: 3, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 2, # 'ó' + 24: 2, # 'ö' + 31: 2, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 2, # 'ű' + }, + 4: { # 'n' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 2, # 'p' + 10: 2, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 2, # 'v' + 62: 1, # 'x' + 16: 3, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 2, # 'ó' + 24: 3, # 'ö' + 31: 2, # 'ú' + 29: 3, # 'ü' + 42: 2, # 'ő' + 56: 1, # 'ű' + }, + 8: { # 'o' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 1, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 2, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 2, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 1, # 'o' + 23: 3, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 2, # 'u' + 19: 3, # 'v' + 62: 1, # 'x' + 16: 1, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 23: { # 'p' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 1, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 2, # 'k' + 6: 3, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 8: 3, # 'o' + 23: 3, # 'p' + 10: 3, # 'r' + 5: 2, # 's' + 3: 2, # 't' + 21: 3, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 2, # 'ó' + 24: 2, # 'ö' + 31: 1, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 10: { # 'r' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 1, # 'x' + 16: 2, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 3, # 'ú' + 29: 3, # 'ü' + 42: 2, # 'ő' + 56: 2, # 'ű' + }, + 5: { # 's' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 2, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 2, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 1, # 'j' + 7: 3, # 'k' + 6: 2, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 3, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 3, # 'ú' + 29: 3, # 'ü' + 42: 2, # 'ő' + 56: 1, # 'ű' + }, + 3: { # 't' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 1, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 3, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 3, # 'ú' + 29: 3, # 'ü' + 42: 3, # 'ő' + 56: 2, # 'ű' + }, + 21: { # 'u' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 2, # 'b' + 26: 2, # 'c' + 17: 3, # 'd' + 1: 2, # 'e' + 27: 1, # 'f' + 12: 3, # 'g' + 20: 2, # 'h' + 9: 2, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 1, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 1, # 'u' + 19: 3, # 'v' + 62: 1, # 'x' + 16: 1, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 2, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 0, # 'ö' + 31: 1, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 19: { # 'v' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 2, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 3, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 1, # 'r' + 5: 2, # 's' + 3: 2, # 't' + 21: 2, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 2, # 'ó' + 24: 2, # 'ö' + 31: 1, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 62: { # 'x' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 0, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 1, # 'i' + 22: 0, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 1, # 'o' + 23: 1, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 16: { # 'y' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 2, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 2, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 2, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 2, # 'p' + 10: 2, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 2, # 'ó' + 24: 3, # 'ö' + 31: 2, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 2, # 'ű' + }, + 11: { # 'z' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 2, # 'b' + 26: 1, # 'c' + 17: 3, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 2, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 1, # 'j' + 7: 3, # 'k' + 6: 2, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 3, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 2, # 'ú' + 29: 3, # 'ü' + 42: 2, # 'ő' + 56: 1, # 'ű' + }, + 51: { # 'Á' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 0, # 'E' + 50: 1, # 'F' + 49: 2, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 2, # 'L' + 34: 1, # 'M' + 35: 2, # 'N' + 47: 0, # 'O' + 46: 1, # 'P' + 43: 2, # 'R' + 33: 2, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 0, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 1, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 44: { # 'É' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 0, # 'F' + 49: 2, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 2, # 'L' + 34: 1, # 'M' + 35: 2, # 'N' + 47: 0, # 'O' + 46: 1, # 'P' + 43: 2, # 'R' + 33: 2, # 'S' + 37: 2, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 0, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 2, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 3, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 0, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 61: { # 'Í' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 0, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 1, # 'J' + 36: 0, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 0, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 2, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 1, # 'm' + 4: 0, # 'n' + 8: 0, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 0, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 58: { # 'Ó' + 28: 1, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 0, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 2, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 0, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 2, # 'h' + 9: 0, # 'i' + 22: 0, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 0, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 59: { # 'Ö' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 0, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 0, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 0, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 0, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 0, # 'o' + 23: 0, # 'p' + 10: 2, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 60: { # 'Ú' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 0, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 2, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 2, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 8: 0, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 63: { # 'Ü' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 0, # 'C' + 45: 1, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 0, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 0, # 'c' + 17: 1, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 1, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 8: 0, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 14: { # 'á' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 1, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 2, # 'h' + 9: 2, # 'i' + 22: 3, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 1, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 2, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 0, # 'ó' + 24: 1, # 'ö' + 31: 0, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 15: { # 'é' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 3, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 2, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 1, # 'o' + 23: 3, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 0, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 30: { # 'í' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 0, # 'e' + 27: 1, # 'f' + 12: 3, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 2, # 's' + 3: 3, # 't' + 21: 0, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 25: { # 'ó' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 3, # 'd' + 1: 1, # 'e' + 27: 2, # 'f' + 12: 2, # 'g' + 20: 2, # 'h' + 9: 2, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 1, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 1, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 0, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 24: { # 'ö' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 0, # 'a' + 18: 3, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 0, # 'e' + 27: 1, # 'f' + 12: 2, # 'g' + 20: 1, # 'h' + 9: 0, # 'i' + 22: 1, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 0, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 0, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 31: { # 'ú' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 1, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 1, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 3, # 'j' + 7: 1, # 'k' + 6: 3, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 2, # 't' + 21: 1, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 29: { # 'ü' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 3, # 'g' + 20: 2, # 'h' + 9: 1, # 'i' + 22: 1, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 1, # 'm' + 4: 3, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 2, # 's' + 3: 2, # 't' + 21: 0, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 42: { # 'ő' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 2, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 1, # 'j' + 7: 2, # 'k' + 6: 3, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 8: 1, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 2, # 's' + 3: 2, # 't' + 21: 1, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 56: { # 'ű' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 1, # 'b' + 26: 0, # 'c' + 17: 1, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 2, # 'n' + 8: 0, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, +} + +# 255: Undefined characters that did not exist in training text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 +# 251: Control characters + +# Character Mapping Table(s): +WINDOWS_1250_HUNGARIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 28, # 'A' + 66: 40, # 'B' + 67: 54, # 'C' + 68: 45, # 'D' + 69: 32, # 'E' + 70: 50, # 'F' + 71: 49, # 'G' + 72: 38, # 'H' + 73: 39, # 'I' + 74: 53, # 'J' + 75: 36, # 'K' + 76: 41, # 'L' + 77: 34, # 'M' + 78: 35, # 'N' + 79: 47, # 'O' + 80: 46, # 'P' + 81: 72, # 'Q' + 82: 43, # 'R' + 83: 33, # 'S' + 84: 37, # 'T' + 85: 57, # 'U' + 86: 48, # 'V' + 87: 64, # 'W' + 88: 68, # 'X' + 89: 55, # 'Y' + 90: 52, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 2, # 'a' + 98: 18, # 'b' + 99: 26, # 'c' + 100: 17, # 'd' + 101: 1, # 'e' + 102: 27, # 'f' + 103: 12, # 'g' + 104: 20, # 'h' + 105: 9, # 'i' + 106: 22, # 'j' + 107: 7, # 'k' + 108: 6, # 'l' + 109: 13, # 'm' + 110: 4, # 'n' + 111: 8, # 'o' + 112: 23, # 'p' + 113: 67, # 'q' + 114: 10, # 'r' + 115: 5, # 's' + 116: 3, # 't' + 117: 21, # 'u' + 118: 19, # 'v' + 119: 65, # 'w' + 120: 62, # 'x' + 121: 16, # 'y' + 122: 11, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 161, # '€' + 129: 162, # None + 130: 163, # '‚' + 131: 164, # None + 132: 165, # '„' + 133: 166, # '…' + 134: 167, # '†' + 135: 168, # '‡' + 136: 169, # None + 137: 170, # '‰' + 138: 171, # 'Š' + 139: 172, # '‹' + 140: 173, # 'Ś' + 141: 174, # 'Ť' + 142: 175, # 'Ž' + 143: 176, # 'Ź' + 144: 177, # None + 145: 178, # '‘' + 146: 179, # '’' + 147: 180, # '“' + 148: 78, # '”' + 149: 181, # '•' + 150: 69, # '–' + 151: 182, # '—' + 152: 183, # None + 153: 184, # '™' + 154: 185, # 'š' + 155: 186, # '›' + 156: 187, # 'ś' + 157: 188, # 'ť' + 158: 189, # 'ž' + 159: 190, # 'ź' + 160: 191, # '\xa0' + 161: 192, # 'ˇ' + 162: 193, # '˘' + 163: 194, # 'Ł' + 164: 195, # '¤' + 165: 196, # 'Ą' + 166: 197, # '¦' + 167: 76, # '§' + 168: 198, # '¨' + 169: 199, # '©' + 170: 200, # 'Ş' + 171: 201, # '«' + 172: 202, # '¬' + 173: 203, # '\xad' + 174: 204, # '®' + 175: 205, # 'Ż' + 176: 81, # '°' + 177: 206, # '±' + 178: 207, # '˛' + 179: 208, # 'ł' + 180: 209, # '´' + 181: 210, # 'µ' + 182: 211, # '¶' + 183: 212, # '·' + 184: 213, # '¸' + 185: 214, # 'ą' + 186: 215, # 'ş' + 187: 216, # '»' + 188: 217, # 'Ľ' + 189: 218, # '˝' + 190: 219, # 'ľ' + 191: 220, # 'ż' + 192: 221, # 'Ŕ' + 193: 51, # 'Á' + 194: 83, # 'Â' + 195: 222, # 'Ă' + 196: 80, # 'Ä' + 197: 223, # 'Ĺ' + 198: 224, # 'Ć' + 199: 225, # 'Ç' + 200: 226, # 'Č' + 201: 44, # 'É' + 202: 227, # 'Ę' + 203: 228, # 'Ë' + 204: 229, # 'Ě' + 205: 61, # 'Í' + 206: 230, # 'Î' + 207: 231, # 'Ď' + 208: 232, # 'Đ' + 209: 233, # 'Ń' + 210: 234, # 'Ň' + 211: 58, # 'Ó' + 212: 235, # 'Ô' + 213: 66, # 'Ő' + 214: 59, # 'Ö' + 215: 236, # '×' + 216: 237, # 'Ř' + 217: 238, # 'Ů' + 218: 60, # 'Ú' + 219: 70, # 'Ű' + 220: 63, # 'Ü' + 221: 239, # 'Ý' + 222: 240, # 'Ţ' + 223: 241, # 'ß' + 224: 84, # 'ŕ' + 225: 14, # 'á' + 226: 75, # 'â' + 227: 242, # 'ă' + 228: 71, # 'ä' + 229: 82, # 'ĺ' + 230: 243, # 'ć' + 231: 73, # 'ç' + 232: 244, # 'č' + 233: 15, # 'é' + 234: 85, # 'ę' + 235: 79, # 'ë' + 236: 86, # 'ě' + 237: 30, # 'í' + 238: 77, # 'î' + 239: 87, # 'ď' + 240: 245, # 'đ' + 241: 246, # 'ń' + 242: 247, # 'ň' + 243: 25, # 'ó' + 244: 74, # 'ô' + 245: 42, # 'ő' + 246: 24, # 'ö' + 247: 248, # '÷' + 248: 249, # 'ř' + 249: 250, # 'ů' + 250: 31, # 'ú' + 251: 56, # 'ű' + 252: 29, # 'ü' + 253: 251, # 'ý' + 254: 252, # 'ţ' + 255: 253, # '˙' +} + +WINDOWS_1250_HUNGARIAN_MODEL = SingleByteCharSetModel(charset_name='windows-1250', + language='Hungarian', + char_to_order_map=WINDOWS_1250_HUNGARIAN_CHAR_TO_ORDER, + language_model=HUNGARIAN_LANG_MODEL, + typical_positive_ratio=0.947368, + keep_ascii_letters=True, + alphabet='ABCDEFGHIJKLMNOPRSTUVZabcdefghijklmnoprstuvzÁÉÍÓÖÚÜáéíóöúüŐőŰű') + +ISO_8859_2_HUNGARIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 28, # 'A' + 66: 40, # 'B' + 67: 54, # 'C' + 68: 45, # 'D' + 69: 32, # 'E' + 70: 50, # 'F' + 71: 49, # 'G' + 72: 38, # 'H' + 73: 39, # 'I' + 74: 53, # 'J' + 75: 36, # 'K' + 76: 41, # 'L' + 77: 34, # 'M' + 78: 35, # 'N' + 79: 47, # 'O' + 80: 46, # 'P' + 81: 71, # 'Q' + 82: 43, # 'R' + 83: 33, # 'S' + 84: 37, # 'T' + 85: 57, # 'U' + 86: 48, # 'V' + 87: 64, # 'W' + 88: 68, # 'X' + 89: 55, # 'Y' + 90: 52, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 2, # 'a' + 98: 18, # 'b' + 99: 26, # 'c' + 100: 17, # 'd' + 101: 1, # 'e' + 102: 27, # 'f' + 103: 12, # 'g' + 104: 20, # 'h' + 105: 9, # 'i' + 106: 22, # 'j' + 107: 7, # 'k' + 108: 6, # 'l' + 109: 13, # 'm' + 110: 4, # 'n' + 111: 8, # 'o' + 112: 23, # 'p' + 113: 67, # 'q' + 114: 10, # 'r' + 115: 5, # 's' + 116: 3, # 't' + 117: 21, # 'u' + 118: 19, # 'v' + 119: 65, # 'w' + 120: 62, # 'x' + 121: 16, # 'y' + 122: 11, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 159, # '\x80' + 129: 160, # '\x81' + 130: 161, # '\x82' + 131: 162, # '\x83' + 132: 163, # '\x84' + 133: 164, # '\x85' + 134: 165, # '\x86' + 135: 166, # '\x87' + 136: 167, # '\x88' + 137: 168, # '\x89' + 138: 169, # '\x8a' + 139: 170, # '\x8b' + 140: 171, # '\x8c' + 141: 172, # '\x8d' + 142: 173, # '\x8e' + 143: 174, # '\x8f' + 144: 175, # '\x90' + 145: 176, # '\x91' + 146: 177, # '\x92' + 147: 178, # '\x93' + 148: 179, # '\x94' + 149: 180, # '\x95' + 150: 181, # '\x96' + 151: 182, # '\x97' + 152: 183, # '\x98' + 153: 184, # '\x99' + 154: 185, # '\x9a' + 155: 186, # '\x9b' + 156: 187, # '\x9c' + 157: 188, # '\x9d' + 158: 189, # '\x9e' + 159: 190, # '\x9f' + 160: 191, # '\xa0' + 161: 192, # 'Ą' + 162: 193, # '˘' + 163: 194, # 'Ł' + 164: 195, # '¤' + 165: 196, # 'Ľ' + 166: 197, # 'Ś' + 167: 75, # '§' + 168: 198, # '¨' + 169: 199, # 'Š' + 170: 200, # 'Ş' + 171: 201, # 'Ť' + 172: 202, # 'Ź' + 173: 203, # '\xad' + 174: 204, # 'Ž' + 175: 205, # 'Ż' + 176: 79, # '°' + 177: 206, # 'ą' + 178: 207, # '˛' + 179: 208, # 'ł' + 180: 209, # '´' + 181: 210, # 'ľ' + 182: 211, # 'ś' + 183: 212, # 'ˇ' + 184: 213, # '¸' + 185: 214, # 'š' + 186: 215, # 'ş' + 187: 216, # 'ť' + 188: 217, # 'ź' + 189: 218, # '˝' + 190: 219, # 'ž' + 191: 220, # 'ż' + 192: 221, # 'Ŕ' + 193: 51, # 'Á' + 194: 81, # 'Â' + 195: 222, # 'Ă' + 196: 78, # 'Ä' + 197: 223, # 'Ĺ' + 198: 224, # 'Ć' + 199: 225, # 'Ç' + 200: 226, # 'Č' + 201: 44, # 'É' + 202: 227, # 'Ę' + 203: 228, # 'Ë' + 204: 229, # 'Ě' + 205: 61, # 'Í' + 206: 230, # 'Î' + 207: 231, # 'Ď' + 208: 232, # 'Đ' + 209: 233, # 'Ń' + 210: 234, # 'Ň' + 211: 58, # 'Ó' + 212: 235, # 'Ô' + 213: 66, # 'Ő' + 214: 59, # 'Ö' + 215: 236, # '×' + 216: 237, # 'Ř' + 217: 238, # 'Ů' + 218: 60, # 'Ú' + 219: 69, # 'Ű' + 220: 63, # 'Ü' + 221: 239, # 'Ý' + 222: 240, # 'Ţ' + 223: 241, # 'ß' + 224: 82, # 'ŕ' + 225: 14, # 'á' + 226: 74, # 'â' + 227: 242, # 'ă' + 228: 70, # 'ä' + 229: 80, # 'ĺ' + 230: 243, # 'ć' + 231: 72, # 'ç' + 232: 244, # 'č' + 233: 15, # 'é' + 234: 83, # 'ę' + 235: 77, # 'ë' + 236: 84, # 'ě' + 237: 30, # 'í' + 238: 76, # 'î' + 239: 85, # 'ď' + 240: 245, # 'đ' + 241: 246, # 'ń' + 242: 247, # 'ň' + 243: 25, # 'ó' + 244: 73, # 'ô' + 245: 42, # 'ő' + 246: 24, # 'ö' + 247: 248, # '÷' + 248: 249, # 'ř' + 249: 250, # 'ů' + 250: 31, # 'ú' + 251: 56, # 'ű' + 252: 29, # 'ü' + 253: 251, # 'ý' + 254: 252, # 'ţ' + 255: 253, # '˙' +} + +ISO_8859_2_HUNGARIAN_MODEL = SingleByteCharSetModel(charset_name='ISO-8859-2', + language='Hungarian', + char_to_order_map=ISO_8859_2_HUNGARIAN_CHAR_TO_ORDER, + language_model=HUNGARIAN_LANG_MODEL, + typical_positive_ratio=0.947368, + keep_ascii_letters=True, + alphabet='ABCDEFGHIJKLMNOPRSTUVZabcdefghijklmnoprstuvzÁÉÍÓÖÚÜáéíóöúüŐőŰű') + diff --git a/openpype/vendor/python/python_2/chardet/langrussianmodel.py b/openpype/vendor/python/python_2/chardet/langrussianmodel.py new file mode 100644 index 0000000000..569689d0f5 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/langrussianmodel.py @@ -0,0 +1,5718 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from chardet.sbcharsetprober import SingleByteCharSetModel + + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +RUSSIAN_LANG_MODEL = { + 37: { # 'А' + 37: 0, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 1, # 'Ж' + 51: 1, # 'З' + 42: 1, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 2, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 1, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 1, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 1, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 0, # 'е' + 24: 1, # 'ж' + 20: 1, # 'з' + 4: 0, # 'и' + 23: 1, # 'й' + 11: 2, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 0, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 2, # 'у' + 39: 2, # 'ф' + 26: 2, # 'х' + 28: 0, # 'ц' + 22: 1, # 'ч' + 25: 2, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 44: { # 'Б' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 1, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 2, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 33: { # 'В' + 37: 2, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 1, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 2, # 'а' + 21: 1, # 'б' + 10: 1, # 'в' + 19: 1, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 2, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 2, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 1, # 'ц' + 22: 2, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 1, # 'ъ' + 18: 3, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 0, # 'ю' + 16: 1, # 'я' + }, + 46: { # 'Г' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 2, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 1, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 1, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 41: { # 'Д' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 2, # 'Е' + 56: 1, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 2, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 3, # 'ж' + 20: 1, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 1, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 48: { # 'Е' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 1, # 'Ж' + 51: 1, # 'З' + 42: 1, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 2, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 2, # 'Р' + 32: 2, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 1, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 0, # 'а' + 21: 0, # 'б' + 10: 2, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 2, # 'е' + 24: 1, # 'ж' + 20: 1, # 'з' + 4: 0, # 'и' + 23: 2, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 1, # 'н' + 1: 0, # 'о' + 15: 1, # 'п' + 9: 1, # 'р' + 7: 3, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 2, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 56: { # 'Ж' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 1, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 1, # 'б' + 10: 0, # 'в' + 19: 1, # 'г' + 13: 1, # 'д' + 2: 2, # 'е' + 24: 1, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 1, # 'м' + 5: 0, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 2, # 'ю' + 16: 0, # 'я' + }, + 51: { # 'З' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 0, # 'г' + 13: 2, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 1, # 'л' + 12: 1, # 'м' + 5: 2, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 1, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 1, # 'я' + }, + 42: { # 'И' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 2, # 'Е' + 56: 1, # 'Ж' + 51: 1, # 'З' + 42: 1, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 2, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 1, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 1, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 2, # 'з' + 4: 1, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 1, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 1, # 'у' + 39: 1, # 'ф' + 26: 2, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 60: { # 'Й' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 1, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 0, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 36: { # 'К' + 37: 2, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 1, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 2, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 1, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 0, # 'м' + 5: 1, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 49: { # 'Л' + 37: 2, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 1, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 1, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 0, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 0, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 1, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 1, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 1, # 'л' + 12: 0, # 'м' + 5: 1, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 2, # 'ю' + 16: 1, # 'я' + }, + 38: { # 'М' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 1, # 'Ф' + 55: 1, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 0, # 'Ь' + 47: 1, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 1, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 1, # 'л' + 12: 1, # 'м' + 5: 2, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 1, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 31: { # 'Н' + 37: 2, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 1, # 'З' + 42: 2, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 1, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 1, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 3, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 2, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 34: { # 'О' + 37: 0, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 2, # 'Д' + 48: 1, # 'Е' + 56: 1, # 'Ж' + 51: 1, # 'З' + 42: 1, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 2, # 'Л' + 38: 1, # 'М' + 31: 2, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 2, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 1, # 'Ф' + 55: 1, # 'Х' + 58: 0, # 'Ц' + 50: 1, # 'Ч' + 57: 1, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 1, # 'а' + 21: 2, # 'б' + 10: 1, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 0, # 'е' + 24: 1, # 'ж' + 20: 1, # 'з' + 4: 0, # 'и' + 23: 1, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 3, # 'н' + 1: 0, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 1, # 'у' + 39: 1, # 'ф' + 26: 2, # 'х' + 28: 1, # 'ц' + 22: 2, # 'ч' + 25: 2, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 35: { # 'П' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 2, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 0, # 'м' + 5: 1, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 3, # 'р' + 7: 1, # 'с' + 6: 1, # 'т' + 14: 2, # 'у' + 39: 1, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 2, # 'ь' + 30: 1, # 'э' + 27: 0, # 'ю' + 16: 2, # 'я' + }, + 45: { # 'Р' + 37: 2, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 2, # 'Е' + 56: 1, # 'Ж' + 51: 0, # 'З' + 42: 2, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 2, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 1, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 1, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 1, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 2, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 2, # 'я' + }, + 32: { # 'С' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 2, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 1, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 1, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 2, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 1, # 'ж' + 20: 1, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 2, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 1, # 'с' + 6: 3, # 'т' + 14: 2, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 1, # 'ц' + 22: 1, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 1, # 'ъ' + 18: 1, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 40: { # 'Т' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 2, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 1, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 1, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 1, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 52: { # 'У' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 1, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 0, # 'Ц' + 50: 1, # 'Ч' + 57: 1, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 1, # 'Ю' + 43: 0, # 'Я' + 3: 1, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 2, # 'д' + 2: 1, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 2, # 'и' + 23: 1, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 1, # 'н' + 1: 2, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 0, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 1, # 'ц' + 22: 2, # 'ч' + 25: 1, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 53: { # 'Ф' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 0, # 'с' + 6: 1, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 55: { # 'Х' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 2, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 0, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 1, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 1, # 'ь' + 30: 1, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 58: { # 'Ц' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 1, # 'а' + 21: 0, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 0, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 1, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 50: { # 'Ч' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 1, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 1, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 1, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 3, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 1, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 57: { # 'Ш' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 1, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 1, # 'н' + 1: 2, # 'о' + 15: 2, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 2, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 63: { # 'Щ' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 1, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 1, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 1, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 1, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 1, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 62: { # 'Ы' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 0, # 'Ч' + 57: 1, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 0, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 0, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 0, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 61: { # 'Ь' + 37: 0, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 0, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 1, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 1, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 1, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 0, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 0, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 0, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 0, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 47: { # 'Э' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 1, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 2, # 'д' + 2: 0, # 'е' + 24: 1, # 'ж' + 20: 0, # 'з' + 4: 0, # 'и' + 23: 2, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 0, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 1, # 'с' + 6: 3, # 'т' + 14: 1, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 59: { # 'Ю' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 0, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 1, # 'б' + 10: 0, # 'в' + 19: 1, # 'г' + 13: 1, # 'д' + 2: 0, # 'е' + 24: 1, # 'ж' + 20: 0, # 'з' + 4: 0, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 2, # 'н' + 1: 0, # 'о' + 15: 1, # 'п' + 9: 1, # 'р' + 7: 1, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 43: { # 'Я' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 0, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 0, # 'а' + 21: 1, # 'б' + 10: 1, # 'в' + 19: 1, # 'г' + 13: 1, # 'д' + 2: 0, # 'е' + 24: 0, # 'ж' + 20: 1, # 'з' + 4: 0, # 'и' + 23: 1, # 'й' + 11: 1, # 'к' + 8: 1, # 'л' + 12: 1, # 'м' + 5: 2, # 'н' + 1: 0, # 'о' + 15: 1, # 'п' + 9: 1, # 'р' + 7: 1, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 3: { # 'а' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 3, # 'з' + 4: 3, # 'и' + 23: 3, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 2, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 3, # 'ц' + 22: 3, # 'ч' + 25: 3, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 2, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 21: { # 'б' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 1, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 0, # 'ф' + 26: 2, # 'х' + 28: 1, # 'ц' + 22: 1, # 'ч' + 25: 2, # 'ш' + 29: 3, # 'щ' + 54: 2, # 'ъ' + 18: 3, # 'ы' + 17: 2, # 'ь' + 30: 1, # 'э' + 27: 2, # 'ю' + 16: 3, # 'я' + }, + 10: { # 'в' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 2, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 1, # 'ж' + 20: 3, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 2, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 3, # 'ш' + 29: 2, # 'щ' + 54: 2, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 3, # 'я' + }, + 19: { # 'г' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 3, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 1, # 'ц' + 22: 2, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 1, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 13: { # 'д' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 3, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 2, # 'х' + 28: 3, # 'ц' + 22: 2, # 'ч' + 25: 2, # 'ш' + 29: 1, # 'щ' + 54: 2, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 1, # 'э' + 27: 2, # 'ю' + 16: 3, # 'я' + }, + 2: { # 'е' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 3, # 'з' + 4: 2, # 'и' + 23: 3, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 2, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 3, # 'ц' + 22: 3, # 'ч' + 25: 3, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 2, # 'ю' + 16: 3, # 'я' + }, + 24: { # 'ж' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 1, # 'в' + 19: 2, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 3, # 'н' + 1: 2, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 1, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 0, # 'х' + 28: 1, # 'ц' + 22: 2, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 2, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 20: { # 'з' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 3, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 1, # 'ц' + 22: 2, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 2, # 'ъ' + 18: 3, # 'ы' + 17: 2, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 3, # 'я' + }, + 4: { # 'и' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 3, # 'з' + 4: 3, # 'и' + 23: 3, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 2, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 3, # 'ц' + 22: 3, # 'ч' + 25: 3, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 2, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 23: { # 'й' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 1, # 'а' + 21: 1, # 'б' + 10: 1, # 'в' + 19: 2, # 'г' + 13: 3, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 2, # 'з' + 4: 1, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 2, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 1, # 'у' + 39: 2, # 'ф' + 26: 1, # 'х' + 28: 2, # 'ц' + 22: 3, # 'ч' + 25: 2, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 2, # 'я' + }, + 11: { # 'к' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 3, # 'в' + 19: 1, # 'г' + 13: 1, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 3, # 'л' + 12: 1, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 2, # 'х' + 28: 2, # 'ц' + 22: 1, # 'ч' + 25: 2, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 1, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 8: { # 'л' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 3, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 1, # 'р' + 7: 3, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 2, # 'х' + 28: 1, # 'ц' + 22: 3, # 'ч' + 25: 2, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 1, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 12: { # 'м' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 2, # 'г' + 13: 1, # 'д' + 2: 3, # 'е' + 24: 1, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 2, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 1, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 2, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 3, # 'я' + }, + 5: { # 'н' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 2, # 'х' + 28: 3, # 'ц' + 22: 3, # 'ч' + 25: 2, # 'ш' + 29: 2, # 'щ' + 54: 1, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 1, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 1: { # 'о' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 3, # 'з' + 4: 3, # 'и' + 23: 3, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 2, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 2, # 'ц' + 22: 3, # 'ч' + 25: 3, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 2, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 15: { # 'п' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 3, # 'л' + 12: 1, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 3, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 0, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 1, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 2, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 3, # 'я' + }, + 9: { # 'р' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 2, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 3, # 'ш' + 29: 2, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 2, # 'э' + 27: 2, # 'ю' + 16: 3, # 'я' + }, + 7: { # 'с' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 1, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 3, # 'в' + 19: 2, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 2, # 'ц' + 22: 3, # 'ч' + 25: 2, # 'ш' + 29: 1, # 'щ' + 54: 2, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 2, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 6: { # 'т' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 3, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 1, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 2, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 2, # 'ш' + 29: 2, # 'щ' + 54: 2, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 2, # 'э' + 27: 2, # 'ю' + 16: 3, # 'я' + }, + 14: { # 'у' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 3, # 'з' + 4: 2, # 'и' + 23: 2, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 2, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 1, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 2, # 'ц' + 22: 3, # 'ч' + 25: 3, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 2, # 'э' + 27: 3, # 'ю' + 16: 2, # 'я' + }, + 39: { # 'ф' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 0, # 'в' + 19: 1, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 1, # 'н' + 1: 3, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 2, # 'у' + 39: 2, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 1, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 2, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 26: { # 'х' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 3, # 'в' + 19: 1, # 'г' + 13: 1, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 1, # 'п' + 9: 3, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 2, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 1, # 'ц' + 22: 1, # 'ч' + 25: 2, # 'ш' + 29: 0, # 'щ' + 54: 1, # 'ъ' + 18: 0, # 'ы' + 17: 1, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 28: { # 'ц' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 1, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 1, # 'л' + 12: 1, # 'м' + 5: 1, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 1, # 'т' + 14: 3, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 1, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 1, # 'ь' + 30: 0, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 22: { # 'ч' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 1, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 3, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 1, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 1, # 'ч' + 25: 2, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 3, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 25: { # 'ш' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 1, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 1, # 'х' + 28: 1, # 'ц' + 22: 1, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 3, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 29: { # 'щ' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 1, # 'м' + 5: 2, # 'н' + 1: 1, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 2, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 54: { # 'ъ' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 0, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 0, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 1, # 'ю' + 16: 2, # 'я' + }, + 18: { # 'ы' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 2, # 'и' + 23: 3, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 1, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 1, # 'у' + 39: 0, # 'ф' + 26: 3, # 'х' + 28: 2, # 'ц' + 22: 3, # 'ч' + 25: 3, # 'ш' + 29: 2, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 2, # 'я' + }, + 17: { # 'ь' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 1, # 'ж' + 20: 3, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 0, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 2, # 'о' + 15: 2, # 'п' + 9: 1, # 'р' + 7: 3, # 'с' + 6: 2, # 'т' + 14: 0, # 'у' + 39: 2, # 'ф' + 26: 1, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 3, # 'ш' + 29: 2, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 30: { # 'э' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 1, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 1, # 'б' + 10: 1, # 'в' + 19: 1, # 'г' + 13: 2, # 'д' + 2: 1, # 'е' + 24: 0, # 'ж' + 20: 1, # 'з' + 4: 0, # 'и' + 23: 2, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 0, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 3, # 'т' + 14: 1, # 'у' + 39: 2, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 27: { # 'ю' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 3, # 'б' + 10: 1, # 'в' + 19: 2, # 'г' + 13: 3, # 'д' + 2: 1, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 1, # 'и' + 23: 1, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 1, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 0, # 'у' + 39: 1, # 'ф' + 26: 2, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 2, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 2, # 'ю' + 16: 1, # 'я' + }, + 16: { # 'я' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 2, # 'б' + 10: 3, # 'в' + 19: 2, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 3, # 'з' + 4: 2, # 'и' + 23: 2, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 0, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 1, # 'у' + 39: 1, # 'ф' + 26: 3, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 2, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 2, # 'ю' + 16: 2, # 'я' + }, +} + +# 255: Undefined characters that did not exist in training text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 +# 251: Control characters + +# Character Mapping Table(s): +IBM866_RUSSIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 142, # 'A' + 66: 143, # 'B' + 67: 144, # 'C' + 68: 145, # 'D' + 69: 146, # 'E' + 70: 147, # 'F' + 71: 148, # 'G' + 72: 149, # 'H' + 73: 150, # 'I' + 74: 151, # 'J' + 75: 152, # 'K' + 76: 74, # 'L' + 77: 153, # 'M' + 78: 75, # 'N' + 79: 154, # 'O' + 80: 155, # 'P' + 81: 156, # 'Q' + 82: 157, # 'R' + 83: 158, # 'S' + 84: 159, # 'T' + 85: 160, # 'U' + 86: 161, # 'V' + 87: 162, # 'W' + 88: 163, # 'X' + 89: 164, # 'Y' + 90: 165, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 71, # 'a' + 98: 172, # 'b' + 99: 66, # 'c' + 100: 173, # 'd' + 101: 65, # 'e' + 102: 174, # 'f' + 103: 76, # 'g' + 104: 175, # 'h' + 105: 64, # 'i' + 106: 176, # 'j' + 107: 177, # 'k' + 108: 77, # 'l' + 109: 72, # 'm' + 110: 178, # 'n' + 111: 69, # 'o' + 112: 67, # 'p' + 113: 179, # 'q' + 114: 78, # 'r' + 115: 73, # 's' + 116: 180, # 't' + 117: 181, # 'u' + 118: 79, # 'v' + 119: 182, # 'w' + 120: 183, # 'x' + 121: 184, # 'y' + 122: 185, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 37, # 'А' + 129: 44, # 'Б' + 130: 33, # 'В' + 131: 46, # 'Г' + 132: 41, # 'Д' + 133: 48, # 'Е' + 134: 56, # 'Ж' + 135: 51, # 'З' + 136: 42, # 'И' + 137: 60, # 'Й' + 138: 36, # 'К' + 139: 49, # 'Л' + 140: 38, # 'М' + 141: 31, # 'Н' + 142: 34, # 'О' + 143: 35, # 'П' + 144: 45, # 'Р' + 145: 32, # 'С' + 146: 40, # 'Т' + 147: 52, # 'У' + 148: 53, # 'Ф' + 149: 55, # 'Х' + 150: 58, # 'Ц' + 151: 50, # 'Ч' + 152: 57, # 'Ш' + 153: 63, # 'Щ' + 154: 70, # 'Ъ' + 155: 62, # 'Ы' + 156: 61, # 'Ь' + 157: 47, # 'Э' + 158: 59, # 'Ю' + 159: 43, # 'Я' + 160: 3, # 'а' + 161: 21, # 'б' + 162: 10, # 'в' + 163: 19, # 'г' + 164: 13, # 'д' + 165: 2, # 'е' + 166: 24, # 'ж' + 167: 20, # 'з' + 168: 4, # 'и' + 169: 23, # 'й' + 170: 11, # 'к' + 171: 8, # 'л' + 172: 12, # 'м' + 173: 5, # 'н' + 174: 1, # 'о' + 175: 15, # 'п' + 176: 191, # '░' + 177: 192, # '▒' + 178: 193, # '▓' + 179: 194, # '│' + 180: 195, # '┤' + 181: 196, # '╡' + 182: 197, # '╢' + 183: 198, # '╖' + 184: 199, # '╕' + 185: 200, # '╣' + 186: 201, # '║' + 187: 202, # '╗' + 188: 203, # '╝' + 189: 204, # '╜' + 190: 205, # '╛' + 191: 206, # '┐' + 192: 207, # '└' + 193: 208, # '┴' + 194: 209, # '┬' + 195: 210, # '├' + 196: 211, # '─' + 197: 212, # '┼' + 198: 213, # '╞' + 199: 214, # '╟' + 200: 215, # '╚' + 201: 216, # '╔' + 202: 217, # '╩' + 203: 218, # '╦' + 204: 219, # '╠' + 205: 220, # '═' + 206: 221, # '╬' + 207: 222, # '╧' + 208: 223, # '╨' + 209: 224, # '╤' + 210: 225, # '╥' + 211: 226, # '╙' + 212: 227, # '╘' + 213: 228, # '╒' + 214: 229, # '╓' + 215: 230, # '╫' + 216: 231, # '╪' + 217: 232, # '┘' + 218: 233, # '┌' + 219: 234, # '█' + 220: 235, # '▄' + 221: 236, # '▌' + 222: 237, # '▐' + 223: 238, # '▀' + 224: 9, # 'р' + 225: 7, # 'с' + 226: 6, # 'т' + 227: 14, # 'у' + 228: 39, # 'ф' + 229: 26, # 'х' + 230: 28, # 'ц' + 231: 22, # 'ч' + 232: 25, # 'ш' + 233: 29, # 'щ' + 234: 54, # 'ъ' + 235: 18, # 'ы' + 236: 17, # 'ь' + 237: 30, # 'э' + 238: 27, # 'ю' + 239: 16, # 'я' + 240: 239, # 'Ё' + 241: 68, # 'ё' + 242: 240, # 'Є' + 243: 241, # 'є' + 244: 242, # 'Ї' + 245: 243, # 'ї' + 246: 244, # 'Ў' + 247: 245, # 'ў' + 248: 246, # '°' + 249: 247, # '∙' + 250: 248, # '·' + 251: 249, # '√' + 252: 250, # '№' + 253: 251, # '¤' + 254: 252, # '■' + 255: 255, # '\xa0' +} + +IBM866_RUSSIAN_MODEL = SingleByteCharSetModel(charset_name='IBM866', + language='Russian', + char_to_order_map=IBM866_RUSSIAN_CHAR_TO_ORDER, + language_model=RUSSIAN_LANG_MODEL, + typical_positive_ratio=0.976601, + keep_ascii_letters=False, + alphabet='ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё') + +WINDOWS_1251_RUSSIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 142, # 'A' + 66: 143, # 'B' + 67: 144, # 'C' + 68: 145, # 'D' + 69: 146, # 'E' + 70: 147, # 'F' + 71: 148, # 'G' + 72: 149, # 'H' + 73: 150, # 'I' + 74: 151, # 'J' + 75: 152, # 'K' + 76: 74, # 'L' + 77: 153, # 'M' + 78: 75, # 'N' + 79: 154, # 'O' + 80: 155, # 'P' + 81: 156, # 'Q' + 82: 157, # 'R' + 83: 158, # 'S' + 84: 159, # 'T' + 85: 160, # 'U' + 86: 161, # 'V' + 87: 162, # 'W' + 88: 163, # 'X' + 89: 164, # 'Y' + 90: 165, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 71, # 'a' + 98: 172, # 'b' + 99: 66, # 'c' + 100: 173, # 'd' + 101: 65, # 'e' + 102: 174, # 'f' + 103: 76, # 'g' + 104: 175, # 'h' + 105: 64, # 'i' + 106: 176, # 'j' + 107: 177, # 'k' + 108: 77, # 'l' + 109: 72, # 'm' + 110: 178, # 'n' + 111: 69, # 'o' + 112: 67, # 'p' + 113: 179, # 'q' + 114: 78, # 'r' + 115: 73, # 's' + 116: 180, # 't' + 117: 181, # 'u' + 118: 79, # 'v' + 119: 182, # 'w' + 120: 183, # 'x' + 121: 184, # 'y' + 122: 185, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 191, # 'Ђ' + 129: 192, # 'Ѓ' + 130: 193, # '‚' + 131: 194, # 'ѓ' + 132: 195, # '„' + 133: 196, # '…' + 134: 197, # '†' + 135: 198, # '‡' + 136: 199, # '€' + 137: 200, # '‰' + 138: 201, # 'Љ' + 139: 202, # '‹' + 140: 203, # 'Њ' + 141: 204, # 'Ќ' + 142: 205, # 'Ћ' + 143: 206, # 'Џ' + 144: 207, # 'ђ' + 145: 208, # '‘' + 146: 209, # '’' + 147: 210, # '“' + 148: 211, # '”' + 149: 212, # '•' + 150: 213, # '–' + 151: 214, # '—' + 152: 215, # None + 153: 216, # '™' + 154: 217, # 'љ' + 155: 218, # '›' + 156: 219, # 'њ' + 157: 220, # 'ќ' + 158: 221, # 'ћ' + 159: 222, # 'џ' + 160: 223, # '\xa0' + 161: 224, # 'Ў' + 162: 225, # 'ў' + 163: 226, # 'Ј' + 164: 227, # '¤' + 165: 228, # 'Ґ' + 166: 229, # '¦' + 167: 230, # '§' + 168: 231, # 'Ё' + 169: 232, # '©' + 170: 233, # 'Є' + 171: 234, # '«' + 172: 235, # '¬' + 173: 236, # '\xad' + 174: 237, # '®' + 175: 238, # 'Ї' + 176: 239, # '°' + 177: 240, # '±' + 178: 241, # 'І' + 179: 242, # 'і' + 180: 243, # 'ґ' + 181: 244, # 'µ' + 182: 245, # '¶' + 183: 246, # '·' + 184: 68, # 'ё' + 185: 247, # '№' + 186: 248, # 'є' + 187: 249, # '»' + 188: 250, # 'ј' + 189: 251, # 'Ѕ' + 190: 252, # 'ѕ' + 191: 253, # 'ї' + 192: 37, # 'А' + 193: 44, # 'Б' + 194: 33, # 'В' + 195: 46, # 'Г' + 196: 41, # 'Д' + 197: 48, # 'Е' + 198: 56, # 'Ж' + 199: 51, # 'З' + 200: 42, # 'И' + 201: 60, # 'Й' + 202: 36, # 'К' + 203: 49, # 'Л' + 204: 38, # 'М' + 205: 31, # 'Н' + 206: 34, # 'О' + 207: 35, # 'П' + 208: 45, # 'Р' + 209: 32, # 'С' + 210: 40, # 'Т' + 211: 52, # 'У' + 212: 53, # 'Ф' + 213: 55, # 'Х' + 214: 58, # 'Ц' + 215: 50, # 'Ч' + 216: 57, # 'Ш' + 217: 63, # 'Щ' + 218: 70, # 'Ъ' + 219: 62, # 'Ы' + 220: 61, # 'Ь' + 221: 47, # 'Э' + 222: 59, # 'Ю' + 223: 43, # 'Я' + 224: 3, # 'а' + 225: 21, # 'б' + 226: 10, # 'в' + 227: 19, # 'г' + 228: 13, # 'д' + 229: 2, # 'е' + 230: 24, # 'ж' + 231: 20, # 'з' + 232: 4, # 'и' + 233: 23, # 'й' + 234: 11, # 'к' + 235: 8, # 'л' + 236: 12, # 'м' + 237: 5, # 'н' + 238: 1, # 'о' + 239: 15, # 'п' + 240: 9, # 'р' + 241: 7, # 'с' + 242: 6, # 'т' + 243: 14, # 'у' + 244: 39, # 'ф' + 245: 26, # 'х' + 246: 28, # 'ц' + 247: 22, # 'ч' + 248: 25, # 'ш' + 249: 29, # 'щ' + 250: 54, # 'ъ' + 251: 18, # 'ы' + 252: 17, # 'ь' + 253: 30, # 'э' + 254: 27, # 'ю' + 255: 16, # 'я' +} + +WINDOWS_1251_RUSSIAN_MODEL = SingleByteCharSetModel(charset_name='windows-1251', + language='Russian', + char_to_order_map=WINDOWS_1251_RUSSIAN_CHAR_TO_ORDER, + language_model=RUSSIAN_LANG_MODEL, + typical_positive_ratio=0.976601, + keep_ascii_letters=False, + alphabet='ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё') + +IBM855_RUSSIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 142, # 'A' + 66: 143, # 'B' + 67: 144, # 'C' + 68: 145, # 'D' + 69: 146, # 'E' + 70: 147, # 'F' + 71: 148, # 'G' + 72: 149, # 'H' + 73: 150, # 'I' + 74: 151, # 'J' + 75: 152, # 'K' + 76: 74, # 'L' + 77: 153, # 'M' + 78: 75, # 'N' + 79: 154, # 'O' + 80: 155, # 'P' + 81: 156, # 'Q' + 82: 157, # 'R' + 83: 158, # 'S' + 84: 159, # 'T' + 85: 160, # 'U' + 86: 161, # 'V' + 87: 162, # 'W' + 88: 163, # 'X' + 89: 164, # 'Y' + 90: 165, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 71, # 'a' + 98: 172, # 'b' + 99: 66, # 'c' + 100: 173, # 'd' + 101: 65, # 'e' + 102: 174, # 'f' + 103: 76, # 'g' + 104: 175, # 'h' + 105: 64, # 'i' + 106: 176, # 'j' + 107: 177, # 'k' + 108: 77, # 'l' + 109: 72, # 'm' + 110: 178, # 'n' + 111: 69, # 'o' + 112: 67, # 'p' + 113: 179, # 'q' + 114: 78, # 'r' + 115: 73, # 's' + 116: 180, # 't' + 117: 181, # 'u' + 118: 79, # 'v' + 119: 182, # 'w' + 120: 183, # 'x' + 121: 184, # 'y' + 122: 185, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 191, # 'ђ' + 129: 192, # 'Ђ' + 130: 193, # 'ѓ' + 131: 194, # 'Ѓ' + 132: 68, # 'ё' + 133: 195, # 'Ё' + 134: 196, # 'є' + 135: 197, # 'Є' + 136: 198, # 'ѕ' + 137: 199, # 'Ѕ' + 138: 200, # 'і' + 139: 201, # 'І' + 140: 202, # 'ї' + 141: 203, # 'Ї' + 142: 204, # 'ј' + 143: 205, # 'Ј' + 144: 206, # 'љ' + 145: 207, # 'Љ' + 146: 208, # 'њ' + 147: 209, # 'Њ' + 148: 210, # 'ћ' + 149: 211, # 'Ћ' + 150: 212, # 'ќ' + 151: 213, # 'Ќ' + 152: 214, # 'ў' + 153: 215, # 'Ў' + 154: 216, # 'џ' + 155: 217, # 'Џ' + 156: 27, # 'ю' + 157: 59, # 'Ю' + 158: 54, # 'ъ' + 159: 70, # 'Ъ' + 160: 3, # 'а' + 161: 37, # 'А' + 162: 21, # 'б' + 163: 44, # 'Б' + 164: 28, # 'ц' + 165: 58, # 'Ц' + 166: 13, # 'д' + 167: 41, # 'Д' + 168: 2, # 'е' + 169: 48, # 'Е' + 170: 39, # 'ф' + 171: 53, # 'Ф' + 172: 19, # 'г' + 173: 46, # 'Г' + 174: 218, # '«' + 175: 219, # '»' + 176: 220, # '░' + 177: 221, # '▒' + 178: 222, # '▓' + 179: 223, # '│' + 180: 224, # '┤' + 181: 26, # 'х' + 182: 55, # 'Х' + 183: 4, # 'и' + 184: 42, # 'И' + 185: 225, # '╣' + 186: 226, # '║' + 187: 227, # '╗' + 188: 228, # '╝' + 189: 23, # 'й' + 190: 60, # 'Й' + 191: 229, # '┐' + 192: 230, # '└' + 193: 231, # '┴' + 194: 232, # '┬' + 195: 233, # '├' + 196: 234, # '─' + 197: 235, # '┼' + 198: 11, # 'к' + 199: 36, # 'К' + 200: 236, # '╚' + 201: 237, # '╔' + 202: 238, # '╩' + 203: 239, # '╦' + 204: 240, # '╠' + 205: 241, # '═' + 206: 242, # '╬' + 207: 243, # '¤' + 208: 8, # 'л' + 209: 49, # 'Л' + 210: 12, # 'м' + 211: 38, # 'М' + 212: 5, # 'н' + 213: 31, # 'Н' + 214: 1, # 'о' + 215: 34, # 'О' + 216: 15, # 'п' + 217: 244, # '┘' + 218: 245, # '┌' + 219: 246, # '█' + 220: 247, # '▄' + 221: 35, # 'П' + 222: 16, # 'я' + 223: 248, # '▀' + 224: 43, # 'Я' + 225: 9, # 'р' + 226: 45, # 'Р' + 227: 7, # 'с' + 228: 32, # 'С' + 229: 6, # 'т' + 230: 40, # 'Т' + 231: 14, # 'у' + 232: 52, # 'У' + 233: 24, # 'ж' + 234: 56, # 'Ж' + 235: 10, # 'в' + 236: 33, # 'В' + 237: 17, # 'ь' + 238: 61, # 'Ь' + 239: 249, # '№' + 240: 250, # '\xad' + 241: 18, # 'ы' + 242: 62, # 'Ы' + 243: 20, # 'з' + 244: 51, # 'З' + 245: 25, # 'ш' + 246: 57, # 'Ш' + 247: 30, # 'э' + 248: 47, # 'Э' + 249: 29, # 'щ' + 250: 63, # 'Щ' + 251: 22, # 'ч' + 252: 50, # 'Ч' + 253: 251, # '§' + 254: 252, # '■' + 255: 255, # '\xa0' +} + +IBM855_RUSSIAN_MODEL = SingleByteCharSetModel(charset_name='IBM855', + language='Russian', + char_to_order_map=IBM855_RUSSIAN_CHAR_TO_ORDER, + language_model=RUSSIAN_LANG_MODEL, + typical_positive_ratio=0.976601, + keep_ascii_letters=False, + alphabet='ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё') + +KOI8_R_RUSSIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 142, # 'A' + 66: 143, # 'B' + 67: 144, # 'C' + 68: 145, # 'D' + 69: 146, # 'E' + 70: 147, # 'F' + 71: 148, # 'G' + 72: 149, # 'H' + 73: 150, # 'I' + 74: 151, # 'J' + 75: 152, # 'K' + 76: 74, # 'L' + 77: 153, # 'M' + 78: 75, # 'N' + 79: 154, # 'O' + 80: 155, # 'P' + 81: 156, # 'Q' + 82: 157, # 'R' + 83: 158, # 'S' + 84: 159, # 'T' + 85: 160, # 'U' + 86: 161, # 'V' + 87: 162, # 'W' + 88: 163, # 'X' + 89: 164, # 'Y' + 90: 165, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 71, # 'a' + 98: 172, # 'b' + 99: 66, # 'c' + 100: 173, # 'd' + 101: 65, # 'e' + 102: 174, # 'f' + 103: 76, # 'g' + 104: 175, # 'h' + 105: 64, # 'i' + 106: 176, # 'j' + 107: 177, # 'k' + 108: 77, # 'l' + 109: 72, # 'm' + 110: 178, # 'n' + 111: 69, # 'o' + 112: 67, # 'p' + 113: 179, # 'q' + 114: 78, # 'r' + 115: 73, # 's' + 116: 180, # 't' + 117: 181, # 'u' + 118: 79, # 'v' + 119: 182, # 'w' + 120: 183, # 'x' + 121: 184, # 'y' + 122: 185, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 191, # '─' + 129: 192, # '│' + 130: 193, # '┌' + 131: 194, # '┐' + 132: 195, # '└' + 133: 196, # '┘' + 134: 197, # '├' + 135: 198, # '┤' + 136: 199, # '┬' + 137: 200, # '┴' + 138: 201, # '┼' + 139: 202, # '▀' + 140: 203, # '▄' + 141: 204, # '█' + 142: 205, # '▌' + 143: 206, # '▐' + 144: 207, # '░' + 145: 208, # '▒' + 146: 209, # '▓' + 147: 210, # '⌠' + 148: 211, # '■' + 149: 212, # '∙' + 150: 213, # '√' + 151: 214, # '≈' + 152: 215, # '≤' + 153: 216, # '≥' + 154: 217, # '\xa0' + 155: 218, # '⌡' + 156: 219, # '°' + 157: 220, # '²' + 158: 221, # '·' + 159: 222, # '÷' + 160: 223, # '═' + 161: 224, # '║' + 162: 225, # '╒' + 163: 68, # 'ё' + 164: 226, # '╓' + 165: 227, # '╔' + 166: 228, # '╕' + 167: 229, # '╖' + 168: 230, # '╗' + 169: 231, # '╘' + 170: 232, # '╙' + 171: 233, # '╚' + 172: 234, # '╛' + 173: 235, # '╜' + 174: 236, # '╝' + 175: 237, # '╞' + 176: 238, # '╟' + 177: 239, # '╠' + 178: 240, # '╡' + 179: 241, # 'Ё' + 180: 242, # '╢' + 181: 243, # '╣' + 182: 244, # '╤' + 183: 245, # '╥' + 184: 246, # '╦' + 185: 247, # '╧' + 186: 248, # '╨' + 187: 249, # '╩' + 188: 250, # '╪' + 189: 251, # '╫' + 190: 252, # '╬' + 191: 253, # '©' + 192: 27, # 'ю' + 193: 3, # 'а' + 194: 21, # 'б' + 195: 28, # 'ц' + 196: 13, # 'д' + 197: 2, # 'е' + 198: 39, # 'ф' + 199: 19, # 'г' + 200: 26, # 'х' + 201: 4, # 'и' + 202: 23, # 'й' + 203: 11, # 'к' + 204: 8, # 'л' + 205: 12, # 'м' + 206: 5, # 'н' + 207: 1, # 'о' + 208: 15, # 'п' + 209: 16, # 'я' + 210: 9, # 'р' + 211: 7, # 'с' + 212: 6, # 'т' + 213: 14, # 'у' + 214: 24, # 'ж' + 215: 10, # 'в' + 216: 17, # 'ь' + 217: 18, # 'ы' + 218: 20, # 'з' + 219: 25, # 'ш' + 220: 30, # 'э' + 221: 29, # 'щ' + 222: 22, # 'ч' + 223: 54, # 'ъ' + 224: 59, # 'Ю' + 225: 37, # 'А' + 226: 44, # 'Б' + 227: 58, # 'Ц' + 228: 41, # 'Д' + 229: 48, # 'Е' + 230: 53, # 'Ф' + 231: 46, # 'Г' + 232: 55, # 'Х' + 233: 42, # 'И' + 234: 60, # 'Й' + 235: 36, # 'К' + 236: 49, # 'Л' + 237: 38, # 'М' + 238: 31, # 'Н' + 239: 34, # 'О' + 240: 35, # 'П' + 241: 43, # 'Я' + 242: 45, # 'Р' + 243: 32, # 'С' + 244: 40, # 'Т' + 245: 52, # 'У' + 246: 56, # 'Ж' + 247: 33, # 'В' + 248: 61, # 'Ь' + 249: 62, # 'Ы' + 250: 51, # 'З' + 251: 57, # 'Ш' + 252: 47, # 'Э' + 253: 63, # 'Щ' + 254: 50, # 'Ч' + 255: 70, # 'Ъ' +} + +KOI8_R_RUSSIAN_MODEL = SingleByteCharSetModel(charset_name='KOI8-R', + language='Russian', + char_to_order_map=KOI8_R_RUSSIAN_CHAR_TO_ORDER, + language_model=RUSSIAN_LANG_MODEL, + typical_positive_ratio=0.976601, + keep_ascii_letters=False, + alphabet='ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё') + +MACCYRILLIC_RUSSIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 142, # 'A' + 66: 143, # 'B' + 67: 144, # 'C' + 68: 145, # 'D' + 69: 146, # 'E' + 70: 147, # 'F' + 71: 148, # 'G' + 72: 149, # 'H' + 73: 150, # 'I' + 74: 151, # 'J' + 75: 152, # 'K' + 76: 74, # 'L' + 77: 153, # 'M' + 78: 75, # 'N' + 79: 154, # 'O' + 80: 155, # 'P' + 81: 156, # 'Q' + 82: 157, # 'R' + 83: 158, # 'S' + 84: 159, # 'T' + 85: 160, # 'U' + 86: 161, # 'V' + 87: 162, # 'W' + 88: 163, # 'X' + 89: 164, # 'Y' + 90: 165, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 71, # 'a' + 98: 172, # 'b' + 99: 66, # 'c' + 100: 173, # 'd' + 101: 65, # 'e' + 102: 174, # 'f' + 103: 76, # 'g' + 104: 175, # 'h' + 105: 64, # 'i' + 106: 176, # 'j' + 107: 177, # 'k' + 108: 77, # 'l' + 109: 72, # 'm' + 110: 178, # 'n' + 111: 69, # 'o' + 112: 67, # 'p' + 113: 179, # 'q' + 114: 78, # 'r' + 115: 73, # 's' + 116: 180, # 't' + 117: 181, # 'u' + 118: 79, # 'v' + 119: 182, # 'w' + 120: 183, # 'x' + 121: 184, # 'y' + 122: 185, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 37, # 'А' + 129: 44, # 'Б' + 130: 33, # 'В' + 131: 46, # 'Г' + 132: 41, # 'Д' + 133: 48, # 'Е' + 134: 56, # 'Ж' + 135: 51, # 'З' + 136: 42, # 'И' + 137: 60, # 'Й' + 138: 36, # 'К' + 139: 49, # 'Л' + 140: 38, # 'М' + 141: 31, # 'Н' + 142: 34, # 'О' + 143: 35, # 'П' + 144: 45, # 'Р' + 145: 32, # 'С' + 146: 40, # 'Т' + 147: 52, # 'У' + 148: 53, # 'Ф' + 149: 55, # 'Х' + 150: 58, # 'Ц' + 151: 50, # 'Ч' + 152: 57, # 'Ш' + 153: 63, # 'Щ' + 154: 70, # 'Ъ' + 155: 62, # 'Ы' + 156: 61, # 'Ь' + 157: 47, # 'Э' + 158: 59, # 'Ю' + 159: 43, # 'Я' + 160: 191, # '†' + 161: 192, # '°' + 162: 193, # 'Ґ' + 163: 194, # '£' + 164: 195, # '§' + 165: 196, # '•' + 166: 197, # '¶' + 167: 198, # 'І' + 168: 199, # '®' + 169: 200, # '©' + 170: 201, # '™' + 171: 202, # 'Ђ' + 172: 203, # 'ђ' + 173: 204, # '≠' + 174: 205, # 'Ѓ' + 175: 206, # 'ѓ' + 176: 207, # '∞' + 177: 208, # '±' + 178: 209, # '≤' + 179: 210, # '≥' + 180: 211, # 'і' + 181: 212, # 'µ' + 182: 213, # 'ґ' + 183: 214, # 'Ј' + 184: 215, # 'Є' + 185: 216, # 'є' + 186: 217, # 'Ї' + 187: 218, # 'ї' + 188: 219, # 'Љ' + 189: 220, # 'љ' + 190: 221, # 'Њ' + 191: 222, # 'њ' + 192: 223, # 'ј' + 193: 224, # 'Ѕ' + 194: 225, # '¬' + 195: 226, # '√' + 196: 227, # 'ƒ' + 197: 228, # '≈' + 198: 229, # '∆' + 199: 230, # '«' + 200: 231, # '»' + 201: 232, # '…' + 202: 233, # '\xa0' + 203: 234, # 'Ћ' + 204: 235, # 'ћ' + 205: 236, # 'Ќ' + 206: 237, # 'ќ' + 207: 238, # 'ѕ' + 208: 239, # '–' + 209: 240, # '—' + 210: 241, # '“' + 211: 242, # '”' + 212: 243, # '‘' + 213: 244, # '’' + 214: 245, # '÷' + 215: 246, # '„' + 216: 247, # 'Ў' + 217: 248, # 'ў' + 218: 249, # 'Џ' + 219: 250, # 'џ' + 220: 251, # '№' + 221: 252, # 'Ё' + 222: 68, # 'ё' + 223: 16, # 'я' + 224: 3, # 'а' + 225: 21, # 'б' + 226: 10, # 'в' + 227: 19, # 'г' + 228: 13, # 'д' + 229: 2, # 'е' + 230: 24, # 'ж' + 231: 20, # 'з' + 232: 4, # 'и' + 233: 23, # 'й' + 234: 11, # 'к' + 235: 8, # 'л' + 236: 12, # 'м' + 237: 5, # 'н' + 238: 1, # 'о' + 239: 15, # 'п' + 240: 9, # 'р' + 241: 7, # 'с' + 242: 6, # 'т' + 243: 14, # 'у' + 244: 39, # 'ф' + 245: 26, # 'х' + 246: 28, # 'ц' + 247: 22, # 'ч' + 248: 25, # 'ш' + 249: 29, # 'щ' + 250: 54, # 'ъ' + 251: 18, # 'ы' + 252: 17, # 'ь' + 253: 30, # 'э' + 254: 27, # 'ю' + 255: 255, # '€' +} + +MACCYRILLIC_RUSSIAN_MODEL = SingleByteCharSetModel(charset_name='MacCyrillic', + language='Russian', + char_to_order_map=MACCYRILLIC_RUSSIAN_CHAR_TO_ORDER, + language_model=RUSSIAN_LANG_MODEL, + typical_positive_ratio=0.976601, + keep_ascii_letters=False, + alphabet='ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё') + +ISO_8859_5_RUSSIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 142, # 'A' + 66: 143, # 'B' + 67: 144, # 'C' + 68: 145, # 'D' + 69: 146, # 'E' + 70: 147, # 'F' + 71: 148, # 'G' + 72: 149, # 'H' + 73: 150, # 'I' + 74: 151, # 'J' + 75: 152, # 'K' + 76: 74, # 'L' + 77: 153, # 'M' + 78: 75, # 'N' + 79: 154, # 'O' + 80: 155, # 'P' + 81: 156, # 'Q' + 82: 157, # 'R' + 83: 158, # 'S' + 84: 159, # 'T' + 85: 160, # 'U' + 86: 161, # 'V' + 87: 162, # 'W' + 88: 163, # 'X' + 89: 164, # 'Y' + 90: 165, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 71, # 'a' + 98: 172, # 'b' + 99: 66, # 'c' + 100: 173, # 'd' + 101: 65, # 'e' + 102: 174, # 'f' + 103: 76, # 'g' + 104: 175, # 'h' + 105: 64, # 'i' + 106: 176, # 'j' + 107: 177, # 'k' + 108: 77, # 'l' + 109: 72, # 'm' + 110: 178, # 'n' + 111: 69, # 'o' + 112: 67, # 'p' + 113: 179, # 'q' + 114: 78, # 'r' + 115: 73, # 's' + 116: 180, # 't' + 117: 181, # 'u' + 118: 79, # 'v' + 119: 182, # 'w' + 120: 183, # 'x' + 121: 184, # 'y' + 122: 185, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 191, # '\x80' + 129: 192, # '\x81' + 130: 193, # '\x82' + 131: 194, # '\x83' + 132: 195, # '\x84' + 133: 196, # '\x85' + 134: 197, # '\x86' + 135: 198, # '\x87' + 136: 199, # '\x88' + 137: 200, # '\x89' + 138: 201, # '\x8a' + 139: 202, # '\x8b' + 140: 203, # '\x8c' + 141: 204, # '\x8d' + 142: 205, # '\x8e' + 143: 206, # '\x8f' + 144: 207, # '\x90' + 145: 208, # '\x91' + 146: 209, # '\x92' + 147: 210, # '\x93' + 148: 211, # '\x94' + 149: 212, # '\x95' + 150: 213, # '\x96' + 151: 214, # '\x97' + 152: 215, # '\x98' + 153: 216, # '\x99' + 154: 217, # '\x9a' + 155: 218, # '\x9b' + 156: 219, # '\x9c' + 157: 220, # '\x9d' + 158: 221, # '\x9e' + 159: 222, # '\x9f' + 160: 223, # '\xa0' + 161: 224, # 'Ё' + 162: 225, # 'Ђ' + 163: 226, # 'Ѓ' + 164: 227, # 'Є' + 165: 228, # 'Ѕ' + 166: 229, # 'І' + 167: 230, # 'Ї' + 168: 231, # 'Ј' + 169: 232, # 'Љ' + 170: 233, # 'Њ' + 171: 234, # 'Ћ' + 172: 235, # 'Ќ' + 173: 236, # '\xad' + 174: 237, # 'Ў' + 175: 238, # 'Џ' + 176: 37, # 'А' + 177: 44, # 'Б' + 178: 33, # 'В' + 179: 46, # 'Г' + 180: 41, # 'Д' + 181: 48, # 'Е' + 182: 56, # 'Ж' + 183: 51, # 'З' + 184: 42, # 'И' + 185: 60, # 'Й' + 186: 36, # 'К' + 187: 49, # 'Л' + 188: 38, # 'М' + 189: 31, # 'Н' + 190: 34, # 'О' + 191: 35, # 'П' + 192: 45, # 'Р' + 193: 32, # 'С' + 194: 40, # 'Т' + 195: 52, # 'У' + 196: 53, # 'Ф' + 197: 55, # 'Х' + 198: 58, # 'Ц' + 199: 50, # 'Ч' + 200: 57, # 'Ш' + 201: 63, # 'Щ' + 202: 70, # 'Ъ' + 203: 62, # 'Ы' + 204: 61, # 'Ь' + 205: 47, # 'Э' + 206: 59, # 'Ю' + 207: 43, # 'Я' + 208: 3, # 'а' + 209: 21, # 'б' + 210: 10, # 'в' + 211: 19, # 'г' + 212: 13, # 'д' + 213: 2, # 'е' + 214: 24, # 'ж' + 215: 20, # 'з' + 216: 4, # 'и' + 217: 23, # 'й' + 218: 11, # 'к' + 219: 8, # 'л' + 220: 12, # 'м' + 221: 5, # 'н' + 222: 1, # 'о' + 223: 15, # 'п' + 224: 9, # 'р' + 225: 7, # 'с' + 226: 6, # 'т' + 227: 14, # 'у' + 228: 39, # 'ф' + 229: 26, # 'х' + 230: 28, # 'ц' + 231: 22, # 'ч' + 232: 25, # 'ш' + 233: 29, # 'щ' + 234: 54, # 'ъ' + 235: 18, # 'ы' + 236: 17, # 'ь' + 237: 30, # 'э' + 238: 27, # 'ю' + 239: 16, # 'я' + 240: 239, # '№' + 241: 68, # 'ё' + 242: 240, # 'ђ' + 243: 241, # 'ѓ' + 244: 242, # 'є' + 245: 243, # 'ѕ' + 246: 244, # 'і' + 247: 245, # 'ї' + 248: 246, # 'ј' + 249: 247, # 'љ' + 250: 248, # 'њ' + 251: 249, # 'ћ' + 252: 250, # 'ќ' + 253: 251, # '§' + 254: 252, # 'ў' + 255: 255, # 'џ' +} + +ISO_8859_5_RUSSIAN_MODEL = SingleByteCharSetModel(charset_name='ISO-8859-5', + language='Russian', + char_to_order_map=ISO_8859_5_RUSSIAN_CHAR_TO_ORDER, + language_model=RUSSIAN_LANG_MODEL, + typical_positive_ratio=0.976601, + keep_ascii_letters=False, + alphabet='ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё') + diff --git a/openpype/vendor/python/python_2/chardet/langthaimodel.py b/openpype/vendor/python/python_2/chardet/langthaimodel.py new file mode 100644 index 0000000000..d0191f241d --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/langthaimodel.py @@ -0,0 +1,4383 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from chardet.sbcharsetprober import SingleByteCharSetModel + + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +THAI_LANG_MODEL = { + 5: { # 'ก' + 5: 2, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 2, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 3, # 'ฎ' + 57: 2, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 2, # 'ณ' + 20: 2, # 'ด' + 19: 3, # 'ต' + 44: 0, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 1, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 1, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 2, # 'ม' + 16: 1, # 'ย' + 2: 3, # 'ร' + 61: 2, # 'ฤ' + 15: 3, # 'ล' + 12: 3, # 'ว' + 42: 2, # 'ศ' + 46: 3, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 3, # 'อ' + 63: 1, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 3, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 0, # 'ึ' + 27: 2, # 'ื' + 32: 2, # 'ุ' + 35: 1, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 1, # 'ใ' + 33: 2, # 'ไ' + 50: 1, # 'ๆ' + 37: 3, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 30: { # 'ข' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 1, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 2, # 'ณ' + 20: 0, # 'ด' + 19: 2, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 1, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 2, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 1, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 2, # 'ี' + 40: 3, # 'ึ' + 27: 1, # 'ื' + 32: 1, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 2, # '่' + 7: 3, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 24: { # 'ค' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 2, # 'ค' + 8: 2, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 2, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 0, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 2, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 3, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 2, # 'า' + 36: 3, # 'ำ' + 23: 3, # 'ิ' + 13: 2, # 'ี' + 40: 0, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 2, # 'ู' + 11: 1, # 'เ' + 28: 0, # 'แ' + 41: 3, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 8: { # 'ง' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 3, # 'ค' + 8: 2, # 'ง' + 26: 2, # 'จ' + 52: 1, # 'ฉ' + 34: 2, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 3, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 1, # 'ฝ' + 31: 2, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 2, # 'ม' + 16: 1, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 2, # 'ว' + 42: 2, # 'ศ' + 46: 1, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 1, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 1, # 'ื' + 32: 1, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 3, # 'ๆ' + 37: 0, # '็' + 6: 2, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 26: { # 'จ' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 0, # 'ค' + 8: 2, # 'ง' + 26: 3, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 1, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 1, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 1, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 1, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 3, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 3, # 'ำ' + 23: 2, # 'ิ' + 13: 1, # 'ี' + 40: 3, # 'ึ' + 27: 1, # 'ื' + 32: 3, # 'ุ' + 35: 2, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 2, # '่' + 7: 2, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 52: { # 'ฉ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 3, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 3, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 1, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 1, # 'ั' + 1: 1, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 1, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 34: { # 'ช' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 1, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 1, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 1, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 2, # 'ั' + 1: 3, # 'า' + 36: 1, # 'ำ' + 23: 3, # 'ิ' + 13: 2, # 'ี' + 40: 0, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 1, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 51: { # 'ซ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 1, # 'ั' + 1: 1, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 2, # 'ี' + 40: 3, # 'ึ' + 27: 2, # 'ื' + 32: 1, # 'ุ' + 35: 1, # 'ู' + 11: 1, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 1, # '่' + 7: 2, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 47: { # 'ญ' + 5: 1, # 'ก' + 30: 1, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 3, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 1, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 2, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 2, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 0, # 'ไ' + 50: 1, # 'ๆ' + 37: 0, # '็' + 6: 2, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 58: { # 'ฎ' + 5: 2, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 1, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 2, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 57: { # 'ฏ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 3, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 49: { # 'ฐ' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 2, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 53: { # 'ฑ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 55: { # 'ฒ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 43: { # 'ณ' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 3, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 3, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 1, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 3, # 'ะ' + 10: 0, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 2, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 20: { # 'ด' + 5: 2, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 1, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 2, # 'ม' + 16: 3, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 3, # 'ั' + 1: 2, # 'า' + 36: 2, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 1, # 'ึ' + 27: 2, # 'ื' + 32: 3, # 'ุ' + 35: 2, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 2, # 'ๆ' + 37: 2, # '็' + 6: 1, # '่' + 7: 3, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 19: { # 'ต' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 1, # 'ต' + 44: 2, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 1, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 2, # 'ภ' + 9: 1, # 'ม' + 16: 1, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 3, # 'ส' + 21: 0, # 'ห' + 4: 3, # 'อ' + 63: 1, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 2, # 'ำ' + 23: 3, # 'ิ' + 13: 2, # 'ี' + 40: 1, # 'ึ' + 27: 1, # 'ื' + 32: 3, # 'ุ' + 35: 2, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 1, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 2, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 44: { # 'ถ' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 2, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 2, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 1, # 'ี' + 40: 3, # 'ึ' + 27: 2, # 'ื' + 32: 2, # 'ุ' + 35: 3, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 2, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 14: { # 'ท' + 5: 1, # 'ก' + 30: 1, # 'ข' + 24: 3, # 'ค' + 8: 1, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 3, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 2, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 3, # 'ย' + 2: 3, # 'ร' + 61: 1, # 'ฤ' + 15: 1, # 'ล' + 12: 2, # 'ว' + 42: 3, # 'ศ' + 46: 1, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 3, # 'ำ' + 23: 2, # 'ิ' + 13: 3, # 'ี' + 40: 2, # 'ึ' + 27: 1, # 'ื' + 32: 3, # 'ุ' + 35: 1, # 'ู' + 11: 0, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 48: { # 'ธ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 1, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 2, # 'า' + 36: 0, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 2, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 3: { # 'น' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 3, # 'ค' + 8: 1, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 1, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 2, # 'ถ' + 14: 3, # 'ท' + 48: 3, # 'ธ' + 3: 2, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 0, # 'ฝ' + 31: 2, # 'พ' + 54: 1, # 'ฟ' + 45: 1, # 'ภ' + 9: 2, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 1, # 'ฤ' + 15: 2, # 'ล' + 12: 3, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 3, # 'อ' + 63: 1, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 3, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 3, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 2, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 3, # 'โ' + 29: 3, # 'ใ' + 33: 3, # 'ไ' + 50: 2, # 'ๆ' + 37: 1, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 17: { # 'บ' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 1, # 'ง' + 26: 1, # 'จ' + 52: 1, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 3, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 1, # 'ฟ' + 45: 1, # 'ภ' + 9: 1, # 'ม' + 16: 0, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 3, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 2, # 'อ' + 63: 1, # 'ฯ' + 22: 0, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 2, # 'ำ' + 23: 2, # 'ิ' + 13: 2, # 'ี' + 40: 0, # 'ึ' + 27: 2, # 'ื' + 32: 3, # 'ุ' + 35: 2, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 2, # '่' + 7: 2, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 25: { # 'ป' + 5: 2, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 1, # 'ฎ' + 57: 3, # 'ฏ' + 49: 1, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 1, # 'ต' + 44: 1, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 0, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 1, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 0, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 1, # 'ษ' + 18: 2, # 'ส' + 21: 1, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 3, # 'ั' + 1: 1, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 3, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 1, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 2, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 2, # 'ไ' + 50: 0, # 'ๆ' + 37: 3, # '็' + 6: 1, # '่' + 7: 2, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 39: { # 'ผ' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 1, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 2, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 1, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 1, # 'ื' + 32: 0, # 'ุ' + 35: 3, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 1, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 62: { # 'ฝ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 1, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 1, # 'ี' + 40: 2, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 2, # '่' + 7: 1, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 31: { # 'พ' + 5: 1, # 'ก' + 30: 1, # 'ข' + 24: 1, # 'ค' + 8: 1, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 1, # 'ณ' + 20: 1, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 2, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 0, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 2, # 'ย' + 2: 3, # 'ร' + 61: 2, # 'ฤ' + 15: 2, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 1, # 'ห' + 4: 2, # 'อ' + 63: 1, # 'ฯ' + 22: 0, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 3, # 'ิ' + 13: 2, # 'ี' + 40: 1, # 'ึ' + 27: 3, # 'ื' + 32: 1, # 'ุ' + 35: 2, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 0, # '่' + 7: 1, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 54: { # 'ฟ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 2, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 2, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 1, # 'ื' + 32: 1, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 2, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 45: { # 'ภ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 3, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 2, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 9: { # 'ม' + 5: 2, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 2, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 1, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 3, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 2, # 'ม' + 16: 1, # 'ย' + 2: 2, # 'ร' + 61: 2, # 'ฤ' + 15: 2, # 'ล' + 12: 2, # 'ว' + 42: 1, # 'ศ' + 46: 1, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 0, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 3, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 2, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 1, # 'ๆ' + 37: 1, # '็' + 6: 3, # '่' + 7: 2, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 16: { # 'ย' + 5: 3, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 2, # 'ช' + 51: 0, # 'ซ' + 47: 2, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 2, # 'ม' + 16: 0, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 3, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 1, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 3, # 'ี' + 40: 1, # 'ึ' + 27: 2, # 'ื' + 32: 2, # 'ุ' + 35: 3, # 'ู' + 11: 2, # 'เ' + 28: 1, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 2, # 'ๆ' + 37: 1, # '็' + 6: 3, # '่' + 7: 2, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 2: { # 'ร' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 2, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 3, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 3, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 3, # 'ถ' + 14: 3, # 'ท' + 48: 1, # 'ธ' + 3: 2, # 'น' + 17: 2, # 'บ' + 25: 3, # 'ป' + 39: 2, # 'ผ' + 62: 1, # 'ฝ' + 31: 2, # 'พ' + 54: 1, # 'ฟ' + 45: 1, # 'ภ' + 9: 3, # 'ม' + 16: 2, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 3, # 'ว' + 42: 2, # 'ศ' + 46: 2, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 3, # 'อ' + 63: 1, # 'ฯ' + 22: 3, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 2, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 3, # 'ู' + 11: 3, # 'เ' + 28: 3, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 3, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 61: { # 'ฤ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 2, # 'ต' + 44: 0, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 2, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 15: { # 'ล' + 5: 2, # 'ก' + 30: 3, # 'ข' + 24: 1, # 'ค' + 8: 3, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 1, # 'ม' + 16: 3, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 1, # 'ห' + 4: 3, # 'อ' + 63: 2, # 'ฯ' + 22: 3, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 2, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 2, # 'ึ' + 27: 3, # 'ื' + 32: 2, # 'ุ' + 35: 3, # 'ู' + 11: 2, # 'เ' + 28: 1, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 2, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 12: { # 'ว' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 1, # 'ค' + 8: 3, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 1, # 'ณ' + 20: 2, # 'ด' + 19: 1, # 'ต' + 44: 1, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 1, # 'ฟ' + 45: 0, # 'ภ' + 9: 3, # 'ม' + 16: 3, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 3, # 'ิ' + 13: 2, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 2, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 1, # 'ใ' + 33: 2, # 'ไ' + 50: 1, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 42: { # 'ศ' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 1, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 2, # 'ว' + 42: 1, # 'ศ' + 46: 2, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 2, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 0, # 'ี' + 40: 3, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 2, # 'ู' + 11: 0, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 46: { # 'ษ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 2, # 'ฎ' + 57: 1, # 'ฏ' + 49: 2, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 3, # 'ณ' + 20: 0, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 1, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 2, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 18: { # 'ส' + 5: 2, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 2, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 3, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 2, # 'ภ' + 9: 3, # 'ม' + 16: 1, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 2, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 3, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 2, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 3, # 'ู' + 11: 2, # 'เ' + 28: 0, # 'แ' + 41: 1, # 'โ' + 29: 0, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 1, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 21: { # 'ห' + 5: 3, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 1, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 2, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 3, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 0, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 3, # 'ม' + 16: 2, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 1, # 'ุ' + 35: 1, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 3, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 4: { # 'อ' + 5: 3, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 1, # 'ฟ' + 45: 1, # 'ภ' + 9: 3, # 'ม' + 16: 3, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 2, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 2, # 'ำ' + 23: 2, # 'ิ' + 13: 3, # 'ี' + 40: 0, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 1, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 1, # 'ๆ' + 37: 1, # '็' + 6: 2, # '่' + 7: 2, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 63: { # 'ฯ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 22: { # 'ะ' + 5: 3, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 1, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 3, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 1, # 'ถ' + 14: 3, # 'ท' + 48: 1, # 'ธ' + 3: 2, # 'น' + 17: 3, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 2, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 3, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 2, # 'อ' + 63: 1, # 'ฯ' + 22: 1, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 10: { # 'ั' + 5: 3, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 3, # 'ง' + 26: 3, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 3, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 2, # 'ฐ' + 53: 0, # 'ฑ' + 55: 3, # 'ฒ' + 43: 3, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 0, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 2, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 3, # 'ม' + 16: 3, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 3, # 'ว' + 42: 2, # 'ศ' + 46: 0, # 'ษ' + 18: 3, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 1: { # 'า' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 3, # 'ค' + 8: 3, # 'ง' + 26: 3, # 'จ' + 52: 0, # 'ฉ' + 34: 3, # 'ช' + 51: 1, # 'ซ' + 47: 2, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 3, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 1, # 'ถ' + 14: 3, # 'ท' + 48: 2, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 1, # 'ฝ' + 31: 3, # 'พ' + 54: 1, # 'ฟ' + 45: 1, # 'ภ' + 9: 3, # 'ม' + 16: 3, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 3, # 'ว' + 42: 2, # 'ศ' + 46: 3, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 2, # 'อ' + 63: 1, # 'ฯ' + 22: 3, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 1, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 36: { # 'ำ' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 3, # 'ค' + 8: 2, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 1, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 1, # 'ต' + 44: 1, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 1, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 1, # 'ม' + 16: 0, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 3, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 23: { # 'ิ' + 5: 3, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 3, # 'จ' + 52: 0, # 'ฉ' + 34: 3, # 'ช' + 51: 0, # 'ซ' + 47: 2, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 1, # 'ถ' + 14: 3, # 'ท' + 48: 3, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 0, # 'ฝ' + 31: 3, # 'พ' + 54: 1, # 'ฟ' + 45: 2, # 'ภ' + 9: 3, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 3, # 'ว' + 42: 3, # 'ศ' + 46: 2, # 'ษ' + 18: 2, # 'ส' + 21: 3, # 'ห' + 4: 1, # 'อ' + 63: 1, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 1, # 'แ' + 41: 1, # 'โ' + 29: 1, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 2, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 13: { # 'ี' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 2, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 3, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 2, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 1, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 1, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 40: { # 'ึ' + 5: 3, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 3, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 27: { # 'ื' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 3, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 32: { # 'ุ' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 3, # 'ค' + 8: 3, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 2, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 1, # 'ฒ' + 43: 3, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 1, # 'ธ' + 3: 2, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 3, # 'ม' + 16: 1, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 1, # 'ว' + 42: 1, # 'ศ' + 46: 2, # 'ษ' + 18: 1, # 'ส' + 21: 1, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 0, # 'แ' + 41: 1, # 'โ' + 29: 0, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 2, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 35: { # 'ู' + 5: 3, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 2, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 2, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 1, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 0, # 'บ' + 25: 3, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 1, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 11: { # 'เ' + 5: 3, # 'ก' + 30: 3, # 'ข' + 24: 3, # 'ค' + 8: 2, # 'ง' + 26: 3, # 'จ' + 52: 3, # 'ฉ' + 34: 3, # 'ช' + 51: 2, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 1, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 1, # 'ถ' + 14: 3, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 3, # 'ป' + 39: 2, # 'ผ' + 62: 1, # 'ฝ' + 31: 3, # 'พ' + 54: 1, # 'ฟ' + 45: 3, # 'ภ' + 9: 3, # 'ม' + 16: 2, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 3, # 'ว' + 42: 2, # 'ศ' + 46: 0, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 28: { # 'แ' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 1, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 3, # 'ต' + 44: 2, # 'ถ' + 14: 3, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 2, # 'ป' + 39: 3, # 'ผ' + 62: 0, # 'ฝ' + 31: 2, # 'พ' + 54: 2, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 41: { # 'โ' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 1, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 2, # 'ต' + 44: 0, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 1, # 'บ' + 25: 3, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 1, # 'ฟ' + 45: 1, # 'ภ' + 9: 1, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 0, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 0, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 29: { # 'ใ' + 5: 2, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 3, # 'จ' + 52: 0, # 'ฉ' + 34: 3, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 1, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 33: { # 'ไ' + 5: 1, # 'ก' + 30: 2, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 3, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 1, # 'บ' + 25: 3, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 2, # 'ฟ' + 45: 0, # 'ภ' + 9: 3, # 'ม' + 16: 0, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 3, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 2, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 50: { # 'ๆ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 37: { # '็' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 2, # 'ง' + 26: 3, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 1, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 2, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 1, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 6: { # '่' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 1, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 1, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 1, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 3, # 'ม' + 16: 3, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 3, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 1, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 0, # 'ั' + 1: 3, # 'า' + 36: 2, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 1, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 7: { # '้' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 1, # 'ฟ' + 45: 0, # 'ภ' + 9: 3, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 3, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 3, # 'า' + 36: 2, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 38: { # '์' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 1, # 'ต' + 44: 1, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 1, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 1, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 1, # 'ฤ' + 15: 1, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 1, # 'ห' + 4: 2, # 'อ' + 63: 1, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 56: { # '๑' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 2, # '๑' + 59: 1, # '๒' + 60: 1, # '๕' + }, + 59: { # '๒' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 1, # '๑' + 59: 1, # '๒' + 60: 3, # '๕' + }, + 60: { # '๕' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 2, # '๑' + 59: 1, # '๒' + 60: 0, # '๕' + }, +} + +# 255: Undefined characters that did not exist in training text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 +# 251: Control characters + +# Character Mapping Table(s): +TIS_620_THAI_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 182, # 'A' + 66: 106, # 'B' + 67: 107, # 'C' + 68: 100, # 'D' + 69: 183, # 'E' + 70: 184, # 'F' + 71: 185, # 'G' + 72: 101, # 'H' + 73: 94, # 'I' + 74: 186, # 'J' + 75: 187, # 'K' + 76: 108, # 'L' + 77: 109, # 'M' + 78: 110, # 'N' + 79: 111, # 'O' + 80: 188, # 'P' + 81: 189, # 'Q' + 82: 190, # 'R' + 83: 89, # 'S' + 84: 95, # 'T' + 85: 112, # 'U' + 86: 113, # 'V' + 87: 191, # 'W' + 88: 192, # 'X' + 89: 193, # 'Y' + 90: 194, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 64, # 'a' + 98: 72, # 'b' + 99: 73, # 'c' + 100: 114, # 'd' + 101: 74, # 'e' + 102: 115, # 'f' + 103: 116, # 'g' + 104: 102, # 'h' + 105: 81, # 'i' + 106: 201, # 'j' + 107: 117, # 'k' + 108: 90, # 'l' + 109: 103, # 'm' + 110: 78, # 'n' + 111: 82, # 'o' + 112: 96, # 'p' + 113: 202, # 'q' + 114: 91, # 'r' + 115: 79, # 's' + 116: 84, # 't' + 117: 104, # 'u' + 118: 105, # 'v' + 119: 97, # 'w' + 120: 98, # 'x' + 121: 92, # 'y' + 122: 203, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 209, # '\x80' + 129: 210, # '\x81' + 130: 211, # '\x82' + 131: 212, # '\x83' + 132: 213, # '\x84' + 133: 88, # '\x85' + 134: 214, # '\x86' + 135: 215, # '\x87' + 136: 216, # '\x88' + 137: 217, # '\x89' + 138: 218, # '\x8a' + 139: 219, # '\x8b' + 140: 220, # '\x8c' + 141: 118, # '\x8d' + 142: 221, # '\x8e' + 143: 222, # '\x8f' + 144: 223, # '\x90' + 145: 224, # '\x91' + 146: 99, # '\x92' + 147: 85, # '\x93' + 148: 83, # '\x94' + 149: 225, # '\x95' + 150: 226, # '\x96' + 151: 227, # '\x97' + 152: 228, # '\x98' + 153: 229, # '\x99' + 154: 230, # '\x9a' + 155: 231, # '\x9b' + 156: 232, # '\x9c' + 157: 233, # '\x9d' + 158: 234, # '\x9e' + 159: 235, # '\x9f' + 160: 236, # None + 161: 5, # 'ก' + 162: 30, # 'ข' + 163: 237, # 'ฃ' + 164: 24, # 'ค' + 165: 238, # 'ฅ' + 166: 75, # 'ฆ' + 167: 8, # 'ง' + 168: 26, # 'จ' + 169: 52, # 'ฉ' + 170: 34, # 'ช' + 171: 51, # 'ซ' + 172: 119, # 'ฌ' + 173: 47, # 'ญ' + 174: 58, # 'ฎ' + 175: 57, # 'ฏ' + 176: 49, # 'ฐ' + 177: 53, # 'ฑ' + 178: 55, # 'ฒ' + 179: 43, # 'ณ' + 180: 20, # 'ด' + 181: 19, # 'ต' + 182: 44, # 'ถ' + 183: 14, # 'ท' + 184: 48, # 'ธ' + 185: 3, # 'น' + 186: 17, # 'บ' + 187: 25, # 'ป' + 188: 39, # 'ผ' + 189: 62, # 'ฝ' + 190: 31, # 'พ' + 191: 54, # 'ฟ' + 192: 45, # 'ภ' + 193: 9, # 'ม' + 194: 16, # 'ย' + 195: 2, # 'ร' + 196: 61, # 'ฤ' + 197: 15, # 'ล' + 198: 239, # 'ฦ' + 199: 12, # 'ว' + 200: 42, # 'ศ' + 201: 46, # 'ษ' + 202: 18, # 'ส' + 203: 21, # 'ห' + 204: 76, # 'ฬ' + 205: 4, # 'อ' + 206: 66, # 'ฮ' + 207: 63, # 'ฯ' + 208: 22, # 'ะ' + 209: 10, # 'ั' + 210: 1, # 'า' + 211: 36, # 'ำ' + 212: 23, # 'ิ' + 213: 13, # 'ี' + 214: 40, # 'ึ' + 215: 27, # 'ื' + 216: 32, # 'ุ' + 217: 35, # 'ู' + 218: 86, # 'ฺ' + 219: 240, # None + 220: 241, # None + 221: 242, # None + 222: 243, # None + 223: 244, # '฿' + 224: 11, # 'เ' + 225: 28, # 'แ' + 226: 41, # 'โ' + 227: 29, # 'ใ' + 228: 33, # 'ไ' + 229: 245, # 'ๅ' + 230: 50, # 'ๆ' + 231: 37, # '็' + 232: 6, # '่' + 233: 7, # '้' + 234: 67, # '๊' + 235: 77, # '๋' + 236: 38, # '์' + 237: 93, # 'ํ' + 238: 246, # '๎' + 239: 247, # '๏' + 240: 68, # '๐' + 241: 56, # '๑' + 242: 59, # '๒' + 243: 65, # '๓' + 244: 69, # '๔' + 245: 60, # '๕' + 246: 70, # '๖' + 247: 80, # '๗' + 248: 71, # '๘' + 249: 87, # '๙' + 250: 248, # '๚' + 251: 249, # '๛' + 252: 250, # None + 253: 251, # None + 254: 252, # None + 255: 253, # None +} + +TIS_620_THAI_MODEL = SingleByteCharSetModel(charset_name='TIS-620', + language='Thai', + char_to_order_map=TIS_620_THAI_CHAR_TO_ORDER, + language_model=THAI_LANG_MODEL, + typical_positive_ratio=0.926386, + keep_ascii_letters=False, + alphabet='กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛') + diff --git a/openpype/vendor/python/python_2/chardet/langturkishmodel.py b/openpype/vendor/python/python_2/chardet/langturkishmodel.py new file mode 100644 index 0000000000..8ba93224de --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/langturkishmodel.py @@ -0,0 +1,4383 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from chardet.sbcharsetprober import SingleByteCharSetModel + + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +TURKISH_LANG_MODEL = { + 23: { # 'A' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 1, # 'h' + 3: 1, # 'i' + 24: 0, # 'j' + 10: 2, # 'k' + 5: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 1, # 'r' + 8: 1, # 's' + 9: 1, # 't' + 14: 1, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 37: { # 'B' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 2, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 1, # 'Ş' + 19: 1, # 'ş' + }, + 47: { # 'C' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 1, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 1, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 2, # 'j' + 10: 1, # 'k' + 5: 2, # 'l' + 13: 2, # 'm' + 4: 2, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 2, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 1, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 39: { # 'D' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 1, # 'l' + 13: 3, # 'm' + 4: 0, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 1, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 1, # 'Ş' + 19: 0, # 'ş' + }, + 29: { # 'E' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 1, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 0, # 'h' + 3: 1, # 'i' + 24: 1, # 'j' + 10: 0, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 1, # 's' + 9: 1, # 't' + 14: 1, # 'u' + 32: 1, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 52: { # 'F' + 23: 0, # 'A' + 37: 1, # 'B' + 47: 1, # 'C' + 39: 1, # 'D' + 29: 1, # 'E' + 52: 2, # 'F' + 36: 0, # 'G' + 45: 2, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 1, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 1, # 'b' + 28: 1, # 'c' + 12: 1, # 'd' + 2: 0, # 'e' + 18: 1, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 2, # 'i' + 24: 1, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 2, # 'r' + 8: 1, # 's' + 9: 1, # 't' + 14: 1, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 1, # 'Ö' + 55: 2, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 2, # 'ş' + }, + 36: { # 'G' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 2, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 2, # 'N' + 42: 1, # 'O' + 48: 1, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 1, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 1, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 0, # 'r' + 8: 1, # 's' + 9: 1, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 1, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 2, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 45: { # 'H' + 23: 0, # 'A' + 37: 1, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 2, # 'G' + 45: 1, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 1, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 2, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 2, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 1, # 'o' + 26: 1, # 'p' + 7: 1, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 2, # 'ğ' + 41: 1, # 'İ' + 6: 0, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 53: { # 'I' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 2, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 1, # 'Ş' + 19: 1, # 'ş' + }, + 60: { # 'J' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 1, # 'd' + 2: 0, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 1, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 1, # 's' + 9: 0, # 't' + 14: 0, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 16: { # 'K' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 1, # 'e' + 18: 3, # 'f' + 27: 3, # 'g' + 25: 3, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 0, # 'u' + 32: 3, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 1, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 49: { # 'L' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 2, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 2, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 0, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 2, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 2, # 'n' + 15: 1, # 'o' + 26: 1, # 'p' + 7: 1, # 'r' + 8: 1, # 's' + 9: 1, # 't' + 14: 0, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 2, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 1, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 20: { # 'M' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 1, # 'h' + 3: 2, # 'i' + 24: 2, # 'j' + 10: 2, # 'k' + 5: 2, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 3, # 'r' + 8: 0, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 46: { # 'N' + 23: 0, # 'A' + 37: 1, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 2, # 'j' + 10: 1, # 'k' + 5: 1, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 1, # 'o' + 26: 1, # 'p' + 7: 1, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 1, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 1, # 'İ' + 6: 2, # 'ı' + 40: 1, # 'Ş' + 19: 1, # 'ş' + }, + 42: { # 'O' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 1, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 0, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 2, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 2, # 'İ' + 6: 1, # 'ı' + 40: 1, # 'Ş' + 19: 1, # 'ş' + }, + 48: { # 'P' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 2, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 2, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 2, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 0, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 44: { # 'R' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 1, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 2, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 1, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 1, # 'Ş' + 19: 1, # 'ş' + }, + 35: { # 'S' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 1, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 1, # 'l' + 13: 2, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 1, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 2, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 31: { # 'T' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 0, # 'c' + 12: 1, # 'd' + 2: 3, # 'e' + 18: 2, # 'f' + 27: 2, # 'g' + 25: 0, # 'h' + 3: 1, # 'i' + 24: 1, # 'j' + 10: 2, # 'k' + 5: 2, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 2, # 'r' + 8: 0, # 's' + 9: 2, # 't' + 14: 2, # 'u' + 32: 1, # 'v' + 57: 1, # 'w' + 58: 1, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 51: { # 'U' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 1, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 1, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 38: { # 'V' + 23: 1, # 'A' + 37: 1, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 1, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 2, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 2, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 1, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 1, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 3, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 62: { # 'W' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 0, # 'd' + 2: 0, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 0, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 43: { # 'Y' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 0, # 'G' + 45: 1, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 2, # 'N' + 42: 0, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 1, # 'j' + 10: 1, # 'k' + 5: 1, # 'l' + 13: 3, # 'm' + 4: 0, # 'n' + 15: 2, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 2, # 'Ö' + 55: 1, # 'Ü' + 59: 1, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 0, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 56: { # 'Z' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 2, # 'Z' + 1: 2, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 2, # 'i' + 24: 1, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 1, # 'r' + 8: 1, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 1: { # 'a' + 23: 3, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 1, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 3, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 2, # 'Z' + 1: 2, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 2, # 'e' + 18: 3, # 'f' + 27: 3, # 'g' + 25: 3, # 'h' + 3: 3, # 'i' + 24: 3, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 3, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 3, # 'v' + 57: 2, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 1, # 'î' + 34: 1, # 'ö' + 17: 3, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 21: { # 'b' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 3, # 'g' + 25: 1, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 3, # 'p' + 7: 1, # 'r' + 8: 2, # 's' + 9: 2, # 't' + 14: 2, # 'u' + 32: 1, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 28: { # 'c' + 23: 0, # 'A' + 37: 1, # 'B' + 47: 1, # 'C' + 39: 1, # 'D' + 29: 2, # 'E' + 52: 0, # 'F' + 36: 2, # 'G' + 45: 2, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 2, # 'T' + 51: 2, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 3, # 'Y' + 56: 0, # 'Z' + 1: 1, # 'a' + 21: 1, # 'b' + 28: 2, # 'c' + 12: 2, # 'd' + 2: 1, # 'e' + 18: 1, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 3, # 'i' + 24: 1, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 2, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 1, # 'u' + 32: 0, # 'v' + 57: 1, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 1, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 1, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 1, # 'î' + 34: 2, # 'ö' + 17: 2, # 'ü' + 30: 2, # 'ğ' + 41: 1, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 2, # 'ş' + }, + 12: { # 'd' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 2, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 1, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 1, # 'f' + 27: 3, # 'g' + 25: 3, # 'h' + 3: 2, # 'i' + 24: 3, # 'j' + 10: 2, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 2, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 1, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 3, # 'y' + 22: 1, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 2: { # 'e' + 23: 2, # 'A' + 37: 0, # 'B' + 47: 2, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 1, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 1, # 'R' + 35: 0, # 'S' + 31: 3, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 2, # 'e' + 18: 3, # 'f' + 27: 3, # 'g' + 25: 3, # 'h' + 3: 3, # 'i' + 24: 3, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 3, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 3, # 'v' + 57: 2, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 1, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 3, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 18: { # 'f' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 2, # 'f' + 27: 1, # 'g' + 25: 1, # 'h' + 3: 1, # 'i' + 24: 1, # 'j' + 10: 1, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 1, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 1, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 27: { # 'g' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 1, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 1, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 1, # 'h' + 3: 2, # 'i' + 24: 3, # 'j' + 10: 2, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 2, # 'r' + 8: 2, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 1, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 25: { # 'h' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 2, # 'h' + 3: 2, # 'i' + 24: 3, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 1, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 1, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 3: { # 'i' + 23: 2, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 0, # 'N' + 42: 1, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 1, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 2, # 'f' + 27: 3, # 'g' + 25: 1, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 3, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 1, # 'w' + 58: 1, # 'x' + 11: 3, # 'y' + 22: 1, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 1, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 3, # 'ü' + 30: 0, # 'ğ' + 41: 1, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 24: { # 'j' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 1, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 2, # 'f' + 27: 1, # 'g' + 25: 1, # 'h' + 3: 2, # 'i' + 24: 1, # 'j' + 10: 2, # 'k' + 5: 2, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 2, # 'r' + 8: 3, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 2, # 'x' + 11: 1, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 10: { # 'k' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 3, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 3, # 'e' + 18: 1, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 2, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 3, # 'p' + 7: 2, # 'r' + 8: 2, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 3, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 5: { # 'l' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 1, # 'e' + 18: 3, # 'f' + 27: 3, # 'g' + 25: 2, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 1, # 'l' + 13: 1, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 2, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 13: { # 'm' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 3, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 2, # 'e' + 18: 3, # 'f' + 27: 3, # 'g' + 25: 3, # 'h' + 3: 3, # 'i' + 24: 3, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 2, # 'u' + 32: 2, # 'v' + 57: 1, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 3, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 4: { # 'n' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 2, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 1, # 'f' + 27: 2, # 'g' + 25: 3, # 'h' + 3: 2, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 3, # 'p' + 7: 2, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 2, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 15: { # 'o' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 2, # 'L' + 20: 0, # 'M' + 46: 2, # 'N' + 42: 1, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 1, # 'i' + 24: 2, # 'j' + 10: 1, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 2, # 'o' + 26: 0, # 'p' + 7: 1, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 2, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 2, # 'ğ' + 41: 2, # 'İ' + 6: 3, # 'ı' + 40: 2, # 'Ş' + 19: 2, # 'ş' + }, + 26: { # 'p' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 1, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 1, # 'h' + 3: 2, # 'i' + 24: 3, # 'j' + 10: 1, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 2, # 'r' + 8: 1, # 's' + 9: 1, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 1, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 7: { # 'r' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 2, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 1, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 3, # 'h' + 3: 2, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 3, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 8: { # 's' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 2, # 'i' + 24: 3, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 3, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 2, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 9: { # 't' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 2, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 2, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 3, # 'v' + 57: 0, # 'w' + 58: 2, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 14: { # 'u' + 23: 3, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 2, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 3, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 2, # 'Z' + 1: 2, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 2, # 'e' + 18: 2, # 'f' + 27: 3, # 'g' + 25: 3, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 3, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 2, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 3, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 32: { # 'v' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 1, # 'j' + 10: 1, # 'k' + 5: 3, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 1, # 'r' + 8: 2, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 1, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 57: { # 'w' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 1, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 1, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 1, # 's' + 9: 0, # 't' + 14: 1, # 'u' + 32: 0, # 'v' + 57: 2, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 58: { # 'x' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 1, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 1, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 2, # 'i' + 24: 2, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 2, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 1, # 'r' + 8: 2, # 's' + 9: 1, # 't' + 14: 0, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 11: { # 'y' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 2, # 'i' + 24: 1, # 'j' + 10: 2, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 2, # 'r' + 8: 1, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 22: { # 'z' + 23: 2, # 'A' + 37: 2, # 'B' + 47: 1, # 'C' + 39: 2, # 'D' + 29: 3, # 'E' + 52: 1, # 'F' + 36: 2, # 'G' + 45: 2, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 2, # 'N' + 42: 2, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 3, # 'T' + 51: 2, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 1, # 'Z' + 1: 1, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 2, # 'd' + 2: 2, # 'e' + 18: 3, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 2, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 0, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 2, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 2, # 'Ü' + 59: 1, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 2, # 'ü' + 30: 2, # 'ğ' + 41: 1, # 'İ' + 6: 3, # 'ı' + 40: 1, # 'Ş' + 19: 2, # 'ş' + }, + 63: { # '·' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 0, # 'd' + 2: 1, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 54: { # 'Ç' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 1, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 1, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 1, # 'd' + 2: 0, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 0, # 'h' + 3: 3, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 2, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 2, # 'r' + 8: 0, # 's' + 9: 1, # 't' + 14: 0, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 2, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 50: { # 'Ö' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 1, # 'D' + 29: 2, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 2, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 1, # 'N' + 42: 2, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 2, # 'd' + 2: 0, # 'e' + 18: 1, # 'f' + 27: 1, # 'g' + 25: 1, # 'h' + 3: 2, # 'i' + 24: 0, # 'j' + 10: 2, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 3, # 'n' + 15: 2, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 1, # 's' + 9: 2, # 't' + 14: 0, # 'u' + 32: 1, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 2, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 55: { # 'Ü' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 1, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 0, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 59: { # 'â' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 1, # 'Ş' + 19: 0, # 'ş' + }, + 33: { # 'ç' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 0, # 'e' + 18: 2, # 'f' + 27: 1, # 'g' + 25: 3, # 'h' + 3: 3, # 'i' + 24: 0, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 3, # 'r' + 8: 2, # 's' + 9: 3, # 't' + 14: 0, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 61: { # 'î' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 1, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 1, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 1, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 34: { # 'ö' + 23: 0, # 'A' + 37: 1, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 1, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 1, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 2, # 'c' + 12: 1, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 1, # 'i' + 24: 2, # 'j' + 10: 1, # 'k' + 5: 2, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 2, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 3, # 's' + 9: 1, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 0, # 'ü' + 30: 2, # 'ğ' + 41: 1, # 'İ' + 6: 1, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 17: { # 'ü' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 1, # 'd' + 2: 3, # 'e' + 18: 1, # 'f' + 27: 2, # 'g' + 25: 0, # 'h' + 3: 1, # 'i' + 24: 1, # 'j' + 10: 2, # 'k' + 5: 3, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 2, # 'r' + 8: 3, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 1, # 'v' + 57: 1, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 30: { # 'ğ' + 23: 0, # 'A' + 37: 2, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 2, # 'N' + 42: 2, # 'O' + 48: 1, # 'P' + 44: 1, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 3, # 'j' + 10: 1, # 'k' + 5: 2, # 'l' + 13: 3, # 'm' + 4: 0, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 1, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 2, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 2, # 'İ' + 6: 2, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 41: { # 'İ' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 1, # 'D' + 29: 1, # 'E' + 52: 0, # 'F' + 36: 2, # 'G' + 45: 2, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 2, # 'P' + 44: 0, # 'R' + 35: 1, # 'S' + 31: 1, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 0, # 'Z' + 1: 1, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 2, # 'd' + 2: 1, # 'e' + 18: 0, # 'f' + 27: 3, # 'g' + 25: 2, # 'h' + 3: 2, # 'i' + 24: 2, # 'j' + 10: 2, # 'k' + 5: 0, # 'l' + 13: 1, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 1, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 2, # 't' + 14: 0, # 'u' + 32: 0, # 'v' + 57: 1, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 1, # 'Ü' + 59: 1, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 1, # 'ü' + 30: 2, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 6: { # 'ı' + 23: 2, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 2, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 1, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 3, # 'f' + 27: 3, # 'g' + 25: 2, # 'h' + 3: 3, # 'i' + 24: 3, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 3, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 3, # 'v' + 57: 1, # 'w' + 58: 1, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 3, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 40: { # 'Ş' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 1, # 'D' + 29: 1, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 2, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 2, # 'P' + 44: 2, # 'R' + 35: 1, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 1, # 'Z' + 1: 0, # 'a' + 21: 2, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 0, # 'e' + 18: 3, # 'f' + 27: 0, # 'g' + 25: 2, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 1, # 'm' + 4: 3, # 'n' + 15: 2, # 'o' + 26: 0, # 'p' + 7: 3, # 'r' + 8: 2, # 's' + 9: 2, # 't' + 14: 1, # 'u' + 32: 3, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 1, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 1, # 'ü' + 30: 2, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 1, # 'Ş' + 19: 2, # 'ş' + }, + 19: { # 'ş' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 2, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 1, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 1, # 'h' + 3: 1, # 'i' + 24: 0, # 'j' + 10: 2, # 'k' + 5: 2, # 'l' + 13: 3, # 'm' + 4: 0, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 3, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 1, # 'î' + 34: 2, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 1, # 'ı' + 40: 1, # 'Ş' + 19: 1, # 'ş' + }, +} + +# 255: Undefined characters that did not exist in training text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 +# 251: Control characters + +# Character Mapping Table(s): +ISO_8859_9_TURKISH_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 255, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 255, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 255, # ' ' + 33: 255, # '!' + 34: 255, # '"' + 35: 255, # '#' + 36: 255, # '$' + 37: 255, # '%' + 38: 255, # '&' + 39: 255, # "'" + 40: 255, # '(' + 41: 255, # ')' + 42: 255, # '*' + 43: 255, # '+' + 44: 255, # ',' + 45: 255, # '-' + 46: 255, # '.' + 47: 255, # '/' + 48: 255, # '0' + 49: 255, # '1' + 50: 255, # '2' + 51: 255, # '3' + 52: 255, # '4' + 53: 255, # '5' + 54: 255, # '6' + 55: 255, # '7' + 56: 255, # '8' + 57: 255, # '9' + 58: 255, # ':' + 59: 255, # ';' + 60: 255, # '<' + 61: 255, # '=' + 62: 255, # '>' + 63: 255, # '?' + 64: 255, # '@' + 65: 23, # 'A' + 66: 37, # 'B' + 67: 47, # 'C' + 68: 39, # 'D' + 69: 29, # 'E' + 70: 52, # 'F' + 71: 36, # 'G' + 72: 45, # 'H' + 73: 53, # 'I' + 74: 60, # 'J' + 75: 16, # 'K' + 76: 49, # 'L' + 77: 20, # 'M' + 78: 46, # 'N' + 79: 42, # 'O' + 80: 48, # 'P' + 81: 69, # 'Q' + 82: 44, # 'R' + 83: 35, # 'S' + 84: 31, # 'T' + 85: 51, # 'U' + 86: 38, # 'V' + 87: 62, # 'W' + 88: 65, # 'X' + 89: 43, # 'Y' + 90: 56, # 'Z' + 91: 255, # '[' + 92: 255, # '\\' + 93: 255, # ']' + 94: 255, # '^' + 95: 255, # '_' + 96: 255, # '`' + 97: 1, # 'a' + 98: 21, # 'b' + 99: 28, # 'c' + 100: 12, # 'd' + 101: 2, # 'e' + 102: 18, # 'f' + 103: 27, # 'g' + 104: 25, # 'h' + 105: 3, # 'i' + 106: 24, # 'j' + 107: 10, # 'k' + 108: 5, # 'l' + 109: 13, # 'm' + 110: 4, # 'n' + 111: 15, # 'o' + 112: 26, # 'p' + 113: 64, # 'q' + 114: 7, # 'r' + 115: 8, # 's' + 116: 9, # 't' + 117: 14, # 'u' + 118: 32, # 'v' + 119: 57, # 'w' + 120: 58, # 'x' + 121: 11, # 'y' + 122: 22, # 'z' + 123: 255, # '{' + 124: 255, # '|' + 125: 255, # '}' + 126: 255, # '~' + 127: 255, # '\x7f' + 128: 180, # '\x80' + 129: 179, # '\x81' + 130: 178, # '\x82' + 131: 177, # '\x83' + 132: 176, # '\x84' + 133: 175, # '\x85' + 134: 174, # '\x86' + 135: 173, # '\x87' + 136: 172, # '\x88' + 137: 171, # '\x89' + 138: 170, # '\x8a' + 139: 169, # '\x8b' + 140: 168, # '\x8c' + 141: 167, # '\x8d' + 142: 166, # '\x8e' + 143: 165, # '\x8f' + 144: 164, # '\x90' + 145: 163, # '\x91' + 146: 162, # '\x92' + 147: 161, # '\x93' + 148: 160, # '\x94' + 149: 159, # '\x95' + 150: 101, # '\x96' + 151: 158, # '\x97' + 152: 157, # '\x98' + 153: 156, # '\x99' + 154: 155, # '\x9a' + 155: 154, # '\x9b' + 156: 153, # '\x9c' + 157: 152, # '\x9d' + 158: 151, # '\x9e' + 159: 106, # '\x9f' + 160: 150, # '\xa0' + 161: 149, # '¡' + 162: 148, # '¢' + 163: 147, # '£' + 164: 146, # '¤' + 165: 145, # '¥' + 166: 144, # '¦' + 167: 100, # '§' + 168: 143, # '¨' + 169: 142, # '©' + 170: 141, # 'ª' + 171: 140, # '«' + 172: 139, # '¬' + 173: 138, # '\xad' + 174: 137, # '®' + 175: 136, # '¯' + 176: 94, # '°' + 177: 80, # '±' + 178: 93, # '²' + 179: 135, # '³' + 180: 105, # '´' + 181: 134, # 'µ' + 182: 133, # '¶' + 183: 63, # '·' + 184: 132, # '¸' + 185: 131, # '¹' + 186: 130, # 'º' + 187: 129, # '»' + 188: 128, # '¼' + 189: 127, # '½' + 190: 126, # '¾' + 191: 125, # '¿' + 192: 124, # 'À' + 193: 104, # 'Á' + 194: 73, # 'Â' + 195: 99, # 'Ã' + 196: 79, # 'Ä' + 197: 85, # 'Å' + 198: 123, # 'Æ' + 199: 54, # 'Ç' + 200: 122, # 'È' + 201: 98, # 'É' + 202: 92, # 'Ê' + 203: 121, # 'Ë' + 204: 120, # 'Ì' + 205: 91, # 'Í' + 206: 103, # 'Î' + 207: 119, # 'Ï' + 208: 68, # 'Ğ' + 209: 118, # 'Ñ' + 210: 117, # 'Ò' + 211: 97, # 'Ó' + 212: 116, # 'Ô' + 213: 115, # 'Õ' + 214: 50, # 'Ö' + 215: 90, # '×' + 216: 114, # 'Ø' + 217: 113, # 'Ù' + 218: 112, # 'Ú' + 219: 111, # 'Û' + 220: 55, # 'Ü' + 221: 41, # 'İ' + 222: 40, # 'Ş' + 223: 86, # 'ß' + 224: 89, # 'à' + 225: 70, # 'á' + 226: 59, # 'â' + 227: 78, # 'ã' + 228: 71, # 'ä' + 229: 82, # 'å' + 230: 88, # 'æ' + 231: 33, # 'ç' + 232: 77, # 'è' + 233: 66, # 'é' + 234: 84, # 'ê' + 235: 83, # 'ë' + 236: 110, # 'ì' + 237: 75, # 'í' + 238: 61, # 'î' + 239: 96, # 'ï' + 240: 30, # 'ğ' + 241: 67, # 'ñ' + 242: 109, # 'ò' + 243: 74, # 'ó' + 244: 87, # 'ô' + 245: 102, # 'õ' + 246: 34, # 'ö' + 247: 95, # '÷' + 248: 81, # 'ø' + 249: 108, # 'ù' + 250: 76, # 'ú' + 251: 72, # 'û' + 252: 17, # 'ü' + 253: 6, # 'ı' + 254: 19, # 'ş' + 255: 107, # 'ÿ' +} + +ISO_8859_9_TURKISH_MODEL = SingleByteCharSetModel(charset_name='ISO-8859-9', + language='Turkish', + char_to_order_map=ISO_8859_9_TURKISH_CHAR_TO_ORDER, + language_model=TURKISH_LANG_MODEL, + typical_positive_ratio=0.97029, + keep_ascii_letters=True, + alphabet='ABCDEFGHIJKLMNOPRSTUVYZabcdefghijklmnoprstuvyzÂÇÎÖÛÜâçîöûüĞğİıŞş') + diff --git a/openpype/vendor/python/python_2/chardet/latin1prober.py b/openpype/vendor/python/python_2/chardet/latin1prober.py new file mode 100644 index 0000000000..7d1e8c20fb --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/latin1prober.py @@ -0,0 +1,145 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetprober import CharSetProber +from .enums import ProbingState + +FREQ_CAT_NUM = 4 + +UDF = 0 # undefined +OTH = 1 # other +ASC = 2 # ascii capital letter +ASS = 3 # ascii small letter +ACV = 4 # accent capital vowel +ACO = 5 # accent capital other +ASV = 6 # accent small vowel +ASO = 7 # accent small other +CLASS_NUM = 8 # total classes + +Latin1_CharToClass = ( + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 00 - 07 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 08 - 0F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 10 - 17 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 18 - 1F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 20 - 27 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 28 - 2F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 30 - 37 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 38 - 3F + OTH, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 40 - 47 + ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 48 - 4F + ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 50 - 57 + ASC, ASC, ASC, OTH, OTH, OTH, OTH, OTH, # 58 - 5F + OTH, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 60 - 67 + ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 68 - 6F + ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 70 - 77 + ASS, ASS, ASS, OTH, OTH, OTH, OTH, OTH, # 78 - 7F + OTH, UDF, OTH, ASO, OTH, OTH, OTH, OTH, # 80 - 87 + OTH, OTH, ACO, OTH, ACO, UDF, ACO, UDF, # 88 - 8F + UDF, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 90 - 97 + OTH, OTH, ASO, OTH, ASO, UDF, ASO, ACO, # 98 - 9F + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A0 - A7 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A8 - AF + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B0 - B7 + OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B8 - BF + ACV, ACV, ACV, ACV, ACV, ACV, ACO, ACO, # C0 - C7 + ACV, ACV, ACV, ACV, ACV, ACV, ACV, ACV, # C8 - CF + ACO, ACO, ACV, ACV, ACV, ACV, ACV, OTH, # D0 - D7 + ACV, ACV, ACV, ACV, ACV, ACO, ACO, ACO, # D8 - DF + ASV, ASV, ASV, ASV, ASV, ASV, ASO, ASO, # E0 - E7 + ASV, ASV, ASV, ASV, ASV, ASV, ASV, ASV, # E8 - EF + ASO, ASO, ASV, ASV, ASV, ASV, ASV, OTH, # F0 - F7 + ASV, ASV, ASV, ASV, ASV, ASO, ASO, ASO, # F8 - FF +) + +# 0 : illegal +# 1 : very unlikely +# 2 : normal +# 3 : very likely +Latin1ClassModel = ( +# UDF OTH ASC ASS ACV ACO ASV ASO + 0, 0, 0, 0, 0, 0, 0, 0, # UDF + 0, 3, 3, 3, 3, 3, 3, 3, # OTH + 0, 3, 3, 3, 3, 3, 3, 3, # ASC + 0, 3, 3, 3, 1, 1, 3, 3, # ASS + 0, 3, 3, 3, 1, 2, 1, 2, # ACV + 0, 3, 3, 3, 3, 3, 3, 3, # ACO + 0, 3, 1, 3, 1, 1, 1, 3, # ASV + 0, 3, 1, 3, 1, 1, 3, 3, # ASO +) + + +class Latin1Prober(CharSetProber): + def __init__(self): + super(Latin1Prober, self).__init__() + self._last_char_class = None + self._freq_counter = None + self.reset() + + def reset(self): + self._last_char_class = OTH + self._freq_counter = [0] * FREQ_CAT_NUM + CharSetProber.reset(self) + + @property + def charset_name(self): + return "ISO-8859-1" + + @property + def language(self): + return "" + + def feed(self, byte_str): + byte_str = self.filter_with_english_letters(byte_str) + for c in byte_str: + char_class = Latin1_CharToClass[c] + freq = Latin1ClassModel[(self._last_char_class * CLASS_NUM) + + char_class] + if freq == 0: + self._state = ProbingState.NOT_ME + break + self._freq_counter[freq] += 1 + self._last_char_class = char_class + + return self.state + + def get_confidence(self): + if self.state == ProbingState.NOT_ME: + return 0.01 + + total = sum(self._freq_counter) + if total < 0.01: + confidence = 0.0 + else: + confidence = ((self._freq_counter[3] - self._freq_counter[1] * 20.0) + / total) + if confidence < 0.0: + confidence = 0.0 + # lower the confidence of latin1 so that other more accurate + # detector can take priority. + confidence = confidence * 0.73 + return confidence diff --git a/openpype/vendor/python/python_2/chardet/mbcharsetprober.py b/openpype/vendor/python/python_2/chardet/mbcharsetprober.py new file mode 100644 index 0000000000..6256ecfd1e --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/mbcharsetprober.py @@ -0,0 +1,91 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# Proofpoint, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetprober import CharSetProber +from .enums import ProbingState, MachineState + + +class MultiByteCharSetProber(CharSetProber): + """ + MultiByteCharSetProber + """ + + def __init__(self, lang_filter=None): + super(MultiByteCharSetProber, self).__init__(lang_filter=lang_filter) + self.distribution_analyzer = None + self.coding_sm = None + self._last_char = [0, 0] + + def reset(self): + super(MultiByteCharSetProber, self).reset() + if self.coding_sm: + self.coding_sm.reset() + if self.distribution_analyzer: + self.distribution_analyzer.reset() + self._last_char = [0, 0] + + @property + def charset_name(self): + raise NotImplementedError + + @property + def language(self): + raise NotImplementedError + + def feed(self, byte_str): + for i in range(len(byte_str)): + coding_state = self.coding_sm.next_state(byte_str[i]) + if coding_state == MachineState.ERROR: + self.logger.debug('%s %s prober hit error at byte %s', + self.charset_name, self.language, i) + self._state = ProbingState.NOT_ME + break + elif coding_state == MachineState.ITS_ME: + self._state = ProbingState.FOUND_IT + break + elif coding_state == MachineState.START: + char_len = self.coding_sm.get_current_charlen() + if i == 0: + self._last_char[1] = byte_str[0] + self.distribution_analyzer.feed(self._last_char, char_len) + else: + self.distribution_analyzer.feed(byte_str[i - 1:i + 1], + char_len) + + self._last_char[0] = byte_str[-1] + + if self.state == ProbingState.DETECTING: + if (self.distribution_analyzer.got_enough_data() and + (self.get_confidence() > self.SHORTCUT_THRESHOLD)): + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self): + return self.distribution_analyzer.get_confidence() diff --git a/openpype/vendor/python/python_2/chardet/mbcsgroupprober.py b/openpype/vendor/python/python_2/chardet/mbcsgroupprober.py new file mode 100644 index 0000000000..530abe75e0 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/mbcsgroupprober.py @@ -0,0 +1,54 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# Proofpoint, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetgroupprober import CharSetGroupProber +from .utf8prober import UTF8Prober +from .sjisprober import SJISProber +from .eucjpprober import EUCJPProber +from .gb2312prober import GB2312Prober +from .euckrprober import EUCKRProber +from .cp949prober import CP949Prober +from .big5prober import Big5Prober +from .euctwprober import EUCTWProber + + +class MBCSGroupProber(CharSetGroupProber): + def __init__(self, lang_filter=None): + super(MBCSGroupProber, self).__init__(lang_filter=lang_filter) + self.probers = [ + UTF8Prober(), + SJISProber(), + EUCJPProber(), + GB2312Prober(), + EUCKRProber(), + CP949Prober(), + Big5Prober(), + EUCTWProber() + ] + self.reset() diff --git a/openpype/vendor/python/python_2/chardet/mbcssm.py b/openpype/vendor/python/python_2/chardet/mbcssm.py new file mode 100644 index 0000000000..8360d0f284 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/mbcssm.py @@ -0,0 +1,572 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .enums import MachineState + +# BIG5 + +BIG5_CLS = ( + 1,1,1,1,1,1,1,1, # 00 - 07 #allow 0x00 as legal value + 1,1,1,1,1,1,0,0, # 08 - 0f + 1,1,1,1,1,1,1,1, # 10 - 17 + 1,1,1,0,1,1,1,1, # 18 - 1f + 1,1,1,1,1,1,1,1, # 20 - 27 + 1,1,1,1,1,1,1,1, # 28 - 2f + 1,1,1,1,1,1,1,1, # 30 - 37 + 1,1,1,1,1,1,1,1, # 38 - 3f + 2,2,2,2,2,2,2,2, # 40 - 47 + 2,2,2,2,2,2,2,2, # 48 - 4f + 2,2,2,2,2,2,2,2, # 50 - 57 + 2,2,2,2,2,2,2,2, # 58 - 5f + 2,2,2,2,2,2,2,2, # 60 - 67 + 2,2,2,2,2,2,2,2, # 68 - 6f + 2,2,2,2,2,2,2,2, # 70 - 77 + 2,2,2,2,2,2,2,1, # 78 - 7f + 4,4,4,4,4,4,4,4, # 80 - 87 + 4,4,4,4,4,4,4,4, # 88 - 8f + 4,4,4,4,4,4,4,4, # 90 - 97 + 4,4,4,4,4,4,4,4, # 98 - 9f + 4,3,3,3,3,3,3,3, # a0 - a7 + 3,3,3,3,3,3,3,3, # a8 - af + 3,3,3,3,3,3,3,3, # b0 - b7 + 3,3,3,3,3,3,3,3, # b8 - bf + 3,3,3,3,3,3,3,3, # c0 - c7 + 3,3,3,3,3,3,3,3, # c8 - cf + 3,3,3,3,3,3,3,3, # d0 - d7 + 3,3,3,3,3,3,3,3, # d8 - df + 3,3,3,3,3,3,3,3, # e0 - e7 + 3,3,3,3,3,3,3,3, # e8 - ef + 3,3,3,3,3,3,3,3, # f0 - f7 + 3,3,3,3,3,3,3,0 # f8 - ff +) + +BIG5_ST = ( + MachineState.ERROR,MachineState.START,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,#08-0f + MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START#10-17 +) + +BIG5_CHAR_LEN_TABLE = (0, 1, 1, 2, 0) + +BIG5_SM_MODEL = {'class_table': BIG5_CLS, + 'class_factor': 5, + 'state_table': BIG5_ST, + 'char_len_table': BIG5_CHAR_LEN_TABLE, + 'name': 'Big5'} + +# CP949 + +CP949_CLS = ( + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,0,0, # 00 - 0f + 1,1,1,1,1,1,1,1, 1,1,1,0,1,1,1,1, # 10 - 1f + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, # 20 - 2f + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, # 30 - 3f + 1,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4, # 40 - 4f + 4,4,5,5,5,5,5,5, 5,5,5,1,1,1,1,1, # 50 - 5f + 1,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5, # 60 - 6f + 5,5,5,5,5,5,5,5, 5,5,5,1,1,1,1,1, # 70 - 7f + 0,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6, # 80 - 8f + 6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6, # 90 - 9f + 6,7,7,7,7,7,7,7, 7,7,7,7,7,8,8,8, # a0 - af + 7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7, # b0 - bf + 7,7,7,7,7,7,9,2, 2,3,2,2,2,2,2,2, # c0 - cf + 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, # d0 - df + 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, # e0 - ef + 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,0, # f0 - ff +) + +CP949_ST = ( +#cls= 0 1 2 3 4 5 6 7 8 9 # previous state = + MachineState.ERROR,MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START, 4, 5,MachineState.ERROR, 6, # MachineState.START + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, # MachineState.ERROR + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME, # MachineState.ITS_ME + MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START, # 3 + MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, # 4 + MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, # 5 + MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START, # 6 +) + +CP949_CHAR_LEN_TABLE = (0, 1, 2, 0, 1, 1, 2, 2, 0, 2) + +CP949_SM_MODEL = {'class_table': CP949_CLS, + 'class_factor': 10, + 'state_table': CP949_ST, + 'char_len_table': CP949_CHAR_LEN_TABLE, + 'name': 'CP949'} + +# EUC-JP + +EUCJP_CLS = ( + 4,4,4,4,4,4,4,4, # 00 - 07 + 4,4,4,4,4,4,5,5, # 08 - 0f + 4,4,4,4,4,4,4,4, # 10 - 17 + 4,4,4,5,4,4,4,4, # 18 - 1f + 4,4,4,4,4,4,4,4, # 20 - 27 + 4,4,4,4,4,4,4,4, # 28 - 2f + 4,4,4,4,4,4,4,4, # 30 - 37 + 4,4,4,4,4,4,4,4, # 38 - 3f + 4,4,4,4,4,4,4,4, # 40 - 47 + 4,4,4,4,4,4,4,4, # 48 - 4f + 4,4,4,4,4,4,4,4, # 50 - 57 + 4,4,4,4,4,4,4,4, # 58 - 5f + 4,4,4,4,4,4,4,4, # 60 - 67 + 4,4,4,4,4,4,4,4, # 68 - 6f + 4,4,4,4,4,4,4,4, # 70 - 77 + 4,4,4,4,4,4,4,4, # 78 - 7f + 5,5,5,5,5,5,5,5, # 80 - 87 + 5,5,5,5,5,5,1,3, # 88 - 8f + 5,5,5,5,5,5,5,5, # 90 - 97 + 5,5,5,5,5,5,5,5, # 98 - 9f + 5,2,2,2,2,2,2,2, # a0 - a7 + 2,2,2,2,2,2,2,2, # a8 - af + 2,2,2,2,2,2,2,2, # b0 - b7 + 2,2,2,2,2,2,2,2, # b8 - bf + 2,2,2,2,2,2,2,2, # c0 - c7 + 2,2,2,2,2,2,2,2, # c8 - cf + 2,2,2,2,2,2,2,2, # d0 - d7 + 2,2,2,2,2,2,2,2, # d8 - df + 0,0,0,0,0,0,0,0, # e0 - e7 + 0,0,0,0,0,0,0,0, # e8 - ef + 0,0,0,0,0,0,0,0, # f0 - f7 + 0,0,0,0,0,0,0,5 # f8 - ff +) + +EUCJP_ST = ( + 3, 4, 3, 5,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.START,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#10-17 + MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 3,MachineState.ERROR,#18-1f + 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START#20-27 +) + +EUCJP_CHAR_LEN_TABLE = (2, 2, 2, 3, 1, 0) + +EUCJP_SM_MODEL = {'class_table': EUCJP_CLS, + 'class_factor': 6, + 'state_table': EUCJP_ST, + 'char_len_table': EUCJP_CHAR_LEN_TABLE, + 'name': 'EUC-JP'} + +# EUC-KR + +EUCKR_CLS = ( + 1,1,1,1,1,1,1,1, # 00 - 07 + 1,1,1,1,1,1,0,0, # 08 - 0f + 1,1,1,1,1,1,1,1, # 10 - 17 + 1,1,1,0,1,1,1,1, # 18 - 1f + 1,1,1,1,1,1,1,1, # 20 - 27 + 1,1,1,1,1,1,1,1, # 28 - 2f + 1,1,1,1,1,1,1,1, # 30 - 37 + 1,1,1,1,1,1,1,1, # 38 - 3f + 1,1,1,1,1,1,1,1, # 40 - 47 + 1,1,1,1,1,1,1,1, # 48 - 4f + 1,1,1,1,1,1,1,1, # 50 - 57 + 1,1,1,1,1,1,1,1, # 58 - 5f + 1,1,1,1,1,1,1,1, # 60 - 67 + 1,1,1,1,1,1,1,1, # 68 - 6f + 1,1,1,1,1,1,1,1, # 70 - 77 + 1,1,1,1,1,1,1,1, # 78 - 7f + 0,0,0,0,0,0,0,0, # 80 - 87 + 0,0,0,0,0,0,0,0, # 88 - 8f + 0,0,0,0,0,0,0,0, # 90 - 97 + 0,0,0,0,0,0,0,0, # 98 - 9f + 0,2,2,2,2,2,2,2, # a0 - a7 + 2,2,2,2,2,3,3,3, # a8 - af + 2,2,2,2,2,2,2,2, # b0 - b7 + 2,2,2,2,2,2,2,2, # b8 - bf + 2,2,2,2,2,2,2,2, # c0 - c7 + 2,3,2,2,2,2,2,2, # c8 - cf + 2,2,2,2,2,2,2,2, # d0 - d7 + 2,2,2,2,2,2,2,2, # d8 - df + 2,2,2,2,2,2,2,2, # e0 - e7 + 2,2,2,2,2,2,2,2, # e8 - ef + 2,2,2,2,2,2,2,2, # f0 - f7 + 2,2,2,2,2,2,2,0 # f8 - ff +) + +EUCKR_ST = ( + MachineState.ERROR,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START #08-0f +) + +EUCKR_CHAR_LEN_TABLE = (0, 1, 2, 0) + +EUCKR_SM_MODEL = {'class_table': EUCKR_CLS, + 'class_factor': 4, + 'state_table': EUCKR_ST, + 'char_len_table': EUCKR_CHAR_LEN_TABLE, + 'name': 'EUC-KR'} + +# EUC-TW + +EUCTW_CLS = ( + 2,2,2,2,2,2,2,2, # 00 - 07 + 2,2,2,2,2,2,0,0, # 08 - 0f + 2,2,2,2,2,2,2,2, # 10 - 17 + 2,2,2,0,2,2,2,2, # 18 - 1f + 2,2,2,2,2,2,2,2, # 20 - 27 + 2,2,2,2,2,2,2,2, # 28 - 2f + 2,2,2,2,2,2,2,2, # 30 - 37 + 2,2,2,2,2,2,2,2, # 38 - 3f + 2,2,2,2,2,2,2,2, # 40 - 47 + 2,2,2,2,2,2,2,2, # 48 - 4f + 2,2,2,2,2,2,2,2, # 50 - 57 + 2,2,2,2,2,2,2,2, # 58 - 5f + 2,2,2,2,2,2,2,2, # 60 - 67 + 2,2,2,2,2,2,2,2, # 68 - 6f + 2,2,2,2,2,2,2,2, # 70 - 77 + 2,2,2,2,2,2,2,2, # 78 - 7f + 0,0,0,0,0,0,0,0, # 80 - 87 + 0,0,0,0,0,0,6,0, # 88 - 8f + 0,0,0,0,0,0,0,0, # 90 - 97 + 0,0,0,0,0,0,0,0, # 98 - 9f + 0,3,4,4,4,4,4,4, # a0 - a7 + 5,5,1,1,1,1,1,1, # a8 - af + 1,1,1,1,1,1,1,1, # b0 - b7 + 1,1,1,1,1,1,1,1, # b8 - bf + 1,1,3,1,3,3,3,3, # c0 - c7 + 3,3,3,3,3,3,3,3, # c8 - cf + 3,3,3,3,3,3,3,3, # d0 - d7 + 3,3,3,3,3,3,3,3, # d8 - df + 3,3,3,3,3,3,3,3, # e0 - e7 + 3,3,3,3,3,3,3,3, # e8 - ef + 3,3,3,3,3,3,3,3, # f0 - f7 + 3,3,3,3,3,3,3,0 # f8 - ff +) + +EUCTW_ST = ( + MachineState.ERROR,MachineState.ERROR,MachineState.START, 3, 3, 3, 4,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.START,MachineState.ERROR,#10-17 + MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f + 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.START,MachineState.START,#20-27 + MachineState.START,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START #28-2f +) + +EUCTW_CHAR_LEN_TABLE = (0, 0, 1, 2, 2, 2, 3) + +EUCTW_SM_MODEL = {'class_table': EUCTW_CLS, + 'class_factor': 7, + 'state_table': EUCTW_ST, + 'char_len_table': EUCTW_CHAR_LEN_TABLE, + 'name': 'x-euc-tw'} + +# GB2312 + +GB2312_CLS = ( + 1,1,1,1,1,1,1,1, # 00 - 07 + 1,1,1,1,1,1,0,0, # 08 - 0f + 1,1,1,1,1,1,1,1, # 10 - 17 + 1,1,1,0,1,1,1,1, # 18 - 1f + 1,1,1,1,1,1,1,1, # 20 - 27 + 1,1,1,1,1,1,1,1, # 28 - 2f + 3,3,3,3,3,3,3,3, # 30 - 37 + 3,3,1,1,1,1,1,1, # 38 - 3f + 2,2,2,2,2,2,2,2, # 40 - 47 + 2,2,2,2,2,2,2,2, # 48 - 4f + 2,2,2,2,2,2,2,2, # 50 - 57 + 2,2,2,2,2,2,2,2, # 58 - 5f + 2,2,2,2,2,2,2,2, # 60 - 67 + 2,2,2,2,2,2,2,2, # 68 - 6f + 2,2,2,2,2,2,2,2, # 70 - 77 + 2,2,2,2,2,2,2,4, # 78 - 7f + 5,6,6,6,6,6,6,6, # 80 - 87 + 6,6,6,6,6,6,6,6, # 88 - 8f + 6,6,6,6,6,6,6,6, # 90 - 97 + 6,6,6,6,6,6,6,6, # 98 - 9f + 6,6,6,6,6,6,6,6, # a0 - a7 + 6,6,6,6,6,6,6,6, # a8 - af + 6,6,6,6,6,6,6,6, # b0 - b7 + 6,6,6,6,6,6,6,6, # b8 - bf + 6,6,6,6,6,6,6,6, # c0 - c7 + 6,6,6,6,6,6,6,6, # c8 - cf + 6,6,6,6,6,6,6,6, # d0 - d7 + 6,6,6,6,6,6,6,6, # d8 - df + 6,6,6,6,6,6,6,6, # e0 - e7 + 6,6,6,6,6,6,6,6, # e8 - ef + 6,6,6,6,6,6,6,6, # f0 - f7 + 6,6,6,6,6,6,6,0 # f8 - ff +) + +GB2312_ST = ( + MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, 3,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,#10-17 + 4,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f + MachineState.ERROR,MachineState.ERROR, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,#20-27 + MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START #28-2f +) + +# To be accurate, the length of class 6 can be either 2 or 4. +# But it is not necessary to discriminate between the two since +# it is used for frequency analysis only, and we are validating +# each code range there as well. So it is safe to set it to be +# 2 here. +GB2312_CHAR_LEN_TABLE = (0, 1, 1, 1, 1, 1, 2) + +GB2312_SM_MODEL = {'class_table': GB2312_CLS, + 'class_factor': 7, + 'state_table': GB2312_ST, + 'char_len_table': GB2312_CHAR_LEN_TABLE, + 'name': 'GB2312'} + +# Shift_JIS + +SJIS_CLS = ( + 1,1,1,1,1,1,1,1, # 00 - 07 + 1,1,1,1,1,1,0,0, # 08 - 0f + 1,1,1,1,1,1,1,1, # 10 - 17 + 1,1,1,0,1,1,1,1, # 18 - 1f + 1,1,1,1,1,1,1,1, # 20 - 27 + 1,1,1,1,1,1,1,1, # 28 - 2f + 1,1,1,1,1,1,1,1, # 30 - 37 + 1,1,1,1,1,1,1,1, # 38 - 3f + 2,2,2,2,2,2,2,2, # 40 - 47 + 2,2,2,2,2,2,2,2, # 48 - 4f + 2,2,2,2,2,2,2,2, # 50 - 57 + 2,2,2,2,2,2,2,2, # 58 - 5f + 2,2,2,2,2,2,2,2, # 60 - 67 + 2,2,2,2,2,2,2,2, # 68 - 6f + 2,2,2,2,2,2,2,2, # 70 - 77 + 2,2,2,2,2,2,2,1, # 78 - 7f + 3,3,3,3,3,2,2,3, # 80 - 87 + 3,3,3,3,3,3,3,3, # 88 - 8f + 3,3,3,3,3,3,3,3, # 90 - 97 + 3,3,3,3,3,3,3,3, # 98 - 9f + #0xa0 is illegal in sjis encoding, but some pages does + #contain such byte. We need to be more error forgiven. + 2,2,2,2,2,2,2,2, # a0 - a7 + 2,2,2,2,2,2,2,2, # a8 - af + 2,2,2,2,2,2,2,2, # b0 - b7 + 2,2,2,2,2,2,2,2, # b8 - bf + 2,2,2,2,2,2,2,2, # c0 - c7 + 2,2,2,2,2,2,2,2, # c8 - cf + 2,2,2,2,2,2,2,2, # d0 - d7 + 2,2,2,2,2,2,2,2, # d8 - df + 3,3,3,3,3,3,3,3, # e0 - e7 + 3,3,3,3,3,4,4,4, # e8 - ef + 3,3,3,3,3,3,3,3, # f0 - f7 + 3,3,3,3,3,0,0,0) # f8 - ff + + +SJIS_ST = ( + MachineState.ERROR,MachineState.START,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START #10-17 +) + +SJIS_CHAR_LEN_TABLE = (0, 1, 1, 2, 0, 0) + +SJIS_SM_MODEL = {'class_table': SJIS_CLS, + 'class_factor': 6, + 'state_table': SJIS_ST, + 'char_len_table': SJIS_CHAR_LEN_TABLE, + 'name': 'Shift_JIS'} + +# UCS2-BE + +UCS2BE_CLS = ( + 0,0,0,0,0,0,0,0, # 00 - 07 + 0,0,1,0,0,2,0,0, # 08 - 0f + 0,0,0,0,0,0,0,0, # 10 - 17 + 0,0,0,3,0,0,0,0, # 18 - 1f + 0,0,0,0,0,0,0,0, # 20 - 27 + 0,3,3,3,3,3,0,0, # 28 - 2f + 0,0,0,0,0,0,0,0, # 30 - 37 + 0,0,0,0,0,0,0,0, # 38 - 3f + 0,0,0,0,0,0,0,0, # 40 - 47 + 0,0,0,0,0,0,0,0, # 48 - 4f + 0,0,0,0,0,0,0,0, # 50 - 57 + 0,0,0,0,0,0,0,0, # 58 - 5f + 0,0,0,0,0,0,0,0, # 60 - 67 + 0,0,0,0,0,0,0,0, # 68 - 6f + 0,0,0,0,0,0,0,0, # 70 - 77 + 0,0,0,0,0,0,0,0, # 78 - 7f + 0,0,0,0,0,0,0,0, # 80 - 87 + 0,0,0,0,0,0,0,0, # 88 - 8f + 0,0,0,0,0,0,0,0, # 90 - 97 + 0,0,0,0,0,0,0,0, # 98 - 9f + 0,0,0,0,0,0,0,0, # a0 - a7 + 0,0,0,0,0,0,0,0, # a8 - af + 0,0,0,0,0,0,0,0, # b0 - b7 + 0,0,0,0,0,0,0,0, # b8 - bf + 0,0,0,0,0,0,0,0, # c0 - c7 + 0,0,0,0,0,0,0,0, # c8 - cf + 0,0,0,0,0,0,0,0, # d0 - d7 + 0,0,0,0,0,0,0,0, # d8 - df + 0,0,0,0,0,0,0,0, # e0 - e7 + 0,0,0,0,0,0,0,0, # e8 - ef + 0,0,0,0,0,0,0,0, # f0 - f7 + 0,0,0,0,0,0,4,5 # f8 - ff +) + +UCS2BE_ST = ( + 5, 7, 7,MachineState.ERROR, 4, 3,MachineState.ERROR,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME, 6, 6, 6, 6,MachineState.ERROR,MachineState.ERROR,#10-17 + 6, 6, 6, 6, 6,MachineState.ITS_ME, 6, 6,#18-1f + 6, 6, 6, 6, 5, 7, 7,MachineState.ERROR,#20-27 + 5, 8, 6, 6,MachineState.ERROR, 6, 6, 6,#28-2f + 6, 6, 6, 6,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START #30-37 +) + +UCS2BE_CHAR_LEN_TABLE = (2, 2, 2, 0, 2, 2) + +UCS2BE_SM_MODEL = {'class_table': UCS2BE_CLS, + 'class_factor': 6, + 'state_table': UCS2BE_ST, + 'char_len_table': UCS2BE_CHAR_LEN_TABLE, + 'name': 'UTF-16BE'} + +# UCS2-LE + +UCS2LE_CLS = ( + 0,0,0,0,0,0,0,0, # 00 - 07 + 0,0,1,0,0,2,0,0, # 08 - 0f + 0,0,0,0,0,0,0,0, # 10 - 17 + 0,0,0,3,0,0,0,0, # 18 - 1f + 0,0,0,0,0,0,0,0, # 20 - 27 + 0,3,3,3,3,3,0,0, # 28 - 2f + 0,0,0,0,0,0,0,0, # 30 - 37 + 0,0,0,0,0,0,0,0, # 38 - 3f + 0,0,0,0,0,0,0,0, # 40 - 47 + 0,0,0,0,0,0,0,0, # 48 - 4f + 0,0,0,0,0,0,0,0, # 50 - 57 + 0,0,0,0,0,0,0,0, # 58 - 5f + 0,0,0,0,0,0,0,0, # 60 - 67 + 0,0,0,0,0,0,0,0, # 68 - 6f + 0,0,0,0,0,0,0,0, # 70 - 77 + 0,0,0,0,0,0,0,0, # 78 - 7f + 0,0,0,0,0,0,0,0, # 80 - 87 + 0,0,0,0,0,0,0,0, # 88 - 8f + 0,0,0,0,0,0,0,0, # 90 - 97 + 0,0,0,0,0,0,0,0, # 98 - 9f + 0,0,0,0,0,0,0,0, # a0 - a7 + 0,0,0,0,0,0,0,0, # a8 - af + 0,0,0,0,0,0,0,0, # b0 - b7 + 0,0,0,0,0,0,0,0, # b8 - bf + 0,0,0,0,0,0,0,0, # c0 - c7 + 0,0,0,0,0,0,0,0, # c8 - cf + 0,0,0,0,0,0,0,0, # d0 - d7 + 0,0,0,0,0,0,0,0, # d8 - df + 0,0,0,0,0,0,0,0, # e0 - e7 + 0,0,0,0,0,0,0,0, # e8 - ef + 0,0,0,0,0,0,0,0, # f0 - f7 + 0,0,0,0,0,0,4,5 # f8 - ff +) + +UCS2LE_ST = ( + 6, 6, 7, 6, 4, 3,MachineState.ERROR,MachineState.ERROR,#00-07 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f + MachineState.ITS_ME,MachineState.ITS_ME, 5, 5, 5,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,#10-17 + 5, 5, 5,MachineState.ERROR, 5,MachineState.ERROR, 6, 6,#18-1f + 7, 6, 8, 8, 5, 5, 5,MachineState.ERROR,#20-27 + 5, 5, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 5, 5,#28-2f + 5, 5, 5,MachineState.ERROR, 5,MachineState.ERROR,MachineState.START,MachineState.START #30-37 +) + +UCS2LE_CHAR_LEN_TABLE = (2, 2, 2, 2, 2, 2) + +UCS2LE_SM_MODEL = {'class_table': UCS2LE_CLS, + 'class_factor': 6, + 'state_table': UCS2LE_ST, + 'char_len_table': UCS2LE_CHAR_LEN_TABLE, + 'name': 'UTF-16LE'} + +# UTF-8 + +UTF8_CLS = ( + 1,1,1,1,1,1,1,1, # 00 - 07 #allow 0x00 as a legal value + 1,1,1,1,1,1,0,0, # 08 - 0f + 1,1,1,1,1,1,1,1, # 10 - 17 + 1,1,1,0,1,1,1,1, # 18 - 1f + 1,1,1,1,1,1,1,1, # 20 - 27 + 1,1,1,1,1,1,1,1, # 28 - 2f + 1,1,1,1,1,1,1,1, # 30 - 37 + 1,1,1,1,1,1,1,1, # 38 - 3f + 1,1,1,1,1,1,1,1, # 40 - 47 + 1,1,1,1,1,1,1,1, # 48 - 4f + 1,1,1,1,1,1,1,1, # 50 - 57 + 1,1,1,1,1,1,1,1, # 58 - 5f + 1,1,1,1,1,1,1,1, # 60 - 67 + 1,1,1,1,1,1,1,1, # 68 - 6f + 1,1,1,1,1,1,1,1, # 70 - 77 + 1,1,1,1,1,1,1,1, # 78 - 7f + 2,2,2,2,3,3,3,3, # 80 - 87 + 4,4,4,4,4,4,4,4, # 88 - 8f + 4,4,4,4,4,4,4,4, # 90 - 97 + 4,4,4,4,4,4,4,4, # 98 - 9f + 5,5,5,5,5,5,5,5, # a0 - a7 + 5,5,5,5,5,5,5,5, # a8 - af + 5,5,5,5,5,5,5,5, # b0 - b7 + 5,5,5,5,5,5,5,5, # b8 - bf + 0,0,6,6,6,6,6,6, # c0 - c7 + 6,6,6,6,6,6,6,6, # c8 - cf + 6,6,6,6,6,6,6,6, # d0 - d7 + 6,6,6,6,6,6,6,6, # d8 - df + 7,8,8,8,8,8,8,8, # e0 - e7 + 8,8,8,8,8,9,8,8, # e8 - ef + 10,11,11,11,11,11,11,11, # f0 - f7 + 12,13,13,13,14,15,0,0 # f8 - ff +) + +UTF8_ST = ( + MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 12, 10,#00-07 + 9, 11, 8, 7, 6, 5, 4, 3,#08-0f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#10-17 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#20-27 + MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#28-2f + MachineState.ERROR,MachineState.ERROR, 5, 5, 5, 5,MachineState.ERROR,MachineState.ERROR,#30-37 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#38-3f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 5, 5, 5,MachineState.ERROR,MachineState.ERROR,#40-47 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#48-4f + MachineState.ERROR,MachineState.ERROR, 7, 7, 7, 7,MachineState.ERROR,MachineState.ERROR,#50-57 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#58-5f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 7, 7,MachineState.ERROR,MachineState.ERROR,#60-67 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#68-6f + MachineState.ERROR,MachineState.ERROR, 9, 9, 9, 9,MachineState.ERROR,MachineState.ERROR,#70-77 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#78-7f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 9,MachineState.ERROR,MachineState.ERROR,#80-87 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#88-8f + MachineState.ERROR,MachineState.ERROR, 12, 12, 12, 12,MachineState.ERROR,MachineState.ERROR,#90-97 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#98-9f + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 12,MachineState.ERROR,MachineState.ERROR,#a0-a7 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#a8-af + MachineState.ERROR,MachineState.ERROR, 12, 12, 12,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#b0-b7 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#b8-bf + MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,#c0-c7 + MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR #c8-cf +) + +UTF8_CHAR_LEN_TABLE = (0, 1, 0, 0, 0, 0, 2, 3, 3, 3, 4, 4, 5, 5, 6, 6) + +UTF8_SM_MODEL = {'class_table': UTF8_CLS, + 'class_factor': 16, + 'state_table': UTF8_ST, + 'char_len_table': UTF8_CHAR_LEN_TABLE, + 'name': 'UTF-8'} diff --git a/openpype/vendor/python/python_2/chardet/metadata/__init__.py b/openpype/vendor/python/python_2/chardet/metadata/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/vendor/python/python_2/chardet/metadata/languages.py b/openpype/vendor/python/python_2/chardet/metadata/languages.py new file mode 100644 index 0000000000..3237d5abf6 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/metadata/languages.py @@ -0,0 +1,310 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Metadata about languages used by our model training code for our +SingleByteCharSetProbers. Could be used for other things in the future. + +This code is based on the language metadata from the uchardet project. +""" +from __future__ import absolute_import, print_function + +from string import ascii_letters + + +# TODO: Add Ukranian (KOI8-U) + +class Language(object): + """Metadata about a language useful for training models + + :ivar name: The human name for the language, in English. + :type name: str + :ivar iso_code: 2-letter ISO 639-1 if possible, 3-letter ISO code otherwise, + or use another catalog as a last resort. + :type iso_code: str + :ivar use_ascii: Whether or not ASCII letters should be included in trained + models. + :type use_ascii: bool + :ivar charsets: The charsets we want to support and create data for. + :type charsets: list of str + :ivar alphabet: The characters in the language's alphabet. If `use_ascii` is + `True`, you only need to add those not in the ASCII set. + :type alphabet: str + :ivar wiki_start_pages: The Wikipedia pages to start from if we're crawling + Wikipedia for training data. + :type wiki_start_pages: list of str + """ + def __init__(self, name=None, iso_code=None, use_ascii=True, charsets=None, + alphabet=None, wiki_start_pages=None): + super(Language, self).__init__() + self.name = name + self.iso_code = iso_code + self.use_ascii = use_ascii + self.charsets = charsets + if self.use_ascii: + if alphabet: + alphabet += ascii_letters + else: + alphabet = ascii_letters + elif not alphabet: + raise ValueError('Must supply alphabet if use_ascii is False') + self.alphabet = ''.join(sorted(set(alphabet))) if alphabet else None + self.wiki_start_pages = wiki_start_pages + + def __repr__(self): + return '{}({})'.format(self.__class__.__name__, + ', '.join('{}={!r}'.format(k, v) + for k, v in self.__dict__.items() + if not k.startswith('_'))) + + +LANGUAGES = {'Arabic': Language(name='Arabic', + iso_code='ar', + use_ascii=False, + # We only support encodings that use isolated + # forms, because the current recommendation is + # that the rendering system handles presentation + # forms. This means we purposefully skip IBM864. + charsets=['ISO-8859-6', 'WINDOWS-1256', + 'CP720', 'CP864'], + alphabet=u'ءآأؤإئابةتثجحخدذرزسشصضطظعغػؼؽؾؿـفقكلمنهوىيًٌٍَُِّ', + wiki_start_pages=[u'الصفحة_الرئيسية']), + 'Belarusian': Language(name='Belarusian', + iso_code='be', + use_ascii=False, + charsets=['ISO-8859-5', 'WINDOWS-1251', + 'IBM866', 'MacCyrillic'], + alphabet=(u'АБВГДЕЁЖЗІЙКЛМНОПРСТУЎФХЦЧШЫЬЭЮЯ' + u'абвгдеёжзійклмнопрстуўфхцчшыьэюяʼ'), + wiki_start_pages=[u'Галоўная_старонка']), + 'Bulgarian': Language(name='Bulgarian', + iso_code='bg', + use_ascii=False, + charsets=['ISO-8859-5', 'WINDOWS-1251', + 'IBM855'], + alphabet=(u'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЮЯ' + u'абвгдежзийклмнопрстуфхцчшщъьюя'), + wiki_start_pages=[u'Начална_страница']), + 'Czech': Language(name='Czech', + iso_code='cz', + use_ascii=True, + charsets=['ISO-8859-2', 'WINDOWS-1250'], + alphabet=u'áčďéěíňóřšťúůýžÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ', + wiki_start_pages=[u'Hlavní_strana']), + 'Danish': Language(name='Danish', + iso_code='da', + use_ascii=True, + charsets=['ISO-8859-1', 'ISO-8859-15', + 'WINDOWS-1252'], + alphabet=u'æøåÆØÅ', + wiki_start_pages=[u'Forside']), + 'German': Language(name='German', + iso_code='de', + use_ascii=True, + charsets=['ISO-8859-1', 'WINDOWS-1252'], + alphabet=u'äöüßÄÖÜ', + wiki_start_pages=[u'Wikipedia:Hauptseite']), + 'Greek': Language(name='Greek', + iso_code='el', + use_ascii=False, + charsets=['ISO-8859-7', 'WINDOWS-1253'], + alphabet=(u'αβγδεζηθικλμνξοπρσςτυφχψωάέήίόύώ' + u'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΣΤΥΦΧΨΩΆΈΉΊΌΎΏ'), + wiki_start_pages=[u'Πύλη:Κύρια']), + 'English': Language(name='English', + iso_code='en', + use_ascii=True, + charsets=['ISO-8859-1', 'WINDOWS-1252'], + wiki_start_pages=[u'Main_Page']), + 'Esperanto': Language(name='Esperanto', + iso_code='eo', + # Q, W, X, and Y not used at all + use_ascii=False, + charsets=['ISO-8859-3'], + alphabet=(u'abcĉdefgĝhĥijĵklmnoprsŝtuŭvz' + u'ABCĈDEFGĜHĤIJĴKLMNOPRSŜTUŬVZ'), + wiki_start_pages=[u'Vikipedio:Ĉefpaĝo']), + 'Spanish': Language(name='Spanish', + iso_code='es', + use_ascii=True, + charsets=['ISO-8859-1', 'ISO-8859-15', + 'WINDOWS-1252'], + alphabet=u'ñáéíóúüÑÁÉÍÓÚÜ', + wiki_start_pages=[u'Wikipedia:Portada']), + 'Estonian': Language(name='Estonian', + iso_code='et', + use_ascii=False, + charsets=['ISO-8859-4', 'ISO-8859-13', + 'WINDOWS-1257'], + # C, F, Š, Q, W, X, Y, Z, Ž are only for + # loanwords + alphabet=(u'ABDEGHIJKLMNOPRSTUVÕÄÖÜ' + u'abdeghijklmnoprstuvõäöü'), + wiki_start_pages=[u'Esileht']), + 'Finnish': Language(name='Finnish', + iso_code='fi', + use_ascii=True, + charsets=['ISO-8859-1', 'ISO-8859-15', + 'WINDOWS-1252'], + alphabet=u'ÅÄÖŠŽåäöšž', + wiki_start_pages=[u'Wikipedia:Etusivu']), + 'French': Language(name='French', + iso_code='fr', + use_ascii=True, + charsets=['ISO-8859-1', 'ISO-8859-15', + 'WINDOWS-1252'], + alphabet=u'œàâçèéîïùûêŒÀÂÇÈÉÎÏÙÛÊ', + wiki_start_pages=[u'Wikipédia:Accueil_principal', + u'Bœuf (animal)']), + 'Hebrew': Language(name='Hebrew', + iso_code='he', + use_ascii=False, + charsets=['ISO-8859-8', 'WINDOWS-1255'], + alphabet=u'אבגדהוזחטיךכלםמןנסעףפץצקרשתװױײ', + wiki_start_pages=[u'עמוד_ראשי']), + 'Croatian': Language(name='Croatian', + iso_code='hr', + # Q, W, X, Y are only used for foreign words. + use_ascii=False, + charsets=['ISO-8859-2', 'WINDOWS-1250'], + alphabet=(u'abcčćdđefghijklmnoprsštuvzž' + u'ABCČĆDĐEFGHIJKLMNOPRSŠTUVZŽ'), + wiki_start_pages=[u'Glavna_stranica']), + 'Hungarian': Language(name='Hungarian', + iso_code='hu', + # Q, W, X, Y are only used for foreign words. + use_ascii=False, + charsets=['ISO-8859-2', 'WINDOWS-1250'], + alphabet=(u'abcdefghijklmnoprstuvzáéíóöőúüű' + u'ABCDEFGHIJKLMNOPRSTUVZÁÉÍÓÖŐÚÜŰ'), + wiki_start_pages=[u'Kezdőlap']), + 'Italian': Language(name='Italian', + iso_code='it', + use_ascii=True, + charsets=['ISO-8859-1', 'ISO-8859-15', + 'WINDOWS-1252'], + alphabet=u'ÀÈÉÌÒÓÙàèéìòóù', + wiki_start_pages=[u'Pagina_principale']), + 'Lithuanian': Language(name='Lithuanian', + iso_code='lt', + use_ascii=False, + charsets=['ISO-8859-13', 'WINDOWS-1257', + 'ISO-8859-4'], + # Q, W, and X not used at all + alphabet=(u'AĄBCČDEĘĖFGHIĮYJKLMNOPRSŠTUŲŪVZŽ' + u'aąbcčdeęėfghiįyjklmnoprsštuųūvzž'), + wiki_start_pages=[u'Pagrindinis_puslapis']), + 'Latvian': Language(name='Latvian', + iso_code='lv', + use_ascii=False, + charsets=['ISO-8859-13', 'WINDOWS-1257', + 'ISO-8859-4'], + # Q, W, X, Y are only for loanwords + alphabet=(u'AĀBCČDEĒFGĢHIĪJKĶLĻMNŅOPRSŠTUŪVZŽ' + u'aābcčdeēfgģhiījkķlļmnņoprsštuūvzž'), + wiki_start_pages=[u'Sākumlapa']), + 'Macedonian': Language(name='Macedonian', + iso_code='mk', + use_ascii=False, + charsets=['ISO-8859-5', 'WINDOWS-1251', + 'MacCyrillic', 'IBM855'], + alphabet=(u'АБВГДЃЕЖЗЅИЈКЛЉМНЊОПРСТЌУФХЦЧЏШ' + u'абвгдѓежзѕијклљмнњопрстќуфхцчџш'), + wiki_start_pages=[u'Главна_страница']), + 'Dutch': Language(name='Dutch', + iso_code='nl', + use_ascii=True, + charsets=['ISO-8859-1', 'WINDOWS-1252'], + wiki_start_pages=[u'Hoofdpagina']), + 'Polish': Language(name='Polish', + iso_code='pl', + # Q and X are only used for foreign words. + use_ascii=False, + charsets=['ISO-8859-2', 'WINDOWS-1250'], + alphabet=(u'AĄBCĆDEĘFGHIJKLŁMNŃOÓPRSŚTUWYZŹŻ' + u'aąbcćdeęfghijklłmnńoóprsśtuwyzźż'), + wiki_start_pages=[u'Wikipedia:Strona_główna']), + 'Portuguese': Language(name='Portuguese', + iso_code='pt', + use_ascii=True, + charsets=['ISO-8859-1', 'ISO-8859-15', + 'WINDOWS-1252'], + alphabet=u'ÁÂÃÀÇÉÊÍÓÔÕÚáâãàçéêíóôõú', + wiki_start_pages=[u'Wikipédia:Página_principal']), + 'Romanian': Language(name='Romanian', + iso_code='ro', + use_ascii=True, + charsets=['ISO-8859-2', 'WINDOWS-1250'], + alphabet=u'ăâîșțĂÂÎȘȚ', + wiki_start_pages=[u'Pagina_principală']), + 'Russian': Language(name='Russian', + iso_code='ru', + use_ascii=False, + charsets=['ISO-8859-5', 'WINDOWS-1251', + 'KOI8-R', 'MacCyrillic', 'IBM866', + 'IBM855'], + alphabet=(u'абвгдеёжзийклмнопрстуфхцчшщъыьэюя' + u'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'), + wiki_start_pages=[u'Заглавная_страница']), + 'Slovak': Language(name='Slovak', + iso_code='sk', + use_ascii=True, + charsets=['ISO-8859-2', 'WINDOWS-1250'], + alphabet=u'áäčďéíĺľňóôŕšťúýžÁÄČĎÉÍĹĽŇÓÔŔŠŤÚÝŽ', + wiki_start_pages=[u'Hlavná_stránka']), + 'Slovene': Language(name='Slovene', + iso_code='sl', + # Q, W, X, Y are only used for foreign words. + use_ascii=False, + charsets=['ISO-8859-2', 'WINDOWS-1250'], + alphabet=(u'abcčdefghijklmnoprsštuvzž' + u'ABCČDEFGHIJKLMNOPRSŠTUVZŽ'), + wiki_start_pages=[u'Glavna_stran']), + # Serbian can be written in both Latin and Cyrillic, but there's no + # simple way to get the Latin alphabet pages from Wikipedia through + # the API, so for now we just support Cyrillic. + 'Serbian': Language(name='Serbian', + iso_code='sr', + alphabet=(u'АБВГДЂЕЖЗИЈКЛЉМНЊОПРСТЋУФХЦЧЏШ' + u'абвгдђежзијклљмнњопрстћуфхцчџш'), + charsets=['ISO-8859-5', 'WINDOWS-1251', + 'MacCyrillic', 'IBM855'], + wiki_start_pages=[u'Главна_страна']), + 'Thai': Language(name='Thai', + iso_code='th', + use_ascii=False, + charsets=['ISO-8859-11', 'TIS-620', 'CP874'], + alphabet=u'กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛', + wiki_start_pages=[u'หน้าหลัก']), + 'Turkish': Language(name='Turkish', + iso_code='tr', + # Q, W, and X are not used by Turkish + use_ascii=False, + charsets=['ISO-8859-3', 'ISO-8859-9', + 'WINDOWS-1254'], + alphabet=(u'abcçdefgğhıijklmnoöprsştuüvyzâîû' + u'ABCÇDEFGĞHIİJKLMNOÖPRSŞTUÜVYZÂÎÛ'), + wiki_start_pages=[u'Ana_Sayfa']), + 'Vietnamese': Language(name='Vietnamese', + iso_code='vi', + use_ascii=False, + # Windows-1258 is the only common 8-bit + # Vietnamese encoding supported by Python. + # From Wikipedia: + # For systems that lack support for Unicode, + # dozens of 8-bit Vietnamese code pages are + # available.[1] The most common are VISCII + # (TCVN 5712:1993), VPS, and Windows-1258.[3] + # Where ASCII is required, such as when + # ensuring readability in plain text e-mail, + # Vietnamese letters are often encoded + # according to Vietnamese Quoted-Readable + # (VIQR) or VSCII Mnemonic (VSCII-MNEM),[4] + # though usage of either variable-width + # scheme has declined dramatically following + # the adoption of Unicode on the World Wide + # Web. + charsets=['WINDOWS-1258'], + alphabet=(u'aăâbcdđeêghiklmnoôơpqrstuưvxy' + u'AĂÂBCDĐEÊGHIKLMNOÔƠPQRSTUƯVXY'), + wiki_start_pages=[u'Chữ_Quốc_ngữ']), + } diff --git a/openpype/vendor/python/python_2/chardet/sbcharsetprober.py b/openpype/vendor/python/python_2/chardet/sbcharsetprober.py new file mode 100644 index 0000000000..46ba835c66 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/sbcharsetprober.py @@ -0,0 +1,145 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from collections import namedtuple + +from .charsetprober import CharSetProber +from .enums import CharacterCategory, ProbingState, SequenceLikelihood + + +SingleByteCharSetModel = namedtuple('SingleByteCharSetModel', + ['charset_name', + 'language', + 'char_to_order_map', + 'language_model', + 'typical_positive_ratio', + 'keep_ascii_letters', + 'alphabet']) + + +class SingleByteCharSetProber(CharSetProber): + SAMPLE_SIZE = 64 + SB_ENOUGH_REL_THRESHOLD = 1024 # 0.25 * SAMPLE_SIZE^2 + POSITIVE_SHORTCUT_THRESHOLD = 0.95 + NEGATIVE_SHORTCUT_THRESHOLD = 0.05 + + def __init__(self, model, reversed=False, name_prober=None): + super(SingleByteCharSetProber, self).__init__() + self._model = model + # TRUE if we need to reverse every pair in the model lookup + self._reversed = reversed + # Optional auxiliary prober for name decision + self._name_prober = name_prober + self._last_order = None + self._seq_counters = None + self._total_seqs = None + self._total_char = None + self._freq_char = None + self.reset() + + def reset(self): + super(SingleByteCharSetProber, self).reset() + # char order of last character + self._last_order = 255 + self._seq_counters = [0] * SequenceLikelihood.get_num_categories() + self._total_seqs = 0 + self._total_char = 0 + # characters that fall in our sampling range + self._freq_char = 0 + + @property + def charset_name(self): + if self._name_prober: + return self._name_prober.charset_name + else: + return self._model.charset_name + + @property + def language(self): + if self._name_prober: + return self._name_prober.language + else: + return self._model.language + + def feed(self, byte_str): + # TODO: Make filter_international_words keep things in self.alphabet + if not self._model.keep_ascii_letters: + byte_str = self.filter_international_words(byte_str) + if not byte_str: + return self.state + char_to_order_map = self._model.char_to_order_map + language_model = self._model.language_model + for char in byte_str: + order = char_to_order_map.get(char, CharacterCategory.UNDEFINED) + # XXX: This was SYMBOL_CAT_ORDER before, with a value of 250, but + # CharacterCategory.SYMBOL is actually 253, so we use CONTROL + # to make it closer to the original intent. The only difference + # is whether or not we count digits and control characters for + # _total_char purposes. + if order < CharacterCategory.CONTROL: + self._total_char += 1 + # TODO: Follow uchardet's lead and discount confidence for frequent + # control characters. + # See https://github.com/BYVoid/uchardet/commit/55b4f23971db61 + if order < self.SAMPLE_SIZE: + self._freq_char += 1 + if self._last_order < self.SAMPLE_SIZE: + self._total_seqs += 1 + if not self._reversed: + lm_cat = language_model[self._last_order][order] + else: + lm_cat = language_model[order][self._last_order] + self._seq_counters[lm_cat] += 1 + self._last_order = order + + charset_name = self._model.charset_name + if self.state == ProbingState.DETECTING: + if self._total_seqs > self.SB_ENOUGH_REL_THRESHOLD: + confidence = self.get_confidence() + if confidence > self.POSITIVE_SHORTCUT_THRESHOLD: + self.logger.debug('%s confidence = %s, we have a winner', + charset_name, confidence) + self._state = ProbingState.FOUND_IT + elif confidence < self.NEGATIVE_SHORTCUT_THRESHOLD: + self.logger.debug('%s confidence = %s, below negative ' + 'shortcut threshhold %s', charset_name, + confidence, + self.NEGATIVE_SHORTCUT_THRESHOLD) + self._state = ProbingState.NOT_ME + + return self.state + + def get_confidence(self): + r = 0.01 + if self._total_seqs > 0: + r = ((1.0 * self._seq_counters[SequenceLikelihood.POSITIVE]) / + self._total_seqs / self._model.typical_positive_ratio) + r = r * self._freq_char / self._total_char + if r >= 1.0: + r = 0.99 + return r diff --git a/openpype/vendor/python/python_2/chardet/sbcsgroupprober.py b/openpype/vendor/python/python_2/chardet/sbcsgroupprober.py new file mode 100644 index 0000000000..bdeef4e15b --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/sbcsgroupprober.py @@ -0,0 +1,83 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetgroupprober import CharSetGroupProber +from .hebrewprober import HebrewProber +from .langbulgarianmodel import (ISO_8859_5_BULGARIAN_MODEL, + WINDOWS_1251_BULGARIAN_MODEL) +from .langgreekmodel import ISO_8859_7_GREEK_MODEL, WINDOWS_1253_GREEK_MODEL +from .langhebrewmodel import WINDOWS_1255_HEBREW_MODEL +# from .langhungarianmodel import (ISO_8859_2_HUNGARIAN_MODEL, +# WINDOWS_1250_HUNGARIAN_MODEL) +from .langrussianmodel import (IBM855_RUSSIAN_MODEL, IBM866_RUSSIAN_MODEL, + ISO_8859_5_RUSSIAN_MODEL, KOI8_R_RUSSIAN_MODEL, + MACCYRILLIC_RUSSIAN_MODEL, + WINDOWS_1251_RUSSIAN_MODEL) +from .langthaimodel import TIS_620_THAI_MODEL +from .langturkishmodel import ISO_8859_9_TURKISH_MODEL +from .sbcharsetprober import SingleByteCharSetProber + + +class SBCSGroupProber(CharSetGroupProber): + def __init__(self): + super(SBCSGroupProber, self).__init__() + hebrew_prober = HebrewProber() + logical_hebrew_prober = SingleByteCharSetProber(WINDOWS_1255_HEBREW_MODEL, + False, hebrew_prober) + # TODO: See if using ISO-8859-8 Hebrew model works better here, since + # it's actually the visual one + visual_hebrew_prober = SingleByteCharSetProber(WINDOWS_1255_HEBREW_MODEL, + True, hebrew_prober) + hebrew_prober.set_model_probers(logical_hebrew_prober, + visual_hebrew_prober) + # TODO: ORDER MATTERS HERE. I changed the order vs what was in master + # and several tests failed that did not before. Some thought + # should be put into the ordering, and we should consider making + # order not matter here, because that is very counter-intuitive. + self.probers = [ + SingleByteCharSetProber(WINDOWS_1251_RUSSIAN_MODEL), + SingleByteCharSetProber(KOI8_R_RUSSIAN_MODEL), + SingleByteCharSetProber(ISO_8859_5_RUSSIAN_MODEL), + SingleByteCharSetProber(MACCYRILLIC_RUSSIAN_MODEL), + SingleByteCharSetProber(IBM866_RUSSIAN_MODEL), + SingleByteCharSetProber(IBM855_RUSSIAN_MODEL), + SingleByteCharSetProber(ISO_8859_7_GREEK_MODEL), + SingleByteCharSetProber(WINDOWS_1253_GREEK_MODEL), + SingleByteCharSetProber(ISO_8859_5_BULGARIAN_MODEL), + SingleByteCharSetProber(WINDOWS_1251_BULGARIAN_MODEL), + # TODO: Restore Hungarian encodings (iso-8859-2 and windows-1250) + # after we retrain model. + # SingleByteCharSetProber(ISO_8859_2_HUNGARIAN_MODEL), + # SingleByteCharSetProber(WINDOWS_1250_HUNGARIAN_MODEL), + SingleByteCharSetProber(TIS_620_THAI_MODEL), + SingleByteCharSetProber(ISO_8859_9_TURKISH_MODEL), + hebrew_prober, + logical_hebrew_prober, + visual_hebrew_prober, + ] + self.reset() diff --git a/openpype/vendor/python/python_2/chardet/sjisprober.py b/openpype/vendor/python/python_2/chardet/sjisprober.py new file mode 100644 index 0000000000..9e29623bdc --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/sjisprober.py @@ -0,0 +1,92 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .mbcharsetprober import MultiByteCharSetProber +from .codingstatemachine import CodingStateMachine +from .chardistribution import SJISDistributionAnalysis +from .jpcntx import SJISContextAnalysis +from .mbcssm import SJIS_SM_MODEL +from .enums import ProbingState, MachineState + + +class SJISProber(MultiByteCharSetProber): + def __init__(self): + super(SJISProber, self).__init__() + self.coding_sm = CodingStateMachine(SJIS_SM_MODEL) + self.distribution_analyzer = SJISDistributionAnalysis() + self.context_analyzer = SJISContextAnalysis() + self.reset() + + def reset(self): + super(SJISProber, self).reset() + self.context_analyzer.reset() + + @property + def charset_name(self): + return self.context_analyzer.charset_name + + @property + def language(self): + return "Japanese" + + def feed(self, byte_str): + for i in range(len(byte_str)): + coding_state = self.coding_sm.next_state(byte_str[i]) + if coding_state == MachineState.ERROR: + self.logger.debug('%s %s prober hit error at byte %s', + self.charset_name, self.language, i) + self._state = ProbingState.NOT_ME + break + elif coding_state == MachineState.ITS_ME: + self._state = ProbingState.FOUND_IT + break + elif coding_state == MachineState.START: + char_len = self.coding_sm.get_current_charlen() + if i == 0: + self._last_char[1] = byte_str[0] + self.context_analyzer.feed(self._last_char[2 - char_len:], + char_len) + self.distribution_analyzer.feed(self._last_char, char_len) + else: + self.context_analyzer.feed(byte_str[i + 1 - char_len:i + 3 + - char_len], char_len) + self.distribution_analyzer.feed(byte_str[i - 1:i + 1], + char_len) + + self._last_char[0] = byte_str[-1] + + if self.state == ProbingState.DETECTING: + if (self.context_analyzer.got_enough_data() and + (self.get_confidence() > self.SHORTCUT_THRESHOLD)): + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self): + context_conf = self.context_analyzer.get_confidence() + distrib_conf = self.distribution_analyzer.get_confidence() + return max(context_conf, distrib_conf) diff --git a/openpype/vendor/python/python_2/chardet/universaldetector.py b/openpype/vendor/python/python_2/chardet/universaldetector.py new file mode 100644 index 0000000000..055a8ac1b1 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/universaldetector.py @@ -0,0 +1,286 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is Mozilla Universal charset detector code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2001 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# Shy Shalom - original C code +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### +""" +Module containing the UniversalDetector detector class, which is the primary +class a user of ``chardet`` should use. + +:author: Mark Pilgrim (initial port to Python) +:author: Shy Shalom (original C code) +:author: Dan Blanchard (major refactoring for 3.0) +:author: Ian Cordasco +""" + + +import codecs +import logging +import re + +from .charsetgroupprober import CharSetGroupProber +from .enums import InputState, LanguageFilter, ProbingState +from .escprober import EscCharSetProber +from .latin1prober import Latin1Prober +from .mbcsgroupprober import MBCSGroupProber +from .sbcsgroupprober import SBCSGroupProber + + +class UniversalDetector(object): + """ + The ``UniversalDetector`` class underlies the ``chardet.detect`` function + and coordinates all of the different charset probers. + + To get a ``dict`` containing an encoding and its confidence, you can simply + run: + + .. code:: + + u = UniversalDetector() + u.feed(some_bytes) + u.close() + detected = u.result + + """ + + MINIMUM_THRESHOLD = 0.20 + HIGH_BYTE_DETECTOR = re.compile(b'[\x80-\xFF]') + ESC_DETECTOR = re.compile(b'(\033|~{)') + WIN_BYTE_DETECTOR = re.compile(b'[\x80-\x9F]') + ISO_WIN_MAP = {'iso-8859-1': 'Windows-1252', + 'iso-8859-2': 'Windows-1250', + 'iso-8859-5': 'Windows-1251', + 'iso-8859-6': 'Windows-1256', + 'iso-8859-7': 'Windows-1253', + 'iso-8859-8': 'Windows-1255', + 'iso-8859-9': 'Windows-1254', + 'iso-8859-13': 'Windows-1257'} + + def __init__(self, lang_filter=LanguageFilter.ALL): + self._esc_charset_prober = None + self._charset_probers = [] + self.result = None + self.done = None + self._got_data = None + self._input_state = None + self._last_char = None + self.lang_filter = lang_filter + self.logger = logging.getLogger(__name__) + self._has_win_bytes = None + self.reset() + + def reset(self): + """ + Reset the UniversalDetector and all of its probers back to their + initial states. This is called by ``__init__``, so you only need to + call this directly in between analyses of different documents. + """ + self.result = {'encoding': None, 'confidence': 0.0, 'language': None} + self.done = False + self._got_data = False + self._has_win_bytes = False + self._input_state = InputState.PURE_ASCII + self._last_char = b'' + if self._esc_charset_prober: + self._esc_charset_prober.reset() + for prober in self._charset_probers: + prober.reset() + + def feed(self, byte_str): + """ + Takes a chunk of a document and feeds it through all of the relevant + charset probers. + + After calling ``feed``, you can check the value of the ``done`` + attribute to see if you need to continue feeding the + ``UniversalDetector`` more data, or if it has made a prediction + (in the ``result`` attribute). + + .. note:: + You should always call ``close`` when you're done feeding in your + document if ``done`` is not already ``True``. + """ + if self.done: + return + + if not len(byte_str): + return + + if not isinstance(byte_str, bytearray): + byte_str = bytearray(byte_str) + + # First check for known BOMs, since these are guaranteed to be correct + if not self._got_data: + # If the data starts with BOM, we know it is UTF + if byte_str.startswith(codecs.BOM_UTF8): + # EF BB BF UTF-8 with BOM + self.result = {'encoding': "UTF-8-SIG", + 'confidence': 1.0, + 'language': ''} + elif byte_str.startswith((codecs.BOM_UTF32_LE, + codecs.BOM_UTF32_BE)): + # FF FE 00 00 UTF-32, little-endian BOM + # 00 00 FE FF UTF-32, big-endian BOM + self.result = {'encoding': "UTF-32", + 'confidence': 1.0, + 'language': ''} + elif byte_str.startswith(b'\xFE\xFF\x00\x00'): + # FE FF 00 00 UCS-4, unusual octet order BOM (3412) + self.result = {'encoding': "X-ISO-10646-UCS-4-3412", + 'confidence': 1.0, + 'language': ''} + elif byte_str.startswith(b'\x00\x00\xFF\xFE'): + # 00 00 FF FE UCS-4, unusual octet order BOM (2143) + self.result = {'encoding': "X-ISO-10646-UCS-4-2143", + 'confidence': 1.0, + 'language': ''} + elif byte_str.startswith((codecs.BOM_LE, codecs.BOM_BE)): + # FF FE UTF-16, little endian BOM + # FE FF UTF-16, big endian BOM + self.result = {'encoding': "UTF-16", + 'confidence': 1.0, + 'language': ''} + + self._got_data = True + if self.result['encoding'] is not None: + self.done = True + return + + # If none of those matched and we've only see ASCII so far, check + # for high bytes and escape sequences + if self._input_state == InputState.PURE_ASCII: + if self.HIGH_BYTE_DETECTOR.search(byte_str): + self._input_state = InputState.HIGH_BYTE + elif self._input_state == InputState.PURE_ASCII and \ + self.ESC_DETECTOR.search(self._last_char + byte_str): + self._input_state = InputState.ESC_ASCII + + self._last_char = byte_str[-1:] + + # If we've seen escape sequences, use the EscCharSetProber, which + # uses a simple state machine to check for known escape sequences in + # HZ and ISO-2022 encodings, since those are the only encodings that + # use such sequences. + if self._input_state == InputState.ESC_ASCII: + if not self._esc_charset_prober: + self._esc_charset_prober = EscCharSetProber(self.lang_filter) + if self._esc_charset_prober.feed(byte_str) == ProbingState.FOUND_IT: + self.result = {'encoding': + self._esc_charset_prober.charset_name, + 'confidence': + self._esc_charset_prober.get_confidence(), + 'language': + self._esc_charset_prober.language} + self.done = True + # If we've seen high bytes (i.e., those with values greater than 127), + # we need to do more complicated checks using all our multi-byte and + # single-byte probers that are left. The single-byte probers + # use character bigram distributions to determine the encoding, whereas + # the multi-byte probers use a combination of character unigram and + # bigram distributions. + elif self._input_state == InputState.HIGH_BYTE: + if not self._charset_probers: + self._charset_probers = [MBCSGroupProber(self.lang_filter)] + # If we're checking non-CJK encodings, use single-byte prober + if self.lang_filter & LanguageFilter.NON_CJK: + self._charset_probers.append(SBCSGroupProber()) + self._charset_probers.append(Latin1Prober()) + for prober in self._charset_probers: + if prober.feed(byte_str) == ProbingState.FOUND_IT: + self.result = {'encoding': prober.charset_name, + 'confidence': prober.get_confidence(), + 'language': prober.language} + self.done = True + break + if self.WIN_BYTE_DETECTOR.search(byte_str): + self._has_win_bytes = True + + def close(self): + """ + Stop analyzing the current document and come up with a final + prediction. + + :returns: The ``result`` attribute, a ``dict`` with the keys + `encoding`, `confidence`, and `language`. + """ + # Don't bother with checks if we're already done + if self.done: + return self.result + self.done = True + + if not self._got_data: + self.logger.debug('no data received!') + + # Default to ASCII if it is all we've seen so far + elif self._input_state == InputState.PURE_ASCII: + self.result = {'encoding': 'ascii', + 'confidence': 1.0, + 'language': ''} + + # If we have seen non-ASCII, return the best that met MINIMUM_THRESHOLD + elif self._input_state == InputState.HIGH_BYTE: + prober_confidence = None + max_prober_confidence = 0.0 + max_prober = None + for prober in self._charset_probers: + if not prober: + continue + prober_confidence = prober.get_confidence() + if prober_confidence > max_prober_confidence: + max_prober_confidence = prober_confidence + max_prober = prober + if max_prober and (max_prober_confidence > self.MINIMUM_THRESHOLD): + charset_name = max_prober.charset_name + lower_charset_name = max_prober.charset_name.lower() + confidence = max_prober.get_confidence() + # Use Windows encoding name instead of ISO-8859 if we saw any + # extra Windows-specific bytes + if lower_charset_name.startswith('iso-8859'): + if self._has_win_bytes: + charset_name = self.ISO_WIN_MAP.get(lower_charset_name, + charset_name) + self.result = {'encoding': charset_name, + 'confidence': confidence, + 'language': max_prober.language} + + # Log all prober confidences if none met MINIMUM_THRESHOLD + if self.logger.getEffectiveLevel() <= logging.DEBUG: + if self.result['encoding'] is None: + self.logger.debug('no probers hit minimum threshold') + for group_prober in self._charset_probers: + if not group_prober: + continue + if isinstance(group_prober, CharSetGroupProber): + for prober in group_prober.probers: + self.logger.debug('%s %s confidence = %s', + prober.charset_name, + prober.language, + prober.get_confidence()) + else: + self.logger.debug('%s %s confidence = %s', + group_prober.charset_name, + group_prober.language, + group_prober.get_confidence()) + return self.result diff --git a/openpype/vendor/python/python_2/chardet/utf8prober.py b/openpype/vendor/python/python_2/chardet/utf8prober.py new file mode 100644 index 0000000000..6c3196cc2d --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/utf8prober.py @@ -0,0 +1,82 @@ +######################## BEGIN LICENSE BLOCK ######################## +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1998 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Mark Pilgrim - port to Python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA +######################### END LICENSE BLOCK ######################### + +from .charsetprober import CharSetProber +from .enums import ProbingState, MachineState +from .codingstatemachine import CodingStateMachine +from .mbcssm import UTF8_SM_MODEL + + + +class UTF8Prober(CharSetProber): + ONE_CHAR_PROB = 0.5 + + def __init__(self): + super(UTF8Prober, self).__init__() + self.coding_sm = CodingStateMachine(UTF8_SM_MODEL) + self._num_mb_chars = None + self.reset() + + def reset(self): + super(UTF8Prober, self).reset() + self.coding_sm.reset() + self._num_mb_chars = 0 + + @property + def charset_name(self): + return "utf-8" + + @property + def language(self): + return "" + + def feed(self, byte_str): + for c in byte_str: + coding_state = self.coding_sm.next_state(c) + if coding_state == MachineState.ERROR: + self._state = ProbingState.NOT_ME + break + elif coding_state == MachineState.ITS_ME: + self._state = ProbingState.FOUND_IT + break + elif coding_state == MachineState.START: + if self.coding_sm.get_current_charlen() >= 2: + self._num_mb_chars += 1 + + if self.state == ProbingState.DETECTING: + if self.get_confidence() > self.SHORTCUT_THRESHOLD: + self._state = ProbingState.FOUND_IT + + return self.state + + def get_confidence(self): + unlike = 0.99 + if self._num_mb_chars < 6: + unlike *= self.ONE_CHAR_PROB ** self._num_mb_chars + return 1.0 - unlike + else: + return unlike diff --git a/openpype/vendor/python/python_2/chardet/version.py b/openpype/vendor/python/python_2/chardet/version.py new file mode 100644 index 0000000000..70369b9d66 --- /dev/null +++ b/openpype/vendor/python/python_2/chardet/version.py @@ -0,0 +1,9 @@ +""" +This module exists only to simplify retrieving the version number of chardet +from within setup.py and from chardet subpackages. + +:author: Dan Blanchard (dan.blanchard@gmail.com) +""" + +__version__ = "4.0.0" +VERSION = __version__.split('.') diff --git a/openpype/vendor/python/python_2/idna/__init__.py b/openpype/vendor/python/python_2/idna/__init__.py new file mode 100644 index 0000000000..847bf93547 --- /dev/null +++ b/openpype/vendor/python/python_2/idna/__init__.py @@ -0,0 +1,2 @@ +from .package_data import __version__ +from .core import * diff --git a/openpype/vendor/python/python_2/idna/codec.py b/openpype/vendor/python/python_2/idna/codec.py new file mode 100644 index 0000000000..98c65ead14 --- /dev/null +++ b/openpype/vendor/python/python_2/idna/codec.py @@ -0,0 +1,118 @@ +from .core import encode, decode, alabel, ulabel, IDNAError +import codecs +import re + +_unicode_dots_re = re.compile(u'[\u002e\u3002\uff0e\uff61]') + +class Codec(codecs.Codec): + + def encode(self, data, errors='strict'): + + if errors != 'strict': + raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + + if not data: + return "", 0 + + return encode(data), len(data) + + def decode(self, data, errors='strict'): + + if errors != 'strict': + raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + + if not data: + return u"", 0 + + return decode(data), len(data) + +class IncrementalEncoder(codecs.BufferedIncrementalEncoder): + def _buffer_encode(self, data, errors, final): + if errors != 'strict': + raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + + if not data: + return ("", 0) + + labels = _unicode_dots_re.split(data) + trailing_dot = u'' + if labels: + if not labels[-1]: + trailing_dot = '.' + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = '.' + + result = [] + size = 0 + for label in labels: + result.append(alabel(label)) + if size: + size += 1 + size += len(label) + + # Join with U+002E + result = ".".join(result) + trailing_dot + size += len(trailing_dot) + return (result, size) + +class IncrementalDecoder(codecs.BufferedIncrementalDecoder): + def _buffer_decode(self, data, errors, final): + if errors != 'strict': + raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + + if not data: + return (u"", 0) + + # IDNA allows decoding to operate on Unicode strings, too. + if isinstance(data, unicode): + labels = _unicode_dots_re.split(data) + else: + # Must be ASCII string + data = str(data) + unicode(data, "ascii") + labels = data.split(".") + + trailing_dot = u'' + if labels: + if not labels[-1]: + trailing_dot = u'.' + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = u'.' + + result = [] + size = 0 + for label in labels: + result.append(ulabel(label)) + if size: + size += 1 + size += len(label) + + result = u".".join(result) + trailing_dot + size += len(trailing_dot) + return (result, size) + + +class StreamWriter(Codec, codecs.StreamWriter): + pass + +class StreamReader(Codec, codecs.StreamReader): + pass + +def getregentry(): + return codecs.CodecInfo( + name='idna', + encode=Codec().encode, + decode=Codec().decode, + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamwriter=StreamWriter, + streamreader=StreamReader, + ) diff --git a/openpype/vendor/python/python_2/idna/compat.py b/openpype/vendor/python/python_2/idna/compat.py new file mode 100644 index 0000000000..4d47f336db --- /dev/null +++ b/openpype/vendor/python/python_2/idna/compat.py @@ -0,0 +1,12 @@ +from .core import * +from .codec import * + +def ToASCII(label): + return encode(label) + +def ToUnicode(label): + return decode(label) + +def nameprep(s): + raise NotImplementedError("IDNA 2008 does not utilise nameprep protocol") + diff --git a/openpype/vendor/python/python_2/idna/core.py b/openpype/vendor/python/python_2/idna/core.py new file mode 100644 index 0000000000..41ec5c711d --- /dev/null +++ b/openpype/vendor/python/python_2/idna/core.py @@ -0,0 +1,400 @@ +from . import idnadata +import bisect +import unicodedata +import re +import sys +from .intranges import intranges_contain + +_virama_combining_class = 9 +_alabel_prefix = b'xn--' +_unicode_dots_re = re.compile(u'[\u002e\u3002\uff0e\uff61]') + +if sys.version_info[0] >= 3: + unicode = str + unichr = chr + +class IDNAError(UnicodeError): + """ Base exception for all IDNA-encoding related problems """ + pass + + +class IDNABidiError(IDNAError): + """ Exception when bidirectional requirements are not satisfied """ + pass + + +class InvalidCodepoint(IDNAError): + """ Exception when a disallowed or unallocated codepoint is used """ + pass + + +class InvalidCodepointContext(IDNAError): + """ Exception when the codepoint is not valid in the context it is used """ + pass + + +def _combining_class(cp): + v = unicodedata.combining(unichr(cp)) + if v == 0: + if not unicodedata.name(unichr(cp)): + raise ValueError("Unknown character in unicodedata") + return v + +def _is_script(cp, script): + return intranges_contain(ord(cp), idnadata.scripts[script]) + +def _punycode(s): + return s.encode('punycode') + +def _unot(s): + return 'U+{0:04X}'.format(s) + + +def valid_label_length(label): + + if len(label) > 63: + return False + return True + + +def valid_string_length(label, trailing_dot): + + if len(label) > (254 if trailing_dot else 253): + return False + return True + + +def check_bidi(label, check_ltr=False): + + # Bidi rules should only be applied if string contains RTL characters + bidi_label = False + for (idx, cp) in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + if direction == '': + # String likely comes from a newer version of Unicode + raise IDNABidiError('Unknown directionality in label {0} at position {1}'.format(repr(label), idx)) + if direction in ['R', 'AL', 'AN']: + bidi_label = True + if not bidi_label and not check_ltr: + return True + + # Bidi rule 1 + direction = unicodedata.bidirectional(label[0]) + if direction in ['R', 'AL']: + rtl = True + elif direction == 'L': + rtl = False + else: + raise IDNABidiError('First codepoint in label {0} must be directionality L, R or AL'.format(repr(label))) + + valid_ending = False + number_type = False + for (idx, cp) in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + + if rtl: + # Bidi rule 2 + if not direction in ['R', 'AL', 'AN', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: + raise IDNABidiError('Invalid direction for codepoint at position {0} in a right-to-left label'.format(idx)) + # Bidi rule 3 + if direction in ['R', 'AL', 'EN', 'AN']: + valid_ending = True + elif direction != 'NSM': + valid_ending = False + # Bidi rule 4 + if direction in ['AN', 'EN']: + if not number_type: + number_type = direction + else: + if number_type != direction: + raise IDNABidiError('Can not mix numeral types in a right-to-left label') + else: + # Bidi rule 5 + if not direction in ['L', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: + raise IDNABidiError('Invalid direction for codepoint at position {0} in a left-to-right label'.format(idx)) + # Bidi rule 6 + if direction in ['L', 'EN']: + valid_ending = True + elif direction != 'NSM': + valid_ending = False + + if not valid_ending: + raise IDNABidiError('Label ends with illegal codepoint directionality') + + return True + + +def check_initial_combiner(label): + + if unicodedata.category(label[0])[0] == 'M': + raise IDNAError('Label begins with an illegal combining character') + return True + + +def check_hyphen_ok(label): + + if label[2:4] == '--': + raise IDNAError('Label has disallowed hyphens in 3rd and 4th position') + if label[0] == '-' or label[-1] == '-': + raise IDNAError('Label must not start or end with a hyphen') + return True + + +def check_nfc(label): + + if unicodedata.normalize('NFC', label) != label: + raise IDNAError('Label must be in Normalization Form C') + + +def valid_contextj(label, pos): + + cp_value = ord(label[pos]) + + if cp_value == 0x200c: + + if pos > 0: + if _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + + ok = False + for i in range(pos-1, -1, -1): + joining_type = idnadata.joining_types.get(ord(label[i])) + if joining_type == ord('T'): + continue + if joining_type in [ord('L'), ord('D')]: + ok = True + break + + if not ok: + return False + + ok = False + for i in range(pos+1, len(label)): + joining_type = idnadata.joining_types.get(ord(label[i])) + if joining_type == ord('T'): + continue + if joining_type in [ord('R'), ord('D')]: + ok = True + break + return ok + + if cp_value == 0x200d: + + if pos > 0: + if _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + return False + + else: + + return False + + +def valid_contexto(label, pos, exception=False): + + cp_value = ord(label[pos]) + + if cp_value == 0x00b7: + if 0 < pos < len(label)-1: + if ord(label[pos - 1]) == 0x006c and ord(label[pos + 1]) == 0x006c: + return True + return False + + elif cp_value == 0x0375: + if pos < len(label)-1 and len(label) > 1: + return _is_script(label[pos + 1], 'Greek') + return False + + elif cp_value == 0x05f3 or cp_value == 0x05f4: + if pos > 0: + return _is_script(label[pos - 1], 'Hebrew') + return False + + elif cp_value == 0x30fb: + for cp in label: + if cp == u'\u30fb': + continue + if _is_script(cp, 'Hiragana') or _is_script(cp, 'Katakana') or _is_script(cp, 'Han'): + return True + return False + + elif 0x660 <= cp_value <= 0x669: + for cp in label: + if 0x6f0 <= ord(cp) <= 0x06f9: + return False + return True + + elif 0x6f0 <= cp_value <= 0x6f9: + for cp in label: + if 0x660 <= ord(cp) <= 0x0669: + return False + return True + + +def check_label(label): + + if isinstance(label, (bytes, bytearray)): + label = label.decode('utf-8') + if len(label) == 0: + raise IDNAError('Empty Label') + + check_nfc(label) + check_hyphen_ok(label) + check_initial_combiner(label) + + for (pos, cp) in enumerate(label): + cp_value = ord(cp) + if intranges_contain(cp_value, idnadata.codepoint_classes['PVALID']): + continue + elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTJ']): + try: + if not valid_contextj(label, pos): + raise InvalidCodepointContext('Joiner {0} not allowed at position {1} in {2}'.format( + _unot(cp_value), pos+1, repr(label))) + except ValueError: + raise IDNAError('Unknown codepoint adjacent to joiner {0} at position {1} in {2}'.format( + _unot(cp_value), pos+1, repr(label))) + elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTO']): + if not valid_contexto(label, pos): + raise InvalidCodepointContext('Codepoint {0} not allowed at position {1} in {2}'.format(_unot(cp_value), pos+1, repr(label))) + else: + raise InvalidCodepoint('Codepoint {0} at position {1} of {2} not allowed'.format(_unot(cp_value), pos+1, repr(label))) + + check_bidi(label) + + +def alabel(label): + + try: + label = label.encode('ascii') + ulabel(label) + if not valid_label_length(label): + raise IDNAError('Label too long') + return label + except UnicodeEncodeError: + pass + + if not label: + raise IDNAError('No Input') + + label = unicode(label) + check_label(label) + label = _punycode(label) + label = _alabel_prefix + label + + if not valid_label_length(label): + raise IDNAError('Label too long') + + return label + + +def ulabel(label): + + if not isinstance(label, (bytes, bytearray)): + try: + label = label.encode('ascii') + except UnicodeEncodeError: + check_label(label) + return label + + label = label.lower() + if label.startswith(_alabel_prefix): + label = label[len(_alabel_prefix):] + if not label: + raise IDNAError('Malformed A-label, no Punycode eligible content found') + if label.decode('ascii')[-1] == '-': + raise IDNAError('A-label must not end with a hyphen') + else: + check_label(label) + return label.decode('ascii') + + label = label.decode('punycode') + check_label(label) + return label + + +def uts46_remap(domain, std3_rules=True, transitional=False): + """Re-map the characters in the string according to UTS46 processing.""" + from .uts46data import uts46data + output = u"" + try: + for pos, char in enumerate(domain): + code_point = ord(char) + uts46row = uts46data[code_point if code_point < 256 else + bisect.bisect_left(uts46data, (code_point, "Z")) - 1] + status = uts46row[1] + replacement = uts46row[2] if len(uts46row) == 3 else None + if (status == "V" or + (status == "D" and not transitional) or + (status == "3" and not std3_rules and replacement is None)): + output += char + elif replacement is not None and (status == "M" or + (status == "3" and not std3_rules) or + (status == "D" and transitional)): + output += replacement + elif status != "I": + raise IndexError() + return unicodedata.normalize("NFC", output) + except IndexError: + raise InvalidCodepoint( + "Codepoint {0} not allowed at position {1} in {2}".format( + _unot(code_point), pos + 1, repr(domain))) + + +def encode(s, strict=False, uts46=False, std3_rules=False, transitional=False): + + if isinstance(s, (bytes, bytearray)): + s = s.decode("ascii") + if uts46: + s = uts46_remap(s, std3_rules, transitional) + trailing_dot = False + result = [] + if strict: + labels = s.split('.') + else: + labels = _unicode_dots_re.split(s) + if not labels or labels == ['']: + raise IDNAError('Empty domain') + if labels[-1] == '': + del labels[-1] + trailing_dot = True + for label in labels: + s = alabel(label) + if s: + result.append(s) + else: + raise IDNAError('Empty label') + if trailing_dot: + result.append(b'') + s = b'.'.join(result) + if not valid_string_length(s, trailing_dot): + raise IDNAError('Domain too long') + return s + + +def decode(s, strict=False, uts46=False, std3_rules=False): + + if isinstance(s, (bytes, bytearray)): + s = s.decode("ascii") + if uts46: + s = uts46_remap(s, std3_rules, False) + trailing_dot = False + result = [] + if not strict: + labels = _unicode_dots_re.split(s) + else: + labels = s.split(u'.') + if not labels or labels == ['']: + raise IDNAError('Empty domain') + if not labels[-1]: + del labels[-1] + trailing_dot = True + for label in labels: + s = ulabel(label) + if s: + result.append(s) + else: + raise IDNAError('Empty label') + if trailing_dot: + result.append(u'') + return u'.'.join(result) diff --git a/openpype/vendor/python/python_2/idna/idnadata.py b/openpype/vendor/python/python_2/idna/idnadata.py new file mode 100644 index 0000000000..a284e4c84a --- /dev/null +++ b/openpype/vendor/python/python_2/idna/idnadata.py @@ -0,0 +1,2050 @@ +# This file is automatically generated by tools/idna-data + +__version__ = "13.0.0" +scripts = { + 'Greek': ( + 0x37000000374, + 0x37500000378, + 0x37a0000037e, + 0x37f00000380, + 0x38400000385, + 0x38600000387, + 0x3880000038b, + 0x38c0000038d, + 0x38e000003a2, + 0x3a3000003e2, + 0x3f000000400, + 0x1d2600001d2b, + 0x1d5d00001d62, + 0x1d6600001d6b, + 0x1dbf00001dc0, + 0x1f0000001f16, + 0x1f1800001f1e, + 0x1f2000001f46, + 0x1f4800001f4e, + 0x1f5000001f58, + 0x1f5900001f5a, + 0x1f5b00001f5c, + 0x1f5d00001f5e, + 0x1f5f00001f7e, + 0x1f8000001fb5, + 0x1fb600001fc5, + 0x1fc600001fd4, + 0x1fd600001fdc, + 0x1fdd00001ff0, + 0x1ff200001ff5, + 0x1ff600001fff, + 0x212600002127, + 0xab650000ab66, + 0x101400001018f, + 0x101a0000101a1, + 0x1d2000001d246, + ), + 'Han': ( + 0x2e8000002e9a, + 0x2e9b00002ef4, + 0x2f0000002fd6, + 0x300500003006, + 0x300700003008, + 0x30210000302a, + 0x30380000303c, + 0x340000004dc0, + 0x4e0000009ffd, + 0xf9000000fa6e, + 0xfa700000fada, + 0x16ff000016ff2, + 0x200000002a6de, + 0x2a7000002b735, + 0x2b7400002b81e, + 0x2b8200002cea2, + 0x2ceb00002ebe1, + 0x2f8000002fa1e, + 0x300000003134b, + ), + 'Hebrew': ( + 0x591000005c8, + 0x5d0000005eb, + 0x5ef000005f5, + 0xfb1d0000fb37, + 0xfb380000fb3d, + 0xfb3e0000fb3f, + 0xfb400000fb42, + 0xfb430000fb45, + 0xfb460000fb50, + ), + 'Hiragana': ( + 0x304100003097, + 0x309d000030a0, + 0x1b0010001b11f, + 0x1b1500001b153, + 0x1f2000001f201, + ), + 'Katakana': ( + 0x30a1000030fb, + 0x30fd00003100, + 0x31f000003200, + 0x32d0000032ff, + 0x330000003358, + 0xff660000ff70, + 0xff710000ff9e, + 0x1b0000001b001, + 0x1b1640001b168, + ), +} +joining_types = { + 0x600: 85, + 0x601: 85, + 0x602: 85, + 0x603: 85, + 0x604: 85, + 0x605: 85, + 0x608: 85, + 0x60b: 85, + 0x620: 68, + 0x621: 85, + 0x622: 82, + 0x623: 82, + 0x624: 82, + 0x625: 82, + 0x626: 68, + 0x627: 82, + 0x628: 68, + 0x629: 82, + 0x62a: 68, + 0x62b: 68, + 0x62c: 68, + 0x62d: 68, + 0x62e: 68, + 0x62f: 82, + 0x630: 82, + 0x631: 82, + 0x632: 82, + 0x633: 68, + 0x634: 68, + 0x635: 68, + 0x636: 68, + 0x637: 68, + 0x638: 68, + 0x639: 68, + 0x63a: 68, + 0x63b: 68, + 0x63c: 68, + 0x63d: 68, + 0x63e: 68, + 0x63f: 68, + 0x640: 67, + 0x641: 68, + 0x642: 68, + 0x643: 68, + 0x644: 68, + 0x645: 68, + 0x646: 68, + 0x647: 68, + 0x648: 82, + 0x649: 68, + 0x64a: 68, + 0x66e: 68, + 0x66f: 68, + 0x671: 82, + 0x672: 82, + 0x673: 82, + 0x674: 85, + 0x675: 82, + 0x676: 82, + 0x677: 82, + 0x678: 68, + 0x679: 68, + 0x67a: 68, + 0x67b: 68, + 0x67c: 68, + 0x67d: 68, + 0x67e: 68, + 0x67f: 68, + 0x680: 68, + 0x681: 68, + 0x682: 68, + 0x683: 68, + 0x684: 68, + 0x685: 68, + 0x686: 68, + 0x687: 68, + 0x688: 82, + 0x689: 82, + 0x68a: 82, + 0x68b: 82, + 0x68c: 82, + 0x68d: 82, + 0x68e: 82, + 0x68f: 82, + 0x690: 82, + 0x691: 82, + 0x692: 82, + 0x693: 82, + 0x694: 82, + 0x695: 82, + 0x696: 82, + 0x697: 82, + 0x698: 82, + 0x699: 82, + 0x69a: 68, + 0x69b: 68, + 0x69c: 68, + 0x69d: 68, + 0x69e: 68, + 0x69f: 68, + 0x6a0: 68, + 0x6a1: 68, + 0x6a2: 68, + 0x6a3: 68, + 0x6a4: 68, + 0x6a5: 68, + 0x6a6: 68, + 0x6a7: 68, + 0x6a8: 68, + 0x6a9: 68, + 0x6aa: 68, + 0x6ab: 68, + 0x6ac: 68, + 0x6ad: 68, + 0x6ae: 68, + 0x6af: 68, + 0x6b0: 68, + 0x6b1: 68, + 0x6b2: 68, + 0x6b3: 68, + 0x6b4: 68, + 0x6b5: 68, + 0x6b6: 68, + 0x6b7: 68, + 0x6b8: 68, + 0x6b9: 68, + 0x6ba: 68, + 0x6bb: 68, + 0x6bc: 68, + 0x6bd: 68, + 0x6be: 68, + 0x6bf: 68, + 0x6c0: 82, + 0x6c1: 68, + 0x6c2: 68, + 0x6c3: 82, + 0x6c4: 82, + 0x6c5: 82, + 0x6c6: 82, + 0x6c7: 82, + 0x6c8: 82, + 0x6c9: 82, + 0x6ca: 82, + 0x6cb: 82, + 0x6cc: 68, + 0x6cd: 82, + 0x6ce: 68, + 0x6cf: 82, + 0x6d0: 68, + 0x6d1: 68, + 0x6d2: 82, + 0x6d3: 82, + 0x6d5: 82, + 0x6dd: 85, + 0x6ee: 82, + 0x6ef: 82, + 0x6fa: 68, + 0x6fb: 68, + 0x6fc: 68, + 0x6ff: 68, + 0x70f: 84, + 0x710: 82, + 0x712: 68, + 0x713: 68, + 0x714: 68, + 0x715: 82, + 0x716: 82, + 0x717: 82, + 0x718: 82, + 0x719: 82, + 0x71a: 68, + 0x71b: 68, + 0x71c: 68, + 0x71d: 68, + 0x71e: 82, + 0x71f: 68, + 0x720: 68, + 0x721: 68, + 0x722: 68, + 0x723: 68, + 0x724: 68, + 0x725: 68, + 0x726: 68, + 0x727: 68, + 0x728: 82, + 0x729: 68, + 0x72a: 82, + 0x72b: 68, + 0x72c: 82, + 0x72d: 68, + 0x72e: 68, + 0x72f: 82, + 0x74d: 82, + 0x74e: 68, + 0x74f: 68, + 0x750: 68, + 0x751: 68, + 0x752: 68, + 0x753: 68, + 0x754: 68, + 0x755: 68, + 0x756: 68, + 0x757: 68, + 0x758: 68, + 0x759: 82, + 0x75a: 82, + 0x75b: 82, + 0x75c: 68, + 0x75d: 68, + 0x75e: 68, + 0x75f: 68, + 0x760: 68, + 0x761: 68, + 0x762: 68, + 0x763: 68, + 0x764: 68, + 0x765: 68, + 0x766: 68, + 0x767: 68, + 0x768: 68, + 0x769: 68, + 0x76a: 68, + 0x76b: 82, + 0x76c: 82, + 0x76d: 68, + 0x76e: 68, + 0x76f: 68, + 0x770: 68, + 0x771: 82, + 0x772: 68, + 0x773: 82, + 0x774: 82, + 0x775: 68, + 0x776: 68, + 0x777: 68, + 0x778: 82, + 0x779: 82, + 0x77a: 68, + 0x77b: 68, + 0x77c: 68, + 0x77d: 68, + 0x77e: 68, + 0x77f: 68, + 0x7ca: 68, + 0x7cb: 68, + 0x7cc: 68, + 0x7cd: 68, + 0x7ce: 68, + 0x7cf: 68, + 0x7d0: 68, + 0x7d1: 68, + 0x7d2: 68, + 0x7d3: 68, + 0x7d4: 68, + 0x7d5: 68, + 0x7d6: 68, + 0x7d7: 68, + 0x7d8: 68, + 0x7d9: 68, + 0x7da: 68, + 0x7db: 68, + 0x7dc: 68, + 0x7dd: 68, + 0x7de: 68, + 0x7df: 68, + 0x7e0: 68, + 0x7e1: 68, + 0x7e2: 68, + 0x7e3: 68, + 0x7e4: 68, + 0x7e5: 68, + 0x7e6: 68, + 0x7e7: 68, + 0x7e8: 68, + 0x7e9: 68, + 0x7ea: 68, + 0x7fa: 67, + 0x840: 82, + 0x841: 68, + 0x842: 68, + 0x843: 68, + 0x844: 68, + 0x845: 68, + 0x846: 82, + 0x847: 82, + 0x848: 68, + 0x849: 82, + 0x84a: 68, + 0x84b: 68, + 0x84c: 68, + 0x84d: 68, + 0x84e: 68, + 0x84f: 68, + 0x850: 68, + 0x851: 68, + 0x852: 68, + 0x853: 68, + 0x854: 82, + 0x855: 68, + 0x856: 82, + 0x857: 82, + 0x858: 82, + 0x860: 68, + 0x861: 85, + 0x862: 68, + 0x863: 68, + 0x864: 68, + 0x865: 68, + 0x866: 85, + 0x867: 82, + 0x868: 68, + 0x869: 82, + 0x86a: 82, + 0x8a0: 68, + 0x8a1: 68, + 0x8a2: 68, + 0x8a3: 68, + 0x8a4: 68, + 0x8a5: 68, + 0x8a6: 68, + 0x8a7: 68, + 0x8a8: 68, + 0x8a9: 68, + 0x8aa: 82, + 0x8ab: 82, + 0x8ac: 82, + 0x8ad: 85, + 0x8ae: 82, + 0x8af: 68, + 0x8b0: 68, + 0x8b1: 82, + 0x8b2: 82, + 0x8b3: 68, + 0x8b4: 68, + 0x8b6: 68, + 0x8b7: 68, + 0x8b8: 68, + 0x8b9: 82, + 0x8ba: 68, + 0x8bb: 68, + 0x8bc: 68, + 0x8bd: 68, + 0x8be: 68, + 0x8bf: 68, + 0x8c0: 68, + 0x8c1: 68, + 0x8c2: 68, + 0x8c3: 68, + 0x8c4: 68, + 0x8c5: 68, + 0x8c6: 68, + 0x8c7: 68, + 0x8e2: 85, + 0x1806: 85, + 0x1807: 68, + 0x180a: 67, + 0x180e: 85, + 0x1820: 68, + 0x1821: 68, + 0x1822: 68, + 0x1823: 68, + 0x1824: 68, + 0x1825: 68, + 0x1826: 68, + 0x1827: 68, + 0x1828: 68, + 0x1829: 68, + 0x182a: 68, + 0x182b: 68, + 0x182c: 68, + 0x182d: 68, + 0x182e: 68, + 0x182f: 68, + 0x1830: 68, + 0x1831: 68, + 0x1832: 68, + 0x1833: 68, + 0x1834: 68, + 0x1835: 68, + 0x1836: 68, + 0x1837: 68, + 0x1838: 68, + 0x1839: 68, + 0x183a: 68, + 0x183b: 68, + 0x183c: 68, + 0x183d: 68, + 0x183e: 68, + 0x183f: 68, + 0x1840: 68, + 0x1841: 68, + 0x1842: 68, + 0x1843: 68, + 0x1844: 68, + 0x1845: 68, + 0x1846: 68, + 0x1847: 68, + 0x1848: 68, + 0x1849: 68, + 0x184a: 68, + 0x184b: 68, + 0x184c: 68, + 0x184d: 68, + 0x184e: 68, + 0x184f: 68, + 0x1850: 68, + 0x1851: 68, + 0x1852: 68, + 0x1853: 68, + 0x1854: 68, + 0x1855: 68, + 0x1856: 68, + 0x1857: 68, + 0x1858: 68, + 0x1859: 68, + 0x185a: 68, + 0x185b: 68, + 0x185c: 68, + 0x185d: 68, + 0x185e: 68, + 0x185f: 68, + 0x1860: 68, + 0x1861: 68, + 0x1862: 68, + 0x1863: 68, + 0x1864: 68, + 0x1865: 68, + 0x1866: 68, + 0x1867: 68, + 0x1868: 68, + 0x1869: 68, + 0x186a: 68, + 0x186b: 68, + 0x186c: 68, + 0x186d: 68, + 0x186e: 68, + 0x186f: 68, + 0x1870: 68, + 0x1871: 68, + 0x1872: 68, + 0x1873: 68, + 0x1874: 68, + 0x1875: 68, + 0x1876: 68, + 0x1877: 68, + 0x1878: 68, + 0x1880: 85, + 0x1881: 85, + 0x1882: 85, + 0x1883: 85, + 0x1884: 85, + 0x1885: 84, + 0x1886: 84, + 0x1887: 68, + 0x1888: 68, + 0x1889: 68, + 0x188a: 68, + 0x188b: 68, + 0x188c: 68, + 0x188d: 68, + 0x188e: 68, + 0x188f: 68, + 0x1890: 68, + 0x1891: 68, + 0x1892: 68, + 0x1893: 68, + 0x1894: 68, + 0x1895: 68, + 0x1896: 68, + 0x1897: 68, + 0x1898: 68, + 0x1899: 68, + 0x189a: 68, + 0x189b: 68, + 0x189c: 68, + 0x189d: 68, + 0x189e: 68, + 0x189f: 68, + 0x18a0: 68, + 0x18a1: 68, + 0x18a2: 68, + 0x18a3: 68, + 0x18a4: 68, + 0x18a5: 68, + 0x18a6: 68, + 0x18a7: 68, + 0x18a8: 68, + 0x18aa: 68, + 0x200c: 85, + 0x200d: 67, + 0x202f: 85, + 0x2066: 85, + 0x2067: 85, + 0x2068: 85, + 0x2069: 85, + 0xa840: 68, + 0xa841: 68, + 0xa842: 68, + 0xa843: 68, + 0xa844: 68, + 0xa845: 68, + 0xa846: 68, + 0xa847: 68, + 0xa848: 68, + 0xa849: 68, + 0xa84a: 68, + 0xa84b: 68, + 0xa84c: 68, + 0xa84d: 68, + 0xa84e: 68, + 0xa84f: 68, + 0xa850: 68, + 0xa851: 68, + 0xa852: 68, + 0xa853: 68, + 0xa854: 68, + 0xa855: 68, + 0xa856: 68, + 0xa857: 68, + 0xa858: 68, + 0xa859: 68, + 0xa85a: 68, + 0xa85b: 68, + 0xa85c: 68, + 0xa85d: 68, + 0xa85e: 68, + 0xa85f: 68, + 0xa860: 68, + 0xa861: 68, + 0xa862: 68, + 0xa863: 68, + 0xa864: 68, + 0xa865: 68, + 0xa866: 68, + 0xa867: 68, + 0xa868: 68, + 0xa869: 68, + 0xa86a: 68, + 0xa86b: 68, + 0xa86c: 68, + 0xa86d: 68, + 0xa86e: 68, + 0xa86f: 68, + 0xa870: 68, + 0xa871: 68, + 0xa872: 76, + 0xa873: 85, + 0x10ac0: 68, + 0x10ac1: 68, + 0x10ac2: 68, + 0x10ac3: 68, + 0x10ac4: 68, + 0x10ac5: 82, + 0x10ac6: 85, + 0x10ac7: 82, + 0x10ac8: 85, + 0x10ac9: 82, + 0x10aca: 82, + 0x10acb: 85, + 0x10acc: 85, + 0x10acd: 76, + 0x10ace: 82, + 0x10acf: 82, + 0x10ad0: 82, + 0x10ad1: 82, + 0x10ad2: 82, + 0x10ad3: 68, + 0x10ad4: 68, + 0x10ad5: 68, + 0x10ad6: 68, + 0x10ad7: 76, + 0x10ad8: 68, + 0x10ad9: 68, + 0x10ada: 68, + 0x10adb: 68, + 0x10adc: 68, + 0x10add: 82, + 0x10ade: 68, + 0x10adf: 68, + 0x10ae0: 68, + 0x10ae1: 82, + 0x10ae2: 85, + 0x10ae3: 85, + 0x10ae4: 82, + 0x10aeb: 68, + 0x10aec: 68, + 0x10aed: 68, + 0x10aee: 68, + 0x10aef: 82, + 0x10b80: 68, + 0x10b81: 82, + 0x10b82: 68, + 0x10b83: 82, + 0x10b84: 82, + 0x10b85: 82, + 0x10b86: 68, + 0x10b87: 68, + 0x10b88: 68, + 0x10b89: 82, + 0x10b8a: 68, + 0x10b8b: 68, + 0x10b8c: 82, + 0x10b8d: 68, + 0x10b8e: 82, + 0x10b8f: 82, + 0x10b90: 68, + 0x10b91: 82, + 0x10ba9: 82, + 0x10baa: 82, + 0x10bab: 82, + 0x10bac: 82, + 0x10bad: 68, + 0x10bae: 68, + 0x10baf: 85, + 0x10d00: 76, + 0x10d01: 68, + 0x10d02: 68, + 0x10d03: 68, + 0x10d04: 68, + 0x10d05: 68, + 0x10d06: 68, + 0x10d07: 68, + 0x10d08: 68, + 0x10d09: 68, + 0x10d0a: 68, + 0x10d0b: 68, + 0x10d0c: 68, + 0x10d0d: 68, + 0x10d0e: 68, + 0x10d0f: 68, + 0x10d10: 68, + 0x10d11: 68, + 0x10d12: 68, + 0x10d13: 68, + 0x10d14: 68, + 0x10d15: 68, + 0x10d16: 68, + 0x10d17: 68, + 0x10d18: 68, + 0x10d19: 68, + 0x10d1a: 68, + 0x10d1b: 68, + 0x10d1c: 68, + 0x10d1d: 68, + 0x10d1e: 68, + 0x10d1f: 68, + 0x10d20: 68, + 0x10d21: 68, + 0x10d22: 82, + 0x10d23: 68, + 0x10f30: 68, + 0x10f31: 68, + 0x10f32: 68, + 0x10f33: 82, + 0x10f34: 68, + 0x10f35: 68, + 0x10f36: 68, + 0x10f37: 68, + 0x10f38: 68, + 0x10f39: 68, + 0x10f3a: 68, + 0x10f3b: 68, + 0x10f3c: 68, + 0x10f3d: 68, + 0x10f3e: 68, + 0x10f3f: 68, + 0x10f40: 68, + 0x10f41: 68, + 0x10f42: 68, + 0x10f43: 68, + 0x10f44: 68, + 0x10f45: 85, + 0x10f51: 68, + 0x10f52: 68, + 0x10f53: 68, + 0x10f54: 82, + 0x10fb0: 68, + 0x10fb1: 85, + 0x10fb2: 68, + 0x10fb3: 68, + 0x10fb4: 82, + 0x10fb5: 82, + 0x10fb6: 82, + 0x10fb7: 85, + 0x10fb8: 68, + 0x10fb9: 82, + 0x10fba: 82, + 0x10fbb: 68, + 0x10fbc: 68, + 0x10fbd: 82, + 0x10fbe: 68, + 0x10fbf: 68, + 0x10fc0: 85, + 0x10fc1: 68, + 0x10fc2: 82, + 0x10fc3: 82, + 0x10fc4: 68, + 0x10fc5: 85, + 0x10fc6: 85, + 0x10fc7: 85, + 0x10fc8: 85, + 0x10fc9: 82, + 0x10fca: 68, + 0x10fcb: 76, + 0x110bd: 85, + 0x110cd: 85, + 0x1e900: 68, + 0x1e901: 68, + 0x1e902: 68, + 0x1e903: 68, + 0x1e904: 68, + 0x1e905: 68, + 0x1e906: 68, + 0x1e907: 68, + 0x1e908: 68, + 0x1e909: 68, + 0x1e90a: 68, + 0x1e90b: 68, + 0x1e90c: 68, + 0x1e90d: 68, + 0x1e90e: 68, + 0x1e90f: 68, + 0x1e910: 68, + 0x1e911: 68, + 0x1e912: 68, + 0x1e913: 68, + 0x1e914: 68, + 0x1e915: 68, + 0x1e916: 68, + 0x1e917: 68, + 0x1e918: 68, + 0x1e919: 68, + 0x1e91a: 68, + 0x1e91b: 68, + 0x1e91c: 68, + 0x1e91d: 68, + 0x1e91e: 68, + 0x1e91f: 68, + 0x1e920: 68, + 0x1e921: 68, + 0x1e922: 68, + 0x1e923: 68, + 0x1e924: 68, + 0x1e925: 68, + 0x1e926: 68, + 0x1e927: 68, + 0x1e928: 68, + 0x1e929: 68, + 0x1e92a: 68, + 0x1e92b: 68, + 0x1e92c: 68, + 0x1e92d: 68, + 0x1e92e: 68, + 0x1e92f: 68, + 0x1e930: 68, + 0x1e931: 68, + 0x1e932: 68, + 0x1e933: 68, + 0x1e934: 68, + 0x1e935: 68, + 0x1e936: 68, + 0x1e937: 68, + 0x1e938: 68, + 0x1e939: 68, + 0x1e93a: 68, + 0x1e93b: 68, + 0x1e93c: 68, + 0x1e93d: 68, + 0x1e93e: 68, + 0x1e93f: 68, + 0x1e940: 68, + 0x1e941: 68, + 0x1e942: 68, + 0x1e943: 68, + 0x1e94b: 84, +} +codepoint_classes = { + 'PVALID': ( + 0x2d0000002e, + 0x300000003a, + 0x610000007b, + 0xdf000000f7, + 0xf800000100, + 0x10100000102, + 0x10300000104, + 0x10500000106, + 0x10700000108, + 0x1090000010a, + 0x10b0000010c, + 0x10d0000010e, + 0x10f00000110, + 0x11100000112, + 0x11300000114, + 0x11500000116, + 0x11700000118, + 0x1190000011a, + 0x11b0000011c, + 0x11d0000011e, + 0x11f00000120, + 0x12100000122, + 0x12300000124, + 0x12500000126, + 0x12700000128, + 0x1290000012a, + 0x12b0000012c, + 0x12d0000012e, + 0x12f00000130, + 0x13100000132, + 0x13500000136, + 0x13700000139, + 0x13a0000013b, + 0x13c0000013d, + 0x13e0000013f, + 0x14200000143, + 0x14400000145, + 0x14600000147, + 0x14800000149, + 0x14b0000014c, + 0x14d0000014e, + 0x14f00000150, + 0x15100000152, + 0x15300000154, + 0x15500000156, + 0x15700000158, + 0x1590000015a, + 0x15b0000015c, + 0x15d0000015e, + 0x15f00000160, + 0x16100000162, + 0x16300000164, + 0x16500000166, + 0x16700000168, + 0x1690000016a, + 0x16b0000016c, + 0x16d0000016e, + 0x16f00000170, + 0x17100000172, + 0x17300000174, + 0x17500000176, + 0x17700000178, + 0x17a0000017b, + 0x17c0000017d, + 0x17e0000017f, + 0x18000000181, + 0x18300000184, + 0x18500000186, + 0x18800000189, + 0x18c0000018e, + 0x19200000193, + 0x19500000196, + 0x1990000019c, + 0x19e0000019f, + 0x1a1000001a2, + 0x1a3000001a4, + 0x1a5000001a6, + 0x1a8000001a9, + 0x1aa000001ac, + 0x1ad000001ae, + 0x1b0000001b1, + 0x1b4000001b5, + 0x1b6000001b7, + 0x1b9000001bc, + 0x1bd000001c4, + 0x1ce000001cf, + 0x1d0000001d1, + 0x1d2000001d3, + 0x1d4000001d5, + 0x1d6000001d7, + 0x1d8000001d9, + 0x1da000001db, + 0x1dc000001de, + 0x1df000001e0, + 0x1e1000001e2, + 0x1e3000001e4, + 0x1e5000001e6, + 0x1e7000001e8, + 0x1e9000001ea, + 0x1eb000001ec, + 0x1ed000001ee, + 0x1ef000001f1, + 0x1f5000001f6, + 0x1f9000001fa, + 0x1fb000001fc, + 0x1fd000001fe, + 0x1ff00000200, + 0x20100000202, + 0x20300000204, + 0x20500000206, + 0x20700000208, + 0x2090000020a, + 0x20b0000020c, + 0x20d0000020e, + 0x20f00000210, + 0x21100000212, + 0x21300000214, + 0x21500000216, + 0x21700000218, + 0x2190000021a, + 0x21b0000021c, + 0x21d0000021e, + 0x21f00000220, + 0x22100000222, + 0x22300000224, + 0x22500000226, + 0x22700000228, + 0x2290000022a, + 0x22b0000022c, + 0x22d0000022e, + 0x22f00000230, + 0x23100000232, + 0x2330000023a, + 0x23c0000023d, + 0x23f00000241, + 0x24200000243, + 0x24700000248, + 0x2490000024a, + 0x24b0000024c, + 0x24d0000024e, + 0x24f000002b0, + 0x2b9000002c2, + 0x2c6000002d2, + 0x2ec000002ed, + 0x2ee000002ef, + 0x30000000340, + 0x34200000343, + 0x3460000034f, + 0x35000000370, + 0x37100000372, + 0x37300000374, + 0x37700000378, + 0x37b0000037e, + 0x39000000391, + 0x3ac000003cf, + 0x3d7000003d8, + 0x3d9000003da, + 0x3db000003dc, + 0x3dd000003de, + 0x3df000003e0, + 0x3e1000003e2, + 0x3e3000003e4, + 0x3e5000003e6, + 0x3e7000003e8, + 0x3e9000003ea, + 0x3eb000003ec, + 0x3ed000003ee, + 0x3ef000003f0, + 0x3f3000003f4, + 0x3f8000003f9, + 0x3fb000003fd, + 0x43000000460, + 0x46100000462, + 0x46300000464, + 0x46500000466, + 0x46700000468, + 0x4690000046a, + 0x46b0000046c, + 0x46d0000046e, + 0x46f00000470, + 0x47100000472, + 0x47300000474, + 0x47500000476, + 0x47700000478, + 0x4790000047a, + 0x47b0000047c, + 0x47d0000047e, + 0x47f00000480, + 0x48100000482, + 0x48300000488, + 0x48b0000048c, + 0x48d0000048e, + 0x48f00000490, + 0x49100000492, + 0x49300000494, + 0x49500000496, + 0x49700000498, + 0x4990000049a, + 0x49b0000049c, + 0x49d0000049e, + 0x49f000004a0, + 0x4a1000004a2, + 0x4a3000004a4, + 0x4a5000004a6, + 0x4a7000004a8, + 0x4a9000004aa, + 0x4ab000004ac, + 0x4ad000004ae, + 0x4af000004b0, + 0x4b1000004b2, + 0x4b3000004b4, + 0x4b5000004b6, + 0x4b7000004b8, + 0x4b9000004ba, + 0x4bb000004bc, + 0x4bd000004be, + 0x4bf000004c0, + 0x4c2000004c3, + 0x4c4000004c5, + 0x4c6000004c7, + 0x4c8000004c9, + 0x4ca000004cb, + 0x4cc000004cd, + 0x4ce000004d0, + 0x4d1000004d2, + 0x4d3000004d4, + 0x4d5000004d6, + 0x4d7000004d8, + 0x4d9000004da, + 0x4db000004dc, + 0x4dd000004de, + 0x4df000004e0, + 0x4e1000004e2, + 0x4e3000004e4, + 0x4e5000004e6, + 0x4e7000004e8, + 0x4e9000004ea, + 0x4eb000004ec, + 0x4ed000004ee, + 0x4ef000004f0, + 0x4f1000004f2, + 0x4f3000004f4, + 0x4f5000004f6, + 0x4f7000004f8, + 0x4f9000004fa, + 0x4fb000004fc, + 0x4fd000004fe, + 0x4ff00000500, + 0x50100000502, + 0x50300000504, + 0x50500000506, + 0x50700000508, + 0x5090000050a, + 0x50b0000050c, + 0x50d0000050e, + 0x50f00000510, + 0x51100000512, + 0x51300000514, + 0x51500000516, + 0x51700000518, + 0x5190000051a, + 0x51b0000051c, + 0x51d0000051e, + 0x51f00000520, + 0x52100000522, + 0x52300000524, + 0x52500000526, + 0x52700000528, + 0x5290000052a, + 0x52b0000052c, + 0x52d0000052e, + 0x52f00000530, + 0x5590000055a, + 0x56000000587, + 0x58800000589, + 0x591000005be, + 0x5bf000005c0, + 0x5c1000005c3, + 0x5c4000005c6, + 0x5c7000005c8, + 0x5d0000005eb, + 0x5ef000005f3, + 0x6100000061b, + 0x62000000640, + 0x64100000660, + 0x66e00000675, + 0x679000006d4, + 0x6d5000006dd, + 0x6df000006e9, + 0x6ea000006f0, + 0x6fa00000700, + 0x7100000074b, + 0x74d000007b2, + 0x7c0000007f6, + 0x7fd000007fe, + 0x8000000082e, + 0x8400000085c, + 0x8600000086b, + 0x8a0000008b5, + 0x8b6000008c8, + 0x8d3000008e2, + 0x8e300000958, + 0x96000000964, + 0x96600000970, + 0x97100000984, + 0x9850000098d, + 0x98f00000991, + 0x993000009a9, + 0x9aa000009b1, + 0x9b2000009b3, + 0x9b6000009ba, + 0x9bc000009c5, + 0x9c7000009c9, + 0x9cb000009cf, + 0x9d7000009d8, + 0x9e0000009e4, + 0x9e6000009f2, + 0x9fc000009fd, + 0x9fe000009ff, + 0xa0100000a04, + 0xa0500000a0b, + 0xa0f00000a11, + 0xa1300000a29, + 0xa2a00000a31, + 0xa3200000a33, + 0xa3500000a36, + 0xa3800000a3a, + 0xa3c00000a3d, + 0xa3e00000a43, + 0xa4700000a49, + 0xa4b00000a4e, + 0xa5100000a52, + 0xa5c00000a5d, + 0xa6600000a76, + 0xa8100000a84, + 0xa8500000a8e, + 0xa8f00000a92, + 0xa9300000aa9, + 0xaaa00000ab1, + 0xab200000ab4, + 0xab500000aba, + 0xabc00000ac6, + 0xac700000aca, + 0xacb00000ace, + 0xad000000ad1, + 0xae000000ae4, + 0xae600000af0, + 0xaf900000b00, + 0xb0100000b04, + 0xb0500000b0d, + 0xb0f00000b11, + 0xb1300000b29, + 0xb2a00000b31, + 0xb3200000b34, + 0xb3500000b3a, + 0xb3c00000b45, + 0xb4700000b49, + 0xb4b00000b4e, + 0xb5500000b58, + 0xb5f00000b64, + 0xb6600000b70, + 0xb7100000b72, + 0xb8200000b84, + 0xb8500000b8b, + 0xb8e00000b91, + 0xb9200000b96, + 0xb9900000b9b, + 0xb9c00000b9d, + 0xb9e00000ba0, + 0xba300000ba5, + 0xba800000bab, + 0xbae00000bba, + 0xbbe00000bc3, + 0xbc600000bc9, + 0xbca00000bce, + 0xbd000000bd1, + 0xbd700000bd8, + 0xbe600000bf0, + 0xc0000000c0d, + 0xc0e00000c11, + 0xc1200000c29, + 0xc2a00000c3a, + 0xc3d00000c45, + 0xc4600000c49, + 0xc4a00000c4e, + 0xc5500000c57, + 0xc5800000c5b, + 0xc6000000c64, + 0xc6600000c70, + 0xc8000000c84, + 0xc8500000c8d, + 0xc8e00000c91, + 0xc9200000ca9, + 0xcaa00000cb4, + 0xcb500000cba, + 0xcbc00000cc5, + 0xcc600000cc9, + 0xcca00000cce, + 0xcd500000cd7, + 0xcde00000cdf, + 0xce000000ce4, + 0xce600000cf0, + 0xcf100000cf3, + 0xd0000000d0d, + 0xd0e00000d11, + 0xd1200000d45, + 0xd4600000d49, + 0xd4a00000d4f, + 0xd5400000d58, + 0xd5f00000d64, + 0xd6600000d70, + 0xd7a00000d80, + 0xd8100000d84, + 0xd8500000d97, + 0xd9a00000db2, + 0xdb300000dbc, + 0xdbd00000dbe, + 0xdc000000dc7, + 0xdca00000dcb, + 0xdcf00000dd5, + 0xdd600000dd7, + 0xdd800000de0, + 0xde600000df0, + 0xdf200000df4, + 0xe0100000e33, + 0xe3400000e3b, + 0xe4000000e4f, + 0xe5000000e5a, + 0xe8100000e83, + 0xe8400000e85, + 0xe8600000e8b, + 0xe8c00000ea4, + 0xea500000ea6, + 0xea700000eb3, + 0xeb400000ebe, + 0xec000000ec5, + 0xec600000ec7, + 0xec800000ece, + 0xed000000eda, + 0xede00000ee0, + 0xf0000000f01, + 0xf0b00000f0c, + 0xf1800000f1a, + 0xf2000000f2a, + 0xf3500000f36, + 0xf3700000f38, + 0xf3900000f3a, + 0xf3e00000f43, + 0xf4400000f48, + 0xf4900000f4d, + 0xf4e00000f52, + 0xf5300000f57, + 0xf5800000f5c, + 0xf5d00000f69, + 0xf6a00000f6d, + 0xf7100000f73, + 0xf7400000f75, + 0xf7a00000f81, + 0xf8200000f85, + 0xf8600000f93, + 0xf9400000f98, + 0xf9900000f9d, + 0xf9e00000fa2, + 0xfa300000fa7, + 0xfa800000fac, + 0xfad00000fb9, + 0xfba00000fbd, + 0xfc600000fc7, + 0x10000000104a, + 0x10500000109e, + 0x10d0000010fb, + 0x10fd00001100, + 0x120000001249, + 0x124a0000124e, + 0x125000001257, + 0x125800001259, + 0x125a0000125e, + 0x126000001289, + 0x128a0000128e, + 0x1290000012b1, + 0x12b2000012b6, + 0x12b8000012bf, + 0x12c0000012c1, + 0x12c2000012c6, + 0x12c8000012d7, + 0x12d800001311, + 0x131200001316, + 0x13180000135b, + 0x135d00001360, + 0x138000001390, + 0x13a0000013f6, + 0x14010000166d, + 0x166f00001680, + 0x16810000169b, + 0x16a0000016eb, + 0x16f1000016f9, + 0x17000000170d, + 0x170e00001715, + 0x172000001735, + 0x174000001754, + 0x17600000176d, + 0x176e00001771, + 0x177200001774, + 0x1780000017b4, + 0x17b6000017d4, + 0x17d7000017d8, + 0x17dc000017de, + 0x17e0000017ea, + 0x18100000181a, + 0x182000001879, + 0x1880000018ab, + 0x18b0000018f6, + 0x19000000191f, + 0x19200000192c, + 0x19300000193c, + 0x19460000196e, + 0x197000001975, + 0x1980000019ac, + 0x19b0000019ca, + 0x19d0000019da, + 0x1a0000001a1c, + 0x1a2000001a5f, + 0x1a6000001a7d, + 0x1a7f00001a8a, + 0x1a9000001a9a, + 0x1aa700001aa8, + 0x1ab000001abe, + 0x1abf00001ac1, + 0x1b0000001b4c, + 0x1b5000001b5a, + 0x1b6b00001b74, + 0x1b8000001bf4, + 0x1c0000001c38, + 0x1c4000001c4a, + 0x1c4d00001c7e, + 0x1cd000001cd3, + 0x1cd400001cfb, + 0x1d0000001d2c, + 0x1d2f00001d30, + 0x1d3b00001d3c, + 0x1d4e00001d4f, + 0x1d6b00001d78, + 0x1d7900001d9b, + 0x1dc000001dfa, + 0x1dfb00001e00, + 0x1e0100001e02, + 0x1e0300001e04, + 0x1e0500001e06, + 0x1e0700001e08, + 0x1e0900001e0a, + 0x1e0b00001e0c, + 0x1e0d00001e0e, + 0x1e0f00001e10, + 0x1e1100001e12, + 0x1e1300001e14, + 0x1e1500001e16, + 0x1e1700001e18, + 0x1e1900001e1a, + 0x1e1b00001e1c, + 0x1e1d00001e1e, + 0x1e1f00001e20, + 0x1e2100001e22, + 0x1e2300001e24, + 0x1e2500001e26, + 0x1e2700001e28, + 0x1e2900001e2a, + 0x1e2b00001e2c, + 0x1e2d00001e2e, + 0x1e2f00001e30, + 0x1e3100001e32, + 0x1e3300001e34, + 0x1e3500001e36, + 0x1e3700001e38, + 0x1e3900001e3a, + 0x1e3b00001e3c, + 0x1e3d00001e3e, + 0x1e3f00001e40, + 0x1e4100001e42, + 0x1e4300001e44, + 0x1e4500001e46, + 0x1e4700001e48, + 0x1e4900001e4a, + 0x1e4b00001e4c, + 0x1e4d00001e4e, + 0x1e4f00001e50, + 0x1e5100001e52, + 0x1e5300001e54, + 0x1e5500001e56, + 0x1e5700001e58, + 0x1e5900001e5a, + 0x1e5b00001e5c, + 0x1e5d00001e5e, + 0x1e5f00001e60, + 0x1e6100001e62, + 0x1e6300001e64, + 0x1e6500001e66, + 0x1e6700001e68, + 0x1e6900001e6a, + 0x1e6b00001e6c, + 0x1e6d00001e6e, + 0x1e6f00001e70, + 0x1e7100001e72, + 0x1e7300001e74, + 0x1e7500001e76, + 0x1e7700001e78, + 0x1e7900001e7a, + 0x1e7b00001e7c, + 0x1e7d00001e7e, + 0x1e7f00001e80, + 0x1e8100001e82, + 0x1e8300001e84, + 0x1e8500001e86, + 0x1e8700001e88, + 0x1e8900001e8a, + 0x1e8b00001e8c, + 0x1e8d00001e8e, + 0x1e8f00001e90, + 0x1e9100001e92, + 0x1e9300001e94, + 0x1e9500001e9a, + 0x1e9c00001e9e, + 0x1e9f00001ea0, + 0x1ea100001ea2, + 0x1ea300001ea4, + 0x1ea500001ea6, + 0x1ea700001ea8, + 0x1ea900001eaa, + 0x1eab00001eac, + 0x1ead00001eae, + 0x1eaf00001eb0, + 0x1eb100001eb2, + 0x1eb300001eb4, + 0x1eb500001eb6, + 0x1eb700001eb8, + 0x1eb900001eba, + 0x1ebb00001ebc, + 0x1ebd00001ebe, + 0x1ebf00001ec0, + 0x1ec100001ec2, + 0x1ec300001ec4, + 0x1ec500001ec6, + 0x1ec700001ec8, + 0x1ec900001eca, + 0x1ecb00001ecc, + 0x1ecd00001ece, + 0x1ecf00001ed0, + 0x1ed100001ed2, + 0x1ed300001ed4, + 0x1ed500001ed6, + 0x1ed700001ed8, + 0x1ed900001eda, + 0x1edb00001edc, + 0x1edd00001ede, + 0x1edf00001ee0, + 0x1ee100001ee2, + 0x1ee300001ee4, + 0x1ee500001ee6, + 0x1ee700001ee8, + 0x1ee900001eea, + 0x1eeb00001eec, + 0x1eed00001eee, + 0x1eef00001ef0, + 0x1ef100001ef2, + 0x1ef300001ef4, + 0x1ef500001ef6, + 0x1ef700001ef8, + 0x1ef900001efa, + 0x1efb00001efc, + 0x1efd00001efe, + 0x1eff00001f08, + 0x1f1000001f16, + 0x1f2000001f28, + 0x1f3000001f38, + 0x1f4000001f46, + 0x1f5000001f58, + 0x1f6000001f68, + 0x1f7000001f71, + 0x1f7200001f73, + 0x1f7400001f75, + 0x1f7600001f77, + 0x1f7800001f79, + 0x1f7a00001f7b, + 0x1f7c00001f7d, + 0x1fb000001fb2, + 0x1fb600001fb7, + 0x1fc600001fc7, + 0x1fd000001fd3, + 0x1fd600001fd8, + 0x1fe000001fe3, + 0x1fe400001fe8, + 0x1ff600001ff7, + 0x214e0000214f, + 0x218400002185, + 0x2c3000002c5f, + 0x2c6100002c62, + 0x2c6500002c67, + 0x2c6800002c69, + 0x2c6a00002c6b, + 0x2c6c00002c6d, + 0x2c7100002c72, + 0x2c7300002c75, + 0x2c7600002c7c, + 0x2c8100002c82, + 0x2c8300002c84, + 0x2c8500002c86, + 0x2c8700002c88, + 0x2c8900002c8a, + 0x2c8b00002c8c, + 0x2c8d00002c8e, + 0x2c8f00002c90, + 0x2c9100002c92, + 0x2c9300002c94, + 0x2c9500002c96, + 0x2c9700002c98, + 0x2c9900002c9a, + 0x2c9b00002c9c, + 0x2c9d00002c9e, + 0x2c9f00002ca0, + 0x2ca100002ca2, + 0x2ca300002ca4, + 0x2ca500002ca6, + 0x2ca700002ca8, + 0x2ca900002caa, + 0x2cab00002cac, + 0x2cad00002cae, + 0x2caf00002cb0, + 0x2cb100002cb2, + 0x2cb300002cb4, + 0x2cb500002cb6, + 0x2cb700002cb8, + 0x2cb900002cba, + 0x2cbb00002cbc, + 0x2cbd00002cbe, + 0x2cbf00002cc0, + 0x2cc100002cc2, + 0x2cc300002cc4, + 0x2cc500002cc6, + 0x2cc700002cc8, + 0x2cc900002cca, + 0x2ccb00002ccc, + 0x2ccd00002cce, + 0x2ccf00002cd0, + 0x2cd100002cd2, + 0x2cd300002cd4, + 0x2cd500002cd6, + 0x2cd700002cd8, + 0x2cd900002cda, + 0x2cdb00002cdc, + 0x2cdd00002cde, + 0x2cdf00002ce0, + 0x2ce100002ce2, + 0x2ce300002ce5, + 0x2cec00002ced, + 0x2cee00002cf2, + 0x2cf300002cf4, + 0x2d0000002d26, + 0x2d2700002d28, + 0x2d2d00002d2e, + 0x2d3000002d68, + 0x2d7f00002d97, + 0x2da000002da7, + 0x2da800002daf, + 0x2db000002db7, + 0x2db800002dbf, + 0x2dc000002dc7, + 0x2dc800002dcf, + 0x2dd000002dd7, + 0x2dd800002ddf, + 0x2de000002e00, + 0x2e2f00002e30, + 0x300500003008, + 0x302a0000302e, + 0x303c0000303d, + 0x304100003097, + 0x30990000309b, + 0x309d0000309f, + 0x30a1000030fb, + 0x30fc000030ff, + 0x310500003130, + 0x31a0000031c0, + 0x31f000003200, + 0x340000004dc0, + 0x4e0000009ffd, + 0xa0000000a48d, + 0xa4d00000a4fe, + 0xa5000000a60d, + 0xa6100000a62c, + 0xa6410000a642, + 0xa6430000a644, + 0xa6450000a646, + 0xa6470000a648, + 0xa6490000a64a, + 0xa64b0000a64c, + 0xa64d0000a64e, + 0xa64f0000a650, + 0xa6510000a652, + 0xa6530000a654, + 0xa6550000a656, + 0xa6570000a658, + 0xa6590000a65a, + 0xa65b0000a65c, + 0xa65d0000a65e, + 0xa65f0000a660, + 0xa6610000a662, + 0xa6630000a664, + 0xa6650000a666, + 0xa6670000a668, + 0xa6690000a66a, + 0xa66b0000a66c, + 0xa66d0000a670, + 0xa6740000a67e, + 0xa67f0000a680, + 0xa6810000a682, + 0xa6830000a684, + 0xa6850000a686, + 0xa6870000a688, + 0xa6890000a68a, + 0xa68b0000a68c, + 0xa68d0000a68e, + 0xa68f0000a690, + 0xa6910000a692, + 0xa6930000a694, + 0xa6950000a696, + 0xa6970000a698, + 0xa6990000a69a, + 0xa69b0000a69c, + 0xa69e0000a6e6, + 0xa6f00000a6f2, + 0xa7170000a720, + 0xa7230000a724, + 0xa7250000a726, + 0xa7270000a728, + 0xa7290000a72a, + 0xa72b0000a72c, + 0xa72d0000a72e, + 0xa72f0000a732, + 0xa7330000a734, + 0xa7350000a736, + 0xa7370000a738, + 0xa7390000a73a, + 0xa73b0000a73c, + 0xa73d0000a73e, + 0xa73f0000a740, + 0xa7410000a742, + 0xa7430000a744, + 0xa7450000a746, + 0xa7470000a748, + 0xa7490000a74a, + 0xa74b0000a74c, + 0xa74d0000a74e, + 0xa74f0000a750, + 0xa7510000a752, + 0xa7530000a754, + 0xa7550000a756, + 0xa7570000a758, + 0xa7590000a75a, + 0xa75b0000a75c, + 0xa75d0000a75e, + 0xa75f0000a760, + 0xa7610000a762, + 0xa7630000a764, + 0xa7650000a766, + 0xa7670000a768, + 0xa7690000a76a, + 0xa76b0000a76c, + 0xa76d0000a76e, + 0xa76f0000a770, + 0xa7710000a779, + 0xa77a0000a77b, + 0xa77c0000a77d, + 0xa77f0000a780, + 0xa7810000a782, + 0xa7830000a784, + 0xa7850000a786, + 0xa7870000a789, + 0xa78c0000a78d, + 0xa78e0000a790, + 0xa7910000a792, + 0xa7930000a796, + 0xa7970000a798, + 0xa7990000a79a, + 0xa79b0000a79c, + 0xa79d0000a79e, + 0xa79f0000a7a0, + 0xa7a10000a7a2, + 0xa7a30000a7a4, + 0xa7a50000a7a6, + 0xa7a70000a7a8, + 0xa7a90000a7aa, + 0xa7af0000a7b0, + 0xa7b50000a7b6, + 0xa7b70000a7b8, + 0xa7b90000a7ba, + 0xa7bb0000a7bc, + 0xa7bd0000a7be, + 0xa7bf0000a7c0, + 0xa7c30000a7c4, + 0xa7c80000a7c9, + 0xa7ca0000a7cb, + 0xa7f60000a7f8, + 0xa7fa0000a828, + 0xa82c0000a82d, + 0xa8400000a874, + 0xa8800000a8c6, + 0xa8d00000a8da, + 0xa8e00000a8f8, + 0xa8fb0000a8fc, + 0xa8fd0000a92e, + 0xa9300000a954, + 0xa9800000a9c1, + 0xa9cf0000a9da, + 0xa9e00000a9ff, + 0xaa000000aa37, + 0xaa400000aa4e, + 0xaa500000aa5a, + 0xaa600000aa77, + 0xaa7a0000aac3, + 0xaadb0000aade, + 0xaae00000aaf0, + 0xaaf20000aaf7, + 0xab010000ab07, + 0xab090000ab0f, + 0xab110000ab17, + 0xab200000ab27, + 0xab280000ab2f, + 0xab300000ab5b, + 0xab600000ab6a, + 0xabc00000abeb, + 0xabec0000abee, + 0xabf00000abfa, + 0xac000000d7a4, + 0xfa0e0000fa10, + 0xfa110000fa12, + 0xfa130000fa15, + 0xfa1f0000fa20, + 0xfa210000fa22, + 0xfa230000fa25, + 0xfa270000fa2a, + 0xfb1e0000fb1f, + 0xfe200000fe30, + 0xfe730000fe74, + 0x100000001000c, + 0x1000d00010027, + 0x100280001003b, + 0x1003c0001003e, + 0x1003f0001004e, + 0x100500001005e, + 0x10080000100fb, + 0x101fd000101fe, + 0x102800001029d, + 0x102a0000102d1, + 0x102e0000102e1, + 0x1030000010320, + 0x1032d00010341, + 0x103420001034a, + 0x103500001037b, + 0x103800001039e, + 0x103a0000103c4, + 0x103c8000103d0, + 0x104280001049e, + 0x104a0000104aa, + 0x104d8000104fc, + 0x1050000010528, + 0x1053000010564, + 0x1060000010737, + 0x1074000010756, + 0x1076000010768, + 0x1080000010806, + 0x1080800010809, + 0x1080a00010836, + 0x1083700010839, + 0x1083c0001083d, + 0x1083f00010856, + 0x1086000010877, + 0x108800001089f, + 0x108e0000108f3, + 0x108f4000108f6, + 0x1090000010916, + 0x109200001093a, + 0x10980000109b8, + 0x109be000109c0, + 0x10a0000010a04, + 0x10a0500010a07, + 0x10a0c00010a14, + 0x10a1500010a18, + 0x10a1900010a36, + 0x10a3800010a3b, + 0x10a3f00010a40, + 0x10a6000010a7d, + 0x10a8000010a9d, + 0x10ac000010ac8, + 0x10ac900010ae7, + 0x10b0000010b36, + 0x10b4000010b56, + 0x10b6000010b73, + 0x10b8000010b92, + 0x10c0000010c49, + 0x10cc000010cf3, + 0x10d0000010d28, + 0x10d3000010d3a, + 0x10e8000010eaa, + 0x10eab00010ead, + 0x10eb000010eb2, + 0x10f0000010f1d, + 0x10f2700010f28, + 0x10f3000010f51, + 0x10fb000010fc5, + 0x10fe000010ff7, + 0x1100000011047, + 0x1106600011070, + 0x1107f000110bb, + 0x110d0000110e9, + 0x110f0000110fa, + 0x1110000011135, + 0x1113600011140, + 0x1114400011148, + 0x1115000011174, + 0x1117600011177, + 0x11180000111c5, + 0x111c9000111cd, + 0x111ce000111db, + 0x111dc000111dd, + 0x1120000011212, + 0x1121300011238, + 0x1123e0001123f, + 0x1128000011287, + 0x1128800011289, + 0x1128a0001128e, + 0x1128f0001129e, + 0x1129f000112a9, + 0x112b0000112eb, + 0x112f0000112fa, + 0x1130000011304, + 0x113050001130d, + 0x1130f00011311, + 0x1131300011329, + 0x1132a00011331, + 0x1133200011334, + 0x113350001133a, + 0x1133b00011345, + 0x1134700011349, + 0x1134b0001134e, + 0x1135000011351, + 0x1135700011358, + 0x1135d00011364, + 0x113660001136d, + 0x1137000011375, + 0x114000001144b, + 0x114500001145a, + 0x1145e00011462, + 0x11480000114c6, + 0x114c7000114c8, + 0x114d0000114da, + 0x11580000115b6, + 0x115b8000115c1, + 0x115d8000115de, + 0x1160000011641, + 0x1164400011645, + 0x116500001165a, + 0x11680000116b9, + 0x116c0000116ca, + 0x117000001171b, + 0x1171d0001172c, + 0x117300001173a, + 0x118000001183b, + 0x118c0000118ea, + 0x118ff00011907, + 0x119090001190a, + 0x1190c00011914, + 0x1191500011917, + 0x1191800011936, + 0x1193700011939, + 0x1193b00011944, + 0x119500001195a, + 0x119a0000119a8, + 0x119aa000119d8, + 0x119da000119e2, + 0x119e3000119e5, + 0x11a0000011a3f, + 0x11a4700011a48, + 0x11a5000011a9a, + 0x11a9d00011a9e, + 0x11ac000011af9, + 0x11c0000011c09, + 0x11c0a00011c37, + 0x11c3800011c41, + 0x11c5000011c5a, + 0x11c7200011c90, + 0x11c9200011ca8, + 0x11ca900011cb7, + 0x11d0000011d07, + 0x11d0800011d0a, + 0x11d0b00011d37, + 0x11d3a00011d3b, + 0x11d3c00011d3e, + 0x11d3f00011d48, + 0x11d5000011d5a, + 0x11d6000011d66, + 0x11d6700011d69, + 0x11d6a00011d8f, + 0x11d9000011d92, + 0x11d9300011d99, + 0x11da000011daa, + 0x11ee000011ef7, + 0x11fb000011fb1, + 0x120000001239a, + 0x1248000012544, + 0x130000001342f, + 0x1440000014647, + 0x1680000016a39, + 0x16a4000016a5f, + 0x16a6000016a6a, + 0x16ad000016aee, + 0x16af000016af5, + 0x16b0000016b37, + 0x16b4000016b44, + 0x16b5000016b5a, + 0x16b6300016b78, + 0x16b7d00016b90, + 0x16e6000016e80, + 0x16f0000016f4b, + 0x16f4f00016f88, + 0x16f8f00016fa0, + 0x16fe000016fe2, + 0x16fe300016fe5, + 0x16ff000016ff2, + 0x17000000187f8, + 0x1880000018cd6, + 0x18d0000018d09, + 0x1b0000001b11f, + 0x1b1500001b153, + 0x1b1640001b168, + 0x1b1700001b2fc, + 0x1bc000001bc6b, + 0x1bc700001bc7d, + 0x1bc800001bc89, + 0x1bc900001bc9a, + 0x1bc9d0001bc9f, + 0x1da000001da37, + 0x1da3b0001da6d, + 0x1da750001da76, + 0x1da840001da85, + 0x1da9b0001daa0, + 0x1daa10001dab0, + 0x1e0000001e007, + 0x1e0080001e019, + 0x1e01b0001e022, + 0x1e0230001e025, + 0x1e0260001e02b, + 0x1e1000001e12d, + 0x1e1300001e13e, + 0x1e1400001e14a, + 0x1e14e0001e14f, + 0x1e2c00001e2fa, + 0x1e8000001e8c5, + 0x1e8d00001e8d7, + 0x1e9220001e94c, + 0x1e9500001e95a, + 0x1fbf00001fbfa, + 0x200000002a6de, + 0x2a7000002b735, + 0x2b7400002b81e, + 0x2b8200002cea2, + 0x2ceb00002ebe1, + 0x300000003134b, + ), + 'CONTEXTJ': ( + 0x200c0000200e, + ), + 'CONTEXTO': ( + 0xb7000000b8, + 0x37500000376, + 0x5f3000005f5, + 0x6600000066a, + 0x6f0000006fa, + 0x30fb000030fc, + ), +} diff --git a/openpype/vendor/python/python_2/idna/intranges.py b/openpype/vendor/python/python_2/idna/intranges.py new file mode 100644 index 0000000000..fa8a735662 --- /dev/null +++ b/openpype/vendor/python/python_2/idna/intranges.py @@ -0,0 +1,53 @@ +""" +Given a list of integers, made up of (hopefully) a small number of long runs +of consecutive integers, compute a representation of the form +((start1, end1), (start2, end2) ...). Then answer the question "was x present +in the original list?" in time O(log(# runs)). +""" + +import bisect + +def intranges_from_list(list_): + """Represent a list of integers as a sequence of ranges: + ((start_0, end_0), (start_1, end_1), ...), such that the original + integers are exactly those x such that start_i <= x < end_i for some i. + + Ranges are encoded as single integers (start << 32 | end), not as tuples. + """ + + sorted_list = sorted(list_) + ranges = [] + last_write = -1 + for i in range(len(sorted_list)): + if i+1 < len(sorted_list): + if sorted_list[i] == sorted_list[i+1]-1: + continue + current_range = sorted_list[last_write+1:i+1] + ranges.append(_encode_range(current_range[0], current_range[-1] + 1)) + last_write = i + + return tuple(ranges) + +def _encode_range(start, end): + return (start << 32) | end + +def _decode_range(r): + return (r >> 32), (r & ((1 << 32) - 1)) + + +def intranges_contain(int_, ranges): + """Determine if `int_` falls into one of the ranges in `ranges`.""" + tuple_ = _encode_range(int_, 0) + pos = bisect.bisect_left(ranges, tuple_) + # we could be immediately ahead of a tuple (start, end) + # with start < int_ <= end + if pos > 0: + left, right = _decode_range(ranges[pos-1]) + if left <= int_ < right: + return True + # or we could be immediately behind a tuple (int_, end) + if pos < len(ranges): + left, _ = _decode_range(ranges[pos]) + if left == int_: + return True + return False diff --git a/openpype/vendor/python/python_2/idna/package_data.py b/openpype/vendor/python/python_2/idna/package_data.py new file mode 100644 index 0000000000..ce1c521d23 --- /dev/null +++ b/openpype/vendor/python/python_2/idna/package_data.py @@ -0,0 +1,2 @@ +__version__ = '2.10' + diff --git a/openpype/vendor/python/python_2/idna/uts46data.py b/openpype/vendor/python/python_2/idna/uts46data.py new file mode 100644 index 0000000000..3766dd49f6 --- /dev/null +++ b/openpype/vendor/python/python_2/idna/uts46data.py @@ -0,0 +1,8357 @@ +# This file is automatically generated by tools/idna-data +# vim: set fileencoding=utf-8 : + +"""IDNA Mapping Table from UTS46.""" + + +__version__ = "13.0.0" +def _seg_0(): + return [ + (0x0, '3'), + (0x1, '3'), + (0x2, '3'), + (0x3, '3'), + (0x4, '3'), + (0x5, '3'), + (0x6, '3'), + (0x7, '3'), + (0x8, '3'), + (0x9, '3'), + (0xA, '3'), + (0xB, '3'), + (0xC, '3'), + (0xD, '3'), + (0xE, '3'), + (0xF, '3'), + (0x10, '3'), + (0x11, '3'), + (0x12, '3'), + (0x13, '3'), + (0x14, '3'), + (0x15, '3'), + (0x16, '3'), + (0x17, '3'), + (0x18, '3'), + (0x19, '3'), + (0x1A, '3'), + (0x1B, '3'), + (0x1C, '3'), + (0x1D, '3'), + (0x1E, '3'), + (0x1F, '3'), + (0x20, '3'), + (0x21, '3'), + (0x22, '3'), + (0x23, '3'), + (0x24, '3'), + (0x25, '3'), + (0x26, '3'), + (0x27, '3'), + (0x28, '3'), + (0x29, '3'), + (0x2A, '3'), + (0x2B, '3'), + (0x2C, '3'), + (0x2D, 'V'), + (0x2E, 'V'), + (0x2F, '3'), + (0x30, 'V'), + (0x31, 'V'), + (0x32, 'V'), + (0x33, 'V'), + (0x34, 'V'), + (0x35, 'V'), + (0x36, 'V'), + (0x37, 'V'), + (0x38, 'V'), + (0x39, 'V'), + (0x3A, '3'), + (0x3B, '3'), + (0x3C, '3'), + (0x3D, '3'), + (0x3E, '3'), + (0x3F, '3'), + (0x40, '3'), + (0x41, 'M', u'a'), + (0x42, 'M', u'b'), + (0x43, 'M', u'c'), + (0x44, 'M', u'd'), + (0x45, 'M', u'e'), + (0x46, 'M', u'f'), + (0x47, 'M', u'g'), + (0x48, 'M', u'h'), + (0x49, 'M', u'i'), + (0x4A, 'M', u'j'), + (0x4B, 'M', u'k'), + (0x4C, 'M', u'l'), + (0x4D, 'M', u'm'), + (0x4E, 'M', u'n'), + (0x4F, 'M', u'o'), + (0x50, 'M', u'p'), + (0x51, 'M', u'q'), + (0x52, 'M', u'r'), + (0x53, 'M', u's'), + (0x54, 'M', u't'), + (0x55, 'M', u'u'), + (0x56, 'M', u'v'), + (0x57, 'M', u'w'), + (0x58, 'M', u'x'), + (0x59, 'M', u'y'), + (0x5A, 'M', u'z'), + (0x5B, '3'), + (0x5C, '3'), + (0x5D, '3'), + (0x5E, '3'), + (0x5F, '3'), + (0x60, '3'), + (0x61, 'V'), + (0x62, 'V'), + (0x63, 'V'), + ] + +def _seg_1(): + return [ + (0x64, 'V'), + (0x65, 'V'), + (0x66, 'V'), + (0x67, 'V'), + (0x68, 'V'), + (0x69, 'V'), + (0x6A, 'V'), + (0x6B, 'V'), + (0x6C, 'V'), + (0x6D, 'V'), + (0x6E, 'V'), + (0x6F, 'V'), + (0x70, 'V'), + (0x71, 'V'), + (0x72, 'V'), + (0x73, 'V'), + (0x74, 'V'), + (0x75, 'V'), + (0x76, 'V'), + (0x77, 'V'), + (0x78, 'V'), + (0x79, 'V'), + (0x7A, 'V'), + (0x7B, '3'), + (0x7C, '3'), + (0x7D, '3'), + (0x7E, '3'), + (0x7F, '3'), + (0x80, 'X'), + (0x81, 'X'), + (0x82, 'X'), + (0x83, 'X'), + (0x84, 'X'), + (0x85, 'X'), + (0x86, 'X'), + (0x87, 'X'), + (0x88, 'X'), + (0x89, 'X'), + (0x8A, 'X'), + (0x8B, 'X'), + (0x8C, 'X'), + (0x8D, 'X'), + (0x8E, 'X'), + (0x8F, 'X'), + (0x90, 'X'), + (0x91, 'X'), + (0x92, 'X'), + (0x93, 'X'), + (0x94, 'X'), + (0x95, 'X'), + (0x96, 'X'), + (0x97, 'X'), + (0x98, 'X'), + (0x99, 'X'), + (0x9A, 'X'), + (0x9B, 'X'), + (0x9C, 'X'), + (0x9D, 'X'), + (0x9E, 'X'), + (0x9F, 'X'), + (0xA0, '3', u' '), + (0xA1, 'V'), + (0xA2, 'V'), + (0xA3, 'V'), + (0xA4, 'V'), + (0xA5, 'V'), + (0xA6, 'V'), + (0xA7, 'V'), + (0xA8, '3', u' ̈'), + (0xA9, 'V'), + (0xAA, 'M', u'a'), + (0xAB, 'V'), + (0xAC, 'V'), + (0xAD, 'I'), + (0xAE, 'V'), + (0xAF, '3', u' ̄'), + (0xB0, 'V'), + (0xB1, 'V'), + (0xB2, 'M', u'2'), + (0xB3, 'M', u'3'), + (0xB4, '3', u' ́'), + (0xB5, 'M', u'μ'), + (0xB6, 'V'), + (0xB7, 'V'), + (0xB8, '3', u' ̧'), + (0xB9, 'M', u'1'), + (0xBA, 'M', u'o'), + (0xBB, 'V'), + (0xBC, 'M', u'1⁄4'), + (0xBD, 'M', u'1⁄2'), + (0xBE, 'M', u'3⁄4'), + (0xBF, 'V'), + (0xC0, 'M', u'à'), + (0xC1, 'M', u'á'), + (0xC2, 'M', u'â'), + (0xC3, 'M', u'ã'), + (0xC4, 'M', u'ä'), + (0xC5, 'M', u'å'), + (0xC6, 'M', u'æ'), + (0xC7, 'M', u'ç'), + ] + +def _seg_2(): + return [ + (0xC8, 'M', u'è'), + (0xC9, 'M', u'é'), + (0xCA, 'M', u'ê'), + (0xCB, 'M', u'ë'), + (0xCC, 'M', u'ì'), + (0xCD, 'M', u'í'), + (0xCE, 'M', u'î'), + (0xCF, 'M', u'ï'), + (0xD0, 'M', u'ð'), + (0xD1, 'M', u'ñ'), + (0xD2, 'M', u'ò'), + (0xD3, 'M', u'ó'), + (0xD4, 'M', u'ô'), + (0xD5, 'M', u'õ'), + (0xD6, 'M', u'ö'), + (0xD7, 'V'), + (0xD8, 'M', u'ø'), + (0xD9, 'M', u'ù'), + (0xDA, 'M', u'ú'), + (0xDB, 'M', u'û'), + (0xDC, 'M', u'ü'), + (0xDD, 'M', u'ý'), + (0xDE, 'M', u'þ'), + (0xDF, 'D', u'ss'), + (0xE0, 'V'), + (0xE1, 'V'), + (0xE2, 'V'), + (0xE3, 'V'), + (0xE4, 'V'), + (0xE5, 'V'), + (0xE6, 'V'), + (0xE7, 'V'), + (0xE8, 'V'), + (0xE9, 'V'), + (0xEA, 'V'), + (0xEB, 'V'), + (0xEC, 'V'), + (0xED, 'V'), + (0xEE, 'V'), + (0xEF, 'V'), + (0xF0, 'V'), + (0xF1, 'V'), + (0xF2, 'V'), + (0xF3, 'V'), + (0xF4, 'V'), + (0xF5, 'V'), + (0xF6, 'V'), + (0xF7, 'V'), + (0xF8, 'V'), + (0xF9, 'V'), + (0xFA, 'V'), + (0xFB, 'V'), + (0xFC, 'V'), + (0xFD, 'V'), + (0xFE, 'V'), + (0xFF, 'V'), + (0x100, 'M', u'ā'), + (0x101, 'V'), + (0x102, 'M', u'ă'), + (0x103, 'V'), + (0x104, 'M', u'ą'), + (0x105, 'V'), + (0x106, 'M', u'ć'), + (0x107, 'V'), + (0x108, 'M', u'ĉ'), + (0x109, 'V'), + (0x10A, 'M', u'ċ'), + (0x10B, 'V'), + (0x10C, 'M', u'č'), + (0x10D, 'V'), + (0x10E, 'M', u'ď'), + (0x10F, 'V'), + (0x110, 'M', u'đ'), + (0x111, 'V'), + (0x112, 'M', u'ē'), + (0x113, 'V'), + (0x114, 'M', u'ĕ'), + (0x115, 'V'), + (0x116, 'M', u'ė'), + (0x117, 'V'), + (0x118, 'M', u'ę'), + (0x119, 'V'), + (0x11A, 'M', u'ě'), + (0x11B, 'V'), + (0x11C, 'M', u'ĝ'), + (0x11D, 'V'), + (0x11E, 'M', u'ğ'), + (0x11F, 'V'), + (0x120, 'M', u'ġ'), + (0x121, 'V'), + (0x122, 'M', u'ģ'), + (0x123, 'V'), + (0x124, 'M', u'ĥ'), + (0x125, 'V'), + (0x126, 'M', u'ħ'), + (0x127, 'V'), + (0x128, 'M', u'ĩ'), + (0x129, 'V'), + (0x12A, 'M', u'ī'), + (0x12B, 'V'), + ] + +def _seg_3(): + return [ + (0x12C, 'M', u'ĭ'), + (0x12D, 'V'), + (0x12E, 'M', u'į'), + (0x12F, 'V'), + (0x130, 'M', u'i̇'), + (0x131, 'V'), + (0x132, 'M', u'ij'), + (0x134, 'M', u'ĵ'), + (0x135, 'V'), + (0x136, 'M', u'ķ'), + (0x137, 'V'), + (0x139, 'M', u'ĺ'), + (0x13A, 'V'), + (0x13B, 'M', u'ļ'), + (0x13C, 'V'), + (0x13D, 'M', u'ľ'), + (0x13E, 'V'), + (0x13F, 'M', u'l·'), + (0x141, 'M', u'ł'), + (0x142, 'V'), + (0x143, 'M', u'ń'), + (0x144, 'V'), + (0x145, 'M', u'ņ'), + (0x146, 'V'), + (0x147, 'M', u'ň'), + (0x148, 'V'), + (0x149, 'M', u'ʼn'), + (0x14A, 'M', u'ŋ'), + (0x14B, 'V'), + (0x14C, 'M', u'ō'), + (0x14D, 'V'), + (0x14E, 'M', u'ŏ'), + (0x14F, 'V'), + (0x150, 'M', u'ő'), + (0x151, 'V'), + (0x152, 'M', u'œ'), + (0x153, 'V'), + (0x154, 'M', u'ŕ'), + (0x155, 'V'), + (0x156, 'M', u'ŗ'), + (0x157, 'V'), + (0x158, 'M', u'ř'), + (0x159, 'V'), + (0x15A, 'M', u'ś'), + (0x15B, 'V'), + (0x15C, 'M', u'ŝ'), + (0x15D, 'V'), + (0x15E, 'M', u'ş'), + (0x15F, 'V'), + (0x160, 'M', u'š'), + (0x161, 'V'), + (0x162, 'M', u'ţ'), + (0x163, 'V'), + (0x164, 'M', u'ť'), + (0x165, 'V'), + (0x166, 'M', u'ŧ'), + (0x167, 'V'), + (0x168, 'M', u'ũ'), + (0x169, 'V'), + (0x16A, 'M', u'ū'), + (0x16B, 'V'), + (0x16C, 'M', u'ŭ'), + (0x16D, 'V'), + (0x16E, 'M', u'ů'), + (0x16F, 'V'), + (0x170, 'M', u'ű'), + (0x171, 'V'), + (0x172, 'M', u'ų'), + (0x173, 'V'), + (0x174, 'M', u'ŵ'), + (0x175, 'V'), + (0x176, 'M', u'ŷ'), + (0x177, 'V'), + (0x178, 'M', u'ÿ'), + (0x179, 'M', u'ź'), + (0x17A, 'V'), + (0x17B, 'M', u'ż'), + (0x17C, 'V'), + (0x17D, 'M', u'ž'), + (0x17E, 'V'), + (0x17F, 'M', u's'), + (0x180, 'V'), + (0x181, 'M', u'ɓ'), + (0x182, 'M', u'ƃ'), + (0x183, 'V'), + (0x184, 'M', u'ƅ'), + (0x185, 'V'), + (0x186, 'M', u'ɔ'), + (0x187, 'M', u'ƈ'), + (0x188, 'V'), + (0x189, 'M', u'ɖ'), + (0x18A, 'M', u'ɗ'), + (0x18B, 'M', u'ƌ'), + (0x18C, 'V'), + (0x18E, 'M', u'ǝ'), + (0x18F, 'M', u'ə'), + (0x190, 'M', u'ɛ'), + (0x191, 'M', u'ƒ'), + (0x192, 'V'), + (0x193, 'M', u'ɠ'), + ] + +def _seg_4(): + return [ + (0x194, 'M', u'ɣ'), + (0x195, 'V'), + (0x196, 'M', u'ɩ'), + (0x197, 'M', u'ɨ'), + (0x198, 'M', u'ƙ'), + (0x199, 'V'), + (0x19C, 'M', u'ɯ'), + (0x19D, 'M', u'ɲ'), + (0x19E, 'V'), + (0x19F, 'M', u'ɵ'), + (0x1A0, 'M', u'ơ'), + (0x1A1, 'V'), + (0x1A2, 'M', u'ƣ'), + (0x1A3, 'V'), + (0x1A4, 'M', u'ƥ'), + (0x1A5, 'V'), + (0x1A6, 'M', u'ʀ'), + (0x1A7, 'M', u'ƨ'), + (0x1A8, 'V'), + (0x1A9, 'M', u'ʃ'), + (0x1AA, 'V'), + (0x1AC, 'M', u'ƭ'), + (0x1AD, 'V'), + (0x1AE, 'M', u'ʈ'), + (0x1AF, 'M', u'ư'), + (0x1B0, 'V'), + (0x1B1, 'M', u'ʊ'), + (0x1B2, 'M', u'ʋ'), + (0x1B3, 'M', u'ƴ'), + (0x1B4, 'V'), + (0x1B5, 'M', u'ƶ'), + (0x1B6, 'V'), + (0x1B7, 'M', u'ʒ'), + (0x1B8, 'M', u'ƹ'), + (0x1B9, 'V'), + (0x1BC, 'M', u'ƽ'), + (0x1BD, 'V'), + (0x1C4, 'M', u'dž'), + (0x1C7, 'M', u'lj'), + (0x1CA, 'M', u'nj'), + (0x1CD, 'M', u'ǎ'), + (0x1CE, 'V'), + (0x1CF, 'M', u'ǐ'), + (0x1D0, 'V'), + (0x1D1, 'M', u'ǒ'), + (0x1D2, 'V'), + (0x1D3, 'M', u'ǔ'), + (0x1D4, 'V'), + (0x1D5, 'M', u'ǖ'), + (0x1D6, 'V'), + (0x1D7, 'M', u'ǘ'), + (0x1D8, 'V'), + (0x1D9, 'M', u'ǚ'), + (0x1DA, 'V'), + (0x1DB, 'M', u'ǜ'), + (0x1DC, 'V'), + (0x1DE, 'M', u'ǟ'), + (0x1DF, 'V'), + (0x1E0, 'M', u'ǡ'), + (0x1E1, 'V'), + (0x1E2, 'M', u'ǣ'), + (0x1E3, 'V'), + (0x1E4, 'M', u'ǥ'), + (0x1E5, 'V'), + (0x1E6, 'M', u'ǧ'), + (0x1E7, 'V'), + (0x1E8, 'M', u'ǩ'), + (0x1E9, 'V'), + (0x1EA, 'M', u'ǫ'), + (0x1EB, 'V'), + (0x1EC, 'M', u'ǭ'), + (0x1ED, 'V'), + (0x1EE, 'M', u'ǯ'), + (0x1EF, 'V'), + (0x1F1, 'M', u'dz'), + (0x1F4, 'M', u'ǵ'), + (0x1F5, 'V'), + (0x1F6, 'M', u'ƕ'), + (0x1F7, 'M', u'ƿ'), + (0x1F8, 'M', u'ǹ'), + (0x1F9, 'V'), + (0x1FA, 'M', u'ǻ'), + (0x1FB, 'V'), + (0x1FC, 'M', u'ǽ'), + (0x1FD, 'V'), + (0x1FE, 'M', u'ǿ'), + (0x1FF, 'V'), + (0x200, 'M', u'ȁ'), + (0x201, 'V'), + (0x202, 'M', u'ȃ'), + (0x203, 'V'), + (0x204, 'M', u'ȅ'), + (0x205, 'V'), + (0x206, 'M', u'ȇ'), + (0x207, 'V'), + (0x208, 'M', u'ȉ'), + (0x209, 'V'), + (0x20A, 'M', u'ȋ'), + (0x20B, 'V'), + (0x20C, 'M', u'ȍ'), + ] + +def _seg_5(): + return [ + (0x20D, 'V'), + (0x20E, 'M', u'ȏ'), + (0x20F, 'V'), + (0x210, 'M', u'ȑ'), + (0x211, 'V'), + (0x212, 'M', u'ȓ'), + (0x213, 'V'), + (0x214, 'M', u'ȕ'), + (0x215, 'V'), + (0x216, 'M', u'ȗ'), + (0x217, 'V'), + (0x218, 'M', u'ș'), + (0x219, 'V'), + (0x21A, 'M', u'ț'), + (0x21B, 'V'), + (0x21C, 'M', u'ȝ'), + (0x21D, 'V'), + (0x21E, 'M', u'ȟ'), + (0x21F, 'V'), + (0x220, 'M', u'ƞ'), + (0x221, 'V'), + (0x222, 'M', u'ȣ'), + (0x223, 'V'), + (0x224, 'M', u'ȥ'), + (0x225, 'V'), + (0x226, 'M', u'ȧ'), + (0x227, 'V'), + (0x228, 'M', u'ȩ'), + (0x229, 'V'), + (0x22A, 'M', u'ȫ'), + (0x22B, 'V'), + (0x22C, 'M', u'ȭ'), + (0x22D, 'V'), + (0x22E, 'M', u'ȯ'), + (0x22F, 'V'), + (0x230, 'M', u'ȱ'), + (0x231, 'V'), + (0x232, 'M', u'ȳ'), + (0x233, 'V'), + (0x23A, 'M', u'ⱥ'), + (0x23B, 'M', u'ȼ'), + (0x23C, 'V'), + (0x23D, 'M', u'ƚ'), + (0x23E, 'M', u'ⱦ'), + (0x23F, 'V'), + (0x241, 'M', u'ɂ'), + (0x242, 'V'), + (0x243, 'M', u'ƀ'), + (0x244, 'M', u'ʉ'), + (0x245, 'M', u'ʌ'), + (0x246, 'M', u'ɇ'), + (0x247, 'V'), + (0x248, 'M', u'ɉ'), + (0x249, 'V'), + (0x24A, 'M', u'ɋ'), + (0x24B, 'V'), + (0x24C, 'M', u'ɍ'), + (0x24D, 'V'), + (0x24E, 'M', u'ɏ'), + (0x24F, 'V'), + (0x2B0, 'M', u'h'), + (0x2B1, 'M', u'ɦ'), + (0x2B2, 'M', u'j'), + (0x2B3, 'M', u'r'), + (0x2B4, 'M', u'ɹ'), + (0x2B5, 'M', u'ɻ'), + (0x2B6, 'M', u'ʁ'), + (0x2B7, 'M', u'w'), + (0x2B8, 'M', u'y'), + (0x2B9, 'V'), + (0x2D8, '3', u' ̆'), + (0x2D9, '3', u' ̇'), + (0x2DA, '3', u' ̊'), + (0x2DB, '3', u' ̨'), + (0x2DC, '3', u' ̃'), + (0x2DD, '3', u' ̋'), + (0x2DE, 'V'), + (0x2E0, 'M', u'ɣ'), + (0x2E1, 'M', u'l'), + (0x2E2, 'M', u's'), + (0x2E3, 'M', u'x'), + (0x2E4, 'M', u'ʕ'), + (0x2E5, 'V'), + (0x340, 'M', u'̀'), + (0x341, 'M', u'́'), + (0x342, 'V'), + (0x343, 'M', u'̓'), + (0x344, 'M', u'̈́'), + (0x345, 'M', u'ι'), + (0x346, 'V'), + (0x34F, 'I'), + (0x350, 'V'), + (0x370, 'M', u'ͱ'), + (0x371, 'V'), + (0x372, 'M', u'ͳ'), + (0x373, 'V'), + (0x374, 'M', u'ʹ'), + (0x375, 'V'), + (0x376, 'M', u'ͷ'), + (0x377, 'V'), + ] + +def _seg_6(): + return [ + (0x378, 'X'), + (0x37A, '3', u' ι'), + (0x37B, 'V'), + (0x37E, '3', u';'), + (0x37F, 'M', u'ϳ'), + (0x380, 'X'), + (0x384, '3', u' ́'), + (0x385, '3', u' ̈́'), + (0x386, 'M', u'ά'), + (0x387, 'M', u'·'), + (0x388, 'M', u'έ'), + (0x389, 'M', u'ή'), + (0x38A, 'M', u'ί'), + (0x38B, 'X'), + (0x38C, 'M', u'ό'), + (0x38D, 'X'), + (0x38E, 'M', u'ύ'), + (0x38F, 'M', u'ώ'), + (0x390, 'V'), + (0x391, 'M', u'α'), + (0x392, 'M', u'β'), + (0x393, 'M', u'γ'), + (0x394, 'M', u'δ'), + (0x395, 'M', u'ε'), + (0x396, 'M', u'ζ'), + (0x397, 'M', u'η'), + (0x398, 'M', u'θ'), + (0x399, 'M', u'ι'), + (0x39A, 'M', u'κ'), + (0x39B, 'M', u'λ'), + (0x39C, 'M', u'μ'), + (0x39D, 'M', u'ν'), + (0x39E, 'M', u'ξ'), + (0x39F, 'M', u'ο'), + (0x3A0, 'M', u'π'), + (0x3A1, 'M', u'ρ'), + (0x3A2, 'X'), + (0x3A3, 'M', u'σ'), + (0x3A4, 'M', u'τ'), + (0x3A5, 'M', u'υ'), + (0x3A6, 'M', u'φ'), + (0x3A7, 'M', u'χ'), + (0x3A8, 'M', u'ψ'), + (0x3A9, 'M', u'ω'), + (0x3AA, 'M', u'ϊ'), + (0x3AB, 'M', u'ϋ'), + (0x3AC, 'V'), + (0x3C2, 'D', u'σ'), + (0x3C3, 'V'), + (0x3CF, 'M', u'ϗ'), + (0x3D0, 'M', u'β'), + (0x3D1, 'M', u'θ'), + (0x3D2, 'M', u'υ'), + (0x3D3, 'M', u'ύ'), + (0x3D4, 'M', u'ϋ'), + (0x3D5, 'M', u'φ'), + (0x3D6, 'M', u'π'), + (0x3D7, 'V'), + (0x3D8, 'M', u'ϙ'), + (0x3D9, 'V'), + (0x3DA, 'M', u'ϛ'), + (0x3DB, 'V'), + (0x3DC, 'M', u'ϝ'), + (0x3DD, 'V'), + (0x3DE, 'M', u'ϟ'), + (0x3DF, 'V'), + (0x3E0, 'M', u'ϡ'), + (0x3E1, 'V'), + (0x3E2, 'M', u'ϣ'), + (0x3E3, 'V'), + (0x3E4, 'M', u'ϥ'), + (0x3E5, 'V'), + (0x3E6, 'M', u'ϧ'), + (0x3E7, 'V'), + (0x3E8, 'M', u'ϩ'), + (0x3E9, 'V'), + (0x3EA, 'M', u'ϫ'), + (0x3EB, 'V'), + (0x3EC, 'M', u'ϭ'), + (0x3ED, 'V'), + (0x3EE, 'M', u'ϯ'), + (0x3EF, 'V'), + (0x3F0, 'M', u'κ'), + (0x3F1, 'M', u'ρ'), + (0x3F2, 'M', u'σ'), + (0x3F3, 'V'), + (0x3F4, 'M', u'θ'), + (0x3F5, 'M', u'ε'), + (0x3F6, 'V'), + (0x3F7, 'M', u'ϸ'), + (0x3F8, 'V'), + (0x3F9, 'M', u'σ'), + (0x3FA, 'M', u'ϻ'), + (0x3FB, 'V'), + (0x3FD, 'M', u'ͻ'), + (0x3FE, 'M', u'ͼ'), + (0x3FF, 'M', u'ͽ'), + (0x400, 'M', u'ѐ'), + (0x401, 'M', u'ё'), + (0x402, 'M', u'ђ'), + ] + +def _seg_7(): + return [ + (0x403, 'M', u'ѓ'), + (0x404, 'M', u'є'), + (0x405, 'M', u'ѕ'), + (0x406, 'M', u'і'), + (0x407, 'M', u'ї'), + (0x408, 'M', u'ј'), + (0x409, 'M', u'љ'), + (0x40A, 'M', u'њ'), + (0x40B, 'M', u'ћ'), + (0x40C, 'M', u'ќ'), + (0x40D, 'M', u'ѝ'), + (0x40E, 'M', u'ў'), + (0x40F, 'M', u'џ'), + (0x410, 'M', u'а'), + (0x411, 'M', u'б'), + (0x412, 'M', u'в'), + (0x413, 'M', u'г'), + (0x414, 'M', u'д'), + (0x415, 'M', u'е'), + (0x416, 'M', u'ж'), + (0x417, 'M', u'з'), + (0x418, 'M', u'и'), + (0x419, 'M', u'й'), + (0x41A, 'M', u'к'), + (0x41B, 'M', u'л'), + (0x41C, 'M', u'м'), + (0x41D, 'M', u'н'), + (0x41E, 'M', u'о'), + (0x41F, 'M', u'п'), + (0x420, 'M', u'р'), + (0x421, 'M', u'с'), + (0x422, 'M', u'т'), + (0x423, 'M', u'у'), + (0x424, 'M', u'ф'), + (0x425, 'M', u'х'), + (0x426, 'M', u'ц'), + (0x427, 'M', u'ч'), + (0x428, 'M', u'ш'), + (0x429, 'M', u'щ'), + (0x42A, 'M', u'ъ'), + (0x42B, 'M', u'ы'), + (0x42C, 'M', u'ь'), + (0x42D, 'M', u'э'), + (0x42E, 'M', u'ю'), + (0x42F, 'M', u'я'), + (0x430, 'V'), + (0x460, 'M', u'ѡ'), + (0x461, 'V'), + (0x462, 'M', u'ѣ'), + (0x463, 'V'), + (0x464, 'M', u'ѥ'), + (0x465, 'V'), + (0x466, 'M', u'ѧ'), + (0x467, 'V'), + (0x468, 'M', u'ѩ'), + (0x469, 'V'), + (0x46A, 'M', u'ѫ'), + (0x46B, 'V'), + (0x46C, 'M', u'ѭ'), + (0x46D, 'V'), + (0x46E, 'M', u'ѯ'), + (0x46F, 'V'), + (0x470, 'M', u'ѱ'), + (0x471, 'V'), + (0x472, 'M', u'ѳ'), + (0x473, 'V'), + (0x474, 'M', u'ѵ'), + (0x475, 'V'), + (0x476, 'M', u'ѷ'), + (0x477, 'V'), + (0x478, 'M', u'ѹ'), + (0x479, 'V'), + (0x47A, 'M', u'ѻ'), + (0x47B, 'V'), + (0x47C, 'M', u'ѽ'), + (0x47D, 'V'), + (0x47E, 'M', u'ѿ'), + (0x47F, 'V'), + (0x480, 'M', u'ҁ'), + (0x481, 'V'), + (0x48A, 'M', u'ҋ'), + (0x48B, 'V'), + (0x48C, 'M', u'ҍ'), + (0x48D, 'V'), + (0x48E, 'M', u'ҏ'), + (0x48F, 'V'), + (0x490, 'M', u'ґ'), + (0x491, 'V'), + (0x492, 'M', u'ғ'), + (0x493, 'V'), + (0x494, 'M', u'ҕ'), + (0x495, 'V'), + (0x496, 'M', u'җ'), + (0x497, 'V'), + (0x498, 'M', u'ҙ'), + (0x499, 'V'), + (0x49A, 'M', u'қ'), + (0x49B, 'V'), + (0x49C, 'M', u'ҝ'), + (0x49D, 'V'), + ] + +def _seg_8(): + return [ + (0x49E, 'M', u'ҟ'), + (0x49F, 'V'), + (0x4A0, 'M', u'ҡ'), + (0x4A1, 'V'), + (0x4A2, 'M', u'ң'), + (0x4A3, 'V'), + (0x4A4, 'M', u'ҥ'), + (0x4A5, 'V'), + (0x4A6, 'M', u'ҧ'), + (0x4A7, 'V'), + (0x4A8, 'M', u'ҩ'), + (0x4A9, 'V'), + (0x4AA, 'M', u'ҫ'), + (0x4AB, 'V'), + (0x4AC, 'M', u'ҭ'), + (0x4AD, 'V'), + (0x4AE, 'M', u'ү'), + (0x4AF, 'V'), + (0x4B0, 'M', u'ұ'), + (0x4B1, 'V'), + (0x4B2, 'M', u'ҳ'), + (0x4B3, 'V'), + (0x4B4, 'M', u'ҵ'), + (0x4B5, 'V'), + (0x4B6, 'M', u'ҷ'), + (0x4B7, 'V'), + (0x4B8, 'M', u'ҹ'), + (0x4B9, 'V'), + (0x4BA, 'M', u'һ'), + (0x4BB, 'V'), + (0x4BC, 'M', u'ҽ'), + (0x4BD, 'V'), + (0x4BE, 'M', u'ҿ'), + (0x4BF, 'V'), + (0x4C0, 'X'), + (0x4C1, 'M', u'ӂ'), + (0x4C2, 'V'), + (0x4C3, 'M', u'ӄ'), + (0x4C4, 'V'), + (0x4C5, 'M', u'ӆ'), + (0x4C6, 'V'), + (0x4C7, 'M', u'ӈ'), + (0x4C8, 'V'), + (0x4C9, 'M', u'ӊ'), + (0x4CA, 'V'), + (0x4CB, 'M', u'ӌ'), + (0x4CC, 'V'), + (0x4CD, 'M', u'ӎ'), + (0x4CE, 'V'), + (0x4D0, 'M', u'ӑ'), + (0x4D1, 'V'), + (0x4D2, 'M', u'ӓ'), + (0x4D3, 'V'), + (0x4D4, 'M', u'ӕ'), + (0x4D5, 'V'), + (0x4D6, 'M', u'ӗ'), + (0x4D7, 'V'), + (0x4D8, 'M', u'ә'), + (0x4D9, 'V'), + (0x4DA, 'M', u'ӛ'), + (0x4DB, 'V'), + (0x4DC, 'M', u'ӝ'), + (0x4DD, 'V'), + (0x4DE, 'M', u'ӟ'), + (0x4DF, 'V'), + (0x4E0, 'M', u'ӡ'), + (0x4E1, 'V'), + (0x4E2, 'M', u'ӣ'), + (0x4E3, 'V'), + (0x4E4, 'M', u'ӥ'), + (0x4E5, 'V'), + (0x4E6, 'M', u'ӧ'), + (0x4E7, 'V'), + (0x4E8, 'M', u'ө'), + (0x4E9, 'V'), + (0x4EA, 'M', u'ӫ'), + (0x4EB, 'V'), + (0x4EC, 'M', u'ӭ'), + (0x4ED, 'V'), + (0x4EE, 'M', u'ӯ'), + (0x4EF, 'V'), + (0x4F0, 'M', u'ӱ'), + (0x4F1, 'V'), + (0x4F2, 'M', u'ӳ'), + (0x4F3, 'V'), + (0x4F4, 'M', u'ӵ'), + (0x4F5, 'V'), + (0x4F6, 'M', u'ӷ'), + (0x4F7, 'V'), + (0x4F8, 'M', u'ӹ'), + (0x4F9, 'V'), + (0x4FA, 'M', u'ӻ'), + (0x4FB, 'V'), + (0x4FC, 'M', u'ӽ'), + (0x4FD, 'V'), + (0x4FE, 'M', u'ӿ'), + (0x4FF, 'V'), + (0x500, 'M', u'ԁ'), + (0x501, 'V'), + (0x502, 'M', u'ԃ'), + ] + +def _seg_9(): + return [ + (0x503, 'V'), + (0x504, 'M', u'ԅ'), + (0x505, 'V'), + (0x506, 'M', u'ԇ'), + (0x507, 'V'), + (0x508, 'M', u'ԉ'), + (0x509, 'V'), + (0x50A, 'M', u'ԋ'), + (0x50B, 'V'), + (0x50C, 'M', u'ԍ'), + (0x50D, 'V'), + (0x50E, 'M', u'ԏ'), + (0x50F, 'V'), + (0x510, 'M', u'ԑ'), + (0x511, 'V'), + (0x512, 'M', u'ԓ'), + (0x513, 'V'), + (0x514, 'M', u'ԕ'), + (0x515, 'V'), + (0x516, 'M', u'ԗ'), + (0x517, 'V'), + (0x518, 'M', u'ԙ'), + (0x519, 'V'), + (0x51A, 'M', u'ԛ'), + (0x51B, 'V'), + (0x51C, 'M', u'ԝ'), + (0x51D, 'V'), + (0x51E, 'M', u'ԟ'), + (0x51F, 'V'), + (0x520, 'M', u'ԡ'), + (0x521, 'V'), + (0x522, 'M', u'ԣ'), + (0x523, 'V'), + (0x524, 'M', u'ԥ'), + (0x525, 'V'), + (0x526, 'M', u'ԧ'), + (0x527, 'V'), + (0x528, 'M', u'ԩ'), + (0x529, 'V'), + (0x52A, 'M', u'ԫ'), + (0x52B, 'V'), + (0x52C, 'M', u'ԭ'), + (0x52D, 'V'), + (0x52E, 'M', u'ԯ'), + (0x52F, 'V'), + (0x530, 'X'), + (0x531, 'M', u'ա'), + (0x532, 'M', u'բ'), + (0x533, 'M', u'գ'), + (0x534, 'M', u'դ'), + (0x535, 'M', u'ե'), + (0x536, 'M', u'զ'), + (0x537, 'M', u'է'), + (0x538, 'M', u'ը'), + (0x539, 'M', u'թ'), + (0x53A, 'M', u'ժ'), + (0x53B, 'M', u'ի'), + (0x53C, 'M', u'լ'), + (0x53D, 'M', u'խ'), + (0x53E, 'M', u'ծ'), + (0x53F, 'M', u'կ'), + (0x540, 'M', u'հ'), + (0x541, 'M', u'ձ'), + (0x542, 'M', u'ղ'), + (0x543, 'M', u'ճ'), + (0x544, 'M', u'մ'), + (0x545, 'M', u'յ'), + (0x546, 'M', u'ն'), + (0x547, 'M', u'շ'), + (0x548, 'M', u'ո'), + (0x549, 'M', u'չ'), + (0x54A, 'M', u'պ'), + (0x54B, 'M', u'ջ'), + (0x54C, 'M', u'ռ'), + (0x54D, 'M', u'ս'), + (0x54E, 'M', u'վ'), + (0x54F, 'M', u'տ'), + (0x550, 'M', u'ր'), + (0x551, 'M', u'ց'), + (0x552, 'M', u'ւ'), + (0x553, 'M', u'փ'), + (0x554, 'M', u'ք'), + (0x555, 'M', u'օ'), + (0x556, 'M', u'ֆ'), + (0x557, 'X'), + (0x559, 'V'), + (0x587, 'M', u'եւ'), + (0x588, 'V'), + (0x58B, 'X'), + (0x58D, 'V'), + (0x590, 'X'), + (0x591, 'V'), + (0x5C8, 'X'), + (0x5D0, 'V'), + (0x5EB, 'X'), + (0x5EF, 'V'), + (0x5F5, 'X'), + (0x606, 'V'), + (0x61C, 'X'), + (0x61E, 'V'), + ] + +def _seg_10(): + return [ + (0x675, 'M', u'اٴ'), + (0x676, 'M', u'وٴ'), + (0x677, 'M', u'ۇٴ'), + (0x678, 'M', u'يٴ'), + (0x679, 'V'), + (0x6DD, 'X'), + (0x6DE, 'V'), + (0x70E, 'X'), + (0x710, 'V'), + (0x74B, 'X'), + (0x74D, 'V'), + (0x7B2, 'X'), + (0x7C0, 'V'), + (0x7FB, 'X'), + (0x7FD, 'V'), + (0x82E, 'X'), + (0x830, 'V'), + (0x83F, 'X'), + (0x840, 'V'), + (0x85C, 'X'), + (0x85E, 'V'), + (0x85F, 'X'), + (0x860, 'V'), + (0x86B, 'X'), + (0x8A0, 'V'), + (0x8B5, 'X'), + (0x8B6, 'V'), + (0x8C8, 'X'), + (0x8D3, 'V'), + (0x8E2, 'X'), + (0x8E3, 'V'), + (0x958, 'M', u'क़'), + (0x959, 'M', u'ख़'), + (0x95A, 'M', u'ग़'), + (0x95B, 'M', u'ज़'), + (0x95C, 'M', u'ड़'), + (0x95D, 'M', u'ढ़'), + (0x95E, 'M', u'फ़'), + (0x95F, 'M', u'य़'), + (0x960, 'V'), + (0x984, 'X'), + (0x985, 'V'), + (0x98D, 'X'), + (0x98F, 'V'), + (0x991, 'X'), + (0x993, 'V'), + (0x9A9, 'X'), + (0x9AA, 'V'), + (0x9B1, 'X'), + (0x9B2, 'V'), + (0x9B3, 'X'), + (0x9B6, 'V'), + (0x9BA, 'X'), + (0x9BC, 'V'), + (0x9C5, 'X'), + (0x9C7, 'V'), + (0x9C9, 'X'), + (0x9CB, 'V'), + (0x9CF, 'X'), + (0x9D7, 'V'), + (0x9D8, 'X'), + (0x9DC, 'M', u'ড়'), + (0x9DD, 'M', u'ঢ়'), + (0x9DE, 'X'), + (0x9DF, 'M', u'য়'), + (0x9E0, 'V'), + (0x9E4, 'X'), + (0x9E6, 'V'), + (0x9FF, 'X'), + (0xA01, 'V'), + (0xA04, 'X'), + (0xA05, 'V'), + (0xA0B, 'X'), + (0xA0F, 'V'), + (0xA11, 'X'), + (0xA13, 'V'), + (0xA29, 'X'), + (0xA2A, 'V'), + (0xA31, 'X'), + (0xA32, 'V'), + (0xA33, 'M', u'ਲ਼'), + (0xA34, 'X'), + (0xA35, 'V'), + (0xA36, 'M', u'ਸ਼'), + (0xA37, 'X'), + (0xA38, 'V'), + (0xA3A, 'X'), + (0xA3C, 'V'), + (0xA3D, 'X'), + (0xA3E, 'V'), + (0xA43, 'X'), + (0xA47, 'V'), + (0xA49, 'X'), + (0xA4B, 'V'), + (0xA4E, 'X'), + (0xA51, 'V'), + (0xA52, 'X'), + (0xA59, 'M', u'ਖ਼'), + (0xA5A, 'M', u'ਗ਼'), + (0xA5B, 'M', u'ਜ਼'), + ] + +def _seg_11(): + return [ + (0xA5C, 'V'), + (0xA5D, 'X'), + (0xA5E, 'M', u'ਫ਼'), + (0xA5F, 'X'), + (0xA66, 'V'), + (0xA77, 'X'), + (0xA81, 'V'), + (0xA84, 'X'), + (0xA85, 'V'), + (0xA8E, 'X'), + (0xA8F, 'V'), + (0xA92, 'X'), + (0xA93, 'V'), + (0xAA9, 'X'), + (0xAAA, 'V'), + (0xAB1, 'X'), + (0xAB2, 'V'), + (0xAB4, 'X'), + (0xAB5, 'V'), + (0xABA, 'X'), + (0xABC, 'V'), + (0xAC6, 'X'), + (0xAC7, 'V'), + (0xACA, 'X'), + (0xACB, 'V'), + (0xACE, 'X'), + (0xAD0, 'V'), + (0xAD1, 'X'), + (0xAE0, 'V'), + (0xAE4, 'X'), + (0xAE6, 'V'), + (0xAF2, 'X'), + (0xAF9, 'V'), + (0xB00, 'X'), + (0xB01, 'V'), + (0xB04, 'X'), + (0xB05, 'V'), + (0xB0D, 'X'), + (0xB0F, 'V'), + (0xB11, 'X'), + (0xB13, 'V'), + (0xB29, 'X'), + (0xB2A, 'V'), + (0xB31, 'X'), + (0xB32, 'V'), + (0xB34, 'X'), + (0xB35, 'V'), + (0xB3A, 'X'), + (0xB3C, 'V'), + (0xB45, 'X'), + (0xB47, 'V'), + (0xB49, 'X'), + (0xB4B, 'V'), + (0xB4E, 'X'), + (0xB55, 'V'), + (0xB58, 'X'), + (0xB5C, 'M', u'ଡ଼'), + (0xB5D, 'M', u'ଢ଼'), + (0xB5E, 'X'), + (0xB5F, 'V'), + (0xB64, 'X'), + (0xB66, 'V'), + (0xB78, 'X'), + (0xB82, 'V'), + (0xB84, 'X'), + (0xB85, 'V'), + (0xB8B, 'X'), + (0xB8E, 'V'), + (0xB91, 'X'), + (0xB92, 'V'), + (0xB96, 'X'), + (0xB99, 'V'), + (0xB9B, 'X'), + (0xB9C, 'V'), + (0xB9D, 'X'), + (0xB9E, 'V'), + (0xBA0, 'X'), + (0xBA3, 'V'), + (0xBA5, 'X'), + (0xBA8, 'V'), + (0xBAB, 'X'), + (0xBAE, 'V'), + (0xBBA, 'X'), + (0xBBE, 'V'), + (0xBC3, 'X'), + (0xBC6, 'V'), + (0xBC9, 'X'), + (0xBCA, 'V'), + (0xBCE, 'X'), + (0xBD0, 'V'), + (0xBD1, 'X'), + (0xBD7, 'V'), + (0xBD8, 'X'), + (0xBE6, 'V'), + (0xBFB, 'X'), + (0xC00, 'V'), + (0xC0D, 'X'), + (0xC0E, 'V'), + (0xC11, 'X'), + (0xC12, 'V'), + ] + +def _seg_12(): + return [ + (0xC29, 'X'), + (0xC2A, 'V'), + (0xC3A, 'X'), + (0xC3D, 'V'), + (0xC45, 'X'), + (0xC46, 'V'), + (0xC49, 'X'), + (0xC4A, 'V'), + (0xC4E, 'X'), + (0xC55, 'V'), + (0xC57, 'X'), + (0xC58, 'V'), + (0xC5B, 'X'), + (0xC60, 'V'), + (0xC64, 'X'), + (0xC66, 'V'), + (0xC70, 'X'), + (0xC77, 'V'), + (0xC8D, 'X'), + (0xC8E, 'V'), + (0xC91, 'X'), + (0xC92, 'V'), + (0xCA9, 'X'), + (0xCAA, 'V'), + (0xCB4, 'X'), + (0xCB5, 'V'), + (0xCBA, 'X'), + (0xCBC, 'V'), + (0xCC5, 'X'), + (0xCC6, 'V'), + (0xCC9, 'X'), + (0xCCA, 'V'), + (0xCCE, 'X'), + (0xCD5, 'V'), + (0xCD7, 'X'), + (0xCDE, 'V'), + (0xCDF, 'X'), + (0xCE0, 'V'), + (0xCE4, 'X'), + (0xCE6, 'V'), + (0xCF0, 'X'), + (0xCF1, 'V'), + (0xCF3, 'X'), + (0xD00, 'V'), + (0xD0D, 'X'), + (0xD0E, 'V'), + (0xD11, 'X'), + (0xD12, 'V'), + (0xD45, 'X'), + (0xD46, 'V'), + (0xD49, 'X'), + (0xD4A, 'V'), + (0xD50, 'X'), + (0xD54, 'V'), + (0xD64, 'X'), + (0xD66, 'V'), + (0xD80, 'X'), + (0xD81, 'V'), + (0xD84, 'X'), + (0xD85, 'V'), + (0xD97, 'X'), + (0xD9A, 'V'), + (0xDB2, 'X'), + (0xDB3, 'V'), + (0xDBC, 'X'), + (0xDBD, 'V'), + (0xDBE, 'X'), + (0xDC0, 'V'), + (0xDC7, 'X'), + (0xDCA, 'V'), + (0xDCB, 'X'), + (0xDCF, 'V'), + (0xDD5, 'X'), + (0xDD6, 'V'), + (0xDD7, 'X'), + (0xDD8, 'V'), + (0xDE0, 'X'), + (0xDE6, 'V'), + (0xDF0, 'X'), + (0xDF2, 'V'), + (0xDF5, 'X'), + (0xE01, 'V'), + (0xE33, 'M', u'ํา'), + (0xE34, 'V'), + (0xE3B, 'X'), + (0xE3F, 'V'), + (0xE5C, 'X'), + (0xE81, 'V'), + (0xE83, 'X'), + (0xE84, 'V'), + (0xE85, 'X'), + (0xE86, 'V'), + (0xE8B, 'X'), + (0xE8C, 'V'), + (0xEA4, 'X'), + (0xEA5, 'V'), + (0xEA6, 'X'), + (0xEA7, 'V'), + (0xEB3, 'M', u'ໍາ'), + (0xEB4, 'V'), + ] + +def _seg_13(): + return [ + (0xEBE, 'X'), + (0xEC0, 'V'), + (0xEC5, 'X'), + (0xEC6, 'V'), + (0xEC7, 'X'), + (0xEC8, 'V'), + (0xECE, 'X'), + (0xED0, 'V'), + (0xEDA, 'X'), + (0xEDC, 'M', u'ຫນ'), + (0xEDD, 'M', u'ຫມ'), + (0xEDE, 'V'), + (0xEE0, 'X'), + (0xF00, 'V'), + (0xF0C, 'M', u'་'), + (0xF0D, 'V'), + (0xF43, 'M', u'གྷ'), + (0xF44, 'V'), + (0xF48, 'X'), + (0xF49, 'V'), + (0xF4D, 'M', u'ཌྷ'), + (0xF4E, 'V'), + (0xF52, 'M', u'དྷ'), + (0xF53, 'V'), + (0xF57, 'M', u'བྷ'), + (0xF58, 'V'), + (0xF5C, 'M', u'ཛྷ'), + (0xF5D, 'V'), + (0xF69, 'M', u'ཀྵ'), + (0xF6A, 'V'), + (0xF6D, 'X'), + (0xF71, 'V'), + (0xF73, 'M', u'ཱི'), + (0xF74, 'V'), + (0xF75, 'M', u'ཱུ'), + (0xF76, 'M', u'ྲྀ'), + (0xF77, 'M', u'ྲཱྀ'), + (0xF78, 'M', u'ླྀ'), + (0xF79, 'M', u'ླཱྀ'), + (0xF7A, 'V'), + (0xF81, 'M', u'ཱྀ'), + (0xF82, 'V'), + (0xF93, 'M', u'ྒྷ'), + (0xF94, 'V'), + (0xF98, 'X'), + (0xF99, 'V'), + (0xF9D, 'M', u'ྜྷ'), + (0xF9E, 'V'), + (0xFA2, 'M', u'ྡྷ'), + (0xFA3, 'V'), + (0xFA7, 'M', u'ྦྷ'), + (0xFA8, 'V'), + (0xFAC, 'M', u'ྫྷ'), + (0xFAD, 'V'), + (0xFB9, 'M', u'ྐྵ'), + (0xFBA, 'V'), + (0xFBD, 'X'), + (0xFBE, 'V'), + (0xFCD, 'X'), + (0xFCE, 'V'), + (0xFDB, 'X'), + (0x1000, 'V'), + (0x10A0, 'X'), + (0x10C7, 'M', u'ⴧ'), + (0x10C8, 'X'), + (0x10CD, 'M', u'ⴭ'), + (0x10CE, 'X'), + (0x10D0, 'V'), + (0x10FC, 'M', u'ნ'), + (0x10FD, 'V'), + (0x115F, 'X'), + (0x1161, 'V'), + (0x1249, 'X'), + (0x124A, 'V'), + (0x124E, 'X'), + (0x1250, 'V'), + (0x1257, 'X'), + (0x1258, 'V'), + (0x1259, 'X'), + (0x125A, 'V'), + (0x125E, 'X'), + (0x1260, 'V'), + (0x1289, 'X'), + (0x128A, 'V'), + (0x128E, 'X'), + (0x1290, 'V'), + (0x12B1, 'X'), + (0x12B2, 'V'), + (0x12B6, 'X'), + (0x12B8, 'V'), + (0x12BF, 'X'), + (0x12C0, 'V'), + (0x12C1, 'X'), + (0x12C2, 'V'), + (0x12C6, 'X'), + (0x12C8, 'V'), + (0x12D7, 'X'), + (0x12D8, 'V'), + (0x1311, 'X'), + (0x1312, 'V'), + ] + +def _seg_14(): + return [ + (0x1316, 'X'), + (0x1318, 'V'), + (0x135B, 'X'), + (0x135D, 'V'), + (0x137D, 'X'), + (0x1380, 'V'), + (0x139A, 'X'), + (0x13A0, 'V'), + (0x13F6, 'X'), + (0x13F8, 'M', u'Ᏸ'), + (0x13F9, 'M', u'Ᏹ'), + (0x13FA, 'M', u'Ᏺ'), + (0x13FB, 'M', u'Ᏻ'), + (0x13FC, 'M', u'Ᏼ'), + (0x13FD, 'M', u'Ᏽ'), + (0x13FE, 'X'), + (0x1400, 'V'), + (0x1680, 'X'), + (0x1681, 'V'), + (0x169D, 'X'), + (0x16A0, 'V'), + (0x16F9, 'X'), + (0x1700, 'V'), + (0x170D, 'X'), + (0x170E, 'V'), + (0x1715, 'X'), + (0x1720, 'V'), + (0x1737, 'X'), + (0x1740, 'V'), + (0x1754, 'X'), + (0x1760, 'V'), + (0x176D, 'X'), + (0x176E, 'V'), + (0x1771, 'X'), + (0x1772, 'V'), + (0x1774, 'X'), + (0x1780, 'V'), + (0x17B4, 'X'), + (0x17B6, 'V'), + (0x17DE, 'X'), + (0x17E0, 'V'), + (0x17EA, 'X'), + (0x17F0, 'V'), + (0x17FA, 'X'), + (0x1800, 'V'), + (0x1806, 'X'), + (0x1807, 'V'), + (0x180B, 'I'), + (0x180E, 'X'), + (0x1810, 'V'), + (0x181A, 'X'), + (0x1820, 'V'), + (0x1879, 'X'), + (0x1880, 'V'), + (0x18AB, 'X'), + (0x18B0, 'V'), + (0x18F6, 'X'), + (0x1900, 'V'), + (0x191F, 'X'), + (0x1920, 'V'), + (0x192C, 'X'), + (0x1930, 'V'), + (0x193C, 'X'), + (0x1940, 'V'), + (0x1941, 'X'), + (0x1944, 'V'), + (0x196E, 'X'), + (0x1970, 'V'), + (0x1975, 'X'), + (0x1980, 'V'), + (0x19AC, 'X'), + (0x19B0, 'V'), + (0x19CA, 'X'), + (0x19D0, 'V'), + (0x19DB, 'X'), + (0x19DE, 'V'), + (0x1A1C, 'X'), + (0x1A1E, 'V'), + (0x1A5F, 'X'), + (0x1A60, 'V'), + (0x1A7D, 'X'), + (0x1A7F, 'V'), + (0x1A8A, 'X'), + (0x1A90, 'V'), + (0x1A9A, 'X'), + (0x1AA0, 'V'), + (0x1AAE, 'X'), + (0x1AB0, 'V'), + (0x1AC1, 'X'), + (0x1B00, 'V'), + (0x1B4C, 'X'), + (0x1B50, 'V'), + (0x1B7D, 'X'), + (0x1B80, 'V'), + (0x1BF4, 'X'), + (0x1BFC, 'V'), + (0x1C38, 'X'), + (0x1C3B, 'V'), + (0x1C4A, 'X'), + (0x1C4D, 'V'), + ] + +def _seg_15(): + return [ + (0x1C80, 'M', u'в'), + (0x1C81, 'M', u'д'), + (0x1C82, 'M', u'о'), + (0x1C83, 'M', u'с'), + (0x1C84, 'M', u'т'), + (0x1C86, 'M', u'ъ'), + (0x1C87, 'M', u'ѣ'), + (0x1C88, 'M', u'ꙋ'), + (0x1C89, 'X'), + (0x1C90, 'M', u'ა'), + (0x1C91, 'M', u'ბ'), + (0x1C92, 'M', u'გ'), + (0x1C93, 'M', u'დ'), + (0x1C94, 'M', u'ე'), + (0x1C95, 'M', u'ვ'), + (0x1C96, 'M', u'ზ'), + (0x1C97, 'M', u'თ'), + (0x1C98, 'M', u'ი'), + (0x1C99, 'M', u'კ'), + (0x1C9A, 'M', u'ლ'), + (0x1C9B, 'M', u'მ'), + (0x1C9C, 'M', u'ნ'), + (0x1C9D, 'M', u'ო'), + (0x1C9E, 'M', u'პ'), + (0x1C9F, 'M', u'ჟ'), + (0x1CA0, 'M', u'რ'), + (0x1CA1, 'M', u'ს'), + (0x1CA2, 'M', u'ტ'), + (0x1CA3, 'M', u'უ'), + (0x1CA4, 'M', u'ფ'), + (0x1CA5, 'M', u'ქ'), + (0x1CA6, 'M', u'ღ'), + (0x1CA7, 'M', u'ყ'), + (0x1CA8, 'M', u'შ'), + (0x1CA9, 'M', u'ჩ'), + (0x1CAA, 'M', u'ც'), + (0x1CAB, 'M', u'ძ'), + (0x1CAC, 'M', u'წ'), + (0x1CAD, 'M', u'ჭ'), + (0x1CAE, 'M', u'ხ'), + (0x1CAF, 'M', u'ჯ'), + (0x1CB0, 'M', u'ჰ'), + (0x1CB1, 'M', u'ჱ'), + (0x1CB2, 'M', u'ჲ'), + (0x1CB3, 'M', u'ჳ'), + (0x1CB4, 'M', u'ჴ'), + (0x1CB5, 'M', u'ჵ'), + (0x1CB6, 'M', u'ჶ'), + (0x1CB7, 'M', u'ჷ'), + (0x1CB8, 'M', u'ჸ'), + (0x1CB9, 'M', u'ჹ'), + (0x1CBA, 'M', u'ჺ'), + (0x1CBB, 'X'), + (0x1CBD, 'M', u'ჽ'), + (0x1CBE, 'M', u'ჾ'), + (0x1CBF, 'M', u'ჿ'), + (0x1CC0, 'V'), + (0x1CC8, 'X'), + (0x1CD0, 'V'), + (0x1CFB, 'X'), + (0x1D00, 'V'), + (0x1D2C, 'M', u'a'), + (0x1D2D, 'M', u'æ'), + (0x1D2E, 'M', u'b'), + (0x1D2F, 'V'), + (0x1D30, 'M', u'd'), + (0x1D31, 'M', u'e'), + (0x1D32, 'M', u'ǝ'), + (0x1D33, 'M', u'g'), + (0x1D34, 'M', u'h'), + (0x1D35, 'M', u'i'), + (0x1D36, 'M', u'j'), + (0x1D37, 'M', u'k'), + (0x1D38, 'M', u'l'), + (0x1D39, 'M', u'm'), + (0x1D3A, 'M', u'n'), + (0x1D3B, 'V'), + (0x1D3C, 'M', u'o'), + (0x1D3D, 'M', u'ȣ'), + (0x1D3E, 'M', u'p'), + (0x1D3F, 'M', u'r'), + (0x1D40, 'M', u't'), + (0x1D41, 'M', u'u'), + (0x1D42, 'M', u'w'), + (0x1D43, 'M', u'a'), + (0x1D44, 'M', u'ɐ'), + (0x1D45, 'M', u'ɑ'), + (0x1D46, 'M', u'ᴂ'), + (0x1D47, 'M', u'b'), + (0x1D48, 'M', u'd'), + (0x1D49, 'M', u'e'), + (0x1D4A, 'M', u'ə'), + (0x1D4B, 'M', u'ɛ'), + (0x1D4C, 'M', u'ɜ'), + (0x1D4D, 'M', u'g'), + (0x1D4E, 'V'), + (0x1D4F, 'M', u'k'), + (0x1D50, 'M', u'm'), + (0x1D51, 'M', u'ŋ'), + (0x1D52, 'M', u'o'), + ] + +def _seg_16(): + return [ + (0x1D53, 'M', u'ɔ'), + (0x1D54, 'M', u'ᴖ'), + (0x1D55, 'M', u'ᴗ'), + (0x1D56, 'M', u'p'), + (0x1D57, 'M', u't'), + (0x1D58, 'M', u'u'), + (0x1D59, 'M', u'ᴝ'), + (0x1D5A, 'M', u'ɯ'), + (0x1D5B, 'M', u'v'), + (0x1D5C, 'M', u'ᴥ'), + (0x1D5D, 'M', u'β'), + (0x1D5E, 'M', u'γ'), + (0x1D5F, 'M', u'δ'), + (0x1D60, 'M', u'φ'), + (0x1D61, 'M', u'χ'), + (0x1D62, 'M', u'i'), + (0x1D63, 'M', u'r'), + (0x1D64, 'M', u'u'), + (0x1D65, 'M', u'v'), + (0x1D66, 'M', u'β'), + (0x1D67, 'M', u'γ'), + (0x1D68, 'M', u'ρ'), + (0x1D69, 'M', u'φ'), + (0x1D6A, 'M', u'χ'), + (0x1D6B, 'V'), + (0x1D78, 'M', u'н'), + (0x1D79, 'V'), + (0x1D9B, 'M', u'ɒ'), + (0x1D9C, 'M', u'c'), + (0x1D9D, 'M', u'ɕ'), + (0x1D9E, 'M', u'ð'), + (0x1D9F, 'M', u'ɜ'), + (0x1DA0, 'M', u'f'), + (0x1DA1, 'M', u'ɟ'), + (0x1DA2, 'M', u'ɡ'), + (0x1DA3, 'M', u'ɥ'), + (0x1DA4, 'M', u'ɨ'), + (0x1DA5, 'M', u'ɩ'), + (0x1DA6, 'M', u'ɪ'), + (0x1DA7, 'M', u'ᵻ'), + (0x1DA8, 'M', u'ʝ'), + (0x1DA9, 'M', u'ɭ'), + (0x1DAA, 'M', u'ᶅ'), + (0x1DAB, 'M', u'ʟ'), + (0x1DAC, 'M', u'ɱ'), + (0x1DAD, 'M', u'ɰ'), + (0x1DAE, 'M', u'ɲ'), + (0x1DAF, 'M', u'ɳ'), + (0x1DB0, 'M', u'ɴ'), + (0x1DB1, 'M', u'ɵ'), + (0x1DB2, 'M', u'ɸ'), + (0x1DB3, 'M', u'ʂ'), + (0x1DB4, 'M', u'ʃ'), + (0x1DB5, 'M', u'ƫ'), + (0x1DB6, 'M', u'ʉ'), + (0x1DB7, 'M', u'ʊ'), + (0x1DB8, 'M', u'ᴜ'), + (0x1DB9, 'M', u'ʋ'), + (0x1DBA, 'M', u'ʌ'), + (0x1DBB, 'M', u'z'), + (0x1DBC, 'M', u'ʐ'), + (0x1DBD, 'M', u'ʑ'), + (0x1DBE, 'M', u'ʒ'), + (0x1DBF, 'M', u'θ'), + (0x1DC0, 'V'), + (0x1DFA, 'X'), + (0x1DFB, 'V'), + (0x1E00, 'M', u'ḁ'), + (0x1E01, 'V'), + (0x1E02, 'M', u'ḃ'), + (0x1E03, 'V'), + (0x1E04, 'M', u'ḅ'), + (0x1E05, 'V'), + (0x1E06, 'M', u'ḇ'), + (0x1E07, 'V'), + (0x1E08, 'M', u'ḉ'), + (0x1E09, 'V'), + (0x1E0A, 'M', u'ḋ'), + (0x1E0B, 'V'), + (0x1E0C, 'M', u'ḍ'), + (0x1E0D, 'V'), + (0x1E0E, 'M', u'ḏ'), + (0x1E0F, 'V'), + (0x1E10, 'M', u'ḑ'), + (0x1E11, 'V'), + (0x1E12, 'M', u'ḓ'), + (0x1E13, 'V'), + (0x1E14, 'M', u'ḕ'), + (0x1E15, 'V'), + (0x1E16, 'M', u'ḗ'), + (0x1E17, 'V'), + (0x1E18, 'M', u'ḙ'), + (0x1E19, 'V'), + (0x1E1A, 'M', u'ḛ'), + (0x1E1B, 'V'), + (0x1E1C, 'M', u'ḝ'), + (0x1E1D, 'V'), + (0x1E1E, 'M', u'ḟ'), + (0x1E1F, 'V'), + (0x1E20, 'M', u'ḡ'), + ] + +def _seg_17(): + return [ + (0x1E21, 'V'), + (0x1E22, 'M', u'ḣ'), + (0x1E23, 'V'), + (0x1E24, 'M', u'ḥ'), + (0x1E25, 'V'), + (0x1E26, 'M', u'ḧ'), + (0x1E27, 'V'), + (0x1E28, 'M', u'ḩ'), + (0x1E29, 'V'), + (0x1E2A, 'M', u'ḫ'), + (0x1E2B, 'V'), + (0x1E2C, 'M', u'ḭ'), + (0x1E2D, 'V'), + (0x1E2E, 'M', u'ḯ'), + (0x1E2F, 'V'), + (0x1E30, 'M', u'ḱ'), + (0x1E31, 'V'), + (0x1E32, 'M', u'ḳ'), + (0x1E33, 'V'), + (0x1E34, 'M', u'ḵ'), + (0x1E35, 'V'), + (0x1E36, 'M', u'ḷ'), + (0x1E37, 'V'), + (0x1E38, 'M', u'ḹ'), + (0x1E39, 'V'), + (0x1E3A, 'M', u'ḻ'), + (0x1E3B, 'V'), + (0x1E3C, 'M', u'ḽ'), + (0x1E3D, 'V'), + (0x1E3E, 'M', u'ḿ'), + (0x1E3F, 'V'), + (0x1E40, 'M', u'ṁ'), + (0x1E41, 'V'), + (0x1E42, 'M', u'ṃ'), + (0x1E43, 'V'), + (0x1E44, 'M', u'ṅ'), + (0x1E45, 'V'), + (0x1E46, 'M', u'ṇ'), + (0x1E47, 'V'), + (0x1E48, 'M', u'ṉ'), + (0x1E49, 'V'), + (0x1E4A, 'M', u'ṋ'), + (0x1E4B, 'V'), + (0x1E4C, 'M', u'ṍ'), + (0x1E4D, 'V'), + (0x1E4E, 'M', u'ṏ'), + (0x1E4F, 'V'), + (0x1E50, 'M', u'ṑ'), + (0x1E51, 'V'), + (0x1E52, 'M', u'ṓ'), + (0x1E53, 'V'), + (0x1E54, 'M', u'ṕ'), + (0x1E55, 'V'), + (0x1E56, 'M', u'ṗ'), + (0x1E57, 'V'), + (0x1E58, 'M', u'ṙ'), + (0x1E59, 'V'), + (0x1E5A, 'M', u'ṛ'), + (0x1E5B, 'V'), + (0x1E5C, 'M', u'ṝ'), + (0x1E5D, 'V'), + (0x1E5E, 'M', u'ṟ'), + (0x1E5F, 'V'), + (0x1E60, 'M', u'ṡ'), + (0x1E61, 'V'), + (0x1E62, 'M', u'ṣ'), + (0x1E63, 'V'), + (0x1E64, 'M', u'ṥ'), + (0x1E65, 'V'), + (0x1E66, 'M', u'ṧ'), + (0x1E67, 'V'), + (0x1E68, 'M', u'ṩ'), + (0x1E69, 'V'), + (0x1E6A, 'M', u'ṫ'), + (0x1E6B, 'V'), + (0x1E6C, 'M', u'ṭ'), + (0x1E6D, 'V'), + (0x1E6E, 'M', u'ṯ'), + (0x1E6F, 'V'), + (0x1E70, 'M', u'ṱ'), + (0x1E71, 'V'), + (0x1E72, 'M', u'ṳ'), + (0x1E73, 'V'), + (0x1E74, 'M', u'ṵ'), + (0x1E75, 'V'), + (0x1E76, 'M', u'ṷ'), + (0x1E77, 'V'), + (0x1E78, 'M', u'ṹ'), + (0x1E79, 'V'), + (0x1E7A, 'M', u'ṻ'), + (0x1E7B, 'V'), + (0x1E7C, 'M', u'ṽ'), + (0x1E7D, 'V'), + (0x1E7E, 'M', u'ṿ'), + (0x1E7F, 'V'), + (0x1E80, 'M', u'ẁ'), + (0x1E81, 'V'), + (0x1E82, 'M', u'ẃ'), + (0x1E83, 'V'), + (0x1E84, 'M', u'ẅ'), + ] + +def _seg_18(): + return [ + (0x1E85, 'V'), + (0x1E86, 'M', u'ẇ'), + (0x1E87, 'V'), + (0x1E88, 'M', u'ẉ'), + (0x1E89, 'V'), + (0x1E8A, 'M', u'ẋ'), + (0x1E8B, 'V'), + (0x1E8C, 'M', u'ẍ'), + (0x1E8D, 'V'), + (0x1E8E, 'M', u'ẏ'), + (0x1E8F, 'V'), + (0x1E90, 'M', u'ẑ'), + (0x1E91, 'V'), + (0x1E92, 'M', u'ẓ'), + (0x1E93, 'V'), + (0x1E94, 'M', u'ẕ'), + (0x1E95, 'V'), + (0x1E9A, 'M', u'aʾ'), + (0x1E9B, 'M', u'ṡ'), + (0x1E9C, 'V'), + (0x1E9E, 'M', u'ss'), + (0x1E9F, 'V'), + (0x1EA0, 'M', u'ạ'), + (0x1EA1, 'V'), + (0x1EA2, 'M', u'ả'), + (0x1EA3, 'V'), + (0x1EA4, 'M', u'ấ'), + (0x1EA5, 'V'), + (0x1EA6, 'M', u'ầ'), + (0x1EA7, 'V'), + (0x1EA8, 'M', u'ẩ'), + (0x1EA9, 'V'), + (0x1EAA, 'M', u'ẫ'), + (0x1EAB, 'V'), + (0x1EAC, 'M', u'ậ'), + (0x1EAD, 'V'), + (0x1EAE, 'M', u'ắ'), + (0x1EAF, 'V'), + (0x1EB0, 'M', u'ằ'), + (0x1EB1, 'V'), + (0x1EB2, 'M', u'ẳ'), + (0x1EB3, 'V'), + (0x1EB4, 'M', u'ẵ'), + (0x1EB5, 'V'), + (0x1EB6, 'M', u'ặ'), + (0x1EB7, 'V'), + (0x1EB8, 'M', u'ẹ'), + (0x1EB9, 'V'), + (0x1EBA, 'M', u'ẻ'), + (0x1EBB, 'V'), + (0x1EBC, 'M', u'ẽ'), + (0x1EBD, 'V'), + (0x1EBE, 'M', u'ế'), + (0x1EBF, 'V'), + (0x1EC0, 'M', u'ề'), + (0x1EC1, 'V'), + (0x1EC2, 'M', u'ể'), + (0x1EC3, 'V'), + (0x1EC4, 'M', u'ễ'), + (0x1EC5, 'V'), + (0x1EC6, 'M', u'ệ'), + (0x1EC7, 'V'), + (0x1EC8, 'M', u'ỉ'), + (0x1EC9, 'V'), + (0x1ECA, 'M', u'ị'), + (0x1ECB, 'V'), + (0x1ECC, 'M', u'ọ'), + (0x1ECD, 'V'), + (0x1ECE, 'M', u'ỏ'), + (0x1ECF, 'V'), + (0x1ED0, 'M', u'ố'), + (0x1ED1, 'V'), + (0x1ED2, 'M', u'ồ'), + (0x1ED3, 'V'), + (0x1ED4, 'M', u'ổ'), + (0x1ED5, 'V'), + (0x1ED6, 'M', u'ỗ'), + (0x1ED7, 'V'), + (0x1ED8, 'M', u'ộ'), + (0x1ED9, 'V'), + (0x1EDA, 'M', u'ớ'), + (0x1EDB, 'V'), + (0x1EDC, 'M', u'ờ'), + (0x1EDD, 'V'), + (0x1EDE, 'M', u'ở'), + (0x1EDF, 'V'), + (0x1EE0, 'M', u'ỡ'), + (0x1EE1, 'V'), + (0x1EE2, 'M', u'ợ'), + (0x1EE3, 'V'), + (0x1EE4, 'M', u'ụ'), + (0x1EE5, 'V'), + (0x1EE6, 'M', u'ủ'), + (0x1EE7, 'V'), + (0x1EE8, 'M', u'ứ'), + (0x1EE9, 'V'), + (0x1EEA, 'M', u'ừ'), + (0x1EEB, 'V'), + (0x1EEC, 'M', u'ử'), + (0x1EED, 'V'), + ] + +def _seg_19(): + return [ + (0x1EEE, 'M', u'ữ'), + (0x1EEF, 'V'), + (0x1EF0, 'M', u'ự'), + (0x1EF1, 'V'), + (0x1EF2, 'M', u'ỳ'), + (0x1EF3, 'V'), + (0x1EF4, 'M', u'ỵ'), + (0x1EF5, 'V'), + (0x1EF6, 'M', u'ỷ'), + (0x1EF7, 'V'), + (0x1EF8, 'M', u'ỹ'), + (0x1EF9, 'V'), + (0x1EFA, 'M', u'ỻ'), + (0x1EFB, 'V'), + (0x1EFC, 'M', u'ỽ'), + (0x1EFD, 'V'), + (0x1EFE, 'M', u'ỿ'), + (0x1EFF, 'V'), + (0x1F08, 'M', u'ἀ'), + (0x1F09, 'M', u'ἁ'), + (0x1F0A, 'M', u'ἂ'), + (0x1F0B, 'M', u'ἃ'), + (0x1F0C, 'M', u'ἄ'), + (0x1F0D, 'M', u'ἅ'), + (0x1F0E, 'M', u'ἆ'), + (0x1F0F, 'M', u'ἇ'), + (0x1F10, 'V'), + (0x1F16, 'X'), + (0x1F18, 'M', u'ἐ'), + (0x1F19, 'M', u'ἑ'), + (0x1F1A, 'M', u'ἒ'), + (0x1F1B, 'M', u'ἓ'), + (0x1F1C, 'M', u'ἔ'), + (0x1F1D, 'M', u'ἕ'), + (0x1F1E, 'X'), + (0x1F20, 'V'), + (0x1F28, 'M', u'ἠ'), + (0x1F29, 'M', u'ἡ'), + (0x1F2A, 'M', u'ἢ'), + (0x1F2B, 'M', u'ἣ'), + (0x1F2C, 'M', u'ἤ'), + (0x1F2D, 'M', u'ἥ'), + (0x1F2E, 'M', u'ἦ'), + (0x1F2F, 'M', u'ἧ'), + (0x1F30, 'V'), + (0x1F38, 'M', u'ἰ'), + (0x1F39, 'M', u'ἱ'), + (0x1F3A, 'M', u'ἲ'), + (0x1F3B, 'M', u'ἳ'), + (0x1F3C, 'M', u'ἴ'), + (0x1F3D, 'M', u'ἵ'), + (0x1F3E, 'M', u'ἶ'), + (0x1F3F, 'M', u'ἷ'), + (0x1F40, 'V'), + (0x1F46, 'X'), + (0x1F48, 'M', u'ὀ'), + (0x1F49, 'M', u'ὁ'), + (0x1F4A, 'M', u'ὂ'), + (0x1F4B, 'M', u'ὃ'), + (0x1F4C, 'M', u'ὄ'), + (0x1F4D, 'M', u'ὅ'), + (0x1F4E, 'X'), + (0x1F50, 'V'), + (0x1F58, 'X'), + (0x1F59, 'M', u'ὑ'), + (0x1F5A, 'X'), + (0x1F5B, 'M', u'ὓ'), + (0x1F5C, 'X'), + (0x1F5D, 'M', u'ὕ'), + (0x1F5E, 'X'), + (0x1F5F, 'M', u'ὗ'), + (0x1F60, 'V'), + (0x1F68, 'M', u'ὠ'), + (0x1F69, 'M', u'ὡ'), + (0x1F6A, 'M', u'ὢ'), + (0x1F6B, 'M', u'ὣ'), + (0x1F6C, 'M', u'ὤ'), + (0x1F6D, 'M', u'ὥ'), + (0x1F6E, 'M', u'ὦ'), + (0x1F6F, 'M', u'ὧ'), + (0x1F70, 'V'), + (0x1F71, 'M', u'ά'), + (0x1F72, 'V'), + (0x1F73, 'M', u'έ'), + (0x1F74, 'V'), + (0x1F75, 'M', u'ή'), + (0x1F76, 'V'), + (0x1F77, 'M', u'ί'), + (0x1F78, 'V'), + (0x1F79, 'M', u'ό'), + (0x1F7A, 'V'), + (0x1F7B, 'M', u'ύ'), + (0x1F7C, 'V'), + (0x1F7D, 'M', u'ώ'), + (0x1F7E, 'X'), + (0x1F80, 'M', u'ἀι'), + (0x1F81, 'M', u'ἁι'), + (0x1F82, 'M', u'ἂι'), + (0x1F83, 'M', u'ἃι'), + (0x1F84, 'M', u'ἄι'), + ] + +def _seg_20(): + return [ + (0x1F85, 'M', u'ἅι'), + (0x1F86, 'M', u'ἆι'), + (0x1F87, 'M', u'ἇι'), + (0x1F88, 'M', u'ἀι'), + (0x1F89, 'M', u'ἁι'), + (0x1F8A, 'M', u'ἂι'), + (0x1F8B, 'M', u'ἃι'), + (0x1F8C, 'M', u'ἄι'), + (0x1F8D, 'M', u'ἅι'), + (0x1F8E, 'M', u'ἆι'), + (0x1F8F, 'M', u'ἇι'), + (0x1F90, 'M', u'ἠι'), + (0x1F91, 'M', u'ἡι'), + (0x1F92, 'M', u'ἢι'), + (0x1F93, 'M', u'ἣι'), + (0x1F94, 'M', u'ἤι'), + (0x1F95, 'M', u'ἥι'), + (0x1F96, 'M', u'ἦι'), + (0x1F97, 'M', u'ἧι'), + (0x1F98, 'M', u'ἠι'), + (0x1F99, 'M', u'ἡι'), + (0x1F9A, 'M', u'ἢι'), + (0x1F9B, 'M', u'ἣι'), + (0x1F9C, 'M', u'ἤι'), + (0x1F9D, 'M', u'ἥι'), + (0x1F9E, 'M', u'ἦι'), + (0x1F9F, 'M', u'ἧι'), + (0x1FA0, 'M', u'ὠι'), + (0x1FA1, 'M', u'ὡι'), + (0x1FA2, 'M', u'ὢι'), + (0x1FA3, 'M', u'ὣι'), + (0x1FA4, 'M', u'ὤι'), + (0x1FA5, 'M', u'ὥι'), + (0x1FA6, 'M', u'ὦι'), + (0x1FA7, 'M', u'ὧι'), + (0x1FA8, 'M', u'ὠι'), + (0x1FA9, 'M', u'ὡι'), + (0x1FAA, 'M', u'ὢι'), + (0x1FAB, 'M', u'ὣι'), + (0x1FAC, 'M', u'ὤι'), + (0x1FAD, 'M', u'ὥι'), + (0x1FAE, 'M', u'ὦι'), + (0x1FAF, 'M', u'ὧι'), + (0x1FB0, 'V'), + (0x1FB2, 'M', u'ὰι'), + (0x1FB3, 'M', u'αι'), + (0x1FB4, 'M', u'άι'), + (0x1FB5, 'X'), + (0x1FB6, 'V'), + (0x1FB7, 'M', u'ᾶι'), + (0x1FB8, 'M', u'ᾰ'), + (0x1FB9, 'M', u'ᾱ'), + (0x1FBA, 'M', u'ὰ'), + (0x1FBB, 'M', u'ά'), + (0x1FBC, 'M', u'αι'), + (0x1FBD, '3', u' ̓'), + (0x1FBE, 'M', u'ι'), + (0x1FBF, '3', u' ̓'), + (0x1FC0, '3', u' ͂'), + (0x1FC1, '3', u' ̈͂'), + (0x1FC2, 'M', u'ὴι'), + (0x1FC3, 'M', u'ηι'), + (0x1FC4, 'M', u'ήι'), + (0x1FC5, 'X'), + (0x1FC6, 'V'), + (0x1FC7, 'M', u'ῆι'), + (0x1FC8, 'M', u'ὲ'), + (0x1FC9, 'M', u'έ'), + (0x1FCA, 'M', u'ὴ'), + (0x1FCB, 'M', u'ή'), + (0x1FCC, 'M', u'ηι'), + (0x1FCD, '3', u' ̓̀'), + (0x1FCE, '3', u' ̓́'), + (0x1FCF, '3', u' ̓͂'), + (0x1FD0, 'V'), + (0x1FD3, 'M', u'ΐ'), + (0x1FD4, 'X'), + (0x1FD6, 'V'), + (0x1FD8, 'M', u'ῐ'), + (0x1FD9, 'M', u'ῑ'), + (0x1FDA, 'M', u'ὶ'), + (0x1FDB, 'M', u'ί'), + (0x1FDC, 'X'), + (0x1FDD, '3', u' ̔̀'), + (0x1FDE, '3', u' ̔́'), + (0x1FDF, '3', u' ̔͂'), + (0x1FE0, 'V'), + (0x1FE3, 'M', u'ΰ'), + (0x1FE4, 'V'), + (0x1FE8, 'M', u'ῠ'), + (0x1FE9, 'M', u'ῡ'), + (0x1FEA, 'M', u'ὺ'), + (0x1FEB, 'M', u'ύ'), + (0x1FEC, 'M', u'ῥ'), + (0x1FED, '3', u' ̈̀'), + (0x1FEE, '3', u' ̈́'), + (0x1FEF, '3', u'`'), + (0x1FF0, 'X'), + (0x1FF2, 'M', u'ὼι'), + (0x1FF3, 'M', u'ωι'), + ] + +def _seg_21(): + return [ + (0x1FF4, 'M', u'ώι'), + (0x1FF5, 'X'), + (0x1FF6, 'V'), + (0x1FF7, 'M', u'ῶι'), + (0x1FF8, 'M', u'ὸ'), + (0x1FF9, 'M', u'ό'), + (0x1FFA, 'M', u'ὼ'), + (0x1FFB, 'M', u'ώ'), + (0x1FFC, 'M', u'ωι'), + (0x1FFD, '3', u' ́'), + (0x1FFE, '3', u' ̔'), + (0x1FFF, 'X'), + (0x2000, '3', u' '), + (0x200B, 'I'), + (0x200C, 'D', u''), + (0x200E, 'X'), + (0x2010, 'V'), + (0x2011, 'M', u'‐'), + (0x2012, 'V'), + (0x2017, '3', u' ̳'), + (0x2018, 'V'), + (0x2024, 'X'), + (0x2027, 'V'), + (0x2028, 'X'), + (0x202F, '3', u' '), + (0x2030, 'V'), + (0x2033, 'M', u'′′'), + (0x2034, 'M', u'′′′'), + (0x2035, 'V'), + (0x2036, 'M', u'‵‵'), + (0x2037, 'M', u'‵‵‵'), + (0x2038, 'V'), + (0x203C, '3', u'!!'), + (0x203D, 'V'), + (0x203E, '3', u' ̅'), + (0x203F, 'V'), + (0x2047, '3', u'??'), + (0x2048, '3', u'?!'), + (0x2049, '3', u'!?'), + (0x204A, 'V'), + (0x2057, 'M', u'′′′′'), + (0x2058, 'V'), + (0x205F, '3', u' '), + (0x2060, 'I'), + (0x2061, 'X'), + (0x2064, 'I'), + (0x2065, 'X'), + (0x2070, 'M', u'0'), + (0x2071, 'M', u'i'), + (0x2072, 'X'), + (0x2074, 'M', u'4'), + (0x2075, 'M', u'5'), + (0x2076, 'M', u'6'), + (0x2077, 'M', u'7'), + (0x2078, 'M', u'8'), + (0x2079, 'M', u'9'), + (0x207A, '3', u'+'), + (0x207B, 'M', u'−'), + (0x207C, '3', u'='), + (0x207D, '3', u'('), + (0x207E, '3', u')'), + (0x207F, 'M', u'n'), + (0x2080, 'M', u'0'), + (0x2081, 'M', u'1'), + (0x2082, 'M', u'2'), + (0x2083, 'M', u'3'), + (0x2084, 'M', u'4'), + (0x2085, 'M', u'5'), + (0x2086, 'M', u'6'), + (0x2087, 'M', u'7'), + (0x2088, 'M', u'8'), + (0x2089, 'M', u'9'), + (0x208A, '3', u'+'), + (0x208B, 'M', u'−'), + (0x208C, '3', u'='), + (0x208D, '3', u'('), + (0x208E, '3', u')'), + (0x208F, 'X'), + (0x2090, 'M', u'a'), + (0x2091, 'M', u'e'), + (0x2092, 'M', u'o'), + (0x2093, 'M', u'x'), + (0x2094, 'M', u'ə'), + (0x2095, 'M', u'h'), + (0x2096, 'M', u'k'), + (0x2097, 'M', u'l'), + (0x2098, 'M', u'm'), + (0x2099, 'M', u'n'), + (0x209A, 'M', u'p'), + (0x209B, 'M', u's'), + (0x209C, 'M', u't'), + (0x209D, 'X'), + (0x20A0, 'V'), + (0x20A8, 'M', u'rs'), + (0x20A9, 'V'), + (0x20C0, 'X'), + (0x20D0, 'V'), + (0x20F1, 'X'), + (0x2100, '3', u'a/c'), + (0x2101, '3', u'a/s'), + ] + +def _seg_22(): + return [ + (0x2102, 'M', u'c'), + (0x2103, 'M', u'°c'), + (0x2104, 'V'), + (0x2105, '3', u'c/o'), + (0x2106, '3', u'c/u'), + (0x2107, 'M', u'ɛ'), + (0x2108, 'V'), + (0x2109, 'M', u'°f'), + (0x210A, 'M', u'g'), + (0x210B, 'M', u'h'), + (0x210F, 'M', u'ħ'), + (0x2110, 'M', u'i'), + (0x2112, 'M', u'l'), + (0x2114, 'V'), + (0x2115, 'M', u'n'), + (0x2116, 'M', u'no'), + (0x2117, 'V'), + (0x2119, 'M', u'p'), + (0x211A, 'M', u'q'), + (0x211B, 'M', u'r'), + (0x211E, 'V'), + (0x2120, 'M', u'sm'), + (0x2121, 'M', u'tel'), + (0x2122, 'M', u'tm'), + (0x2123, 'V'), + (0x2124, 'M', u'z'), + (0x2125, 'V'), + (0x2126, 'M', u'ω'), + (0x2127, 'V'), + (0x2128, 'M', u'z'), + (0x2129, 'V'), + (0x212A, 'M', u'k'), + (0x212B, 'M', u'å'), + (0x212C, 'M', u'b'), + (0x212D, 'M', u'c'), + (0x212E, 'V'), + (0x212F, 'M', u'e'), + (0x2131, 'M', u'f'), + (0x2132, 'X'), + (0x2133, 'M', u'm'), + (0x2134, 'M', u'o'), + (0x2135, 'M', u'א'), + (0x2136, 'M', u'ב'), + (0x2137, 'M', u'ג'), + (0x2138, 'M', u'ד'), + (0x2139, 'M', u'i'), + (0x213A, 'V'), + (0x213B, 'M', u'fax'), + (0x213C, 'M', u'π'), + (0x213D, 'M', u'γ'), + (0x213F, 'M', u'π'), + (0x2140, 'M', u'∑'), + (0x2141, 'V'), + (0x2145, 'M', u'd'), + (0x2147, 'M', u'e'), + (0x2148, 'M', u'i'), + (0x2149, 'M', u'j'), + (0x214A, 'V'), + (0x2150, 'M', u'1⁄7'), + (0x2151, 'M', u'1⁄9'), + (0x2152, 'M', u'1⁄10'), + (0x2153, 'M', u'1⁄3'), + (0x2154, 'M', u'2⁄3'), + (0x2155, 'M', u'1⁄5'), + (0x2156, 'M', u'2⁄5'), + (0x2157, 'M', u'3⁄5'), + (0x2158, 'M', u'4⁄5'), + (0x2159, 'M', u'1⁄6'), + (0x215A, 'M', u'5⁄6'), + (0x215B, 'M', u'1⁄8'), + (0x215C, 'M', u'3⁄8'), + (0x215D, 'M', u'5⁄8'), + (0x215E, 'M', u'7⁄8'), + (0x215F, 'M', u'1⁄'), + (0x2160, 'M', u'i'), + (0x2161, 'M', u'ii'), + (0x2162, 'M', u'iii'), + (0x2163, 'M', u'iv'), + (0x2164, 'M', u'v'), + (0x2165, 'M', u'vi'), + (0x2166, 'M', u'vii'), + (0x2167, 'M', u'viii'), + (0x2168, 'M', u'ix'), + (0x2169, 'M', u'x'), + (0x216A, 'M', u'xi'), + (0x216B, 'M', u'xii'), + (0x216C, 'M', u'l'), + (0x216D, 'M', u'c'), + (0x216E, 'M', u'd'), + (0x216F, 'M', u'm'), + (0x2170, 'M', u'i'), + (0x2171, 'M', u'ii'), + (0x2172, 'M', u'iii'), + (0x2173, 'M', u'iv'), + (0x2174, 'M', u'v'), + (0x2175, 'M', u'vi'), + (0x2176, 'M', u'vii'), + (0x2177, 'M', u'viii'), + (0x2178, 'M', u'ix'), + (0x2179, 'M', u'x'), + ] + +def _seg_23(): + return [ + (0x217A, 'M', u'xi'), + (0x217B, 'M', u'xii'), + (0x217C, 'M', u'l'), + (0x217D, 'M', u'c'), + (0x217E, 'M', u'd'), + (0x217F, 'M', u'm'), + (0x2180, 'V'), + (0x2183, 'X'), + (0x2184, 'V'), + (0x2189, 'M', u'0⁄3'), + (0x218A, 'V'), + (0x218C, 'X'), + (0x2190, 'V'), + (0x222C, 'M', u'∫∫'), + (0x222D, 'M', u'∫∫∫'), + (0x222E, 'V'), + (0x222F, 'M', u'∮∮'), + (0x2230, 'M', u'∮∮∮'), + (0x2231, 'V'), + (0x2260, '3'), + (0x2261, 'V'), + (0x226E, '3'), + (0x2270, 'V'), + (0x2329, 'M', u'〈'), + (0x232A, 'M', u'〉'), + (0x232B, 'V'), + (0x2427, 'X'), + (0x2440, 'V'), + (0x244B, 'X'), + (0x2460, 'M', u'1'), + (0x2461, 'M', u'2'), + (0x2462, 'M', u'3'), + (0x2463, 'M', u'4'), + (0x2464, 'M', u'5'), + (0x2465, 'M', u'6'), + (0x2466, 'M', u'7'), + (0x2467, 'M', u'8'), + (0x2468, 'M', u'9'), + (0x2469, 'M', u'10'), + (0x246A, 'M', u'11'), + (0x246B, 'M', u'12'), + (0x246C, 'M', u'13'), + (0x246D, 'M', u'14'), + (0x246E, 'M', u'15'), + (0x246F, 'M', u'16'), + (0x2470, 'M', u'17'), + (0x2471, 'M', u'18'), + (0x2472, 'M', u'19'), + (0x2473, 'M', u'20'), + (0x2474, '3', u'(1)'), + (0x2475, '3', u'(2)'), + (0x2476, '3', u'(3)'), + (0x2477, '3', u'(4)'), + (0x2478, '3', u'(5)'), + (0x2479, '3', u'(6)'), + (0x247A, '3', u'(7)'), + (0x247B, '3', u'(8)'), + (0x247C, '3', u'(9)'), + (0x247D, '3', u'(10)'), + (0x247E, '3', u'(11)'), + (0x247F, '3', u'(12)'), + (0x2480, '3', u'(13)'), + (0x2481, '3', u'(14)'), + (0x2482, '3', u'(15)'), + (0x2483, '3', u'(16)'), + (0x2484, '3', u'(17)'), + (0x2485, '3', u'(18)'), + (0x2486, '3', u'(19)'), + (0x2487, '3', u'(20)'), + (0x2488, 'X'), + (0x249C, '3', u'(a)'), + (0x249D, '3', u'(b)'), + (0x249E, '3', u'(c)'), + (0x249F, '3', u'(d)'), + (0x24A0, '3', u'(e)'), + (0x24A1, '3', u'(f)'), + (0x24A2, '3', u'(g)'), + (0x24A3, '3', u'(h)'), + (0x24A4, '3', u'(i)'), + (0x24A5, '3', u'(j)'), + (0x24A6, '3', u'(k)'), + (0x24A7, '3', u'(l)'), + (0x24A8, '3', u'(m)'), + (0x24A9, '3', u'(n)'), + (0x24AA, '3', u'(o)'), + (0x24AB, '3', u'(p)'), + (0x24AC, '3', u'(q)'), + (0x24AD, '3', u'(r)'), + (0x24AE, '3', u'(s)'), + (0x24AF, '3', u'(t)'), + (0x24B0, '3', u'(u)'), + (0x24B1, '3', u'(v)'), + (0x24B2, '3', u'(w)'), + (0x24B3, '3', u'(x)'), + (0x24B4, '3', u'(y)'), + (0x24B5, '3', u'(z)'), + (0x24B6, 'M', u'a'), + (0x24B7, 'M', u'b'), + (0x24B8, 'M', u'c'), + (0x24B9, 'M', u'd'), + ] + +def _seg_24(): + return [ + (0x24BA, 'M', u'e'), + (0x24BB, 'M', u'f'), + (0x24BC, 'M', u'g'), + (0x24BD, 'M', u'h'), + (0x24BE, 'M', u'i'), + (0x24BF, 'M', u'j'), + (0x24C0, 'M', u'k'), + (0x24C1, 'M', u'l'), + (0x24C2, 'M', u'm'), + (0x24C3, 'M', u'n'), + (0x24C4, 'M', u'o'), + (0x24C5, 'M', u'p'), + (0x24C6, 'M', u'q'), + (0x24C7, 'M', u'r'), + (0x24C8, 'M', u's'), + (0x24C9, 'M', u't'), + (0x24CA, 'M', u'u'), + (0x24CB, 'M', u'v'), + (0x24CC, 'M', u'w'), + (0x24CD, 'M', u'x'), + (0x24CE, 'M', u'y'), + (0x24CF, 'M', u'z'), + (0x24D0, 'M', u'a'), + (0x24D1, 'M', u'b'), + (0x24D2, 'M', u'c'), + (0x24D3, 'M', u'd'), + (0x24D4, 'M', u'e'), + (0x24D5, 'M', u'f'), + (0x24D6, 'M', u'g'), + (0x24D7, 'M', u'h'), + (0x24D8, 'M', u'i'), + (0x24D9, 'M', u'j'), + (0x24DA, 'M', u'k'), + (0x24DB, 'M', u'l'), + (0x24DC, 'M', u'm'), + (0x24DD, 'M', u'n'), + (0x24DE, 'M', u'o'), + (0x24DF, 'M', u'p'), + (0x24E0, 'M', u'q'), + (0x24E1, 'M', u'r'), + (0x24E2, 'M', u's'), + (0x24E3, 'M', u't'), + (0x24E4, 'M', u'u'), + (0x24E5, 'M', u'v'), + (0x24E6, 'M', u'w'), + (0x24E7, 'M', u'x'), + (0x24E8, 'M', u'y'), + (0x24E9, 'M', u'z'), + (0x24EA, 'M', u'0'), + (0x24EB, 'V'), + (0x2A0C, 'M', u'∫∫∫∫'), + (0x2A0D, 'V'), + (0x2A74, '3', u'::='), + (0x2A75, '3', u'=='), + (0x2A76, '3', u'==='), + (0x2A77, 'V'), + (0x2ADC, 'M', u'⫝̸'), + (0x2ADD, 'V'), + (0x2B74, 'X'), + (0x2B76, 'V'), + (0x2B96, 'X'), + (0x2B97, 'V'), + (0x2C00, 'M', u'ⰰ'), + (0x2C01, 'M', u'ⰱ'), + (0x2C02, 'M', u'ⰲ'), + (0x2C03, 'M', u'ⰳ'), + (0x2C04, 'M', u'ⰴ'), + (0x2C05, 'M', u'ⰵ'), + (0x2C06, 'M', u'ⰶ'), + (0x2C07, 'M', u'ⰷ'), + (0x2C08, 'M', u'ⰸ'), + (0x2C09, 'M', u'ⰹ'), + (0x2C0A, 'M', u'ⰺ'), + (0x2C0B, 'M', u'ⰻ'), + (0x2C0C, 'M', u'ⰼ'), + (0x2C0D, 'M', u'ⰽ'), + (0x2C0E, 'M', u'ⰾ'), + (0x2C0F, 'M', u'ⰿ'), + (0x2C10, 'M', u'ⱀ'), + (0x2C11, 'M', u'ⱁ'), + (0x2C12, 'M', u'ⱂ'), + (0x2C13, 'M', u'ⱃ'), + (0x2C14, 'M', u'ⱄ'), + (0x2C15, 'M', u'ⱅ'), + (0x2C16, 'M', u'ⱆ'), + (0x2C17, 'M', u'ⱇ'), + (0x2C18, 'M', u'ⱈ'), + (0x2C19, 'M', u'ⱉ'), + (0x2C1A, 'M', u'ⱊ'), + (0x2C1B, 'M', u'ⱋ'), + (0x2C1C, 'M', u'ⱌ'), + (0x2C1D, 'M', u'ⱍ'), + (0x2C1E, 'M', u'ⱎ'), + (0x2C1F, 'M', u'ⱏ'), + (0x2C20, 'M', u'ⱐ'), + (0x2C21, 'M', u'ⱑ'), + (0x2C22, 'M', u'ⱒ'), + (0x2C23, 'M', u'ⱓ'), + (0x2C24, 'M', u'ⱔ'), + (0x2C25, 'M', u'ⱕ'), + ] + +def _seg_25(): + return [ + (0x2C26, 'M', u'ⱖ'), + (0x2C27, 'M', u'ⱗ'), + (0x2C28, 'M', u'ⱘ'), + (0x2C29, 'M', u'ⱙ'), + (0x2C2A, 'M', u'ⱚ'), + (0x2C2B, 'M', u'ⱛ'), + (0x2C2C, 'M', u'ⱜ'), + (0x2C2D, 'M', u'ⱝ'), + (0x2C2E, 'M', u'ⱞ'), + (0x2C2F, 'X'), + (0x2C30, 'V'), + (0x2C5F, 'X'), + (0x2C60, 'M', u'ⱡ'), + (0x2C61, 'V'), + (0x2C62, 'M', u'ɫ'), + (0x2C63, 'M', u'ᵽ'), + (0x2C64, 'M', u'ɽ'), + (0x2C65, 'V'), + (0x2C67, 'M', u'ⱨ'), + (0x2C68, 'V'), + (0x2C69, 'M', u'ⱪ'), + (0x2C6A, 'V'), + (0x2C6B, 'M', u'ⱬ'), + (0x2C6C, 'V'), + (0x2C6D, 'M', u'ɑ'), + (0x2C6E, 'M', u'ɱ'), + (0x2C6F, 'M', u'ɐ'), + (0x2C70, 'M', u'ɒ'), + (0x2C71, 'V'), + (0x2C72, 'M', u'ⱳ'), + (0x2C73, 'V'), + (0x2C75, 'M', u'ⱶ'), + (0x2C76, 'V'), + (0x2C7C, 'M', u'j'), + (0x2C7D, 'M', u'v'), + (0x2C7E, 'M', u'ȿ'), + (0x2C7F, 'M', u'ɀ'), + (0x2C80, 'M', u'ⲁ'), + (0x2C81, 'V'), + (0x2C82, 'M', u'ⲃ'), + (0x2C83, 'V'), + (0x2C84, 'M', u'ⲅ'), + (0x2C85, 'V'), + (0x2C86, 'M', u'ⲇ'), + (0x2C87, 'V'), + (0x2C88, 'M', u'ⲉ'), + (0x2C89, 'V'), + (0x2C8A, 'M', u'ⲋ'), + (0x2C8B, 'V'), + (0x2C8C, 'M', u'ⲍ'), + (0x2C8D, 'V'), + (0x2C8E, 'M', u'ⲏ'), + (0x2C8F, 'V'), + (0x2C90, 'M', u'ⲑ'), + (0x2C91, 'V'), + (0x2C92, 'M', u'ⲓ'), + (0x2C93, 'V'), + (0x2C94, 'M', u'ⲕ'), + (0x2C95, 'V'), + (0x2C96, 'M', u'ⲗ'), + (0x2C97, 'V'), + (0x2C98, 'M', u'ⲙ'), + (0x2C99, 'V'), + (0x2C9A, 'M', u'ⲛ'), + (0x2C9B, 'V'), + (0x2C9C, 'M', u'ⲝ'), + (0x2C9D, 'V'), + (0x2C9E, 'M', u'ⲟ'), + (0x2C9F, 'V'), + (0x2CA0, 'M', u'ⲡ'), + (0x2CA1, 'V'), + (0x2CA2, 'M', u'ⲣ'), + (0x2CA3, 'V'), + (0x2CA4, 'M', u'ⲥ'), + (0x2CA5, 'V'), + (0x2CA6, 'M', u'ⲧ'), + (0x2CA7, 'V'), + (0x2CA8, 'M', u'ⲩ'), + (0x2CA9, 'V'), + (0x2CAA, 'M', u'ⲫ'), + (0x2CAB, 'V'), + (0x2CAC, 'M', u'ⲭ'), + (0x2CAD, 'V'), + (0x2CAE, 'M', u'ⲯ'), + (0x2CAF, 'V'), + (0x2CB0, 'M', u'ⲱ'), + (0x2CB1, 'V'), + (0x2CB2, 'M', u'ⲳ'), + (0x2CB3, 'V'), + (0x2CB4, 'M', u'ⲵ'), + (0x2CB5, 'V'), + (0x2CB6, 'M', u'ⲷ'), + (0x2CB7, 'V'), + (0x2CB8, 'M', u'ⲹ'), + (0x2CB9, 'V'), + (0x2CBA, 'M', u'ⲻ'), + (0x2CBB, 'V'), + (0x2CBC, 'M', u'ⲽ'), + (0x2CBD, 'V'), + (0x2CBE, 'M', u'ⲿ'), + ] + +def _seg_26(): + return [ + (0x2CBF, 'V'), + (0x2CC0, 'M', u'ⳁ'), + (0x2CC1, 'V'), + (0x2CC2, 'M', u'ⳃ'), + (0x2CC3, 'V'), + (0x2CC4, 'M', u'ⳅ'), + (0x2CC5, 'V'), + (0x2CC6, 'M', u'ⳇ'), + (0x2CC7, 'V'), + (0x2CC8, 'M', u'ⳉ'), + (0x2CC9, 'V'), + (0x2CCA, 'M', u'ⳋ'), + (0x2CCB, 'V'), + (0x2CCC, 'M', u'ⳍ'), + (0x2CCD, 'V'), + (0x2CCE, 'M', u'ⳏ'), + (0x2CCF, 'V'), + (0x2CD0, 'M', u'ⳑ'), + (0x2CD1, 'V'), + (0x2CD2, 'M', u'ⳓ'), + (0x2CD3, 'V'), + (0x2CD4, 'M', u'ⳕ'), + (0x2CD5, 'V'), + (0x2CD6, 'M', u'ⳗ'), + (0x2CD7, 'V'), + (0x2CD8, 'M', u'ⳙ'), + (0x2CD9, 'V'), + (0x2CDA, 'M', u'ⳛ'), + (0x2CDB, 'V'), + (0x2CDC, 'M', u'ⳝ'), + (0x2CDD, 'V'), + (0x2CDE, 'M', u'ⳟ'), + (0x2CDF, 'V'), + (0x2CE0, 'M', u'ⳡ'), + (0x2CE1, 'V'), + (0x2CE2, 'M', u'ⳣ'), + (0x2CE3, 'V'), + (0x2CEB, 'M', u'ⳬ'), + (0x2CEC, 'V'), + (0x2CED, 'M', u'ⳮ'), + (0x2CEE, 'V'), + (0x2CF2, 'M', u'ⳳ'), + (0x2CF3, 'V'), + (0x2CF4, 'X'), + (0x2CF9, 'V'), + (0x2D26, 'X'), + (0x2D27, 'V'), + (0x2D28, 'X'), + (0x2D2D, 'V'), + (0x2D2E, 'X'), + (0x2D30, 'V'), + (0x2D68, 'X'), + (0x2D6F, 'M', u'ⵡ'), + (0x2D70, 'V'), + (0x2D71, 'X'), + (0x2D7F, 'V'), + (0x2D97, 'X'), + (0x2DA0, 'V'), + (0x2DA7, 'X'), + (0x2DA8, 'V'), + (0x2DAF, 'X'), + (0x2DB0, 'V'), + (0x2DB7, 'X'), + (0x2DB8, 'V'), + (0x2DBF, 'X'), + (0x2DC0, 'V'), + (0x2DC7, 'X'), + (0x2DC8, 'V'), + (0x2DCF, 'X'), + (0x2DD0, 'V'), + (0x2DD7, 'X'), + (0x2DD8, 'V'), + (0x2DDF, 'X'), + (0x2DE0, 'V'), + (0x2E53, 'X'), + (0x2E80, 'V'), + (0x2E9A, 'X'), + (0x2E9B, 'V'), + (0x2E9F, 'M', u'母'), + (0x2EA0, 'V'), + (0x2EF3, 'M', u'龟'), + (0x2EF4, 'X'), + (0x2F00, 'M', u'一'), + (0x2F01, 'M', u'丨'), + (0x2F02, 'M', u'丶'), + (0x2F03, 'M', u'丿'), + (0x2F04, 'M', u'乙'), + (0x2F05, 'M', u'亅'), + (0x2F06, 'M', u'二'), + (0x2F07, 'M', u'亠'), + (0x2F08, 'M', u'人'), + (0x2F09, 'M', u'儿'), + (0x2F0A, 'M', u'入'), + (0x2F0B, 'M', u'八'), + (0x2F0C, 'M', u'冂'), + (0x2F0D, 'M', u'冖'), + (0x2F0E, 'M', u'冫'), + (0x2F0F, 'M', u'几'), + (0x2F10, 'M', u'凵'), + (0x2F11, 'M', u'刀'), + ] + +def _seg_27(): + return [ + (0x2F12, 'M', u'力'), + (0x2F13, 'M', u'勹'), + (0x2F14, 'M', u'匕'), + (0x2F15, 'M', u'匚'), + (0x2F16, 'M', u'匸'), + (0x2F17, 'M', u'十'), + (0x2F18, 'M', u'卜'), + (0x2F19, 'M', u'卩'), + (0x2F1A, 'M', u'厂'), + (0x2F1B, 'M', u'厶'), + (0x2F1C, 'M', u'又'), + (0x2F1D, 'M', u'口'), + (0x2F1E, 'M', u'囗'), + (0x2F1F, 'M', u'土'), + (0x2F20, 'M', u'士'), + (0x2F21, 'M', u'夂'), + (0x2F22, 'M', u'夊'), + (0x2F23, 'M', u'夕'), + (0x2F24, 'M', u'大'), + (0x2F25, 'M', u'女'), + (0x2F26, 'M', u'子'), + (0x2F27, 'M', u'宀'), + (0x2F28, 'M', u'寸'), + (0x2F29, 'M', u'小'), + (0x2F2A, 'M', u'尢'), + (0x2F2B, 'M', u'尸'), + (0x2F2C, 'M', u'屮'), + (0x2F2D, 'M', u'山'), + (0x2F2E, 'M', u'巛'), + (0x2F2F, 'M', u'工'), + (0x2F30, 'M', u'己'), + (0x2F31, 'M', u'巾'), + (0x2F32, 'M', u'干'), + (0x2F33, 'M', u'幺'), + (0x2F34, 'M', u'广'), + (0x2F35, 'M', u'廴'), + (0x2F36, 'M', u'廾'), + (0x2F37, 'M', u'弋'), + (0x2F38, 'M', u'弓'), + (0x2F39, 'M', u'彐'), + (0x2F3A, 'M', u'彡'), + (0x2F3B, 'M', u'彳'), + (0x2F3C, 'M', u'心'), + (0x2F3D, 'M', u'戈'), + (0x2F3E, 'M', u'戶'), + (0x2F3F, 'M', u'手'), + (0x2F40, 'M', u'支'), + (0x2F41, 'M', u'攴'), + (0x2F42, 'M', u'文'), + (0x2F43, 'M', u'斗'), + (0x2F44, 'M', u'斤'), + (0x2F45, 'M', u'方'), + (0x2F46, 'M', u'无'), + (0x2F47, 'M', u'日'), + (0x2F48, 'M', u'曰'), + (0x2F49, 'M', u'月'), + (0x2F4A, 'M', u'木'), + (0x2F4B, 'M', u'欠'), + (0x2F4C, 'M', u'止'), + (0x2F4D, 'M', u'歹'), + (0x2F4E, 'M', u'殳'), + (0x2F4F, 'M', u'毋'), + (0x2F50, 'M', u'比'), + (0x2F51, 'M', u'毛'), + (0x2F52, 'M', u'氏'), + (0x2F53, 'M', u'气'), + (0x2F54, 'M', u'水'), + (0x2F55, 'M', u'火'), + (0x2F56, 'M', u'爪'), + (0x2F57, 'M', u'父'), + (0x2F58, 'M', u'爻'), + (0x2F59, 'M', u'爿'), + (0x2F5A, 'M', u'片'), + (0x2F5B, 'M', u'牙'), + (0x2F5C, 'M', u'牛'), + (0x2F5D, 'M', u'犬'), + (0x2F5E, 'M', u'玄'), + (0x2F5F, 'M', u'玉'), + (0x2F60, 'M', u'瓜'), + (0x2F61, 'M', u'瓦'), + (0x2F62, 'M', u'甘'), + (0x2F63, 'M', u'生'), + (0x2F64, 'M', u'用'), + (0x2F65, 'M', u'田'), + (0x2F66, 'M', u'疋'), + (0x2F67, 'M', u'疒'), + (0x2F68, 'M', u'癶'), + (0x2F69, 'M', u'白'), + (0x2F6A, 'M', u'皮'), + (0x2F6B, 'M', u'皿'), + (0x2F6C, 'M', u'目'), + (0x2F6D, 'M', u'矛'), + (0x2F6E, 'M', u'矢'), + (0x2F6F, 'M', u'石'), + (0x2F70, 'M', u'示'), + (0x2F71, 'M', u'禸'), + (0x2F72, 'M', u'禾'), + (0x2F73, 'M', u'穴'), + (0x2F74, 'M', u'立'), + (0x2F75, 'M', u'竹'), + ] + +def _seg_28(): + return [ + (0x2F76, 'M', u'米'), + (0x2F77, 'M', u'糸'), + (0x2F78, 'M', u'缶'), + (0x2F79, 'M', u'网'), + (0x2F7A, 'M', u'羊'), + (0x2F7B, 'M', u'羽'), + (0x2F7C, 'M', u'老'), + (0x2F7D, 'M', u'而'), + (0x2F7E, 'M', u'耒'), + (0x2F7F, 'M', u'耳'), + (0x2F80, 'M', u'聿'), + (0x2F81, 'M', u'肉'), + (0x2F82, 'M', u'臣'), + (0x2F83, 'M', u'自'), + (0x2F84, 'M', u'至'), + (0x2F85, 'M', u'臼'), + (0x2F86, 'M', u'舌'), + (0x2F87, 'M', u'舛'), + (0x2F88, 'M', u'舟'), + (0x2F89, 'M', u'艮'), + (0x2F8A, 'M', u'色'), + (0x2F8B, 'M', u'艸'), + (0x2F8C, 'M', u'虍'), + (0x2F8D, 'M', u'虫'), + (0x2F8E, 'M', u'血'), + (0x2F8F, 'M', u'行'), + (0x2F90, 'M', u'衣'), + (0x2F91, 'M', u'襾'), + (0x2F92, 'M', u'見'), + (0x2F93, 'M', u'角'), + (0x2F94, 'M', u'言'), + (0x2F95, 'M', u'谷'), + (0x2F96, 'M', u'豆'), + (0x2F97, 'M', u'豕'), + (0x2F98, 'M', u'豸'), + (0x2F99, 'M', u'貝'), + (0x2F9A, 'M', u'赤'), + (0x2F9B, 'M', u'走'), + (0x2F9C, 'M', u'足'), + (0x2F9D, 'M', u'身'), + (0x2F9E, 'M', u'車'), + (0x2F9F, 'M', u'辛'), + (0x2FA0, 'M', u'辰'), + (0x2FA1, 'M', u'辵'), + (0x2FA2, 'M', u'邑'), + (0x2FA3, 'M', u'酉'), + (0x2FA4, 'M', u'釆'), + (0x2FA5, 'M', u'里'), + (0x2FA6, 'M', u'金'), + (0x2FA7, 'M', u'長'), + (0x2FA8, 'M', u'門'), + (0x2FA9, 'M', u'阜'), + (0x2FAA, 'M', u'隶'), + (0x2FAB, 'M', u'隹'), + (0x2FAC, 'M', u'雨'), + (0x2FAD, 'M', u'靑'), + (0x2FAE, 'M', u'非'), + (0x2FAF, 'M', u'面'), + (0x2FB0, 'M', u'革'), + (0x2FB1, 'M', u'韋'), + (0x2FB2, 'M', u'韭'), + (0x2FB3, 'M', u'音'), + (0x2FB4, 'M', u'頁'), + (0x2FB5, 'M', u'風'), + (0x2FB6, 'M', u'飛'), + (0x2FB7, 'M', u'食'), + (0x2FB8, 'M', u'首'), + (0x2FB9, 'M', u'香'), + (0x2FBA, 'M', u'馬'), + (0x2FBB, 'M', u'骨'), + (0x2FBC, 'M', u'高'), + (0x2FBD, 'M', u'髟'), + (0x2FBE, 'M', u'鬥'), + (0x2FBF, 'M', u'鬯'), + (0x2FC0, 'M', u'鬲'), + (0x2FC1, 'M', u'鬼'), + (0x2FC2, 'M', u'魚'), + (0x2FC3, 'M', u'鳥'), + (0x2FC4, 'M', u'鹵'), + (0x2FC5, 'M', u'鹿'), + (0x2FC6, 'M', u'麥'), + (0x2FC7, 'M', u'麻'), + (0x2FC8, 'M', u'黃'), + (0x2FC9, 'M', u'黍'), + (0x2FCA, 'M', u'黑'), + (0x2FCB, 'M', u'黹'), + (0x2FCC, 'M', u'黽'), + (0x2FCD, 'M', u'鼎'), + (0x2FCE, 'M', u'鼓'), + (0x2FCF, 'M', u'鼠'), + (0x2FD0, 'M', u'鼻'), + (0x2FD1, 'M', u'齊'), + (0x2FD2, 'M', u'齒'), + (0x2FD3, 'M', u'龍'), + (0x2FD4, 'M', u'龜'), + (0x2FD5, 'M', u'龠'), + (0x2FD6, 'X'), + (0x3000, '3', u' '), + (0x3001, 'V'), + (0x3002, 'M', u'.'), + ] + +def _seg_29(): + return [ + (0x3003, 'V'), + (0x3036, 'M', u'〒'), + (0x3037, 'V'), + (0x3038, 'M', u'十'), + (0x3039, 'M', u'卄'), + (0x303A, 'M', u'卅'), + (0x303B, 'V'), + (0x3040, 'X'), + (0x3041, 'V'), + (0x3097, 'X'), + (0x3099, 'V'), + (0x309B, '3', u' ゙'), + (0x309C, '3', u' ゚'), + (0x309D, 'V'), + (0x309F, 'M', u'より'), + (0x30A0, 'V'), + (0x30FF, 'M', u'コト'), + (0x3100, 'X'), + (0x3105, 'V'), + (0x3130, 'X'), + (0x3131, 'M', u'ᄀ'), + (0x3132, 'M', u'ᄁ'), + (0x3133, 'M', u'ᆪ'), + (0x3134, 'M', u'ᄂ'), + (0x3135, 'M', u'ᆬ'), + (0x3136, 'M', u'ᆭ'), + (0x3137, 'M', u'ᄃ'), + (0x3138, 'M', u'ᄄ'), + (0x3139, 'M', u'ᄅ'), + (0x313A, 'M', u'ᆰ'), + (0x313B, 'M', u'ᆱ'), + (0x313C, 'M', u'ᆲ'), + (0x313D, 'M', u'ᆳ'), + (0x313E, 'M', u'ᆴ'), + (0x313F, 'M', u'ᆵ'), + (0x3140, 'M', u'ᄚ'), + (0x3141, 'M', u'ᄆ'), + (0x3142, 'M', u'ᄇ'), + (0x3143, 'M', u'ᄈ'), + (0x3144, 'M', u'ᄡ'), + (0x3145, 'M', u'ᄉ'), + (0x3146, 'M', u'ᄊ'), + (0x3147, 'M', u'ᄋ'), + (0x3148, 'M', u'ᄌ'), + (0x3149, 'M', u'ᄍ'), + (0x314A, 'M', u'ᄎ'), + (0x314B, 'M', u'ᄏ'), + (0x314C, 'M', u'ᄐ'), + (0x314D, 'M', u'ᄑ'), + (0x314E, 'M', u'ᄒ'), + (0x314F, 'M', u'ᅡ'), + (0x3150, 'M', u'ᅢ'), + (0x3151, 'M', u'ᅣ'), + (0x3152, 'M', u'ᅤ'), + (0x3153, 'M', u'ᅥ'), + (0x3154, 'M', u'ᅦ'), + (0x3155, 'M', u'ᅧ'), + (0x3156, 'M', u'ᅨ'), + (0x3157, 'M', u'ᅩ'), + (0x3158, 'M', u'ᅪ'), + (0x3159, 'M', u'ᅫ'), + (0x315A, 'M', u'ᅬ'), + (0x315B, 'M', u'ᅭ'), + (0x315C, 'M', u'ᅮ'), + (0x315D, 'M', u'ᅯ'), + (0x315E, 'M', u'ᅰ'), + (0x315F, 'M', u'ᅱ'), + (0x3160, 'M', u'ᅲ'), + (0x3161, 'M', u'ᅳ'), + (0x3162, 'M', u'ᅴ'), + (0x3163, 'M', u'ᅵ'), + (0x3164, 'X'), + (0x3165, 'M', u'ᄔ'), + (0x3166, 'M', u'ᄕ'), + (0x3167, 'M', u'ᇇ'), + (0x3168, 'M', u'ᇈ'), + (0x3169, 'M', u'ᇌ'), + (0x316A, 'M', u'ᇎ'), + (0x316B, 'M', u'ᇓ'), + (0x316C, 'M', u'ᇗ'), + (0x316D, 'M', u'ᇙ'), + (0x316E, 'M', u'ᄜ'), + (0x316F, 'M', u'ᇝ'), + (0x3170, 'M', u'ᇟ'), + (0x3171, 'M', u'ᄝ'), + (0x3172, 'M', u'ᄞ'), + (0x3173, 'M', u'ᄠ'), + (0x3174, 'M', u'ᄢ'), + (0x3175, 'M', u'ᄣ'), + (0x3176, 'M', u'ᄧ'), + (0x3177, 'M', u'ᄩ'), + (0x3178, 'M', u'ᄫ'), + (0x3179, 'M', u'ᄬ'), + (0x317A, 'M', u'ᄭ'), + (0x317B, 'M', u'ᄮ'), + (0x317C, 'M', u'ᄯ'), + (0x317D, 'M', u'ᄲ'), + (0x317E, 'M', u'ᄶ'), + (0x317F, 'M', u'ᅀ'), + (0x3180, 'M', u'ᅇ'), + ] + +def _seg_30(): + return [ + (0x3181, 'M', u'ᅌ'), + (0x3182, 'M', u'ᇱ'), + (0x3183, 'M', u'ᇲ'), + (0x3184, 'M', u'ᅗ'), + (0x3185, 'M', u'ᅘ'), + (0x3186, 'M', u'ᅙ'), + (0x3187, 'M', u'ᆄ'), + (0x3188, 'M', u'ᆅ'), + (0x3189, 'M', u'ᆈ'), + (0x318A, 'M', u'ᆑ'), + (0x318B, 'M', u'ᆒ'), + (0x318C, 'M', u'ᆔ'), + (0x318D, 'M', u'ᆞ'), + (0x318E, 'M', u'ᆡ'), + (0x318F, 'X'), + (0x3190, 'V'), + (0x3192, 'M', u'一'), + (0x3193, 'M', u'二'), + (0x3194, 'M', u'三'), + (0x3195, 'M', u'四'), + (0x3196, 'M', u'上'), + (0x3197, 'M', u'中'), + (0x3198, 'M', u'下'), + (0x3199, 'M', u'甲'), + (0x319A, 'M', u'乙'), + (0x319B, 'M', u'丙'), + (0x319C, 'M', u'丁'), + (0x319D, 'M', u'天'), + (0x319E, 'M', u'地'), + (0x319F, 'M', u'人'), + (0x31A0, 'V'), + (0x31E4, 'X'), + (0x31F0, 'V'), + (0x3200, '3', u'(ᄀ)'), + (0x3201, '3', u'(ᄂ)'), + (0x3202, '3', u'(ᄃ)'), + (0x3203, '3', u'(ᄅ)'), + (0x3204, '3', u'(ᄆ)'), + (0x3205, '3', u'(ᄇ)'), + (0x3206, '3', u'(ᄉ)'), + (0x3207, '3', u'(ᄋ)'), + (0x3208, '3', u'(ᄌ)'), + (0x3209, '3', u'(ᄎ)'), + (0x320A, '3', u'(ᄏ)'), + (0x320B, '3', u'(ᄐ)'), + (0x320C, '3', u'(ᄑ)'), + (0x320D, '3', u'(ᄒ)'), + (0x320E, '3', u'(가)'), + (0x320F, '3', u'(나)'), + (0x3210, '3', u'(다)'), + (0x3211, '3', u'(라)'), + (0x3212, '3', u'(마)'), + (0x3213, '3', u'(바)'), + (0x3214, '3', u'(사)'), + (0x3215, '3', u'(아)'), + (0x3216, '3', u'(자)'), + (0x3217, '3', u'(차)'), + (0x3218, '3', u'(카)'), + (0x3219, '3', u'(타)'), + (0x321A, '3', u'(파)'), + (0x321B, '3', u'(하)'), + (0x321C, '3', u'(주)'), + (0x321D, '3', u'(오전)'), + (0x321E, '3', u'(오후)'), + (0x321F, 'X'), + (0x3220, '3', u'(一)'), + (0x3221, '3', u'(二)'), + (0x3222, '3', u'(三)'), + (0x3223, '3', u'(四)'), + (0x3224, '3', u'(五)'), + (0x3225, '3', u'(六)'), + (0x3226, '3', u'(七)'), + (0x3227, '3', u'(八)'), + (0x3228, '3', u'(九)'), + (0x3229, '3', u'(十)'), + (0x322A, '3', u'(月)'), + (0x322B, '3', u'(火)'), + (0x322C, '3', u'(水)'), + (0x322D, '3', u'(木)'), + (0x322E, '3', u'(金)'), + (0x322F, '3', u'(土)'), + (0x3230, '3', u'(日)'), + (0x3231, '3', u'(株)'), + (0x3232, '3', u'(有)'), + (0x3233, '3', u'(社)'), + (0x3234, '3', u'(名)'), + (0x3235, '3', u'(特)'), + (0x3236, '3', u'(財)'), + (0x3237, '3', u'(祝)'), + (0x3238, '3', u'(労)'), + (0x3239, '3', u'(代)'), + (0x323A, '3', u'(呼)'), + (0x323B, '3', u'(学)'), + (0x323C, '3', u'(監)'), + (0x323D, '3', u'(企)'), + (0x323E, '3', u'(資)'), + (0x323F, '3', u'(協)'), + (0x3240, '3', u'(祭)'), + (0x3241, '3', u'(休)'), + (0x3242, '3', u'(自)'), + ] + +def _seg_31(): + return [ + (0x3243, '3', u'(至)'), + (0x3244, 'M', u'問'), + (0x3245, 'M', u'幼'), + (0x3246, 'M', u'文'), + (0x3247, 'M', u'箏'), + (0x3248, 'V'), + (0x3250, 'M', u'pte'), + (0x3251, 'M', u'21'), + (0x3252, 'M', u'22'), + (0x3253, 'M', u'23'), + (0x3254, 'M', u'24'), + (0x3255, 'M', u'25'), + (0x3256, 'M', u'26'), + (0x3257, 'M', u'27'), + (0x3258, 'M', u'28'), + (0x3259, 'M', u'29'), + (0x325A, 'M', u'30'), + (0x325B, 'M', u'31'), + (0x325C, 'M', u'32'), + (0x325D, 'M', u'33'), + (0x325E, 'M', u'34'), + (0x325F, 'M', u'35'), + (0x3260, 'M', u'ᄀ'), + (0x3261, 'M', u'ᄂ'), + (0x3262, 'M', u'ᄃ'), + (0x3263, 'M', u'ᄅ'), + (0x3264, 'M', u'ᄆ'), + (0x3265, 'M', u'ᄇ'), + (0x3266, 'M', u'ᄉ'), + (0x3267, 'M', u'ᄋ'), + (0x3268, 'M', u'ᄌ'), + (0x3269, 'M', u'ᄎ'), + (0x326A, 'M', u'ᄏ'), + (0x326B, 'M', u'ᄐ'), + (0x326C, 'M', u'ᄑ'), + (0x326D, 'M', u'ᄒ'), + (0x326E, 'M', u'가'), + (0x326F, 'M', u'나'), + (0x3270, 'M', u'다'), + (0x3271, 'M', u'라'), + (0x3272, 'M', u'마'), + (0x3273, 'M', u'바'), + (0x3274, 'M', u'사'), + (0x3275, 'M', u'아'), + (0x3276, 'M', u'자'), + (0x3277, 'M', u'차'), + (0x3278, 'M', u'카'), + (0x3279, 'M', u'타'), + (0x327A, 'M', u'파'), + (0x327B, 'M', u'하'), + (0x327C, 'M', u'참고'), + (0x327D, 'M', u'주의'), + (0x327E, 'M', u'우'), + (0x327F, 'V'), + (0x3280, 'M', u'一'), + (0x3281, 'M', u'二'), + (0x3282, 'M', u'三'), + (0x3283, 'M', u'四'), + (0x3284, 'M', u'五'), + (0x3285, 'M', u'六'), + (0x3286, 'M', u'七'), + (0x3287, 'M', u'八'), + (0x3288, 'M', u'九'), + (0x3289, 'M', u'十'), + (0x328A, 'M', u'月'), + (0x328B, 'M', u'火'), + (0x328C, 'M', u'水'), + (0x328D, 'M', u'木'), + (0x328E, 'M', u'金'), + (0x328F, 'M', u'土'), + (0x3290, 'M', u'日'), + (0x3291, 'M', u'株'), + (0x3292, 'M', u'有'), + (0x3293, 'M', u'社'), + (0x3294, 'M', u'名'), + (0x3295, 'M', u'特'), + (0x3296, 'M', u'財'), + (0x3297, 'M', u'祝'), + (0x3298, 'M', u'労'), + (0x3299, 'M', u'秘'), + (0x329A, 'M', u'男'), + (0x329B, 'M', u'女'), + (0x329C, 'M', u'適'), + (0x329D, 'M', u'優'), + (0x329E, 'M', u'印'), + (0x329F, 'M', u'注'), + (0x32A0, 'M', u'項'), + (0x32A1, 'M', u'休'), + (0x32A2, 'M', u'写'), + (0x32A3, 'M', u'正'), + (0x32A4, 'M', u'上'), + (0x32A5, 'M', u'中'), + (0x32A6, 'M', u'下'), + (0x32A7, 'M', u'左'), + (0x32A8, 'M', u'右'), + (0x32A9, 'M', u'医'), + (0x32AA, 'M', u'宗'), + (0x32AB, 'M', u'学'), + (0x32AC, 'M', u'監'), + (0x32AD, 'M', u'企'), + ] + +def _seg_32(): + return [ + (0x32AE, 'M', u'資'), + (0x32AF, 'M', u'協'), + (0x32B0, 'M', u'夜'), + (0x32B1, 'M', u'36'), + (0x32B2, 'M', u'37'), + (0x32B3, 'M', u'38'), + (0x32B4, 'M', u'39'), + (0x32B5, 'M', u'40'), + (0x32B6, 'M', u'41'), + (0x32B7, 'M', u'42'), + (0x32B8, 'M', u'43'), + (0x32B9, 'M', u'44'), + (0x32BA, 'M', u'45'), + (0x32BB, 'M', u'46'), + (0x32BC, 'M', u'47'), + (0x32BD, 'M', u'48'), + (0x32BE, 'M', u'49'), + (0x32BF, 'M', u'50'), + (0x32C0, 'M', u'1月'), + (0x32C1, 'M', u'2月'), + (0x32C2, 'M', u'3月'), + (0x32C3, 'M', u'4月'), + (0x32C4, 'M', u'5月'), + (0x32C5, 'M', u'6月'), + (0x32C6, 'M', u'7月'), + (0x32C7, 'M', u'8月'), + (0x32C8, 'M', u'9月'), + (0x32C9, 'M', u'10月'), + (0x32CA, 'M', u'11月'), + (0x32CB, 'M', u'12月'), + (0x32CC, 'M', u'hg'), + (0x32CD, 'M', u'erg'), + (0x32CE, 'M', u'ev'), + (0x32CF, 'M', u'ltd'), + (0x32D0, 'M', u'ア'), + (0x32D1, 'M', u'イ'), + (0x32D2, 'M', u'ウ'), + (0x32D3, 'M', u'エ'), + (0x32D4, 'M', u'オ'), + (0x32D5, 'M', u'カ'), + (0x32D6, 'M', u'キ'), + (0x32D7, 'M', u'ク'), + (0x32D8, 'M', u'ケ'), + (0x32D9, 'M', u'コ'), + (0x32DA, 'M', u'サ'), + (0x32DB, 'M', u'シ'), + (0x32DC, 'M', u'ス'), + (0x32DD, 'M', u'セ'), + (0x32DE, 'M', u'ソ'), + (0x32DF, 'M', u'タ'), + (0x32E0, 'M', u'チ'), + (0x32E1, 'M', u'ツ'), + (0x32E2, 'M', u'テ'), + (0x32E3, 'M', u'ト'), + (0x32E4, 'M', u'ナ'), + (0x32E5, 'M', u'ニ'), + (0x32E6, 'M', u'ヌ'), + (0x32E7, 'M', u'ネ'), + (0x32E8, 'M', u'ノ'), + (0x32E9, 'M', u'ハ'), + (0x32EA, 'M', u'ヒ'), + (0x32EB, 'M', u'フ'), + (0x32EC, 'M', u'ヘ'), + (0x32ED, 'M', u'ホ'), + (0x32EE, 'M', u'マ'), + (0x32EF, 'M', u'ミ'), + (0x32F0, 'M', u'ム'), + (0x32F1, 'M', u'メ'), + (0x32F2, 'M', u'モ'), + (0x32F3, 'M', u'ヤ'), + (0x32F4, 'M', u'ユ'), + (0x32F5, 'M', u'ヨ'), + (0x32F6, 'M', u'ラ'), + (0x32F7, 'M', u'リ'), + (0x32F8, 'M', u'ル'), + (0x32F9, 'M', u'レ'), + (0x32FA, 'M', u'ロ'), + (0x32FB, 'M', u'ワ'), + (0x32FC, 'M', u'ヰ'), + (0x32FD, 'M', u'ヱ'), + (0x32FE, 'M', u'ヲ'), + (0x32FF, 'M', u'令和'), + (0x3300, 'M', u'アパート'), + (0x3301, 'M', u'アルファ'), + (0x3302, 'M', u'アンペア'), + (0x3303, 'M', u'アール'), + (0x3304, 'M', u'イニング'), + (0x3305, 'M', u'インチ'), + (0x3306, 'M', u'ウォン'), + (0x3307, 'M', u'エスクード'), + (0x3308, 'M', u'エーカー'), + (0x3309, 'M', u'オンス'), + (0x330A, 'M', u'オーム'), + (0x330B, 'M', u'カイリ'), + (0x330C, 'M', u'カラット'), + (0x330D, 'M', u'カロリー'), + (0x330E, 'M', u'ガロン'), + (0x330F, 'M', u'ガンマ'), + (0x3310, 'M', u'ギガ'), + (0x3311, 'M', u'ギニー'), + ] + +def _seg_33(): + return [ + (0x3312, 'M', u'キュリー'), + (0x3313, 'M', u'ギルダー'), + (0x3314, 'M', u'キロ'), + (0x3315, 'M', u'キログラム'), + (0x3316, 'M', u'キロメートル'), + (0x3317, 'M', u'キロワット'), + (0x3318, 'M', u'グラム'), + (0x3319, 'M', u'グラムトン'), + (0x331A, 'M', u'クルゼイロ'), + (0x331B, 'M', u'クローネ'), + (0x331C, 'M', u'ケース'), + (0x331D, 'M', u'コルナ'), + (0x331E, 'M', u'コーポ'), + (0x331F, 'M', u'サイクル'), + (0x3320, 'M', u'サンチーム'), + (0x3321, 'M', u'シリング'), + (0x3322, 'M', u'センチ'), + (0x3323, 'M', u'セント'), + (0x3324, 'M', u'ダース'), + (0x3325, 'M', u'デシ'), + (0x3326, 'M', u'ドル'), + (0x3327, 'M', u'トン'), + (0x3328, 'M', u'ナノ'), + (0x3329, 'M', u'ノット'), + (0x332A, 'M', u'ハイツ'), + (0x332B, 'M', u'パーセント'), + (0x332C, 'M', u'パーツ'), + (0x332D, 'M', u'バーレル'), + (0x332E, 'M', u'ピアストル'), + (0x332F, 'M', u'ピクル'), + (0x3330, 'M', u'ピコ'), + (0x3331, 'M', u'ビル'), + (0x3332, 'M', u'ファラッド'), + (0x3333, 'M', u'フィート'), + (0x3334, 'M', u'ブッシェル'), + (0x3335, 'M', u'フラン'), + (0x3336, 'M', u'ヘクタール'), + (0x3337, 'M', u'ペソ'), + (0x3338, 'M', u'ペニヒ'), + (0x3339, 'M', u'ヘルツ'), + (0x333A, 'M', u'ペンス'), + (0x333B, 'M', u'ページ'), + (0x333C, 'M', u'ベータ'), + (0x333D, 'M', u'ポイント'), + (0x333E, 'M', u'ボルト'), + (0x333F, 'M', u'ホン'), + (0x3340, 'M', u'ポンド'), + (0x3341, 'M', u'ホール'), + (0x3342, 'M', u'ホーン'), + (0x3343, 'M', u'マイクロ'), + (0x3344, 'M', u'マイル'), + (0x3345, 'M', u'マッハ'), + (0x3346, 'M', u'マルク'), + (0x3347, 'M', u'マンション'), + (0x3348, 'M', u'ミクロン'), + (0x3349, 'M', u'ミリ'), + (0x334A, 'M', u'ミリバール'), + (0x334B, 'M', u'メガ'), + (0x334C, 'M', u'メガトン'), + (0x334D, 'M', u'メートル'), + (0x334E, 'M', u'ヤード'), + (0x334F, 'M', u'ヤール'), + (0x3350, 'M', u'ユアン'), + (0x3351, 'M', u'リットル'), + (0x3352, 'M', u'リラ'), + (0x3353, 'M', u'ルピー'), + (0x3354, 'M', u'ルーブル'), + (0x3355, 'M', u'レム'), + (0x3356, 'M', u'レントゲン'), + (0x3357, 'M', u'ワット'), + (0x3358, 'M', u'0点'), + (0x3359, 'M', u'1点'), + (0x335A, 'M', u'2点'), + (0x335B, 'M', u'3点'), + (0x335C, 'M', u'4点'), + (0x335D, 'M', u'5点'), + (0x335E, 'M', u'6点'), + (0x335F, 'M', u'7点'), + (0x3360, 'M', u'8点'), + (0x3361, 'M', u'9点'), + (0x3362, 'M', u'10点'), + (0x3363, 'M', u'11点'), + (0x3364, 'M', u'12点'), + (0x3365, 'M', u'13点'), + (0x3366, 'M', u'14点'), + (0x3367, 'M', u'15点'), + (0x3368, 'M', u'16点'), + (0x3369, 'M', u'17点'), + (0x336A, 'M', u'18点'), + (0x336B, 'M', u'19点'), + (0x336C, 'M', u'20点'), + (0x336D, 'M', u'21点'), + (0x336E, 'M', u'22点'), + (0x336F, 'M', u'23点'), + (0x3370, 'M', u'24点'), + (0x3371, 'M', u'hpa'), + (0x3372, 'M', u'da'), + (0x3373, 'M', u'au'), + (0x3374, 'M', u'bar'), + (0x3375, 'M', u'ov'), + ] + +def _seg_34(): + return [ + (0x3376, 'M', u'pc'), + (0x3377, 'M', u'dm'), + (0x3378, 'M', u'dm2'), + (0x3379, 'M', u'dm3'), + (0x337A, 'M', u'iu'), + (0x337B, 'M', u'平成'), + (0x337C, 'M', u'昭和'), + (0x337D, 'M', u'大正'), + (0x337E, 'M', u'明治'), + (0x337F, 'M', u'株式会社'), + (0x3380, 'M', u'pa'), + (0x3381, 'M', u'na'), + (0x3382, 'M', u'μa'), + (0x3383, 'M', u'ma'), + (0x3384, 'M', u'ka'), + (0x3385, 'M', u'kb'), + (0x3386, 'M', u'mb'), + (0x3387, 'M', u'gb'), + (0x3388, 'M', u'cal'), + (0x3389, 'M', u'kcal'), + (0x338A, 'M', u'pf'), + (0x338B, 'M', u'nf'), + (0x338C, 'M', u'μf'), + (0x338D, 'M', u'μg'), + (0x338E, 'M', u'mg'), + (0x338F, 'M', u'kg'), + (0x3390, 'M', u'hz'), + (0x3391, 'M', u'khz'), + (0x3392, 'M', u'mhz'), + (0x3393, 'M', u'ghz'), + (0x3394, 'M', u'thz'), + (0x3395, 'M', u'μl'), + (0x3396, 'M', u'ml'), + (0x3397, 'M', u'dl'), + (0x3398, 'M', u'kl'), + (0x3399, 'M', u'fm'), + (0x339A, 'M', u'nm'), + (0x339B, 'M', u'μm'), + (0x339C, 'M', u'mm'), + (0x339D, 'M', u'cm'), + (0x339E, 'M', u'km'), + (0x339F, 'M', u'mm2'), + (0x33A0, 'M', u'cm2'), + (0x33A1, 'M', u'm2'), + (0x33A2, 'M', u'km2'), + (0x33A3, 'M', u'mm3'), + (0x33A4, 'M', u'cm3'), + (0x33A5, 'M', u'm3'), + (0x33A6, 'M', u'km3'), + (0x33A7, 'M', u'm∕s'), + (0x33A8, 'M', u'm∕s2'), + (0x33A9, 'M', u'pa'), + (0x33AA, 'M', u'kpa'), + (0x33AB, 'M', u'mpa'), + (0x33AC, 'M', u'gpa'), + (0x33AD, 'M', u'rad'), + (0x33AE, 'M', u'rad∕s'), + (0x33AF, 'M', u'rad∕s2'), + (0x33B0, 'M', u'ps'), + (0x33B1, 'M', u'ns'), + (0x33B2, 'M', u'μs'), + (0x33B3, 'M', u'ms'), + (0x33B4, 'M', u'pv'), + (0x33B5, 'M', u'nv'), + (0x33B6, 'M', u'μv'), + (0x33B7, 'M', u'mv'), + (0x33B8, 'M', u'kv'), + (0x33B9, 'M', u'mv'), + (0x33BA, 'M', u'pw'), + (0x33BB, 'M', u'nw'), + (0x33BC, 'M', u'μw'), + (0x33BD, 'M', u'mw'), + (0x33BE, 'M', u'kw'), + (0x33BF, 'M', u'mw'), + (0x33C0, 'M', u'kω'), + (0x33C1, 'M', u'mω'), + (0x33C2, 'X'), + (0x33C3, 'M', u'bq'), + (0x33C4, 'M', u'cc'), + (0x33C5, 'M', u'cd'), + (0x33C6, 'M', u'c∕kg'), + (0x33C7, 'X'), + (0x33C8, 'M', u'db'), + (0x33C9, 'M', u'gy'), + (0x33CA, 'M', u'ha'), + (0x33CB, 'M', u'hp'), + (0x33CC, 'M', u'in'), + (0x33CD, 'M', u'kk'), + (0x33CE, 'M', u'km'), + (0x33CF, 'M', u'kt'), + (0x33D0, 'M', u'lm'), + (0x33D1, 'M', u'ln'), + (0x33D2, 'M', u'log'), + (0x33D3, 'M', u'lx'), + (0x33D4, 'M', u'mb'), + (0x33D5, 'M', u'mil'), + (0x33D6, 'M', u'mol'), + (0x33D7, 'M', u'ph'), + (0x33D8, 'X'), + (0x33D9, 'M', u'ppm'), + ] + +def _seg_35(): + return [ + (0x33DA, 'M', u'pr'), + (0x33DB, 'M', u'sr'), + (0x33DC, 'M', u'sv'), + (0x33DD, 'M', u'wb'), + (0x33DE, 'M', u'v∕m'), + (0x33DF, 'M', u'a∕m'), + (0x33E0, 'M', u'1日'), + (0x33E1, 'M', u'2日'), + (0x33E2, 'M', u'3日'), + (0x33E3, 'M', u'4日'), + (0x33E4, 'M', u'5日'), + (0x33E5, 'M', u'6日'), + (0x33E6, 'M', u'7日'), + (0x33E7, 'M', u'8日'), + (0x33E8, 'M', u'9日'), + (0x33E9, 'M', u'10日'), + (0x33EA, 'M', u'11日'), + (0x33EB, 'M', u'12日'), + (0x33EC, 'M', u'13日'), + (0x33ED, 'M', u'14日'), + (0x33EE, 'M', u'15日'), + (0x33EF, 'M', u'16日'), + (0x33F0, 'M', u'17日'), + (0x33F1, 'M', u'18日'), + (0x33F2, 'M', u'19日'), + (0x33F3, 'M', u'20日'), + (0x33F4, 'M', u'21日'), + (0x33F5, 'M', u'22日'), + (0x33F6, 'M', u'23日'), + (0x33F7, 'M', u'24日'), + (0x33F8, 'M', u'25日'), + (0x33F9, 'M', u'26日'), + (0x33FA, 'M', u'27日'), + (0x33FB, 'M', u'28日'), + (0x33FC, 'M', u'29日'), + (0x33FD, 'M', u'30日'), + (0x33FE, 'M', u'31日'), + (0x33FF, 'M', u'gal'), + (0x3400, 'V'), + (0x9FFD, 'X'), + (0xA000, 'V'), + (0xA48D, 'X'), + (0xA490, 'V'), + (0xA4C7, 'X'), + (0xA4D0, 'V'), + (0xA62C, 'X'), + (0xA640, 'M', u'ꙁ'), + (0xA641, 'V'), + (0xA642, 'M', u'ꙃ'), + (0xA643, 'V'), + (0xA644, 'M', u'ꙅ'), + (0xA645, 'V'), + (0xA646, 'M', u'ꙇ'), + (0xA647, 'V'), + (0xA648, 'M', u'ꙉ'), + (0xA649, 'V'), + (0xA64A, 'M', u'ꙋ'), + (0xA64B, 'V'), + (0xA64C, 'M', u'ꙍ'), + (0xA64D, 'V'), + (0xA64E, 'M', u'ꙏ'), + (0xA64F, 'V'), + (0xA650, 'M', u'ꙑ'), + (0xA651, 'V'), + (0xA652, 'M', u'ꙓ'), + (0xA653, 'V'), + (0xA654, 'M', u'ꙕ'), + (0xA655, 'V'), + (0xA656, 'M', u'ꙗ'), + (0xA657, 'V'), + (0xA658, 'M', u'ꙙ'), + (0xA659, 'V'), + (0xA65A, 'M', u'ꙛ'), + (0xA65B, 'V'), + (0xA65C, 'M', u'ꙝ'), + (0xA65D, 'V'), + (0xA65E, 'M', u'ꙟ'), + (0xA65F, 'V'), + (0xA660, 'M', u'ꙡ'), + (0xA661, 'V'), + (0xA662, 'M', u'ꙣ'), + (0xA663, 'V'), + (0xA664, 'M', u'ꙥ'), + (0xA665, 'V'), + (0xA666, 'M', u'ꙧ'), + (0xA667, 'V'), + (0xA668, 'M', u'ꙩ'), + (0xA669, 'V'), + (0xA66A, 'M', u'ꙫ'), + (0xA66B, 'V'), + (0xA66C, 'M', u'ꙭ'), + (0xA66D, 'V'), + (0xA680, 'M', u'ꚁ'), + (0xA681, 'V'), + (0xA682, 'M', u'ꚃ'), + (0xA683, 'V'), + (0xA684, 'M', u'ꚅ'), + (0xA685, 'V'), + (0xA686, 'M', u'ꚇ'), + (0xA687, 'V'), + ] + +def _seg_36(): + return [ + (0xA688, 'M', u'ꚉ'), + (0xA689, 'V'), + (0xA68A, 'M', u'ꚋ'), + (0xA68B, 'V'), + (0xA68C, 'M', u'ꚍ'), + (0xA68D, 'V'), + (0xA68E, 'M', u'ꚏ'), + (0xA68F, 'V'), + (0xA690, 'M', u'ꚑ'), + (0xA691, 'V'), + (0xA692, 'M', u'ꚓ'), + (0xA693, 'V'), + (0xA694, 'M', u'ꚕ'), + (0xA695, 'V'), + (0xA696, 'M', u'ꚗ'), + (0xA697, 'V'), + (0xA698, 'M', u'ꚙ'), + (0xA699, 'V'), + (0xA69A, 'M', u'ꚛ'), + (0xA69B, 'V'), + (0xA69C, 'M', u'ъ'), + (0xA69D, 'M', u'ь'), + (0xA69E, 'V'), + (0xA6F8, 'X'), + (0xA700, 'V'), + (0xA722, 'M', u'ꜣ'), + (0xA723, 'V'), + (0xA724, 'M', u'ꜥ'), + (0xA725, 'V'), + (0xA726, 'M', u'ꜧ'), + (0xA727, 'V'), + (0xA728, 'M', u'ꜩ'), + (0xA729, 'V'), + (0xA72A, 'M', u'ꜫ'), + (0xA72B, 'V'), + (0xA72C, 'M', u'ꜭ'), + (0xA72D, 'V'), + (0xA72E, 'M', u'ꜯ'), + (0xA72F, 'V'), + (0xA732, 'M', u'ꜳ'), + (0xA733, 'V'), + (0xA734, 'M', u'ꜵ'), + (0xA735, 'V'), + (0xA736, 'M', u'ꜷ'), + (0xA737, 'V'), + (0xA738, 'M', u'ꜹ'), + (0xA739, 'V'), + (0xA73A, 'M', u'ꜻ'), + (0xA73B, 'V'), + (0xA73C, 'M', u'ꜽ'), + (0xA73D, 'V'), + (0xA73E, 'M', u'ꜿ'), + (0xA73F, 'V'), + (0xA740, 'M', u'ꝁ'), + (0xA741, 'V'), + (0xA742, 'M', u'ꝃ'), + (0xA743, 'V'), + (0xA744, 'M', u'ꝅ'), + (0xA745, 'V'), + (0xA746, 'M', u'ꝇ'), + (0xA747, 'V'), + (0xA748, 'M', u'ꝉ'), + (0xA749, 'V'), + (0xA74A, 'M', u'ꝋ'), + (0xA74B, 'V'), + (0xA74C, 'M', u'ꝍ'), + (0xA74D, 'V'), + (0xA74E, 'M', u'ꝏ'), + (0xA74F, 'V'), + (0xA750, 'M', u'ꝑ'), + (0xA751, 'V'), + (0xA752, 'M', u'ꝓ'), + (0xA753, 'V'), + (0xA754, 'M', u'ꝕ'), + (0xA755, 'V'), + (0xA756, 'M', u'ꝗ'), + (0xA757, 'V'), + (0xA758, 'M', u'ꝙ'), + (0xA759, 'V'), + (0xA75A, 'M', u'ꝛ'), + (0xA75B, 'V'), + (0xA75C, 'M', u'ꝝ'), + (0xA75D, 'V'), + (0xA75E, 'M', u'ꝟ'), + (0xA75F, 'V'), + (0xA760, 'M', u'ꝡ'), + (0xA761, 'V'), + (0xA762, 'M', u'ꝣ'), + (0xA763, 'V'), + (0xA764, 'M', u'ꝥ'), + (0xA765, 'V'), + (0xA766, 'M', u'ꝧ'), + (0xA767, 'V'), + (0xA768, 'M', u'ꝩ'), + (0xA769, 'V'), + (0xA76A, 'M', u'ꝫ'), + (0xA76B, 'V'), + (0xA76C, 'M', u'ꝭ'), + (0xA76D, 'V'), + (0xA76E, 'M', u'ꝯ'), + ] + +def _seg_37(): + return [ + (0xA76F, 'V'), + (0xA770, 'M', u'ꝯ'), + (0xA771, 'V'), + (0xA779, 'M', u'ꝺ'), + (0xA77A, 'V'), + (0xA77B, 'M', u'ꝼ'), + (0xA77C, 'V'), + (0xA77D, 'M', u'ᵹ'), + (0xA77E, 'M', u'ꝿ'), + (0xA77F, 'V'), + (0xA780, 'M', u'ꞁ'), + (0xA781, 'V'), + (0xA782, 'M', u'ꞃ'), + (0xA783, 'V'), + (0xA784, 'M', u'ꞅ'), + (0xA785, 'V'), + (0xA786, 'M', u'ꞇ'), + (0xA787, 'V'), + (0xA78B, 'M', u'ꞌ'), + (0xA78C, 'V'), + (0xA78D, 'M', u'ɥ'), + (0xA78E, 'V'), + (0xA790, 'M', u'ꞑ'), + (0xA791, 'V'), + (0xA792, 'M', u'ꞓ'), + (0xA793, 'V'), + (0xA796, 'M', u'ꞗ'), + (0xA797, 'V'), + (0xA798, 'M', u'ꞙ'), + (0xA799, 'V'), + (0xA79A, 'M', u'ꞛ'), + (0xA79B, 'V'), + (0xA79C, 'M', u'ꞝ'), + (0xA79D, 'V'), + (0xA79E, 'M', u'ꞟ'), + (0xA79F, 'V'), + (0xA7A0, 'M', u'ꞡ'), + (0xA7A1, 'V'), + (0xA7A2, 'M', u'ꞣ'), + (0xA7A3, 'V'), + (0xA7A4, 'M', u'ꞥ'), + (0xA7A5, 'V'), + (0xA7A6, 'M', u'ꞧ'), + (0xA7A7, 'V'), + (0xA7A8, 'M', u'ꞩ'), + (0xA7A9, 'V'), + (0xA7AA, 'M', u'ɦ'), + (0xA7AB, 'M', u'ɜ'), + (0xA7AC, 'M', u'ɡ'), + (0xA7AD, 'M', u'ɬ'), + (0xA7AE, 'M', u'ɪ'), + (0xA7AF, 'V'), + (0xA7B0, 'M', u'ʞ'), + (0xA7B1, 'M', u'ʇ'), + (0xA7B2, 'M', u'ʝ'), + (0xA7B3, 'M', u'ꭓ'), + (0xA7B4, 'M', u'ꞵ'), + (0xA7B5, 'V'), + (0xA7B6, 'M', u'ꞷ'), + (0xA7B7, 'V'), + (0xA7B8, 'M', u'ꞹ'), + (0xA7B9, 'V'), + (0xA7BA, 'M', u'ꞻ'), + (0xA7BB, 'V'), + (0xA7BC, 'M', u'ꞽ'), + (0xA7BD, 'V'), + (0xA7BE, 'M', u'ꞿ'), + (0xA7BF, 'V'), + (0xA7C0, 'X'), + (0xA7C2, 'M', u'ꟃ'), + (0xA7C3, 'V'), + (0xA7C4, 'M', u'ꞔ'), + (0xA7C5, 'M', u'ʂ'), + (0xA7C6, 'M', u'ᶎ'), + (0xA7C7, 'M', u'ꟈ'), + (0xA7C8, 'V'), + (0xA7C9, 'M', u'ꟊ'), + (0xA7CA, 'V'), + (0xA7CB, 'X'), + (0xA7F5, 'M', u'ꟶ'), + (0xA7F6, 'V'), + (0xA7F8, 'M', u'ħ'), + (0xA7F9, 'M', u'œ'), + (0xA7FA, 'V'), + (0xA82D, 'X'), + (0xA830, 'V'), + (0xA83A, 'X'), + (0xA840, 'V'), + (0xA878, 'X'), + (0xA880, 'V'), + (0xA8C6, 'X'), + (0xA8CE, 'V'), + (0xA8DA, 'X'), + (0xA8E0, 'V'), + (0xA954, 'X'), + (0xA95F, 'V'), + (0xA97D, 'X'), + (0xA980, 'V'), + (0xA9CE, 'X'), + (0xA9CF, 'V'), + ] + +def _seg_38(): + return [ + (0xA9DA, 'X'), + (0xA9DE, 'V'), + (0xA9FF, 'X'), + (0xAA00, 'V'), + (0xAA37, 'X'), + (0xAA40, 'V'), + (0xAA4E, 'X'), + (0xAA50, 'V'), + (0xAA5A, 'X'), + (0xAA5C, 'V'), + (0xAAC3, 'X'), + (0xAADB, 'V'), + (0xAAF7, 'X'), + (0xAB01, 'V'), + (0xAB07, 'X'), + (0xAB09, 'V'), + (0xAB0F, 'X'), + (0xAB11, 'V'), + (0xAB17, 'X'), + (0xAB20, 'V'), + (0xAB27, 'X'), + (0xAB28, 'V'), + (0xAB2F, 'X'), + (0xAB30, 'V'), + (0xAB5C, 'M', u'ꜧ'), + (0xAB5D, 'M', u'ꬷ'), + (0xAB5E, 'M', u'ɫ'), + (0xAB5F, 'M', u'ꭒ'), + (0xAB60, 'V'), + (0xAB69, 'M', u'ʍ'), + (0xAB6A, 'V'), + (0xAB6C, 'X'), + (0xAB70, 'M', u'Ꭰ'), + (0xAB71, 'M', u'Ꭱ'), + (0xAB72, 'M', u'Ꭲ'), + (0xAB73, 'M', u'Ꭳ'), + (0xAB74, 'M', u'Ꭴ'), + (0xAB75, 'M', u'Ꭵ'), + (0xAB76, 'M', u'Ꭶ'), + (0xAB77, 'M', u'Ꭷ'), + (0xAB78, 'M', u'Ꭸ'), + (0xAB79, 'M', u'Ꭹ'), + (0xAB7A, 'M', u'Ꭺ'), + (0xAB7B, 'M', u'Ꭻ'), + (0xAB7C, 'M', u'Ꭼ'), + (0xAB7D, 'M', u'Ꭽ'), + (0xAB7E, 'M', u'Ꭾ'), + (0xAB7F, 'M', u'Ꭿ'), + (0xAB80, 'M', u'Ꮀ'), + (0xAB81, 'M', u'Ꮁ'), + (0xAB82, 'M', u'Ꮂ'), + (0xAB83, 'M', u'Ꮃ'), + (0xAB84, 'M', u'Ꮄ'), + (0xAB85, 'M', u'Ꮅ'), + (0xAB86, 'M', u'Ꮆ'), + (0xAB87, 'M', u'Ꮇ'), + (0xAB88, 'M', u'Ꮈ'), + (0xAB89, 'M', u'Ꮉ'), + (0xAB8A, 'M', u'Ꮊ'), + (0xAB8B, 'M', u'Ꮋ'), + (0xAB8C, 'M', u'Ꮌ'), + (0xAB8D, 'M', u'Ꮍ'), + (0xAB8E, 'M', u'Ꮎ'), + (0xAB8F, 'M', u'Ꮏ'), + (0xAB90, 'M', u'Ꮐ'), + (0xAB91, 'M', u'Ꮑ'), + (0xAB92, 'M', u'Ꮒ'), + (0xAB93, 'M', u'Ꮓ'), + (0xAB94, 'M', u'Ꮔ'), + (0xAB95, 'M', u'Ꮕ'), + (0xAB96, 'M', u'Ꮖ'), + (0xAB97, 'M', u'Ꮗ'), + (0xAB98, 'M', u'Ꮘ'), + (0xAB99, 'M', u'Ꮙ'), + (0xAB9A, 'M', u'Ꮚ'), + (0xAB9B, 'M', u'Ꮛ'), + (0xAB9C, 'M', u'Ꮜ'), + (0xAB9D, 'M', u'Ꮝ'), + (0xAB9E, 'M', u'Ꮞ'), + (0xAB9F, 'M', u'Ꮟ'), + (0xABA0, 'M', u'Ꮠ'), + (0xABA1, 'M', u'Ꮡ'), + (0xABA2, 'M', u'Ꮢ'), + (0xABA3, 'M', u'Ꮣ'), + (0xABA4, 'M', u'Ꮤ'), + (0xABA5, 'M', u'Ꮥ'), + (0xABA6, 'M', u'Ꮦ'), + (0xABA7, 'M', u'Ꮧ'), + (0xABA8, 'M', u'Ꮨ'), + (0xABA9, 'M', u'Ꮩ'), + (0xABAA, 'M', u'Ꮪ'), + (0xABAB, 'M', u'Ꮫ'), + (0xABAC, 'M', u'Ꮬ'), + (0xABAD, 'M', u'Ꮭ'), + (0xABAE, 'M', u'Ꮮ'), + (0xABAF, 'M', u'Ꮯ'), + (0xABB0, 'M', u'Ꮰ'), + (0xABB1, 'M', u'Ꮱ'), + (0xABB2, 'M', u'Ꮲ'), + (0xABB3, 'M', u'Ꮳ'), + ] + +def _seg_39(): + return [ + (0xABB4, 'M', u'Ꮴ'), + (0xABB5, 'M', u'Ꮵ'), + (0xABB6, 'M', u'Ꮶ'), + (0xABB7, 'M', u'Ꮷ'), + (0xABB8, 'M', u'Ꮸ'), + (0xABB9, 'M', u'Ꮹ'), + (0xABBA, 'M', u'Ꮺ'), + (0xABBB, 'M', u'Ꮻ'), + (0xABBC, 'M', u'Ꮼ'), + (0xABBD, 'M', u'Ꮽ'), + (0xABBE, 'M', u'Ꮾ'), + (0xABBF, 'M', u'Ꮿ'), + (0xABC0, 'V'), + (0xABEE, 'X'), + (0xABF0, 'V'), + (0xABFA, 'X'), + (0xAC00, 'V'), + (0xD7A4, 'X'), + (0xD7B0, 'V'), + (0xD7C7, 'X'), + (0xD7CB, 'V'), + (0xD7FC, 'X'), + (0xF900, 'M', u'豈'), + (0xF901, 'M', u'更'), + (0xF902, 'M', u'車'), + (0xF903, 'M', u'賈'), + (0xF904, 'M', u'滑'), + (0xF905, 'M', u'串'), + (0xF906, 'M', u'句'), + (0xF907, 'M', u'龜'), + (0xF909, 'M', u'契'), + (0xF90A, 'M', u'金'), + (0xF90B, 'M', u'喇'), + (0xF90C, 'M', u'奈'), + (0xF90D, 'M', u'懶'), + (0xF90E, 'M', u'癩'), + (0xF90F, 'M', u'羅'), + (0xF910, 'M', u'蘿'), + (0xF911, 'M', u'螺'), + (0xF912, 'M', u'裸'), + (0xF913, 'M', u'邏'), + (0xF914, 'M', u'樂'), + (0xF915, 'M', u'洛'), + (0xF916, 'M', u'烙'), + (0xF917, 'M', u'珞'), + (0xF918, 'M', u'落'), + (0xF919, 'M', u'酪'), + (0xF91A, 'M', u'駱'), + (0xF91B, 'M', u'亂'), + (0xF91C, 'M', u'卵'), + (0xF91D, 'M', u'欄'), + (0xF91E, 'M', u'爛'), + (0xF91F, 'M', u'蘭'), + (0xF920, 'M', u'鸞'), + (0xF921, 'M', u'嵐'), + (0xF922, 'M', u'濫'), + (0xF923, 'M', u'藍'), + (0xF924, 'M', u'襤'), + (0xF925, 'M', u'拉'), + (0xF926, 'M', u'臘'), + (0xF927, 'M', u'蠟'), + (0xF928, 'M', u'廊'), + (0xF929, 'M', u'朗'), + (0xF92A, 'M', u'浪'), + (0xF92B, 'M', u'狼'), + (0xF92C, 'M', u'郎'), + (0xF92D, 'M', u'來'), + (0xF92E, 'M', u'冷'), + (0xF92F, 'M', u'勞'), + (0xF930, 'M', u'擄'), + (0xF931, 'M', u'櫓'), + (0xF932, 'M', u'爐'), + (0xF933, 'M', u'盧'), + (0xF934, 'M', u'老'), + (0xF935, 'M', u'蘆'), + (0xF936, 'M', u'虜'), + (0xF937, 'M', u'路'), + (0xF938, 'M', u'露'), + (0xF939, 'M', u'魯'), + (0xF93A, 'M', u'鷺'), + (0xF93B, 'M', u'碌'), + (0xF93C, 'M', u'祿'), + (0xF93D, 'M', u'綠'), + (0xF93E, 'M', u'菉'), + (0xF93F, 'M', u'錄'), + (0xF940, 'M', u'鹿'), + (0xF941, 'M', u'論'), + (0xF942, 'M', u'壟'), + (0xF943, 'M', u'弄'), + (0xF944, 'M', u'籠'), + (0xF945, 'M', u'聾'), + (0xF946, 'M', u'牢'), + (0xF947, 'M', u'磊'), + (0xF948, 'M', u'賂'), + (0xF949, 'M', u'雷'), + (0xF94A, 'M', u'壘'), + (0xF94B, 'M', u'屢'), + (0xF94C, 'M', u'樓'), + (0xF94D, 'M', u'淚'), + (0xF94E, 'M', u'漏'), + ] + +def _seg_40(): + return [ + (0xF94F, 'M', u'累'), + (0xF950, 'M', u'縷'), + (0xF951, 'M', u'陋'), + (0xF952, 'M', u'勒'), + (0xF953, 'M', u'肋'), + (0xF954, 'M', u'凜'), + (0xF955, 'M', u'凌'), + (0xF956, 'M', u'稜'), + (0xF957, 'M', u'綾'), + (0xF958, 'M', u'菱'), + (0xF959, 'M', u'陵'), + (0xF95A, 'M', u'讀'), + (0xF95B, 'M', u'拏'), + (0xF95C, 'M', u'樂'), + (0xF95D, 'M', u'諾'), + (0xF95E, 'M', u'丹'), + (0xF95F, 'M', u'寧'), + (0xF960, 'M', u'怒'), + (0xF961, 'M', u'率'), + (0xF962, 'M', u'異'), + (0xF963, 'M', u'北'), + (0xF964, 'M', u'磻'), + (0xF965, 'M', u'便'), + (0xF966, 'M', u'復'), + (0xF967, 'M', u'不'), + (0xF968, 'M', u'泌'), + (0xF969, 'M', u'數'), + (0xF96A, 'M', u'索'), + (0xF96B, 'M', u'參'), + (0xF96C, 'M', u'塞'), + (0xF96D, 'M', u'省'), + (0xF96E, 'M', u'葉'), + (0xF96F, 'M', u'說'), + (0xF970, 'M', u'殺'), + (0xF971, 'M', u'辰'), + (0xF972, 'M', u'沈'), + (0xF973, 'M', u'拾'), + (0xF974, 'M', u'若'), + (0xF975, 'M', u'掠'), + (0xF976, 'M', u'略'), + (0xF977, 'M', u'亮'), + (0xF978, 'M', u'兩'), + (0xF979, 'M', u'凉'), + (0xF97A, 'M', u'梁'), + (0xF97B, 'M', u'糧'), + (0xF97C, 'M', u'良'), + (0xF97D, 'M', u'諒'), + (0xF97E, 'M', u'量'), + (0xF97F, 'M', u'勵'), + (0xF980, 'M', u'呂'), + (0xF981, 'M', u'女'), + (0xF982, 'M', u'廬'), + (0xF983, 'M', u'旅'), + (0xF984, 'M', u'濾'), + (0xF985, 'M', u'礪'), + (0xF986, 'M', u'閭'), + (0xF987, 'M', u'驪'), + (0xF988, 'M', u'麗'), + (0xF989, 'M', u'黎'), + (0xF98A, 'M', u'力'), + (0xF98B, 'M', u'曆'), + (0xF98C, 'M', u'歷'), + (0xF98D, 'M', u'轢'), + (0xF98E, 'M', u'年'), + (0xF98F, 'M', u'憐'), + (0xF990, 'M', u'戀'), + (0xF991, 'M', u'撚'), + (0xF992, 'M', u'漣'), + (0xF993, 'M', u'煉'), + (0xF994, 'M', u'璉'), + (0xF995, 'M', u'秊'), + (0xF996, 'M', u'練'), + (0xF997, 'M', u'聯'), + (0xF998, 'M', u'輦'), + (0xF999, 'M', u'蓮'), + (0xF99A, 'M', u'連'), + (0xF99B, 'M', u'鍊'), + (0xF99C, 'M', u'列'), + (0xF99D, 'M', u'劣'), + (0xF99E, 'M', u'咽'), + (0xF99F, 'M', u'烈'), + (0xF9A0, 'M', u'裂'), + (0xF9A1, 'M', u'說'), + (0xF9A2, 'M', u'廉'), + (0xF9A3, 'M', u'念'), + (0xF9A4, 'M', u'捻'), + (0xF9A5, 'M', u'殮'), + (0xF9A6, 'M', u'簾'), + (0xF9A7, 'M', u'獵'), + (0xF9A8, 'M', u'令'), + (0xF9A9, 'M', u'囹'), + (0xF9AA, 'M', u'寧'), + (0xF9AB, 'M', u'嶺'), + (0xF9AC, 'M', u'怜'), + (0xF9AD, 'M', u'玲'), + (0xF9AE, 'M', u'瑩'), + (0xF9AF, 'M', u'羚'), + (0xF9B0, 'M', u'聆'), + (0xF9B1, 'M', u'鈴'), + (0xF9B2, 'M', u'零'), + ] + +def _seg_41(): + return [ + (0xF9B3, 'M', u'靈'), + (0xF9B4, 'M', u'領'), + (0xF9B5, 'M', u'例'), + (0xF9B6, 'M', u'禮'), + (0xF9B7, 'M', u'醴'), + (0xF9B8, 'M', u'隸'), + (0xF9B9, 'M', u'惡'), + (0xF9BA, 'M', u'了'), + (0xF9BB, 'M', u'僚'), + (0xF9BC, 'M', u'寮'), + (0xF9BD, 'M', u'尿'), + (0xF9BE, 'M', u'料'), + (0xF9BF, 'M', u'樂'), + (0xF9C0, 'M', u'燎'), + (0xF9C1, 'M', u'療'), + (0xF9C2, 'M', u'蓼'), + (0xF9C3, 'M', u'遼'), + (0xF9C4, 'M', u'龍'), + (0xF9C5, 'M', u'暈'), + (0xF9C6, 'M', u'阮'), + (0xF9C7, 'M', u'劉'), + (0xF9C8, 'M', u'杻'), + (0xF9C9, 'M', u'柳'), + (0xF9CA, 'M', u'流'), + (0xF9CB, 'M', u'溜'), + (0xF9CC, 'M', u'琉'), + (0xF9CD, 'M', u'留'), + (0xF9CE, 'M', u'硫'), + (0xF9CF, 'M', u'紐'), + (0xF9D0, 'M', u'類'), + (0xF9D1, 'M', u'六'), + (0xF9D2, 'M', u'戮'), + (0xF9D3, 'M', u'陸'), + (0xF9D4, 'M', u'倫'), + (0xF9D5, 'M', u'崙'), + (0xF9D6, 'M', u'淪'), + (0xF9D7, 'M', u'輪'), + (0xF9D8, 'M', u'律'), + (0xF9D9, 'M', u'慄'), + (0xF9DA, 'M', u'栗'), + (0xF9DB, 'M', u'率'), + (0xF9DC, 'M', u'隆'), + (0xF9DD, 'M', u'利'), + (0xF9DE, 'M', u'吏'), + (0xF9DF, 'M', u'履'), + (0xF9E0, 'M', u'易'), + (0xF9E1, 'M', u'李'), + (0xF9E2, 'M', u'梨'), + (0xF9E3, 'M', u'泥'), + (0xF9E4, 'M', u'理'), + (0xF9E5, 'M', u'痢'), + (0xF9E6, 'M', u'罹'), + (0xF9E7, 'M', u'裏'), + (0xF9E8, 'M', u'裡'), + (0xF9E9, 'M', u'里'), + (0xF9EA, 'M', u'離'), + (0xF9EB, 'M', u'匿'), + (0xF9EC, 'M', u'溺'), + (0xF9ED, 'M', u'吝'), + (0xF9EE, 'M', u'燐'), + (0xF9EF, 'M', u'璘'), + (0xF9F0, 'M', u'藺'), + (0xF9F1, 'M', u'隣'), + (0xF9F2, 'M', u'鱗'), + (0xF9F3, 'M', u'麟'), + (0xF9F4, 'M', u'林'), + (0xF9F5, 'M', u'淋'), + (0xF9F6, 'M', u'臨'), + (0xF9F7, 'M', u'立'), + (0xF9F8, 'M', u'笠'), + (0xF9F9, 'M', u'粒'), + (0xF9FA, 'M', u'狀'), + (0xF9FB, 'M', u'炙'), + (0xF9FC, 'M', u'識'), + (0xF9FD, 'M', u'什'), + (0xF9FE, 'M', u'茶'), + (0xF9FF, 'M', u'刺'), + (0xFA00, 'M', u'切'), + (0xFA01, 'M', u'度'), + (0xFA02, 'M', u'拓'), + (0xFA03, 'M', u'糖'), + (0xFA04, 'M', u'宅'), + (0xFA05, 'M', u'洞'), + (0xFA06, 'M', u'暴'), + (0xFA07, 'M', u'輻'), + (0xFA08, 'M', u'行'), + (0xFA09, 'M', u'降'), + (0xFA0A, 'M', u'見'), + (0xFA0B, 'M', u'廓'), + (0xFA0C, 'M', u'兀'), + (0xFA0D, 'M', u'嗀'), + (0xFA0E, 'V'), + (0xFA10, 'M', u'塚'), + (0xFA11, 'V'), + (0xFA12, 'M', u'晴'), + (0xFA13, 'V'), + (0xFA15, 'M', u'凞'), + (0xFA16, 'M', u'猪'), + (0xFA17, 'M', u'益'), + (0xFA18, 'M', u'礼'), + ] + +def _seg_42(): + return [ + (0xFA19, 'M', u'神'), + (0xFA1A, 'M', u'祥'), + (0xFA1B, 'M', u'福'), + (0xFA1C, 'M', u'靖'), + (0xFA1D, 'M', u'精'), + (0xFA1E, 'M', u'羽'), + (0xFA1F, 'V'), + (0xFA20, 'M', u'蘒'), + (0xFA21, 'V'), + (0xFA22, 'M', u'諸'), + (0xFA23, 'V'), + (0xFA25, 'M', u'逸'), + (0xFA26, 'M', u'都'), + (0xFA27, 'V'), + (0xFA2A, 'M', u'飯'), + (0xFA2B, 'M', u'飼'), + (0xFA2C, 'M', u'館'), + (0xFA2D, 'M', u'鶴'), + (0xFA2E, 'M', u'郞'), + (0xFA2F, 'M', u'隷'), + (0xFA30, 'M', u'侮'), + (0xFA31, 'M', u'僧'), + (0xFA32, 'M', u'免'), + (0xFA33, 'M', u'勉'), + (0xFA34, 'M', u'勤'), + (0xFA35, 'M', u'卑'), + (0xFA36, 'M', u'喝'), + (0xFA37, 'M', u'嘆'), + (0xFA38, 'M', u'器'), + (0xFA39, 'M', u'塀'), + (0xFA3A, 'M', u'墨'), + (0xFA3B, 'M', u'層'), + (0xFA3C, 'M', u'屮'), + (0xFA3D, 'M', u'悔'), + (0xFA3E, 'M', u'慨'), + (0xFA3F, 'M', u'憎'), + (0xFA40, 'M', u'懲'), + (0xFA41, 'M', u'敏'), + (0xFA42, 'M', u'既'), + (0xFA43, 'M', u'暑'), + (0xFA44, 'M', u'梅'), + (0xFA45, 'M', u'海'), + (0xFA46, 'M', u'渚'), + (0xFA47, 'M', u'漢'), + (0xFA48, 'M', u'煮'), + (0xFA49, 'M', u'爫'), + (0xFA4A, 'M', u'琢'), + (0xFA4B, 'M', u'碑'), + (0xFA4C, 'M', u'社'), + (0xFA4D, 'M', u'祉'), + (0xFA4E, 'M', u'祈'), + (0xFA4F, 'M', u'祐'), + (0xFA50, 'M', u'祖'), + (0xFA51, 'M', u'祝'), + (0xFA52, 'M', u'禍'), + (0xFA53, 'M', u'禎'), + (0xFA54, 'M', u'穀'), + (0xFA55, 'M', u'突'), + (0xFA56, 'M', u'節'), + (0xFA57, 'M', u'練'), + (0xFA58, 'M', u'縉'), + (0xFA59, 'M', u'繁'), + (0xFA5A, 'M', u'署'), + (0xFA5B, 'M', u'者'), + (0xFA5C, 'M', u'臭'), + (0xFA5D, 'M', u'艹'), + (0xFA5F, 'M', u'著'), + (0xFA60, 'M', u'褐'), + (0xFA61, 'M', u'視'), + (0xFA62, 'M', u'謁'), + (0xFA63, 'M', u'謹'), + (0xFA64, 'M', u'賓'), + (0xFA65, 'M', u'贈'), + (0xFA66, 'M', u'辶'), + (0xFA67, 'M', u'逸'), + (0xFA68, 'M', u'難'), + (0xFA69, 'M', u'響'), + (0xFA6A, 'M', u'頻'), + (0xFA6B, 'M', u'恵'), + (0xFA6C, 'M', u'𤋮'), + (0xFA6D, 'M', u'舘'), + (0xFA6E, 'X'), + (0xFA70, 'M', u'並'), + (0xFA71, 'M', u'况'), + (0xFA72, 'M', u'全'), + (0xFA73, 'M', u'侀'), + (0xFA74, 'M', u'充'), + (0xFA75, 'M', u'冀'), + (0xFA76, 'M', u'勇'), + (0xFA77, 'M', u'勺'), + (0xFA78, 'M', u'喝'), + (0xFA79, 'M', u'啕'), + (0xFA7A, 'M', u'喙'), + (0xFA7B, 'M', u'嗢'), + (0xFA7C, 'M', u'塚'), + (0xFA7D, 'M', u'墳'), + (0xFA7E, 'M', u'奄'), + (0xFA7F, 'M', u'奔'), + (0xFA80, 'M', u'婢'), + (0xFA81, 'M', u'嬨'), + ] + +def _seg_43(): + return [ + (0xFA82, 'M', u'廒'), + (0xFA83, 'M', u'廙'), + (0xFA84, 'M', u'彩'), + (0xFA85, 'M', u'徭'), + (0xFA86, 'M', u'惘'), + (0xFA87, 'M', u'慎'), + (0xFA88, 'M', u'愈'), + (0xFA89, 'M', u'憎'), + (0xFA8A, 'M', u'慠'), + (0xFA8B, 'M', u'懲'), + (0xFA8C, 'M', u'戴'), + (0xFA8D, 'M', u'揄'), + (0xFA8E, 'M', u'搜'), + (0xFA8F, 'M', u'摒'), + (0xFA90, 'M', u'敖'), + (0xFA91, 'M', u'晴'), + (0xFA92, 'M', u'朗'), + (0xFA93, 'M', u'望'), + (0xFA94, 'M', u'杖'), + (0xFA95, 'M', u'歹'), + (0xFA96, 'M', u'殺'), + (0xFA97, 'M', u'流'), + (0xFA98, 'M', u'滛'), + (0xFA99, 'M', u'滋'), + (0xFA9A, 'M', u'漢'), + (0xFA9B, 'M', u'瀞'), + (0xFA9C, 'M', u'煮'), + (0xFA9D, 'M', u'瞧'), + (0xFA9E, 'M', u'爵'), + (0xFA9F, 'M', u'犯'), + (0xFAA0, 'M', u'猪'), + (0xFAA1, 'M', u'瑱'), + (0xFAA2, 'M', u'甆'), + (0xFAA3, 'M', u'画'), + (0xFAA4, 'M', u'瘝'), + (0xFAA5, 'M', u'瘟'), + (0xFAA6, 'M', u'益'), + (0xFAA7, 'M', u'盛'), + (0xFAA8, 'M', u'直'), + (0xFAA9, 'M', u'睊'), + (0xFAAA, 'M', u'着'), + (0xFAAB, 'M', u'磌'), + (0xFAAC, 'M', u'窱'), + (0xFAAD, 'M', u'節'), + (0xFAAE, 'M', u'类'), + (0xFAAF, 'M', u'絛'), + (0xFAB0, 'M', u'練'), + (0xFAB1, 'M', u'缾'), + (0xFAB2, 'M', u'者'), + (0xFAB3, 'M', u'荒'), + (0xFAB4, 'M', u'華'), + (0xFAB5, 'M', u'蝹'), + (0xFAB6, 'M', u'襁'), + (0xFAB7, 'M', u'覆'), + (0xFAB8, 'M', u'視'), + (0xFAB9, 'M', u'調'), + (0xFABA, 'M', u'諸'), + (0xFABB, 'M', u'請'), + (0xFABC, 'M', u'謁'), + (0xFABD, 'M', u'諾'), + (0xFABE, 'M', u'諭'), + (0xFABF, 'M', u'謹'), + (0xFAC0, 'M', u'變'), + (0xFAC1, 'M', u'贈'), + (0xFAC2, 'M', u'輸'), + (0xFAC3, 'M', u'遲'), + (0xFAC4, 'M', u'醙'), + (0xFAC5, 'M', u'鉶'), + (0xFAC6, 'M', u'陼'), + (0xFAC7, 'M', u'難'), + (0xFAC8, 'M', u'靖'), + (0xFAC9, 'M', u'韛'), + (0xFACA, 'M', u'響'), + (0xFACB, 'M', u'頋'), + (0xFACC, 'M', u'頻'), + (0xFACD, 'M', u'鬒'), + (0xFACE, 'M', u'龜'), + (0xFACF, 'M', u'𢡊'), + (0xFAD0, 'M', u'𢡄'), + (0xFAD1, 'M', u'𣏕'), + (0xFAD2, 'M', u'㮝'), + (0xFAD3, 'M', u'䀘'), + (0xFAD4, 'M', u'䀹'), + (0xFAD5, 'M', u'𥉉'), + (0xFAD6, 'M', u'𥳐'), + (0xFAD7, 'M', u'𧻓'), + (0xFAD8, 'M', u'齃'), + (0xFAD9, 'M', u'龎'), + (0xFADA, 'X'), + (0xFB00, 'M', u'ff'), + (0xFB01, 'M', u'fi'), + (0xFB02, 'M', u'fl'), + (0xFB03, 'M', u'ffi'), + (0xFB04, 'M', u'ffl'), + (0xFB05, 'M', u'st'), + (0xFB07, 'X'), + (0xFB13, 'M', u'մն'), + (0xFB14, 'M', u'մե'), + (0xFB15, 'M', u'մի'), + (0xFB16, 'M', u'վն'), + ] + +def _seg_44(): + return [ + (0xFB17, 'M', u'մխ'), + (0xFB18, 'X'), + (0xFB1D, 'M', u'יִ'), + (0xFB1E, 'V'), + (0xFB1F, 'M', u'ײַ'), + (0xFB20, 'M', u'ע'), + (0xFB21, 'M', u'א'), + (0xFB22, 'M', u'ד'), + (0xFB23, 'M', u'ה'), + (0xFB24, 'M', u'כ'), + (0xFB25, 'M', u'ל'), + (0xFB26, 'M', u'ם'), + (0xFB27, 'M', u'ר'), + (0xFB28, 'M', u'ת'), + (0xFB29, '3', u'+'), + (0xFB2A, 'M', u'שׁ'), + (0xFB2B, 'M', u'שׂ'), + (0xFB2C, 'M', u'שּׁ'), + (0xFB2D, 'M', u'שּׂ'), + (0xFB2E, 'M', u'אַ'), + (0xFB2F, 'M', u'אָ'), + (0xFB30, 'M', u'אּ'), + (0xFB31, 'M', u'בּ'), + (0xFB32, 'M', u'גּ'), + (0xFB33, 'M', u'דּ'), + (0xFB34, 'M', u'הּ'), + (0xFB35, 'M', u'וּ'), + (0xFB36, 'M', u'זּ'), + (0xFB37, 'X'), + (0xFB38, 'M', u'טּ'), + (0xFB39, 'M', u'יּ'), + (0xFB3A, 'M', u'ךּ'), + (0xFB3B, 'M', u'כּ'), + (0xFB3C, 'M', u'לּ'), + (0xFB3D, 'X'), + (0xFB3E, 'M', u'מּ'), + (0xFB3F, 'X'), + (0xFB40, 'M', u'נּ'), + (0xFB41, 'M', u'סּ'), + (0xFB42, 'X'), + (0xFB43, 'M', u'ףּ'), + (0xFB44, 'M', u'פּ'), + (0xFB45, 'X'), + (0xFB46, 'M', u'צּ'), + (0xFB47, 'M', u'קּ'), + (0xFB48, 'M', u'רּ'), + (0xFB49, 'M', u'שּ'), + (0xFB4A, 'M', u'תּ'), + (0xFB4B, 'M', u'וֹ'), + (0xFB4C, 'M', u'בֿ'), + (0xFB4D, 'M', u'כֿ'), + (0xFB4E, 'M', u'פֿ'), + (0xFB4F, 'M', u'אל'), + (0xFB50, 'M', u'ٱ'), + (0xFB52, 'M', u'ٻ'), + (0xFB56, 'M', u'پ'), + (0xFB5A, 'M', u'ڀ'), + (0xFB5E, 'M', u'ٺ'), + (0xFB62, 'M', u'ٿ'), + (0xFB66, 'M', u'ٹ'), + (0xFB6A, 'M', u'ڤ'), + (0xFB6E, 'M', u'ڦ'), + (0xFB72, 'M', u'ڄ'), + (0xFB76, 'M', u'ڃ'), + (0xFB7A, 'M', u'چ'), + (0xFB7E, 'M', u'ڇ'), + (0xFB82, 'M', u'ڍ'), + (0xFB84, 'M', u'ڌ'), + (0xFB86, 'M', u'ڎ'), + (0xFB88, 'M', u'ڈ'), + (0xFB8A, 'M', u'ژ'), + (0xFB8C, 'M', u'ڑ'), + (0xFB8E, 'M', u'ک'), + (0xFB92, 'M', u'گ'), + (0xFB96, 'M', u'ڳ'), + (0xFB9A, 'M', u'ڱ'), + (0xFB9E, 'M', u'ں'), + (0xFBA0, 'M', u'ڻ'), + (0xFBA4, 'M', u'ۀ'), + (0xFBA6, 'M', u'ہ'), + (0xFBAA, 'M', u'ھ'), + (0xFBAE, 'M', u'ے'), + (0xFBB0, 'M', u'ۓ'), + (0xFBB2, 'V'), + (0xFBC2, 'X'), + (0xFBD3, 'M', u'ڭ'), + (0xFBD7, 'M', u'ۇ'), + (0xFBD9, 'M', u'ۆ'), + (0xFBDB, 'M', u'ۈ'), + (0xFBDD, 'M', u'ۇٴ'), + (0xFBDE, 'M', u'ۋ'), + (0xFBE0, 'M', u'ۅ'), + (0xFBE2, 'M', u'ۉ'), + (0xFBE4, 'M', u'ې'), + (0xFBE8, 'M', u'ى'), + (0xFBEA, 'M', u'ئا'), + (0xFBEC, 'M', u'ئە'), + (0xFBEE, 'M', u'ئو'), + (0xFBF0, 'M', u'ئۇ'), + (0xFBF2, 'M', u'ئۆ'), + ] + +def _seg_45(): + return [ + (0xFBF4, 'M', u'ئۈ'), + (0xFBF6, 'M', u'ئې'), + (0xFBF9, 'M', u'ئى'), + (0xFBFC, 'M', u'ی'), + (0xFC00, 'M', u'ئج'), + (0xFC01, 'M', u'ئح'), + (0xFC02, 'M', u'ئم'), + (0xFC03, 'M', u'ئى'), + (0xFC04, 'M', u'ئي'), + (0xFC05, 'M', u'بج'), + (0xFC06, 'M', u'بح'), + (0xFC07, 'M', u'بخ'), + (0xFC08, 'M', u'بم'), + (0xFC09, 'M', u'بى'), + (0xFC0A, 'M', u'بي'), + (0xFC0B, 'M', u'تج'), + (0xFC0C, 'M', u'تح'), + (0xFC0D, 'M', u'تخ'), + (0xFC0E, 'M', u'تم'), + (0xFC0F, 'M', u'تى'), + (0xFC10, 'M', u'تي'), + (0xFC11, 'M', u'ثج'), + (0xFC12, 'M', u'ثم'), + (0xFC13, 'M', u'ثى'), + (0xFC14, 'M', u'ثي'), + (0xFC15, 'M', u'جح'), + (0xFC16, 'M', u'جم'), + (0xFC17, 'M', u'حج'), + (0xFC18, 'M', u'حم'), + (0xFC19, 'M', u'خج'), + (0xFC1A, 'M', u'خح'), + (0xFC1B, 'M', u'خم'), + (0xFC1C, 'M', u'سج'), + (0xFC1D, 'M', u'سح'), + (0xFC1E, 'M', u'سخ'), + (0xFC1F, 'M', u'سم'), + (0xFC20, 'M', u'صح'), + (0xFC21, 'M', u'صم'), + (0xFC22, 'M', u'ضج'), + (0xFC23, 'M', u'ضح'), + (0xFC24, 'M', u'ضخ'), + (0xFC25, 'M', u'ضم'), + (0xFC26, 'M', u'طح'), + (0xFC27, 'M', u'طم'), + (0xFC28, 'M', u'ظم'), + (0xFC29, 'M', u'عج'), + (0xFC2A, 'M', u'عم'), + (0xFC2B, 'M', u'غج'), + (0xFC2C, 'M', u'غم'), + (0xFC2D, 'M', u'فج'), + (0xFC2E, 'M', u'فح'), + (0xFC2F, 'M', u'فخ'), + (0xFC30, 'M', u'فم'), + (0xFC31, 'M', u'فى'), + (0xFC32, 'M', u'في'), + (0xFC33, 'M', u'قح'), + (0xFC34, 'M', u'قم'), + (0xFC35, 'M', u'قى'), + (0xFC36, 'M', u'قي'), + (0xFC37, 'M', u'كا'), + (0xFC38, 'M', u'كج'), + (0xFC39, 'M', u'كح'), + (0xFC3A, 'M', u'كخ'), + (0xFC3B, 'M', u'كل'), + (0xFC3C, 'M', u'كم'), + (0xFC3D, 'M', u'كى'), + (0xFC3E, 'M', u'كي'), + (0xFC3F, 'M', u'لج'), + (0xFC40, 'M', u'لح'), + (0xFC41, 'M', u'لخ'), + (0xFC42, 'M', u'لم'), + (0xFC43, 'M', u'لى'), + (0xFC44, 'M', u'لي'), + (0xFC45, 'M', u'مج'), + (0xFC46, 'M', u'مح'), + (0xFC47, 'M', u'مخ'), + (0xFC48, 'M', u'مم'), + (0xFC49, 'M', u'مى'), + (0xFC4A, 'M', u'مي'), + (0xFC4B, 'M', u'نج'), + (0xFC4C, 'M', u'نح'), + (0xFC4D, 'M', u'نخ'), + (0xFC4E, 'M', u'نم'), + (0xFC4F, 'M', u'نى'), + (0xFC50, 'M', u'ني'), + (0xFC51, 'M', u'هج'), + (0xFC52, 'M', u'هم'), + (0xFC53, 'M', u'هى'), + (0xFC54, 'M', u'هي'), + (0xFC55, 'M', u'يج'), + (0xFC56, 'M', u'يح'), + (0xFC57, 'M', u'يخ'), + (0xFC58, 'M', u'يم'), + (0xFC59, 'M', u'يى'), + (0xFC5A, 'M', u'يي'), + (0xFC5B, 'M', u'ذٰ'), + (0xFC5C, 'M', u'رٰ'), + (0xFC5D, 'M', u'ىٰ'), + (0xFC5E, '3', u' ٌّ'), + (0xFC5F, '3', u' ٍّ'), + ] + +def _seg_46(): + return [ + (0xFC60, '3', u' َّ'), + (0xFC61, '3', u' ُّ'), + (0xFC62, '3', u' ِّ'), + (0xFC63, '3', u' ّٰ'), + (0xFC64, 'M', u'ئر'), + (0xFC65, 'M', u'ئز'), + (0xFC66, 'M', u'ئم'), + (0xFC67, 'M', u'ئن'), + (0xFC68, 'M', u'ئى'), + (0xFC69, 'M', u'ئي'), + (0xFC6A, 'M', u'بر'), + (0xFC6B, 'M', u'بز'), + (0xFC6C, 'M', u'بم'), + (0xFC6D, 'M', u'بن'), + (0xFC6E, 'M', u'بى'), + (0xFC6F, 'M', u'بي'), + (0xFC70, 'M', u'تر'), + (0xFC71, 'M', u'تز'), + (0xFC72, 'M', u'تم'), + (0xFC73, 'M', u'تن'), + (0xFC74, 'M', u'تى'), + (0xFC75, 'M', u'تي'), + (0xFC76, 'M', u'ثر'), + (0xFC77, 'M', u'ثز'), + (0xFC78, 'M', u'ثم'), + (0xFC79, 'M', u'ثن'), + (0xFC7A, 'M', u'ثى'), + (0xFC7B, 'M', u'ثي'), + (0xFC7C, 'M', u'فى'), + (0xFC7D, 'M', u'في'), + (0xFC7E, 'M', u'قى'), + (0xFC7F, 'M', u'قي'), + (0xFC80, 'M', u'كا'), + (0xFC81, 'M', u'كل'), + (0xFC82, 'M', u'كم'), + (0xFC83, 'M', u'كى'), + (0xFC84, 'M', u'كي'), + (0xFC85, 'M', u'لم'), + (0xFC86, 'M', u'لى'), + (0xFC87, 'M', u'لي'), + (0xFC88, 'M', u'ما'), + (0xFC89, 'M', u'مم'), + (0xFC8A, 'M', u'نر'), + (0xFC8B, 'M', u'نز'), + (0xFC8C, 'M', u'نم'), + (0xFC8D, 'M', u'نن'), + (0xFC8E, 'M', u'نى'), + (0xFC8F, 'M', u'ني'), + (0xFC90, 'M', u'ىٰ'), + (0xFC91, 'M', u'ير'), + (0xFC92, 'M', u'يز'), + (0xFC93, 'M', u'يم'), + (0xFC94, 'M', u'ين'), + (0xFC95, 'M', u'يى'), + (0xFC96, 'M', u'يي'), + (0xFC97, 'M', u'ئج'), + (0xFC98, 'M', u'ئح'), + (0xFC99, 'M', u'ئخ'), + (0xFC9A, 'M', u'ئم'), + (0xFC9B, 'M', u'ئه'), + (0xFC9C, 'M', u'بج'), + (0xFC9D, 'M', u'بح'), + (0xFC9E, 'M', u'بخ'), + (0xFC9F, 'M', u'بم'), + (0xFCA0, 'M', u'به'), + (0xFCA1, 'M', u'تج'), + (0xFCA2, 'M', u'تح'), + (0xFCA3, 'M', u'تخ'), + (0xFCA4, 'M', u'تم'), + (0xFCA5, 'M', u'ته'), + (0xFCA6, 'M', u'ثم'), + (0xFCA7, 'M', u'جح'), + (0xFCA8, 'M', u'جم'), + (0xFCA9, 'M', u'حج'), + (0xFCAA, 'M', u'حم'), + (0xFCAB, 'M', u'خج'), + (0xFCAC, 'M', u'خم'), + (0xFCAD, 'M', u'سج'), + (0xFCAE, 'M', u'سح'), + (0xFCAF, 'M', u'سخ'), + (0xFCB0, 'M', u'سم'), + (0xFCB1, 'M', u'صح'), + (0xFCB2, 'M', u'صخ'), + (0xFCB3, 'M', u'صم'), + (0xFCB4, 'M', u'ضج'), + (0xFCB5, 'M', u'ضح'), + (0xFCB6, 'M', u'ضخ'), + (0xFCB7, 'M', u'ضم'), + (0xFCB8, 'M', u'طح'), + (0xFCB9, 'M', u'ظم'), + (0xFCBA, 'M', u'عج'), + (0xFCBB, 'M', u'عم'), + (0xFCBC, 'M', u'غج'), + (0xFCBD, 'M', u'غم'), + (0xFCBE, 'M', u'فج'), + (0xFCBF, 'M', u'فح'), + (0xFCC0, 'M', u'فخ'), + (0xFCC1, 'M', u'فم'), + (0xFCC2, 'M', u'قح'), + (0xFCC3, 'M', u'قم'), + ] + +def _seg_47(): + return [ + (0xFCC4, 'M', u'كج'), + (0xFCC5, 'M', u'كح'), + (0xFCC6, 'M', u'كخ'), + (0xFCC7, 'M', u'كل'), + (0xFCC8, 'M', u'كم'), + (0xFCC9, 'M', u'لج'), + (0xFCCA, 'M', u'لح'), + (0xFCCB, 'M', u'لخ'), + (0xFCCC, 'M', u'لم'), + (0xFCCD, 'M', u'له'), + (0xFCCE, 'M', u'مج'), + (0xFCCF, 'M', u'مح'), + (0xFCD0, 'M', u'مخ'), + (0xFCD1, 'M', u'مم'), + (0xFCD2, 'M', u'نج'), + (0xFCD3, 'M', u'نح'), + (0xFCD4, 'M', u'نخ'), + (0xFCD5, 'M', u'نم'), + (0xFCD6, 'M', u'نه'), + (0xFCD7, 'M', u'هج'), + (0xFCD8, 'M', u'هم'), + (0xFCD9, 'M', u'هٰ'), + (0xFCDA, 'M', u'يج'), + (0xFCDB, 'M', u'يح'), + (0xFCDC, 'M', u'يخ'), + (0xFCDD, 'M', u'يم'), + (0xFCDE, 'M', u'يه'), + (0xFCDF, 'M', u'ئم'), + (0xFCE0, 'M', u'ئه'), + (0xFCE1, 'M', u'بم'), + (0xFCE2, 'M', u'به'), + (0xFCE3, 'M', u'تم'), + (0xFCE4, 'M', u'ته'), + (0xFCE5, 'M', u'ثم'), + (0xFCE6, 'M', u'ثه'), + (0xFCE7, 'M', u'سم'), + (0xFCE8, 'M', u'سه'), + (0xFCE9, 'M', u'شم'), + (0xFCEA, 'M', u'شه'), + (0xFCEB, 'M', u'كل'), + (0xFCEC, 'M', u'كم'), + (0xFCED, 'M', u'لم'), + (0xFCEE, 'M', u'نم'), + (0xFCEF, 'M', u'نه'), + (0xFCF0, 'M', u'يم'), + (0xFCF1, 'M', u'يه'), + (0xFCF2, 'M', u'ـَّ'), + (0xFCF3, 'M', u'ـُّ'), + (0xFCF4, 'M', u'ـِّ'), + (0xFCF5, 'M', u'طى'), + (0xFCF6, 'M', u'طي'), + (0xFCF7, 'M', u'عى'), + (0xFCF8, 'M', u'عي'), + (0xFCF9, 'M', u'غى'), + (0xFCFA, 'M', u'غي'), + (0xFCFB, 'M', u'سى'), + (0xFCFC, 'M', u'سي'), + (0xFCFD, 'M', u'شى'), + (0xFCFE, 'M', u'شي'), + (0xFCFF, 'M', u'حى'), + (0xFD00, 'M', u'حي'), + (0xFD01, 'M', u'جى'), + (0xFD02, 'M', u'جي'), + (0xFD03, 'M', u'خى'), + (0xFD04, 'M', u'خي'), + (0xFD05, 'M', u'صى'), + (0xFD06, 'M', u'صي'), + (0xFD07, 'M', u'ضى'), + (0xFD08, 'M', u'ضي'), + (0xFD09, 'M', u'شج'), + (0xFD0A, 'M', u'شح'), + (0xFD0B, 'M', u'شخ'), + (0xFD0C, 'M', u'شم'), + (0xFD0D, 'M', u'شر'), + (0xFD0E, 'M', u'سر'), + (0xFD0F, 'M', u'صر'), + (0xFD10, 'M', u'ضر'), + (0xFD11, 'M', u'طى'), + (0xFD12, 'M', u'طي'), + (0xFD13, 'M', u'عى'), + (0xFD14, 'M', u'عي'), + (0xFD15, 'M', u'غى'), + (0xFD16, 'M', u'غي'), + (0xFD17, 'M', u'سى'), + (0xFD18, 'M', u'سي'), + (0xFD19, 'M', u'شى'), + (0xFD1A, 'M', u'شي'), + (0xFD1B, 'M', u'حى'), + (0xFD1C, 'M', u'حي'), + (0xFD1D, 'M', u'جى'), + (0xFD1E, 'M', u'جي'), + (0xFD1F, 'M', u'خى'), + (0xFD20, 'M', u'خي'), + (0xFD21, 'M', u'صى'), + (0xFD22, 'M', u'صي'), + (0xFD23, 'M', u'ضى'), + (0xFD24, 'M', u'ضي'), + (0xFD25, 'M', u'شج'), + (0xFD26, 'M', u'شح'), + (0xFD27, 'M', u'شخ'), + ] + +def _seg_48(): + return [ + (0xFD28, 'M', u'شم'), + (0xFD29, 'M', u'شر'), + (0xFD2A, 'M', u'سر'), + (0xFD2B, 'M', u'صر'), + (0xFD2C, 'M', u'ضر'), + (0xFD2D, 'M', u'شج'), + (0xFD2E, 'M', u'شح'), + (0xFD2F, 'M', u'شخ'), + (0xFD30, 'M', u'شم'), + (0xFD31, 'M', u'سه'), + (0xFD32, 'M', u'شه'), + (0xFD33, 'M', u'طم'), + (0xFD34, 'M', u'سج'), + (0xFD35, 'M', u'سح'), + (0xFD36, 'M', u'سخ'), + (0xFD37, 'M', u'شج'), + (0xFD38, 'M', u'شح'), + (0xFD39, 'M', u'شخ'), + (0xFD3A, 'M', u'طم'), + (0xFD3B, 'M', u'ظم'), + (0xFD3C, 'M', u'اً'), + (0xFD3E, 'V'), + (0xFD40, 'X'), + (0xFD50, 'M', u'تجم'), + (0xFD51, 'M', u'تحج'), + (0xFD53, 'M', u'تحم'), + (0xFD54, 'M', u'تخم'), + (0xFD55, 'M', u'تمج'), + (0xFD56, 'M', u'تمح'), + (0xFD57, 'M', u'تمخ'), + (0xFD58, 'M', u'جمح'), + (0xFD5A, 'M', u'حمي'), + (0xFD5B, 'M', u'حمى'), + (0xFD5C, 'M', u'سحج'), + (0xFD5D, 'M', u'سجح'), + (0xFD5E, 'M', u'سجى'), + (0xFD5F, 'M', u'سمح'), + (0xFD61, 'M', u'سمج'), + (0xFD62, 'M', u'سمم'), + (0xFD64, 'M', u'صحح'), + (0xFD66, 'M', u'صمم'), + (0xFD67, 'M', u'شحم'), + (0xFD69, 'M', u'شجي'), + (0xFD6A, 'M', u'شمخ'), + (0xFD6C, 'M', u'شمم'), + (0xFD6E, 'M', u'ضحى'), + (0xFD6F, 'M', u'ضخم'), + (0xFD71, 'M', u'طمح'), + (0xFD73, 'M', u'طمم'), + (0xFD74, 'M', u'طمي'), + (0xFD75, 'M', u'عجم'), + (0xFD76, 'M', u'عمم'), + (0xFD78, 'M', u'عمى'), + (0xFD79, 'M', u'غمم'), + (0xFD7A, 'M', u'غمي'), + (0xFD7B, 'M', u'غمى'), + (0xFD7C, 'M', u'فخم'), + (0xFD7E, 'M', u'قمح'), + (0xFD7F, 'M', u'قمم'), + (0xFD80, 'M', u'لحم'), + (0xFD81, 'M', u'لحي'), + (0xFD82, 'M', u'لحى'), + (0xFD83, 'M', u'لجج'), + (0xFD85, 'M', u'لخم'), + (0xFD87, 'M', u'لمح'), + (0xFD89, 'M', u'محج'), + (0xFD8A, 'M', u'محم'), + (0xFD8B, 'M', u'محي'), + (0xFD8C, 'M', u'مجح'), + (0xFD8D, 'M', u'مجم'), + (0xFD8E, 'M', u'مخج'), + (0xFD8F, 'M', u'مخم'), + (0xFD90, 'X'), + (0xFD92, 'M', u'مجخ'), + (0xFD93, 'M', u'همج'), + (0xFD94, 'M', u'همم'), + (0xFD95, 'M', u'نحم'), + (0xFD96, 'M', u'نحى'), + (0xFD97, 'M', u'نجم'), + (0xFD99, 'M', u'نجى'), + (0xFD9A, 'M', u'نمي'), + (0xFD9B, 'M', u'نمى'), + (0xFD9C, 'M', u'يمم'), + (0xFD9E, 'M', u'بخي'), + (0xFD9F, 'M', u'تجي'), + (0xFDA0, 'M', u'تجى'), + (0xFDA1, 'M', u'تخي'), + (0xFDA2, 'M', u'تخى'), + (0xFDA3, 'M', u'تمي'), + (0xFDA4, 'M', u'تمى'), + (0xFDA5, 'M', u'جمي'), + (0xFDA6, 'M', u'جحى'), + (0xFDA7, 'M', u'جمى'), + (0xFDA8, 'M', u'سخى'), + (0xFDA9, 'M', u'صحي'), + (0xFDAA, 'M', u'شحي'), + (0xFDAB, 'M', u'ضحي'), + (0xFDAC, 'M', u'لجي'), + (0xFDAD, 'M', u'لمي'), + (0xFDAE, 'M', u'يحي'), + ] + +def _seg_49(): + return [ + (0xFDAF, 'M', u'يجي'), + (0xFDB0, 'M', u'يمي'), + (0xFDB1, 'M', u'ممي'), + (0xFDB2, 'M', u'قمي'), + (0xFDB3, 'M', u'نحي'), + (0xFDB4, 'M', u'قمح'), + (0xFDB5, 'M', u'لحم'), + (0xFDB6, 'M', u'عمي'), + (0xFDB7, 'M', u'كمي'), + (0xFDB8, 'M', u'نجح'), + (0xFDB9, 'M', u'مخي'), + (0xFDBA, 'M', u'لجم'), + (0xFDBB, 'M', u'كمم'), + (0xFDBC, 'M', u'لجم'), + (0xFDBD, 'M', u'نجح'), + (0xFDBE, 'M', u'جحي'), + (0xFDBF, 'M', u'حجي'), + (0xFDC0, 'M', u'مجي'), + (0xFDC1, 'M', u'فمي'), + (0xFDC2, 'M', u'بحي'), + (0xFDC3, 'M', u'كمم'), + (0xFDC4, 'M', u'عجم'), + (0xFDC5, 'M', u'صمم'), + (0xFDC6, 'M', u'سخي'), + (0xFDC7, 'M', u'نجي'), + (0xFDC8, 'X'), + (0xFDF0, 'M', u'صلے'), + (0xFDF1, 'M', u'قلے'), + (0xFDF2, 'M', u'الله'), + (0xFDF3, 'M', u'اكبر'), + (0xFDF4, 'M', u'محمد'), + (0xFDF5, 'M', u'صلعم'), + (0xFDF6, 'M', u'رسول'), + (0xFDF7, 'M', u'عليه'), + (0xFDF8, 'M', u'وسلم'), + (0xFDF9, 'M', u'صلى'), + (0xFDFA, '3', u'صلى الله عليه وسلم'), + (0xFDFB, '3', u'جل جلاله'), + (0xFDFC, 'M', u'ریال'), + (0xFDFD, 'V'), + (0xFDFE, 'X'), + (0xFE00, 'I'), + (0xFE10, '3', u','), + (0xFE11, 'M', u'、'), + (0xFE12, 'X'), + (0xFE13, '3', u':'), + (0xFE14, '3', u';'), + (0xFE15, '3', u'!'), + (0xFE16, '3', u'?'), + (0xFE17, 'M', u'〖'), + (0xFE18, 'M', u'〗'), + (0xFE19, 'X'), + (0xFE20, 'V'), + (0xFE30, 'X'), + (0xFE31, 'M', u'—'), + (0xFE32, 'M', u'–'), + (0xFE33, '3', u'_'), + (0xFE35, '3', u'('), + (0xFE36, '3', u')'), + (0xFE37, '3', u'{'), + (0xFE38, '3', u'}'), + (0xFE39, 'M', u'〔'), + (0xFE3A, 'M', u'〕'), + (0xFE3B, 'M', u'【'), + (0xFE3C, 'M', u'】'), + (0xFE3D, 'M', u'《'), + (0xFE3E, 'M', u'》'), + (0xFE3F, 'M', u'〈'), + (0xFE40, 'M', u'〉'), + (0xFE41, 'M', u'「'), + (0xFE42, 'M', u'」'), + (0xFE43, 'M', u'『'), + (0xFE44, 'M', u'』'), + (0xFE45, 'V'), + (0xFE47, '3', u'['), + (0xFE48, '3', u']'), + (0xFE49, '3', u' ̅'), + (0xFE4D, '3', u'_'), + (0xFE50, '3', u','), + (0xFE51, 'M', u'、'), + (0xFE52, 'X'), + (0xFE54, '3', u';'), + (0xFE55, '3', u':'), + (0xFE56, '3', u'?'), + (0xFE57, '3', u'!'), + (0xFE58, 'M', u'—'), + (0xFE59, '3', u'('), + (0xFE5A, '3', u')'), + (0xFE5B, '3', u'{'), + (0xFE5C, '3', u'}'), + (0xFE5D, 'M', u'〔'), + (0xFE5E, 'M', u'〕'), + (0xFE5F, '3', u'#'), + (0xFE60, '3', u'&'), + (0xFE61, '3', u'*'), + (0xFE62, '3', u'+'), + (0xFE63, 'M', u'-'), + (0xFE64, '3', u'<'), + (0xFE65, '3', u'>'), + (0xFE66, '3', u'='), + ] + +def _seg_50(): + return [ + (0xFE67, 'X'), + (0xFE68, '3', u'\\'), + (0xFE69, '3', u'$'), + (0xFE6A, '3', u'%'), + (0xFE6B, '3', u'@'), + (0xFE6C, 'X'), + (0xFE70, '3', u' ً'), + (0xFE71, 'M', u'ـً'), + (0xFE72, '3', u' ٌ'), + (0xFE73, 'V'), + (0xFE74, '3', u' ٍ'), + (0xFE75, 'X'), + (0xFE76, '3', u' َ'), + (0xFE77, 'M', u'ـَ'), + (0xFE78, '3', u' ُ'), + (0xFE79, 'M', u'ـُ'), + (0xFE7A, '3', u' ِ'), + (0xFE7B, 'M', u'ـِ'), + (0xFE7C, '3', u' ّ'), + (0xFE7D, 'M', u'ـّ'), + (0xFE7E, '3', u' ْ'), + (0xFE7F, 'M', u'ـْ'), + (0xFE80, 'M', u'ء'), + (0xFE81, 'M', u'آ'), + (0xFE83, 'M', u'أ'), + (0xFE85, 'M', u'ؤ'), + (0xFE87, 'M', u'إ'), + (0xFE89, 'M', u'ئ'), + (0xFE8D, 'M', u'ا'), + (0xFE8F, 'M', u'ب'), + (0xFE93, 'M', u'ة'), + (0xFE95, 'M', u'ت'), + (0xFE99, 'M', u'ث'), + (0xFE9D, 'M', u'ج'), + (0xFEA1, 'M', u'ح'), + (0xFEA5, 'M', u'خ'), + (0xFEA9, 'M', u'د'), + (0xFEAB, 'M', u'ذ'), + (0xFEAD, 'M', u'ر'), + (0xFEAF, 'M', u'ز'), + (0xFEB1, 'M', u'س'), + (0xFEB5, 'M', u'ش'), + (0xFEB9, 'M', u'ص'), + (0xFEBD, 'M', u'ض'), + (0xFEC1, 'M', u'ط'), + (0xFEC5, 'M', u'ظ'), + (0xFEC9, 'M', u'ع'), + (0xFECD, 'M', u'غ'), + (0xFED1, 'M', u'ف'), + (0xFED5, 'M', u'ق'), + (0xFED9, 'M', u'ك'), + (0xFEDD, 'M', u'ل'), + (0xFEE1, 'M', u'م'), + (0xFEE5, 'M', u'ن'), + (0xFEE9, 'M', u'ه'), + (0xFEED, 'M', u'و'), + (0xFEEF, 'M', u'ى'), + (0xFEF1, 'M', u'ي'), + (0xFEF5, 'M', u'لآ'), + (0xFEF7, 'M', u'لأ'), + (0xFEF9, 'M', u'لإ'), + (0xFEFB, 'M', u'لا'), + (0xFEFD, 'X'), + (0xFEFF, 'I'), + (0xFF00, 'X'), + (0xFF01, '3', u'!'), + (0xFF02, '3', u'"'), + (0xFF03, '3', u'#'), + (0xFF04, '3', u'$'), + (0xFF05, '3', u'%'), + (0xFF06, '3', u'&'), + (0xFF07, '3', u'\''), + (0xFF08, '3', u'('), + (0xFF09, '3', u')'), + (0xFF0A, '3', u'*'), + (0xFF0B, '3', u'+'), + (0xFF0C, '3', u','), + (0xFF0D, 'M', u'-'), + (0xFF0E, 'M', u'.'), + (0xFF0F, '3', u'/'), + (0xFF10, 'M', u'0'), + (0xFF11, 'M', u'1'), + (0xFF12, 'M', u'2'), + (0xFF13, 'M', u'3'), + (0xFF14, 'M', u'4'), + (0xFF15, 'M', u'5'), + (0xFF16, 'M', u'6'), + (0xFF17, 'M', u'7'), + (0xFF18, 'M', u'8'), + (0xFF19, 'M', u'9'), + (0xFF1A, '3', u':'), + (0xFF1B, '3', u';'), + (0xFF1C, '3', u'<'), + (0xFF1D, '3', u'='), + (0xFF1E, '3', u'>'), + (0xFF1F, '3', u'?'), + (0xFF20, '3', u'@'), + (0xFF21, 'M', u'a'), + (0xFF22, 'M', u'b'), + (0xFF23, 'M', u'c'), + ] + +def _seg_51(): + return [ + (0xFF24, 'M', u'd'), + (0xFF25, 'M', u'e'), + (0xFF26, 'M', u'f'), + (0xFF27, 'M', u'g'), + (0xFF28, 'M', u'h'), + (0xFF29, 'M', u'i'), + (0xFF2A, 'M', u'j'), + (0xFF2B, 'M', u'k'), + (0xFF2C, 'M', u'l'), + (0xFF2D, 'M', u'm'), + (0xFF2E, 'M', u'n'), + (0xFF2F, 'M', u'o'), + (0xFF30, 'M', u'p'), + (0xFF31, 'M', u'q'), + (0xFF32, 'M', u'r'), + (0xFF33, 'M', u's'), + (0xFF34, 'M', u't'), + (0xFF35, 'M', u'u'), + (0xFF36, 'M', u'v'), + (0xFF37, 'M', u'w'), + (0xFF38, 'M', u'x'), + (0xFF39, 'M', u'y'), + (0xFF3A, 'M', u'z'), + (0xFF3B, '3', u'['), + (0xFF3C, '3', u'\\'), + (0xFF3D, '3', u']'), + (0xFF3E, '3', u'^'), + (0xFF3F, '3', u'_'), + (0xFF40, '3', u'`'), + (0xFF41, 'M', u'a'), + (0xFF42, 'M', u'b'), + (0xFF43, 'M', u'c'), + (0xFF44, 'M', u'd'), + (0xFF45, 'M', u'e'), + (0xFF46, 'M', u'f'), + (0xFF47, 'M', u'g'), + (0xFF48, 'M', u'h'), + (0xFF49, 'M', u'i'), + (0xFF4A, 'M', u'j'), + (0xFF4B, 'M', u'k'), + (0xFF4C, 'M', u'l'), + (0xFF4D, 'M', u'm'), + (0xFF4E, 'M', u'n'), + (0xFF4F, 'M', u'o'), + (0xFF50, 'M', u'p'), + (0xFF51, 'M', u'q'), + (0xFF52, 'M', u'r'), + (0xFF53, 'M', u's'), + (0xFF54, 'M', u't'), + (0xFF55, 'M', u'u'), + (0xFF56, 'M', u'v'), + (0xFF57, 'M', u'w'), + (0xFF58, 'M', u'x'), + (0xFF59, 'M', u'y'), + (0xFF5A, 'M', u'z'), + (0xFF5B, '3', u'{'), + (0xFF5C, '3', u'|'), + (0xFF5D, '3', u'}'), + (0xFF5E, '3', u'~'), + (0xFF5F, 'M', u'⦅'), + (0xFF60, 'M', u'⦆'), + (0xFF61, 'M', u'.'), + (0xFF62, 'M', u'「'), + (0xFF63, 'M', u'」'), + (0xFF64, 'M', u'、'), + (0xFF65, 'M', u'・'), + (0xFF66, 'M', u'ヲ'), + (0xFF67, 'M', u'ァ'), + (0xFF68, 'M', u'ィ'), + (0xFF69, 'M', u'ゥ'), + (0xFF6A, 'M', u'ェ'), + (0xFF6B, 'M', u'ォ'), + (0xFF6C, 'M', u'ャ'), + (0xFF6D, 'M', u'ュ'), + (0xFF6E, 'M', u'ョ'), + (0xFF6F, 'M', u'ッ'), + (0xFF70, 'M', u'ー'), + (0xFF71, 'M', u'ア'), + (0xFF72, 'M', u'イ'), + (0xFF73, 'M', u'ウ'), + (0xFF74, 'M', u'エ'), + (0xFF75, 'M', u'オ'), + (0xFF76, 'M', u'カ'), + (0xFF77, 'M', u'キ'), + (0xFF78, 'M', u'ク'), + (0xFF79, 'M', u'ケ'), + (0xFF7A, 'M', u'コ'), + (0xFF7B, 'M', u'サ'), + (0xFF7C, 'M', u'シ'), + (0xFF7D, 'M', u'ス'), + (0xFF7E, 'M', u'セ'), + (0xFF7F, 'M', u'ソ'), + (0xFF80, 'M', u'タ'), + (0xFF81, 'M', u'チ'), + (0xFF82, 'M', u'ツ'), + (0xFF83, 'M', u'テ'), + (0xFF84, 'M', u'ト'), + (0xFF85, 'M', u'ナ'), + (0xFF86, 'M', u'ニ'), + (0xFF87, 'M', u'ヌ'), + ] + +def _seg_52(): + return [ + (0xFF88, 'M', u'ネ'), + (0xFF89, 'M', u'ノ'), + (0xFF8A, 'M', u'ハ'), + (0xFF8B, 'M', u'ヒ'), + (0xFF8C, 'M', u'フ'), + (0xFF8D, 'M', u'ヘ'), + (0xFF8E, 'M', u'ホ'), + (0xFF8F, 'M', u'マ'), + (0xFF90, 'M', u'ミ'), + (0xFF91, 'M', u'ム'), + (0xFF92, 'M', u'メ'), + (0xFF93, 'M', u'モ'), + (0xFF94, 'M', u'ヤ'), + (0xFF95, 'M', u'ユ'), + (0xFF96, 'M', u'ヨ'), + (0xFF97, 'M', u'ラ'), + (0xFF98, 'M', u'リ'), + (0xFF99, 'M', u'ル'), + (0xFF9A, 'M', u'レ'), + (0xFF9B, 'M', u'ロ'), + (0xFF9C, 'M', u'ワ'), + (0xFF9D, 'M', u'ン'), + (0xFF9E, 'M', u'゙'), + (0xFF9F, 'M', u'゚'), + (0xFFA0, 'X'), + (0xFFA1, 'M', u'ᄀ'), + (0xFFA2, 'M', u'ᄁ'), + (0xFFA3, 'M', u'ᆪ'), + (0xFFA4, 'M', u'ᄂ'), + (0xFFA5, 'M', u'ᆬ'), + (0xFFA6, 'M', u'ᆭ'), + (0xFFA7, 'M', u'ᄃ'), + (0xFFA8, 'M', u'ᄄ'), + (0xFFA9, 'M', u'ᄅ'), + (0xFFAA, 'M', u'ᆰ'), + (0xFFAB, 'M', u'ᆱ'), + (0xFFAC, 'M', u'ᆲ'), + (0xFFAD, 'M', u'ᆳ'), + (0xFFAE, 'M', u'ᆴ'), + (0xFFAF, 'M', u'ᆵ'), + (0xFFB0, 'M', u'ᄚ'), + (0xFFB1, 'M', u'ᄆ'), + (0xFFB2, 'M', u'ᄇ'), + (0xFFB3, 'M', u'ᄈ'), + (0xFFB4, 'M', u'ᄡ'), + (0xFFB5, 'M', u'ᄉ'), + (0xFFB6, 'M', u'ᄊ'), + (0xFFB7, 'M', u'ᄋ'), + (0xFFB8, 'M', u'ᄌ'), + (0xFFB9, 'M', u'ᄍ'), + (0xFFBA, 'M', u'ᄎ'), + (0xFFBB, 'M', u'ᄏ'), + (0xFFBC, 'M', u'ᄐ'), + (0xFFBD, 'M', u'ᄑ'), + (0xFFBE, 'M', u'ᄒ'), + (0xFFBF, 'X'), + (0xFFC2, 'M', u'ᅡ'), + (0xFFC3, 'M', u'ᅢ'), + (0xFFC4, 'M', u'ᅣ'), + (0xFFC5, 'M', u'ᅤ'), + (0xFFC6, 'M', u'ᅥ'), + (0xFFC7, 'M', u'ᅦ'), + (0xFFC8, 'X'), + (0xFFCA, 'M', u'ᅧ'), + (0xFFCB, 'M', u'ᅨ'), + (0xFFCC, 'M', u'ᅩ'), + (0xFFCD, 'M', u'ᅪ'), + (0xFFCE, 'M', u'ᅫ'), + (0xFFCF, 'M', u'ᅬ'), + (0xFFD0, 'X'), + (0xFFD2, 'M', u'ᅭ'), + (0xFFD3, 'M', u'ᅮ'), + (0xFFD4, 'M', u'ᅯ'), + (0xFFD5, 'M', u'ᅰ'), + (0xFFD6, 'M', u'ᅱ'), + (0xFFD7, 'M', u'ᅲ'), + (0xFFD8, 'X'), + (0xFFDA, 'M', u'ᅳ'), + (0xFFDB, 'M', u'ᅴ'), + (0xFFDC, 'M', u'ᅵ'), + (0xFFDD, 'X'), + (0xFFE0, 'M', u'¢'), + (0xFFE1, 'M', u'£'), + (0xFFE2, 'M', u'¬'), + (0xFFE3, '3', u' ̄'), + (0xFFE4, 'M', u'¦'), + (0xFFE5, 'M', u'¥'), + (0xFFE6, 'M', u'₩'), + (0xFFE7, 'X'), + (0xFFE8, 'M', u'│'), + (0xFFE9, 'M', u'←'), + (0xFFEA, 'M', u'↑'), + (0xFFEB, 'M', u'→'), + (0xFFEC, 'M', u'↓'), + (0xFFED, 'M', u'■'), + (0xFFEE, 'M', u'○'), + (0xFFEF, 'X'), + (0x10000, 'V'), + (0x1000C, 'X'), + (0x1000D, 'V'), + ] + +def _seg_53(): + return [ + (0x10027, 'X'), + (0x10028, 'V'), + (0x1003B, 'X'), + (0x1003C, 'V'), + (0x1003E, 'X'), + (0x1003F, 'V'), + (0x1004E, 'X'), + (0x10050, 'V'), + (0x1005E, 'X'), + (0x10080, 'V'), + (0x100FB, 'X'), + (0x10100, 'V'), + (0x10103, 'X'), + (0x10107, 'V'), + (0x10134, 'X'), + (0x10137, 'V'), + (0x1018F, 'X'), + (0x10190, 'V'), + (0x1019D, 'X'), + (0x101A0, 'V'), + (0x101A1, 'X'), + (0x101D0, 'V'), + (0x101FE, 'X'), + (0x10280, 'V'), + (0x1029D, 'X'), + (0x102A0, 'V'), + (0x102D1, 'X'), + (0x102E0, 'V'), + (0x102FC, 'X'), + (0x10300, 'V'), + (0x10324, 'X'), + (0x1032D, 'V'), + (0x1034B, 'X'), + (0x10350, 'V'), + (0x1037B, 'X'), + (0x10380, 'V'), + (0x1039E, 'X'), + (0x1039F, 'V'), + (0x103C4, 'X'), + (0x103C8, 'V'), + (0x103D6, 'X'), + (0x10400, 'M', u'𐐨'), + (0x10401, 'M', u'𐐩'), + (0x10402, 'M', u'𐐪'), + (0x10403, 'M', u'𐐫'), + (0x10404, 'M', u'𐐬'), + (0x10405, 'M', u'𐐭'), + (0x10406, 'M', u'𐐮'), + (0x10407, 'M', u'𐐯'), + (0x10408, 'M', u'𐐰'), + (0x10409, 'M', u'𐐱'), + (0x1040A, 'M', u'𐐲'), + (0x1040B, 'M', u'𐐳'), + (0x1040C, 'M', u'𐐴'), + (0x1040D, 'M', u'𐐵'), + (0x1040E, 'M', u'𐐶'), + (0x1040F, 'M', u'𐐷'), + (0x10410, 'M', u'𐐸'), + (0x10411, 'M', u'𐐹'), + (0x10412, 'M', u'𐐺'), + (0x10413, 'M', u'𐐻'), + (0x10414, 'M', u'𐐼'), + (0x10415, 'M', u'𐐽'), + (0x10416, 'M', u'𐐾'), + (0x10417, 'M', u'𐐿'), + (0x10418, 'M', u'𐑀'), + (0x10419, 'M', u'𐑁'), + (0x1041A, 'M', u'𐑂'), + (0x1041B, 'M', u'𐑃'), + (0x1041C, 'M', u'𐑄'), + (0x1041D, 'M', u'𐑅'), + (0x1041E, 'M', u'𐑆'), + (0x1041F, 'M', u'𐑇'), + (0x10420, 'M', u'𐑈'), + (0x10421, 'M', u'𐑉'), + (0x10422, 'M', u'𐑊'), + (0x10423, 'M', u'𐑋'), + (0x10424, 'M', u'𐑌'), + (0x10425, 'M', u'𐑍'), + (0x10426, 'M', u'𐑎'), + (0x10427, 'M', u'𐑏'), + (0x10428, 'V'), + (0x1049E, 'X'), + (0x104A0, 'V'), + (0x104AA, 'X'), + (0x104B0, 'M', u'𐓘'), + (0x104B1, 'M', u'𐓙'), + (0x104B2, 'M', u'𐓚'), + (0x104B3, 'M', u'𐓛'), + (0x104B4, 'M', u'𐓜'), + (0x104B5, 'M', u'𐓝'), + (0x104B6, 'M', u'𐓞'), + (0x104B7, 'M', u'𐓟'), + (0x104B8, 'M', u'𐓠'), + (0x104B9, 'M', u'𐓡'), + (0x104BA, 'M', u'𐓢'), + (0x104BB, 'M', u'𐓣'), + (0x104BC, 'M', u'𐓤'), + (0x104BD, 'M', u'𐓥'), + (0x104BE, 'M', u'𐓦'), + ] + +def _seg_54(): + return [ + (0x104BF, 'M', u'𐓧'), + (0x104C0, 'M', u'𐓨'), + (0x104C1, 'M', u'𐓩'), + (0x104C2, 'M', u'𐓪'), + (0x104C3, 'M', u'𐓫'), + (0x104C4, 'M', u'𐓬'), + (0x104C5, 'M', u'𐓭'), + (0x104C6, 'M', u'𐓮'), + (0x104C7, 'M', u'𐓯'), + (0x104C8, 'M', u'𐓰'), + (0x104C9, 'M', u'𐓱'), + (0x104CA, 'M', u'𐓲'), + (0x104CB, 'M', u'𐓳'), + (0x104CC, 'M', u'𐓴'), + (0x104CD, 'M', u'𐓵'), + (0x104CE, 'M', u'𐓶'), + (0x104CF, 'M', u'𐓷'), + (0x104D0, 'M', u'𐓸'), + (0x104D1, 'M', u'𐓹'), + (0x104D2, 'M', u'𐓺'), + (0x104D3, 'M', u'𐓻'), + (0x104D4, 'X'), + (0x104D8, 'V'), + (0x104FC, 'X'), + (0x10500, 'V'), + (0x10528, 'X'), + (0x10530, 'V'), + (0x10564, 'X'), + (0x1056F, 'V'), + (0x10570, 'X'), + (0x10600, 'V'), + (0x10737, 'X'), + (0x10740, 'V'), + (0x10756, 'X'), + (0x10760, 'V'), + (0x10768, 'X'), + (0x10800, 'V'), + (0x10806, 'X'), + (0x10808, 'V'), + (0x10809, 'X'), + (0x1080A, 'V'), + (0x10836, 'X'), + (0x10837, 'V'), + (0x10839, 'X'), + (0x1083C, 'V'), + (0x1083D, 'X'), + (0x1083F, 'V'), + (0x10856, 'X'), + (0x10857, 'V'), + (0x1089F, 'X'), + (0x108A7, 'V'), + (0x108B0, 'X'), + (0x108E0, 'V'), + (0x108F3, 'X'), + (0x108F4, 'V'), + (0x108F6, 'X'), + (0x108FB, 'V'), + (0x1091C, 'X'), + (0x1091F, 'V'), + (0x1093A, 'X'), + (0x1093F, 'V'), + (0x10940, 'X'), + (0x10980, 'V'), + (0x109B8, 'X'), + (0x109BC, 'V'), + (0x109D0, 'X'), + (0x109D2, 'V'), + (0x10A04, 'X'), + (0x10A05, 'V'), + (0x10A07, 'X'), + (0x10A0C, 'V'), + (0x10A14, 'X'), + (0x10A15, 'V'), + (0x10A18, 'X'), + (0x10A19, 'V'), + (0x10A36, 'X'), + (0x10A38, 'V'), + (0x10A3B, 'X'), + (0x10A3F, 'V'), + (0x10A49, 'X'), + (0x10A50, 'V'), + (0x10A59, 'X'), + (0x10A60, 'V'), + (0x10AA0, 'X'), + (0x10AC0, 'V'), + (0x10AE7, 'X'), + (0x10AEB, 'V'), + (0x10AF7, 'X'), + (0x10B00, 'V'), + (0x10B36, 'X'), + (0x10B39, 'V'), + (0x10B56, 'X'), + (0x10B58, 'V'), + (0x10B73, 'X'), + (0x10B78, 'V'), + (0x10B92, 'X'), + (0x10B99, 'V'), + (0x10B9D, 'X'), + (0x10BA9, 'V'), + (0x10BB0, 'X'), + ] + +def _seg_55(): + return [ + (0x10C00, 'V'), + (0x10C49, 'X'), + (0x10C80, 'M', u'𐳀'), + (0x10C81, 'M', u'𐳁'), + (0x10C82, 'M', u'𐳂'), + (0x10C83, 'M', u'𐳃'), + (0x10C84, 'M', u'𐳄'), + (0x10C85, 'M', u'𐳅'), + (0x10C86, 'M', u'𐳆'), + (0x10C87, 'M', u'𐳇'), + (0x10C88, 'M', u'𐳈'), + (0x10C89, 'M', u'𐳉'), + (0x10C8A, 'M', u'𐳊'), + (0x10C8B, 'M', u'𐳋'), + (0x10C8C, 'M', u'𐳌'), + (0x10C8D, 'M', u'𐳍'), + (0x10C8E, 'M', u'𐳎'), + (0x10C8F, 'M', u'𐳏'), + (0x10C90, 'M', u'𐳐'), + (0x10C91, 'M', u'𐳑'), + (0x10C92, 'M', u'𐳒'), + (0x10C93, 'M', u'𐳓'), + (0x10C94, 'M', u'𐳔'), + (0x10C95, 'M', u'𐳕'), + (0x10C96, 'M', u'𐳖'), + (0x10C97, 'M', u'𐳗'), + (0x10C98, 'M', u'𐳘'), + (0x10C99, 'M', u'𐳙'), + (0x10C9A, 'M', u'𐳚'), + (0x10C9B, 'M', u'𐳛'), + (0x10C9C, 'M', u'𐳜'), + (0x10C9D, 'M', u'𐳝'), + (0x10C9E, 'M', u'𐳞'), + (0x10C9F, 'M', u'𐳟'), + (0x10CA0, 'M', u'𐳠'), + (0x10CA1, 'M', u'𐳡'), + (0x10CA2, 'M', u'𐳢'), + (0x10CA3, 'M', u'𐳣'), + (0x10CA4, 'M', u'𐳤'), + (0x10CA5, 'M', u'𐳥'), + (0x10CA6, 'M', u'𐳦'), + (0x10CA7, 'M', u'𐳧'), + (0x10CA8, 'M', u'𐳨'), + (0x10CA9, 'M', u'𐳩'), + (0x10CAA, 'M', u'𐳪'), + (0x10CAB, 'M', u'𐳫'), + (0x10CAC, 'M', u'𐳬'), + (0x10CAD, 'M', u'𐳭'), + (0x10CAE, 'M', u'𐳮'), + (0x10CAF, 'M', u'𐳯'), + (0x10CB0, 'M', u'𐳰'), + (0x10CB1, 'M', u'𐳱'), + (0x10CB2, 'M', u'𐳲'), + (0x10CB3, 'X'), + (0x10CC0, 'V'), + (0x10CF3, 'X'), + (0x10CFA, 'V'), + (0x10D28, 'X'), + (0x10D30, 'V'), + (0x10D3A, 'X'), + (0x10E60, 'V'), + (0x10E7F, 'X'), + (0x10E80, 'V'), + (0x10EAA, 'X'), + (0x10EAB, 'V'), + (0x10EAE, 'X'), + (0x10EB0, 'V'), + (0x10EB2, 'X'), + (0x10F00, 'V'), + (0x10F28, 'X'), + (0x10F30, 'V'), + (0x10F5A, 'X'), + (0x10FB0, 'V'), + (0x10FCC, 'X'), + (0x10FE0, 'V'), + (0x10FF7, 'X'), + (0x11000, 'V'), + (0x1104E, 'X'), + (0x11052, 'V'), + (0x11070, 'X'), + (0x1107F, 'V'), + (0x110BD, 'X'), + (0x110BE, 'V'), + (0x110C2, 'X'), + (0x110D0, 'V'), + (0x110E9, 'X'), + (0x110F0, 'V'), + (0x110FA, 'X'), + (0x11100, 'V'), + (0x11135, 'X'), + (0x11136, 'V'), + (0x11148, 'X'), + (0x11150, 'V'), + (0x11177, 'X'), + (0x11180, 'V'), + (0x111E0, 'X'), + (0x111E1, 'V'), + (0x111F5, 'X'), + (0x11200, 'V'), + (0x11212, 'X'), + ] + +def _seg_56(): + return [ + (0x11213, 'V'), + (0x1123F, 'X'), + (0x11280, 'V'), + (0x11287, 'X'), + (0x11288, 'V'), + (0x11289, 'X'), + (0x1128A, 'V'), + (0x1128E, 'X'), + (0x1128F, 'V'), + (0x1129E, 'X'), + (0x1129F, 'V'), + (0x112AA, 'X'), + (0x112B0, 'V'), + (0x112EB, 'X'), + (0x112F0, 'V'), + (0x112FA, 'X'), + (0x11300, 'V'), + (0x11304, 'X'), + (0x11305, 'V'), + (0x1130D, 'X'), + (0x1130F, 'V'), + (0x11311, 'X'), + (0x11313, 'V'), + (0x11329, 'X'), + (0x1132A, 'V'), + (0x11331, 'X'), + (0x11332, 'V'), + (0x11334, 'X'), + (0x11335, 'V'), + (0x1133A, 'X'), + (0x1133B, 'V'), + (0x11345, 'X'), + (0x11347, 'V'), + (0x11349, 'X'), + (0x1134B, 'V'), + (0x1134E, 'X'), + (0x11350, 'V'), + (0x11351, 'X'), + (0x11357, 'V'), + (0x11358, 'X'), + (0x1135D, 'V'), + (0x11364, 'X'), + (0x11366, 'V'), + (0x1136D, 'X'), + (0x11370, 'V'), + (0x11375, 'X'), + (0x11400, 'V'), + (0x1145C, 'X'), + (0x1145D, 'V'), + (0x11462, 'X'), + (0x11480, 'V'), + (0x114C8, 'X'), + (0x114D0, 'V'), + (0x114DA, 'X'), + (0x11580, 'V'), + (0x115B6, 'X'), + (0x115B8, 'V'), + (0x115DE, 'X'), + (0x11600, 'V'), + (0x11645, 'X'), + (0x11650, 'V'), + (0x1165A, 'X'), + (0x11660, 'V'), + (0x1166D, 'X'), + (0x11680, 'V'), + (0x116B9, 'X'), + (0x116C0, 'V'), + (0x116CA, 'X'), + (0x11700, 'V'), + (0x1171B, 'X'), + (0x1171D, 'V'), + (0x1172C, 'X'), + (0x11730, 'V'), + (0x11740, 'X'), + (0x11800, 'V'), + (0x1183C, 'X'), + (0x118A0, 'M', u'𑣀'), + (0x118A1, 'M', u'𑣁'), + (0x118A2, 'M', u'𑣂'), + (0x118A3, 'M', u'𑣃'), + (0x118A4, 'M', u'𑣄'), + (0x118A5, 'M', u'𑣅'), + (0x118A6, 'M', u'𑣆'), + (0x118A7, 'M', u'𑣇'), + (0x118A8, 'M', u'𑣈'), + (0x118A9, 'M', u'𑣉'), + (0x118AA, 'M', u'𑣊'), + (0x118AB, 'M', u'𑣋'), + (0x118AC, 'M', u'𑣌'), + (0x118AD, 'M', u'𑣍'), + (0x118AE, 'M', u'𑣎'), + (0x118AF, 'M', u'𑣏'), + (0x118B0, 'M', u'𑣐'), + (0x118B1, 'M', u'𑣑'), + (0x118B2, 'M', u'𑣒'), + (0x118B3, 'M', u'𑣓'), + (0x118B4, 'M', u'𑣔'), + (0x118B5, 'M', u'𑣕'), + (0x118B6, 'M', u'𑣖'), + (0x118B7, 'M', u'𑣗'), + ] + +def _seg_57(): + return [ + (0x118B8, 'M', u'𑣘'), + (0x118B9, 'M', u'𑣙'), + (0x118BA, 'M', u'𑣚'), + (0x118BB, 'M', u'𑣛'), + (0x118BC, 'M', u'𑣜'), + (0x118BD, 'M', u'𑣝'), + (0x118BE, 'M', u'𑣞'), + (0x118BF, 'M', u'𑣟'), + (0x118C0, 'V'), + (0x118F3, 'X'), + (0x118FF, 'V'), + (0x11907, 'X'), + (0x11909, 'V'), + (0x1190A, 'X'), + (0x1190C, 'V'), + (0x11914, 'X'), + (0x11915, 'V'), + (0x11917, 'X'), + (0x11918, 'V'), + (0x11936, 'X'), + (0x11937, 'V'), + (0x11939, 'X'), + (0x1193B, 'V'), + (0x11947, 'X'), + (0x11950, 'V'), + (0x1195A, 'X'), + (0x119A0, 'V'), + (0x119A8, 'X'), + (0x119AA, 'V'), + (0x119D8, 'X'), + (0x119DA, 'V'), + (0x119E5, 'X'), + (0x11A00, 'V'), + (0x11A48, 'X'), + (0x11A50, 'V'), + (0x11AA3, 'X'), + (0x11AC0, 'V'), + (0x11AF9, 'X'), + (0x11C00, 'V'), + (0x11C09, 'X'), + (0x11C0A, 'V'), + (0x11C37, 'X'), + (0x11C38, 'V'), + (0x11C46, 'X'), + (0x11C50, 'V'), + (0x11C6D, 'X'), + (0x11C70, 'V'), + (0x11C90, 'X'), + (0x11C92, 'V'), + (0x11CA8, 'X'), + (0x11CA9, 'V'), + (0x11CB7, 'X'), + (0x11D00, 'V'), + (0x11D07, 'X'), + (0x11D08, 'V'), + (0x11D0A, 'X'), + (0x11D0B, 'V'), + (0x11D37, 'X'), + (0x11D3A, 'V'), + (0x11D3B, 'X'), + (0x11D3C, 'V'), + (0x11D3E, 'X'), + (0x11D3F, 'V'), + (0x11D48, 'X'), + (0x11D50, 'V'), + (0x11D5A, 'X'), + (0x11D60, 'V'), + (0x11D66, 'X'), + (0x11D67, 'V'), + (0x11D69, 'X'), + (0x11D6A, 'V'), + (0x11D8F, 'X'), + (0x11D90, 'V'), + (0x11D92, 'X'), + (0x11D93, 'V'), + (0x11D99, 'X'), + (0x11DA0, 'V'), + (0x11DAA, 'X'), + (0x11EE0, 'V'), + (0x11EF9, 'X'), + (0x11FB0, 'V'), + (0x11FB1, 'X'), + (0x11FC0, 'V'), + (0x11FF2, 'X'), + (0x11FFF, 'V'), + (0x1239A, 'X'), + (0x12400, 'V'), + (0x1246F, 'X'), + (0x12470, 'V'), + (0x12475, 'X'), + (0x12480, 'V'), + (0x12544, 'X'), + (0x13000, 'V'), + (0x1342F, 'X'), + (0x14400, 'V'), + (0x14647, 'X'), + (0x16800, 'V'), + (0x16A39, 'X'), + (0x16A40, 'V'), + (0x16A5F, 'X'), + ] + +def _seg_58(): + return [ + (0x16A60, 'V'), + (0x16A6A, 'X'), + (0x16A6E, 'V'), + (0x16A70, 'X'), + (0x16AD0, 'V'), + (0x16AEE, 'X'), + (0x16AF0, 'V'), + (0x16AF6, 'X'), + (0x16B00, 'V'), + (0x16B46, 'X'), + (0x16B50, 'V'), + (0x16B5A, 'X'), + (0x16B5B, 'V'), + (0x16B62, 'X'), + (0x16B63, 'V'), + (0x16B78, 'X'), + (0x16B7D, 'V'), + (0x16B90, 'X'), + (0x16E40, 'M', u'𖹠'), + (0x16E41, 'M', u'𖹡'), + (0x16E42, 'M', u'𖹢'), + (0x16E43, 'M', u'𖹣'), + (0x16E44, 'M', u'𖹤'), + (0x16E45, 'M', u'𖹥'), + (0x16E46, 'M', u'𖹦'), + (0x16E47, 'M', u'𖹧'), + (0x16E48, 'M', u'𖹨'), + (0x16E49, 'M', u'𖹩'), + (0x16E4A, 'M', u'𖹪'), + (0x16E4B, 'M', u'𖹫'), + (0x16E4C, 'M', u'𖹬'), + (0x16E4D, 'M', u'𖹭'), + (0x16E4E, 'M', u'𖹮'), + (0x16E4F, 'M', u'𖹯'), + (0x16E50, 'M', u'𖹰'), + (0x16E51, 'M', u'𖹱'), + (0x16E52, 'M', u'𖹲'), + (0x16E53, 'M', u'𖹳'), + (0x16E54, 'M', u'𖹴'), + (0x16E55, 'M', u'𖹵'), + (0x16E56, 'M', u'𖹶'), + (0x16E57, 'M', u'𖹷'), + (0x16E58, 'M', u'𖹸'), + (0x16E59, 'M', u'𖹹'), + (0x16E5A, 'M', u'𖹺'), + (0x16E5B, 'M', u'𖹻'), + (0x16E5C, 'M', u'𖹼'), + (0x16E5D, 'M', u'𖹽'), + (0x16E5E, 'M', u'𖹾'), + (0x16E5F, 'M', u'𖹿'), + (0x16E60, 'V'), + (0x16E9B, 'X'), + (0x16F00, 'V'), + (0x16F4B, 'X'), + (0x16F4F, 'V'), + (0x16F88, 'X'), + (0x16F8F, 'V'), + (0x16FA0, 'X'), + (0x16FE0, 'V'), + (0x16FE5, 'X'), + (0x16FF0, 'V'), + (0x16FF2, 'X'), + (0x17000, 'V'), + (0x187F8, 'X'), + (0x18800, 'V'), + (0x18CD6, 'X'), + (0x18D00, 'V'), + (0x18D09, 'X'), + (0x1B000, 'V'), + (0x1B11F, 'X'), + (0x1B150, 'V'), + (0x1B153, 'X'), + (0x1B164, 'V'), + (0x1B168, 'X'), + (0x1B170, 'V'), + (0x1B2FC, 'X'), + (0x1BC00, 'V'), + (0x1BC6B, 'X'), + (0x1BC70, 'V'), + (0x1BC7D, 'X'), + (0x1BC80, 'V'), + (0x1BC89, 'X'), + (0x1BC90, 'V'), + (0x1BC9A, 'X'), + (0x1BC9C, 'V'), + (0x1BCA0, 'I'), + (0x1BCA4, 'X'), + (0x1D000, 'V'), + (0x1D0F6, 'X'), + (0x1D100, 'V'), + (0x1D127, 'X'), + (0x1D129, 'V'), + (0x1D15E, 'M', u'𝅗𝅥'), + (0x1D15F, 'M', u'𝅘𝅥'), + (0x1D160, 'M', u'𝅘𝅥𝅮'), + (0x1D161, 'M', u'𝅘𝅥𝅯'), + (0x1D162, 'M', u'𝅘𝅥𝅰'), + (0x1D163, 'M', u'𝅘𝅥𝅱'), + (0x1D164, 'M', u'𝅘𝅥𝅲'), + (0x1D165, 'V'), + ] + +def _seg_59(): + return [ + (0x1D173, 'X'), + (0x1D17B, 'V'), + (0x1D1BB, 'M', u'𝆹𝅥'), + (0x1D1BC, 'M', u'𝆺𝅥'), + (0x1D1BD, 'M', u'𝆹𝅥𝅮'), + (0x1D1BE, 'M', u'𝆺𝅥𝅮'), + (0x1D1BF, 'M', u'𝆹𝅥𝅯'), + (0x1D1C0, 'M', u'𝆺𝅥𝅯'), + (0x1D1C1, 'V'), + (0x1D1E9, 'X'), + (0x1D200, 'V'), + (0x1D246, 'X'), + (0x1D2E0, 'V'), + (0x1D2F4, 'X'), + (0x1D300, 'V'), + (0x1D357, 'X'), + (0x1D360, 'V'), + (0x1D379, 'X'), + (0x1D400, 'M', u'a'), + (0x1D401, 'M', u'b'), + (0x1D402, 'M', u'c'), + (0x1D403, 'M', u'd'), + (0x1D404, 'M', u'e'), + (0x1D405, 'M', u'f'), + (0x1D406, 'M', u'g'), + (0x1D407, 'M', u'h'), + (0x1D408, 'M', u'i'), + (0x1D409, 'M', u'j'), + (0x1D40A, 'M', u'k'), + (0x1D40B, 'M', u'l'), + (0x1D40C, 'M', u'm'), + (0x1D40D, 'M', u'n'), + (0x1D40E, 'M', u'o'), + (0x1D40F, 'M', u'p'), + (0x1D410, 'M', u'q'), + (0x1D411, 'M', u'r'), + (0x1D412, 'M', u's'), + (0x1D413, 'M', u't'), + (0x1D414, 'M', u'u'), + (0x1D415, 'M', u'v'), + (0x1D416, 'M', u'w'), + (0x1D417, 'M', u'x'), + (0x1D418, 'M', u'y'), + (0x1D419, 'M', u'z'), + (0x1D41A, 'M', u'a'), + (0x1D41B, 'M', u'b'), + (0x1D41C, 'M', u'c'), + (0x1D41D, 'M', u'd'), + (0x1D41E, 'M', u'e'), + (0x1D41F, 'M', u'f'), + (0x1D420, 'M', u'g'), + (0x1D421, 'M', u'h'), + (0x1D422, 'M', u'i'), + (0x1D423, 'M', u'j'), + (0x1D424, 'M', u'k'), + (0x1D425, 'M', u'l'), + (0x1D426, 'M', u'm'), + (0x1D427, 'M', u'n'), + (0x1D428, 'M', u'o'), + (0x1D429, 'M', u'p'), + (0x1D42A, 'M', u'q'), + (0x1D42B, 'M', u'r'), + (0x1D42C, 'M', u's'), + (0x1D42D, 'M', u't'), + (0x1D42E, 'M', u'u'), + (0x1D42F, 'M', u'v'), + (0x1D430, 'M', u'w'), + (0x1D431, 'M', u'x'), + (0x1D432, 'M', u'y'), + (0x1D433, 'M', u'z'), + (0x1D434, 'M', u'a'), + (0x1D435, 'M', u'b'), + (0x1D436, 'M', u'c'), + (0x1D437, 'M', u'd'), + (0x1D438, 'M', u'e'), + (0x1D439, 'M', u'f'), + (0x1D43A, 'M', u'g'), + (0x1D43B, 'M', u'h'), + (0x1D43C, 'M', u'i'), + (0x1D43D, 'M', u'j'), + (0x1D43E, 'M', u'k'), + (0x1D43F, 'M', u'l'), + (0x1D440, 'M', u'm'), + (0x1D441, 'M', u'n'), + (0x1D442, 'M', u'o'), + (0x1D443, 'M', u'p'), + (0x1D444, 'M', u'q'), + (0x1D445, 'M', u'r'), + (0x1D446, 'M', u's'), + (0x1D447, 'M', u't'), + (0x1D448, 'M', u'u'), + (0x1D449, 'M', u'v'), + (0x1D44A, 'M', u'w'), + (0x1D44B, 'M', u'x'), + (0x1D44C, 'M', u'y'), + (0x1D44D, 'M', u'z'), + (0x1D44E, 'M', u'a'), + (0x1D44F, 'M', u'b'), + (0x1D450, 'M', u'c'), + (0x1D451, 'M', u'd'), + ] + +def _seg_60(): + return [ + (0x1D452, 'M', u'e'), + (0x1D453, 'M', u'f'), + (0x1D454, 'M', u'g'), + (0x1D455, 'X'), + (0x1D456, 'M', u'i'), + (0x1D457, 'M', u'j'), + (0x1D458, 'M', u'k'), + (0x1D459, 'M', u'l'), + (0x1D45A, 'M', u'm'), + (0x1D45B, 'M', u'n'), + (0x1D45C, 'M', u'o'), + (0x1D45D, 'M', u'p'), + (0x1D45E, 'M', u'q'), + (0x1D45F, 'M', u'r'), + (0x1D460, 'M', u's'), + (0x1D461, 'M', u't'), + (0x1D462, 'M', u'u'), + (0x1D463, 'M', u'v'), + (0x1D464, 'M', u'w'), + (0x1D465, 'M', u'x'), + (0x1D466, 'M', u'y'), + (0x1D467, 'M', u'z'), + (0x1D468, 'M', u'a'), + (0x1D469, 'M', u'b'), + (0x1D46A, 'M', u'c'), + (0x1D46B, 'M', u'd'), + (0x1D46C, 'M', u'e'), + (0x1D46D, 'M', u'f'), + (0x1D46E, 'M', u'g'), + (0x1D46F, 'M', u'h'), + (0x1D470, 'M', u'i'), + (0x1D471, 'M', u'j'), + (0x1D472, 'M', u'k'), + (0x1D473, 'M', u'l'), + (0x1D474, 'M', u'm'), + (0x1D475, 'M', u'n'), + (0x1D476, 'M', u'o'), + (0x1D477, 'M', u'p'), + (0x1D478, 'M', u'q'), + (0x1D479, 'M', u'r'), + (0x1D47A, 'M', u's'), + (0x1D47B, 'M', u't'), + (0x1D47C, 'M', u'u'), + (0x1D47D, 'M', u'v'), + (0x1D47E, 'M', u'w'), + (0x1D47F, 'M', u'x'), + (0x1D480, 'M', u'y'), + (0x1D481, 'M', u'z'), + (0x1D482, 'M', u'a'), + (0x1D483, 'M', u'b'), + (0x1D484, 'M', u'c'), + (0x1D485, 'M', u'd'), + (0x1D486, 'M', u'e'), + (0x1D487, 'M', u'f'), + (0x1D488, 'M', u'g'), + (0x1D489, 'M', u'h'), + (0x1D48A, 'M', u'i'), + (0x1D48B, 'M', u'j'), + (0x1D48C, 'M', u'k'), + (0x1D48D, 'M', u'l'), + (0x1D48E, 'M', u'm'), + (0x1D48F, 'M', u'n'), + (0x1D490, 'M', u'o'), + (0x1D491, 'M', u'p'), + (0x1D492, 'M', u'q'), + (0x1D493, 'M', u'r'), + (0x1D494, 'M', u's'), + (0x1D495, 'M', u't'), + (0x1D496, 'M', u'u'), + (0x1D497, 'M', u'v'), + (0x1D498, 'M', u'w'), + (0x1D499, 'M', u'x'), + (0x1D49A, 'M', u'y'), + (0x1D49B, 'M', u'z'), + (0x1D49C, 'M', u'a'), + (0x1D49D, 'X'), + (0x1D49E, 'M', u'c'), + (0x1D49F, 'M', u'd'), + (0x1D4A0, 'X'), + (0x1D4A2, 'M', u'g'), + (0x1D4A3, 'X'), + (0x1D4A5, 'M', u'j'), + (0x1D4A6, 'M', u'k'), + (0x1D4A7, 'X'), + (0x1D4A9, 'M', u'n'), + (0x1D4AA, 'M', u'o'), + (0x1D4AB, 'M', u'p'), + (0x1D4AC, 'M', u'q'), + (0x1D4AD, 'X'), + (0x1D4AE, 'M', u's'), + (0x1D4AF, 'M', u't'), + (0x1D4B0, 'M', u'u'), + (0x1D4B1, 'M', u'v'), + (0x1D4B2, 'M', u'w'), + (0x1D4B3, 'M', u'x'), + (0x1D4B4, 'M', u'y'), + (0x1D4B5, 'M', u'z'), + (0x1D4B6, 'M', u'a'), + (0x1D4B7, 'M', u'b'), + (0x1D4B8, 'M', u'c'), + ] + +def _seg_61(): + return [ + (0x1D4B9, 'M', u'd'), + (0x1D4BA, 'X'), + (0x1D4BB, 'M', u'f'), + (0x1D4BC, 'X'), + (0x1D4BD, 'M', u'h'), + (0x1D4BE, 'M', u'i'), + (0x1D4BF, 'M', u'j'), + (0x1D4C0, 'M', u'k'), + (0x1D4C1, 'M', u'l'), + (0x1D4C2, 'M', u'm'), + (0x1D4C3, 'M', u'n'), + (0x1D4C4, 'X'), + (0x1D4C5, 'M', u'p'), + (0x1D4C6, 'M', u'q'), + (0x1D4C7, 'M', u'r'), + (0x1D4C8, 'M', u's'), + (0x1D4C9, 'M', u't'), + (0x1D4CA, 'M', u'u'), + (0x1D4CB, 'M', u'v'), + (0x1D4CC, 'M', u'w'), + (0x1D4CD, 'M', u'x'), + (0x1D4CE, 'M', u'y'), + (0x1D4CF, 'M', u'z'), + (0x1D4D0, 'M', u'a'), + (0x1D4D1, 'M', u'b'), + (0x1D4D2, 'M', u'c'), + (0x1D4D3, 'M', u'd'), + (0x1D4D4, 'M', u'e'), + (0x1D4D5, 'M', u'f'), + (0x1D4D6, 'M', u'g'), + (0x1D4D7, 'M', u'h'), + (0x1D4D8, 'M', u'i'), + (0x1D4D9, 'M', u'j'), + (0x1D4DA, 'M', u'k'), + (0x1D4DB, 'M', u'l'), + (0x1D4DC, 'M', u'm'), + (0x1D4DD, 'M', u'n'), + (0x1D4DE, 'M', u'o'), + (0x1D4DF, 'M', u'p'), + (0x1D4E0, 'M', u'q'), + (0x1D4E1, 'M', u'r'), + (0x1D4E2, 'M', u's'), + (0x1D4E3, 'M', u't'), + (0x1D4E4, 'M', u'u'), + (0x1D4E5, 'M', u'v'), + (0x1D4E6, 'M', u'w'), + (0x1D4E7, 'M', u'x'), + (0x1D4E8, 'M', u'y'), + (0x1D4E9, 'M', u'z'), + (0x1D4EA, 'M', u'a'), + (0x1D4EB, 'M', u'b'), + (0x1D4EC, 'M', u'c'), + (0x1D4ED, 'M', u'd'), + (0x1D4EE, 'M', u'e'), + (0x1D4EF, 'M', u'f'), + (0x1D4F0, 'M', u'g'), + (0x1D4F1, 'M', u'h'), + (0x1D4F2, 'M', u'i'), + (0x1D4F3, 'M', u'j'), + (0x1D4F4, 'M', u'k'), + (0x1D4F5, 'M', u'l'), + (0x1D4F6, 'M', u'm'), + (0x1D4F7, 'M', u'n'), + (0x1D4F8, 'M', u'o'), + (0x1D4F9, 'M', u'p'), + (0x1D4FA, 'M', u'q'), + (0x1D4FB, 'M', u'r'), + (0x1D4FC, 'M', u's'), + (0x1D4FD, 'M', u't'), + (0x1D4FE, 'M', u'u'), + (0x1D4FF, 'M', u'v'), + (0x1D500, 'M', u'w'), + (0x1D501, 'M', u'x'), + (0x1D502, 'M', u'y'), + (0x1D503, 'M', u'z'), + (0x1D504, 'M', u'a'), + (0x1D505, 'M', u'b'), + (0x1D506, 'X'), + (0x1D507, 'M', u'd'), + (0x1D508, 'M', u'e'), + (0x1D509, 'M', u'f'), + (0x1D50A, 'M', u'g'), + (0x1D50B, 'X'), + (0x1D50D, 'M', u'j'), + (0x1D50E, 'M', u'k'), + (0x1D50F, 'M', u'l'), + (0x1D510, 'M', u'm'), + (0x1D511, 'M', u'n'), + (0x1D512, 'M', u'o'), + (0x1D513, 'M', u'p'), + (0x1D514, 'M', u'q'), + (0x1D515, 'X'), + (0x1D516, 'M', u's'), + (0x1D517, 'M', u't'), + (0x1D518, 'M', u'u'), + (0x1D519, 'M', u'v'), + (0x1D51A, 'M', u'w'), + (0x1D51B, 'M', u'x'), + (0x1D51C, 'M', u'y'), + (0x1D51D, 'X'), + ] + +def _seg_62(): + return [ + (0x1D51E, 'M', u'a'), + (0x1D51F, 'M', u'b'), + (0x1D520, 'M', u'c'), + (0x1D521, 'M', u'd'), + (0x1D522, 'M', u'e'), + (0x1D523, 'M', u'f'), + (0x1D524, 'M', u'g'), + (0x1D525, 'M', u'h'), + (0x1D526, 'M', u'i'), + (0x1D527, 'M', u'j'), + (0x1D528, 'M', u'k'), + (0x1D529, 'M', u'l'), + (0x1D52A, 'M', u'm'), + (0x1D52B, 'M', u'n'), + (0x1D52C, 'M', u'o'), + (0x1D52D, 'M', u'p'), + (0x1D52E, 'M', u'q'), + (0x1D52F, 'M', u'r'), + (0x1D530, 'M', u's'), + (0x1D531, 'M', u't'), + (0x1D532, 'M', u'u'), + (0x1D533, 'M', u'v'), + (0x1D534, 'M', u'w'), + (0x1D535, 'M', u'x'), + (0x1D536, 'M', u'y'), + (0x1D537, 'M', u'z'), + (0x1D538, 'M', u'a'), + (0x1D539, 'M', u'b'), + (0x1D53A, 'X'), + (0x1D53B, 'M', u'd'), + (0x1D53C, 'M', u'e'), + (0x1D53D, 'M', u'f'), + (0x1D53E, 'M', u'g'), + (0x1D53F, 'X'), + (0x1D540, 'M', u'i'), + (0x1D541, 'M', u'j'), + (0x1D542, 'M', u'k'), + (0x1D543, 'M', u'l'), + (0x1D544, 'M', u'm'), + (0x1D545, 'X'), + (0x1D546, 'M', u'o'), + (0x1D547, 'X'), + (0x1D54A, 'M', u's'), + (0x1D54B, 'M', u't'), + (0x1D54C, 'M', u'u'), + (0x1D54D, 'M', u'v'), + (0x1D54E, 'M', u'w'), + (0x1D54F, 'M', u'x'), + (0x1D550, 'M', u'y'), + (0x1D551, 'X'), + (0x1D552, 'M', u'a'), + (0x1D553, 'M', u'b'), + (0x1D554, 'M', u'c'), + (0x1D555, 'M', u'd'), + (0x1D556, 'M', u'e'), + (0x1D557, 'M', u'f'), + (0x1D558, 'M', u'g'), + (0x1D559, 'M', u'h'), + (0x1D55A, 'M', u'i'), + (0x1D55B, 'M', u'j'), + (0x1D55C, 'M', u'k'), + (0x1D55D, 'M', u'l'), + (0x1D55E, 'M', u'm'), + (0x1D55F, 'M', u'n'), + (0x1D560, 'M', u'o'), + (0x1D561, 'M', u'p'), + (0x1D562, 'M', u'q'), + (0x1D563, 'M', u'r'), + (0x1D564, 'M', u's'), + (0x1D565, 'M', u't'), + (0x1D566, 'M', u'u'), + (0x1D567, 'M', u'v'), + (0x1D568, 'M', u'w'), + (0x1D569, 'M', u'x'), + (0x1D56A, 'M', u'y'), + (0x1D56B, 'M', u'z'), + (0x1D56C, 'M', u'a'), + (0x1D56D, 'M', u'b'), + (0x1D56E, 'M', u'c'), + (0x1D56F, 'M', u'd'), + (0x1D570, 'M', u'e'), + (0x1D571, 'M', u'f'), + (0x1D572, 'M', u'g'), + (0x1D573, 'M', u'h'), + (0x1D574, 'M', u'i'), + (0x1D575, 'M', u'j'), + (0x1D576, 'M', u'k'), + (0x1D577, 'M', u'l'), + (0x1D578, 'M', u'm'), + (0x1D579, 'M', u'n'), + (0x1D57A, 'M', u'o'), + (0x1D57B, 'M', u'p'), + (0x1D57C, 'M', u'q'), + (0x1D57D, 'M', u'r'), + (0x1D57E, 'M', u's'), + (0x1D57F, 'M', u't'), + (0x1D580, 'M', u'u'), + (0x1D581, 'M', u'v'), + (0x1D582, 'M', u'w'), + (0x1D583, 'M', u'x'), + ] + +def _seg_63(): + return [ + (0x1D584, 'M', u'y'), + (0x1D585, 'M', u'z'), + (0x1D586, 'M', u'a'), + (0x1D587, 'M', u'b'), + (0x1D588, 'M', u'c'), + (0x1D589, 'M', u'd'), + (0x1D58A, 'M', u'e'), + (0x1D58B, 'M', u'f'), + (0x1D58C, 'M', u'g'), + (0x1D58D, 'M', u'h'), + (0x1D58E, 'M', u'i'), + (0x1D58F, 'M', u'j'), + (0x1D590, 'M', u'k'), + (0x1D591, 'M', u'l'), + (0x1D592, 'M', u'm'), + (0x1D593, 'M', u'n'), + (0x1D594, 'M', u'o'), + (0x1D595, 'M', u'p'), + (0x1D596, 'M', u'q'), + (0x1D597, 'M', u'r'), + (0x1D598, 'M', u's'), + (0x1D599, 'M', u't'), + (0x1D59A, 'M', u'u'), + (0x1D59B, 'M', u'v'), + (0x1D59C, 'M', u'w'), + (0x1D59D, 'M', u'x'), + (0x1D59E, 'M', u'y'), + (0x1D59F, 'M', u'z'), + (0x1D5A0, 'M', u'a'), + (0x1D5A1, 'M', u'b'), + (0x1D5A2, 'M', u'c'), + (0x1D5A3, 'M', u'd'), + (0x1D5A4, 'M', u'e'), + (0x1D5A5, 'M', u'f'), + (0x1D5A6, 'M', u'g'), + (0x1D5A7, 'M', u'h'), + (0x1D5A8, 'M', u'i'), + (0x1D5A9, 'M', u'j'), + (0x1D5AA, 'M', u'k'), + (0x1D5AB, 'M', u'l'), + (0x1D5AC, 'M', u'm'), + (0x1D5AD, 'M', u'n'), + (0x1D5AE, 'M', u'o'), + (0x1D5AF, 'M', u'p'), + (0x1D5B0, 'M', u'q'), + (0x1D5B1, 'M', u'r'), + (0x1D5B2, 'M', u's'), + (0x1D5B3, 'M', u't'), + (0x1D5B4, 'M', u'u'), + (0x1D5B5, 'M', u'v'), + (0x1D5B6, 'M', u'w'), + (0x1D5B7, 'M', u'x'), + (0x1D5B8, 'M', u'y'), + (0x1D5B9, 'M', u'z'), + (0x1D5BA, 'M', u'a'), + (0x1D5BB, 'M', u'b'), + (0x1D5BC, 'M', u'c'), + (0x1D5BD, 'M', u'd'), + (0x1D5BE, 'M', u'e'), + (0x1D5BF, 'M', u'f'), + (0x1D5C0, 'M', u'g'), + (0x1D5C1, 'M', u'h'), + (0x1D5C2, 'M', u'i'), + (0x1D5C3, 'M', u'j'), + (0x1D5C4, 'M', u'k'), + (0x1D5C5, 'M', u'l'), + (0x1D5C6, 'M', u'm'), + (0x1D5C7, 'M', u'n'), + (0x1D5C8, 'M', u'o'), + (0x1D5C9, 'M', u'p'), + (0x1D5CA, 'M', u'q'), + (0x1D5CB, 'M', u'r'), + (0x1D5CC, 'M', u's'), + (0x1D5CD, 'M', u't'), + (0x1D5CE, 'M', u'u'), + (0x1D5CF, 'M', u'v'), + (0x1D5D0, 'M', u'w'), + (0x1D5D1, 'M', u'x'), + (0x1D5D2, 'M', u'y'), + (0x1D5D3, 'M', u'z'), + (0x1D5D4, 'M', u'a'), + (0x1D5D5, 'M', u'b'), + (0x1D5D6, 'M', u'c'), + (0x1D5D7, 'M', u'd'), + (0x1D5D8, 'M', u'e'), + (0x1D5D9, 'M', u'f'), + (0x1D5DA, 'M', u'g'), + (0x1D5DB, 'M', u'h'), + (0x1D5DC, 'M', u'i'), + (0x1D5DD, 'M', u'j'), + (0x1D5DE, 'M', u'k'), + (0x1D5DF, 'M', u'l'), + (0x1D5E0, 'M', u'm'), + (0x1D5E1, 'M', u'n'), + (0x1D5E2, 'M', u'o'), + (0x1D5E3, 'M', u'p'), + (0x1D5E4, 'M', u'q'), + (0x1D5E5, 'M', u'r'), + (0x1D5E6, 'M', u's'), + (0x1D5E7, 'M', u't'), + ] + +def _seg_64(): + return [ + (0x1D5E8, 'M', u'u'), + (0x1D5E9, 'M', u'v'), + (0x1D5EA, 'M', u'w'), + (0x1D5EB, 'M', u'x'), + (0x1D5EC, 'M', u'y'), + (0x1D5ED, 'M', u'z'), + (0x1D5EE, 'M', u'a'), + (0x1D5EF, 'M', u'b'), + (0x1D5F0, 'M', u'c'), + (0x1D5F1, 'M', u'd'), + (0x1D5F2, 'M', u'e'), + (0x1D5F3, 'M', u'f'), + (0x1D5F4, 'M', u'g'), + (0x1D5F5, 'M', u'h'), + (0x1D5F6, 'M', u'i'), + (0x1D5F7, 'M', u'j'), + (0x1D5F8, 'M', u'k'), + (0x1D5F9, 'M', u'l'), + (0x1D5FA, 'M', u'm'), + (0x1D5FB, 'M', u'n'), + (0x1D5FC, 'M', u'o'), + (0x1D5FD, 'M', u'p'), + (0x1D5FE, 'M', u'q'), + (0x1D5FF, 'M', u'r'), + (0x1D600, 'M', u's'), + (0x1D601, 'M', u't'), + (0x1D602, 'M', u'u'), + (0x1D603, 'M', u'v'), + (0x1D604, 'M', u'w'), + (0x1D605, 'M', u'x'), + (0x1D606, 'M', u'y'), + (0x1D607, 'M', u'z'), + (0x1D608, 'M', u'a'), + (0x1D609, 'M', u'b'), + (0x1D60A, 'M', u'c'), + (0x1D60B, 'M', u'd'), + (0x1D60C, 'M', u'e'), + (0x1D60D, 'M', u'f'), + (0x1D60E, 'M', u'g'), + (0x1D60F, 'M', u'h'), + (0x1D610, 'M', u'i'), + (0x1D611, 'M', u'j'), + (0x1D612, 'M', u'k'), + (0x1D613, 'M', u'l'), + (0x1D614, 'M', u'm'), + (0x1D615, 'M', u'n'), + (0x1D616, 'M', u'o'), + (0x1D617, 'M', u'p'), + (0x1D618, 'M', u'q'), + (0x1D619, 'M', u'r'), + (0x1D61A, 'M', u's'), + (0x1D61B, 'M', u't'), + (0x1D61C, 'M', u'u'), + (0x1D61D, 'M', u'v'), + (0x1D61E, 'M', u'w'), + (0x1D61F, 'M', u'x'), + (0x1D620, 'M', u'y'), + (0x1D621, 'M', u'z'), + (0x1D622, 'M', u'a'), + (0x1D623, 'M', u'b'), + (0x1D624, 'M', u'c'), + (0x1D625, 'M', u'd'), + (0x1D626, 'M', u'e'), + (0x1D627, 'M', u'f'), + (0x1D628, 'M', u'g'), + (0x1D629, 'M', u'h'), + (0x1D62A, 'M', u'i'), + (0x1D62B, 'M', u'j'), + (0x1D62C, 'M', u'k'), + (0x1D62D, 'M', u'l'), + (0x1D62E, 'M', u'm'), + (0x1D62F, 'M', u'n'), + (0x1D630, 'M', u'o'), + (0x1D631, 'M', u'p'), + (0x1D632, 'M', u'q'), + (0x1D633, 'M', u'r'), + (0x1D634, 'M', u's'), + (0x1D635, 'M', u't'), + (0x1D636, 'M', u'u'), + (0x1D637, 'M', u'v'), + (0x1D638, 'M', u'w'), + (0x1D639, 'M', u'x'), + (0x1D63A, 'M', u'y'), + (0x1D63B, 'M', u'z'), + (0x1D63C, 'M', u'a'), + (0x1D63D, 'M', u'b'), + (0x1D63E, 'M', u'c'), + (0x1D63F, 'M', u'd'), + (0x1D640, 'M', u'e'), + (0x1D641, 'M', u'f'), + (0x1D642, 'M', u'g'), + (0x1D643, 'M', u'h'), + (0x1D644, 'M', u'i'), + (0x1D645, 'M', u'j'), + (0x1D646, 'M', u'k'), + (0x1D647, 'M', u'l'), + (0x1D648, 'M', u'm'), + (0x1D649, 'M', u'n'), + (0x1D64A, 'M', u'o'), + (0x1D64B, 'M', u'p'), + ] + +def _seg_65(): + return [ + (0x1D64C, 'M', u'q'), + (0x1D64D, 'M', u'r'), + (0x1D64E, 'M', u's'), + (0x1D64F, 'M', u't'), + (0x1D650, 'M', u'u'), + (0x1D651, 'M', u'v'), + (0x1D652, 'M', u'w'), + (0x1D653, 'M', u'x'), + (0x1D654, 'M', u'y'), + (0x1D655, 'M', u'z'), + (0x1D656, 'M', u'a'), + (0x1D657, 'M', u'b'), + (0x1D658, 'M', u'c'), + (0x1D659, 'M', u'd'), + (0x1D65A, 'M', u'e'), + (0x1D65B, 'M', u'f'), + (0x1D65C, 'M', u'g'), + (0x1D65D, 'M', u'h'), + (0x1D65E, 'M', u'i'), + (0x1D65F, 'M', u'j'), + (0x1D660, 'M', u'k'), + (0x1D661, 'M', u'l'), + (0x1D662, 'M', u'm'), + (0x1D663, 'M', u'n'), + (0x1D664, 'M', u'o'), + (0x1D665, 'M', u'p'), + (0x1D666, 'M', u'q'), + (0x1D667, 'M', u'r'), + (0x1D668, 'M', u's'), + (0x1D669, 'M', u't'), + (0x1D66A, 'M', u'u'), + (0x1D66B, 'M', u'v'), + (0x1D66C, 'M', u'w'), + (0x1D66D, 'M', u'x'), + (0x1D66E, 'M', u'y'), + (0x1D66F, 'M', u'z'), + (0x1D670, 'M', u'a'), + (0x1D671, 'M', u'b'), + (0x1D672, 'M', u'c'), + (0x1D673, 'M', u'd'), + (0x1D674, 'M', u'e'), + (0x1D675, 'M', u'f'), + (0x1D676, 'M', u'g'), + (0x1D677, 'M', u'h'), + (0x1D678, 'M', u'i'), + (0x1D679, 'M', u'j'), + (0x1D67A, 'M', u'k'), + (0x1D67B, 'M', u'l'), + (0x1D67C, 'M', u'm'), + (0x1D67D, 'M', u'n'), + (0x1D67E, 'M', u'o'), + (0x1D67F, 'M', u'p'), + (0x1D680, 'M', u'q'), + (0x1D681, 'M', u'r'), + (0x1D682, 'M', u's'), + (0x1D683, 'M', u't'), + (0x1D684, 'M', u'u'), + (0x1D685, 'M', u'v'), + (0x1D686, 'M', u'w'), + (0x1D687, 'M', u'x'), + (0x1D688, 'M', u'y'), + (0x1D689, 'M', u'z'), + (0x1D68A, 'M', u'a'), + (0x1D68B, 'M', u'b'), + (0x1D68C, 'M', u'c'), + (0x1D68D, 'M', u'd'), + (0x1D68E, 'M', u'e'), + (0x1D68F, 'M', u'f'), + (0x1D690, 'M', u'g'), + (0x1D691, 'M', u'h'), + (0x1D692, 'M', u'i'), + (0x1D693, 'M', u'j'), + (0x1D694, 'M', u'k'), + (0x1D695, 'M', u'l'), + (0x1D696, 'M', u'm'), + (0x1D697, 'M', u'n'), + (0x1D698, 'M', u'o'), + (0x1D699, 'M', u'p'), + (0x1D69A, 'M', u'q'), + (0x1D69B, 'M', u'r'), + (0x1D69C, 'M', u's'), + (0x1D69D, 'M', u't'), + (0x1D69E, 'M', u'u'), + (0x1D69F, 'M', u'v'), + (0x1D6A0, 'M', u'w'), + (0x1D6A1, 'M', u'x'), + (0x1D6A2, 'M', u'y'), + (0x1D6A3, 'M', u'z'), + (0x1D6A4, 'M', u'ı'), + (0x1D6A5, 'M', u'ȷ'), + (0x1D6A6, 'X'), + (0x1D6A8, 'M', u'α'), + (0x1D6A9, 'M', u'β'), + (0x1D6AA, 'M', u'γ'), + (0x1D6AB, 'M', u'δ'), + (0x1D6AC, 'M', u'ε'), + (0x1D6AD, 'M', u'ζ'), + (0x1D6AE, 'M', u'η'), + (0x1D6AF, 'M', u'θ'), + (0x1D6B0, 'M', u'ι'), + ] + +def _seg_66(): + return [ + (0x1D6B1, 'M', u'κ'), + (0x1D6B2, 'M', u'λ'), + (0x1D6B3, 'M', u'μ'), + (0x1D6B4, 'M', u'ν'), + (0x1D6B5, 'M', u'ξ'), + (0x1D6B6, 'M', u'ο'), + (0x1D6B7, 'M', u'π'), + (0x1D6B8, 'M', u'ρ'), + (0x1D6B9, 'M', u'θ'), + (0x1D6BA, 'M', u'σ'), + (0x1D6BB, 'M', u'τ'), + (0x1D6BC, 'M', u'υ'), + (0x1D6BD, 'M', u'φ'), + (0x1D6BE, 'M', u'χ'), + (0x1D6BF, 'M', u'ψ'), + (0x1D6C0, 'M', u'ω'), + (0x1D6C1, 'M', u'∇'), + (0x1D6C2, 'M', u'α'), + (0x1D6C3, 'M', u'β'), + (0x1D6C4, 'M', u'γ'), + (0x1D6C5, 'M', u'δ'), + (0x1D6C6, 'M', u'ε'), + (0x1D6C7, 'M', u'ζ'), + (0x1D6C8, 'M', u'η'), + (0x1D6C9, 'M', u'θ'), + (0x1D6CA, 'M', u'ι'), + (0x1D6CB, 'M', u'κ'), + (0x1D6CC, 'M', u'λ'), + (0x1D6CD, 'M', u'μ'), + (0x1D6CE, 'M', u'ν'), + (0x1D6CF, 'M', u'ξ'), + (0x1D6D0, 'M', u'ο'), + (0x1D6D1, 'M', u'π'), + (0x1D6D2, 'M', u'ρ'), + (0x1D6D3, 'M', u'σ'), + (0x1D6D5, 'M', u'τ'), + (0x1D6D6, 'M', u'υ'), + (0x1D6D7, 'M', u'φ'), + (0x1D6D8, 'M', u'χ'), + (0x1D6D9, 'M', u'ψ'), + (0x1D6DA, 'M', u'ω'), + (0x1D6DB, 'M', u'∂'), + (0x1D6DC, 'M', u'ε'), + (0x1D6DD, 'M', u'θ'), + (0x1D6DE, 'M', u'κ'), + (0x1D6DF, 'M', u'φ'), + (0x1D6E0, 'M', u'ρ'), + (0x1D6E1, 'M', u'π'), + (0x1D6E2, 'M', u'α'), + (0x1D6E3, 'M', u'β'), + (0x1D6E4, 'M', u'γ'), + (0x1D6E5, 'M', u'δ'), + (0x1D6E6, 'M', u'ε'), + (0x1D6E7, 'M', u'ζ'), + (0x1D6E8, 'M', u'η'), + (0x1D6E9, 'M', u'θ'), + (0x1D6EA, 'M', u'ι'), + (0x1D6EB, 'M', u'κ'), + (0x1D6EC, 'M', u'λ'), + (0x1D6ED, 'M', u'μ'), + (0x1D6EE, 'M', u'ν'), + (0x1D6EF, 'M', u'ξ'), + (0x1D6F0, 'M', u'ο'), + (0x1D6F1, 'M', u'π'), + (0x1D6F2, 'M', u'ρ'), + (0x1D6F3, 'M', u'θ'), + (0x1D6F4, 'M', u'σ'), + (0x1D6F5, 'M', u'τ'), + (0x1D6F6, 'M', u'υ'), + (0x1D6F7, 'M', u'φ'), + (0x1D6F8, 'M', u'χ'), + (0x1D6F9, 'M', u'ψ'), + (0x1D6FA, 'M', u'ω'), + (0x1D6FB, 'M', u'∇'), + (0x1D6FC, 'M', u'α'), + (0x1D6FD, 'M', u'β'), + (0x1D6FE, 'M', u'γ'), + (0x1D6FF, 'M', u'δ'), + (0x1D700, 'M', u'ε'), + (0x1D701, 'M', u'ζ'), + (0x1D702, 'M', u'η'), + (0x1D703, 'M', u'θ'), + (0x1D704, 'M', u'ι'), + (0x1D705, 'M', u'κ'), + (0x1D706, 'M', u'λ'), + (0x1D707, 'M', u'μ'), + (0x1D708, 'M', u'ν'), + (0x1D709, 'M', u'ξ'), + (0x1D70A, 'M', u'ο'), + (0x1D70B, 'M', u'π'), + (0x1D70C, 'M', u'ρ'), + (0x1D70D, 'M', u'σ'), + (0x1D70F, 'M', u'τ'), + (0x1D710, 'M', u'υ'), + (0x1D711, 'M', u'φ'), + (0x1D712, 'M', u'χ'), + (0x1D713, 'M', u'ψ'), + (0x1D714, 'M', u'ω'), + (0x1D715, 'M', u'∂'), + (0x1D716, 'M', u'ε'), + ] + +def _seg_67(): + return [ + (0x1D717, 'M', u'θ'), + (0x1D718, 'M', u'κ'), + (0x1D719, 'M', u'φ'), + (0x1D71A, 'M', u'ρ'), + (0x1D71B, 'M', u'π'), + (0x1D71C, 'M', u'α'), + (0x1D71D, 'M', u'β'), + (0x1D71E, 'M', u'γ'), + (0x1D71F, 'M', u'δ'), + (0x1D720, 'M', u'ε'), + (0x1D721, 'M', u'ζ'), + (0x1D722, 'M', u'η'), + (0x1D723, 'M', u'θ'), + (0x1D724, 'M', u'ι'), + (0x1D725, 'M', u'κ'), + (0x1D726, 'M', u'λ'), + (0x1D727, 'M', u'μ'), + (0x1D728, 'M', u'ν'), + (0x1D729, 'M', u'ξ'), + (0x1D72A, 'M', u'ο'), + (0x1D72B, 'M', u'π'), + (0x1D72C, 'M', u'ρ'), + (0x1D72D, 'M', u'θ'), + (0x1D72E, 'M', u'σ'), + (0x1D72F, 'M', u'τ'), + (0x1D730, 'M', u'υ'), + (0x1D731, 'M', u'φ'), + (0x1D732, 'M', u'χ'), + (0x1D733, 'M', u'ψ'), + (0x1D734, 'M', u'ω'), + (0x1D735, 'M', u'∇'), + (0x1D736, 'M', u'α'), + (0x1D737, 'M', u'β'), + (0x1D738, 'M', u'γ'), + (0x1D739, 'M', u'δ'), + (0x1D73A, 'M', u'ε'), + (0x1D73B, 'M', u'ζ'), + (0x1D73C, 'M', u'η'), + (0x1D73D, 'M', u'θ'), + (0x1D73E, 'M', u'ι'), + (0x1D73F, 'M', u'κ'), + (0x1D740, 'M', u'λ'), + (0x1D741, 'M', u'μ'), + (0x1D742, 'M', u'ν'), + (0x1D743, 'M', u'ξ'), + (0x1D744, 'M', u'ο'), + (0x1D745, 'M', u'π'), + (0x1D746, 'M', u'ρ'), + (0x1D747, 'M', u'σ'), + (0x1D749, 'M', u'τ'), + (0x1D74A, 'M', u'υ'), + (0x1D74B, 'M', u'φ'), + (0x1D74C, 'M', u'χ'), + (0x1D74D, 'M', u'ψ'), + (0x1D74E, 'M', u'ω'), + (0x1D74F, 'M', u'∂'), + (0x1D750, 'M', u'ε'), + (0x1D751, 'M', u'θ'), + (0x1D752, 'M', u'κ'), + (0x1D753, 'M', u'φ'), + (0x1D754, 'M', u'ρ'), + (0x1D755, 'M', u'π'), + (0x1D756, 'M', u'α'), + (0x1D757, 'M', u'β'), + (0x1D758, 'M', u'γ'), + (0x1D759, 'M', u'δ'), + (0x1D75A, 'M', u'ε'), + (0x1D75B, 'M', u'ζ'), + (0x1D75C, 'M', u'η'), + (0x1D75D, 'M', u'θ'), + (0x1D75E, 'M', u'ι'), + (0x1D75F, 'M', u'κ'), + (0x1D760, 'M', u'λ'), + (0x1D761, 'M', u'μ'), + (0x1D762, 'M', u'ν'), + (0x1D763, 'M', u'ξ'), + (0x1D764, 'M', u'ο'), + (0x1D765, 'M', u'π'), + (0x1D766, 'M', u'ρ'), + (0x1D767, 'M', u'θ'), + (0x1D768, 'M', u'σ'), + (0x1D769, 'M', u'τ'), + (0x1D76A, 'M', u'υ'), + (0x1D76B, 'M', u'φ'), + (0x1D76C, 'M', u'χ'), + (0x1D76D, 'M', u'ψ'), + (0x1D76E, 'M', u'ω'), + (0x1D76F, 'M', u'∇'), + (0x1D770, 'M', u'α'), + (0x1D771, 'M', u'β'), + (0x1D772, 'M', u'γ'), + (0x1D773, 'M', u'δ'), + (0x1D774, 'M', u'ε'), + (0x1D775, 'M', u'ζ'), + (0x1D776, 'M', u'η'), + (0x1D777, 'M', u'θ'), + (0x1D778, 'M', u'ι'), + (0x1D779, 'M', u'κ'), + (0x1D77A, 'M', u'λ'), + (0x1D77B, 'M', u'μ'), + ] + +def _seg_68(): + return [ + (0x1D77C, 'M', u'ν'), + (0x1D77D, 'M', u'ξ'), + (0x1D77E, 'M', u'ο'), + (0x1D77F, 'M', u'π'), + (0x1D780, 'M', u'ρ'), + (0x1D781, 'M', u'σ'), + (0x1D783, 'M', u'τ'), + (0x1D784, 'M', u'υ'), + (0x1D785, 'M', u'φ'), + (0x1D786, 'M', u'χ'), + (0x1D787, 'M', u'ψ'), + (0x1D788, 'M', u'ω'), + (0x1D789, 'M', u'∂'), + (0x1D78A, 'M', u'ε'), + (0x1D78B, 'M', u'θ'), + (0x1D78C, 'M', u'κ'), + (0x1D78D, 'M', u'φ'), + (0x1D78E, 'M', u'ρ'), + (0x1D78F, 'M', u'π'), + (0x1D790, 'M', u'α'), + (0x1D791, 'M', u'β'), + (0x1D792, 'M', u'γ'), + (0x1D793, 'M', u'δ'), + (0x1D794, 'M', u'ε'), + (0x1D795, 'M', u'ζ'), + (0x1D796, 'M', u'η'), + (0x1D797, 'M', u'θ'), + (0x1D798, 'M', u'ι'), + (0x1D799, 'M', u'κ'), + (0x1D79A, 'M', u'λ'), + (0x1D79B, 'M', u'μ'), + (0x1D79C, 'M', u'ν'), + (0x1D79D, 'M', u'ξ'), + (0x1D79E, 'M', u'ο'), + (0x1D79F, 'M', u'π'), + (0x1D7A0, 'M', u'ρ'), + (0x1D7A1, 'M', u'θ'), + (0x1D7A2, 'M', u'σ'), + (0x1D7A3, 'M', u'τ'), + (0x1D7A4, 'M', u'υ'), + (0x1D7A5, 'M', u'φ'), + (0x1D7A6, 'M', u'χ'), + (0x1D7A7, 'M', u'ψ'), + (0x1D7A8, 'M', u'ω'), + (0x1D7A9, 'M', u'∇'), + (0x1D7AA, 'M', u'α'), + (0x1D7AB, 'M', u'β'), + (0x1D7AC, 'M', u'γ'), + (0x1D7AD, 'M', u'δ'), + (0x1D7AE, 'M', u'ε'), + (0x1D7AF, 'M', u'ζ'), + (0x1D7B0, 'M', u'η'), + (0x1D7B1, 'M', u'θ'), + (0x1D7B2, 'M', u'ι'), + (0x1D7B3, 'M', u'κ'), + (0x1D7B4, 'M', u'λ'), + (0x1D7B5, 'M', u'μ'), + (0x1D7B6, 'M', u'ν'), + (0x1D7B7, 'M', u'ξ'), + (0x1D7B8, 'M', u'ο'), + (0x1D7B9, 'M', u'π'), + (0x1D7BA, 'M', u'ρ'), + (0x1D7BB, 'M', u'σ'), + (0x1D7BD, 'M', u'τ'), + (0x1D7BE, 'M', u'υ'), + (0x1D7BF, 'M', u'φ'), + (0x1D7C0, 'M', u'χ'), + (0x1D7C1, 'M', u'ψ'), + (0x1D7C2, 'M', u'ω'), + (0x1D7C3, 'M', u'∂'), + (0x1D7C4, 'M', u'ε'), + (0x1D7C5, 'M', u'θ'), + (0x1D7C6, 'M', u'κ'), + (0x1D7C7, 'M', u'φ'), + (0x1D7C8, 'M', u'ρ'), + (0x1D7C9, 'M', u'π'), + (0x1D7CA, 'M', u'ϝ'), + (0x1D7CC, 'X'), + (0x1D7CE, 'M', u'0'), + (0x1D7CF, 'M', u'1'), + (0x1D7D0, 'M', u'2'), + (0x1D7D1, 'M', u'3'), + (0x1D7D2, 'M', u'4'), + (0x1D7D3, 'M', u'5'), + (0x1D7D4, 'M', u'6'), + (0x1D7D5, 'M', u'7'), + (0x1D7D6, 'M', u'8'), + (0x1D7D7, 'M', u'9'), + (0x1D7D8, 'M', u'0'), + (0x1D7D9, 'M', u'1'), + (0x1D7DA, 'M', u'2'), + (0x1D7DB, 'M', u'3'), + (0x1D7DC, 'M', u'4'), + (0x1D7DD, 'M', u'5'), + (0x1D7DE, 'M', u'6'), + (0x1D7DF, 'M', u'7'), + (0x1D7E0, 'M', u'8'), + (0x1D7E1, 'M', u'9'), + (0x1D7E2, 'M', u'0'), + (0x1D7E3, 'M', u'1'), + ] + +def _seg_69(): + return [ + (0x1D7E4, 'M', u'2'), + (0x1D7E5, 'M', u'3'), + (0x1D7E6, 'M', u'4'), + (0x1D7E7, 'M', u'5'), + (0x1D7E8, 'M', u'6'), + (0x1D7E9, 'M', u'7'), + (0x1D7EA, 'M', u'8'), + (0x1D7EB, 'M', u'9'), + (0x1D7EC, 'M', u'0'), + (0x1D7ED, 'M', u'1'), + (0x1D7EE, 'M', u'2'), + (0x1D7EF, 'M', u'3'), + (0x1D7F0, 'M', u'4'), + (0x1D7F1, 'M', u'5'), + (0x1D7F2, 'M', u'6'), + (0x1D7F3, 'M', u'7'), + (0x1D7F4, 'M', u'8'), + (0x1D7F5, 'M', u'9'), + (0x1D7F6, 'M', u'0'), + (0x1D7F7, 'M', u'1'), + (0x1D7F8, 'M', u'2'), + (0x1D7F9, 'M', u'3'), + (0x1D7FA, 'M', u'4'), + (0x1D7FB, 'M', u'5'), + (0x1D7FC, 'M', u'6'), + (0x1D7FD, 'M', u'7'), + (0x1D7FE, 'M', u'8'), + (0x1D7FF, 'M', u'9'), + (0x1D800, 'V'), + (0x1DA8C, 'X'), + (0x1DA9B, 'V'), + (0x1DAA0, 'X'), + (0x1DAA1, 'V'), + (0x1DAB0, 'X'), + (0x1E000, 'V'), + (0x1E007, 'X'), + (0x1E008, 'V'), + (0x1E019, 'X'), + (0x1E01B, 'V'), + (0x1E022, 'X'), + (0x1E023, 'V'), + (0x1E025, 'X'), + (0x1E026, 'V'), + (0x1E02B, 'X'), + (0x1E100, 'V'), + (0x1E12D, 'X'), + (0x1E130, 'V'), + (0x1E13E, 'X'), + (0x1E140, 'V'), + (0x1E14A, 'X'), + (0x1E14E, 'V'), + (0x1E150, 'X'), + (0x1E2C0, 'V'), + (0x1E2FA, 'X'), + (0x1E2FF, 'V'), + (0x1E300, 'X'), + (0x1E800, 'V'), + (0x1E8C5, 'X'), + (0x1E8C7, 'V'), + (0x1E8D7, 'X'), + (0x1E900, 'M', u'𞤢'), + (0x1E901, 'M', u'𞤣'), + (0x1E902, 'M', u'𞤤'), + (0x1E903, 'M', u'𞤥'), + (0x1E904, 'M', u'𞤦'), + (0x1E905, 'M', u'𞤧'), + (0x1E906, 'M', u'𞤨'), + (0x1E907, 'M', u'𞤩'), + (0x1E908, 'M', u'𞤪'), + (0x1E909, 'M', u'𞤫'), + (0x1E90A, 'M', u'𞤬'), + (0x1E90B, 'M', u'𞤭'), + (0x1E90C, 'M', u'𞤮'), + (0x1E90D, 'M', u'𞤯'), + (0x1E90E, 'M', u'𞤰'), + (0x1E90F, 'M', u'𞤱'), + (0x1E910, 'M', u'𞤲'), + (0x1E911, 'M', u'𞤳'), + (0x1E912, 'M', u'𞤴'), + (0x1E913, 'M', u'𞤵'), + (0x1E914, 'M', u'𞤶'), + (0x1E915, 'M', u'𞤷'), + (0x1E916, 'M', u'𞤸'), + (0x1E917, 'M', u'𞤹'), + (0x1E918, 'M', u'𞤺'), + (0x1E919, 'M', u'𞤻'), + (0x1E91A, 'M', u'𞤼'), + (0x1E91B, 'M', u'𞤽'), + (0x1E91C, 'M', u'𞤾'), + (0x1E91D, 'M', u'𞤿'), + (0x1E91E, 'M', u'𞥀'), + (0x1E91F, 'M', u'𞥁'), + (0x1E920, 'M', u'𞥂'), + (0x1E921, 'M', u'𞥃'), + (0x1E922, 'V'), + (0x1E94C, 'X'), + (0x1E950, 'V'), + (0x1E95A, 'X'), + (0x1E95E, 'V'), + (0x1E960, 'X'), + ] + +def _seg_70(): + return [ + (0x1EC71, 'V'), + (0x1ECB5, 'X'), + (0x1ED01, 'V'), + (0x1ED3E, 'X'), + (0x1EE00, 'M', u'ا'), + (0x1EE01, 'M', u'ب'), + (0x1EE02, 'M', u'ج'), + (0x1EE03, 'M', u'د'), + (0x1EE04, 'X'), + (0x1EE05, 'M', u'و'), + (0x1EE06, 'M', u'ز'), + (0x1EE07, 'M', u'ح'), + (0x1EE08, 'M', u'ط'), + (0x1EE09, 'M', u'ي'), + (0x1EE0A, 'M', u'ك'), + (0x1EE0B, 'M', u'ل'), + (0x1EE0C, 'M', u'م'), + (0x1EE0D, 'M', u'ن'), + (0x1EE0E, 'M', u'س'), + (0x1EE0F, 'M', u'ع'), + (0x1EE10, 'M', u'ف'), + (0x1EE11, 'M', u'ص'), + (0x1EE12, 'M', u'ق'), + (0x1EE13, 'M', u'ر'), + (0x1EE14, 'M', u'ش'), + (0x1EE15, 'M', u'ت'), + (0x1EE16, 'M', u'ث'), + (0x1EE17, 'M', u'خ'), + (0x1EE18, 'M', u'ذ'), + (0x1EE19, 'M', u'ض'), + (0x1EE1A, 'M', u'ظ'), + (0x1EE1B, 'M', u'غ'), + (0x1EE1C, 'M', u'ٮ'), + (0x1EE1D, 'M', u'ں'), + (0x1EE1E, 'M', u'ڡ'), + (0x1EE1F, 'M', u'ٯ'), + (0x1EE20, 'X'), + (0x1EE21, 'M', u'ب'), + (0x1EE22, 'M', u'ج'), + (0x1EE23, 'X'), + (0x1EE24, 'M', u'ه'), + (0x1EE25, 'X'), + (0x1EE27, 'M', u'ح'), + (0x1EE28, 'X'), + (0x1EE29, 'M', u'ي'), + (0x1EE2A, 'M', u'ك'), + (0x1EE2B, 'M', u'ل'), + (0x1EE2C, 'M', u'م'), + (0x1EE2D, 'M', u'ن'), + (0x1EE2E, 'M', u'س'), + (0x1EE2F, 'M', u'ع'), + (0x1EE30, 'M', u'ف'), + (0x1EE31, 'M', u'ص'), + (0x1EE32, 'M', u'ق'), + (0x1EE33, 'X'), + (0x1EE34, 'M', u'ش'), + (0x1EE35, 'M', u'ت'), + (0x1EE36, 'M', u'ث'), + (0x1EE37, 'M', u'خ'), + (0x1EE38, 'X'), + (0x1EE39, 'M', u'ض'), + (0x1EE3A, 'X'), + (0x1EE3B, 'M', u'غ'), + (0x1EE3C, 'X'), + (0x1EE42, 'M', u'ج'), + (0x1EE43, 'X'), + (0x1EE47, 'M', u'ح'), + (0x1EE48, 'X'), + (0x1EE49, 'M', u'ي'), + (0x1EE4A, 'X'), + (0x1EE4B, 'M', u'ل'), + (0x1EE4C, 'X'), + (0x1EE4D, 'M', u'ن'), + (0x1EE4E, 'M', u'س'), + (0x1EE4F, 'M', u'ع'), + (0x1EE50, 'X'), + (0x1EE51, 'M', u'ص'), + (0x1EE52, 'M', u'ق'), + (0x1EE53, 'X'), + (0x1EE54, 'M', u'ش'), + (0x1EE55, 'X'), + (0x1EE57, 'M', u'خ'), + (0x1EE58, 'X'), + (0x1EE59, 'M', u'ض'), + (0x1EE5A, 'X'), + (0x1EE5B, 'M', u'غ'), + (0x1EE5C, 'X'), + (0x1EE5D, 'M', u'ں'), + (0x1EE5E, 'X'), + (0x1EE5F, 'M', u'ٯ'), + (0x1EE60, 'X'), + (0x1EE61, 'M', u'ب'), + (0x1EE62, 'M', u'ج'), + (0x1EE63, 'X'), + (0x1EE64, 'M', u'ه'), + (0x1EE65, 'X'), + (0x1EE67, 'M', u'ح'), + (0x1EE68, 'M', u'ط'), + (0x1EE69, 'M', u'ي'), + (0x1EE6A, 'M', u'ك'), + ] + +def _seg_71(): + return [ + (0x1EE6B, 'X'), + (0x1EE6C, 'M', u'م'), + (0x1EE6D, 'M', u'ن'), + (0x1EE6E, 'M', u'س'), + (0x1EE6F, 'M', u'ع'), + (0x1EE70, 'M', u'ف'), + (0x1EE71, 'M', u'ص'), + (0x1EE72, 'M', u'ق'), + (0x1EE73, 'X'), + (0x1EE74, 'M', u'ش'), + (0x1EE75, 'M', u'ت'), + (0x1EE76, 'M', u'ث'), + (0x1EE77, 'M', u'خ'), + (0x1EE78, 'X'), + (0x1EE79, 'M', u'ض'), + (0x1EE7A, 'M', u'ظ'), + (0x1EE7B, 'M', u'غ'), + (0x1EE7C, 'M', u'ٮ'), + (0x1EE7D, 'X'), + (0x1EE7E, 'M', u'ڡ'), + (0x1EE7F, 'X'), + (0x1EE80, 'M', u'ا'), + (0x1EE81, 'M', u'ب'), + (0x1EE82, 'M', u'ج'), + (0x1EE83, 'M', u'د'), + (0x1EE84, 'M', u'ه'), + (0x1EE85, 'M', u'و'), + (0x1EE86, 'M', u'ز'), + (0x1EE87, 'M', u'ح'), + (0x1EE88, 'M', u'ط'), + (0x1EE89, 'M', u'ي'), + (0x1EE8A, 'X'), + (0x1EE8B, 'M', u'ل'), + (0x1EE8C, 'M', u'م'), + (0x1EE8D, 'M', u'ن'), + (0x1EE8E, 'M', u'س'), + (0x1EE8F, 'M', u'ع'), + (0x1EE90, 'M', u'ف'), + (0x1EE91, 'M', u'ص'), + (0x1EE92, 'M', u'ق'), + (0x1EE93, 'M', u'ر'), + (0x1EE94, 'M', u'ش'), + (0x1EE95, 'M', u'ت'), + (0x1EE96, 'M', u'ث'), + (0x1EE97, 'M', u'خ'), + (0x1EE98, 'M', u'ذ'), + (0x1EE99, 'M', u'ض'), + (0x1EE9A, 'M', u'ظ'), + (0x1EE9B, 'M', u'غ'), + (0x1EE9C, 'X'), + (0x1EEA1, 'M', u'ب'), + (0x1EEA2, 'M', u'ج'), + (0x1EEA3, 'M', u'د'), + (0x1EEA4, 'X'), + (0x1EEA5, 'M', u'و'), + (0x1EEA6, 'M', u'ز'), + (0x1EEA7, 'M', u'ح'), + (0x1EEA8, 'M', u'ط'), + (0x1EEA9, 'M', u'ي'), + (0x1EEAA, 'X'), + (0x1EEAB, 'M', u'ل'), + (0x1EEAC, 'M', u'م'), + (0x1EEAD, 'M', u'ن'), + (0x1EEAE, 'M', u'س'), + (0x1EEAF, 'M', u'ع'), + (0x1EEB0, 'M', u'ف'), + (0x1EEB1, 'M', u'ص'), + (0x1EEB2, 'M', u'ق'), + (0x1EEB3, 'M', u'ر'), + (0x1EEB4, 'M', u'ش'), + (0x1EEB5, 'M', u'ت'), + (0x1EEB6, 'M', u'ث'), + (0x1EEB7, 'M', u'خ'), + (0x1EEB8, 'M', u'ذ'), + (0x1EEB9, 'M', u'ض'), + (0x1EEBA, 'M', u'ظ'), + (0x1EEBB, 'M', u'غ'), + (0x1EEBC, 'X'), + (0x1EEF0, 'V'), + (0x1EEF2, 'X'), + (0x1F000, 'V'), + (0x1F02C, 'X'), + (0x1F030, 'V'), + (0x1F094, 'X'), + (0x1F0A0, 'V'), + (0x1F0AF, 'X'), + (0x1F0B1, 'V'), + (0x1F0C0, 'X'), + (0x1F0C1, 'V'), + (0x1F0D0, 'X'), + (0x1F0D1, 'V'), + (0x1F0F6, 'X'), + (0x1F101, '3', u'0,'), + (0x1F102, '3', u'1,'), + (0x1F103, '3', u'2,'), + (0x1F104, '3', u'3,'), + (0x1F105, '3', u'4,'), + (0x1F106, '3', u'5,'), + (0x1F107, '3', u'6,'), + (0x1F108, '3', u'7,'), + ] + +def _seg_72(): + return [ + (0x1F109, '3', u'8,'), + (0x1F10A, '3', u'9,'), + (0x1F10B, 'V'), + (0x1F110, '3', u'(a)'), + (0x1F111, '3', u'(b)'), + (0x1F112, '3', u'(c)'), + (0x1F113, '3', u'(d)'), + (0x1F114, '3', u'(e)'), + (0x1F115, '3', u'(f)'), + (0x1F116, '3', u'(g)'), + (0x1F117, '3', u'(h)'), + (0x1F118, '3', u'(i)'), + (0x1F119, '3', u'(j)'), + (0x1F11A, '3', u'(k)'), + (0x1F11B, '3', u'(l)'), + (0x1F11C, '3', u'(m)'), + (0x1F11D, '3', u'(n)'), + (0x1F11E, '3', u'(o)'), + (0x1F11F, '3', u'(p)'), + (0x1F120, '3', u'(q)'), + (0x1F121, '3', u'(r)'), + (0x1F122, '3', u'(s)'), + (0x1F123, '3', u'(t)'), + (0x1F124, '3', u'(u)'), + (0x1F125, '3', u'(v)'), + (0x1F126, '3', u'(w)'), + (0x1F127, '3', u'(x)'), + (0x1F128, '3', u'(y)'), + (0x1F129, '3', u'(z)'), + (0x1F12A, 'M', u'〔s〕'), + (0x1F12B, 'M', u'c'), + (0x1F12C, 'M', u'r'), + (0x1F12D, 'M', u'cd'), + (0x1F12E, 'M', u'wz'), + (0x1F12F, 'V'), + (0x1F130, 'M', u'a'), + (0x1F131, 'M', u'b'), + (0x1F132, 'M', u'c'), + (0x1F133, 'M', u'd'), + (0x1F134, 'M', u'e'), + (0x1F135, 'M', u'f'), + (0x1F136, 'M', u'g'), + (0x1F137, 'M', u'h'), + (0x1F138, 'M', u'i'), + (0x1F139, 'M', u'j'), + (0x1F13A, 'M', u'k'), + (0x1F13B, 'M', u'l'), + (0x1F13C, 'M', u'm'), + (0x1F13D, 'M', u'n'), + (0x1F13E, 'M', u'o'), + (0x1F13F, 'M', u'p'), + (0x1F140, 'M', u'q'), + (0x1F141, 'M', u'r'), + (0x1F142, 'M', u's'), + (0x1F143, 'M', u't'), + (0x1F144, 'M', u'u'), + (0x1F145, 'M', u'v'), + (0x1F146, 'M', u'w'), + (0x1F147, 'M', u'x'), + (0x1F148, 'M', u'y'), + (0x1F149, 'M', u'z'), + (0x1F14A, 'M', u'hv'), + (0x1F14B, 'M', u'mv'), + (0x1F14C, 'M', u'sd'), + (0x1F14D, 'M', u'ss'), + (0x1F14E, 'M', u'ppv'), + (0x1F14F, 'M', u'wc'), + (0x1F150, 'V'), + (0x1F16A, 'M', u'mc'), + (0x1F16B, 'M', u'md'), + (0x1F16C, 'M', u'mr'), + (0x1F16D, 'V'), + (0x1F190, 'M', u'dj'), + (0x1F191, 'V'), + (0x1F1AE, 'X'), + (0x1F1E6, 'V'), + (0x1F200, 'M', u'ほか'), + (0x1F201, 'M', u'ココ'), + (0x1F202, 'M', u'サ'), + (0x1F203, 'X'), + (0x1F210, 'M', u'手'), + (0x1F211, 'M', u'字'), + (0x1F212, 'M', u'双'), + (0x1F213, 'M', u'デ'), + (0x1F214, 'M', u'二'), + (0x1F215, 'M', u'多'), + (0x1F216, 'M', u'解'), + (0x1F217, 'M', u'天'), + (0x1F218, 'M', u'交'), + (0x1F219, 'M', u'映'), + (0x1F21A, 'M', u'無'), + (0x1F21B, 'M', u'料'), + (0x1F21C, 'M', u'前'), + (0x1F21D, 'M', u'後'), + (0x1F21E, 'M', u'再'), + (0x1F21F, 'M', u'新'), + (0x1F220, 'M', u'初'), + (0x1F221, 'M', u'終'), + (0x1F222, 'M', u'生'), + (0x1F223, 'M', u'販'), + ] + +def _seg_73(): + return [ + (0x1F224, 'M', u'声'), + (0x1F225, 'M', u'吹'), + (0x1F226, 'M', u'演'), + (0x1F227, 'M', u'投'), + (0x1F228, 'M', u'捕'), + (0x1F229, 'M', u'一'), + (0x1F22A, 'M', u'三'), + (0x1F22B, 'M', u'遊'), + (0x1F22C, 'M', u'左'), + (0x1F22D, 'M', u'中'), + (0x1F22E, 'M', u'右'), + (0x1F22F, 'M', u'指'), + (0x1F230, 'M', u'走'), + (0x1F231, 'M', u'打'), + (0x1F232, 'M', u'禁'), + (0x1F233, 'M', u'空'), + (0x1F234, 'M', u'合'), + (0x1F235, 'M', u'満'), + (0x1F236, 'M', u'有'), + (0x1F237, 'M', u'月'), + (0x1F238, 'M', u'申'), + (0x1F239, 'M', u'割'), + (0x1F23A, 'M', u'営'), + (0x1F23B, 'M', u'配'), + (0x1F23C, 'X'), + (0x1F240, 'M', u'〔本〕'), + (0x1F241, 'M', u'〔三〕'), + (0x1F242, 'M', u'〔二〕'), + (0x1F243, 'M', u'〔安〕'), + (0x1F244, 'M', u'〔点〕'), + (0x1F245, 'M', u'〔打〕'), + (0x1F246, 'M', u'〔盗〕'), + (0x1F247, 'M', u'〔勝〕'), + (0x1F248, 'M', u'〔敗〕'), + (0x1F249, 'X'), + (0x1F250, 'M', u'得'), + (0x1F251, 'M', u'可'), + (0x1F252, 'X'), + (0x1F260, 'V'), + (0x1F266, 'X'), + (0x1F300, 'V'), + (0x1F6D8, 'X'), + (0x1F6E0, 'V'), + (0x1F6ED, 'X'), + (0x1F6F0, 'V'), + (0x1F6FD, 'X'), + (0x1F700, 'V'), + (0x1F774, 'X'), + (0x1F780, 'V'), + (0x1F7D9, 'X'), + (0x1F7E0, 'V'), + (0x1F7EC, 'X'), + (0x1F800, 'V'), + (0x1F80C, 'X'), + (0x1F810, 'V'), + (0x1F848, 'X'), + (0x1F850, 'V'), + (0x1F85A, 'X'), + (0x1F860, 'V'), + (0x1F888, 'X'), + (0x1F890, 'V'), + (0x1F8AE, 'X'), + (0x1F8B0, 'V'), + (0x1F8B2, 'X'), + (0x1F900, 'V'), + (0x1F979, 'X'), + (0x1F97A, 'V'), + (0x1F9CC, 'X'), + (0x1F9CD, 'V'), + (0x1FA54, 'X'), + (0x1FA60, 'V'), + (0x1FA6E, 'X'), + (0x1FA70, 'V'), + (0x1FA75, 'X'), + (0x1FA78, 'V'), + (0x1FA7B, 'X'), + (0x1FA80, 'V'), + (0x1FA87, 'X'), + (0x1FA90, 'V'), + (0x1FAA9, 'X'), + (0x1FAB0, 'V'), + (0x1FAB7, 'X'), + (0x1FAC0, 'V'), + (0x1FAC3, 'X'), + (0x1FAD0, 'V'), + (0x1FAD7, 'X'), + (0x1FB00, 'V'), + (0x1FB93, 'X'), + (0x1FB94, 'V'), + (0x1FBCB, 'X'), + (0x1FBF0, 'M', u'0'), + (0x1FBF1, 'M', u'1'), + (0x1FBF2, 'M', u'2'), + (0x1FBF3, 'M', u'3'), + (0x1FBF4, 'M', u'4'), + (0x1FBF5, 'M', u'5'), + (0x1FBF6, 'M', u'6'), + (0x1FBF7, 'M', u'7'), + (0x1FBF8, 'M', u'8'), + (0x1FBF9, 'M', u'9'), + ] + +def _seg_74(): + return [ + (0x1FBFA, 'X'), + (0x20000, 'V'), + (0x2A6DE, 'X'), + (0x2A700, 'V'), + (0x2B735, 'X'), + (0x2B740, 'V'), + (0x2B81E, 'X'), + (0x2B820, 'V'), + (0x2CEA2, 'X'), + (0x2CEB0, 'V'), + (0x2EBE1, 'X'), + (0x2F800, 'M', u'丽'), + (0x2F801, 'M', u'丸'), + (0x2F802, 'M', u'乁'), + (0x2F803, 'M', u'𠄢'), + (0x2F804, 'M', u'你'), + (0x2F805, 'M', u'侮'), + (0x2F806, 'M', u'侻'), + (0x2F807, 'M', u'倂'), + (0x2F808, 'M', u'偺'), + (0x2F809, 'M', u'備'), + (0x2F80A, 'M', u'僧'), + (0x2F80B, 'M', u'像'), + (0x2F80C, 'M', u'㒞'), + (0x2F80D, 'M', u'𠘺'), + (0x2F80E, 'M', u'免'), + (0x2F80F, 'M', u'兔'), + (0x2F810, 'M', u'兤'), + (0x2F811, 'M', u'具'), + (0x2F812, 'M', u'𠔜'), + (0x2F813, 'M', u'㒹'), + (0x2F814, 'M', u'內'), + (0x2F815, 'M', u'再'), + (0x2F816, 'M', u'𠕋'), + (0x2F817, 'M', u'冗'), + (0x2F818, 'M', u'冤'), + (0x2F819, 'M', u'仌'), + (0x2F81A, 'M', u'冬'), + (0x2F81B, 'M', u'况'), + (0x2F81C, 'M', u'𩇟'), + (0x2F81D, 'M', u'凵'), + (0x2F81E, 'M', u'刃'), + (0x2F81F, 'M', u'㓟'), + (0x2F820, 'M', u'刻'), + (0x2F821, 'M', u'剆'), + (0x2F822, 'M', u'割'), + (0x2F823, 'M', u'剷'), + (0x2F824, 'M', u'㔕'), + (0x2F825, 'M', u'勇'), + (0x2F826, 'M', u'勉'), + (0x2F827, 'M', u'勤'), + (0x2F828, 'M', u'勺'), + (0x2F829, 'M', u'包'), + (0x2F82A, 'M', u'匆'), + (0x2F82B, 'M', u'北'), + (0x2F82C, 'M', u'卉'), + (0x2F82D, 'M', u'卑'), + (0x2F82E, 'M', u'博'), + (0x2F82F, 'M', u'即'), + (0x2F830, 'M', u'卽'), + (0x2F831, 'M', u'卿'), + (0x2F834, 'M', u'𠨬'), + (0x2F835, 'M', u'灰'), + (0x2F836, 'M', u'及'), + (0x2F837, 'M', u'叟'), + (0x2F838, 'M', u'𠭣'), + (0x2F839, 'M', u'叫'), + (0x2F83A, 'M', u'叱'), + (0x2F83B, 'M', u'吆'), + (0x2F83C, 'M', u'咞'), + (0x2F83D, 'M', u'吸'), + (0x2F83E, 'M', u'呈'), + (0x2F83F, 'M', u'周'), + (0x2F840, 'M', u'咢'), + (0x2F841, 'M', u'哶'), + (0x2F842, 'M', u'唐'), + (0x2F843, 'M', u'啓'), + (0x2F844, 'M', u'啣'), + (0x2F845, 'M', u'善'), + (0x2F847, 'M', u'喙'), + (0x2F848, 'M', u'喫'), + (0x2F849, 'M', u'喳'), + (0x2F84A, 'M', u'嗂'), + (0x2F84B, 'M', u'圖'), + (0x2F84C, 'M', u'嘆'), + (0x2F84D, 'M', u'圗'), + (0x2F84E, 'M', u'噑'), + (0x2F84F, 'M', u'噴'), + (0x2F850, 'M', u'切'), + (0x2F851, 'M', u'壮'), + (0x2F852, 'M', u'城'), + (0x2F853, 'M', u'埴'), + (0x2F854, 'M', u'堍'), + (0x2F855, 'M', u'型'), + (0x2F856, 'M', u'堲'), + (0x2F857, 'M', u'報'), + (0x2F858, 'M', u'墬'), + (0x2F859, 'M', u'𡓤'), + (0x2F85A, 'M', u'売'), + (0x2F85B, 'M', u'壷'), + ] + +def _seg_75(): + return [ + (0x2F85C, 'M', u'夆'), + (0x2F85D, 'M', u'多'), + (0x2F85E, 'M', u'夢'), + (0x2F85F, 'M', u'奢'), + (0x2F860, 'M', u'𡚨'), + (0x2F861, 'M', u'𡛪'), + (0x2F862, 'M', u'姬'), + (0x2F863, 'M', u'娛'), + (0x2F864, 'M', u'娧'), + (0x2F865, 'M', u'姘'), + (0x2F866, 'M', u'婦'), + (0x2F867, 'M', u'㛮'), + (0x2F868, 'X'), + (0x2F869, 'M', u'嬈'), + (0x2F86A, 'M', u'嬾'), + (0x2F86C, 'M', u'𡧈'), + (0x2F86D, 'M', u'寃'), + (0x2F86E, 'M', u'寘'), + (0x2F86F, 'M', u'寧'), + (0x2F870, 'M', u'寳'), + (0x2F871, 'M', u'𡬘'), + (0x2F872, 'M', u'寿'), + (0x2F873, 'M', u'将'), + (0x2F874, 'X'), + (0x2F875, 'M', u'尢'), + (0x2F876, 'M', u'㞁'), + (0x2F877, 'M', u'屠'), + (0x2F878, 'M', u'屮'), + (0x2F879, 'M', u'峀'), + (0x2F87A, 'M', u'岍'), + (0x2F87B, 'M', u'𡷤'), + (0x2F87C, 'M', u'嵃'), + (0x2F87D, 'M', u'𡷦'), + (0x2F87E, 'M', u'嵮'), + (0x2F87F, 'M', u'嵫'), + (0x2F880, 'M', u'嵼'), + (0x2F881, 'M', u'巡'), + (0x2F882, 'M', u'巢'), + (0x2F883, 'M', u'㠯'), + (0x2F884, 'M', u'巽'), + (0x2F885, 'M', u'帨'), + (0x2F886, 'M', u'帽'), + (0x2F887, 'M', u'幩'), + (0x2F888, 'M', u'㡢'), + (0x2F889, 'M', u'𢆃'), + (0x2F88A, 'M', u'㡼'), + (0x2F88B, 'M', u'庰'), + (0x2F88C, 'M', u'庳'), + (0x2F88D, 'M', u'庶'), + (0x2F88E, 'M', u'廊'), + (0x2F88F, 'M', u'𪎒'), + (0x2F890, 'M', u'廾'), + (0x2F891, 'M', u'𢌱'), + (0x2F893, 'M', u'舁'), + (0x2F894, 'M', u'弢'), + (0x2F896, 'M', u'㣇'), + (0x2F897, 'M', u'𣊸'), + (0x2F898, 'M', u'𦇚'), + (0x2F899, 'M', u'形'), + (0x2F89A, 'M', u'彫'), + (0x2F89B, 'M', u'㣣'), + (0x2F89C, 'M', u'徚'), + (0x2F89D, 'M', u'忍'), + (0x2F89E, 'M', u'志'), + (0x2F89F, 'M', u'忹'), + (0x2F8A0, 'M', u'悁'), + (0x2F8A1, 'M', u'㤺'), + (0x2F8A2, 'M', u'㤜'), + (0x2F8A3, 'M', u'悔'), + (0x2F8A4, 'M', u'𢛔'), + (0x2F8A5, 'M', u'惇'), + (0x2F8A6, 'M', u'慈'), + (0x2F8A7, 'M', u'慌'), + (0x2F8A8, 'M', u'慎'), + (0x2F8A9, 'M', u'慌'), + (0x2F8AA, 'M', u'慺'), + (0x2F8AB, 'M', u'憎'), + (0x2F8AC, 'M', u'憲'), + (0x2F8AD, 'M', u'憤'), + (0x2F8AE, 'M', u'憯'), + (0x2F8AF, 'M', u'懞'), + (0x2F8B0, 'M', u'懲'), + (0x2F8B1, 'M', u'懶'), + (0x2F8B2, 'M', u'成'), + (0x2F8B3, 'M', u'戛'), + (0x2F8B4, 'M', u'扝'), + (0x2F8B5, 'M', u'抱'), + (0x2F8B6, 'M', u'拔'), + (0x2F8B7, 'M', u'捐'), + (0x2F8B8, 'M', u'𢬌'), + (0x2F8B9, 'M', u'挽'), + (0x2F8BA, 'M', u'拼'), + (0x2F8BB, 'M', u'捨'), + (0x2F8BC, 'M', u'掃'), + (0x2F8BD, 'M', u'揤'), + (0x2F8BE, 'M', u'𢯱'), + (0x2F8BF, 'M', u'搢'), + (0x2F8C0, 'M', u'揅'), + (0x2F8C1, 'M', u'掩'), + (0x2F8C2, 'M', u'㨮'), + ] + +def _seg_76(): + return [ + (0x2F8C3, 'M', u'摩'), + (0x2F8C4, 'M', u'摾'), + (0x2F8C5, 'M', u'撝'), + (0x2F8C6, 'M', u'摷'), + (0x2F8C7, 'M', u'㩬'), + (0x2F8C8, 'M', u'敏'), + (0x2F8C9, 'M', u'敬'), + (0x2F8CA, 'M', u'𣀊'), + (0x2F8CB, 'M', u'旣'), + (0x2F8CC, 'M', u'書'), + (0x2F8CD, 'M', u'晉'), + (0x2F8CE, 'M', u'㬙'), + (0x2F8CF, 'M', u'暑'), + (0x2F8D0, 'M', u'㬈'), + (0x2F8D1, 'M', u'㫤'), + (0x2F8D2, 'M', u'冒'), + (0x2F8D3, 'M', u'冕'), + (0x2F8D4, 'M', u'最'), + (0x2F8D5, 'M', u'暜'), + (0x2F8D6, 'M', u'肭'), + (0x2F8D7, 'M', u'䏙'), + (0x2F8D8, 'M', u'朗'), + (0x2F8D9, 'M', u'望'), + (0x2F8DA, 'M', u'朡'), + (0x2F8DB, 'M', u'杞'), + (0x2F8DC, 'M', u'杓'), + (0x2F8DD, 'M', u'𣏃'), + (0x2F8DE, 'M', u'㭉'), + (0x2F8DF, 'M', u'柺'), + (0x2F8E0, 'M', u'枅'), + (0x2F8E1, 'M', u'桒'), + (0x2F8E2, 'M', u'梅'), + (0x2F8E3, 'M', u'𣑭'), + (0x2F8E4, 'M', u'梎'), + (0x2F8E5, 'M', u'栟'), + (0x2F8E6, 'M', u'椔'), + (0x2F8E7, 'M', u'㮝'), + (0x2F8E8, 'M', u'楂'), + (0x2F8E9, 'M', u'榣'), + (0x2F8EA, 'M', u'槪'), + (0x2F8EB, 'M', u'檨'), + (0x2F8EC, 'M', u'𣚣'), + (0x2F8ED, 'M', u'櫛'), + (0x2F8EE, 'M', u'㰘'), + (0x2F8EF, 'M', u'次'), + (0x2F8F0, 'M', u'𣢧'), + (0x2F8F1, 'M', u'歔'), + (0x2F8F2, 'M', u'㱎'), + (0x2F8F3, 'M', u'歲'), + (0x2F8F4, 'M', u'殟'), + (0x2F8F5, 'M', u'殺'), + (0x2F8F6, 'M', u'殻'), + (0x2F8F7, 'M', u'𣪍'), + (0x2F8F8, 'M', u'𡴋'), + (0x2F8F9, 'M', u'𣫺'), + (0x2F8FA, 'M', u'汎'), + (0x2F8FB, 'M', u'𣲼'), + (0x2F8FC, 'M', u'沿'), + (0x2F8FD, 'M', u'泍'), + (0x2F8FE, 'M', u'汧'), + (0x2F8FF, 'M', u'洖'), + (0x2F900, 'M', u'派'), + (0x2F901, 'M', u'海'), + (0x2F902, 'M', u'流'), + (0x2F903, 'M', u'浩'), + (0x2F904, 'M', u'浸'), + (0x2F905, 'M', u'涅'), + (0x2F906, 'M', u'𣴞'), + (0x2F907, 'M', u'洴'), + (0x2F908, 'M', u'港'), + (0x2F909, 'M', u'湮'), + (0x2F90A, 'M', u'㴳'), + (0x2F90B, 'M', u'滋'), + (0x2F90C, 'M', u'滇'), + (0x2F90D, 'M', u'𣻑'), + (0x2F90E, 'M', u'淹'), + (0x2F90F, 'M', u'潮'), + (0x2F910, 'M', u'𣽞'), + (0x2F911, 'M', u'𣾎'), + (0x2F912, 'M', u'濆'), + (0x2F913, 'M', u'瀹'), + (0x2F914, 'M', u'瀞'), + (0x2F915, 'M', u'瀛'), + (0x2F916, 'M', u'㶖'), + (0x2F917, 'M', u'灊'), + (0x2F918, 'M', u'災'), + (0x2F919, 'M', u'灷'), + (0x2F91A, 'M', u'炭'), + (0x2F91B, 'M', u'𠔥'), + (0x2F91C, 'M', u'煅'), + (0x2F91D, 'M', u'𤉣'), + (0x2F91E, 'M', u'熜'), + (0x2F91F, 'X'), + (0x2F920, 'M', u'爨'), + (0x2F921, 'M', u'爵'), + (0x2F922, 'M', u'牐'), + (0x2F923, 'M', u'𤘈'), + (0x2F924, 'M', u'犀'), + (0x2F925, 'M', u'犕'), + (0x2F926, 'M', u'𤜵'), + ] + +def _seg_77(): + return [ + (0x2F927, 'M', u'𤠔'), + (0x2F928, 'M', u'獺'), + (0x2F929, 'M', u'王'), + (0x2F92A, 'M', u'㺬'), + (0x2F92B, 'M', u'玥'), + (0x2F92C, 'M', u'㺸'), + (0x2F92E, 'M', u'瑇'), + (0x2F92F, 'M', u'瑜'), + (0x2F930, 'M', u'瑱'), + (0x2F931, 'M', u'璅'), + (0x2F932, 'M', u'瓊'), + (0x2F933, 'M', u'㼛'), + (0x2F934, 'M', u'甤'), + (0x2F935, 'M', u'𤰶'), + (0x2F936, 'M', u'甾'), + (0x2F937, 'M', u'𤲒'), + (0x2F938, 'M', u'異'), + (0x2F939, 'M', u'𢆟'), + (0x2F93A, 'M', u'瘐'), + (0x2F93B, 'M', u'𤾡'), + (0x2F93C, 'M', u'𤾸'), + (0x2F93D, 'M', u'𥁄'), + (0x2F93E, 'M', u'㿼'), + (0x2F93F, 'M', u'䀈'), + (0x2F940, 'M', u'直'), + (0x2F941, 'M', u'𥃳'), + (0x2F942, 'M', u'𥃲'), + (0x2F943, 'M', u'𥄙'), + (0x2F944, 'M', u'𥄳'), + (0x2F945, 'M', u'眞'), + (0x2F946, 'M', u'真'), + (0x2F948, 'M', u'睊'), + (0x2F949, 'M', u'䀹'), + (0x2F94A, 'M', u'瞋'), + (0x2F94B, 'M', u'䁆'), + (0x2F94C, 'M', u'䂖'), + (0x2F94D, 'M', u'𥐝'), + (0x2F94E, 'M', u'硎'), + (0x2F94F, 'M', u'碌'), + (0x2F950, 'M', u'磌'), + (0x2F951, 'M', u'䃣'), + (0x2F952, 'M', u'𥘦'), + (0x2F953, 'M', u'祖'), + (0x2F954, 'M', u'𥚚'), + (0x2F955, 'M', u'𥛅'), + (0x2F956, 'M', u'福'), + (0x2F957, 'M', u'秫'), + (0x2F958, 'M', u'䄯'), + (0x2F959, 'M', u'穀'), + (0x2F95A, 'M', u'穊'), + (0x2F95B, 'M', u'穏'), + (0x2F95C, 'M', u'𥥼'), + (0x2F95D, 'M', u'𥪧'), + (0x2F95F, 'X'), + (0x2F960, 'M', u'䈂'), + (0x2F961, 'M', u'𥮫'), + (0x2F962, 'M', u'篆'), + (0x2F963, 'M', u'築'), + (0x2F964, 'M', u'䈧'), + (0x2F965, 'M', u'𥲀'), + (0x2F966, 'M', u'糒'), + (0x2F967, 'M', u'䊠'), + (0x2F968, 'M', u'糨'), + (0x2F969, 'M', u'糣'), + (0x2F96A, 'M', u'紀'), + (0x2F96B, 'M', u'𥾆'), + (0x2F96C, 'M', u'絣'), + (0x2F96D, 'M', u'䌁'), + (0x2F96E, 'M', u'緇'), + (0x2F96F, 'M', u'縂'), + (0x2F970, 'M', u'繅'), + (0x2F971, 'M', u'䌴'), + (0x2F972, 'M', u'𦈨'), + (0x2F973, 'M', u'𦉇'), + (0x2F974, 'M', u'䍙'), + (0x2F975, 'M', u'𦋙'), + (0x2F976, 'M', u'罺'), + (0x2F977, 'M', u'𦌾'), + (0x2F978, 'M', u'羕'), + (0x2F979, 'M', u'翺'), + (0x2F97A, 'M', u'者'), + (0x2F97B, 'M', u'𦓚'), + (0x2F97C, 'M', u'𦔣'), + (0x2F97D, 'M', u'聠'), + (0x2F97E, 'M', u'𦖨'), + (0x2F97F, 'M', u'聰'), + (0x2F980, 'M', u'𣍟'), + (0x2F981, 'M', u'䏕'), + (0x2F982, 'M', u'育'), + (0x2F983, 'M', u'脃'), + (0x2F984, 'M', u'䐋'), + (0x2F985, 'M', u'脾'), + (0x2F986, 'M', u'媵'), + (0x2F987, 'M', u'𦞧'), + (0x2F988, 'M', u'𦞵'), + (0x2F989, 'M', u'𣎓'), + (0x2F98A, 'M', u'𣎜'), + (0x2F98B, 'M', u'舁'), + (0x2F98C, 'M', u'舄'), + (0x2F98D, 'M', u'辞'), + ] + +def _seg_78(): + return [ + (0x2F98E, 'M', u'䑫'), + (0x2F98F, 'M', u'芑'), + (0x2F990, 'M', u'芋'), + (0x2F991, 'M', u'芝'), + (0x2F992, 'M', u'劳'), + (0x2F993, 'M', u'花'), + (0x2F994, 'M', u'芳'), + (0x2F995, 'M', u'芽'), + (0x2F996, 'M', u'苦'), + (0x2F997, 'M', u'𦬼'), + (0x2F998, 'M', u'若'), + (0x2F999, 'M', u'茝'), + (0x2F99A, 'M', u'荣'), + (0x2F99B, 'M', u'莭'), + (0x2F99C, 'M', u'茣'), + (0x2F99D, 'M', u'莽'), + (0x2F99E, 'M', u'菧'), + (0x2F99F, 'M', u'著'), + (0x2F9A0, 'M', u'荓'), + (0x2F9A1, 'M', u'菊'), + (0x2F9A2, 'M', u'菌'), + (0x2F9A3, 'M', u'菜'), + (0x2F9A4, 'M', u'𦰶'), + (0x2F9A5, 'M', u'𦵫'), + (0x2F9A6, 'M', u'𦳕'), + (0x2F9A7, 'M', u'䔫'), + (0x2F9A8, 'M', u'蓱'), + (0x2F9A9, 'M', u'蓳'), + (0x2F9AA, 'M', u'蔖'), + (0x2F9AB, 'M', u'𧏊'), + (0x2F9AC, 'M', u'蕤'), + (0x2F9AD, 'M', u'𦼬'), + (0x2F9AE, 'M', u'䕝'), + (0x2F9AF, 'M', u'䕡'), + (0x2F9B0, 'M', u'𦾱'), + (0x2F9B1, 'M', u'𧃒'), + (0x2F9B2, 'M', u'䕫'), + (0x2F9B3, 'M', u'虐'), + (0x2F9B4, 'M', u'虜'), + (0x2F9B5, 'M', u'虧'), + (0x2F9B6, 'M', u'虩'), + (0x2F9B7, 'M', u'蚩'), + (0x2F9B8, 'M', u'蚈'), + (0x2F9B9, 'M', u'蜎'), + (0x2F9BA, 'M', u'蛢'), + (0x2F9BB, 'M', u'蝹'), + (0x2F9BC, 'M', u'蜨'), + (0x2F9BD, 'M', u'蝫'), + (0x2F9BE, 'M', u'螆'), + (0x2F9BF, 'X'), + (0x2F9C0, 'M', u'蟡'), + (0x2F9C1, 'M', u'蠁'), + (0x2F9C2, 'M', u'䗹'), + (0x2F9C3, 'M', u'衠'), + (0x2F9C4, 'M', u'衣'), + (0x2F9C5, 'M', u'𧙧'), + (0x2F9C6, 'M', u'裗'), + (0x2F9C7, 'M', u'裞'), + (0x2F9C8, 'M', u'䘵'), + (0x2F9C9, 'M', u'裺'), + (0x2F9CA, 'M', u'㒻'), + (0x2F9CB, 'M', u'𧢮'), + (0x2F9CC, 'M', u'𧥦'), + (0x2F9CD, 'M', u'䚾'), + (0x2F9CE, 'M', u'䛇'), + (0x2F9CF, 'M', u'誠'), + (0x2F9D0, 'M', u'諭'), + (0x2F9D1, 'M', u'變'), + (0x2F9D2, 'M', u'豕'), + (0x2F9D3, 'M', u'𧲨'), + (0x2F9D4, 'M', u'貫'), + (0x2F9D5, 'M', u'賁'), + (0x2F9D6, 'M', u'贛'), + (0x2F9D7, 'M', u'起'), + (0x2F9D8, 'M', u'𧼯'), + (0x2F9D9, 'M', u'𠠄'), + (0x2F9DA, 'M', u'跋'), + (0x2F9DB, 'M', u'趼'), + (0x2F9DC, 'M', u'跰'), + (0x2F9DD, 'M', u'𠣞'), + (0x2F9DE, 'M', u'軔'), + (0x2F9DF, 'M', u'輸'), + (0x2F9E0, 'M', u'𨗒'), + (0x2F9E1, 'M', u'𨗭'), + (0x2F9E2, 'M', u'邔'), + (0x2F9E3, 'M', u'郱'), + (0x2F9E4, 'M', u'鄑'), + (0x2F9E5, 'M', u'𨜮'), + (0x2F9E6, 'M', u'鄛'), + (0x2F9E7, 'M', u'鈸'), + (0x2F9E8, 'M', u'鋗'), + (0x2F9E9, 'M', u'鋘'), + (0x2F9EA, 'M', u'鉼'), + (0x2F9EB, 'M', u'鏹'), + (0x2F9EC, 'M', u'鐕'), + (0x2F9ED, 'M', u'𨯺'), + (0x2F9EE, 'M', u'開'), + (0x2F9EF, 'M', u'䦕'), + (0x2F9F0, 'M', u'閷'), + (0x2F9F1, 'M', u'𨵷'), + ] + +def _seg_79(): + return [ + (0x2F9F2, 'M', u'䧦'), + (0x2F9F3, 'M', u'雃'), + (0x2F9F4, 'M', u'嶲'), + (0x2F9F5, 'M', u'霣'), + (0x2F9F6, 'M', u'𩅅'), + (0x2F9F7, 'M', u'𩈚'), + (0x2F9F8, 'M', u'䩮'), + (0x2F9F9, 'M', u'䩶'), + (0x2F9FA, 'M', u'韠'), + (0x2F9FB, 'M', u'𩐊'), + (0x2F9FC, 'M', u'䪲'), + (0x2F9FD, 'M', u'𩒖'), + (0x2F9FE, 'M', u'頋'), + (0x2FA00, 'M', u'頩'), + (0x2FA01, 'M', u'𩖶'), + (0x2FA02, 'M', u'飢'), + (0x2FA03, 'M', u'䬳'), + (0x2FA04, 'M', u'餩'), + (0x2FA05, 'M', u'馧'), + (0x2FA06, 'M', u'駂'), + (0x2FA07, 'M', u'駾'), + (0x2FA08, 'M', u'䯎'), + (0x2FA09, 'M', u'𩬰'), + (0x2FA0A, 'M', u'鬒'), + (0x2FA0B, 'M', u'鱀'), + (0x2FA0C, 'M', u'鳽'), + (0x2FA0D, 'M', u'䳎'), + (0x2FA0E, 'M', u'䳭'), + (0x2FA0F, 'M', u'鵧'), + (0x2FA10, 'M', u'𪃎'), + (0x2FA11, 'M', u'䳸'), + (0x2FA12, 'M', u'𪄅'), + (0x2FA13, 'M', u'𪈎'), + (0x2FA14, 'M', u'𪊑'), + (0x2FA15, 'M', u'麻'), + (0x2FA16, 'M', u'䵖'), + (0x2FA17, 'M', u'黹'), + (0x2FA18, 'M', u'黾'), + (0x2FA19, 'M', u'鼅'), + (0x2FA1A, 'M', u'鼏'), + (0x2FA1B, 'M', u'鼖'), + (0x2FA1C, 'M', u'鼻'), + (0x2FA1D, 'M', u'𪘀'), + (0x2FA1E, 'X'), + (0x30000, 'V'), + (0x3134B, 'X'), + (0xE0100, 'I'), + (0xE01F0, 'X'), + ] + +uts46data = tuple( + _seg_0() + + _seg_1() + + _seg_2() + + _seg_3() + + _seg_4() + + _seg_5() + + _seg_6() + + _seg_7() + + _seg_8() + + _seg_9() + + _seg_10() + + _seg_11() + + _seg_12() + + _seg_13() + + _seg_14() + + _seg_15() + + _seg_16() + + _seg_17() + + _seg_18() + + _seg_19() + + _seg_20() + + _seg_21() + + _seg_22() + + _seg_23() + + _seg_24() + + _seg_25() + + _seg_26() + + _seg_27() + + _seg_28() + + _seg_29() + + _seg_30() + + _seg_31() + + _seg_32() + + _seg_33() + + _seg_34() + + _seg_35() + + _seg_36() + + _seg_37() + + _seg_38() + + _seg_39() + + _seg_40() + + _seg_41() + + _seg_42() + + _seg_43() + + _seg_44() + + _seg_45() + + _seg_46() + + _seg_47() + + _seg_48() + + _seg_49() + + _seg_50() + + _seg_51() + + _seg_52() + + _seg_53() + + _seg_54() + + _seg_55() + + _seg_56() + + _seg_57() + + _seg_58() + + _seg_59() + + _seg_60() + + _seg_61() + + _seg_62() + + _seg_63() + + _seg_64() + + _seg_65() + + _seg_66() + + _seg_67() + + _seg_68() + + _seg_69() + + _seg_70() + + _seg_71() + + _seg_72() + + _seg_73() + + _seg_74() + + _seg_75() + + _seg_76() + + _seg_77() + + _seg_78() + + _seg_79() +) diff --git a/openpype/vendor/python/python_2/requests/__init__.py b/openpype/vendor/python/python_2/requests/__init__.py new file mode 100644 index 0000000000..53a5b42af6 --- /dev/null +++ b/openpype/vendor/python/python_2/requests/__init__.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- + +# __ +# /__) _ _ _ _ _/ _ +# / ( (- (/ (/ (- _) / _) +# / + +""" +Requests HTTP Library +~~~~~~~~~~~~~~~~~~~~~ + +Requests is an HTTP library, written in Python, for human beings. +Basic GET usage: + + >>> import requests + >>> r = requests.get('https://www.python.org') + >>> r.status_code + 200 + >>> b'Python is a programming language' in r.content + True + +... or POST: + + >>> payload = dict(key1='value1', key2='value2') + >>> r = requests.post('https://httpbin.org/post', data=payload) + >>> print(r.text) + { + ... + "form": { + "key1": "value1", + "key2": "value2" + }, + ... + } + +The other HTTP methods are supported - see `requests.api`. Full documentation +is at . + +:copyright: (c) 2017 by Kenneth Reitz. +:license: Apache 2.0, see LICENSE for more details. +""" + +import urllib3 +import warnings +from .exceptions import RequestsDependencyWarning + +try: + from charset_normalizer import __version__ as charset_normalizer_version +except ImportError: + charset_normalizer_version = None + +try: + from chardet import __version__ as chardet_version +except ImportError: + chardet_version = None + +def check_compatibility(urllib3_version, chardet_version, charset_normalizer_version): + urllib3_version = urllib3_version.split('.') + assert urllib3_version != ['dev'] # Verify urllib3 isn't installed from git. + + # Sometimes, urllib3 only reports its version as 16.1. + if len(urllib3_version) == 2: + urllib3_version.append('0') + + # Check urllib3 for compatibility. + major, minor, patch = urllib3_version # noqa: F811 + major, minor, patch = int(major), int(minor), int(patch) + # urllib3 >= 1.21.1, <= 1.26 + assert major == 1 + assert minor >= 21 + assert minor <= 26 + + # Check charset_normalizer for compatibility. + if chardet_version: + major, minor, patch = chardet_version.split('.')[:3] + major, minor, patch = int(major), int(minor), int(patch) + # chardet_version >= 3.0.2, < 5.0.0 + assert (3, 0, 2) <= (major, minor, patch) < (5, 0, 0) + elif charset_normalizer_version: + major, minor, patch = charset_normalizer_version.split('.')[:3] + major, minor, patch = int(major), int(minor), int(patch) + # charset_normalizer >= 2.0.0 < 3.0.0 + assert (2, 0, 0) <= (major, minor, patch) < (3, 0, 0) + else: + raise Exception("You need either charset_normalizer or chardet installed") + +def _check_cryptography(cryptography_version): + # cryptography < 1.3.4 + try: + cryptography_version = list(map(int, cryptography_version.split('.'))) + except ValueError: + return + + if cryptography_version < [1, 3, 4]: + warning = 'Old version of cryptography ({}) may cause slowdown.'.format(cryptography_version) + warnings.warn(warning, RequestsDependencyWarning) + +# Check imported dependencies for compatibility. +try: + check_compatibility(urllib3.__version__, chardet_version, charset_normalizer_version) +except (AssertionError, ValueError): + warnings.warn("urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported " + "version!".format(urllib3.__version__, chardet_version, charset_normalizer_version), + RequestsDependencyWarning) + +# Attempt to enable urllib3's fallback for SNI support +# if the standard library doesn't support SNI or the +# 'ssl' library isn't available. +try: + try: + import ssl + except ImportError: + ssl = None + + if not getattr(ssl, "HAS_SNI", False): + from urllib3.contrib import pyopenssl + pyopenssl.inject_into_urllib3() + + # Check cryptography version + from cryptography import __version__ as cryptography_version + _check_cryptography(cryptography_version) +except ImportError: + pass + +# urllib3's DependencyWarnings should be silenced. +from urllib3.exceptions import DependencyWarning +warnings.simplefilter('ignore', DependencyWarning) + +from .__version__ import __title__, __description__, __url__, __version__ +from .__version__ import __build__, __author__, __author_email__, __license__ +from .__version__ import __copyright__, __cake__ + +from . import utils +from . import packages +from .models import Request, Response, PreparedRequest +from .api import request, get, head, post, patch, put, delete, options +from .sessions import session, Session +from .status_codes import codes +from .exceptions import ( + RequestException, Timeout, URLRequired, + TooManyRedirects, HTTPError, ConnectionError, + FileModeWarning, ConnectTimeout, ReadTimeout, JSONDecodeError +) + +# Set default logging handler to avoid "No handler found" warnings. +import logging +from logging import NullHandler + +logging.getLogger(__name__).addHandler(NullHandler()) + +# FileModeWarnings go off per the default. +warnings.simplefilter('default', FileModeWarning, append=True) diff --git a/openpype/vendor/python/python_2/requests/__version__.py b/openpype/vendor/python/python_2/requests/__version__.py new file mode 100644 index 0000000000..e973b03b5f --- /dev/null +++ b/openpype/vendor/python/python_2/requests/__version__.py @@ -0,0 +1,14 @@ +# .-. .-. .-. . . .-. .-. .-. .-. +# |( |- |.| | | |- `-. | `-. +# ' ' `-' `-`.`-' `-' `-' ' `-' + +__title__ = 'requests' +__description__ = 'Python HTTP for Humans.' +__url__ = 'https://requests.readthedocs.io' +__version__ = '2.27.1' +__build__ = 0x022701 +__author__ = 'Kenneth Reitz' +__author_email__ = 'me@kennethreitz.org' +__license__ = 'Apache 2.0' +__copyright__ = 'Copyright 2022 Kenneth Reitz' +__cake__ = u'\u2728 \U0001f370 \u2728' diff --git a/openpype/vendor/python/python_2/requests/_internal_utils.py b/openpype/vendor/python/python_2/requests/_internal_utils.py new file mode 100644 index 0000000000..759d9a56ba --- /dev/null +++ b/openpype/vendor/python/python_2/requests/_internal_utils.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +""" +requests._internal_utils +~~~~~~~~~~~~~~ + +Provides utility functions that are consumed internally by Requests +which depend on extremely few external helpers (such as compat) +""" + +from .compat import is_py2, builtin_str, str + + +def to_native_string(string, encoding='ascii'): + """Given a string object, regardless of type, returns a representation of + that string in the native string type, encoding and decoding where + necessary. This assumes ASCII unless told otherwise. + """ + if isinstance(string, builtin_str): + out = string + else: + if is_py2: + out = string.encode(encoding) + else: + out = string.decode(encoding) + + return out + + +def unicode_is_ascii(u_string): + """Determine if unicode string only contains ASCII characters. + + :param str u_string: unicode string to check. Must be unicode + and not Python 2 `str`. + :rtype: bool + """ + assert isinstance(u_string, str) + try: + u_string.encode('ascii') + return True + except UnicodeEncodeError: + return False diff --git a/openpype/vendor/python/python_2/requests/adapters.py b/openpype/vendor/python/python_2/requests/adapters.py new file mode 100644 index 0000000000..fe22ff450e --- /dev/null +++ b/openpype/vendor/python/python_2/requests/adapters.py @@ -0,0 +1,538 @@ +# -*- coding: utf-8 -*- + +""" +requests.adapters +~~~~~~~~~~~~~~~~~ + +This module contains the transport adapters that Requests uses to define +and maintain connections. +""" + +import os.path +import socket + +from urllib3.poolmanager import PoolManager, proxy_from_url +from urllib3.response import HTTPResponse +from urllib3.util import parse_url +from urllib3.util import Timeout as TimeoutSauce +from urllib3.util.retry import Retry +from urllib3.exceptions import ClosedPoolError +from urllib3.exceptions import ConnectTimeoutError +from urllib3.exceptions import HTTPError as _HTTPError +from urllib3.exceptions import InvalidHeader as _InvalidHeader +from urllib3.exceptions import MaxRetryError +from urllib3.exceptions import NewConnectionError +from urllib3.exceptions import ProxyError as _ProxyError +from urllib3.exceptions import ProtocolError +from urllib3.exceptions import ReadTimeoutError +from urllib3.exceptions import SSLError as _SSLError +from urllib3.exceptions import ResponseError +from urllib3.exceptions import LocationValueError + +from .models import Response +from .compat import urlparse, basestring +from .utils import (DEFAULT_CA_BUNDLE_PATH, extract_zipped_paths, + get_encoding_from_headers, prepend_scheme_if_needed, + get_auth_from_url, urldefragauth, select_proxy) +from .structures import CaseInsensitiveDict +from .cookies import extract_cookies_to_jar +from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError, + ProxyError, RetryError, InvalidSchema, InvalidProxyURL, + InvalidURL, InvalidHeader) +from .auth import _basic_auth_str + +try: + from urllib3.contrib.socks import SOCKSProxyManager +except ImportError: + def SOCKSProxyManager(*args, **kwargs): + raise InvalidSchema("Missing dependencies for SOCKS support.") + +DEFAULT_POOLBLOCK = False +DEFAULT_POOLSIZE = 10 +DEFAULT_RETRIES = 0 +DEFAULT_POOL_TIMEOUT = None + + +class BaseAdapter(object): + """The Base Transport Adapter""" + + def __init__(self): + super(BaseAdapter, self).__init__() + + def send(self, request, stream=False, timeout=None, verify=True, + cert=None, proxies=None): + """Sends PreparedRequest object. Returns Response object. + + :param request: The :class:`PreparedRequest ` being sent. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + """ + raise NotImplementedError + + def close(self): + """Cleans up adapter specific items.""" + raise NotImplementedError + + +class HTTPAdapter(BaseAdapter): + """The built-in HTTP Adapter for urllib3. + + Provides a general-case interface for Requests sessions to contact HTTP and + HTTPS urls by implementing the Transport Adapter interface. This class will + usually be created by the :class:`Session ` class under the + covers. + + :param pool_connections: The number of urllib3 connection pools to cache. + :param pool_maxsize: The maximum number of connections to save in the pool. + :param max_retries: The maximum number of retries each connection + should attempt. Note, this applies only to failed DNS lookups, socket + connections and connection timeouts, never to requests where data has + made it to the server. By default, Requests does not retry failed + connections. If you need granular control over the conditions under + which we retry a request, import urllib3's ``Retry`` class and pass + that instead. + :param pool_block: Whether the connection pool should block for connections. + + Usage:: + + >>> import requests + >>> s = requests.Session() + >>> a = requests.adapters.HTTPAdapter(max_retries=3) + >>> s.mount('http://', a) + """ + __attrs__ = ['max_retries', 'config', '_pool_connections', '_pool_maxsize', + '_pool_block'] + + def __init__(self, pool_connections=DEFAULT_POOLSIZE, + pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES, + pool_block=DEFAULT_POOLBLOCK): + if max_retries == DEFAULT_RETRIES: + self.max_retries = Retry(0, read=False) + else: + self.max_retries = Retry.from_int(max_retries) + self.config = {} + self.proxy_manager = {} + + super(HTTPAdapter, self).__init__() + + self._pool_connections = pool_connections + self._pool_maxsize = pool_maxsize + self._pool_block = pool_block + + self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block) + + def __getstate__(self): + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state): + # Can't handle by adding 'proxy_manager' to self.__attrs__ because + # self.poolmanager uses a lambda function, which isn't pickleable. + self.proxy_manager = {} + self.config = {} + + for attr, value in state.items(): + setattr(self, attr, value) + + self.init_poolmanager(self._pool_connections, self._pool_maxsize, + block=self._pool_block) + + def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs): + """Initializes a urllib3 PoolManager. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param connections: The number of urllib3 connection pools to cache. + :param maxsize: The maximum number of connections to save in the pool. + :param block: Block when no free connections are available. + :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager. + """ + # save these values for pickling + self._pool_connections = connections + self._pool_maxsize = maxsize + self._pool_block = block + + self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize, + block=block, strict=True, **pool_kwargs) + + def proxy_manager_for(self, proxy, **proxy_kwargs): + """Return urllib3 ProxyManager for the given proxy. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param proxy: The proxy to return a urllib3 ProxyManager for. + :param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager. + :returns: ProxyManager + :rtype: urllib3.ProxyManager + """ + if proxy in self.proxy_manager: + manager = self.proxy_manager[proxy] + elif proxy.lower().startswith('socks'): + username, password = get_auth_from_url(proxy) + manager = self.proxy_manager[proxy] = SOCKSProxyManager( + proxy, + username=username, + password=password, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs + ) + else: + proxy_headers = self.proxy_headers(proxy) + manager = self.proxy_manager[proxy] = proxy_from_url( + proxy, + proxy_headers=proxy_headers, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs) + + return manager + + def cert_verify(self, conn, url, verify, cert): + """Verify a SSL certificate. This method should not be called from user + code, and is only exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param conn: The urllib3 connection object associated with the cert. + :param url: The requested URL. + :param verify: Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: The SSL certificate to verify. + """ + if url.lower().startswith('https') and verify: + + cert_loc = None + + # Allow self-specified cert location. + if verify is not True: + cert_loc = verify + + if not cert_loc: + cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH) + + if not cert_loc or not os.path.exists(cert_loc): + raise IOError("Could not find a suitable TLS CA certificate bundle, " + "invalid path: {}".format(cert_loc)) + + conn.cert_reqs = 'CERT_REQUIRED' + + if not os.path.isdir(cert_loc): + conn.ca_certs = cert_loc + else: + conn.ca_cert_dir = cert_loc + else: + conn.cert_reqs = 'CERT_NONE' + conn.ca_certs = None + conn.ca_cert_dir = None + + if cert: + if not isinstance(cert, basestring): + conn.cert_file = cert[0] + conn.key_file = cert[1] + else: + conn.cert_file = cert + conn.key_file = None + if conn.cert_file and not os.path.exists(conn.cert_file): + raise IOError("Could not find the TLS certificate file, " + "invalid path: {}".format(conn.cert_file)) + if conn.key_file and not os.path.exists(conn.key_file): + raise IOError("Could not find the TLS key file, " + "invalid path: {}".format(conn.key_file)) + + def build_response(self, req, resp): + """Builds a :class:`Response ` object from a urllib3 + response. This should not be called from user code, and is only exposed + for use when subclassing the + :class:`HTTPAdapter ` + + :param req: The :class:`PreparedRequest ` used to generate the response. + :param resp: The urllib3 response object. + :rtype: requests.Response + """ + response = Response() + + # Fallback to None if there's no status_code, for whatever reason. + response.status_code = getattr(resp, 'status', None) + + # Make headers case-insensitive. + response.headers = CaseInsensitiveDict(getattr(resp, 'headers', {})) + + # Set encoding. + response.encoding = get_encoding_from_headers(response.headers) + response.raw = resp + response.reason = response.raw.reason + + if isinstance(req.url, bytes): + response.url = req.url.decode('utf-8') + else: + response.url = req.url + + # Add new cookies from the server. + extract_cookies_to_jar(response.cookies, req, resp) + + # Give the Response some context. + response.request = req + response.connection = self + + return response + + def get_connection(self, url, proxies=None): + """Returns a urllib3 connection for the given URL. This should not be + called from user code, and is only exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param url: The URL to connect to. + :param proxies: (optional) A Requests-style dictionary of proxies used on this request. + :rtype: urllib3.ConnectionPool + """ + proxy = select_proxy(url, proxies) + + if proxy: + proxy = prepend_scheme_if_needed(proxy, 'http') + proxy_url = parse_url(proxy) + if not proxy_url.host: + raise InvalidProxyURL("Please check proxy URL. It is malformed" + " and could be missing the host.") + proxy_manager = self.proxy_manager_for(proxy) + conn = proxy_manager.connection_from_url(url) + else: + # Only scheme should be lower case + parsed = urlparse(url) + url = parsed.geturl() + conn = self.poolmanager.connection_from_url(url) + + return conn + + def close(self): + """Disposes of any internal state. + + Currently, this closes the PoolManager and any active ProxyManager, + which closes any pooled connections. + """ + self.poolmanager.clear() + for proxy in self.proxy_manager.values(): + proxy.clear() + + def request_url(self, request, proxies): + """Obtain the url to use when making the final request. + + If the message is being sent through a HTTP proxy, the full URL has to + be used. Otherwise, we should only use the path portion of the URL. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param request: The :class:`PreparedRequest ` being sent. + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs. + :rtype: str + """ + proxy = select_proxy(request.url, proxies) + scheme = urlparse(request.url).scheme + + is_proxied_http_request = (proxy and scheme != 'https') + using_socks_proxy = False + if proxy: + proxy_scheme = urlparse(proxy).scheme.lower() + using_socks_proxy = proxy_scheme.startswith('socks') + + url = request.path_url + if is_proxied_http_request and not using_socks_proxy: + url = urldefragauth(request.url) + + return url + + def add_headers(self, request, **kwargs): + """Add any headers needed by the connection. As of v2.0 this does + nothing by default, but is left for overriding by users that subclass + the :class:`HTTPAdapter `. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param request: The :class:`PreparedRequest ` to add headers to. + :param kwargs: The keyword arguments from the call to send(). + """ + pass + + def proxy_headers(self, proxy): + """Returns a dictionary of the headers to add to any request sent + through a proxy. This works with urllib3 magic to ensure that they are + correctly sent to the proxy, rather than in a tunnelled request if + CONNECT is being used. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param proxy: The url of the proxy being used for this request. + :rtype: dict + """ + headers = {} + username, password = get_auth_from_url(proxy) + + if username: + headers['Proxy-Authorization'] = _basic_auth_str(username, + password) + + return headers + + def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None): + """Sends PreparedRequest object. Returns Response object. + + :param request: The :class:`PreparedRequest ` being sent. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple or urllib3 Timeout object + :param verify: (optional) Either a boolean, in which case it controls whether + we verify the server's TLS certificate, or a string, in which case it + must be a path to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + :rtype: requests.Response + """ + + try: + conn = self.get_connection(request.url, proxies) + except LocationValueError as e: + raise InvalidURL(e, request=request) + + self.cert_verify(conn, request.url, verify, cert) + url = self.request_url(request, proxies) + self.add_headers(request, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies) + + chunked = not (request.body is None or 'Content-Length' in request.headers) + + if isinstance(timeout, tuple): + try: + connect, read = timeout + timeout = TimeoutSauce(connect=connect, read=read) + except ValueError as e: + # this may raise a string formatting error. + err = ("Invalid timeout {}. Pass a (connect, read) " + "timeout tuple, or a single float to set " + "both timeouts to the same value".format(timeout)) + raise ValueError(err) + elif isinstance(timeout, TimeoutSauce): + pass + else: + timeout = TimeoutSauce(connect=timeout, read=timeout) + + try: + if not chunked: + resp = conn.urlopen( + method=request.method, + url=url, + body=request.body, + headers=request.headers, + redirect=False, + assert_same_host=False, + preload_content=False, + decode_content=False, + retries=self.max_retries, + timeout=timeout + ) + + # Send the request. + else: + if hasattr(conn, 'proxy_pool'): + conn = conn.proxy_pool + + low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT) + + try: + skip_host = 'Host' in request.headers + low_conn.putrequest(request.method, + url, + skip_accept_encoding=True, + skip_host=skip_host) + + for header, value in request.headers.items(): + low_conn.putheader(header, value) + + low_conn.endheaders() + + for i in request.body: + low_conn.send(hex(len(i))[2:].encode('utf-8')) + low_conn.send(b'\r\n') + low_conn.send(i) + low_conn.send(b'\r\n') + low_conn.send(b'0\r\n\r\n') + + # Receive the response from the server + try: + # For Python 2.7, use buffering of HTTP responses + r = low_conn.getresponse(buffering=True) + except TypeError: + # For compatibility with Python 3.3+ + r = low_conn.getresponse() + + resp = HTTPResponse.from_httplib( + r, + pool=conn, + connection=low_conn, + preload_content=False, + decode_content=False + ) + except: + # If we hit any problems here, clean up the connection. + # Then, reraise so that we can handle the actual exception. + low_conn.close() + raise + + except (ProtocolError, socket.error) as err: + raise ConnectionError(err, request=request) + + except MaxRetryError as e: + if isinstance(e.reason, ConnectTimeoutError): + # TODO: Remove this in 3.0.0: see #2811 + if not isinstance(e.reason, NewConnectionError): + raise ConnectTimeout(e, request=request) + + if isinstance(e.reason, ResponseError): + raise RetryError(e, request=request) + + if isinstance(e.reason, _ProxyError): + raise ProxyError(e, request=request) + + if isinstance(e.reason, _SSLError): + # This branch is for urllib3 v1.22 and later. + raise SSLError(e, request=request) + + raise ConnectionError(e, request=request) + + except ClosedPoolError as e: + raise ConnectionError(e, request=request) + + except _ProxyError as e: + raise ProxyError(e) + + except (_SSLError, _HTTPError) as e: + if isinstance(e, _SSLError): + # This branch is for urllib3 versions earlier than v1.22 + raise SSLError(e, request=request) + elif isinstance(e, ReadTimeoutError): + raise ReadTimeout(e, request=request) + elif isinstance(e, _InvalidHeader): + raise InvalidHeader(e, request=request) + else: + raise + + return self.build_response(request, resp) diff --git a/openpype/vendor/python/python_2/requests/api.py b/openpype/vendor/python/python_2/requests/api.py new file mode 100644 index 0000000000..4cba90eefe --- /dev/null +++ b/openpype/vendor/python/python_2/requests/api.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- + +""" +requests.api +~~~~~~~~~~~~ + +This module implements the Requests API. + +:copyright: (c) 2012 by Kenneth Reitz. +:license: Apache2, see LICENSE for more details. +""" + +from . import sessions + + +def request(method, url, **kwargs): + """Constructs and sends a :class:`Request `. + + :param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the query string for the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. + :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload. + ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')`` + or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string + defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers + to add for the file. + :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How many seconds to wait for the server to send data + before giving up, as a float, or a :ref:`(connect timeout, read + timeout) ` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. + :param stream: (optional) if ``False``, the response content will be immediately downloaded. + :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. + :return: :class:`Response ` object + :rtype: requests.Response + + Usage:: + + >>> import requests + >>> req = requests.request('GET', 'https://httpbin.org/get') + >>> req + + """ + + # By using the 'with' statement we are sure the session is closed, thus we + # avoid leaving sockets open which can trigger a ResourceWarning in some + # cases, and look like a memory leak in others. + with sessions.Session() as session: + return session.request(method=method, url=url, **kwargs) + + +def get(url, params=None, **kwargs): + r"""Sends a GET request. + + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the query string for the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request('get', url, params=params, **kwargs) + + +def options(url, **kwargs): + r"""Sends an OPTIONS request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request('options', url, **kwargs) + + +def head(url, **kwargs): + r"""Sends a HEAD request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. If + `allow_redirects` is not provided, it will be set to `False` (as + opposed to the default :meth:`request` behavior). + :return: :class:`Response ` object + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', False) + return request('head', url, **kwargs) + + +def post(url, data=None, json=None, **kwargs): + r"""Sends a POST request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request('post', url, data=data, json=json, **kwargs) + + +def put(url, data=None, **kwargs): + r"""Sends a PUT request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request('put', url, data=data, **kwargs) + + +def patch(url, data=None, **kwargs): + r"""Sends a PATCH request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request('patch', url, data=data, **kwargs) + + +def delete(url, **kwargs): + r"""Sends a DELETE request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request('delete', url, **kwargs) diff --git a/openpype/vendor/python/python_2/requests/auth.py b/openpype/vendor/python/python_2/requests/auth.py new file mode 100644 index 0000000000..eeface39ae --- /dev/null +++ b/openpype/vendor/python/python_2/requests/auth.py @@ -0,0 +1,305 @@ +# -*- coding: utf-8 -*- + +""" +requests.auth +~~~~~~~~~~~~~ + +This module contains the authentication handlers for Requests. +""" + +import os +import re +import time +import hashlib +import threading +import warnings + +from base64 import b64encode + +from .compat import urlparse, str, basestring +from .cookies import extract_cookies_to_jar +from ._internal_utils import to_native_string +from .utils import parse_dict_header + +CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded' +CONTENT_TYPE_MULTI_PART = 'multipart/form-data' + + +def _basic_auth_str(username, password): + """Returns a Basic Auth string.""" + + # "I want us to put a big-ol' comment on top of it that + # says that this behaviour is dumb but we need to preserve + # it because people are relying on it." + # - Lukasa + # + # These are here solely to maintain backwards compatibility + # for things like ints. This will be removed in 3.0.0. + if not isinstance(username, basestring): + warnings.warn( + "Non-string usernames will no longer be supported in Requests " + "3.0.0. Please convert the object you've passed in ({!r}) to " + "a string or bytes object in the near future to avoid " + "problems.".format(username), + category=DeprecationWarning, + ) + username = str(username) + + if not isinstance(password, basestring): + warnings.warn( + "Non-string passwords will no longer be supported in Requests " + "3.0.0. Please convert the object you've passed in ({!r}) to " + "a string or bytes object in the near future to avoid " + "problems.".format(type(password)), + category=DeprecationWarning, + ) + password = str(password) + # -- End Removal -- + + if isinstance(username, str): + username = username.encode('latin1') + + if isinstance(password, str): + password = password.encode('latin1') + + authstr = 'Basic ' + to_native_string( + b64encode(b':'.join((username, password))).strip() + ) + + return authstr + + +class AuthBase(object): + """Base class that all auth implementations derive from""" + + def __call__(self, r): + raise NotImplementedError('Auth hooks must be callable.') + + +class HTTPBasicAuth(AuthBase): + """Attaches HTTP Basic Authentication to the given Request object.""" + + def __init__(self, username, password): + self.username = username + self.password = password + + def __eq__(self, other): + return all([ + self.username == getattr(other, 'username', None), + self.password == getattr(other, 'password', None) + ]) + + def __ne__(self, other): + return not self == other + + def __call__(self, r): + r.headers['Authorization'] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPProxyAuth(HTTPBasicAuth): + """Attaches HTTP Proxy Authentication to a given Request object.""" + + def __call__(self, r): + r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPDigestAuth(AuthBase): + """Attaches HTTP Digest Authentication to the given Request object.""" + + def __init__(self, username, password): + self.username = username + self.password = password + # Keep state in per-thread local storage + self._thread_local = threading.local() + + def init_per_thread_state(self): + # Ensure state is initialized just once per-thread + if not hasattr(self._thread_local, 'init'): + self._thread_local.init = True + self._thread_local.last_nonce = '' + self._thread_local.nonce_count = 0 + self._thread_local.chal = {} + self._thread_local.pos = None + self._thread_local.num_401_calls = None + + def build_digest_header(self, method, url): + """ + :rtype: str + """ + + realm = self._thread_local.chal['realm'] + nonce = self._thread_local.chal['nonce'] + qop = self._thread_local.chal.get('qop') + algorithm = self._thread_local.chal.get('algorithm') + opaque = self._thread_local.chal.get('opaque') + hash_utf8 = None + + if algorithm is None: + _algorithm = 'MD5' + else: + _algorithm = algorithm.upper() + # lambdas assume digest modules are imported at the top level + if _algorithm == 'MD5' or _algorithm == 'MD5-SESS': + def md5_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.md5(x).hexdigest() + hash_utf8 = md5_utf8 + elif _algorithm == 'SHA': + def sha_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.sha1(x).hexdigest() + hash_utf8 = sha_utf8 + elif _algorithm == 'SHA-256': + def sha256_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.sha256(x).hexdigest() + hash_utf8 = sha256_utf8 + elif _algorithm == 'SHA-512': + def sha512_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.sha512(x).hexdigest() + hash_utf8 = sha512_utf8 + + KD = lambda s, d: hash_utf8("%s:%s" % (s, d)) + + if hash_utf8 is None: + return None + + # XXX not implemented yet + entdig = None + p_parsed = urlparse(url) + #: path is request-uri defined in RFC 2616 which should not be empty + path = p_parsed.path or "/" + if p_parsed.query: + path += '?' + p_parsed.query + + A1 = '%s:%s:%s' % (self.username, realm, self.password) + A2 = '%s:%s' % (method, path) + + HA1 = hash_utf8(A1) + HA2 = hash_utf8(A2) + + if nonce == self._thread_local.last_nonce: + self._thread_local.nonce_count += 1 + else: + self._thread_local.nonce_count = 1 + ncvalue = '%08x' % self._thread_local.nonce_count + s = str(self._thread_local.nonce_count).encode('utf-8') + s += nonce.encode('utf-8') + s += time.ctime().encode('utf-8') + s += os.urandom(8) + + cnonce = (hashlib.sha1(s).hexdigest()[:16]) + if _algorithm == 'MD5-SESS': + HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce)) + + if not qop: + respdig = KD(HA1, "%s:%s" % (nonce, HA2)) + elif qop == 'auth' or 'auth' in qop.split(','): + noncebit = "%s:%s:%s:%s:%s" % ( + nonce, ncvalue, cnonce, 'auth', HA2 + ) + respdig = KD(HA1, noncebit) + else: + # XXX handle auth-int. + return None + + self._thread_local.last_nonce = nonce + + # XXX should the partial digests be encoded too? + base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \ + 'response="%s"' % (self.username, realm, nonce, path, respdig) + if opaque: + base += ', opaque="%s"' % opaque + if algorithm: + base += ', algorithm="%s"' % algorithm + if entdig: + base += ', digest="%s"' % entdig + if qop: + base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce) + + return 'Digest %s' % (base) + + def handle_redirect(self, r, **kwargs): + """Reset num_401_calls counter on redirects.""" + if r.is_redirect: + self._thread_local.num_401_calls = 1 + + def handle_401(self, r, **kwargs): + """ + Takes the given response and tries digest-auth, if needed. + + :rtype: requests.Response + """ + + # If response is not 4xx, do not auth + # See https://github.com/psf/requests/issues/3772 + if not 400 <= r.status_code < 500: + self._thread_local.num_401_calls = 1 + return r + + if self._thread_local.pos is not None: + # Rewind the file position indicator of the body to where + # it was to resend the request. + r.request.body.seek(self._thread_local.pos) + s_auth = r.headers.get('www-authenticate', '') + + if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2: + + self._thread_local.num_401_calls += 1 + pat = re.compile(r'digest ', flags=re.IGNORECASE) + self._thread_local.chal = parse_dict_header(pat.sub('', s_auth, count=1)) + + # Consume content and release the original connection + # to allow our new request to reuse the same one. + r.content + r.close() + prep = r.request.copy() + extract_cookies_to_jar(prep._cookies, r.request, r.raw) + prep.prepare_cookies(prep._cookies) + + prep.headers['Authorization'] = self.build_digest_header( + prep.method, prep.url) + _r = r.connection.send(prep, **kwargs) + _r.history.append(r) + _r.request = prep + + return _r + + self._thread_local.num_401_calls = 1 + return r + + def __call__(self, r): + # Initialize per-thread state, if needed + self.init_per_thread_state() + # If we have a saved nonce, skip the 401 + if self._thread_local.last_nonce: + r.headers['Authorization'] = self.build_digest_header(r.method, r.url) + try: + self._thread_local.pos = r.body.tell() + except AttributeError: + # In the case of HTTPDigestAuth being reused and the body of + # the previous request was a file-like object, pos has the + # file position of the previous body. Ensure it's set to + # None. + self._thread_local.pos = None + r.register_hook('response', self.handle_401) + r.register_hook('response', self.handle_redirect) + self._thread_local.num_401_calls = 1 + + return r + + def __eq__(self, other): + return all([ + self.username == getattr(other, 'username', None), + self.password == getattr(other, 'password', None) + ]) + + def __ne__(self, other): + return not self == other diff --git a/openpype/vendor/python/python_2/requests/certs.py b/openpype/vendor/python/python_2/requests/certs.py new file mode 100644 index 0000000000..d1a378d787 --- /dev/null +++ b/openpype/vendor/python/python_2/requests/certs.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +requests.certs +~~~~~~~~~~~~~~ + +This module returns the preferred default CA certificate bundle. There is +only one — the one from the certifi package. + +If you are packaging Requests, e.g., for a Linux distribution or a managed +environment, you can change the definition of where() to return a separately +packaged CA bundle. +""" +from certifi import where + +if __name__ == '__main__': + print(where()) diff --git a/openpype/vendor/python/python_2/requests/compat.py b/openpype/vendor/python/python_2/requests/compat.py new file mode 100644 index 0000000000..029ae62ac3 --- /dev/null +++ b/openpype/vendor/python/python_2/requests/compat.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- + +""" +requests.compat +~~~~~~~~~~~~~~~ + +This module handles import compatibility issues between Python 2 and +Python 3. +""" + +try: + import chardet +except ImportError: + import charset_normalizer as chardet + +import sys + +# ------- +# Pythons +# ------- + +# Syntax sugar. +_ver = sys.version_info + +#: Python 2.x? +is_py2 = (_ver[0] == 2) + +#: Python 3.x? +is_py3 = (_ver[0] == 3) + +has_simplejson = False +try: + import simplejson as json + has_simplejson = True +except ImportError: + import json + +# --------- +# Specifics +# --------- + +if is_py2: + from urllib import ( + quote, unquote, quote_plus, unquote_plus, urlencode, getproxies, + proxy_bypass, proxy_bypass_environment, getproxies_environment) + from urlparse import urlparse, urlunparse, urljoin, urlsplit, urldefrag + from urllib2 import parse_http_list + import cookielib + from Cookie import Morsel + from StringIO import StringIO + # Keep OrderedDict for backwards compatibility. + from collections import Callable, Mapping, MutableMapping, OrderedDict + + builtin_str = str + bytes = str + str = unicode + basestring = basestring + numeric_types = (int, long, float) + integer_types = (int, long) + JSONDecodeError = ValueError + +elif is_py3: + from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag + from urllib.request import parse_http_list, getproxies, proxy_bypass, proxy_bypass_environment, getproxies_environment + from http import cookiejar as cookielib + from http.cookies import Morsel + from io import StringIO + # Keep OrderedDict for backwards compatibility. + from collections import OrderedDict + from collections.abc import Callable, Mapping, MutableMapping + if has_simplejson: + from simplejson import JSONDecodeError + else: + from json import JSONDecodeError + + builtin_str = str + str = str + bytes = bytes + basestring = (str, bytes) + numeric_types = (int, float) + integer_types = (int,) diff --git a/openpype/vendor/python/python_2/requests/cookies.py b/openpype/vendor/python/python_2/requests/cookies.py new file mode 100644 index 0000000000..56fccd9c25 --- /dev/null +++ b/openpype/vendor/python/python_2/requests/cookies.py @@ -0,0 +1,549 @@ +# -*- coding: utf-8 -*- + +""" +requests.cookies +~~~~~~~~~~~~~~~~ + +Compatibility code to be able to use `cookielib.CookieJar` with requests. + +requests.utils imports from here, so be careful with imports. +""" + +import copy +import time +import calendar + +from ._internal_utils import to_native_string +from .compat import cookielib, urlparse, urlunparse, Morsel, MutableMapping + +try: + import threading +except ImportError: + import dummy_threading as threading + + +class MockRequest(object): + """Wraps a `requests.Request` to mimic a `urllib2.Request`. + + The code in `cookielib.CookieJar` expects this interface in order to correctly + manage cookie policies, i.e., determine whether a cookie can be set, given the + domains of the request and the cookie. + + The original request object is read-only. The client is responsible for collecting + the new headers via `get_new_headers()` and interpreting them appropriately. You + probably want `get_cookie_header`, defined below. + """ + + def __init__(self, request): + self._r = request + self._new_headers = {} + self.type = urlparse(self._r.url).scheme + + def get_type(self): + return self.type + + def get_host(self): + return urlparse(self._r.url).netloc + + def get_origin_req_host(self): + return self.get_host() + + def get_full_url(self): + # Only return the response's URL if the user hadn't set the Host + # header + if not self._r.headers.get('Host'): + return self._r.url + # If they did set it, retrieve it and reconstruct the expected domain + host = to_native_string(self._r.headers['Host'], encoding='utf-8') + parsed = urlparse(self._r.url) + # Reconstruct the URL as we expect it + return urlunparse([ + parsed.scheme, host, parsed.path, parsed.params, parsed.query, + parsed.fragment + ]) + + def is_unverifiable(self): + return True + + def has_header(self, name): + return name in self._r.headers or name in self._new_headers + + def get_header(self, name, default=None): + return self._r.headers.get(name, self._new_headers.get(name, default)) + + def add_header(self, key, val): + """cookielib has no legitimate use for this method; add it back if you find one.""" + raise NotImplementedError("Cookie headers should be added with add_unredirected_header()") + + def add_unredirected_header(self, name, value): + self._new_headers[name] = value + + def get_new_headers(self): + return self._new_headers + + @property + def unverifiable(self): + return self.is_unverifiable() + + @property + def origin_req_host(self): + return self.get_origin_req_host() + + @property + def host(self): + return self.get_host() + + +class MockResponse(object): + """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`. + + ...what? Basically, expose the parsed HTTP headers from the server response + the way `cookielib` expects to see them. + """ + + def __init__(self, headers): + """Make a MockResponse for `cookielib` to read. + + :param headers: a httplib.HTTPMessage or analogous carrying the headers + """ + self._headers = headers + + def info(self): + return self._headers + + def getheaders(self, name): + self._headers.getheaders(name) + + +def extract_cookies_to_jar(jar, request, response): + """Extract the cookies from the response into a CookieJar. + + :param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar) + :param request: our own requests.Request object + :param response: urllib3.HTTPResponse object + """ + if not (hasattr(response, '_original_response') and + response._original_response): + return + # the _original_response field is the wrapped httplib.HTTPResponse object, + req = MockRequest(request) + # pull out the HTTPMessage with the headers and put it in the mock: + res = MockResponse(response._original_response.msg) + jar.extract_cookies(res, req) + + +def get_cookie_header(jar, request): + """ + Produce an appropriate Cookie header string to be sent with `request`, or None. + + :rtype: str + """ + r = MockRequest(request) + jar.add_cookie_header(r) + return r.get_new_headers().get('Cookie') + + +def remove_cookie_by_name(cookiejar, name, domain=None, path=None): + """Unsets a cookie by name, by default over all domains and paths. + + Wraps CookieJar.clear(), is O(n). + """ + clearables = [] + for cookie in cookiejar: + if cookie.name != name: + continue + if domain is not None and domain != cookie.domain: + continue + if path is not None and path != cookie.path: + continue + clearables.append((cookie.domain, cookie.path, cookie.name)) + + for domain, path, name in clearables: + cookiejar.clear(domain, path, name) + + +class CookieConflictError(RuntimeError): + """There are two cookies that meet the criteria specified in the cookie jar. + Use .get and .set and include domain and path args in order to be more specific. + """ + + +class RequestsCookieJar(cookielib.CookieJar, MutableMapping): + """Compatibility class; is a cookielib.CookieJar, but exposes a dict + interface. + + This is the CookieJar we create by default for requests and sessions that + don't specify one, since some clients may expect response.cookies and + session.cookies to support dict operations. + + Requests does not use the dict interface internally; it's just for + compatibility with external client code. All requests code should work + out of the box with externally provided instances of ``CookieJar``, e.g. + ``LWPCookieJar`` and ``FileCookieJar``. + + Unlike a regular CookieJar, this class is pickleable. + + .. warning:: dictionary operations that are normally O(1) may be O(n). + """ + + def get(self, name, default=None, domain=None, path=None): + """Dict-like get() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + + .. warning:: operation is O(n), not O(1). + """ + try: + return self._find_no_duplicates(name, domain, path) + except KeyError: + return default + + def set(self, name, value, **kwargs): + """Dict-like set() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + """ + # support client code that unsets cookies by assignment of a None value: + if value is None: + remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path')) + return + + if isinstance(value, Morsel): + c = morsel_to_cookie(value) + else: + c = create_cookie(name, value, **kwargs) + self.set_cookie(c) + return c + + def iterkeys(self): + """Dict-like iterkeys() that returns an iterator of names of cookies + from the jar. + + .. seealso:: itervalues() and iteritems(). + """ + for cookie in iter(self): + yield cookie.name + + def keys(self): + """Dict-like keys() that returns a list of names of cookies from the + jar. + + .. seealso:: values() and items(). + """ + return list(self.iterkeys()) + + def itervalues(self): + """Dict-like itervalues() that returns an iterator of values of cookies + from the jar. + + .. seealso:: iterkeys() and iteritems(). + """ + for cookie in iter(self): + yield cookie.value + + def values(self): + """Dict-like values() that returns a list of values of cookies from the + jar. + + .. seealso:: keys() and items(). + """ + return list(self.itervalues()) + + def iteritems(self): + """Dict-like iteritems() that returns an iterator of name-value tuples + from the jar. + + .. seealso:: iterkeys() and itervalues(). + """ + for cookie in iter(self): + yield cookie.name, cookie.value + + def items(self): + """Dict-like items() that returns a list of name-value tuples from the + jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a + vanilla python dict of key value pairs. + + .. seealso:: keys() and values(). + """ + return list(self.iteritems()) + + def list_domains(self): + """Utility method to list all the domains in the jar.""" + domains = [] + for cookie in iter(self): + if cookie.domain not in domains: + domains.append(cookie.domain) + return domains + + def list_paths(self): + """Utility method to list all the paths in the jar.""" + paths = [] + for cookie in iter(self): + if cookie.path not in paths: + paths.append(cookie.path) + return paths + + def multiple_domains(self): + """Returns True if there are multiple domains in the jar. + Returns False otherwise. + + :rtype: bool + """ + domains = [] + for cookie in iter(self): + if cookie.domain is not None and cookie.domain in domains: + return True + domains.append(cookie.domain) + return False # there is only one domain in jar + + def get_dict(self, domain=None, path=None): + """Takes as an argument an optional domain and path and returns a plain + old Python dict of name-value pairs of cookies that meet the + requirements. + + :rtype: dict + """ + dictionary = {} + for cookie in iter(self): + if ( + (domain is None or cookie.domain == domain) and + (path is None or cookie.path == path) + ): + dictionary[cookie.name] = cookie.value + return dictionary + + def __contains__(self, name): + try: + return super(RequestsCookieJar, self).__contains__(name) + except CookieConflictError: + return True + + def __getitem__(self, name): + """Dict-like __getitem__() for compatibility with client code. Throws + exception if there are more than one cookie with name. In that case, + use the more explicit get() method instead. + + .. warning:: operation is O(n), not O(1). + """ + return self._find_no_duplicates(name) + + def __setitem__(self, name, value): + """Dict-like __setitem__ for compatibility with client code. Throws + exception if there is already a cookie of that name in the jar. In that + case, use the more explicit set() method instead. + """ + self.set(name, value) + + def __delitem__(self, name): + """Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s + ``remove_cookie_by_name()``. + """ + remove_cookie_by_name(self, name) + + def set_cookie(self, cookie, *args, **kwargs): + if hasattr(cookie.value, 'startswith') and cookie.value.startswith('"') and cookie.value.endswith('"'): + cookie.value = cookie.value.replace('\\"', '') + return super(RequestsCookieJar, self).set_cookie(cookie, *args, **kwargs) + + def update(self, other): + """Updates this jar with cookies from another CookieJar or dict-like""" + if isinstance(other, cookielib.CookieJar): + for cookie in other: + self.set_cookie(copy.copy(cookie)) + else: + super(RequestsCookieJar, self).update(other) + + def _find(self, name, domain=None, path=None): + """Requests uses this method internally to get cookie values. + + If there are conflicting cookies, _find arbitrarily chooses one. + See _find_no_duplicates if you want an exception thrown if there are + conflicting cookies. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :return: cookie.value + """ + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + return cookie.value + + raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) + + def _find_no_duplicates(self, name, domain=None, path=None): + """Both ``__get_item__`` and ``get`` call this function: it's never + used elsewhere in Requests. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :raises KeyError: if cookie is not found + :raises CookieConflictError: if there are multiple cookies + that match name and optionally domain and path + :return: cookie.value + """ + toReturn = None + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + if toReturn is not None: # if there are multiple cookies that meet passed in criteria + raise CookieConflictError('There are multiple cookies with name, %r' % (name)) + toReturn = cookie.value # we will eventually return this as long as no cookie conflict + + if toReturn: + return toReturn + raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) + + def __getstate__(self): + """Unlike a normal CookieJar, this class is pickleable.""" + state = self.__dict__.copy() + # remove the unpickleable RLock object + state.pop('_cookies_lock') + return state + + def __setstate__(self, state): + """Unlike a normal CookieJar, this class is pickleable.""" + self.__dict__.update(state) + if '_cookies_lock' not in self.__dict__: + self._cookies_lock = threading.RLock() + + def copy(self): + """Return a copy of this RequestsCookieJar.""" + new_cj = RequestsCookieJar() + new_cj.set_policy(self.get_policy()) + new_cj.update(self) + return new_cj + + def get_policy(self): + """Return the CookiePolicy instance used.""" + return self._policy + + +def _copy_cookie_jar(jar): + if jar is None: + return None + + if hasattr(jar, 'copy'): + # We're dealing with an instance of RequestsCookieJar + return jar.copy() + # We're dealing with a generic CookieJar instance + new_jar = copy.copy(jar) + new_jar.clear() + for cookie in jar: + new_jar.set_cookie(copy.copy(cookie)) + return new_jar + + +def create_cookie(name, value, **kwargs): + """Make a cookie from underspecified parameters. + + By default, the pair of `name` and `value` will be set for the domain '' + and sent on every request (this is sometimes called a "supercookie"). + """ + result = { + 'version': 0, + 'name': name, + 'value': value, + 'port': None, + 'domain': '', + 'path': '/', + 'secure': False, + 'expires': None, + 'discard': True, + 'comment': None, + 'comment_url': None, + 'rest': {'HttpOnly': None}, + 'rfc2109': False, + } + + badargs = set(kwargs) - set(result) + if badargs: + err = 'create_cookie() got unexpected keyword arguments: %s' + raise TypeError(err % list(badargs)) + + result.update(kwargs) + result['port_specified'] = bool(result['port']) + result['domain_specified'] = bool(result['domain']) + result['domain_initial_dot'] = result['domain'].startswith('.') + result['path_specified'] = bool(result['path']) + + return cookielib.Cookie(**result) + + +def morsel_to_cookie(morsel): + """Convert a Morsel object into a Cookie containing the one k/v pair.""" + + expires = None + if morsel['max-age']: + try: + expires = int(time.time() + int(morsel['max-age'])) + except ValueError: + raise TypeError('max-age: %s must be integer' % morsel['max-age']) + elif morsel['expires']: + time_template = '%a, %d-%b-%Y %H:%M:%S GMT' + expires = calendar.timegm( + time.strptime(morsel['expires'], time_template) + ) + return create_cookie( + comment=morsel['comment'], + comment_url=bool(morsel['comment']), + discard=False, + domain=morsel['domain'], + expires=expires, + name=morsel.key, + path=morsel['path'], + port=None, + rest={'HttpOnly': morsel['httponly']}, + rfc2109=False, + secure=bool(morsel['secure']), + value=morsel.value, + version=morsel['version'] or 0, + ) + + +def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True): + """Returns a CookieJar from a key/value dictionary. + + :param cookie_dict: Dict of key/values to insert into CookieJar. + :param cookiejar: (optional) A cookiejar to add the cookies to. + :param overwrite: (optional) If False, will not replace cookies + already in the jar with new ones. + :rtype: CookieJar + """ + if cookiejar is None: + cookiejar = RequestsCookieJar() + + if cookie_dict is not None: + names_from_jar = [cookie.name for cookie in cookiejar] + for name in cookie_dict: + if overwrite or (name not in names_from_jar): + cookiejar.set_cookie(create_cookie(name, cookie_dict[name])) + + return cookiejar + + +def merge_cookies(cookiejar, cookies): + """Add cookies to cookiejar and returns a merged CookieJar. + + :param cookiejar: CookieJar object to add the cookies to. + :param cookies: Dictionary or CookieJar object to be added. + :rtype: CookieJar + """ + if not isinstance(cookiejar, cookielib.CookieJar): + raise ValueError('You can only merge into CookieJar') + + if isinstance(cookies, dict): + cookiejar = cookiejar_from_dict( + cookies, cookiejar=cookiejar, overwrite=False) + elif isinstance(cookies, cookielib.CookieJar): + try: + cookiejar.update(cookies) + except AttributeError: + for cookie_in_jar in cookies: + cookiejar.set_cookie(cookie_in_jar) + + return cookiejar diff --git a/openpype/vendor/python/python_2/requests/exceptions.py b/openpype/vendor/python/python_2/requests/exceptions.py new file mode 100644 index 0000000000..79697635a5 --- /dev/null +++ b/openpype/vendor/python/python_2/requests/exceptions.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- + +""" +requests.exceptions +~~~~~~~~~~~~~~~~~~~ + +This module contains the set of Requests' exceptions. +""" +from urllib3.exceptions import HTTPError as BaseHTTPError + +from .compat import JSONDecodeError as CompatJSONDecodeError + + +class RequestException(IOError): + """There was an ambiguous exception that occurred while handling your + request. + """ + + def __init__(self, *args, **kwargs): + """Initialize RequestException with `request` and `response` objects.""" + response = kwargs.pop('response', None) + self.response = response + self.request = kwargs.pop('request', None) + if (response is not None and not self.request and + hasattr(response, 'request')): + self.request = self.response.request + super(RequestException, self).__init__(*args, **kwargs) + + +class InvalidJSONError(RequestException): + """A JSON error occurred.""" + + +class JSONDecodeError(InvalidJSONError, CompatJSONDecodeError): + """Couldn't decode the text into json""" + + +class HTTPError(RequestException): + """An HTTP error occurred.""" + + +class ConnectionError(RequestException): + """A Connection error occurred.""" + + +class ProxyError(ConnectionError): + """A proxy error occurred.""" + + +class SSLError(ConnectionError): + """An SSL error occurred.""" + + +class Timeout(RequestException): + """The request timed out. + + Catching this error will catch both + :exc:`~requests.exceptions.ConnectTimeout` and + :exc:`~requests.exceptions.ReadTimeout` errors. + """ + + +class ConnectTimeout(ConnectionError, Timeout): + """The request timed out while trying to connect to the remote server. + + Requests that produced this error are safe to retry. + """ + + +class ReadTimeout(Timeout): + """The server did not send any data in the allotted amount of time.""" + + +class URLRequired(RequestException): + """A valid URL is required to make a request.""" + + +class TooManyRedirects(RequestException): + """Too many redirects.""" + + +class MissingSchema(RequestException, ValueError): + """The URL scheme (e.g. http or https) is missing.""" + + +class InvalidSchema(RequestException, ValueError): + """The URL scheme provided is either invalid or unsupported.""" + + +class InvalidURL(RequestException, ValueError): + """The URL provided was somehow invalid.""" + + +class InvalidHeader(RequestException, ValueError): + """The header value provided was somehow invalid.""" + + +class InvalidProxyURL(InvalidURL): + """The proxy URL provided is invalid.""" + + +class ChunkedEncodingError(RequestException): + """The server declared chunked encoding but sent an invalid chunk.""" + + +class ContentDecodingError(RequestException, BaseHTTPError): + """Failed to decode response content.""" + + +class StreamConsumedError(RequestException, TypeError): + """The content for this response was already consumed.""" + + +class RetryError(RequestException): + """Custom retries logic failed""" + + +class UnrewindableBodyError(RequestException): + """Requests encountered an error when trying to rewind a body.""" + +# Warnings + + +class RequestsWarning(Warning): + """Base warning for Requests.""" + + +class FileModeWarning(RequestsWarning, DeprecationWarning): + """A file was opened in text mode, but Requests determined its binary length.""" + + +class RequestsDependencyWarning(RequestsWarning): + """An imported dependency doesn't match the expected version range.""" diff --git a/openpype/vendor/python/python_2/requests/help.py b/openpype/vendor/python/python_2/requests/help.py new file mode 100644 index 0000000000..4cd6389f55 --- /dev/null +++ b/openpype/vendor/python/python_2/requests/help.py @@ -0,0 +1,135 @@ +"""Module containing bug report helper(s).""" +from __future__ import print_function + +import json +import platform +import sys +import ssl + +import idna +import urllib3 + +from . import __version__ as requests_version + +try: + import charset_normalizer +except ImportError: + charset_normalizer = None + +try: + import chardet +except ImportError: + chardet = None + +try: + from urllib3.contrib import pyopenssl +except ImportError: + pyopenssl = None + OpenSSL = None + cryptography = None +else: + import OpenSSL + import cryptography + + +def _implementation(): + """Return a dict with the Python implementation and version. + + Provide both the name and the version of the Python implementation + currently running. For example, on CPython 2.7.5 it will return + {'name': 'CPython', 'version': '2.7.5'}. + + This function works best on CPython and PyPy: in particular, it probably + doesn't work for Jython or IronPython. Future investigation should be done + to work out the correct shape of the code for those platforms. + """ + implementation = platform.python_implementation() + + if implementation == 'CPython': + implementation_version = platform.python_version() + elif implementation == 'PyPy': + implementation_version = '%s.%s.%s' % (sys.pypy_version_info.major, + sys.pypy_version_info.minor, + sys.pypy_version_info.micro) + if sys.pypy_version_info.releaselevel != 'final': + implementation_version = ''.join([ + implementation_version, sys.pypy_version_info.releaselevel + ]) + elif implementation == 'Jython': + implementation_version = platform.python_version() # Complete Guess + elif implementation == 'IronPython': + implementation_version = platform.python_version() # Complete Guess + else: + implementation_version = 'Unknown' + + return {'name': implementation, 'version': implementation_version} + + +def info(): + """Generate information for a bug report.""" + try: + platform_info = { + 'system': platform.system(), + 'release': platform.release(), + } + except IOError: + platform_info = { + 'system': 'Unknown', + 'release': 'Unknown', + } + + implementation_info = _implementation() + urllib3_info = {'version': urllib3.__version__} + charset_normalizer_info = {'version': None} + chardet_info = {'version': None} + if charset_normalizer: + charset_normalizer_info = {'version': charset_normalizer.__version__} + if chardet: + chardet_info = {'version': chardet.__version__} + + pyopenssl_info = { + 'version': None, + 'openssl_version': '', + } + if OpenSSL: + pyopenssl_info = { + 'version': OpenSSL.__version__, + 'openssl_version': '%x' % OpenSSL.SSL.OPENSSL_VERSION_NUMBER, + } + cryptography_info = { + 'version': getattr(cryptography, '__version__', ''), + } + idna_info = { + 'version': getattr(idna, '__version__', ''), + } + + system_ssl = ssl.OPENSSL_VERSION_NUMBER + system_ssl_info = { + 'version': '%x' % system_ssl if system_ssl is not None else '' + } + + return { + 'platform': platform_info, + 'implementation': implementation_info, + 'system_ssl': system_ssl_info, + 'using_pyopenssl': pyopenssl is not None, + 'using_charset_normalizer': chardet is None, + 'pyOpenSSL': pyopenssl_info, + 'urllib3': urllib3_info, + 'chardet': chardet_info, + 'charset_normalizer': charset_normalizer_info, + 'cryptography': cryptography_info, + 'idna': idna_info, + 'requests': { + 'version': requests_version, + }, + } + + +def main(): + """Pretty-print the bug information as JSON.""" + print(json.dumps(info(), sort_keys=True, indent=2)) + + +if __name__ == '__main__': + main() diff --git a/openpype/vendor/python/python_2/requests/hooks.py b/openpype/vendor/python/python_2/requests/hooks.py new file mode 100644 index 0000000000..7a51f212c8 --- /dev/null +++ b/openpype/vendor/python/python_2/requests/hooks.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +""" +requests.hooks +~~~~~~~~~~~~~~ + +This module provides the capabilities for the Requests hooks system. + +Available hooks: + +``response``: + The response generated from a Request. +""" +HOOKS = ['response'] + + +def default_hooks(): + return {event: [] for event in HOOKS} + +# TODO: response is the only one + + +def dispatch_hook(key, hooks, hook_data, **kwargs): + """Dispatches a hook dictionary on a given piece of data.""" + hooks = hooks or {} + hooks = hooks.get(key) + if hooks: + if hasattr(hooks, '__call__'): + hooks = [hooks] + for hook in hooks: + _hook_data = hook(hook_data, **kwargs) + if _hook_data is not None: + hook_data = _hook_data + return hook_data diff --git a/openpype/vendor/python/python_2/requests/models.py b/openpype/vendor/python/python_2/requests/models.py new file mode 100644 index 0000000000..dfbea854f9 --- /dev/null +++ b/openpype/vendor/python/python_2/requests/models.py @@ -0,0 +1,973 @@ +# -*- coding: utf-8 -*- + +""" +requests.models +~~~~~~~~~~~~~~~ + +This module contains the primary objects that power Requests. +""" + +import datetime +import sys + +# Import encoding now, to avoid implicit import later. +# Implicit import within threads may cause LookupError when standard library is in a ZIP, +# such as in Embedded Python. See https://github.com/psf/requests/issues/3578. +import encodings.idna + +from urllib3.fields import RequestField +from urllib3.filepost import encode_multipart_formdata +from urllib3.util import parse_url +from urllib3.exceptions import ( + DecodeError, ReadTimeoutError, ProtocolError, LocationParseError) + +from io import UnsupportedOperation +from .hooks import default_hooks +from .structures import CaseInsensitiveDict + +from .auth import HTTPBasicAuth +from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar +from .exceptions import ( + HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError, + ContentDecodingError, ConnectionError, StreamConsumedError, + InvalidJSONError) +from .exceptions import JSONDecodeError as RequestsJSONDecodeError +from ._internal_utils import to_native_string, unicode_is_ascii +from .utils import ( + guess_filename, get_auth_from_url, requote_uri, + stream_decode_response_unicode, to_key_val_list, parse_header_links, + iter_slices, guess_json_utf, super_len, check_header_validity) +from .compat import ( + Callable, Mapping, + cookielib, urlunparse, urlsplit, urlencode, str, bytes, + is_py2, chardet, builtin_str, basestring, JSONDecodeError) +from .compat import json as complexjson +from .status_codes import codes + +#: The set of HTTP status codes that indicate an automatically +#: processable redirect. +REDIRECT_STATI = ( + codes.moved, # 301 + codes.found, # 302 + codes.other, # 303 + codes.temporary_redirect, # 307 + codes.permanent_redirect, # 308 +) + +DEFAULT_REDIRECT_LIMIT = 30 +CONTENT_CHUNK_SIZE = 10 * 1024 +ITER_CHUNK_SIZE = 512 + + +class RequestEncodingMixin(object): + @property + def path_url(self): + """Build the path URL to use.""" + + url = [] + + p = urlsplit(self.url) + + path = p.path + if not path: + path = '/' + + url.append(path) + + query = p.query + if query: + url.append('?') + url.append(query) + + return ''.join(url) + + @staticmethod + def _encode_params(data): + """Encode parameters in a piece of data. + + Will successfully encode parameters when passed as a dict or a list of + 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary + if parameters are supplied as a dict. + """ + + if isinstance(data, (str, bytes)): + return data + elif hasattr(data, 'read'): + return data + elif hasattr(data, '__iter__'): + result = [] + for k, vs in to_key_val_list(data): + if isinstance(vs, basestring) or not hasattr(vs, '__iter__'): + vs = [vs] + for v in vs: + if v is not None: + result.append( + (k.encode('utf-8') if isinstance(k, str) else k, + v.encode('utf-8') if isinstance(v, str) else v)) + return urlencode(result, doseq=True) + else: + return data + + @staticmethod + def _encode_files(files, data): + """Build the body for a multipart/form-data request. + + Will successfully encode files when passed as a dict or a list of + tuples. Order is retained if data is a list of tuples but arbitrary + if parameters are supplied as a dict. + The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype) + or 4-tuples (filename, fileobj, contentype, custom_headers). + """ + if (not files): + raise ValueError("Files must be provided.") + elif isinstance(data, basestring): + raise ValueError("Data must not be a string.") + + new_fields = [] + fields = to_key_val_list(data or {}) + files = to_key_val_list(files or {}) + + for field, val in fields: + if isinstance(val, basestring) or not hasattr(val, '__iter__'): + val = [val] + for v in val: + if v is not None: + # Don't call str() on bytestrings: in Py3 it all goes wrong. + if not isinstance(v, bytes): + v = str(v) + + new_fields.append( + (field.decode('utf-8') if isinstance(field, bytes) else field, + v.encode('utf-8') if isinstance(v, str) else v)) + + for (k, v) in files: + # support for explicit filename + ft = None + fh = None + if isinstance(v, (tuple, list)): + if len(v) == 2: + fn, fp = v + elif len(v) == 3: + fn, fp, ft = v + else: + fn, fp, ft, fh = v + else: + fn = guess_filename(v) or k + fp = v + + if isinstance(fp, (str, bytes, bytearray)): + fdata = fp + elif hasattr(fp, 'read'): + fdata = fp.read() + elif fp is None: + continue + else: + fdata = fp + + rf = RequestField(name=k, data=fdata, filename=fn, headers=fh) + rf.make_multipart(content_type=ft) + new_fields.append(rf) + + body, content_type = encode_multipart_formdata(new_fields) + + return body, content_type + + +class RequestHooksMixin(object): + def register_hook(self, event, hook): + """Properly register a hook.""" + + if event not in self.hooks: + raise ValueError('Unsupported event specified, with event name "%s"' % (event)) + + if isinstance(hook, Callable): + self.hooks[event].append(hook) + elif hasattr(hook, '__iter__'): + self.hooks[event].extend(h for h in hook if isinstance(h, Callable)) + + def deregister_hook(self, event, hook): + """Deregister a previously registered hook. + Returns True if the hook existed, False if not. + """ + + try: + self.hooks[event].remove(hook) + return True + except ValueError: + return False + + +class Request(RequestHooksMixin): + """A user-created :class:`Request ` object. + + Used to prepare a :class:`PreparedRequest `, which is sent to the server. + + :param method: HTTP method to use. + :param url: URL to send. + :param headers: dictionary of headers to send. + :param files: dictionary of {filename: fileobject} files to multipart upload. + :param data: the body to attach to the request. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param json: json for the body to attach to the request (if files or data is not specified). + :param params: URL parameters to append to the URL. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param auth: Auth handler or (user, pass) tuple. + :param cookies: dictionary or CookieJar of cookies to attach to this request. + :param hooks: dictionary of callback hooks, for internal usage. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> req.prepare() + + """ + + def __init__(self, + method=None, url=None, headers=None, files=None, data=None, + params=None, auth=None, cookies=None, hooks=None, json=None): + + # Default empty dicts for dict params. + data = [] if data is None else data + files = [] if files is None else files + headers = {} if headers is None else headers + params = {} if params is None else params + hooks = {} if hooks is None else hooks + + self.hooks = default_hooks() + for (k, v) in list(hooks.items()): + self.register_hook(event=k, hook=v) + + self.method = method + self.url = url + self.headers = headers + self.files = files + self.data = data + self.json = json + self.params = params + self.auth = auth + self.cookies = cookies + + def __repr__(self): + return '' % (self.method) + + def prepare(self): + """Constructs a :class:`PreparedRequest ` for transmission and returns it.""" + p = PreparedRequest() + p.prepare( + method=self.method, + url=self.url, + headers=self.headers, + files=self.files, + data=self.data, + json=self.json, + params=self.params, + auth=self.auth, + cookies=self.cookies, + hooks=self.hooks, + ) + return p + + +class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): + """The fully mutable :class:`PreparedRequest ` object, + containing the exact bytes that will be sent to the server. + + Instances are generated from a :class:`Request ` object, and + should not be instantiated manually; doing so may produce undesirable + effects. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> r = req.prepare() + >>> r + + + >>> s = requests.Session() + >>> s.send(r) + + """ + + def __init__(self): + #: HTTP verb to send to the server. + self.method = None + #: HTTP URL to send the request to. + self.url = None + #: dictionary of HTTP headers. + self.headers = None + # The `CookieJar` used to create the Cookie header will be stored here + # after prepare_cookies is called + self._cookies = None + #: request body to send to the server. + self.body = None + #: dictionary of callback hooks, for internal usage. + self.hooks = default_hooks() + #: integer denoting starting position of a readable file-like body. + self._body_position = None + + def prepare(self, + method=None, url=None, headers=None, files=None, data=None, + params=None, auth=None, cookies=None, hooks=None, json=None): + """Prepares the entire request with the given parameters.""" + + self.prepare_method(method) + self.prepare_url(url, params) + self.prepare_headers(headers) + self.prepare_cookies(cookies) + self.prepare_body(data, files, json) + self.prepare_auth(auth, url) + + # Note that prepare_auth must be last to enable authentication schemes + # such as OAuth to work on a fully prepared request. + + # This MUST go after prepare_auth. Authenticators could add a hook + self.prepare_hooks(hooks) + + def __repr__(self): + return '' % (self.method) + + def copy(self): + p = PreparedRequest() + p.method = self.method + p.url = self.url + p.headers = self.headers.copy() if self.headers is not None else None + p._cookies = _copy_cookie_jar(self._cookies) + p.body = self.body + p.hooks = self.hooks + p._body_position = self._body_position + return p + + def prepare_method(self, method): + """Prepares the given HTTP method.""" + self.method = method + if self.method is not None: + self.method = to_native_string(self.method.upper()) + + @staticmethod + def _get_idna_encoded_host(host): + import idna + + try: + host = idna.encode(host, uts46=True).decode('utf-8') + except idna.IDNAError: + raise UnicodeError + return host + + def prepare_url(self, url, params): + """Prepares the given HTTP URL.""" + #: Accept objects that have string representations. + #: We're unable to blindly call unicode/str functions + #: as this will include the bytestring indicator (b'') + #: on python 3.x. + #: https://github.com/psf/requests/pull/2238 + if isinstance(url, bytes): + url = url.decode('utf8') + else: + url = unicode(url) if is_py2 else str(url) + + # Remove leading whitespaces from url + url = url.lstrip() + + # Don't do any URL preparation for non-HTTP schemes like `mailto`, + # `data` etc to work around exceptions from `url_parse`, which + # handles RFC 3986 only. + if ':' in url and not url.lower().startswith('http'): + self.url = url + return + + # Support for unicode domain names and paths. + try: + scheme, auth, host, port, path, query, fragment = parse_url(url) + except LocationParseError as e: + raise InvalidURL(*e.args) + + if not scheme: + error = ("Invalid URL {0!r}: No scheme supplied. Perhaps you meant http://{0}?") + error = error.format(to_native_string(url, 'utf8')) + + raise MissingSchema(error) + + if not host: + raise InvalidURL("Invalid URL %r: No host supplied" % url) + + # In general, we want to try IDNA encoding the hostname if the string contains + # non-ASCII characters. This allows users to automatically get the correct IDNA + # behaviour. For strings containing only ASCII characters, we need to also verify + # it doesn't start with a wildcard (*), before allowing the unencoded hostname. + if not unicode_is_ascii(host): + try: + host = self._get_idna_encoded_host(host) + except UnicodeError: + raise InvalidURL('URL has an invalid label.') + elif host.startswith((u'*', u'.')): + raise InvalidURL('URL has an invalid label.') + + # Carefully reconstruct the network location + netloc = auth or '' + if netloc: + netloc += '@' + netloc += host + if port: + netloc += ':' + str(port) + + # Bare domains aren't valid URLs. + if not path: + path = '/' + + if is_py2: + if isinstance(scheme, str): + scheme = scheme.encode('utf-8') + if isinstance(netloc, str): + netloc = netloc.encode('utf-8') + if isinstance(path, str): + path = path.encode('utf-8') + if isinstance(query, str): + query = query.encode('utf-8') + if isinstance(fragment, str): + fragment = fragment.encode('utf-8') + + if isinstance(params, (str, bytes)): + params = to_native_string(params) + + enc_params = self._encode_params(params) + if enc_params: + if query: + query = '%s&%s' % (query, enc_params) + else: + query = enc_params + + url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment])) + self.url = url + + def prepare_headers(self, headers): + """Prepares the given HTTP headers.""" + + self.headers = CaseInsensitiveDict() + if headers: + for header in headers.items(): + # Raise exception on invalid header value. + check_header_validity(header) + name, value = header + self.headers[to_native_string(name)] = value + + def prepare_body(self, data, files, json=None): + """Prepares the given HTTP body data.""" + + # Check if file, fo, generator, iterator. + # If not, run through normal process. + + # Nottin' on you. + body = None + content_type = None + + if not data and json is not None: + # urllib3 requires a bytes-like body. Python 2's json.dumps + # provides this natively, but Python 3 gives a Unicode string. + content_type = 'application/json' + + try: + body = complexjson.dumps(json, allow_nan=False) + except ValueError as ve: + raise InvalidJSONError(ve, request=self) + + if not isinstance(body, bytes): + body = body.encode('utf-8') + + is_stream = all([ + hasattr(data, '__iter__'), + not isinstance(data, (basestring, list, tuple, Mapping)) + ]) + + if is_stream: + try: + length = super_len(data) + except (TypeError, AttributeError, UnsupportedOperation): + length = None + + body = data + + if getattr(body, 'tell', None) is not None: + # Record the current file position before reading. + # This will allow us to rewind a file in the event + # of a redirect. + try: + self._body_position = body.tell() + except (IOError, OSError): + # This differentiates from None, allowing us to catch + # a failed `tell()` later when trying to rewind the body + self._body_position = object() + + if files: + raise NotImplementedError('Streamed bodies and files are mutually exclusive.') + + if length: + self.headers['Content-Length'] = builtin_str(length) + else: + self.headers['Transfer-Encoding'] = 'chunked' + else: + # Multi-part file uploads. + if files: + (body, content_type) = self._encode_files(files, data) + else: + if data: + body = self._encode_params(data) + if isinstance(data, basestring) or hasattr(data, 'read'): + content_type = None + else: + content_type = 'application/x-www-form-urlencoded' + + self.prepare_content_length(body) + + # Add content-type if it wasn't explicitly provided. + if content_type and ('content-type' not in self.headers): + self.headers['Content-Type'] = content_type + + self.body = body + + def prepare_content_length(self, body): + """Prepare Content-Length header based on request method and body""" + if body is not None: + length = super_len(body) + if length: + # If length exists, set it. Otherwise, we fallback + # to Transfer-Encoding: chunked. + self.headers['Content-Length'] = builtin_str(length) + elif self.method not in ('GET', 'HEAD') and self.headers.get('Content-Length') is None: + # Set Content-Length to 0 for methods that can have a body + # but don't provide one. (i.e. not GET or HEAD) + self.headers['Content-Length'] = '0' + + def prepare_auth(self, auth, url=''): + """Prepares the given HTTP auth data.""" + + # If no Auth is explicitly provided, extract it from the URL first. + if auth is None: + url_auth = get_auth_from_url(self.url) + auth = url_auth if any(url_auth) else None + + if auth: + if isinstance(auth, tuple) and len(auth) == 2: + # special-case basic HTTP auth + auth = HTTPBasicAuth(*auth) + + # Allow auth to make its changes. + r = auth(self) + + # Update self to reflect the auth changes. + self.__dict__.update(r.__dict__) + + # Recompute Content-Length + self.prepare_content_length(self.body) + + def prepare_cookies(self, cookies): + """Prepares the given HTTP cookie data. + + This function eventually generates a ``Cookie`` header from the + given cookies using cookielib. Due to cookielib's design, the header + will not be regenerated if it already exists, meaning this function + can only be called once for the life of the + :class:`PreparedRequest ` object. Any subsequent calls + to ``prepare_cookies`` will have no actual effect, unless the "Cookie" + header is removed beforehand. + """ + if isinstance(cookies, cookielib.CookieJar): + self._cookies = cookies + else: + self._cookies = cookiejar_from_dict(cookies) + + cookie_header = get_cookie_header(self._cookies, self) + if cookie_header is not None: + self.headers['Cookie'] = cookie_header + + def prepare_hooks(self, hooks): + """Prepares the given hooks.""" + # hooks can be passed as None to the prepare method and to this + # method. To prevent iterating over None, simply use an empty list + # if hooks is False-y + hooks = hooks or [] + for event in hooks: + self.register_hook(event, hooks[event]) + + +class Response(object): + """The :class:`Response ` object, which contains a + server's response to an HTTP request. + """ + + __attrs__ = [ + '_content', 'status_code', 'headers', 'url', 'history', + 'encoding', 'reason', 'cookies', 'elapsed', 'request' + ] + + def __init__(self): + self._content = False + self._content_consumed = False + self._next = None + + #: Integer Code of responded HTTP Status, e.g. 404 or 200. + self.status_code = None + + #: Case-insensitive Dictionary of Response Headers. + #: For example, ``headers['content-encoding']`` will return the + #: value of a ``'Content-Encoding'`` response header. + self.headers = CaseInsensitiveDict() + + #: File-like object representation of response (for advanced usage). + #: Use of ``raw`` requires that ``stream=True`` be set on the request. + #: This requirement does not apply for use internally to Requests. + self.raw = None + + #: Final URL location of Response. + self.url = None + + #: Encoding to decode with when accessing r.text. + self.encoding = None + + #: A list of :class:`Response ` objects from + #: the history of the Request. Any redirect responses will end + #: up here. The list is sorted from the oldest to the most recent request. + self.history = [] + + #: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK". + self.reason = None + + #: A CookieJar of Cookies the server sent back. + self.cookies = cookiejar_from_dict({}) + + #: The amount of time elapsed between sending the request + #: and the arrival of the response (as a timedelta). + #: This property specifically measures the time taken between sending + #: the first byte of the request and finishing parsing the headers. It + #: is therefore unaffected by consuming the response content or the + #: value of the ``stream`` keyword argument. + self.elapsed = datetime.timedelta(0) + + #: The :class:`PreparedRequest ` object to which this + #: is a response. + self.request = None + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def __getstate__(self): + # Consume everything; accessing the content attribute makes + # sure the content has been fully read. + if not self._content_consumed: + self.content + + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state): + for name, value in state.items(): + setattr(self, name, value) + + # pickled objects do not have .raw + setattr(self, '_content_consumed', True) + setattr(self, 'raw', None) + + def __repr__(self): + return '' % (self.status_code) + + def __bool__(self): + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __nonzero__(self): + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __iter__(self): + """Allows you to use a response as an iterator.""" + return self.iter_content(128) + + @property + def ok(self): + """Returns True if :attr:`status_code` is less than 400, False if not. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + try: + self.raise_for_status() + except HTTPError: + return False + return True + + @property + def is_redirect(self): + """True if this Response is a well-formed HTTP redirect that could have + been processed automatically (by :meth:`Session.resolve_redirects`). + """ + return ('location' in self.headers and self.status_code in REDIRECT_STATI) + + @property + def is_permanent_redirect(self): + """True if this Response one of the permanent versions of redirect.""" + return ('location' in self.headers and self.status_code in (codes.moved_permanently, codes.permanent_redirect)) + + @property + def next(self): + """Returns a PreparedRequest for the next request in a redirect chain, if there is one.""" + return self._next + + @property + def apparent_encoding(self): + """The apparent encoding, provided by the charset_normalizer or chardet libraries.""" + return chardet.detect(self.content)['encoding'] + + def iter_content(self, chunk_size=1, decode_unicode=False): + """Iterates over the response data. When stream=True is set on the + request, this avoids reading the content at once into memory for + large responses. The chunk size is the number of bytes it should + read into memory. This is not necessarily the length of each item + returned as decoding can take place. + + chunk_size must be of type int or None. A value of None will + function differently depending on the value of `stream`. + stream=True will read data as it arrives in whatever size the + chunks are received. If stream=False, data is returned as + a single chunk. + + If decode_unicode is True, content will be decoded using the best + available encoding based on the response. + """ + + def generate(): + # Special case for urllib3. + if hasattr(self.raw, 'stream'): + try: + for chunk in self.raw.stream(chunk_size, decode_content=True): + yield chunk + except ProtocolError as e: + raise ChunkedEncodingError(e) + except DecodeError as e: + raise ContentDecodingError(e) + except ReadTimeoutError as e: + raise ConnectionError(e) + else: + # Standard file-like object. + while True: + chunk = self.raw.read(chunk_size) + if not chunk: + break + yield chunk + + self._content_consumed = True + + if self._content_consumed and isinstance(self._content, bool): + raise StreamConsumedError() + elif chunk_size is not None and not isinstance(chunk_size, int): + raise TypeError("chunk_size must be an int, it is instead a %s." % type(chunk_size)) + # simulate reading small chunks of the content + reused_chunks = iter_slices(self._content, chunk_size) + + stream_chunks = generate() + + chunks = reused_chunks if self._content_consumed else stream_chunks + + if decode_unicode: + chunks = stream_decode_response_unicode(chunks, self) + + return chunks + + def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=False, delimiter=None): + """Iterates over the response data, one line at a time. When + stream=True is set on the request, this avoids reading the + content at once into memory for large responses. + + .. note:: This method is not reentrant safe. + """ + + pending = None + + for chunk in self.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode): + + if pending is not None: + chunk = pending + chunk + + if delimiter: + lines = chunk.split(delimiter) + else: + lines = chunk.splitlines() + + if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]: + pending = lines.pop() + else: + pending = None + + for line in lines: + yield line + + if pending is not None: + yield pending + + @property + def content(self): + """Content of the response, in bytes.""" + + if self._content is False: + # Read the contents. + if self._content_consumed: + raise RuntimeError( + 'The content for this response was already consumed') + + if self.status_code == 0 or self.raw is None: + self._content = None + else: + self._content = b''.join(self.iter_content(CONTENT_CHUNK_SIZE)) or b'' + + self._content_consumed = True + # don't need to release the connection; that's been handled by urllib3 + # since we exhausted the data. + return self._content + + @property + def text(self): + """Content of the response, in unicode. + + If Response.encoding is None, encoding will be guessed using + ``charset_normalizer`` or ``chardet``. + + The encoding of the response content is determined based solely on HTTP + headers, following RFC 2616 to the letter. If you can take advantage of + non-HTTP knowledge to make a better guess at the encoding, you should + set ``r.encoding`` appropriately before accessing this property. + """ + + # Try charset from content-type + content = None + encoding = self.encoding + + if not self.content: + return str('') + + # Fallback to auto-detected encoding. + if self.encoding is None: + encoding = self.apparent_encoding + + # Decode unicode from given encoding. + try: + content = str(self.content, encoding, errors='replace') + except (LookupError, TypeError): + # A LookupError is raised if the encoding was not found which could + # indicate a misspelling or similar mistake. + # + # A TypeError can be raised if encoding is None + # + # So we try blindly encoding. + content = str(self.content, errors='replace') + + return content + + def json(self, **kwargs): + r"""Returns the json-encoded content of a response, if any. + + :param \*\*kwargs: Optional arguments that ``json.loads`` takes. + :raises requests.exceptions.JSONDecodeError: If the response body does not + contain valid json. + """ + + if not self.encoding and self.content and len(self.content) > 3: + # No encoding set. JSON RFC 4627 section 3 states we should expect + # UTF-8, -16 or -32. Detect which one to use; If the detection or + # decoding fails, fall back to `self.text` (using charset_normalizer to make + # a best guess). + encoding = guess_json_utf(self.content) + if encoding is not None: + try: + return complexjson.loads( + self.content.decode(encoding), **kwargs + ) + except UnicodeDecodeError: + # Wrong UTF codec detected; usually because it's not UTF-8 + # but some other 8-bit codec. This is an RFC violation, + # and the server didn't bother to tell us what codec *was* + # used. + pass + + try: + return complexjson.loads(self.text, **kwargs) + except JSONDecodeError as e: + # Catch JSON-related errors and raise as requests.JSONDecodeError + # This aliases json.JSONDecodeError and simplejson.JSONDecodeError + if is_py2: # e is a ValueError + raise RequestsJSONDecodeError(e.message) + else: + raise RequestsJSONDecodeError(e.msg, e.doc, e.pos) + + @property + def links(self): + """Returns the parsed header links of the response, if any.""" + + header = self.headers.get('link') + + # l = MultiDict() + l = {} + + if header: + links = parse_header_links(header) + + for link in links: + key = link.get('rel') or link.get('url') + l[key] = link + + return l + + def raise_for_status(self): + """Raises :class:`HTTPError`, if one occurred.""" + + http_error_msg = '' + if isinstance(self.reason, bytes): + # We attempt to decode utf-8 first because some servers + # choose to localize their reason strings. If the string + # isn't utf-8, we fall back to iso-8859-1 for all other + # encodings. (See PR #3538) + try: + reason = self.reason.decode('utf-8') + except UnicodeDecodeError: + reason = self.reason.decode('iso-8859-1') + else: + reason = self.reason + + if 400 <= self.status_code < 500: + http_error_msg = u'%s Client Error: %s for url: %s' % (self.status_code, reason, self.url) + + elif 500 <= self.status_code < 600: + http_error_msg = u'%s Server Error: %s for url: %s' % (self.status_code, reason, self.url) + + if http_error_msg: + raise HTTPError(http_error_msg, response=self) + + def close(self): + """Releases the connection back to the pool. Once this method has been + called the underlying ``raw`` object must not be accessed again. + + *Note: Should not normally need to be called explicitly.* + """ + if not self._content_consumed: + self.raw.close() + + release_conn = getattr(self.raw, 'release_conn', None) + if release_conn is not None: + release_conn() diff --git a/openpype/vendor/python/python_2/requests/packages.py b/openpype/vendor/python/python_2/requests/packages.py new file mode 100644 index 0000000000..00196bff25 --- /dev/null +++ b/openpype/vendor/python/python_2/requests/packages.py @@ -0,0 +1,26 @@ +import sys + +try: + import chardet +except ImportError: + import charset_normalizer as chardet + import warnings + + warnings.filterwarnings('ignore', 'Trying to detect', module='charset_normalizer') + +# This code exists for backwards compatibility reasons. +# I don't like it either. Just look the other way. :) + +for package in ('urllib3', 'idna'): + locals()[package] = __import__(package) + # This traversal is apparently necessary such that the identities are + # preserved (requests.packages.urllib3.* is urllib3.*) + for mod in list(sys.modules): + if mod == package or mod.startswith(package + '.'): + sys.modules['requests.packages.' + mod] = sys.modules[mod] + +target = chardet.__name__ +for mod in list(sys.modules): + if mod == target or mod.startswith(target + '.'): + sys.modules['requests.packages.' + target.replace(target, 'chardet')] = sys.modules[mod] +# Kinda cool, though, right? diff --git a/openpype/vendor/python/python_2/requests/sessions.py b/openpype/vendor/python/python_2/requests/sessions.py new file mode 100644 index 0000000000..3f59cab922 --- /dev/null +++ b/openpype/vendor/python/python_2/requests/sessions.py @@ -0,0 +1,771 @@ +# -*- coding: utf-8 -*- + +""" +requests.sessions +~~~~~~~~~~~~~~~~~ + +This module provides a Session object to manage and persist settings across +requests (cookies, auth, proxies). +""" +import os +import sys +import time +from datetime import timedelta +from collections import OrderedDict + +from .auth import _basic_auth_str +from .compat import cookielib, is_py3, urljoin, urlparse, Mapping +from .cookies import ( + cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies) +from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT +from .hooks import default_hooks, dispatch_hook +from ._internal_utils import to_native_string +from .utils import to_key_val_list, default_headers, DEFAULT_PORTS +from .exceptions import ( + TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError) + +from .structures import CaseInsensitiveDict +from .adapters import HTTPAdapter + +from .utils import ( + requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies, + get_auth_from_url, rewind_body, resolve_proxies +) + +from .status_codes import codes + +# formerly defined here, reexposed here for backward compatibility +from .models import REDIRECT_STATI + +# Preferred clock, based on which one is more accurate on a given system. +if sys.platform == 'win32': + try: # Python 3.4+ + preferred_clock = time.perf_counter + except AttributeError: # Earlier than Python 3. + preferred_clock = time.clock +else: + preferred_clock = time.time + + +def merge_setting(request_setting, session_setting, dict_class=OrderedDict): + """Determines appropriate setting for a given request, taking into account + the explicit setting on that request, and the setting in the session. If a + setting is a dictionary, they will be merged together using `dict_class` + """ + + if session_setting is None: + return request_setting + + if request_setting is None: + return session_setting + + # Bypass if not a dictionary (e.g. verify) + if not ( + isinstance(session_setting, Mapping) and + isinstance(request_setting, Mapping) + ): + return request_setting + + merged_setting = dict_class(to_key_val_list(session_setting)) + merged_setting.update(to_key_val_list(request_setting)) + + # Remove keys that are set to None. Extract keys first to avoid altering + # the dictionary during iteration. + none_keys = [k for (k, v) in merged_setting.items() if v is None] + for key in none_keys: + del merged_setting[key] + + return merged_setting + + +def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict): + """Properly merges both requests and session hooks. + + This is necessary because when request_hooks == {'response': []}, the + merge breaks Session hooks entirely. + """ + if session_hooks is None or session_hooks.get('response') == []: + return request_hooks + + if request_hooks is None or request_hooks.get('response') == []: + return session_hooks + + return merge_setting(request_hooks, session_hooks, dict_class) + + +class SessionRedirectMixin(object): + + def get_redirect_target(self, resp): + """Receives a Response. Returns a redirect URI or ``None``""" + # Due to the nature of how requests processes redirects this method will + # be called at least once upon the original response and at least twice + # on each subsequent redirect response (if any). + # If a custom mixin is used to handle this logic, it may be advantageous + # to cache the redirect location onto the response object as a private + # attribute. + if resp.is_redirect: + location = resp.headers['location'] + # Currently the underlying http module on py3 decode headers + # in latin1, but empirical evidence suggests that latin1 is very + # rarely used with non-ASCII characters in HTTP headers. + # It is more likely to get UTF8 header rather than latin1. + # This causes incorrect handling of UTF8 encoded location headers. + # To solve this, we re-encode the location in latin1. + if is_py3: + location = location.encode('latin1') + return to_native_string(location, 'utf8') + return None + + def should_strip_auth(self, old_url, new_url): + """Decide whether Authorization header should be removed when redirecting""" + old_parsed = urlparse(old_url) + new_parsed = urlparse(new_url) + if old_parsed.hostname != new_parsed.hostname: + return True + # Special case: allow http -> https redirect when using the standard + # ports. This isn't specified by RFC 7235, but is kept to avoid + # breaking backwards compatibility with older versions of requests + # that allowed any redirects on the same host. + if (old_parsed.scheme == 'http' and old_parsed.port in (80, None) + and new_parsed.scheme == 'https' and new_parsed.port in (443, None)): + return False + + # Handle default port usage corresponding to scheme. + changed_port = old_parsed.port != new_parsed.port + changed_scheme = old_parsed.scheme != new_parsed.scheme + default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None) + if (not changed_scheme and old_parsed.port in default_port + and new_parsed.port in default_port): + return False + + # Standard case: root URI must match + return changed_port or changed_scheme + + def resolve_redirects(self, resp, req, stream=False, timeout=None, + verify=True, cert=None, proxies=None, yield_requests=False, **adapter_kwargs): + """Receives a Response. Returns a generator of Responses or Requests.""" + + hist = [] # keep track of history + + url = self.get_redirect_target(resp) + previous_fragment = urlparse(req.url).fragment + while url: + prepared_request = req.copy() + + # Update history and keep track of redirects. + # resp.history must ignore the original request in this loop + hist.append(resp) + resp.history = hist[1:] + + try: + resp.content # Consume socket so it can be released + except (ChunkedEncodingError, ContentDecodingError, RuntimeError): + resp.raw.read(decode_content=False) + + if len(resp.history) >= self.max_redirects: + raise TooManyRedirects('Exceeded {} redirects.'.format(self.max_redirects), response=resp) + + # Release the connection back into the pool. + resp.close() + + # Handle redirection without scheme (see: RFC 1808 Section 4) + if url.startswith('//'): + parsed_rurl = urlparse(resp.url) + url = ':'.join([to_native_string(parsed_rurl.scheme), url]) + + # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2) + parsed = urlparse(url) + if parsed.fragment == '' and previous_fragment: + parsed = parsed._replace(fragment=previous_fragment) + elif parsed.fragment: + previous_fragment = parsed.fragment + url = parsed.geturl() + + # Facilitate relative 'location' headers, as allowed by RFC 7231. + # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') + # Compliant with RFC3986, we percent encode the url. + if not parsed.netloc: + url = urljoin(resp.url, requote_uri(url)) + else: + url = requote_uri(url) + + prepared_request.url = to_native_string(url) + + self.rebuild_method(prepared_request, resp) + + # https://github.com/psf/requests/issues/1084 + if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect): + # https://github.com/psf/requests/issues/3490 + purged_headers = ('Content-Length', 'Content-Type', 'Transfer-Encoding') + for header in purged_headers: + prepared_request.headers.pop(header, None) + prepared_request.body = None + + headers = prepared_request.headers + headers.pop('Cookie', None) + + # Extract any cookies sent on the response to the cookiejar + # in the new request. Because we've mutated our copied prepared + # request, use the old one that we haven't yet touched. + extract_cookies_to_jar(prepared_request._cookies, req, resp.raw) + merge_cookies(prepared_request._cookies, self.cookies) + prepared_request.prepare_cookies(prepared_request._cookies) + + # Rebuild auth and proxy information. + proxies = self.rebuild_proxies(prepared_request, proxies) + self.rebuild_auth(prepared_request, resp) + + # A failed tell() sets `_body_position` to `object()`. This non-None + # value ensures `rewindable` will be True, allowing us to raise an + # UnrewindableBodyError, instead of hanging the connection. + rewindable = ( + prepared_request._body_position is not None and + ('Content-Length' in headers or 'Transfer-Encoding' in headers) + ) + + # Attempt to rewind consumed file-like object. + if rewindable: + rewind_body(prepared_request) + + # Override the original request. + req = prepared_request + + if yield_requests: + yield req + else: + + resp = self.send( + req, + stream=stream, + timeout=timeout, + verify=verify, + cert=cert, + proxies=proxies, + allow_redirects=False, + **adapter_kwargs + ) + + extract_cookies_to_jar(self.cookies, prepared_request, resp.raw) + + # extract redirect url, if any, for the next loop + url = self.get_redirect_target(resp) + yield resp + + def rebuild_auth(self, prepared_request, response): + """When being redirected we may want to strip authentication from the + request to avoid leaking credentials. This method intelligently removes + and reapplies authentication where possible to avoid credential loss. + """ + headers = prepared_request.headers + url = prepared_request.url + + if 'Authorization' in headers and self.should_strip_auth(response.request.url, url): + # If we get redirected to a new host, we should strip out any + # authentication headers. + del headers['Authorization'] + + # .netrc might have more auth for us on our new host. + new_auth = get_netrc_auth(url) if self.trust_env else None + if new_auth is not None: + prepared_request.prepare_auth(new_auth) + + def rebuild_proxies(self, prepared_request, proxies): + """This method re-evaluates the proxy configuration by considering the + environment variables. If we are redirected to a URL covered by + NO_PROXY, we strip the proxy configuration. Otherwise, we set missing + proxy keys for this URL (in case they were stripped by a previous + redirect). + + This method also replaces the Proxy-Authorization header where + necessary. + + :rtype: dict + """ + headers = prepared_request.headers + scheme = urlparse(prepared_request.url).scheme + new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env) + + if 'Proxy-Authorization' in headers: + del headers['Proxy-Authorization'] + + try: + username, password = get_auth_from_url(new_proxies[scheme]) + except KeyError: + username, password = None, None + + if username and password: + headers['Proxy-Authorization'] = _basic_auth_str(username, password) + + return new_proxies + + def rebuild_method(self, prepared_request, response): + """When being redirected we may want to change the method of the request + based on certain specs or browser behavior. + """ + method = prepared_request.method + + # https://tools.ietf.org/html/rfc7231#section-6.4.4 + if response.status_code == codes.see_other and method != 'HEAD': + method = 'GET' + + # Do what the browsers do, despite standards... + # First, turn 302s into GETs. + if response.status_code == codes.found and method != 'HEAD': + method = 'GET' + + # Second, if a POST is responded to with a 301, turn it into a GET. + # This bizarre behaviour is explained in Issue 1704. + if response.status_code == codes.moved and method == 'POST': + method = 'GET' + + prepared_request.method = method + + +class Session(SessionRedirectMixin): + """A Requests session. + + Provides cookie persistence, connection-pooling, and configuration. + + Basic Usage:: + + >>> import requests + >>> s = requests.Session() + >>> s.get('https://httpbin.org/get') + + + Or as a context manager:: + + >>> with requests.Session() as s: + ... s.get('https://httpbin.org/get') + + """ + + __attrs__ = [ + 'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify', + 'cert', 'adapters', 'stream', 'trust_env', + 'max_redirects', + ] + + def __init__(self): + + #: A case-insensitive dictionary of headers to be sent on each + #: :class:`Request ` sent from this + #: :class:`Session `. + self.headers = default_headers() + + #: Default Authentication tuple or object to attach to + #: :class:`Request `. + self.auth = None + + #: Dictionary mapping protocol or protocol and host to the URL of the proxy + #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to + #: be used on each :class:`Request `. + self.proxies = {} + + #: Event-handling hooks. + self.hooks = default_hooks() + + #: Dictionary of querystring data to attach to each + #: :class:`Request `. The dictionary values may be lists for + #: representing multivalued query parameters. + self.params = {} + + #: Stream response content default. + self.stream = False + + #: SSL Verification default. + #: Defaults to `True`, requiring requests to verify the TLS certificate at the + #: remote end. + #: If verify is set to `False`, requests will accept any TLS certificate + #: presented by the server, and will ignore hostname mismatches and/or + #: expired certificates, which will make your application vulnerable to + #: man-in-the-middle (MitM) attacks. + #: Only set this to `False` for testing. + self.verify = True + + #: SSL client certificate default, if String, path to ssl client + #: cert file (.pem). If Tuple, ('cert', 'key') pair. + self.cert = None + + #: Maximum number of redirects allowed. If the request exceeds this + #: limit, a :class:`TooManyRedirects` exception is raised. + #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is + #: 30. + self.max_redirects = DEFAULT_REDIRECT_LIMIT + + #: Trust environment settings for proxy configuration, default + #: authentication and similar. + self.trust_env = True + + #: A CookieJar containing all currently outstanding cookies set on this + #: session. By default it is a + #: :class:`RequestsCookieJar `, but + #: may be any other ``cookielib.CookieJar`` compatible object. + self.cookies = cookiejar_from_dict({}) + + # Default connection adapters. + self.adapters = OrderedDict() + self.mount('https://', HTTPAdapter()) + self.mount('http://', HTTPAdapter()) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def prepare_request(self, request): + """Constructs a :class:`PreparedRequest ` for + transmission and returns it. The :class:`PreparedRequest` has settings + merged from the :class:`Request ` instance and those of the + :class:`Session`. + + :param request: :class:`Request` instance to prepare with this + session's settings. + :rtype: requests.PreparedRequest + """ + cookies = request.cookies or {} + + # Bootstrap CookieJar. + if not isinstance(cookies, cookielib.CookieJar): + cookies = cookiejar_from_dict(cookies) + + # Merge with session cookies + merged_cookies = merge_cookies( + merge_cookies(RequestsCookieJar(), self.cookies), cookies) + + # Set environment's basic authentication if not explicitly set. + auth = request.auth + if self.trust_env and not auth and not self.auth: + auth = get_netrc_auth(request.url) + + p = PreparedRequest() + p.prepare( + method=request.method.upper(), + url=request.url, + files=request.files, + data=request.data, + json=request.json, + headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict), + params=merge_setting(request.params, self.params), + auth=merge_setting(auth, self.auth), + cookies=merged_cookies, + hooks=merge_hooks(request.hooks, self.hooks), + ) + return p + + def request(self, method, url, + params=None, data=None, headers=None, cookies=None, files=None, + auth=None, timeout=None, allow_redirects=True, proxies=None, + hooks=None, stream=None, verify=None, cert=None, json=None): + """Constructs a :class:`Request `, prepares it and sends it. + Returns :class:`Response ` object. + + :param method: method for the new :class:`Request` object. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary or bytes to be sent in the query + string for the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the + :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the + :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the + :class:`Request`. + :param files: (optional) Dictionary of ``'filename': file-like-objects`` + for multipart encoding upload. + :param auth: (optional) Auth tuple or callable to enable + Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Set to True by default. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol or protocol and + hostname to the URL of the proxy. + :param stream: (optional) whether to immediately download the response + content. Defaults to ``False``. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. When set to + ``False``, requests will accept any TLS certificate presented by + the server, and will ignore hostname mismatches and/or expired + certificates, which will make your application vulnerable to + man-in-the-middle (MitM) attacks. Setting verify to ``False`` + may be useful during local development or testing. + :param cert: (optional) if String, path to ssl client cert file (.pem). + If Tuple, ('cert', 'key') pair. + :rtype: requests.Response + """ + # Create the Request. + req = Request( + method=method.upper(), + url=url, + headers=headers, + files=files, + data=data or {}, + json=json, + params=params or {}, + auth=auth, + cookies=cookies, + hooks=hooks, + ) + prep = self.prepare_request(req) + + proxies = proxies or {} + + settings = self.merge_environment_settings( + prep.url, proxies, stream, verify, cert + ) + + # Send the request. + send_kwargs = { + 'timeout': timeout, + 'allow_redirects': allow_redirects, + } + send_kwargs.update(settings) + resp = self.send(prep, **send_kwargs) + + return resp + + def get(self, url, **kwargs): + r"""Sends a GET request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', True) + return self.request('GET', url, **kwargs) + + def options(self, url, **kwargs): + r"""Sends a OPTIONS request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', True) + return self.request('OPTIONS', url, **kwargs) + + def head(self, url, **kwargs): + r"""Sends a HEAD request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', False) + return self.request('HEAD', url, **kwargs) + + def post(self, url, data=None, json=None, **kwargs): + r"""Sends a POST request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request('POST', url, data=data, json=json, **kwargs) + + def put(self, url, data=None, **kwargs): + r"""Sends a PUT request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request('PUT', url, data=data, **kwargs) + + def patch(self, url, data=None, **kwargs): + r"""Sends a PATCH request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request('PATCH', url, data=data, **kwargs) + + def delete(self, url, **kwargs): + r"""Sends a DELETE request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request('DELETE', url, **kwargs) + + def send(self, request, **kwargs): + """Send a given PreparedRequest. + + :rtype: requests.Response + """ + # Set defaults that the hooks can utilize to ensure they always have + # the correct parameters to reproduce the previous request. + kwargs.setdefault('stream', self.stream) + kwargs.setdefault('verify', self.verify) + kwargs.setdefault('cert', self.cert) + if 'proxies' not in kwargs: + kwargs['proxies'] = resolve_proxies( + request, self.proxies, self.trust_env + ) + + # It's possible that users might accidentally send a Request object. + # Guard against that specific failure case. + if isinstance(request, Request): + raise ValueError('You can only send PreparedRequests.') + + # Set up variables needed for resolve_redirects and dispatching of hooks + allow_redirects = kwargs.pop('allow_redirects', True) + stream = kwargs.get('stream') + hooks = request.hooks + + # Get the appropriate adapter to use + adapter = self.get_adapter(url=request.url) + + # Start time (approximately) of the request + start = preferred_clock() + + # Send the request + r = adapter.send(request, **kwargs) + + # Total elapsed time of the request (approximately) + elapsed = preferred_clock() - start + r.elapsed = timedelta(seconds=elapsed) + + # Response manipulation hooks + r = dispatch_hook('response', hooks, r, **kwargs) + + # Persist cookies + if r.history: + + # If the hooks create history then we want those cookies too + for resp in r.history: + extract_cookies_to_jar(self.cookies, resp.request, resp.raw) + + extract_cookies_to_jar(self.cookies, request, r.raw) + + # Resolve redirects if allowed. + if allow_redirects: + # Redirect resolving generator. + gen = self.resolve_redirects(r, request, **kwargs) + history = [resp for resp in gen] + else: + history = [] + + # Shuffle things around if there's history. + if history: + # Insert the first (original) request at the start + history.insert(0, r) + # Get the last request made + r = history.pop() + r.history = history + + # If redirects aren't being followed, store the response on the Request for Response.next(). + if not allow_redirects: + try: + r._next = next(self.resolve_redirects(r, request, yield_requests=True, **kwargs)) + except StopIteration: + pass + + if not stream: + r.content + + return r + + def merge_environment_settings(self, url, proxies, stream, verify, cert): + """ + Check the environment and merge it with some settings. + + :rtype: dict + """ + # Gather clues from the surrounding environment. + if self.trust_env: + # Set environment's proxies. + no_proxy = proxies.get('no_proxy') if proxies is not None else None + env_proxies = get_environ_proxies(url, no_proxy=no_proxy) + for (k, v) in env_proxies.items(): + proxies.setdefault(k, v) + + # Look for requests environment configuration and be compatible + # with cURL. + if verify is True or verify is None: + verify = (os.environ.get('REQUESTS_CA_BUNDLE') or + os.environ.get('CURL_CA_BUNDLE')) + + # Merge all the kwargs. + proxies = merge_setting(proxies, self.proxies) + stream = merge_setting(stream, self.stream) + verify = merge_setting(verify, self.verify) + cert = merge_setting(cert, self.cert) + + return {'verify': verify, 'proxies': proxies, 'stream': stream, + 'cert': cert} + + def get_adapter(self, url): + """ + Returns the appropriate connection adapter for the given URL. + + :rtype: requests.adapters.BaseAdapter + """ + for (prefix, adapter) in self.adapters.items(): + + if url.lower().startswith(prefix.lower()): + return adapter + + # Nothing matches :-/ + raise InvalidSchema("No connection adapters were found for {!r}".format(url)) + + def close(self): + """Closes all adapters and as such the session""" + for v in self.adapters.values(): + v.close() + + def mount(self, prefix, adapter): + """Registers a connection adapter to a prefix. + + Adapters are sorted in descending order by prefix length. + """ + self.adapters[prefix] = adapter + keys_to_move = [k for k in self.adapters if len(k) < len(prefix)] + + for key in keys_to_move: + self.adapters[key] = self.adapters.pop(key) + + def __getstate__(self): + state = {attr: getattr(self, attr, None) for attr in self.__attrs__} + return state + + def __setstate__(self, state): + for attr, value in state.items(): + setattr(self, attr, value) + + +def session(): + """ + Returns a :class:`Session` for context-management. + + .. deprecated:: 1.0.0 + + This method has been deprecated since version 1.0.0 and is only kept for + backwards compatibility. New code should use :class:`~requests.sessions.Session` + to create a session. This may be removed at a future date. + + :rtype: Session + """ + return Session() diff --git a/openpype/vendor/python/python_2/requests/status_codes.py b/openpype/vendor/python/python_2/requests/status_codes.py new file mode 100644 index 0000000000..d80a7cd4dd --- /dev/null +++ b/openpype/vendor/python/python_2/requests/status_codes.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- + +r""" +The ``codes`` object defines a mapping from common names for HTTP statuses +to their numerical codes, accessible either as attributes or as dictionary +items. + +Example:: + + >>> import requests + >>> requests.codes['temporary_redirect'] + 307 + >>> requests.codes.teapot + 418 + >>> requests.codes['\o/'] + 200 + +Some codes have multiple names, and both upper- and lower-case versions of +the names are allowed. For example, ``codes.ok``, ``codes.OK``, and +``codes.okay`` all correspond to the HTTP status code 200. +""" + +from .structures import LookupDict + +_codes = { + + # Informational. + 100: ('continue',), + 101: ('switching_protocols',), + 102: ('processing',), + 103: ('checkpoint',), + 122: ('uri_too_long', 'request_uri_too_long'), + 200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', '✓'), + 201: ('created',), + 202: ('accepted',), + 203: ('non_authoritative_info', 'non_authoritative_information'), + 204: ('no_content',), + 205: ('reset_content', 'reset'), + 206: ('partial_content', 'partial'), + 207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'), + 208: ('already_reported',), + 226: ('im_used',), + + # Redirection. + 300: ('multiple_choices',), + 301: ('moved_permanently', 'moved', '\\o-'), + 302: ('found',), + 303: ('see_other', 'other'), + 304: ('not_modified',), + 305: ('use_proxy',), + 306: ('switch_proxy',), + 307: ('temporary_redirect', 'temporary_moved', 'temporary'), + 308: ('permanent_redirect', + 'resume_incomplete', 'resume',), # These 2 to be removed in 3.0 + + # Client Error. + 400: ('bad_request', 'bad'), + 401: ('unauthorized',), + 402: ('payment_required', 'payment'), + 403: ('forbidden',), + 404: ('not_found', '-o-'), + 405: ('method_not_allowed', 'not_allowed'), + 406: ('not_acceptable',), + 407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'), + 408: ('request_timeout', 'timeout'), + 409: ('conflict',), + 410: ('gone',), + 411: ('length_required',), + 412: ('precondition_failed', 'precondition'), + 413: ('request_entity_too_large',), + 414: ('request_uri_too_large',), + 415: ('unsupported_media_type', 'unsupported_media', 'media_type'), + 416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'), + 417: ('expectation_failed',), + 418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'), + 421: ('misdirected_request',), + 422: ('unprocessable_entity', 'unprocessable'), + 423: ('locked',), + 424: ('failed_dependency', 'dependency'), + 425: ('unordered_collection', 'unordered'), + 426: ('upgrade_required', 'upgrade'), + 428: ('precondition_required', 'precondition'), + 429: ('too_many_requests', 'too_many'), + 431: ('header_fields_too_large', 'fields_too_large'), + 444: ('no_response', 'none'), + 449: ('retry_with', 'retry'), + 450: ('blocked_by_windows_parental_controls', 'parental_controls'), + 451: ('unavailable_for_legal_reasons', 'legal_reasons'), + 499: ('client_closed_request',), + + # Server Error. + 500: ('internal_server_error', 'server_error', '/o\\', '✗'), + 501: ('not_implemented',), + 502: ('bad_gateway',), + 503: ('service_unavailable', 'unavailable'), + 504: ('gateway_timeout',), + 505: ('http_version_not_supported', 'http_version'), + 506: ('variant_also_negotiates',), + 507: ('insufficient_storage',), + 509: ('bandwidth_limit_exceeded', 'bandwidth'), + 510: ('not_extended',), + 511: ('network_authentication_required', 'network_auth', 'network_authentication'), +} + +codes = LookupDict(name='status_codes') + +def _init(): + for code, titles in _codes.items(): + for title in titles: + setattr(codes, title, code) + if not title.startswith(('\\', '/')): + setattr(codes, title.upper(), code) + + def doc(code): + names = ', '.join('``%s``' % n for n in _codes[code]) + return '* %d: %s' % (code, names) + + global __doc__ + __doc__ = (__doc__ + '\n' + + '\n'.join(doc(code) for code in sorted(_codes)) + if __doc__ is not None else None) + +_init() diff --git a/openpype/vendor/python/python_2/requests/structures.py b/openpype/vendor/python/python_2/requests/structures.py new file mode 100644 index 0000000000..8ee0ba7a08 --- /dev/null +++ b/openpype/vendor/python/python_2/requests/structures.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- + +""" +requests.structures +~~~~~~~~~~~~~~~~~~~ + +Data structures that power Requests. +""" + +from collections import OrderedDict + +from .compat import Mapping, MutableMapping + + +class CaseInsensitiveDict(MutableMapping): + """A case-insensitive ``dict``-like object. + + Implements all methods and operations of + ``MutableMapping`` as well as dict's ``copy``. Also + provides ``lower_items``. + + All keys are expected to be strings. The structure remembers the + case of the last key to be set, and ``iter(instance)``, + ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` + will contain case-sensitive keys. However, querying and contains + testing is case insensitive:: + + cid = CaseInsensitiveDict() + cid['Accept'] = 'application/json' + cid['aCCEPT'] == 'application/json' # True + list(cid) == ['Accept'] # True + + For example, ``headers['content-encoding']`` will return the + value of a ``'Content-Encoding'`` response header, regardless + of how the header name was originally stored. + + If the constructor, ``.update``, or equality comparison + operations are given keys that have equal ``.lower()``s, the + behavior is undefined. + """ + + def __init__(self, data=None, **kwargs): + self._store = OrderedDict() + if data is None: + data = {} + self.update(data, **kwargs) + + def __setitem__(self, key, value): + # Use the lowercased key for lookups, but store the actual + # key alongside the value. + self._store[key.lower()] = (key, value) + + def __getitem__(self, key): + return self._store[key.lower()][1] + + def __delitem__(self, key): + del self._store[key.lower()] + + def __iter__(self): + return (casedkey for casedkey, mappedvalue in self._store.values()) + + def __len__(self): + return len(self._store) + + def lower_items(self): + """Like iteritems(), but with all lowercase keys.""" + return ( + (lowerkey, keyval[1]) + for (lowerkey, keyval) + in self._store.items() + ) + + def __eq__(self, other): + if isinstance(other, Mapping): + other = CaseInsensitiveDict(other) + else: + return NotImplemented + # Compare insensitively + return dict(self.lower_items()) == dict(other.lower_items()) + + # Copy is required + def copy(self): + return CaseInsensitiveDict(self._store.values()) + + def __repr__(self): + return str(dict(self.items())) + + +class LookupDict(dict): + """Dictionary lookup object.""" + + def __init__(self, name=None): + self.name = name + super(LookupDict, self).__init__() + + def __repr__(self): + return '' % (self.name) + + def __getitem__(self, key): + # We allow fall-through here, so values default to None + + return self.__dict__.get(key, None) + + def get(self, key, default=None): + return self.__dict__.get(key, default) diff --git a/openpype/vendor/python/python_2/requests/utils.py b/openpype/vendor/python/python_2/requests/utils.py new file mode 100644 index 0000000000..153776c7f3 --- /dev/null +++ b/openpype/vendor/python/python_2/requests/utils.py @@ -0,0 +1,1060 @@ +# -*- coding: utf-8 -*- + +""" +requests.utils +~~~~~~~~~~~~~~ + +This module provides utility functions that are used within Requests +that are also useful for external consumption. +""" + +import codecs +import contextlib +import io +import os +import re +import socket +import struct +import sys +import tempfile +import warnings +import zipfile +from collections import OrderedDict +from urllib3.util import make_headers +from urllib3.util import parse_url + +from .__version__ import __version__ +from . import certs +# to_native_string is unused here, but imported here for backwards compatibility +from ._internal_utils import to_native_string +from .compat import parse_http_list as _parse_list_header +from .compat import ( + quote, urlparse, bytes, str, unquote, getproxies, + proxy_bypass, urlunparse, basestring, integer_types, is_py3, + proxy_bypass_environment, getproxies_environment, Mapping) +from .cookies import cookiejar_from_dict +from .structures import CaseInsensitiveDict +from .exceptions import ( + InvalidURL, InvalidHeader, FileModeWarning, UnrewindableBodyError) + +NETRC_FILES = ('.netrc', '_netrc') + +DEFAULT_CA_BUNDLE_PATH = certs.where() + +DEFAULT_PORTS = {'http': 80, 'https': 443} + +# Ensure that ', ' is used to preserve previous delimiter behavior. +DEFAULT_ACCEPT_ENCODING = ", ".join( + re.split(r",\s*", make_headers(accept_encoding=True)["accept-encoding"]) +) + + +if sys.platform == 'win32': + # provide a proxy_bypass version on Windows without DNS lookups + + def proxy_bypass_registry(host): + try: + if is_py3: + import winreg + else: + import _winreg as winreg + except ImportError: + return False + + try: + internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER, + r'Software\Microsoft\Windows\CurrentVersion\Internet Settings') + # ProxyEnable could be REG_SZ or REG_DWORD, normalizing it + proxyEnable = int(winreg.QueryValueEx(internetSettings, + 'ProxyEnable')[0]) + # ProxyOverride is almost always a string + proxyOverride = winreg.QueryValueEx(internetSettings, + 'ProxyOverride')[0] + except OSError: + return False + if not proxyEnable or not proxyOverride: + return False + + # make a check value list from the registry entry: replace the + # '' string by the localhost entry and the corresponding + # canonical entry. + proxyOverride = proxyOverride.split(';') + # now check if we match one of the registry values. + for test in proxyOverride: + if test == '': + if '.' not in host: + return True + test = test.replace(".", r"\.") # mask dots + test = test.replace("*", r".*") # change glob sequence + test = test.replace("?", r".") # change glob char + if re.match(test, host, re.I): + return True + return False + + def proxy_bypass(host): # noqa + """Return True, if the host should be bypassed. + + Checks proxy settings gathered from the environment, if specified, + or the registry. + """ + if getproxies_environment(): + return proxy_bypass_environment(host) + else: + return proxy_bypass_registry(host) + + +def dict_to_sequence(d): + """Returns an internal sequence dictionary update.""" + + if hasattr(d, 'items'): + d = d.items() + + return d + + +def super_len(o): + total_length = None + current_position = 0 + + if hasattr(o, '__len__'): + total_length = len(o) + + elif hasattr(o, 'len'): + total_length = o.len + + elif hasattr(o, 'fileno'): + try: + fileno = o.fileno() + except (io.UnsupportedOperation, AttributeError): + # AttributeError is a surprising exception, seeing as how we've just checked + # that `hasattr(o, 'fileno')`. It happens for objects obtained via + # `Tarfile.extractfile()`, per issue 5229. + pass + else: + total_length = os.fstat(fileno).st_size + + # Having used fstat to determine the file length, we need to + # confirm that this file was opened up in binary mode. + if 'b' not in o.mode: + warnings.warn(( + "Requests has determined the content-length for this " + "request using the binary size of the file: however, the " + "file has been opened in text mode (i.e. without the 'b' " + "flag in the mode). This may lead to an incorrect " + "content-length. In Requests 3.0, support will be removed " + "for files in text mode."), + FileModeWarning + ) + + if hasattr(o, 'tell'): + try: + current_position = o.tell() + except (OSError, IOError): + # This can happen in some weird situations, such as when the file + # is actually a special file descriptor like stdin. In this + # instance, we don't know what the length is, so set it to zero and + # let requests chunk it instead. + if total_length is not None: + current_position = total_length + else: + if hasattr(o, 'seek') and total_length is None: + # StringIO and BytesIO have seek but no usable fileno + try: + # seek to end of file + o.seek(0, 2) + total_length = o.tell() + + # seek back to current position to support + # partially read file-like objects + o.seek(current_position or 0) + except (OSError, IOError): + total_length = 0 + + if total_length is None: + total_length = 0 + + return max(0, total_length - current_position) + + +def get_netrc_auth(url, raise_errors=False): + """Returns the Requests tuple auth for a given url from netrc.""" + + netrc_file = os.environ.get('NETRC') + if netrc_file is not None: + netrc_locations = (netrc_file,) + else: + netrc_locations = ('~/{}'.format(f) for f in NETRC_FILES) + + try: + from netrc import netrc, NetrcParseError + + netrc_path = None + + for f in netrc_locations: + try: + loc = os.path.expanduser(f) + except KeyError: + # os.path.expanduser can fail when $HOME is undefined and + # getpwuid fails. See https://bugs.python.org/issue20164 & + # https://github.com/psf/requests/issues/1846 + return + + if os.path.exists(loc): + netrc_path = loc + break + + # Abort early if there isn't one. + if netrc_path is None: + return + + ri = urlparse(url) + + # Strip port numbers from netloc. This weird `if...encode`` dance is + # used for Python 3.2, which doesn't support unicode literals. + splitstr = b':' + if isinstance(url, str): + splitstr = splitstr.decode('ascii') + host = ri.netloc.split(splitstr)[0] + + try: + _netrc = netrc(netrc_path).authenticators(host) + if _netrc: + # Return with login / password + login_i = (0 if _netrc[0] else 1) + return (_netrc[login_i], _netrc[2]) + except (NetrcParseError, IOError): + # If there was a parsing error or a permissions issue reading the file, + # we'll just skip netrc auth unless explicitly asked to raise errors. + if raise_errors: + raise + + # App Engine hackiness. + except (ImportError, AttributeError): + pass + + +def guess_filename(obj): + """Tries to guess the filename of the given object.""" + name = getattr(obj, 'name', None) + if (name and isinstance(name, basestring) and name[0] != '<' and + name[-1] != '>'): + return os.path.basename(name) + + +def extract_zipped_paths(path): + """Replace nonexistent paths that look like they refer to a member of a zip + archive with the location of an extracted copy of the target, or else + just return the provided path unchanged. + """ + if os.path.exists(path): + # this is already a valid path, no need to do anything further + return path + + # find the first valid part of the provided path and treat that as a zip archive + # assume the rest of the path is the name of a member in the archive + archive, member = os.path.split(path) + while archive and not os.path.exists(archive): + archive, prefix = os.path.split(archive) + if not prefix: + # If we don't check for an empty prefix after the split (in other words, archive remains unchanged after the split), + # we _can_ end up in an infinite loop on a rare corner case affecting a small number of users + break + member = '/'.join([prefix, member]) + + if not zipfile.is_zipfile(archive): + return path + + zip_file = zipfile.ZipFile(archive) + if member not in zip_file.namelist(): + return path + + # we have a valid zip archive and a valid member of that archive + tmp = tempfile.gettempdir() + extracted_path = os.path.join(tmp, member.split('/')[-1]) + if not os.path.exists(extracted_path): + # use read + write to avoid the creating nested folders, we only want the file, avoids mkdir racing condition + with atomic_open(extracted_path) as file_handler: + file_handler.write(zip_file.read(member)) + return extracted_path + + +@contextlib.contextmanager +def atomic_open(filename): + """Write a file to the disk in an atomic fashion""" + replacer = os.rename if sys.version_info[0] == 2 else os.replace + tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename)) + try: + with os.fdopen(tmp_descriptor, 'wb') as tmp_handler: + yield tmp_handler + replacer(tmp_name, filename) + except BaseException: + os.remove(tmp_name) + raise + + +def from_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. Unless it can not be represented as such, return an + OrderedDict, e.g., + + :: + + >>> from_key_val_list([('key', 'val')]) + OrderedDict([('key', 'val')]) + >>> from_key_val_list('string') + Traceback (most recent call last): + ... + ValueError: cannot encode objects that are not 2-tuples + >>> from_key_val_list({'key': 'val'}) + OrderedDict([('key', 'val')]) + + :rtype: OrderedDict + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError('cannot encode objects that are not 2-tuples') + + return OrderedDict(value) + + +def to_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. If it can be, return a list of tuples, e.g., + + :: + + >>> to_key_val_list([('key', 'val')]) + [('key', 'val')] + >>> to_key_val_list({'key': 'val'}) + [('key', 'val')] + >>> to_key_val_list('string') + Traceback (most recent call last): + ... + ValueError: cannot encode objects that are not 2-tuples + + :rtype: list + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError('cannot encode objects that are not 2-tuples') + + if isinstance(value, Mapping): + value = value.items() + + return list(value) + + +# From mitsuhiko/werkzeug (used with permission). +def parse_list_header(value): + """Parse lists as described by RFC 2068 Section 2. + + In particular, parse comma-separated lists where the elements of + the list may include quoted-strings. A quoted-string could + contain a comma. A non-quoted string could have quotes in the + middle. Quotes are removed automatically after parsing. + + It basically works like :func:`parse_set_header` just that items + may appear multiple times and case sensitivity is preserved. + + The return value is a standard :class:`list`: + + >>> parse_list_header('token, "quoted value"') + ['token', 'quoted value'] + + To create a header from the :class:`list` again, use the + :func:`dump_header` function. + + :param value: a string with a list header. + :return: :class:`list` + :rtype: list + """ + result = [] + for item in _parse_list_header(value): + if item[:1] == item[-1:] == '"': + item = unquote_header_value(item[1:-1]) + result.append(item) + return result + + +# From mitsuhiko/werkzeug (used with permission). +def parse_dict_header(value): + """Parse lists of key, value pairs as described by RFC 2068 Section 2 and + convert them into a python dict: + + >>> d = parse_dict_header('foo="is a fish", bar="as well"') + >>> type(d) is dict + True + >>> sorted(d.items()) + [('bar', 'as well'), ('foo', 'is a fish')] + + If there is no value for a key it will be `None`: + + >>> parse_dict_header('key_without_value') + {'key_without_value': None} + + To create a header from the :class:`dict` again, use the + :func:`dump_header` function. + + :param value: a string with a dict header. + :return: :class:`dict` + :rtype: dict + """ + result = {} + for item in _parse_list_header(value): + if '=' not in item: + result[item] = None + continue + name, value = item.split('=', 1) + if value[:1] == value[-1:] == '"': + value = unquote_header_value(value[1:-1]) + result[name] = value + return result + + +# From mitsuhiko/werkzeug (used with permission). +def unquote_header_value(value, is_filename=False): + r"""Unquotes a header value. (Reversal of :func:`quote_header_value`). + This does not use the real unquoting but what browsers are actually + using for quoting. + + :param value: the header value to unquote. + :rtype: str + """ + if value and value[0] == value[-1] == '"': + # this is not the real unquoting, but fixing this so that the + # RFC is met will result in bugs with internet explorer and + # probably some other browsers as well. IE for example is + # uploading files with "C:\foo\bar.txt" as filename + value = value[1:-1] + + # if this is a filename and the starting characters look like + # a UNC path, then just return the value without quotes. Using the + # replace sequence below on a UNC path has the effect of turning + # the leading double slash into a single slash and then + # _fix_ie_filename() doesn't work correctly. See #458. + if not is_filename or value[:2] != '\\\\': + return value.replace('\\\\', '\\').replace('\\"', '"') + return value + + +def dict_from_cookiejar(cj): + """Returns a key/value dictionary from a CookieJar. + + :param cj: CookieJar object to extract cookies from. + :rtype: dict + """ + + cookie_dict = {} + + for cookie in cj: + cookie_dict[cookie.name] = cookie.value + + return cookie_dict + + +def add_dict_to_cookiejar(cj, cookie_dict): + """Returns a CookieJar from a key/value dictionary. + + :param cj: CookieJar to insert cookies into. + :param cookie_dict: Dict of key/values to insert into CookieJar. + :rtype: CookieJar + """ + + return cookiejar_from_dict(cookie_dict, cj) + + +def get_encodings_from_content(content): + """Returns encodings from given content string. + + :param content: bytestring to extract encodings from. + """ + warnings.warn(( + 'In requests 3.0, get_encodings_from_content will be removed. For ' + 'more information, please see the discussion on issue #2266. (This' + ' warning should only appear once.)'), + DeprecationWarning) + + charset_re = re.compile(r']', flags=re.I) + pragma_re = re.compile(r']', flags=re.I) + xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]') + + return (charset_re.findall(content) + + pragma_re.findall(content) + + xml_re.findall(content)) + + +def _parse_content_type_header(header): + """Returns content type and parameters from given header + + :param header: string + :return: tuple containing content type and dictionary of + parameters + """ + + tokens = header.split(';') + content_type, params = tokens[0].strip(), tokens[1:] + params_dict = {} + items_to_strip = "\"' " + + for param in params: + param = param.strip() + if param: + key, value = param, True + index_of_equals = param.find("=") + if index_of_equals != -1: + key = param[:index_of_equals].strip(items_to_strip) + value = param[index_of_equals + 1:].strip(items_to_strip) + params_dict[key.lower()] = value + return content_type, params_dict + + +def get_encoding_from_headers(headers): + """Returns encodings from given HTTP Header Dict. + + :param headers: dictionary to extract encoding from. + :rtype: str + """ + + content_type = headers.get('content-type') + + if not content_type: + return None + + content_type, params = _parse_content_type_header(content_type) + + if 'charset' in params: + return params['charset'].strip("'\"") + + if 'text' in content_type: + return 'ISO-8859-1' + + if 'application/json' in content_type: + # Assume UTF-8 based on RFC 4627: https://www.ietf.org/rfc/rfc4627.txt since the charset was unset + return 'utf-8' + + +def stream_decode_response_unicode(iterator, r): + """Stream decodes a iterator.""" + + if r.encoding is None: + for item in iterator: + yield item + return + + decoder = codecs.getincrementaldecoder(r.encoding)(errors='replace') + for chunk in iterator: + rv = decoder.decode(chunk) + if rv: + yield rv + rv = decoder.decode(b'', final=True) + if rv: + yield rv + + +def iter_slices(string, slice_length): + """Iterate over slices of a string.""" + pos = 0 + if slice_length is None or slice_length <= 0: + slice_length = len(string) + while pos < len(string): + yield string[pos:pos + slice_length] + pos += slice_length + + +def get_unicode_from_response(r): + """Returns the requested content back in unicode. + + :param r: Response object to get unicode content from. + + Tried: + + 1. charset from content-type + 2. fall back and replace all unicode characters + + :rtype: str + """ + warnings.warn(( + 'In requests 3.0, get_unicode_from_response will be removed. For ' + 'more information, please see the discussion on issue #2266. (This' + ' warning should only appear once.)'), + DeprecationWarning) + + tried_encodings = [] + + # Try charset from content-type + encoding = get_encoding_from_headers(r.headers) + + if encoding: + try: + return str(r.content, encoding) + except UnicodeError: + tried_encodings.append(encoding) + + # Fall back: + try: + return str(r.content, encoding, errors='replace') + except TypeError: + return r.content + + +# The unreserved URI characters (RFC 3986) +UNRESERVED_SET = frozenset( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~") + + +def unquote_unreserved(uri): + """Un-escape any percent-escape sequences in a URI that are unreserved + characters. This leaves all reserved, illegal and non-ASCII bytes encoded. + + :rtype: str + """ + parts = uri.split('%') + for i in range(1, len(parts)): + h = parts[i][0:2] + if len(h) == 2 and h.isalnum(): + try: + c = chr(int(h, 16)) + except ValueError: + raise InvalidURL("Invalid percent-escape sequence: '%s'" % h) + + if c in UNRESERVED_SET: + parts[i] = c + parts[i][2:] + else: + parts[i] = '%' + parts[i] + else: + parts[i] = '%' + parts[i] + return ''.join(parts) + + +def requote_uri(uri): + """Re-quote the given URI. + + This function passes the given URI through an unquote/quote cycle to + ensure that it is fully and consistently quoted. + + :rtype: str + """ + safe_with_percent = "!#$%&'()*+,/:;=?@[]~" + safe_without_percent = "!#$&'()*+,/:;=?@[]~" + try: + # Unquote only the unreserved characters + # Then quote only illegal characters (do not quote reserved, + # unreserved, or '%') + return quote(unquote_unreserved(uri), safe=safe_with_percent) + except InvalidURL: + # We couldn't unquote the given URI, so let's try quoting it, but + # there may be unquoted '%'s in the URI. We need to make sure they're + # properly quoted so they do not cause issues elsewhere. + return quote(uri, safe=safe_without_percent) + + +def address_in_network(ip, net): + """This function allows you to check if an IP belongs to a network subnet + + Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24 + returns False if ip = 192.168.1.1 and net = 192.168.100.0/24 + + :rtype: bool + """ + ipaddr = struct.unpack('=L', socket.inet_aton(ip))[0] + netaddr, bits = net.split('/') + netmask = struct.unpack('=L', socket.inet_aton(dotted_netmask(int(bits))))[0] + network = struct.unpack('=L', socket.inet_aton(netaddr))[0] & netmask + return (ipaddr & netmask) == (network & netmask) + + +def dotted_netmask(mask): + """Converts mask from /xx format to xxx.xxx.xxx.xxx + + Example: if mask is 24 function returns 255.255.255.0 + + :rtype: str + """ + bits = 0xffffffff ^ (1 << 32 - mask) - 1 + return socket.inet_ntoa(struct.pack('>I', bits)) + + +def is_ipv4_address(string_ip): + """ + :rtype: bool + """ + try: + socket.inet_aton(string_ip) + except socket.error: + return False + return True + + +def is_valid_cidr(string_network): + """ + Very simple check of the cidr format in no_proxy variable. + + :rtype: bool + """ + if string_network.count('/') == 1: + try: + mask = int(string_network.split('/')[1]) + except ValueError: + return False + + if mask < 1 or mask > 32: + return False + + try: + socket.inet_aton(string_network.split('/')[0]) + except socket.error: + return False + else: + return False + return True + + +@contextlib.contextmanager +def set_environ(env_name, value): + """Set the environment variable 'env_name' to 'value' + + Save previous value, yield, and then restore the previous value stored in + the environment variable 'env_name'. + + If 'value' is None, do nothing""" + value_changed = value is not None + if value_changed: + old_value = os.environ.get(env_name) + os.environ[env_name] = value + try: + yield + finally: + if value_changed: + if old_value is None: + del os.environ[env_name] + else: + os.environ[env_name] = old_value + + +def should_bypass_proxies(url, no_proxy): + """ + Returns whether we should bypass proxies or not. + + :rtype: bool + """ + # Prioritize lowercase environment variables over uppercase + # to keep a consistent behaviour with other http projects (curl, wget). + get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper()) + + # First check whether no_proxy is defined. If it is, check that the URL + # we're getting isn't in the no_proxy list. + no_proxy_arg = no_proxy + if no_proxy is None: + no_proxy = get_proxy('no_proxy') + parsed = urlparse(url) + + if parsed.hostname is None: + # URLs don't always have hostnames, e.g. file:/// urls. + return True + + if no_proxy: + # We need to check whether we match here. We need to see if we match + # the end of the hostname, both with and without the port. + no_proxy = ( + host for host in no_proxy.replace(' ', '').split(',') if host + ) + + if is_ipv4_address(parsed.hostname): + for proxy_ip in no_proxy: + if is_valid_cidr(proxy_ip): + if address_in_network(parsed.hostname, proxy_ip): + return True + elif parsed.hostname == proxy_ip: + # If no_proxy ip was defined in plain IP notation instead of cidr notation & + # matches the IP of the index + return True + else: + host_with_port = parsed.hostname + if parsed.port: + host_with_port += ':{}'.format(parsed.port) + + for host in no_proxy: + if parsed.hostname.endswith(host) or host_with_port.endswith(host): + # The URL does match something in no_proxy, so we don't want + # to apply the proxies on this URL. + return True + + with set_environ('no_proxy', no_proxy_arg): + # parsed.hostname can be `None` in cases such as a file URI. + try: + bypass = proxy_bypass(parsed.hostname) + except (TypeError, socket.gaierror): + bypass = False + + if bypass: + return True + + return False + + +def get_environ_proxies(url, no_proxy=None): + """ + Return a dict of environment proxies. + + :rtype: dict + """ + if should_bypass_proxies(url, no_proxy=no_proxy): + return {} + else: + return getproxies() + + +def select_proxy(url, proxies): + """Select a proxy for the url, if applicable. + + :param url: The url being for the request + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + """ + proxies = proxies or {} + urlparts = urlparse(url) + if urlparts.hostname is None: + return proxies.get(urlparts.scheme, proxies.get('all')) + + proxy_keys = [ + urlparts.scheme + '://' + urlparts.hostname, + urlparts.scheme, + 'all://' + urlparts.hostname, + 'all', + ] + proxy = None + for proxy_key in proxy_keys: + if proxy_key in proxies: + proxy = proxies[proxy_key] + break + + return proxy + + +def resolve_proxies(request, proxies, trust_env=True): + """This method takes proxy information from a request and configuration + input to resolve a mapping of target proxies. This will consider settings + such a NO_PROXY to strip proxy configurations. + + :param request: Request or PreparedRequest + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + :param trust_env: Boolean declaring whether to trust environment configs + + :rtype: dict + """ + proxies = proxies if proxies is not None else {} + url = request.url + scheme = urlparse(url).scheme + no_proxy = proxies.get('no_proxy') + new_proxies = proxies.copy() + + if trust_env and not should_bypass_proxies(url, no_proxy=no_proxy): + environ_proxies = get_environ_proxies(url, no_proxy=no_proxy) + + proxy = environ_proxies.get(scheme, environ_proxies.get('all')) + + if proxy: + new_proxies.setdefault(scheme, proxy) + return new_proxies + + +def default_user_agent(name="python-requests"): + """ + Return a string representing the default user agent. + + :rtype: str + """ + return '%s/%s' % (name, __version__) + + +def default_headers(): + """ + :rtype: requests.structures.CaseInsensitiveDict + """ + return CaseInsensitiveDict({ + 'User-Agent': default_user_agent(), + 'Accept-Encoding': DEFAULT_ACCEPT_ENCODING, + 'Accept': '*/*', + 'Connection': 'keep-alive', + }) + + +def parse_header_links(value): + """Return a list of parsed link headers proxies. + + i.e. Link: ; rel=front; type="image/jpeg",; rel=back;type="image/jpeg" + + :rtype: list + """ + + links = [] + + replace_chars = ' \'"' + + value = value.strip(replace_chars) + if not value: + return links + + for val in re.split(', *<', value): + try: + url, params = val.split(';', 1) + except ValueError: + url, params = val, '' + + link = {'url': url.strip('<> \'"')} + + for param in params.split(';'): + try: + key, value = param.split('=') + except ValueError: + break + + link[key.strip(replace_chars)] = value.strip(replace_chars) + + links.append(link) + + return links + + +# Null bytes; no need to recreate these on each call to guess_json_utf +_null = '\x00'.encode('ascii') # encoding to ASCII for Python 3 +_null2 = _null * 2 +_null3 = _null * 3 + + +def guess_json_utf(data): + """ + :rtype: str + """ + # JSON always starts with two ASCII characters, so detection is as + # easy as counting the nulls and from their location and count + # determine the encoding. Also detect a BOM, if present. + sample = data[:4] + if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE): + return 'utf-32' # BOM included + if sample[:3] == codecs.BOM_UTF8: + return 'utf-8-sig' # BOM included, MS style (discouraged) + if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE): + return 'utf-16' # BOM included + nullcount = sample.count(_null) + if nullcount == 0: + return 'utf-8' + if nullcount == 2: + if sample[::2] == _null2: # 1st and 3rd are null + return 'utf-16-be' + if sample[1::2] == _null2: # 2nd and 4th are null + return 'utf-16-le' + # Did not detect 2 valid UTF-16 ascii-range characters + if nullcount == 3: + if sample[:3] == _null3: + return 'utf-32-be' + if sample[1:] == _null3: + return 'utf-32-le' + # Did not detect a valid UTF-32 ascii-range character + return None + + +def prepend_scheme_if_needed(url, new_scheme): + """Given a URL that may or may not have a scheme, prepend the given scheme. + Does not replace a present scheme with the one provided as an argument. + + :rtype: str + """ + parsed = parse_url(url) + scheme, auth, host, port, path, query, fragment = parsed + + # A defect in urlparse determines that there isn't a netloc present in some + # urls. We previously assumed parsing was overly cautious, and swapped the + # netloc and path. Due to a lack of tests on the original defect, this is + # maintained with parse_url for backwards compatibility. + netloc = parsed.netloc + if not netloc: + netloc, path = path, netloc + + if auth: + # parse_url doesn't provide the netloc with auth + # so we'll add it ourselves. + netloc = '@'.join([auth, netloc]) + if scheme is None: + scheme = new_scheme + if path is None: + path = '' + + return urlunparse((scheme, netloc, path, '', query, fragment)) + + +def get_auth_from_url(url): + """Given a url with authentication components, extract them into a tuple of + username,password. + + :rtype: (str,str) + """ + parsed = urlparse(url) + + try: + auth = (unquote(parsed.username), unquote(parsed.password)) + except (AttributeError, TypeError): + auth = ('', '') + + return auth + + +# Moved outside of function to avoid recompile every call +_CLEAN_HEADER_REGEX_BYTE = re.compile(b'^\\S[^\\r\\n]*$|^$') +_CLEAN_HEADER_REGEX_STR = re.compile(r'^\S[^\r\n]*$|^$') + + +def check_header_validity(header): + """Verifies that header value is a string which doesn't contain + leading whitespace or return characters. This prevents unintended + header injection. + + :param header: tuple, in the format (name, value). + """ + name, value = header + + if isinstance(value, bytes): + pat = _CLEAN_HEADER_REGEX_BYTE + else: + pat = _CLEAN_HEADER_REGEX_STR + try: + if not pat.match(value): + raise InvalidHeader("Invalid return character or leading space in header: %s" % name) + except TypeError: + raise InvalidHeader("Value for header {%s: %s} must be of type str or " + "bytes, not %s" % (name, value, type(value))) + + +def urldefragauth(url): + """ + Given a url remove the fragment and the authentication part. + + :rtype: str + """ + scheme, netloc, path, params, query, fragment = urlparse(url) + + # see func:`prepend_scheme_if_needed` + if not netloc: + netloc, path = path, netloc + + netloc = netloc.rsplit('@', 1)[-1] + + return urlunparse((scheme, netloc, path, params, query, '')) + + +def rewind_body(prepared_request): + """Move file pointer back to its recorded starting position + so it can be read again on redirect. + """ + body_seek = getattr(prepared_request.body, 'seek', None) + if body_seek is not None and isinstance(prepared_request._body_position, integer_types): + try: + body_seek(prepared_request._body_position) + except (IOError, OSError): + raise UnrewindableBodyError("An error occurred when rewinding request " + "body for redirect.") + else: + raise UnrewindableBodyError("Unable to rewind request body for redirect.") diff --git a/openpype/vendor/python/python_2/urllib3/__init__.py b/openpype/vendor/python/python_2/urllib3/__init__.py new file mode 100644 index 0000000000..fe86b59d78 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/__init__.py @@ -0,0 +1,85 @@ +""" +Python HTTP library with thread-safe connection pooling, file post support, user friendly, and more +""" +from __future__ import absolute_import + +# Set default logging handler to avoid "No handler found" warnings. +import logging +import warnings +from logging import NullHandler + +from . import exceptions +from ._version import __version__ +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, connection_from_url +from .filepost import encode_multipart_formdata +from .poolmanager import PoolManager, ProxyManager, proxy_from_url +from .response import HTTPResponse +from .util.request import make_headers +from .util.retry import Retry +from .util.timeout import Timeout +from .util.url import get_host + +__author__ = "Andrey Petrov (andrey.petrov@shazow.net)" +__license__ = "MIT" +__version__ = __version__ + +__all__ = ( + "HTTPConnectionPool", + "HTTPSConnectionPool", + "PoolManager", + "ProxyManager", + "HTTPResponse", + "Retry", + "Timeout", + "add_stderr_logger", + "connection_from_url", + "disable_warnings", + "encode_multipart_formdata", + "get_host", + "make_headers", + "proxy_from_url", +) + +logging.getLogger(__name__).addHandler(NullHandler()) + + +def add_stderr_logger(level=logging.DEBUG): + """ + Helper for quickly adding a StreamHandler to the logger. Useful for + debugging. + + Returns the handler after adding it. + """ + # This method needs to be in this __init__.py to get the __name__ correct + # even if urllib3 is vendored within another package. + logger = logging.getLogger(__name__) + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s")) + logger.addHandler(handler) + logger.setLevel(level) + logger.debug("Added a stderr logging handler to logger: %s", __name__) + return handler + + +# ... Clean up. +del NullHandler + + +# All warning filters *must* be appended unless you're really certain that they +# shouldn't be: otherwise, it's very hard for users to use most Python +# mechanisms to silence them. +# SecurityWarning's always go off by default. +warnings.simplefilter("always", exceptions.SecurityWarning, append=True) +# SubjectAltNameWarning's should go off once per host +warnings.simplefilter("default", exceptions.SubjectAltNameWarning, append=True) +# InsecurePlatformWarning's don't vary between requests, so we keep it default. +warnings.simplefilter("default", exceptions.InsecurePlatformWarning, append=True) +# SNIMissingWarnings should go off only once. +warnings.simplefilter("default", exceptions.SNIMissingWarning, append=True) + + +def disable_warnings(category=exceptions.HTTPWarning): + """ + Helper for quickly disabling all urllib3 warnings. + """ + warnings.simplefilter("ignore", category) diff --git a/openpype/vendor/python/python_2/urllib3/_collections.py b/openpype/vendor/python/python_2/urllib3/_collections.py new file mode 100644 index 0000000000..da9857e986 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/_collections.py @@ -0,0 +1,337 @@ +from __future__ import absolute_import + +try: + from collections.abc import Mapping, MutableMapping +except ImportError: + from collections import Mapping, MutableMapping +try: + from threading import RLock +except ImportError: # Platform-specific: No threads available + + class RLock: + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_value, traceback): + pass + + +from collections import OrderedDict + +from .exceptions import InvalidHeader +from .packages import six +from .packages.six import iterkeys, itervalues + +__all__ = ["RecentlyUsedContainer", "HTTPHeaderDict"] + + +_Null = object() + + +class RecentlyUsedContainer(MutableMapping): + """ + Provides a thread-safe dict-like container which maintains up to + ``maxsize`` keys while throwing away the least-recently-used keys beyond + ``maxsize``. + + :param maxsize: + Maximum number of recent elements to retain. + + :param dispose_func: + Every time an item is evicted from the container, + ``dispose_func(value)`` is called. Callback which will get called + """ + + ContainerCls = OrderedDict + + def __init__(self, maxsize=10, dispose_func=None): + self._maxsize = maxsize + self.dispose_func = dispose_func + + self._container = self.ContainerCls() + self.lock = RLock() + + def __getitem__(self, key): + # Re-insert the item, moving it to the end of the eviction line. + with self.lock: + item = self._container.pop(key) + self._container[key] = item + return item + + def __setitem__(self, key, value): + evicted_value = _Null + with self.lock: + # Possibly evict the existing value of 'key' + evicted_value = self._container.get(key, _Null) + self._container[key] = value + + # If we didn't evict an existing value, we might have to evict the + # least recently used item from the beginning of the container. + if len(self._container) > self._maxsize: + _key, evicted_value = self._container.popitem(last=False) + + if self.dispose_func and evicted_value is not _Null: + self.dispose_func(evicted_value) + + def __delitem__(self, key): + with self.lock: + value = self._container.pop(key) + + if self.dispose_func: + self.dispose_func(value) + + def __len__(self): + with self.lock: + return len(self._container) + + def __iter__(self): + raise NotImplementedError( + "Iteration over this class is unlikely to be threadsafe." + ) + + def clear(self): + with self.lock: + # Copy pointers to all values, then wipe the mapping + values = list(itervalues(self._container)) + self._container.clear() + + if self.dispose_func: + for value in values: + self.dispose_func(value) + + def keys(self): + with self.lock: + return list(iterkeys(self._container)) + + +class HTTPHeaderDict(MutableMapping): + """ + :param headers: + An iterable of field-value pairs. Must not contain multiple field names + when compared case-insensitively. + + :param kwargs: + Additional field-value pairs to pass in to ``dict.update``. + + A ``dict`` like container for storing HTTP Headers. + + Field names are stored and compared case-insensitively in compliance with + RFC 7230. Iteration provides the first case-sensitive key seen for each + case-insensitive pair. + + Using ``__setitem__`` syntax overwrites fields that compare equal + case-insensitively in order to maintain ``dict``'s api. For fields that + compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add`` + in a loop. + + If multiple fields that are equal case-insensitively are passed to the + constructor or ``.update``, the behavior is undefined and some will be + lost. + + >>> headers = HTTPHeaderDict() + >>> headers.add('Set-Cookie', 'foo=bar') + >>> headers.add('set-cookie', 'baz=quxx') + >>> headers['content-length'] = '7' + >>> headers['SET-cookie'] + 'foo=bar, baz=quxx' + >>> headers['Content-Length'] + '7' + """ + + def __init__(self, headers=None, **kwargs): + super(HTTPHeaderDict, self).__init__() + self._container = OrderedDict() + if headers is not None: + if isinstance(headers, HTTPHeaderDict): + self._copy_from(headers) + else: + self.extend(headers) + if kwargs: + self.extend(kwargs) + + def __setitem__(self, key, val): + self._container[key.lower()] = [key, val] + return self._container[key.lower()] + + def __getitem__(self, key): + val = self._container[key.lower()] + return ", ".join(val[1:]) + + def __delitem__(self, key): + del self._container[key.lower()] + + def __contains__(self, key): + return key.lower() in self._container + + def __eq__(self, other): + if not isinstance(other, Mapping) and not hasattr(other, "keys"): + return False + if not isinstance(other, type(self)): + other = type(self)(other) + return dict((k.lower(), v) for k, v in self.itermerged()) == dict( + (k.lower(), v) for k, v in other.itermerged() + ) + + def __ne__(self, other): + return not self.__eq__(other) + + if six.PY2: # Python 2 + iterkeys = MutableMapping.iterkeys + itervalues = MutableMapping.itervalues + + __marker = object() + + def __len__(self): + return len(self._container) + + def __iter__(self): + # Only provide the originally cased names + for vals in self._container.values(): + yield vals[0] + + def pop(self, key, default=__marker): + """D.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised. + """ + # Using the MutableMapping function directly fails due to the private marker. + # Using ordinary dict.pop would expose the internal structures. + # So let's reinvent the wheel. + try: + value = self[key] + except KeyError: + if default is self.__marker: + raise + return default + else: + del self[key] + return value + + def discard(self, key): + try: + del self[key] + except KeyError: + pass + + def add(self, key, val): + """Adds a (name, value) pair, doesn't overwrite the value if it already + exists. + + >>> headers = HTTPHeaderDict(foo='bar') + >>> headers.add('Foo', 'baz') + >>> headers['foo'] + 'bar, baz' + """ + key_lower = key.lower() + new_vals = [key, val] + # Keep the common case aka no item present as fast as possible + vals = self._container.setdefault(key_lower, new_vals) + if new_vals is not vals: + vals.append(val) + + def extend(self, *args, **kwargs): + """Generic import function for any type of header-like object. + Adapted version of MutableMapping.update in order to insert items + with self.add instead of self.__setitem__ + """ + if len(args) > 1: + raise TypeError( + "extend() takes at most 1 positional " + "arguments ({0} given)".format(len(args)) + ) + other = args[0] if len(args) >= 1 else () + + if isinstance(other, HTTPHeaderDict): + for key, val in other.iteritems(): + self.add(key, val) + elif isinstance(other, Mapping): + for key in other: + self.add(key, other[key]) + elif hasattr(other, "keys"): + for key in other.keys(): + self.add(key, other[key]) + else: + for key, value in other: + self.add(key, value) + + for key, value in kwargs.items(): + self.add(key, value) + + def getlist(self, key, default=__marker): + """Returns a list of all the values for the named field. Returns an + empty list if the key doesn't exist.""" + try: + vals = self._container[key.lower()] + except KeyError: + if default is self.__marker: + return [] + return default + else: + return vals[1:] + + # Backwards compatibility for httplib + getheaders = getlist + getallmatchingheaders = getlist + iget = getlist + + # Backwards compatibility for http.cookiejar + get_all = getlist + + def __repr__(self): + return "%s(%s)" % (type(self).__name__, dict(self.itermerged())) + + def _copy_from(self, other): + for key in other: + val = other.getlist(key) + if isinstance(val, list): + # Don't need to convert tuples + val = list(val) + self._container[key.lower()] = [key] + val + + def copy(self): + clone = type(self)() + clone._copy_from(self) + return clone + + def iteritems(self): + """Iterate over all header lines, including duplicate ones.""" + for key in self: + vals = self._container[key.lower()] + for val in vals[1:]: + yield vals[0], val + + def itermerged(self): + """Iterate over all headers, merging duplicate ones together.""" + for key in self: + val = self._container[key.lower()] + yield val[0], ", ".join(val[1:]) + + def items(self): + return list(self.iteritems()) + + @classmethod + def from_httplib(cls, message): # Python 2 + """Read headers from a Python 2 httplib message object.""" + # python2.7 does not expose a proper API for exporting multiheaders + # efficiently. This function re-reads raw lines from the message + # object and extracts the multiheaders properly. + obs_fold_continued_leaders = (" ", "\t") + headers = [] + + for line in message.headers: + if line.startswith(obs_fold_continued_leaders): + if not headers: + # We received a header line that starts with OWS as described + # in RFC-7230 S3.2.4. This indicates a multiline header, but + # there exists no previous header to which we can attach it. + raise InvalidHeader( + "Header continuation with no previous header: %s" % line + ) + else: + key, value = headers[-1] + headers[-1] = (key, value + " " + line.strip()) + continue + + key, value = line.split(":", 1) + headers.append((key, value.strip())) + + return cls(headers) diff --git a/openpype/vendor/python/python_2/urllib3/_version.py b/openpype/vendor/python/python_2/urllib3/_version.py new file mode 100644 index 0000000000..d905b69755 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/_version.py @@ -0,0 +1,2 @@ +# This file is protected via CODEOWNERS +__version__ = "1.26.9" diff --git a/openpype/vendor/python/python_2/urllib3/connection.py b/openpype/vendor/python/python_2/urllib3/connection.py new file mode 100644 index 0000000000..7bf395bdac --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/connection.py @@ -0,0 +1,567 @@ +from __future__ import absolute_import + +import datetime +import logging +import os +import re +import socket +import warnings +from socket import error as SocketError +from socket import timeout as SocketTimeout + +from .packages import six +from .packages.six.moves.http_client import HTTPConnection as _HTTPConnection +from .packages.six.moves.http_client import HTTPException # noqa: F401 +from .util.proxy import create_proxy_ssl_context + +try: # Compiled with SSL? + import ssl + + BaseSSLError = ssl.SSLError +except (ImportError, AttributeError): # Platform-specific: No SSL. + ssl = None + + class BaseSSLError(BaseException): + pass + + +try: + # Python 3: not a no-op, we're adding this to the namespace so it can be imported. + ConnectionError = ConnectionError +except NameError: + # Python 2 + class ConnectionError(Exception): + pass + + +try: # Python 3: + # Not a no-op, we're adding this to the namespace so it can be imported. + BrokenPipeError = BrokenPipeError +except NameError: # Python 2: + + class BrokenPipeError(Exception): + pass + + +from ._collections import HTTPHeaderDict # noqa (historical, removed in v2) +from ._version import __version__ +from .exceptions import ( + ConnectTimeoutError, + NewConnectionError, + SubjectAltNameWarning, + SystemTimeWarning, +) +from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection +from .util.ssl_ import ( + assert_fingerprint, + create_urllib3_context, + is_ipaddress, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, +) +from .util.ssl_match_hostname import CertificateError, match_hostname + +log = logging.getLogger(__name__) + +port_by_scheme = {"http": 80, "https": 443} + +# When it comes time to update this value as a part of regular maintenance +# (ie test_recent_date is failing) update it to ~6 months before the current date. +RECENT_DATE = datetime.date(2020, 7, 1) + +_CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]") + + +class HTTPConnection(_HTTPConnection, object): + """ + Based on :class:`http.client.HTTPConnection` but provides an extra constructor + backwards-compatibility layer between older and newer Pythons. + + Additional keyword parameters are used to configure attributes of the connection. + Accepted parameters include: + + - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool` + - ``source_address``: Set the source address for the current connection. + - ``socket_options``: Set specific options on the underlying socket. If not specified, then + defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling + Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. + + For example, if you wish to enable TCP Keep Alive in addition to the defaults, + you might pass: + + .. code-block:: python + + HTTPConnection.default_socket_options + [ + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), + ] + + Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). + """ + + default_port = port_by_scheme["http"] + + #: Disable Nagle's algorithm by default. + #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` + default_socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)] + + #: Whether this connection verifies the host's certificate. + is_verified = False + + #: Whether this proxy connection (if used) verifies the proxy host's + #: certificate. + proxy_is_verified = None + + def __init__(self, *args, **kw): + if not six.PY2: + kw.pop("strict", None) + + # Pre-set source_address. + self.source_address = kw.get("source_address") + + #: The socket options provided by the user. If no options are + #: provided, we use the default options. + self.socket_options = kw.pop("socket_options", self.default_socket_options) + + # Proxy options provided by the user. + self.proxy = kw.pop("proxy", None) + self.proxy_config = kw.pop("proxy_config", None) + + _HTTPConnection.__init__(self, *args, **kw) + + @property + def host(self): + """ + Getter method to remove any trailing dots that indicate the hostname is an FQDN. + + In general, SSL certificates don't include the trailing dot indicating a + fully-qualified domain name, and thus, they don't validate properly when + checked against a domain name that includes the dot. In addition, some + servers may not expect to receive the trailing dot when provided. + + However, the hostname with trailing dot is critical to DNS resolution; doing a + lookup with the trailing dot will properly only resolve the appropriate FQDN, + whereas a lookup without a trailing dot will search the system's search domain + list. Thus, it's important to keep the original host around for use only in + those cases where it's appropriate (i.e., when doing DNS lookup to establish the + actual TCP connection across which we're going to send HTTP requests). + """ + return self._dns_host.rstrip(".") + + @host.setter + def host(self, value): + """ + Setter for the `host` property. + + We assume that only urllib3 uses the _dns_host attribute; httplib itself + only uses `host`, and it seems reasonable that other libraries follow suit. + """ + self._dns_host = value + + def _new_conn(self): + """Establish a socket connection and set nodelay settings on it. + + :return: New socket connection. + """ + extra_kw = {} + if self.source_address: + extra_kw["source_address"] = self.source_address + + if self.socket_options: + extra_kw["socket_options"] = self.socket_options + + try: + conn = connection.create_connection( + (self._dns_host, self.port), self.timeout, **extra_kw + ) + + except SocketTimeout: + raise ConnectTimeoutError( + self, + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) + + except SocketError as e: + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e + ) + + return conn + + def _is_using_tunnel(self): + # Google App Engine's httplib does not define _tunnel_host + return getattr(self, "_tunnel_host", None) + + def _prepare_conn(self, conn): + self.sock = conn + if self._is_using_tunnel(): + # TODO: Fix tunnel so it doesn't depend on self.sock state. + self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 + + def connect(self): + conn = self._new_conn() + self._prepare_conn(conn) + + def putrequest(self, method, url, *args, **kwargs): + """ """ + # Empty docstring because the indentation of CPython's implementation + # is broken but we don't want this method in our documentation. + match = _CONTAINS_CONTROL_CHAR_RE.search(method) + if match: + raise ValueError( + "Method cannot contain non-token characters %r (found at least %r)" + % (method, match.group()) + ) + + return _HTTPConnection.putrequest(self, method, url, *args, **kwargs) + + def putheader(self, header, *values): + """ """ + if not any(isinstance(v, str) and v == SKIP_HEADER for v in values): + _HTTPConnection.putheader(self, header, *values) + elif six.ensure_str(header.lower()) not in SKIPPABLE_HEADERS: + raise ValueError( + "urllib3.util.SKIP_HEADER only supports '%s'" + % ("', '".join(map(str.title, sorted(SKIPPABLE_HEADERS))),) + ) + + def request(self, method, url, body=None, headers=None): + if headers is None: + headers = {} + else: + # Avoid modifying the headers passed into .request() + headers = headers.copy() + if "user-agent" not in (six.ensure_str(k.lower()) for k in headers): + headers["User-Agent"] = _get_default_user_agent() + super(HTTPConnection, self).request(method, url, body=body, headers=headers) + + def request_chunked(self, method, url, body=None, headers=None): + """ + Alternative to the common request method, which sends the + body with chunked encoding and not as one block + """ + headers = headers or {} + header_keys = set([six.ensure_str(k.lower()) for k in headers]) + skip_accept_encoding = "accept-encoding" in header_keys + skip_host = "host" in header_keys + self.putrequest( + method, url, skip_accept_encoding=skip_accept_encoding, skip_host=skip_host + ) + if "user-agent" not in header_keys: + self.putheader("User-Agent", _get_default_user_agent()) + for header, value in headers.items(): + self.putheader(header, value) + if "transfer-encoding" not in header_keys: + self.putheader("Transfer-Encoding", "chunked") + self.endheaders() + + if body is not None: + stringish_types = six.string_types + (bytes,) + if isinstance(body, stringish_types): + body = (body,) + for chunk in body: + if not chunk: + continue + if not isinstance(chunk, bytes): + chunk = chunk.encode("utf8") + len_str = hex(len(chunk))[2:] + to_send = bytearray(len_str.encode()) + to_send += b"\r\n" + to_send += chunk + to_send += b"\r\n" + self.send(to_send) + + # After the if clause, to always have a closed body + self.send(b"0\r\n\r\n") + + +class HTTPSConnection(HTTPConnection): + """ + Many of the parameters to this constructor are passed to the underlying SSL + socket by means of :py:func:`urllib3.util.ssl_wrap_socket`. + """ + + default_port = port_by_scheme["https"] + + cert_reqs = None + ca_certs = None + ca_cert_dir = None + ca_cert_data = None + ssl_version = None + assert_fingerprint = None + tls_in_tls_required = False + + def __init__( + self, + host, + port=None, + key_file=None, + cert_file=None, + key_password=None, + strict=None, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + ssl_context=None, + server_hostname=None, + **kw + ): + + HTTPConnection.__init__(self, host, port, strict=strict, timeout=timeout, **kw) + + self.key_file = key_file + self.cert_file = cert_file + self.key_password = key_password + self.ssl_context = ssl_context + self.server_hostname = server_hostname + + # Required property for Google AppEngine 1.9.0 which otherwise causes + # HTTPS requests to go out as HTTP. (See Issue #356) + self._protocol = "https" + + def set_cert( + self, + key_file=None, + cert_file=None, + cert_reqs=None, + key_password=None, + ca_certs=None, + assert_hostname=None, + assert_fingerprint=None, + ca_cert_dir=None, + ca_cert_data=None, + ): + """ + This method should only be called once, before the connection is used. + """ + # If cert_reqs is not provided we'll assume CERT_REQUIRED unless we also + # have an SSLContext object in which case we'll use its verify_mode. + if cert_reqs is None: + if self.ssl_context is not None: + cert_reqs = self.ssl_context.verify_mode + else: + cert_reqs = resolve_cert_reqs(None) + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.key_password = key_password + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + self.ca_certs = ca_certs and os.path.expanduser(ca_certs) + self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) + self.ca_cert_data = ca_cert_data + + def connect(self): + # Add certificate verification + self.sock = conn = self._new_conn() + hostname = self.host + tls_in_tls = False + + if self._is_using_tunnel(): + if self.tls_in_tls_required: + self.sock = conn = self._connect_tls_proxy(hostname, conn) + tls_in_tls = True + + # Calls self._set_hostport(), so self.host is + # self._tunnel_host below. + self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 + + # Override the host with the one we're requesting data from. + hostname = self._tunnel_host + + server_hostname = hostname + if self.server_hostname is not None: + server_hostname = self.server_hostname + + is_time_off = datetime.date.today() < RECENT_DATE + if is_time_off: + warnings.warn( + ( + "System time is way off (before {0}). This will probably " + "lead to SSL verification errors" + ).format(RECENT_DATE), + SystemTimeWarning, + ) + + # Wrap socket using verification with the root certs in + # trusted_root_certs + default_ssl_context = False + if self.ssl_context is None: + default_ssl_context = True + self.ssl_context = create_urllib3_context( + ssl_version=resolve_ssl_version(self.ssl_version), + cert_reqs=resolve_cert_reqs(self.cert_reqs), + ) + + context = self.ssl_context + context.verify_mode = resolve_cert_reqs(self.cert_reqs) + + # Try to load OS default certs if none are given. + # Works well on Windows (requires Python3.4+) + if ( + not self.ca_certs + and not self.ca_cert_dir + and not self.ca_cert_data + and default_ssl_context + and hasattr(context, "load_default_certs") + ): + context.load_default_certs() + + self.sock = ssl_wrap_socket( + sock=conn, + keyfile=self.key_file, + certfile=self.cert_file, + key_password=self.key_password, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, + server_hostname=server_hostname, + ssl_context=context, + tls_in_tls=tls_in_tls, + ) + + # If we're using all defaults and the connection + # is TLSv1 or TLSv1.1 we throw a DeprecationWarning + # for the host. + if ( + default_ssl_context + and self.ssl_version is None + and hasattr(self.sock, "version") + and self.sock.version() in {"TLSv1", "TLSv1.1"} + ): + warnings.warn( + "Negotiating TLSv1/TLSv1.1 by default is deprecated " + "and will be disabled in urllib3 v2.0.0. Connecting to " + "'%s' with '%s' can be enabled by explicitly opting-in " + "with 'ssl_version'" % (self.host, self.sock.version()), + DeprecationWarning, + ) + + if self.assert_fingerprint: + assert_fingerprint( + self.sock.getpeercert(binary_form=True), self.assert_fingerprint + ) + elif ( + context.verify_mode != ssl.CERT_NONE + and not getattr(context, "check_hostname", False) + and self.assert_hostname is not False + ): + # While urllib3 attempts to always turn off hostname matching from + # the TLS library, this cannot always be done. So we check whether + # the TLS Library still thinks it's matching hostnames. + cert = self.sock.getpeercert() + if not cert.get("subjectAltName", ()): + warnings.warn( + ( + "Certificate for {0} has no `subjectAltName`, falling back to check for a " + "`commonName` for now. This feature is being removed by major browsers and " + "deprecated by RFC 2818. (See https://github.com/urllib3/urllib3/issues/497 " + "for details.)".format(hostname) + ), + SubjectAltNameWarning, + ) + _match_hostname(cert, self.assert_hostname or server_hostname) + + self.is_verified = ( + context.verify_mode == ssl.CERT_REQUIRED + or self.assert_fingerprint is not None + ) + + def _connect_tls_proxy(self, hostname, conn): + """ + Establish a TLS connection to the proxy using the provided SSL context. + """ + proxy_config = self.proxy_config + ssl_context = proxy_config.ssl_context + if ssl_context: + # If the user provided a proxy context, we assume CA and client + # certificates have already been set + return ssl_wrap_socket( + sock=conn, + server_hostname=hostname, + ssl_context=ssl_context, + ) + + ssl_context = create_proxy_ssl_context( + self.ssl_version, + self.cert_reqs, + self.ca_certs, + self.ca_cert_dir, + self.ca_cert_data, + ) + + # If no cert was provided, use only the default options for server + # certificate validation + socket = ssl_wrap_socket( + sock=conn, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, + server_hostname=hostname, + ssl_context=ssl_context, + ) + + if ssl_context.verify_mode != ssl.CERT_NONE and not getattr( + ssl_context, "check_hostname", False + ): + # While urllib3 attempts to always turn off hostname matching from + # the TLS library, this cannot always be done. So we check whether + # the TLS Library still thinks it's matching hostnames. + cert = socket.getpeercert() + if not cert.get("subjectAltName", ()): + warnings.warn( + ( + "Certificate for {0} has no `subjectAltName`, falling back to check for a " + "`commonName` for now. This feature is being removed by major browsers and " + "deprecated by RFC 2818. (See https://github.com/urllib3/urllib3/issues/497 " + "for details.)".format(hostname) + ), + SubjectAltNameWarning, + ) + _match_hostname(cert, hostname) + + self.proxy_is_verified = ssl_context.verify_mode == ssl.CERT_REQUIRED + return socket + + +def _match_hostname(cert, asserted_hostname): + # Our upstream implementation of ssl.match_hostname() + # only applies this normalization to IP addresses so it doesn't + # match DNS SANs so we do the same thing! + stripped_hostname = asserted_hostname.strip("u[]") + if is_ipaddress(stripped_hostname): + asserted_hostname = stripped_hostname + + try: + match_hostname(cert, asserted_hostname) + except CertificateError as e: + log.warning( + "Certificate did not match expected hostname: %s. Certificate: %s", + asserted_hostname, + cert, + ) + # Add cert to exception and reraise so client code can inspect + # the cert when catching the exception, if they want to + e._peer_cert = cert + raise + + +def _get_default_user_agent(): + return "python-urllib3/%s" % __version__ + + +class DummyConnection(object): + """Used to detect a failed ConnectionCls import.""" + + pass + + +if not ssl: + HTTPSConnection = DummyConnection # noqa: F811 + + +VerifiedHTTPSConnection = HTTPSConnection diff --git a/openpype/vendor/python/python_2/urllib3/connectionpool.py b/openpype/vendor/python/python_2/urllib3/connectionpool.py new file mode 100644 index 0000000000..15bffcb23a --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/connectionpool.py @@ -0,0 +1,1108 @@ +from __future__ import absolute_import + +import errno +import logging +import re +import socket +import sys +import warnings +from socket import error as SocketError +from socket import timeout as SocketTimeout + +from .connection import ( + BaseSSLError, + BrokenPipeError, + DummyConnection, + HTTPConnection, + HTTPException, + HTTPSConnection, + VerifiedHTTPSConnection, + port_by_scheme, +) +from .exceptions import ( + ClosedPoolError, + EmptyPoolError, + HeaderParsingError, + HostChangedError, + InsecureRequestWarning, + LocationValueError, + MaxRetryError, + NewConnectionError, + ProtocolError, + ProxyError, + ReadTimeoutError, + SSLError, + TimeoutError, +) +from .packages import six +from .packages.six.moves import queue +from .request import RequestMethods +from .response import HTTPResponse +from .util.connection import is_connection_dropped +from .util.proxy import connection_requires_http_tunnel +from .util.queue import LifoQueue +from .util.request import set_file_position +from .util.response import assert_header_parsing +from .util.retry import Retry +from .util.ssl_match_hostname import CertificateError +from .util.timeout import Timeout +from .util.url import Url, _encode_target +from .util.url import _normalize_host as normalize_host +from .util.url import get_host, parse_url + +xrange = six.moves.xrange + +log = logging.getLogger(__name__) + +_Default = object() + + +# Pool objects +class ConnectionPool(object): + """ + Base class for all connection pools, such as + :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`. + + .. note:: + ConnectionPool.urlopen() does not normalize or percent-encode target URIs + which is useful if your target server doesn't support percent-encoded + target URIs. + """ + + scheme = None + QueueCls = LifoQueue + + def __init__(self, host, port=None): + if not host: + raise LocationValueError("No host specified.") + + self.host = _normalize_host(host, scheme=self.scheme) + self._proxy_host = host.lower() + self.port = port + + def __str__(self): + return "%s(host=%r, port=%r)" % (type(self).__name__, self.host, self.port) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + # Return False to re-raise any potential exceptions + return False + + def close(self): + """ + Close all pooled connections and disable the pool. + """ + pass + + +# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252 +_blocking_errnos = {errno.EAGAIN, errno.EWOULDBLOCK} + + +class HTTPConnectionPool(ConnectionPool, RequestMethods): + """ + Thread-safe connection pool for one host. + + :param host: + Host used for this HTTP Connection (e.g. "localhost"), passed into + :class:`http.client.HTTPConnection`. + + :param port: + Port used for this HTTP Connection (None is equivalent to 80), passed + into :class:`http.client.HTTPConnection`. + + :param strict: + Causes BadStatusLine to be raised if the status line can't be parsed + as a valid HTTP/1.0 or 1.1 status line, passed into + :class:`http.client.HTTPConnection`. + + .. note:: + Only works in Python 2. This parameter is ignored in Python 3. + + :param timeout: + Socket timeout in seconds for each individual connection. This can + be a float or integer, which sets the timeout for the HTTP request, + or an instance of :class:`urllib3.util.Timeout` which gives you more + fine-grained control over request timeouts. After the constructor has + been parsed, this is always a `urllib3.util.Timeout` object. + + :param maxsize: + Number of connections to save that can be reused. More than 1 is useful + in multithreaded situations. If ``block`` is set to False, more + connections will be created but they will not be saved once they've + been used. + + :param block: + If set to True, no more than ``maxsize`` connections will be used at + a time. When no free connections are available, the call will block + until a connection has been released. This is a useful side effect for + particular multithreaded situations where one does not want to use more + than maxsize connections per host to prevent flooding. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param retries: + Retry configuration to use by default with requests in this pool. + + :param _proxy: + Parsed proxy URL, should not be used directly, instead, see + :class:`urllib3.ProxyManager` + + :param _proxy_headers: + A dictionary with proxy headers, should not be used directly, + instead, see :class:`urllib3.ProxyManager` + + :param \\**conn_kw: + Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`, + :class:`urllib3.connection.HTTPSConnection` instances. + """ + + scheme = "http" + ConnectionCls = HTTPConnection + ResponseCls = HTTPResponse + + def __init__( + self, + host, + port=None, + strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, + maxsize=1, + block=False, + headers=None, + retries=None, + _proxy=None, + _proxy_headers=None, + _proxy_config=None, + **conn_kw + ): + ConnectionPool.__init__(self, host, port) + RequestMethods.__init__(self, headers) + + self.strict = strict + + if not isinstance(timeout, Timeout): + timeout = Timeout.from_float(timeout) + + if retries is None: + retries = Retry.DEFAULT + + self.timeout = timeout + self.retries = retries + + self.pool = self.QueueCls(maxsize) + self.block = block + + self.proxy = _proxy + self.proxy_headers = _proxy_headers or {} + self.proxy_config = _proxy_config + + # Fill the queue up so that doing get() on it will block properly + for _ in xrange(maxsize): + self.pool.put(None) + + # These are mostly for testing and debugging purposes. + self.num_connections = 0 + self.num_requests = 0 + self.conn_kw = conn_kw + + if self.proxy: + # Enable Nagle's algorithm for proxies, to avoid packet fragmentation. + # We cannot know if the user has added default socket options, so we cannot replace the + # list. + self.conn_kw.setdefault("socket_options", []) + + self.conn_kw["proxy"] = self.proxy + self.conn_kw["proxy_config"] = self.proxy_config + + def _new_conn(self): + """ + Return a fresh :class:`HTTPConnection`. + """ + self.num_connections += 1 + log.debug( + "Starting new HTTP connection (%d): %s:%s", + self.num_connections, + self.host, + self.port or "80", + ) + + conn = self.ConnectionCls( + host=self.host, + port=self.port, + timeout=self.timeout.connect_timeout, + strict=self.strict, + **self.conn_kw + ) + return conn + + def _get_conn(self, timeout=None): + """ + Get a connection. Will return a pooled connection if one is available. + + If no connections are available and :prop:`.block` is ``False``, then a + fresh connection is returned. + + :param timeout: + Seconds to wait before giving up and raising + :class:`urllib3.exceptions.EmptyPoolError` if the pool is empty and + :prop:`.block` is ``True``. + """ + conn = None + try: + conn = self.pool.get(block=self.block, timeout=timeout) + + except AttributeError: # self.pool is None + raise ClosedPoolError(self, "Pool is closed.") + + except queue.Empty: + if self.block: + raise EmptyPoolError( + self, + "Pool reached maximum size and no more connections are allowed.", + ) + pass # Oh well, we'll create a new connection then + + # If this is a persistent connection, check if it got disconnected + if conn and is_connection_dropped(conn): + log.debug("Resetting dropped connection: %s", self.host) + conn.close() + if getattr(conn, "auto_open", 1) == 0: + # This is a proxied connection that has been mutated by + # http.client._tunnel() and cannot be reused (since it would + # attempt to bypass the proxy) + conn = None + + return conn or self._new_conn() + + def _put_conn(self, conn): + """ + Put a connection back into the pool. + + :param conn: + Connection object for the current host and port as returned by + :meth:`._new_conn` or :meth:`._get_conn`. + + If the pool is already full, the connection is closed and discarded + because we exceeded maxsize. If connections are discarded frequently, + then maxsize should be increased. + + If the pool is closed, then the connection will be closed and discarded. + """ + try: + self.pool.put(conn, block=False) + return # Everything is dandy, done. + except AttributeError: + # self.pool is None. + pass + except queue.Full: + # This should never happen if self.block == True + log.warning( + "Connection pool is full, discarding connection: %s. Connection pool size: %s", + self.host, + self.pool.qsize(), + ) + # Connection never got put back into the pool, close it. + if conn: + conn.close() + + def _validate_conn(self, conn): + """ + Called right before a request is made, after the socket is created. + """ + pass + + def _prepare_proxy(self, conn): + # Nothing to do for HTTP connections. + pass + + def _get_timeout(self, timeout): + """Helper that always returns a :class:`urllib3.util.Timeout`""" + if timeout is _Default: + return self.timeout.clone() + + if isinstance(timeout, Timeout): + return timeout.clone() + else: + # User passed us an int/float. This is for backwards compatibility, + # can be removed later + return Timeout.from_float(timeout) + + def _raise_timeout(self, err, url, timeout_value): + """Is the error actually a timeout? Will raise a ReadTimeout or pass""" + + if isinstance(err, SocketTimeout): + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) + + # See the above comment about EAGAIN in Python 3. In Python 2 we have + # to specifically catch it and throw the timeout error + if hasattr(err, "errno") and err.errno in _blocking_errnos: + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) + + # Catch possible read timeouts thrown as SSL errors. If not the + # case, rethrow the original. We need to do this because of: + # http://bugs.python.org/issue10272 + if "timed out" in str(err) or "did not complete (read)" in str( + err + ): # Python < 2.7.4 + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) + + def _make_request( + self, conn, method, url, timeout=_Default, chunked=False, **httplib_request_kw + ): + """ + Perform a request on a given urllib connection object taken from our + pool. + + :param conn: + a connection from one of our connection pools + + :param timeout: + Socket timeout in seconds for the request. This can be a + float or integer, which will set the same timeout value for + the socket connect and the socket read, or an instance of + :class:`urllib3.util.Timeout`, which gives you more fine-grained + control over your timeouts. + """ + self.num_requests += 1 + + timeout_obj = self._get_timeout(timeout) + timeout_obj.start_connect() + conn.timeout = timeout_obj.connect_timeout + + # Trigger any extra validation we need to do. + try: + self._validate_conn(conn) + except (SocketTimeout, BaseSSLError) as e: + # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout. + self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) + raise + + # conn.request() calls http.client.*.request, not the method in + # urllib3.request. It also calls makefile (recv) on the socket. + try: + if chunked: + conn.request_chunked(method, url, **httplib_request_kw) + else: + conn.request(method, url, **httplib_request_kw) + + # We are swallowing BrokenPipeError (errno.EPIPE) since the server is + # legitimately able to close the connection after sending a valid response. + # With this behaviour, the received response is still readable. + except BrokenPipeError: + # Python 3 + pass + except IOError as e: + # Python 2 and macOS/Linux + # EPIPE and ESHUTDOWN are BrokenPipeError on Python 2, and EPROTOTYPE is needed on macOS + # https://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/ + if e.errno not in { + errno.EPIPE, + errno.ESHUTDOWN, + errno.EPROTOTYPE, + }: + raise + + # Reset the timeout for the recv() on the socket + read_timeout = timeout_obj.read_timeout + + # App Engine doesn't have a sock attr + if getattr(conn, "sock", None): + # In Python 3 socket.py will catch EAGAIN and return None when you + # try and read into the file pointer created by http.client, which + # instead raises a BadStatusLine exception. Instead of catching + # the exception and assuming all BadStatusLine exceptions are read + # timeouts, check for a zero timeout before making the request. + if read_timeout == 0: + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % read_timeout + ) + if read_timeout is Timeout.DEFAULT_TIMEOUT: + conn.sock.settimeout(socket.getdefaulttimeout()) + else: # None or a value + conn.sock.settimeout(read_timeout) + + # Receive the response from the server + try: + try: + # Python 2.7, use buffering of HTTP responses + httplib_response = conn.getresponse(buffering=True) + except TypeError: + # Python 3 + try: + httplib_response = conn.getresponse() + except BaseException as e: + # Remove the TypeError from the exception chain in + # Python 3 (including for exceptions like SystemExit). + # Otherwise it looks like a bug in the code. + six.raise_from(e, None) + except (SocketTimeout, BaseSSLError, SocketError) as e: + self._raise_timeout(err=e, url=url, timeout_value=read_timeout) + raise + + # AppEngine doesn't have a version attr. + http_version = getattr(conn, "_http_vsn_str", "HTTP/?") + log.debug( + '%s://%s:%s "%s %s %s" %s %s', + self.scheme, + self.host, + self.port, + method, + url, + http_version, + httplib_response.status, + httplib_response.length, + ) + + try: + assert_header_parsing(httplib_response.msg) + except (HeaderParsingError, TypeError) as hpe: # Platform-specific: Python 3 + log.warning( + "Failed to parse headers (url=%s): %s", + self._absolute_url(url), + hpe, + exc_info=True, + ) + + return httplib_response + + def _absolute_url(self, path): + return Url(scheme=self.scheme, host=self.host, port=self.port, path=path).url + + def close(self): + """ + Close all pooled connections and disable the pool. + """ + if self.pool is None: + return + # Disable access to the pool + old_pool, self.pool = self.pool, None + + try: + while True: + conn = old_pool.get(block=False) + if conn: + conn.close() + + except queue.Empty: + pass # Done. + + def is_same_host(self, url): + """ + Check if the given ``url`` is a member of the same host as this + connection pool. + """ + if url.startswith("/"): + return True + + # TODO: Add optional support for socket.gethostbyname checking. + scheme, host, port = get_host(url) + if host is not None: + host = _normalize_host(host, scheme=scheme) + + # Use explicit default port for comparison when none is given + if self.port and not port: + port = port_by_scheme.get(scheme) + elif not self.port and port == port_by_scheme.get(scheme): + port = None + + return (scheme, host, port) == (self.scheme, self.host, self.port) + + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=None, + redirect=True, + assert_same_host=True, + timeout=_Default, + pool_timeout=None, + release_conn=None, + chunked=False, + body_pos=None, + **response_kw + ): + """ + Get a connection from the pool and perform an HTTP request. This is the + lowest level call for making a request, so you'll need to specify all + the raw details. + + .. note:: + + More commonly, it's appropriate to use a convenience method provided + by :class:`.RequestMethods`, such as :meth:`request`. + + .. note:: + + `release_conn` will only behave as expected if + `preload_content=False` because we want to make + `preload_content=False` the default behaviour someday soon without + breaking backwards compatibility. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param body: + Data to send in the request body, either :class:`str`, :class:`bytes`, + an iterable of :class:`str`/:class:`bytes`, or a file-like object. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param retries: + Configure the number of retries to allow before raising a + :class:`~urllib3.exceptions.MaxRetryError` exception. + + Pass ``None`` to retry until you receive a response. Pass a + :class:`~urllib3.util.retry.Retry` object for fine-grained control + over different types of retries. + Pass an integer number to retry connection errors that many times, + but no other types of errors. Pass zero to never retry. + + If ``False``, then retries are disabled and any exception is raised + immediately. Also, instead of raising a MaxRetryError on redirects, + the redirect response will be returned. + + :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. + + :param redirect: + If True, automatically handle redirects (status codes 301, 302, + 303, 307, 308). Each redirect counts as a retry. Disabling retries + will disable redirect, too. + + :param assert_same_host: + If ``True``, will make sure that the host of the pool requests is + consistent else will raise HostChangedError. When ``False``, you can + use the pool on an HTTP proxy and request foreign hosts. + + :param timeout: + If specified, overrides the default timeout for this one + request. It may be a float (in seconds) or an instance of + :class:`urllib3.util.Timeout`. + + :param pool_timeout: + If set and the pool is set to block=True, then this method will + block for ``pool_timeout`` seconds and raise EmptyPoolError if no + connection is available within the time period. + + :param release_conn: + If False, then the urlopen call will not release the connection + back into the pool once a response is received (but will release if + you read the entire contents of the response such as when + `preload_content=True`). This is useful if you're not preloading + the response's content immediately. You will need to call + ``r.release_conn()`` on the response ``r`` to return the connection + back into the pool. If None, it takes the value of + ``response_kw.get('preload_content', True)``. + + :param chunked: + If True, urllib3 will send the body using chunked transfer + encoding. Otherwise, urllib3 will send the body using the standard + content-length form. Defaults to False. + + :param int body_pos: + Position to seek to in file-like body in the event of a retry or + redirect. Typically this won't need to be set because urllib3 will + auto-populate the value when needed. + + :param \\**response_kw: + Additional parameters are passed to + :meth:`urllib3.response.HTTPResponse.from_httplib` + """ + + parsed_url = parse_url(url) + destination_scheme = parsed_url.scheme + + if headers is None: + headers = self.headers + + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect, default=self.retries) + + if release_conn is None: + release_conn = response_kw.get("preload_content", True) + + # Check host + if assert_same_host and not self.is_same_host(url): + raise HostChangedError(self, url, retries) + + # Ensure that the URL we're connecting to is properly encoded + if url.startswith("/"): + url = six.ensure_str(_encode_target(url)) + else: + url = six.ensure_str(parsed_url.url) + + conn = None + + # Track whether `conn` needs to be released before + # returning/raising/recursing. Update this variable if necessary, and + # leave `release_conn` constant throughout the function. That way, if + # the function recurses, the original value of `release_conn` will be + # passed down into the recursive call, and its value will be respected. + # + # See issue #651 [1] for details. + # + # [1] + release_this_conn = release_conn + + http_tunnel_required = connection_requires_http_tunnel( + self.proxy, self.proxy_config, destination_scheme + ) + + # Merge the proxy headers. Only done when not using HTTP CONNECT. We + # have to copy the headers dict so we can safely change it without those + # changes being reflected in anyone else's copy. + if not http_tunnel_required: + headers = headers.copy() + headers.update(self.proxy_headers) + + # Must keep the exception bound to a separate variable or else Python 3 + # complains about UnboundLocalError. + err = None + + # Keep track of whether we cleanly exited the except block. This + # ensures we do proper cleanup in finally. + clean_exit = False + + # Rewind body position, if needed. Record current position + # for future rewinds in the event of a redirect/retry. + body_pos = set_file_position(body, body_pos) + + try: + # Request a connection from the queue. + timeout_obj = self._get_timeout(timeout) + conn = self._get_conn(timeout=pool_timeout) + + conn.timeout = timeout_obj.connect_timeout + + is_new_proxy_conn = self.proxy is not None and not getattr( + conn, "sock", None + ) + if is_new_proxy_conn and http_tunnel_required: + self._prepare_proxy(conn) + + # Make the request on the httplib connection object. + httplib_response = self._make_request( + conn, + method, + url, + timeout=timeout_obj, + body=body, + headers=headers, + chunked=chunked, + ) + + # If we're going to release the connection in ``finally:``, then + # the response doesn't need to know about the connection. Otherwise + # it will also try to release it and we'll have a double-release + # mess. + response_conn = conn if not release_conn else None + + # Pass method to Response for length checking + response_kw["request_method"] = method + + # Import httplib's response into our own wrapper object + response = self.ResponseCls.from_httplib( + httplib_response, + pool=self, + connection=response_conn, + retries=retries, + **response_kw + ) + + # Everything went great! + clean_exit = True + + except EmptyPoolError: + # Didn't get a connection from the pool, no need to clean up + clean_exit = True + release_this_conn = False + raise + + except ( + TimeoutError, + HTTPException, + SocketError, + ProtocolError, + BaseSSLError, + SSLError, + CertificateError, + ) as e: + # Discard the connection for these exceptions. It will be + # replaced during the next _get_conn() call. + clean_exit = False + + def _is_ssl_error_message_from_http_proxy(ssl_error): + # We're trying to detect the message 'WRONG_VERSION_NUMBER' but + # SSLErrors are kinda all over the place when it comes to the message, + # so we try to cover our bases here! + message = " ".join(re.split("[^a-z]", str(ssl_error).lower())) + return ( + "wrong version number" in message or "unknown protocol" in message + ) + + # Try to detect a common user error with proxies which is to + # set an HTTP proxy to be HTTPS when it should be 'http://' + # (ie {'http': 'http://proxy', 'https': 'https://proxy'}) + # Instead we add a nice error message and point to a URL. + if ( + isinstance(e, BaseSSLError) + and self.proxy + and _is_ssl_error_message_from_http_proxy(e) + ): + e = ProxyError( + "Your proxy appears to only use HTTP and not HTTPS, " + "try changing your proxy URL to be HTTP. See: " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#https-proxy-error-http-proxy", + SSLError(e), + ) + elif isinstance(e, (BaseSSLError, CertificateError)): + e = SSLError(e) + elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy: + e = ProxyError("Cannot connect to proxy.", e) + elif isinstance(e, (SocketError, HTTPException)): + e = ProtocolError("Connection aborted.", e) + + retries = retries.increment( + method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2] + ) + retries.sleep() + + # Keep track of the error for the retry warning. + err = e + + finally: + if not clean_exit: + # We hit some kind of exception, handled or otherwise. We need + # to throw the connection away unless explicitly told not to. + # Close the connection, set the variable to None, and make sure + # we put the None back in the pool to avoid leaking it. + conn = conn and conn.close() + release_this_conn = True + + if release_this_conn: + # Put the connection back to be reused. If the connection is + # expired then it will be None, which will get replaced with a + # fresh connection during _get_conn. + self._put_conn(conn) + + if not conn: + # Try again + log.warning( + "Retrying (%r) after connection broken by '%r': %s", retries, err, url + ) + return self.urlopen( + method, + url, + body, + headers, + retries, + redirect, + assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + **response_kw + ) + + # Handle redirect? + redirect_location = redirect and response.get_redirect_location() + if redirect_location: + if response.status == 303: + method = "GET" + + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_redirect: + response.drain_conn() + raise + return response + + response.drain_conn() + retries.sleep_for_retry(response) + log.debug("Redirecting %s -> %s", url, redirect_location) + return self.urlopen( + method, + redirect_location, + body, + headers, + retries=retries, + redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + **response_kw + ) + + # Check if we should retry the HTTP response. + has_retry_after = bool(response.getheader("Retry-After")) + if retries.is_retry(method, response.status, has_retry_after): + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_status: + response.drain_conn() + raise + return response + + response.drain_conn() + retries.sleep(response) + log.debug("Retry: %s", url) + return self.urlopen( + method, + url, + body, + headers, + retries=retries, + redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + **response_kw + ) + + return response + + +class HTTPSConnectionPool(HTTPConnectionPool): + """ + Same as :class:`.HTTPConnectionPool`, but HTTPS. + + :class:`.HTTPSConnection` uses one of ``assert_fingerprint``, + ``assert_hostname`` and ``host`` in this order to verify connections. + If ``assert_hostname`` is False, no verification is done. + + The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``, + ``ca_cert_dir``, ``ssl_version``, ``key_password`` are only used if :mod:`ssl` + is available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade + the connection socket into an SSL socket. + """ + + scheme = "https" + ConnectionCls = HTTPSConnection + + def __init__( + self, + host, + port=None, + strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, + maxsize=1, + block=False, + headers=None, + retries=None, + _proxy=None, + _proxy_headers=None, + key_file=None, + cert_file=None, + cert_reqs=None, + key_password=None, + ca_certs=None, + ssl_version=None, + assert_hostname=None, + assert_fingerprint=None, + ca_cert_dir=None, + **conn_kw + ): + + HTTPConnectionPool.__init__( + self, + host, + port, + strict, + timeout, + maxsize, + block, + headers, + retries, + _proxy, + _proxy_headers, + **conn_kw + ) + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.key_password = key_password + self.ca_certs = ca_certs + self.ca_cert_dir = ca_cert_dir + self.ssl_version = ssl_version + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + + def _prepare_conn(self, conn): + """ + Prepare the ``connection`` for :meth:`urllib3.util.ssl_wrap_socket` + and establish the tunnel if proxy is used. + """ + + if isinstance(conn, VerifiedHTTPSConnection): + conn.set_cert( + key_file=self.key_file, + key_password=self.key_password, + cert_file=self.cert_file, + cert_reqs=self.cert_reqs, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + assert_hostname=self.assert_hostname, + assert_fingerprint=self.assert_fingerprint, + ) + conn.ssl_version = self.ssl_version + return conn + + def _prepare_proxy(self, conn): + """ + Establishes a tunnel connection through HTTP CONNECT. + + Tunnel connection is established early because otherwise httplib would + improperly set Host: header to proxy's IP:port. + """ + + conn.set_tunnel(self._proxy_host, self.port, self.proxy_headers) + + if self.proxy.scheme == "https": + conn.tls_in_tls_required = True + + conn.connect() + + def _new_conn(self): + """ + Return a fresh :class:`http.client.HTTPSConnection`. + """ + self.num_connections += 1 + log.debug( + "Starting new HTTPS connection (%d): %s:%s", + self.num_connections, + self.host, + self.port or "443", + ) + + if not self.ConnectionCls or self.ConnectionCls is DummyConnection: + raise SSLError( + "Can't connect to HTTPS URL because the SSL module is not available." + ) + + actual_host = self.host + actual_port = self.port + if self.proxy is not None: + actual_host = self.proxy.host + actual_port = self.proxy.port + + conn = self.ConnectionCls( + host=actual_host, + port=actual_port, + timeout=self.timeout.connect_timeout, + strict=self.strict, + cert_file=self.cert_file, + key_file=self.key_file, + key_password=self.key_password, + **self.conn_kw + ) + + return self._prepare_conn(conn) + + def _validate_conn(self, conn): + """ + Called right before a request is made, after the socket is created. + """ + super(HTTPSConnectionPool, self)._validate_conn(conn) + + # Force connect early to allow us to validate the connection. + if not getattr(conn, "sock", None): # AppEngine might not have `.sock` + conn.connect() + + if not conn.is_verified: + warnings.warn( + ( + "Unverified HTTPS request is being made to host '%s'. " + "Adding certificate verification is strongly advised. See: " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings" % conn.host + ), + InsecureRequestWarning, + ) + + if getattr(conn, "proxy_is_verified", None) is False: + warnings.warn( + ( + "Unverified HTTPS connection done to an HTTPS proxy. " + "Adding certificate verification is strongly advised. See: " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings" + ), + InsecureRequestWarning, + ) + + +def connection_from_url(url, **kw): + """ + Given a url, return an :class:`.ConnectionPool` instance of its host. + + This is a shortcut for not having to parse out the scheme, host, and port + of the url before creating an :class:`.ConnectionPool` instance. + + :param url: + Absolute URL string that must include the scheme. Port is optional. + + :param \\**kw: + Passes additional parameters to the constructor of the appropriate + :class:`.ConnectionPool`. Useful for specifying things like + timeout, maxsize, headers, etc. + + Example:: + + >>> conn = connection_from_url('http://google.com/') + >>> r = conn.request('GET', '/') + """ + scheme, host, port = get_host(url) + port = port or port_by_scheme.get(scheme, 80) + if scheme == "https": + return HTTPSConnectionPool(host, port=port, **kw) + else: + return HTTPConnectionPool(host, port=port, **kw) + + +def _normalize_host(host, scheme): + """ + Normalize hosts for comparisons and use with sockets. + """ + + host = normalize_host(host, scheme) + + # httplib doesn't like it when we include brackets in IPv6 addresses + # Specifically, if we include brackets but also pass the port then + # httplib crazily doubles up the square brackets on the Host header. + # Instead, we need to make sure we never pass ``None`` as the port. + # However, for backward compatibility reasons we can't actually + # *assert* that. See http://bugs.python.org/issue28539 + if host.startswith("[") and host.endswith("]"): + host = host[1:-1] + return host diff --git a/openpype/vendor/python/python_2/urllib3/contrib/__init__.py b/openpype/vendor/python/python_2/urllib3/contrib/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/vendor/python/python_2/urllib3/contrib/_appengine_environ.py b/openpype/vendor/python/python_2/urllib3/contrib/_appengine_environ.py new file mode 100644 index 0000000000..8765b907d7 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/contrib/_appengine_environ.py @@ -0,0 +1,36 @@ +""" +This module provides means to detect the App Engine environment. +""" + +import os + + +def is_appengine(): + return is_local_appengine() or is_prod_appengine() + + +def is_appengine_sandbox(): + """Reports if the app is running in the first generation sandbox. + + The second generation runtimes are technically still in a sandbox, but it + is much less restrictive, so generally you shouldn't need to check for it. + see https://cloud.google.com/appengine/docs/standard/runtimes + """ + return is_appengine() and os.environ["APPENGINE_RUNTIME"] == "python27" + + +def is_local_appengine(): + return "APPENGINE_RUNTIME" in os.environ and os.environ.get( + "SERVER_SOFTWARE", "" + ).startswith("Development/") + + +def is_prod_appengine(): + return "APPENGINE_RUNTIME" in os.environ and os.environ.get( + "SERVER_SOFTWARE", "" + ).startswith("Google App Engine/") + + +def is_prod_appengine_mvms(): + """Deprecated.""" + return False diff --git a/openpype/vendor/python/python_2/urllib3/contrib/_securetransport/__init__.py b/openpype/vendor/python/python_2/urllib3/contrib/_securetransport/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/vendor/python/python_2/urllib3/contrib/_securetransport/bindings.py b/openpype/vendor/python/python_2/urllib3/contrib/_securetransport/bindings.py new file mode 100644 index 0000000000..264d564dbd --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/contrib/_securetransport/bindings.py @@ -0,0 +1,519 @@ +""" +This module uses ctypes to bind a whole bunch of functions and constants from +SecureTransport. The goal here is to provide the low-level API to +SecureTransport. These are essentially the C-level functions and constants, and +they're pretty gross to work with. + +This code is a bastardised version of the code found in Will Bond's oscrypto +library. An enormous debt is owed to him for blazing this trail for us. For +that reason, this code should be considered to be covered both by urllib3's +license and by oscrypto's: + + Copyright (c) 2015-2016 Will Bond + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +""" +from __future__ import absolute_import + +import platform +from ctypes import ( + CDLL, + CFUNCTYPE, + POINTER, + c_bool, + c_byte, + c_char_p, + c_int32, + c_long, + c_size_t, + c_uint32, + c_ulong, + c_void_p, +) +from ctypes.util import find_library + +from ...packages.six import raise_from + +if platform.system() != "Darwin": + raise ImportError("Only macOS is supported") + +version = platform.mac_ver()[0] +version_info = tuple(map(int, version.split("."))) +if version_info < (10, 8): + raise OSError( + "Only OS X 10.8 and newer are supported, not %s.%s" + % (version_info[0], version_info[1]) + ) + + +def load_cdll(name, macos10_16_path): + """Loads a CDLL by name, falling back to known path on 10.16+""" + try: + # Big Sur is technically 11 but we use 10.16 due to the Big Sur + # beta being labeled as 10.16. + if version_info >= (10, 16): + path = macos10_16_path + else: + path = find_library(name) + if not path: + raise OSError # Caught and reraised as 'ImportError' + return CDLL(path, use_errno=True) + except OSError: + raise_from(ImportError("The library %s failed to load" % name), None) + + +Security = load_cdll( + "Security", "/System/Library/Frameworks/Security.framework/Security" +) +CoreFoundation = load_cdll( + "CoreFoundation", + "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", +) + + +Boolean = c_bool +CFIndex = c_long +CFStringEncoding = c_uint32 +CFData = c_void_p +CFString = c_void_p +CFArray = c_void_p +CFMutableArray = c_void_p +CFDictionary = c_void_p +CFError = c_void_p +CFType = c_void_p +CFTypeID = c_ulong + +CFTypeRef = POINTER(CFType) +CFAllocatorRef = c_void_p + +OSStatus = c_int32 + +CFDataRef = POINTER(CFData) +CFStringRef = POINTER(CFString) +CFArrayRef = POINTER(CFArray) +CFMutableArrayRef = POINTER(CFMutableArray) +CFDictionaryRef = POINTER(CFDictionary) +CFArrayCallBacks = c_void_p +CFDictionaryKeyCallBacks = c_void_p +CFDictionaryValueCallBacks = c_void_p + +SecCertificateRef = POINTER(c_void_p) +SecExternalFormat = c_uint32 +SecExternalItemType = c_uint32 +SecIdentityRef = POINTER(c_void_p) +SecItemImportExportFlags = c_uint32 +SecItemImportExportKeyParameters = c_void_p +SecKeychainRef = POINTER(c_void_p) +SSLProtocol = c_uint32 +SSLCipherSuite = c_uint32 +SSLContextRef = POINTER(c_void_p) +SecTrustRef = POINTER(c_void_p) +SSLConnectionRef = c_uint32 +SecTrustResultType = c_uint32 +SecTrustOptionFlags = c_uint32 +SSLProtocolSide = c_uint32 +SSLConnectionType = c_uint32 +SSLSessionOption = c_uint32 + + +try: + Security.SecItemImport.argtypes = [ + CFDataRef, + CFStringRef, + POINTER(SecExternalFormat), + POINTER(SecExternalItemType), + SecItemImportExportFlags, + POINTER(SecItemImportExportKeyParameters), + SecKeychainRef, + POINTER(CFArrayRef), + ] + Security.SecItemImport.restype = OSStatus + + Security.SecCertificateGetTypeID.argtypes = [] + Security.SecCertificateGetTypeID.restype = CFTypeID + + Security.SecIdentityGetTypeID.argtypes = [] + Security.SecIdentityGetTypeID.restype = CFTypeID + + Security.SecKeyGetTypeID.argtypes = [] + Security.SecKeyGetTypeID.restype = CFTypeID + + Security.SecCertificateCreateWithData.argtypes = [CFAllocatorRef, CFDataRef] + Security.SecCertificateCreateWithData.restype = SecCertificateRef + + Security.SecCertificateCopyData.argtypes = [SecCertificateRef] + Security.SecCertificateCopyData.restype = CFDataRef + + Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p] + Security.SecCopyErrorMessageString.restype = CFStringRef + + Security.SecIdentityCreateWithCertificate.argtypes = [ + CFTypeRef, + SecCertificateRef, + POINTER(SecIdentityRef), + ] + Security.SecIdentityCreateWithCertificate.restype = OSStatus + + Security.SecKeychainCreate.argtypes = [ + c_char_p, + c_uint32, + c_void_p, + Boolean, + c_void_p, + POINTER(SecKeychainRef), + ] + Security.SecKeychainCreate.restype = OSStatus + + Security.SecKeychainDelete.argtypes = [SecKeychainRef] + Security.SecKeychainDelete.restype = OSStatus + + Security.SecPKCS12Import.argtypes = [ + CFDataRef, + CFDictionaryRef, + POINTER(CFArrayRef), + ] + Security.SecPKCS12Import.restype = OSStatus + + SSLReadFunc = CFUNCTYPE(OSStatus, SSLConnectionRef, c_void_p, POINTER(c_size_t)) + SSLWriteFunc = CFUNCTYPE( + OSStatus, SSLConnectionRef, POINTER(c_byte), POINTER(c_size_t) + ) + + Security.SSLSetIOFuncs.argtypes = [SSLContextRef, SSLReadFunc, SSLWriteFunc] + Security.SSLSetIOFuncs.restype = OSStatus + + Security.SSLSetPeerID.argtypes = [SSLContextRef, c_char_p, c_size_t] + Security.SSLSetPeerID.restype = OSStatus + + Security.SSLSetCertificate.argtypes = [SSLContextRef, CFArrayRef] + Security.SSLSetCertificate.restype = OSStatus + + Security.SSLSetCertificateAuthorities.argtypes = [SSLContextRef, CFTypeRef, Boolean] + Security.SSLSetCertificateAuthorities.restype = OSStatus + + Security.SSLSetConnection.argtypes = [SSLContextRef, SSLConnectionRef] + Security.SSLSetConnection.restype = OSStatus + + Security.SSLSetPeerDomainName.argtypes = [SSLContextRef, c_char_p, c_size_t] + Security.SSLSetPeerDomainName.restype = OSStatus + + Security.SSLHandshake.argtypes = [SSLContextRef] + Security.SSLHandshake.restype = OSStatus + + Security.SSLRead.argtypes = [SSLContextRef, c_char_p, c_size_t, POINTER(c_size_t)] + Security.SSLRead.restype = OSStatus + + Security.SSLWrite.argtypes = [SSLContextRef, c_char_p, c_size_t, POINTER(c_size_t)] + Security.SSLWrite.restype = OSStatus + + Security.SSLClose.argtypes = [SSLContextRef] + Security.SSLClose.restype = OSStatus + + Security.SSLGetNumberSupportedCiphers.argtypes = [SSLContextRef, POINTER(c_size_t)] + Security.SSLGetNumberSupportedCiphers.restype = OSStatus + + Security.SSLGetSupportedCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + POINTER(c_size_t), + ] + Security.SSLGetSupportedCiphers.restype = OSStatus + + Security.SSLSetEnabledCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + c_size_t, + ] + Security.SSLSetEnabledCiphers.restype = OSStatus + + Security.SSLGetNumberEnabledCiphers.argtype = [SSLContextRef, POINTER(c_size_t)] + Security.SSLGetNumberEnabledCiphers.restype = OSStatus + + Security.SSLGetEnabledCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + POINTER(c_size_t), + ] + Security.SSLGetEnabledCiphers.restype = OSStatus + + Security.SSLGetNegotiatedCipher.argtypes = [SSLContextRef, POINTER(SSLCipherSuite)] + Security.SSLGetNegotiatedCipher.restype = OSStatus + + Security.SSLGetNegotiatedProtocolVersion.argtypes = [ + SSLContextRef, + POINTER(SSLProtocol), + ] + Security.SSLGetNegotiatedProtocolVersion.restype = OSStatus + + Security.SSLCopyPeerTrust.argtypes = [SSLContextRef, POINTER(SecTrustRef)] + Security.SSLCopyPeerTrust.restype = OSStatus + + Security.SecTrustSetAnchorCertificates.argtypes = [SecTrustRef, CFArrayRef] + Security.SecTrustSetAnchorCertificates.restype = OSStatus + + Security.SecTrustSetAnchorCertificatesOnly.argstypes = [SecTrustRef, Boolean] + Security.SecTrustSetAnchorCertificatesOnly.restype = OSStatus + + Security.SecTrustEvaluate.argtypes = [SecTrustRef, POINTER(SecTrustResultType)] + Security.SecTrustEvaluate.restype = OSStatus + + Security.SecTrustGetCertificateCount.argtypes = [SecTrustRef] + Security.SecTrustGetCertificateCount.restype = CFIndex + + Security.SecTrustGetCertificateAtIndex.argtypes = [SecTrustRef, CFIndex] + Security.SecTrustGetCertificateAtIndex.restype = SecCertificateRef + + Security.SSLCreateContext.argtypes = [ + CFAllocatorRef, + SSLProtocolSide, + SSLConnectionType, + ] + Security.SSLCreateContext.restype = SSLContextRef + + Security.SSLSetSessionOption.argtypes = [SSLContextRef, SSLSessionOption, Boolean] + Security.SSLSetSessionOption.restype = OSStatus + + Security.SSLSetProtocolVersionMin.argtypes = [SSLContextRef, SSLProtocol] + Security.SSLSetProtocolVersionMin.restype = OSStatus + + Security.SSLSetProtocolVersionMax.argtypes = [SSLContextRef, SSLProtocol] + Security.SSLSetProtocolVersionMax.restype = OSStatus + + try: + Security.SSLSetALPNProtocols.argtypes = [SSLContextRef, CFArrayRef] + Security.SSLSetALPNProtocols.restype = OSStatus + except AttributeError: + # Supported only in 10.12+ + pass + + Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p] + Security.SecCopyErrorMessageString.restype = CFStringRef + + Security.SSLReadFunc = SSLReadFunc + Security.SSLWriteFunc = SSLWriteFunc + Security.SSLContextRef = SSLContextRef + Security.SSLProtocol = SSLProtocol + Security.SSLCipherSuite = SSLCipherSuite + Security.SecIdentityRef = SecIdentityRef + Security.SecKeychainRef = SecKeychainRef + Security.SecTrustRef = SecTrustRef + Security.SecTrustResultType = SecTrustResultType + Security.SecExternalFormat = SecExternalFormat + Security.OSStatus = OSStatus + + Security.kSecImportExportPassphrase = CFStringRef.in_dll( + Security, "kSecImportExportPassphrase" + ) + Security.kSecImportItemIdentity = CFStringRef.in_dll( + Security, "kSecImportItemIdentity" + ) + + # CoreFoundation time! + CoreFoundation.CFRetain.argtypes = [CFTypeRef] + CoreFoundation.CFRetain.restype = CFTypeRef + + CoreFoundation.CFRelease.argtypes = [CFTypeRef] + CoreFoundation.CFRelease.restype = None + + CoreFoundation.CFGetTypeID.argtypes = [CFTypeRef] + CoreFoundation.CFGetTypeID.restype = CFTypeID + + CoreFoundation.CFStringCreateWithCString.argtypes = [ + CFAllocatorRef, + c_char_p, + CFStringEncoding, + ] + CoreFoundation.CFStringCreateWithCString.restype = CFStringRef + + CoreFoundation.CFStringGetCStringPtr.argtypes = [CFStringRef, CFStringEncoding] + CoreFoundation.CFStringGetCStringPtr.restype = c_char_p + + CoreFoundation.CFStringGetCString.argtypes = [ + CFStringRef, + c_char_p, + CFIndex, + CFStringEncoding, + ] + CoreFoundation.CFStringGetCString.restype = c_bool + + CoreFoundation.CFDataCreate.argtypes = [CFAllocatorRef, c_char_p, CFIndex] + CoreFoundation.CFDataCreate.restype = CFDataRef + + CoreFoundation.CFDataGetLength.argtypes = [CFDataRef] + CoreFoundation.CFDataGetLength.restype = CFIndex + + CoreFoundation.CFDataGetBytePtr.argtypes = [CFDataRef] + CoreFoundation.CFDataGetBytePtr.restype = c_void_p + + CoreFoundation.CFDictionaryCreate.argtypes = [ + CFAllocatorRef, + POINTER(CFTypeRef), + POINTER(CFTypeRef), + CFIndex, + CFDictionaryKeyCallBacks, + CFDictionaryValueCallBacks, + ] + CoreFoundation.CFDictionaryCreate.restype = CFDictionaryRef + + CoreFoundation.CFDictionaryGetValue.argtypes = [CFDictionaryRef, CFTypeRef] + CoreFoundation.CFDictionaryGetValue.restype = CFTypeRef + + CoreFoundation.CFArrayCreate.argtypes = [ + CFAllocatorRef, + POINTER(CFTypeRef), + CFIndex, + CFArrayCallBacks, + ] + CoreFoundation.CFArrayCreate.restype = CFArrayRef + + CoreFoundation.CFArrayCreateMutable.argtypes = [ + CFAllocatorRef, + CFIndex, + CFArrayCallBacks, + ] + CoreFoundation.CFArrayCreateMutable.restype = CFMutableArrayRef + + CoreFoundation.CFArrayAppendValue.argtypes = [CFMutableArrayRef, c_void_p] + CoreFoundation.CFArrayAppendValue.restype = None + + CoreFoundation.CFArrayGetCount.argtypes = [CFArrayRef] + CoreFoundation.CFArrayGetCount.restype = CFIndex + + CoreFoundation.CFArrayGetValueAtIndex.argtypes = [CFArrayRef, CFIndex] + CoreFoundation.CFArrayGetValueAtIndex.restype = c_void_p + + CoreFoundation.kCFAllocatorDefault = CFAllocatorRef.in_dll( + CoreFoundation, "kCFAllocatorDefault" + ) + CoreFoundation.kCFTypeArrayCallBacks = c_void_p.in_dll( + CoreFoundation, "kCFTypeArrayCallBacks" + ) + CoreFoundation.kCFTypeDictionaryKeyCallBacks = c_void_p.in_dll( + CoreFoundation, "kCFTypeDictionaryKeyCallBacks" + ) + CoreFoundation.kCFTypeDictionaryValueCallBacks = c_void_p.in_dll( + CoreFoundation, "kCFTypeDictionaryValueCallBacks" + ) + + CoreFoundation.CFTypeRef = CFTypeRef + CoreFoundation.CFArrayRef = CFArrayRef + CoreFoundation.CFStringRef = CFStringRef + CoreFoundation.CFDictionaryRef = CFDictionaryRef + +except (AttributeError): + raise ImportError("Error initializing ctypes") + + +class CFConst(object): + """ + A class object that acts as essentially a namespace for CoreFoundation + constants. + """ + + kCFStringEncodingUTF8 = CFStringEncoding(0x08000100) + + +class SecurityConst(object): + """ + A class object that acts as essentially a namespace for Security constants. + """ + + kSSLSessionOptionBreakOnServerAuth = 0 + + kSSLProtocol2 = 1 + kSSLProtocol3 = 2 + kTLSProtocol1 = 4 + kTLSProtocol11 = 7 + kTLSProtocol12 = 8 + # SecureTransport does not support TLS 1.3 even if there's a constant for it + kTLSProtocol13 = 10 + kTLSProtocolMaxSupported = 999 + + kSSLClientSide = 1 + kSSLStreamType = 0 + + kSecFormatPEMSequence = 10 + + kSecTrustResultInvalid = 0 + kSecTrustResultProceed = 1 + # This gap is present on purpose: this was kSecTrustResultConfirm, which + # is deprecated. + kSecTrustResultDeny = 3 + kSecTrustResultUnspecified = 4 + kSecTrustResultRecoverableTrustFailure = 5 + kSecTrustResultFatalTrustFailure = 6 + kSecTrustResultOtherError = 7 + + errSSLProtocol = -9800 + errSSLWouldBlock = -9803 + errSSLClosedGraceful = -9805 + errSSLClosedNoNotify = -9816 + errSSLClosedAbort = -9806 + + errSSLXCertChainInvalid = -9807 + errSSLCrypto = -9809 + errSSLInternal = -9810 + errSSLCertExpired = -9814 + errSSLCertNotYetValid = -9815 + errSSLUnknownRootCert = -9812 + errSSLNoRootCert = -9813 + errSSLHostNameMismatch = -9843 + errSSLPeerHandshakeFail = -9824 + errSSLPeerUserCancelled = -9839 + errSSLWeakPeerEphemeralDHKey = -9850 + errSSLServerAuthCompleted = -9841 + errSSLRecordOverflow = -9847 + + errSecVerifyFailed = -67808 + errSecNoTrustSettings = -25263 + errSecItemNotFound = -25300 + errSecInvalidTrustSettings = -25262 + + # Cipher suites. We only pick the ones our default cipher string allows. + # Source: https://developer.apple.com/documentation/security/1550981-ssl_cipher_suite_values + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030 + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA9 + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA8 + TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024 + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028 + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014 + TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B + TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023 + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027 + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009 + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013 + TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067 + TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 + TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D + TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C + TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D + TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C + TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035 + TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F + TLS_AES_128_GCM_SHA256 = 0x1301 + TLS_AES_256_GCM_SHA384 = 0x1302 + TLS_AES_128_CCM_8_SHA256 = 0x1305 + TLS_AES_128_CCM_SHA256 = 0x1304 diff --git a/openpype/vendor/python/python_2/urllib3/contrib/_securetransport/low_level.py b/openpype/vendor/python/python_2/urllib3/contrib/_securetransport/low_level.py new file mode 100644 index 0000000000..fa0b245d27 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/contrib/_securetransport/low_level.py @@ -0,0 +1,397 @@ +""" +Low-level helpers for the SecureTransport bindings. + +These are Python functions that are not directly related to the high-level APIs +but are necessary to get them to work. They include a whole bunch of low-level +CoreFoundation messing about and memory management. The concerns in this module +are almost entirely about trying to avoid memory leaks and providing +appropriate and useful assistance to the higher-level code. +""" +import base64 +import ctypes +import itertools +import os +import re +import ssl +import struct +import tempfile + +from .bindings import CFConst, CoreFoundation, Security + +# This regular expression is used to grab PEM data out of a PEM bundle. +_PEM_CERTS_RE = re.compile( + b"-----BEGIN CERTIFICATE-----\n(.*?)\n-----END CERTIFICATE-----", re.DOTALL +) + + +def _cf_data_from_bytes(bytestring): + """ + Given a bytestring, create a CFData object from it. This CFData object must + be CFReleased by the caller. + """ + return CoreFoundation.CFDataCreate( + CoreFoundation.kCFAllocatorDefault, bytestring, len(bytestring) + ) + + +def _cf_dictionary_from_tuples(tuples): + """ + Given a list of Python tuples, create an associated CFDictionary. + """ + dictionary_size = len(tuples) + + # We need to get the dictionary keys and values out in the same order. + keys = (t[0] for t in tuples) + values = (t[1] for t in tuples) + cf_keys = (CoreFoundation.CFTypeRef * dictionary_size)(*keys) + cf_values = (CoreFoundation.CFTypeRef * dictionary_size)(*values) + + return CoreFoundation.CFDictionaryCreate( + CoreFoundation.kCFAllocatorDefault, + cf_keys, + cf_values, + dictionary_size, + CoreFoundation.kCFTypeDictionaryKeyCallBacks, + CoreFoundation.kCFTypeDictionaryValueCallBacks, + ) + + +def _cfstr(py_bstr): + """ + Given a Python binary data, create a CFString. + The string must be CFReleased by the caller. + """ + c_str = ctypes.c_char_p(py_bstr) + cf_str = CoreFoundation.CFStringCreateWithCString( + CoreFoundation.kCFAllocatorDefault, + c_str, + CFConst.kCFStringEncodingUTF8, + ) + return cf_str + + +def _create_cfstring_array(lst): + """ + Given a list of Python binary data, create an associated CFMutableArray. + The array must be CFReleased by the caller. + + Raises an ssl.SSLError on failure. + """ + cf_arr = None + try: + cf_arr = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + if not cf_arr: + raise MemoryError("Unable to allocate memory!") + for item in lst: + cf_str = _cfstr(item) + if not cf_str: + raise MemoryError("Unable to allocate memory!") + try: + CoreFoundation.CFArrayAppendValue(cf_arr, cf_str) + finally: + CoreFoundation.CFRelease(cf_str) + except BaseException as e: + if cf_arr: + CoreFoundation.CFRelease(cf_arr) + raise ssl.SSLError("Unable to allocate array: %s" % (e,)) + return cf_arr + + +def _cf_string_to_unicode(value): + """ + Creates a Unicode string from a CFString object. Used entirely for error + reporting. + + Yes, it annoys me quite a lot that this function is this complex. + """ + value_as_void_p = ctypes.cast(value, ctypes.POINTER(ctypes.c_void_p)) + + string = CoreFoundation.CFStringGetCStringPtr( + value_as_void_p, CFConst.kCFStringEncodingUTF8 + ) + if string is None: + buffer = ctypes.create_string_buffer(1024) + result = CoreFoundation.CFStringGetCString( + value_as_void_p, buffer, 1024, CFConst.kCFStringEncodingUTF8 + ) + if not result: + raise OSError("Error copying C string from CFStringRef") + string = buffer.value + if string is not None: + string = string.decode("utf-8") + return string + + +def _assert_no_error(error, exception_class=None): + """ + Checks the return code and throws an exception if there is an error to + report + """ + if error == 0: + return + + cf_error_string = Security.SecCopyErrorMessageString(error, None) + output = _cf_string_to_unicode(cf_error_string) + CoreFoundation.CFRelease(cf_error_string) + + if output is None or output == u"": + output = u"OSStatus %s" % error + + if exception_class is None: + exception_class = ssl.SSLError + + raise exception_class(output) + + +def _cert_array_from_pem(pem_bundle): + """ + Given a bundle of certs in PEM format, turns them into a CFArray of certs + that can be used to validate a cert chain. + """ + # Normalize the PEM bundle's line endings. + pem_bundle = pem_bundle.replace(b"\r\n", b"\n") + + der_certs = [ + base64.b64decode(match.group(1)) for match in _PEM_CERTS_RE.finditer(pem_bundle) + ] + if not der_certs: + raise ssl.SSLError("No root certificates specified") + + cert_array = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + if not cert_array: + raise ssl.SSLError("Unable to allocate memory!") + + try: + for der_bytes in der_certs: + certdata = _cf_data_from_bytes(der_bytes) + if not certdata: + raise ssl.SSLError("Unable to allocate memory!") + cert = Security.SecCertificateCreateWithData( + CoreFoundation.kCFAllocatorDefault, certdata + ) + CoreFoundation.CFRelease(certdata) + if not cert: + raise ssl.SSLError("Unable to build cert object!") + + CoreFoundation.CFArrayAppendValue(cert_array, cert) + CoreFoundation.CFRelease(cert) + except Exception: + # We need to free the array before the exception bubbles further. + # We only want to do that if an error occurs: otherwise, the caller + # should free. + CoreFoundation.CFRelease(cert_array) + raise + + return cert_array + + +def _is_cert(item): + """ + Returns True if a given CFTypeRef is a certificate. + """ + expected = Security.SecCertificateGetTypeID() + return CoreFoundation.CFGetTypeID(item) == expected + + +def _is_identity(item): + """ + Returns True if a given CFTypeRef is an identity. + """ + expected = Security.SecIdentityGetTypeID() + return CoreFoundation.CFGetTypeID(item) == expected + + +def _temporary_keychain(): + """ + This function creates a temporary Mac keychain that we can use to work with + credentials. This keychain uses a one-time password and a temporary file to + store the data. We expect to have one keychain per socket. The returned + SecKeychainRef must be freed by the caller, including calling + SecKeychainDelete. + + Returns a tuple of the SecKeychainRef and the path to the temporary + directory that contains it. + """ + # Unfortunately, SecKeychainCreate requires a path to a keychain. This + # means we cannot use mkstemp to use a generic temporary file. Instead, + # we're going to create a temporary directory and a filename to use there. + # This filename will be 8 random bytes expanded into base64. We also need + # some random bytes to password-protect the keychain we're creating, so we + # ask for 40 random bytes. + random_bytes = os.urandom(40) + filename = base64.b16encode(random_bytes[:8]).decode("utf-8") + password = base64.b16encode(random_bytes[8:]) # Must be valid UTF-8 + tempdirectory = tempfile.mkdtemp() + + keychain_path = os.path.join(tempdirectory, filename).encode("utf-8") + + # We now want to create the keychain itself. + keychain = Security.SecKeychainRef() + status = Security.SecKeychainCreate( + keychain_path, len(password), password, False, None, ctypes.byref(keychain) + ) + _assert_no_error(status) + + # Having created the keychain, we want to pass it off to the caller. + return keychain, tempdirectory + + +def _load_items_from_file(keychain, path): + """ + Given a single file, loads all the trust objects from it into arrays and + the keychain. + Returns a tuple of lists: the first list is a list of identities, the + second a list of certs. + """ + certificates = [] + identities = [] + result_array = None + + with open(path, "rb") as f: + raw_filedata = f.read() + + try: + filedata = CoreFoundation.CFDataCreate( + CoreFoundation.kCFAllocatorDefault, raw_filedata, len(raw_filedata) + ) + result_array = CoreFoundation.CFArrayRef() + result = Security.SecItemImport( + filedata, # cert data + None, # Filename, leaving it out for now + None, # What the type of the file is, we don't care + None, # what's in the file, we don't care + 0, # import flags + None, # key params, can include passphrase in the future + keychain, # The keychain to insert into + ctypes.byref(result_array), # Results + ) + _assert_no_error(result) + + # A CFArray is not very useful to us as an intermediary + # representation, so we are going to extract the objects we want + # and then free the array. We don't need to keep hold of keys: the + # keychain already has them! + result_count = CoreFoundation.CFArrayGetCount(result_array) + for index in range(result_count): + item = CoreFoundation.CFArrayGetValueAtIndex(result_array, index) + item = ctypes.cast(item, CoreFoundation.CFTypeRef) + + if _is_cert(item): + CoreFoundation.CFRetain(item) + certificates.append(item) + elif _is_identity(item): + CoreFoundation.CFRetain(item) + identities.append(item) + finally: + if result_array: + CoreFoundation.CFRelease(result_array) + + CoreFoundation.CFRelease(filedata) + + return (identities, certificates) + + +def _load_client_cert_chain(keychain, *paths): + """ + Load certificates and maybe keys from a number of files. Has the end goal + of returning a CFArray containing one SecIdentityRef, and then zero or more + SecCertificateRef objects, suitable for use as a client certificate trust + chain. + """ + # Ok, the strategy. + # + # This relies on knowing that macOS will not give you a SecIdentityRef + # unless you have imported a key into a keychain. This is a somewhat + # artificial limitation of macOS (for example, it doesn't necessarily + # affect iOS), but there is nothing inside Security.framework that lets you + # get a SecIdentityRef without having a key in a keychain. + # + # So the policy here is we take all the files and iterate them in order. + # Each one will use SecItemImport to have one or more objects loaded from + # it. We will also point at a keychain that macOS can use to work with the + # private key. + # + # Once we have all the objects, we'll check what we actually have. If we + # already have a SecIdentityRef in hand, fab: we'll use that. Otherwise, + # we'll take the first certificate (which we assume to be our leaf) and + # ask the keychain to give us a SecIdentityRef with that cert's associated + # key. + # + # We'll then return a CFArray containing the trust chain: one + # SecIdentityRef and then zero-or-more SecCertificateRef objects. The + # responsibility for freeing this CFArray will be with the caller. This + # CFArray must remain alive for the entire connection, so in practice it + # will be stored with a single SSLSocket, along with the reference to the + # keychain. + certificates = [] + identities = [] + + # Filter out bad paths. + paths = (path for path in paths if path) + + try: + for file_path in paths: + new_identities, new_certs = _load_items_from_file(keychain, file_path) + identities.extend(new_identities) + certificates.extend(new_certs) + + # Ok, we have everything. The question is: do we have an identity? If + # not, we want to grab one from the first cert we have. + if not identities: + new_identity = Security.SecIdentityRef() + status = Security.SecIdentityCreateWithCertificate( + keychain, certificates[0], ctypes.byref(new_identity) + ) + _assert_no_error(status) + identities.append(new_identity) + + # We now want to release the original certificate, as we no longer + # need it. + CoreFoundation.CFRelease(certificates.pop(0)) + + # We now need to build a new CFArray that holds the trust chain. + trust_chain = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + for item in itertools.chain(identities, certificates): + # ArrayAppendValue does a CFRetain on the item. That's fine, + # because the finally block will release our other refs to them. + CoreFoundation.CFArrayAppendValue(trust_chain, item) + + return trust_chain + finally: + for obj in itertools.chain(identities, certificates): + CoreFoundation.CFRelease(obj) + + +TLS_PROTOCOL_VERSIONS = { + "SSLv2": (0, 2), + "SSLv3": (3, 0), + "TLSv1": (3, 1), + "TLSv1.1": (3, 2), + "TLSv1.2": (3, 3), +} + + +def _build_tls_unknown_ca_alert(version): + """ + Builds a TLS alert record for an unknown CA. + """ + ver_maj, ver_min = TLS_PROTOCOL_VERSIONS[version] + severity_fatal = 0x02 + description_unknown_ca = 0x30 + msg = struct.pack(">BB", severity_fatal, description_unknown_ca) + msg_len = len(msg) + record_type_alert = 0x15 + record = struct.pack(">BBBH", record_type_alert, ver_maj, ver_min, msg_len) + msg + return record diff --git a/openpype/vendor/python/python_2/urllib3/contrib/appengine.py b/openpype/vendor/python/python_2/urllib3/contrib/appengine.py new file mode 100644 index 0000000000..f91bdd6e77 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/contrib/appengine.py @@ -0,0 +1,314 @@ +""" +This module provides a pool manager that uses Google App Engine's +`URLFetch Service `_. + +Example usage:: + + from urllib3 import PoolManager + from urllib3.contrib.appengine import AppEngineManager, is_appengine_sandbox + + if is_appengine_sandbox(): + # AppEngineManager uses AppEngine's URLFetch API behind the scenes + http = AppEngineManager() + else: + # PoolManager uses a socket-level API behind the scenes + http = PoolManager() + + r = http.request('GET', 'https://google.com/') + +There are `limitations `_ to the URLFetch service and it may not be +the best choice for your application. There are three options for using +urllib3 on Google App Engine: + +1. You can use :class:`AppEngineManager` with URLFetch. URLFetch is + cost-effective in many circumstances as long as your usage is within the + limitations. +2. You can use a normal :class:`~urllib3.PoolManager` by enabling sockets. + Sockets also have `limitations and restrictions + `_ and have a lower free quota than URLFetch. + To use sockets, be sure to specify the following in your ``app.yaml``:: + + env_variables: + GAE_USE_SOCKETS_HTTPLIB : 'true' + +3. If you are using `App Engine Flexible +`_, you can use the standard +:class:`PoolManager` without any configuration or special environment variables. +""" + +from __future__ import absolute_import + +import io +import logging +import warnings + +from ..exceptions import ( + HTTPError, + HTTPWarning, + MaxRetryError, + ProtocolError, + SSLError, + TimeoutError, +) +from ..packages.six.moves.urllib.parse import urljoin +from ..request import RequestMethods +from ..response import HTTPResponse +from ..util.retry import Retry +from ..util.timeout import Timeout +from . import _appengine_environ + +try: + from google.appengine.api import urlfetch +except ImportError: + urlfetch = None + + +log = logging.getLogger(__name__) + + +class AppEnginePlatformWarning(HTTPWarning): + pass + + +class AppEnginePlatformError(HTTPError): + pass + + +class AppEngineManager(RequestMethods): + """ + Connection manager for Google App Engine sandbox applications. + + This manager uses the URLFetch service directly instead of using the + emulated httplib, and is subject to URLFetch limitations as described in + the App Engine documentation `here + `_. + + Notably it will raise an :class:`AppEnginePlatformError` if: + * URLFetch is not available. + * If you attempt to use this on App Engine Flexible, as full socket + support is available. + * If a request size is more than 10 megabytes. + * If a response size is more than 32 megabytes. + * If you use an unsupported request method such as OPTIONS. + + Beyond those cases, it will raise normal urllib3 errors. + """ + + def __init__( + self, + headers=None, + retries=None, + validate_certificate=True, + urlfetch_retries=True, + ): + if not urlfetch: + raise AppEnginePlatformError( + "URLFetch is not available in this environment." + ) + + warnings.warn( + "urllib3 is using URLFetch on Google App Engine sandbox instead " + "of sockets. To use sockets directly instead of URLFetch see " + "https://urllib3.readthedocs.io/en/1.26.x/reference/urllib3.contrib.html.", + AppEnginePlatformWarning, + ) + + RequestMethods.__init__(self, headers) + self.validate_certificate = validate_certificate + self.urlfetch_retries = urlfetch_retries + + self.retries = retries or Retry.DEFAULT + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + # Return False to re-raise any potential exceptions + return False + + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=None, + redirect=True, + timeout=Timeout.DEFAULT_TIMEOUT, + **response_kw + ): + + retries = self._get_retries(retries, redirect) + + try: + follow_redirects = redirect and retries.redirect != 0 and retries.total + response = urlfetch.fetch( + url, + payload=body, + method=method, + headers=headers or {}, + allow_truncated=False, + follow_redirects=self.urlfetch_retries and follow_redirects, + deadline=self._get_absolute_timeout(timeout), + validate_certificate=self.validate_certificate, + ) + except urlfetch.DeadlineExceededError as e: + raise TimeoutError(self, e) + + except urlfetch.InvalidURLError as e: + if "too large" in str(e): + raise AppEnginePlatformError( + "URLFetch request too large, URLFetch only " + "supports requests up to 10mb in size.", + e, + ) + raise ProtocolError(e) + + except urlfetch.DownloadError as e: + if "Too many redirects" in str(e): + raise MaxRetryError(self, url, reason=e) + raise ProtocolError(e) + + except urlfetch.ResponseTooLargeError as e: + raise AppEnginePlatformError( + "URLFetch response too large, URLFetch only supports" + "responses up to 32mb in size.", + e, + ) + + except urlfetch.SSLCertificateError as e: + raise SSLError(e) + + except urlfetch.InvalidMethodError as e: + raise AppEnginePlatformError( + "URLFetch does not support method: %s" % method, e + ) + + http_response = self._urlfetch_response_to_http_response( + response, retries=retries, **response_kw + ) + + # Handle redirect? + redirect_location = redirect and http_response.get_redirect_location() + if redirect_location: + # Check for redirect response + if self.urlfetch_retries and retries.raise_on_redirect: + raise MaxRetryError(self, url, "too many redirects") + else: + if http_response.status == 303: + method = "GET" + + try: + retries = retries.increment( + method, url, response=http_response, _pool=self + ) + except MaxRetryError: + if retries.raise_on_redirect: + raise MaxRetryError(self, url, "too many redirects") + return http_response + + retries.sleep_for_retry(http_response) + log.debug("Redirecting %s -> %s", url, redirect_location) + redirect_url = urljoin(url, redirect_location) + return self.urlopen( + method, + redirect_url, + body, + headers, + retries=retries, + redirect=redirect, + timeout=timeout, + **response_kw + ) + + # Check if we should retry the HTTP response. + has_retry_after = bool(http_response.getheader("Retry-After")) + if retries.is_retry(method, http_response.status, has_retry_after): + retries = retries.increment(method, url, response=http_response, _pool=self) + log.debug("Retry: %s", url) + retries.sleep(http_response) + return self.urlopen( + method, + url, + body=body, + headers=headers, + retries=retries, + redirect=redirect, + timeout=timeout, + **response_kw + ) + + return http_response + + def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw): + + if is_prod_appengine(): + # Production GAE handles deflate encoding automatically, but does + # not remove the encoding header. + content_encoding = urlfetch_resp.headers.get("content-encoding") + + if content_encoding == "deflate": + del urlfetch_resp.headers["content-encoding"] + + transfer_encoding = urlfetch_resp.headers.get("transfer-encoding") + # We have a full response's content, + # so let's make sure we don't report ourselves as chunked data. + if transfer_encoding == "chunked": + encodings = transfer_encoding.split(",") + encodings.remove("chunked") + urlfetch_resp.headers["transfer-encoding"] = ",".join(encodings) + + original_response = HTTPResponse( + # In order for decoding to work, we must present the content as + # a file-like object. + body=io.BytesIO(urlfetch_resp.content), + msg=urlfetch_resp.header_msg, + headers=urlfetch_resp.headers, + status=urlfetch_resp.status_code, + **response_kw + ) + + return HTTPResponse( + body=io.BytesIO(urlfetch_resp.content), + headers=urlfetch_resp.headers, + status=urlfetch_resp.status_code, + original_response=original_response, + **response_kw + ) + + def _get_absolute_timeout(self, timeout): + if timeout is Timeout.DEFAULT_TIMEOUT: + return None # Defer to URLFetch's default. + if isinstance(timeout, Timeout): + if timeout._read is not None or timeout._connect is not None: + warnings.warn( + "URLFetch does not support granular timeout settings, " + "reverting to total or default URLFetch timeout.", + AppEnginePlatformWarning, + ) + return timeout.total + return timeout + + def _get_retries(self, retries, redirect): + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect, default=self.retries) + + if retries.connect or retries.read or retries.redirect: + warnings.warn( + "URLFetch only supports total retries and does not " + "recognize connect, read, or redirect retry parameters.", + AppEnginePlatformWarning, + ) + + return retries + + +# Alias methods from _appengine_environ to maintain public API interface. + +is_appengine = _appengine_environ.is_appengine +is_appengine_sandbox = _appengine_environ.is_appengine_sandbox +is_local_appengine = _appengine_environ.is_local_appengine +is_prod_appengine = _appengine_environ.is_prod_appengine +is_prod_appengine_mvms = _appengine_environ.is_prod_appengine_mvms diff --git a/openpype/vendor/python/python_2/urllib3/contrib/ntlmpool.py b/openpype/vendor/python/python_2/urllib3/contrib/ntlmpool.py new file mode 100644 index 0000000000..41a8fd174c --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/contrib/ntlmpool.py @@ -0,0 +1,130 @@ +""" +NTLM authenticating pool, contributed by erikcederstran + +Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10 +""" +from __future__ import absolute_import + +import warnings +from logging import getLogger + +from ntlm import ntlm + +from .. import HTTPSConnectionPool +from ..packages.six.moves.http_client import HTTPSConnection + +warnings.warn( + "The 'urllib3.contrib.ntlmpool' module is deprecated and will be removed " + "in urllib3 v2.0 release, urllib3 is not able to support it properly due " + "to reasons listed in issue: https://github.com/urllib3/urllib3/issues/2282. " + "If you are a user of this module please comment in the mentioned issue.", + DeprecationWarning, +) + +log = getLogger(__name__) + + +class NTLMConnectionPool(HTTPSConnectionPool): + """ + Implements an NTLM authentication version of an urllib3 connection pool + """ + + scheme = "https" + + def __init__(self, user, pw, authurl, *args, **kwargs): + """ + authurl is a random URL on the server that is protected by NTLM. + user is the Windows user, probably in the DOMAIN\\username format. + pw is the password for the user. + """ + super(NTLMConnectionPool, self).__init__(*args, **kwargs) + self.authurl = authurl + self.rawuser = user + user_parts = user.split("\\", 1) + self.domain = user_parts[0].upper() + self.user = user_parts[1] + self.pw = pw + + def _new_conn(self): + # Performs the NTLM handshake that secures the connection. The socket + # must be kept open while requests are performed. + self.num_connections += 1 + log.debug( + "Starting NTLM HTTPS connection no. %d: https://%s%s", + self.num_connections, + self.host, + self.authurl, + ) + + headers = {"Connection": "Keep-Alive"} + req_header = "Authorization" + resp_header = "www-authenticate" + + conn = HTTPSConnection(host=self.host, port=self.port) + + # Send negotiation message + headers[req_header] = "NTLM %s" % ntlm.create_NTLM_NEGOTIATE_MESSAGE( + self.rawuser + ) + log.debug("Request headers: %s", headers) + conn.request("GET", self.authurl, None, headers) + res = conn.getresponse() + reshdr = dict(res.getheaders()) + log.debug("Response status: %s %s", res.status, res.reason) + log.debug("Response headers: %s", reshdr) + log.debug("Response data: %s [...]", res.read(100)) + + # Remove the reference to the socket, so that it can not be closed by + # the response object (we want to keep the socket open) + res.fp = None + + # Server should respond with a challenge message + auth_header_values = reshdr[resp_header].split(", ") + auth_header_value = None + for s in auth_header_values: + if s[:5] == "NTLM ": + auth_header_value = s[5:] + if auth_header_value is None: + raise Exception( + "Unexpected %s response header: %s" % (resp_header, reshdr[resp_header]) + ) + + # Send authentication message + ServerChallenge, NegotiateFlags = ntlm.parse_NTLM_CHALLENGE_MESSAGE( + auth_header_value + ) + auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE( + ServerChallenge, self.user, self.domain, self.pw, NegotiateFlags + ) + headers[req_header] = "NTLM %s" % auth_msg + log.debug("Request headers: %s", headers) + conn.request("GET", self.authurl, None, headers) + res = conn.getresponse() + log.debug("Response status: %s %s", res.status, res.reason) + log.debug("Response headers: %s", dict(res.getheaders())) + log.debug("Response data: %s [...]", res.read()[:100]) + if res.status != 200: + if res.status == 401: + raise Exception("Server rejected request: wrong username or password") + raise Exception("Wrong server response: %s %s" % (res.status, res.reason)) + + res.fp = None + log.debug("Connection established") + return conn + + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=3, + redirect=True, + assert_same_host=True, + ): + if headers is None: + headers = {} + headers["Connection"] = "Keep-Alive" + return super(NTLMConnectionPool, self).urlopen( + method, url, body, headers, retries, redirect, assert_same_host + ) diff --git a/openpype/vendor/python/python_2/urllib3/contrib/pyopenssl.py b/openpype/vendor/python/python_2/urllib3/contrib/pyopenssl.py new file mode 100644 index 0000000000..def83afdb2 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/contrib/pyopenssl.py @@ -0,0 +1,511 @@ +""" +TLS with SNI_-support for Python 2. Follow these instructions if you would +like to verify TLS certificates in Python 2. Note, the default libraries do +*not* do certificate checking; you need to do additional work to validate +certificates yourself. + +This needs the following packages installed: + +* `pyOpenSSL`_ (tested with 16.0.0) +* `cryptography`_ (minimum 1.3.4, from pyopenssl) +* `idna`_ (minimum 2.0, from cryptography) + +However, pyopenssl depends on cryptography, which depends on idna, so while we +use all three directly here we end up having relatively few packages required. + +You can install them with the following command: + +.. code-block:: bash + + $ python -m pip install pyopenssl cryptography idna + +To activate certificate checking, call +:func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code +before you begin making HTTP requests. This can be done in a ``sitecustomize`` +module, or at any other time before your application begins using ``urllib3``, +like this: + +.. code-block:: python + + try: + import urllib3.contrib.pyopenssl + urllib3.contrib.pyopenssl.inject_into_urllib3() + except ImportError: + pass + +Now you can use :mod:`urllib3` as you normally would, and it will support SNI +when the required modules are installed. + +Activating this module also has the positive side effect of disabling SSL/TLS +compression in Python 2 (see `CRIME attack`_). + +.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication +.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit) +.. _pyopenssl: https://www.pyopenssl.org +.. _cryptography: https://cryptography.io +.. _idna: https://github.com/kjd/idna +""" +from __future__ import absolute_import + +import OpenSSL.SSL +from cryptography import x509 +from cryptography.hazmat.backends.openssl import backend as openssl_backend +from cryptography.hazmat.backends.openssl.x509 import _Certificate + +try: + from cryptography.x509 import UnsupportedExtension +except ImportError: + # UnsupportedExtension is gone in cryptography >= 2.1.0 + class UnsupportedExtension(Exception): + pass + + +from io import BytesIO +from socket import error as SocketError +from socket import timeout + +try: # Platform-specific: Python 2 + from socket import _fileobject +except ImportError: # Platform-specific: Python 3 + _fileobject = None + from ..packages.backports.makefile import backport_makefile + +import logging +import ssl +import sys + +from .. import util +from ..packages import six +from ..util.ssl_ import PROTOCOL_TLS_CLIENT + +__all__ = ["inject_into_urllib3", "extract_from_urllib3"] + +# SNI always works. +HAS_SNI = True + +# Map from urllib3 to PyOpenSSL compatible parameter-values. +_openssl_versions = { + util.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD, + PROTOCOL_TLS_CLIENT: OpenSSL.SSL.SSLv23_METHOD, + ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, +} + +if hasattr(ssl, "PROTOCOL_SSLv3") and hasattr(OpenSSL.SSL, "SSLv3_METHOD"): + _openssl_versions[ssl.PROTOCOL_SSLv3] = OpenSSL.SSL.SSLv3_METHOD + +if hasattr(ssl, "PROTOCOL_TLSv1_1") and hasattr(OpenSSL.SSL, "TLSv1_1_METHOD"): + _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD + +if hasattr(ssl, "PROTOCOL_TLSv1_2") and hasattr(OpenSSL.SSL, "TLSv1_2_METHOD"): + _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD + + +_stdlib_to_openssl_verify = { + ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, + ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, + ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER + + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, +} +_openssl_to_stdlib_verify = dict((v, k) for k, v in _stdlib_to_openssl_verify.items()) + +# OpenSSL will only write 16K at a time +SSL_WRITE_BLOCKSIZE = 16384 + +orig_util_HAS_SNI = util.HAS_SNI +orig_util_SSLContext = util.ssl_.SSLContext + + +log = logging.getLogger(__name__) + + +def inject_into_urllib3(): + "Monkey-patch urllib3 with PyOpenSSL-backed SSL-support." + + _validate_dependencies_met() + + util.SSLContext = PyOpenSSLContext + util.ssl_.SSLContext = PyOpenSSLContext + util.HAS_SNI = HAS_SNI + util.ssl_.HAS_SNI = HAS_SNI + util.IS_PYOPENSSL = True + util.ssl_.IS_PYOPENSSL = True + + +def extract_from_urllib3(): + "Undo monkey-patching by :func:`inject_into_urllib3`." + + util.SSLContext = orig_util_SSLContext + util.ssl_.SSLContext = orig_util_SSLContext + util.HAS_SNI = orig_util_HAS_SNI + util.ssl_.HAS_SNI = orig_util_HAS_SNI + util.IS_PYOPENSSL = False + util.ssl_.IS_PYOPENSSL = False + + +def _validate_dependencies_met(): + """ + Verifies that PyOpenSSL's package-level dependencies have been met. + Throws `ImportError` if they are not met. + """ + # Method added in `cryptography==1.1`; not available in older versions + from cryptography.x509.extensions import Extensions + + if getattr(Extensions, "get_extension_for_class", None) is None: + raise ImportError( + "'cryptography' module missing required functionality. " + "Try upgrading to v1.3.4 or newer." + ) + + # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509 + # attribute is only present on those versions. + from OpenSSL.crypto import X509 + + x509 = X509() + if getattr(x509, "_x509", None) is None: + raise ImportError( + "'pyOpenSSL' module missing required functionality. " + "Try upgrading to v0.14 or newer." + ) + + +def _dnsname_to_stdlib(name): + """ + Converts a dNSName SubjectAlternativeName field to the form used by the + standard library on the given Python version. + + Cryptography produces a dNSName as a unicode string that was idna-decoded + from ASCII bytes. We need to idna-encode that string to get it back, and + then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib + uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8). + + If the name cannot be idna-encoded then we return None signalling that + the name given should be skipped. + """ + + def idna_encode(name): + """ + Borrowed wholesale from the Python Cryptography Project. It turns out + that we can't just safely call `idna.encode`: it can explode for + wildcard names. This avoids that problem. + """ + import idna + + try: + for prefix in [u"*.", u"."]: + if name.startswith(prefix): + name = name[len(prefix) :] + return prefix.encode("ascii") + idna.encode(name) + return idna.encode(name) + except idna.core.IDNAError: + return None + + # Don't send IPv6 addresses through the IDNA encoder. + if ":" in name: + return name + + name = idna_encode(name) + if name is None: + return None + elif sys.version_info >= (3, 0): + name = name.decode("utf-8") + return name + + +def get_subj_alt_name(peer_cert): + """ + Given an PyOpenSSL certificate, provides all the subject alternative names. + """ + # Pass the cert to cryptography, which has much better APIs for this. + if hasattr(peer_cert, "to_cryptography"): + cert = peer_cert.to_cryptography() + else: + # This is technically using private APIs, but should work across all + # relevant versions before PyOpenSSL got a proper API for this. + cert = _Certificate(openssl_backend, peer_cert._x509) + + # We want to find the SAN extension. Ask Cryptography to locate it (it's + # faster than looping in Python) + try: + ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value + except x509.ExtensionNotFound: + # No such extension, return the empty list. + return [] + except ( + x509.DuplicateExtension, + UnsupportedExtension, + x509.UnsupportedGeneralNameType, + UnicodeError, + ) as e: + # A problem has been found with the quality of the certificate. Assume + # no SAN field is present. + log.warning( + "A problem was encountered with the certificate that prevented " + "urllib3 from finding the SubjectAlternativeName field. This can " + "affect certificate validation. The error was %s", + e, + ) + return [] + + # We want to return dNSName and iPAddress fields. We need to cast the IPs + # back to strings because the match_hostname function wants them as + # strings. + # Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8 + # decoded. This is pretty frustrating, but that's what the standard library + # does with certificates, and so we need to attempt to do the same. + # We also want to skip over names which cannot be idna encoded. + names = [ + ("DNS", name) + for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName)) + if name is not None + ] + names.extend( + ("IP Address", str(name)) for name in ext.get_values_for_type(x509.IPAddress) + ) + + return names + + +class WrappedSocket(object): + """API-compatibility wrapper for Python OpenSSL's Connection-class. + + Note: _makefile_refs, _drop() and _reuse() are needed for the garbage + collector of pypy. + """ + + def __init__(self, connection, socket, suppress_ragged_eofs=True): + self.connection = connection + self.socket = socket + self.suppress_ragged_eofs = suppress_ragged_eofs + self._makefile_refs = 0 + self._closed = False + + def fileno(self): + return self.socket.fileno() + + # Copy-pasted from Python 3.5 source code + def _decref_socketios(self): + if self._makefile_refs > 0: + self._makefile_refs -= 1 + if self._closed: + self.close() + + def recv(self, *args, **kwargs): + try: + data = self.connection.recv(*args, **kwargs) + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): + return b"" + else: + raise SocketError(str(e)) + except OpenSSL.SSL.ZeroReturnError: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return b"" + else: + raise + except OpenSSL.SSL.WantReadError: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise timeout("The read operation timed out") + else: + return self.recv(*args, **kwargs) + + # TLS 1.3 post-handshake authentication + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("read error: %r" % e) + else: + return data + + def recv_into(self, *args, **kwargs): + try: + return self.connection.recv_into(*args, **kwargs) + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): + return 0 + else: + raise SocketError(str(e)) + except OpenSSL.SSL.ZeroReturnError: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return 0 + else: + raise + except OpenSSL.SSL.WantReadError: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise timeout("The read operation timed out") + else: + return self.recv_into(*args, **kwargs) + + # TLS 1.3 post-handshake authentication + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("read error: %r" % e) + + def settimeout(self, timeout): + return self.socket.settimeout(timeout) + + def _send_until_done(self, data): + while True: + try: + return self.connection.send(data) + except OpenSSL.SSL.WantWriteError: + if not util.wait_for_write(self.socket, self.socket.gettimeout()): + raise timeout() + continue + except OpenSSL.SSL.SysCallError as e: + raise SocketError(str(e)) + + def sendall(self, data): + total_sent = 0 + while total_sent < len(data): + sent = self._send_until_done( + data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE] + ) + total_sent += sent + + def shutdown(self): + # FIXME rethrow compatible exceptions should we ever use this + self.connection.shutdown() + + def close(self): + if self._makefile_refs < 1: + try: + self._closed = True + return self.connection.close() + except OpenSSL.SSL.Error: + return + else: + self._makefile_refs -= 1 + + def getpeercert(self, binary_form=False): + x509 = self.connection.get_peer_certificate() + + if not x509: + return x509 + + if binary_form: + return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509) + + return { + "subject": ((("commonName", x509.get_subject().CN),),), + "subjectAltName": get_subj_alt_name(x509), + } + + def version(self): + return self.connection.get_protocol_version_name() + + def _reuse(self): + self._makefile_refs += 1 + + def _drop(self): + if self._makefile_refs < 1: + self.close() + else: + self._makefile_refs -= 1 + + +if _fileobject: # Platform-specific: Python 2 + + def makefile(self, mode, bufsize=-1): + self._makefile_refs += 1 + return _fileobject(self, mode, bufsize, close=True) + + +else: # Platform-specific: Python 3 + makefile = backport_makefile + +WrappedSocket.makefile = makefile + + +class PyOpenSSLContext(object): + """ + I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible + for translating the interface of the standard library ``SSLContext`` object + to calls into PyOpenSSL. + """ + + def __init__(self, protocol): + self.protocol = _openssl_versions[protocol] + self._ctx = OpenSSL.SSL.Context(self.protocol) + self._options = 0 + self.check_hostname = False + + @property + def options(self): + return self._options + + @options.setter + def options(self, value): + self._options = value + self._ctx.set_options(value) + + @property + def verify_mode(self): + return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()] + + @verify_mode.setter + def verify_mode(self, value): + self._ctx.set_verify(_stdlib_to_openssl_verify[value], _verify_callback) + + def set_default_verify_paths(self): + self._ctx.set_default_verify_paths() + + def set_ciphers(self, ciphers): + if isinstance(ciphers, six.text_type): + ciphers = ciphers.encode("utf-8") + self._ctx.set_cipher_list(ciphers) + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + if cafile is not None: + cafile = cafile.encode("utf-8") + if capath is not None: + capath = capath.encode("utf-8") + try: + self._ctx.load_verify_locations(cafile, capath) + if cadata is not None: + self._ctx.load_verify_locations(BytesIO(cadata)) + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("unable to load trusted certificates: %r" % e) + + def load_cert_chain(self, certfile, keyfile=None, password=None): + self._ctx.use_certificate_chain_file(certfile) + if password is not None: + if not isinstance(password, six.binary_type): + password = password.encode("utf-8") + self._ctx.set_passwd_cb(lambda *_: password) + self._ctx.use_privatekey_file(keyfile or certfile) + + def set_alpn_protocols(self, protocols): + protocols = [six.ensure_binary(p) for p in protocols] + return self._ctx.set_alpn_protos(protocols) + + def wrap_socket( + self, + sock, + server_side=False, + do_handshake_on_connect=True, + suppress_ragged_eofs=True, + server_hostname=None, + ): + cnx = OpenSSL.SSL.Connection(self._ctx, sock) + + if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3 + server_hostname = server_hostname.encode("utf-8") + + if server_hostname is not None: + cnx.set_tlsext_host_name(server_hostname) + + cnx.set_connect_state() + + while True: + try: + cnx.do_handshake() + except OpenSSL.SSL.WantReadError: + if not util.wait_for_read(sock, sock.gettimeout()): + raise timeout("select timed out") + continue + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("bad handshake: %r" % e) + break + + return WrappedSocket(cnx, sock) + + +def _verify_callback(cnx, x509, err_no, err_depth, return_code): + return err_no == 0 diff --git a/openpype/vendor/python/python_2/urllib3/contrib/securetransport.py b/openpype/vendor/python/python_2/urllib3/contrib/securetransport.py new file mode 100644 index 0000000000..554c015fed --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/contrib/securetransport.py @@ -0,0 +1,922 @@ +""" +SecureTranport support for urllib3 via ctypes. + +This makes platform-native TLS available to urllib3 users on macOS without the +use of a compiler. This is an important feature because the Python Package +Index is moving to become a TLSv1.2-or-higher server, and the default OpenSSL +that ships with macOS is not capable of doing TLSv1.2. The only way to resolve +this is to give macOS users an alternative solution to the problem, and that +solution is to use SecureTransport. + +We use ctypes here because this solution must not require a compiler. That's +because pip is not allowed to require a compiler either. + +This is not intended to be a seriously long-term solution to this problem. +The hope is that PEP 543 will eventually solve this issue for us, at which +point we can retire this contrib module. But in the short term, we need to +solve the impending tire fire that is Python on Mac without this kind of +contrib module. So...here we are. + +To use this module, simply import and inject it:: + + import urllib3.contrib.securetransport + urllib3.contrib.securetransport.inject_into_urllib3() + +Happy TLSing! + +This code is a bastardised version of the code found in Will Bond's oscrypto +library. An enormous debt is owed to him for blazing this trail for us. For +that reason, this code should be considered to be covered both by urllib3's +license and by oscrypto's: + +.. code-block:: + + Copyright (c) 2015-2016 Will Bond + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +""" +from __future__ import absolute_import + +import contextlib +import ctypes +import errno +import os.path +import shutil +import socket +import ssl +import struct +import threading +import weakref + +import six + +from .. import util +from ..util.ssl_ import PROTOCOL_TLS_CLIENT +from ._securetransport.bindings import CoreFoundation, Security, SecurityConst +from ._securetransport.low_level import ( + _assert_no_error, + _build_tls_unknown_ca_alert, + _cert_array_from_pem, + _create_cfstring_array, + _load_client_cert_chain, + _temporary_keychain, +) + +try: # Platform-specific: Python 2 + from socket import _fileobject +except ImportError: # Platform-specific: Python 3 + _fileobject = None + from ..packages.backports.makefile import backport_makefile + +__all__ = ["inject_into_urllib3", "extract_from_urllib3"] + +# SNI always works +HAS_SNI = True + +orig_util_HAS_SNI = util.HAS_SNI +orig_util_SSLContext = util.ssl_.SSLContext + +# This dictionary is used by the read callback to obtain a handle to the +# calling wrapped socket. This is a pretty silly approach, but for now it'll +# do. I feel like I should be able to smuggle a handle to the wrapped socket +# directly in the SSLConnectionRef, but for now this approach will work I +# guess. +# +# We need to lock around this structure for inserts, but we don't do it for +# reads/writes in the callbacks. The reasoning here goes as follows: +# +# 1. It is not possible to call into the callbacks before the dictionary is +# populated, so once in the callback the id must be in the dictionary. +# 2. The callbacks don't mutate the dictionary, they only read from it, and +# so cannot conflict with any of the insertions. +# +# This is good: if we had to lock in the callbacks we'd drastically slow down +# the performance of this code. +_connection_refs = weakref.WeakValueDictionary() +_connection_ref_lock = threading.Lock() + +# Limit writes to 16kB. This is OpenSSL's limit, but we'll cargo-cult it over +# for no better reason than we need *a* limit, and this one is right there. +SSL_WRITE_BLOCKSIZE = 16384 + +# This is our equivalent of util.ssl_.DEFAULT_CIPHERS, but expanded out to +# individual cipher suites. We need to do this because this is how +# SecureTransport wants them. +CIPHER_SUITES = [ + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_AES_256_GCM_SHA384, + SecurityConst.TLS_AES_128_GCM_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_AES_128_CCM_8_SHA256, + SecurityConst.TLS_AES_128_CCM_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA256, + SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA, +] + +# Basically this is simple: for PROTOCOL_SSLv23 we turn it into a low of +# TLSv1 and a high of TLSv1.2. For everything else, we pin to that version. +# TLSv1 to 1.2 are supported on macOS 10.8+ +_protocol_to_min_max = { + util.PROTOCOL_TLS: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), + PROTOCOL_TLS_CLIENT: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), +} + +if hasattr(ssl, "PROTOCOL_SSLv2"): + _protocol_to_min_max[ssl.PROTOCOL_SSLv2] = ( + SecurityConst.kSSLProtocol2, + SecurityConst.kSSLProtocol2, + ) +if hasattr(ssl, "PROTOCOL_SSLv3"): + _protocol_to_min_max[ssl.PROTOCOL_SSLv3] = ( + SecurityConst.kSSLProtocol3, + SecurityConst.kSSLProtocol3, + ) +if hasattr(ssl, "PROTOCOL_TLSv1"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1] = ( + SecurityConst.kTLSProtocol1, + SecurityConst.kTLSProtocol1, + ) +if hasattr(ssl, "PROTOCOL_TLSv1_1"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1_1] = ( + SecurityConst.kTLSProtocol11, + SecurityConst.kTLSProtocol11, + ) +if hasattr(ssl, "PROTOCOL_TLSv1_2"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1_2] = ( + SecurityConst.kTLSProtocol12, + SecurityConst.kTLSProtocol12, + ) + + +def inject_into_urllib3(): + """ + Monkey-patch urllib3 with SecureTransport-backed SSL-support. + """ + util.SSLContext = SecureTransportContext + util.ssl_.SSLContext = SecureTransportContext + util.HAS_SNI = HAS_SNI + util.ssl_.HAS_SNI = HAS_SNI + util.IS_SECURETRANSPORT = True + util.ssl_.IS_SECURETRANSPORT = True + + +def extract_from_urllib3(): + """ + Undo monkey-patching by :func:`inject_into_urllib3`. + """ + util.SSLContext = orig_util_SSLContext + util.ssl_.SSLContext = orig_util_SSLContext + util.HAS_SNI = orig_util_HAS_SNI + util.ssl_.HAS_SNI = orig_util_HAS_SNI + util.IS_SECURETRANSPORT = False + util.ssl_.IS_SECURETRANSPORT = False + + +def _read_callback(connection_id, data_buffer, data_length_pointer): + """ + SecureTransport read callback. This is called by ST to request that data + be returned from the socket. + """ + wrapped_socket = None + try: + wrapped_socket = _connection_refs.get(connection_id) + if wrapped_socket is None: + return SecurityConst.errSSLInternal + base_socket = wrapped_socket.socket + + requested_length = data_length_pointer[0] + + timeout = wrapped_socket.gettimeout() + error = None + read_count = 0 + + try: + while read_count < requested_length: + if timeout is None or timeout >= 0: + if not util.wait_for_read(base_socket, timeout): + raise socket.error(errno.EAGAIN, "timed out") + + remaining = requested_length - read_count + buffer = (ctypes.c_char * remaining).from_address( + data_buffer + read_count + ) + chunk_size = base_socket.recv_into(buffer, remaining) + read_count += chunk_size + if not chunk_size: + if not read_count: + return SecurityConst.errSSLClosedGraceful + break + except (socket.error) as e: + error = e.errno + + if error is not None and error != errno.EAGAIN: + data_length_pointer[0] = read_count + if error == errno.ECONNRESET or error == errno.EPIPE: + return SecurityConst.errSSLClosedAbort + raise + + data_length_pointer[0] = read_count + + if read_count != requested_length: + return SecurityConst.errSSLWouldBlock + + return 0 + except Exception as e: + if wrapped_socket is not None: + wrapped_socket._exception = e + return SecurityConst.errSSLInternal + + +def _write_callback(connection_id, data_buffer, data_length_pointer): + """ + SecureTransport write callback. This is called by ST to request that data + actually be sent on the network. + """ + wrapped_socket = None + try: + wrapped_socket = _connection_refs.get(connection_id) + if wrapped_socket is None: + return SecurityConst.errSSLInternal + base_socket = wrapped_socket.socket + + bytes_to_write = data_length_pointer[0] + data = ctypes.string_at(data_buffer, bytes_to_write) + + timeout = wrapped_socket.gettimeout() + error = None + sent = 0 + + try: + while sent < bytes_to_write: + if timeout is None or timeout >= 0: + if not util.wait_for_write(base_socket, timeout): + raise socket.error(errno.EAGAIN, "timed out") + chunk_sent = base_socket.send(data) + sent += chunk_sent + + # This has some needless copying here, but I'm not sure there's + # much value in optimising this data path. + data = data[chunk_sent:] + except (socket.error) as e: + error = e.errno + + if error is not None and error != errno.EAGAIN: + data_length_pointer[0] = sent + if error == errno.ECONNRESET or error == errno.EPIPE: + return SecurityConst.errSSLClosedAbort + raise + + data_length_pointer[0] = sent + + if sent != bytes_to_write: + return SecurityConst.errSSLWouldBlock + + return 0 + except Exception as e: + if wrapped_socket is not None: + wrapped_socket._exception = e + return SecurityConst.errSSLInternal + + +# We need to keep these two objects references alive: if they get GC'd while +# in use then SecureTransport could attempt to call a function that is in freed +# memory. That would be...uh...bad. Yeah, that's the word. Bad. +_read_callback_pointer = Security.SSLReadFunc(_read_callback) +_write_callback_pointer = Security.SSLWriteFunc(_write_callback) + + +class WrappedSocket(object): + """ + API-compatibility wrapper for Python's OpenSSL wrapped socket object. + + Note: _makefile_refs, _drop(), and _reuse() are needed for the garbage + collector of PyPy. + """ + + def __init__(self, socket): + self.socket = socket + self.context = None + self._makefile_refs = 0 + self._closed = False + self._exception = None + self._keychain = None + self._keychain_dir = None + self._client_cert_chain = None + + # We save off the previously-configured timeout and then set it to + # zero. This is done because we use select and friends to handle the + # timeouts, but if we leave the timeout set on the lower socket then + # Python will "kindly" call select on that socket again for us. Avoid + # that by forcing the timeout to zero. + self._timeout = self.socket.gettimeout() + self.socket.settimeout(0) + + @contextlib.contextmanager + def _raise_on_error(self): + """ + A context manager that can be used to wrap calls that do I/O from + SecureTransport. If any of the I/O callbacks hit an exception, this + context manager will correctly propagate the exception after the fact. + This avoids silently swallowing those exceptions. + + It also correctly forces the socket closed. + """ + self._exception = None + + # We explicitly don't catch around this yield because in the unlikely + # event that an exception was hit in the block we don't want to swallow + # it. + yield + if self._exception is not None: + exception, self._exception = self._exception, None + self.close() + raise exception + + def _set_ciphers(self): + """ + Sets up the allowed ciphers. By default this matches the set in + util.ssl_.DEFAULT_CIPHERS, at least as supported by macOS. This is done + custom and doesn't allow changing at this time, mostly because parsing + OpenSSL cipher strings is going to be a freaking nightmare. + """ + ciphers = (Security.SSLCipherSuite * len(CIPHER_SUITES))(*CIPHER_SUITES) + result = Security.SSLSetEnabledCiphers( + self.context, ciphers, len(CIPHER_SUITES) + ) + _assert_no_error(result) + + def _set_alpn_protocols(self, protocols): + """ + Sets up the ALPN protocols on the context. + """ + if not protocols: + return + protocols_arr = _create_cfstring_array(protocols) + try: + result = Security.SSLSetALPNProtocols(self.context, protocols_arr) + _assert_no_error(result) + finally: + CoreFoundation.CFRelease(protocols_arr) + + def _custom_validate(self, verify, trust_bundle): + """ + Called when we have set custom validation. We do this in two cases: + first, when cert validation is entirely disabled; and second, when + using a custom trust DB. + Raises an SSLError if the connection is not trusted. + """ + # If we disabled cert validation, just say: cool. + if not verify: + return + + successes = ( + SecurityConst.kSecTrustResultUnspecified, + SecurityConst.kSecTrustResultProceed, + ) + try: + trust_result = self._evaluate_trust(trust_bundle) + if trust_result in successes: + return + reason = "error code: %d" % (trust_result,) + except Exception as e: + # Do not trust on error + reason = "exception: %r" % (e,) + + # SecureTransport does not send an alert nor shuts down the connection. + rec = _build_tls_unknown_ca_alert(self.version()) + self.socket.sendall(rec) + # close the connection immediately + # l_onoff = 1, activate linger + # l_linger = 0, linger for 0 seoncds + opts = struct.pack("ii", 1, 0) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, opts) + self.close() + raise ssl.SSLError("certificate verify failed, %s" % reason) + + def _evaluate_trust(self, trust_bundle): + # We want data in memory, so load it up. + if os.path.isfile(trust_bundle): + with open(trust_bundle, "rb") as f: + trust_bundle = f.read() + + cert_array = None + trust = Security.SecTrustRef() + + try: + # Get a CFArray that contains the certs we want. + cert_array = _cert_array_from_pem(trust_bundle) + + # Ok, now the hard part. We want to get the SecTrustRef that ST has + # created for this connection, shove our CAs into it, tell ST to + # ignore everything else it knows, and then ask if it can build a + # chain. This is a buuuunch of code. + result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust)) + _assert_no_error(result) + if not trust: + raise ssl.SSLError("Failed to copy trust reference") + + result = Security.SecTrustSetAnchorCertificates(trust, cert_array) + _assert_no_error(result) + + result = Security.SecTrustSetAnchorCertificatesOnly(trust, True) + _assert_no_error(result) + + trust_result = Security.SecTrustResultType() + result = Security.SecTrustEvaluate(trust, ctypes.byref(trust_result)) + _assert_no_error(result) + finally: + if trust: + CoreFoundation.CFRelease(trust) + + if cert_array is not None: + CoreFoundation.CFRelease(cert_array) + + return trust_result.value + + def handshake( + self, + server_hostname, + verify, + trust_bundle, + min_version, + max_version, + client_cert, + client_key, + client_key_passphrase, + alpn_protocols, + ): + """ + Actually performs the TLS handshake. This is run automatically by + wrapped socket, and shouldn't be needed in user code. + """ + # First, we do the initial bits of connection setup. We need to create + # a context, set its I/O funcs, and set the connection reference. + self.context = Security.SSLCreateContext( + None, SecurityConst.kSSLClientSide, SecurityConst.kSSLStreamType + ) + result = Security.SSLSetIOFuncs( + self.context, _read_callback_pointer, _write_callback_pointer + ) + _assert_no_error(result) + + # Here we need to compute the handle to use. We do this by taking the + # id of self modulo 2**31 - 1. If this is already in the dictionary, we + # just keep incrementing by one until we find a free space. + with _connection_ref_lock: + handle = id(self) % 2147483647 + while handle in _connection_refs: + handle = (handle + 1) % 2147483647 + _connection_refs[handle] = self + + result = Security.SSLSetConnection(self.context, handle) + _assert_no_error(result) + + # If we have a server hostname, we should set that too. + if server_hostname: + if not isinstance(server_hostname, bytes): + server_hostname = server_hostname.encode("utf-8") + + result = Security.SSLSetPeerDomainName( + self.context, server_hostname, len(server_hostname) + ) + _assert_no_error(result) + + # Setup the ciphers. + self._set_ciphers() + + # Setup the ALPN protocols. + self._set_alpn_protocols(alpn_protocols) + + # Set the minimum and maximum TLS versions. + result = Security.SSLSetProtocolVersionMin(self.context, min_version) + _assert_no_error(result) + + result = Security.SSLSetProtocolVersionMax(self.context, max_version) + _assert_no_error(result) + + # If there's a trust DB, we need to use it. We do that by telling + # SecureTransport to break on server auth. We also do that if we don't + # want to validate the certs at all: we just won't actually do any + # authing in that case. + if not verify or trust_bundle is not None: + result = Security.SSLSetSessionOption( + self.context, SecurityConst.kSSLSessionOptionBreakOnServerAuth, True + ) + _assert_no_error(result) + + # If there's a client cert, we need to use it. + if client_cert: + self._keychain, self._keychain_dir = _temporary_keychain() + self._client_cert_chain = _load_client_cert_chain( + self._keychain, client_cert, client_key + ) + result = Security.SSLSetCertificate(self.context, self._client_cert_chain) + _assert_no_error(result) + + while True: + with self._raise_on_error(): + result = Security.SSLHandshake(self.context) + + if result == SecurityConst.errSSLWouldBlock: + raise socket.timeout("handshake timed out") + elif result == SecurityConst.errSSLServerAuthCompleted: + self._custom_validate(verify, trust_bundle) + continue + else: + _assert_no_error(result) + break + + def fileno(self): + return self.socket.fileno() + + # Copy-pasted from Python 3.5 source code + def _decref_socketios(self): + if self._makefile_refs > 0: + self._makefile_refs -= 1 + if self._closed: + self.close() + + def recv(self, bufsiz): + buffer = ctypes.create_string_buffer(bufsiz) + bytes_read = self.recv_into(buffer, bufsiz) + data = buffer[:bytes_read] + return data + + def recv_into(self, buffer, nbytes=None): + # Read short on EOF. + if self._closed: + return 0 + + if nbytes is None: + nbytes = len(buffer) + + buffer = (ctypes.c_char * nbytes).from_buffer(buffer) + processed_bytes = ctypes.c_size_t(0) + + with self._raise_on_error(): + result = Security.SSLRead( + self.context, buffer, nbytes, ctypes.byref(processed_bytes) + ) + + # There are some result codes that we want to treat as "not always + # errors". Specifically, those are errSSLWouldBlock, + # errSSLClosedGraceful, and errSSLClosedNoNotify. + if result == SecurityConst.errSSLWouldBlock: + # If we didn't process any bytes, then this was just a time out. + # However, we can get errSSLWouldBlock in situations when we *did* + # read some data, and in those cases we should just read "short" + # and return. + if processed_bytes.value == 0: + # Timed out, no data read. + raise socket.timeout("recv timed out") + elif result in ( + SecurityConst.errSSLClosedGraceful, + SecurityConst.errSSLClosedNoNotify, + ): + # The remote peer has closed this connection. We should do so as + # well. Note that we don't actually return here because in + # principle this could actually be fired along with return data. + # It's unlikely though. + self.close() + else: + _assert_no_error(result) + + # Ok, we read and probably succeeded. We should return whatever data + # was actually read. + return processed_bytes.value + + def settimeout(self, timeout): + self._timeout = timeout + + def gettimeout(self): + return self._timeout + + def send(self, data): + processed_bytes = ctypes.c_size_t(0) + + with self._raise_on_error(): + result = Security.SSLWrite( + self.context, data, len(data), ctypes.byref(processed_bytes) + ) + + if result == SecurityConst.errSSLWouldBlock and processed_bytes.value == 0: + # Timed out + raise socket.timeout("send timed out") + else: + _assert_no_error(result) + + # We sent, and probably succeeded. Tell them how much we sent. + return processed_bytes.value + + def sendall(self, data): + total_sent = 0 + while total_sent < len(data): + sent = self.send(data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE]) + total_sent += sent + + def shutdown(self): + with self._raise_on_error(): + Security.SSLClose(self.context) + + def close(self): + # TODO: should I do clean shutdown here? Do I have to? + if self._makefile_refs < 1: + self._closed = True + if self.context: + CoreFoundation.CFRelease(self.context) + self.context = None + if self._client_cert_chain: + CoreFoundation.CFRelease(self._client_cert_chain) + self._client_cert_chain = None + if self._keychain: + Security.SecKeychainDelete(self._keychain) + CoreFoundation.CFRelease(self._keychain) + shutil.rmtree(self._keychain_dir) + self._keychain = self._keychain_dir = None + return self.socket.close() + else: + self._makefile_refs -= 1 + + def getpeercert(self, binary_form=False): + # Urgh, annoying. + # + # Here's how we do this: + # + # 1. Call SSLCopyPeerTrust to get hold of the trust object for this + # connection. + # 2. Call SecTrustGetCertificateAtIndex for index 0 to get the leaf. + # 3. To get the CN, call SecCertificateCopyCommonName and process that + # string so that it's of the appropriate type. + # 4. To get the SAN, we need to do something a bit more complex: + # a. Call SecCertificateCopyValues to get the data, requesting + # kSecOIDSubjectAltName. + # b. Mess about with this dictionary to try to get the SANs out. + # + # This is gross. Really gross. It's going to be a few hundred LoC extra + # just to repeat something that SecureTransport can *already do*. So my + # operating assumption at this time is that what we want to do is + # instead to just flag to urllib3 that it shouldn't do its own hostname + # validation when using SecureTransport. + if not binary_form: + raise ValueError("SecureTransport only supports dumping binary certs") + trust = Security.SecTrustRef() + certdata = None + der_bytes = None + + try: + # Grab the trust store. + result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust)) + _assert_no_error(result) + if not trust: + # Probably we haven't done the handshake yet. No biggie. + return None + + cert_count = Security.SecTrustGetCertificateCount(trust) + if not cert_count: + # Also a case that might happen if we haven't handshaked. + # Handshook? Handshaken? + return None + + leaf = Security.SecTrustGetCertificateAtIndex(trust, 0) + assert leaf + + # Ok, now we want the DER bytes. + certdata = Security.SecCertificateCopyData(leaf) + assert certdata + + data_length = CoreFoundation.CFDataGetLength(certdata) + data_buffer = CoreFoundation.CFDataGetBytePtr(certdata) + der_bytes = ctypes.string_at(data_buffer, data_length) + finally: + if certdata: + CoreFoundation.CFRelease(certdata) + if trust: + CoreFoundation.CFRelease(trust) + + return der_bytes + + def version(self): + protocol = Security.SSLProtocol() + result = Security.SSLGetNegotiatedProtocolVersion( + self.context, ctypes.byref(protocol) + ) + _assert_no_error(result) + if protocol.value == SecurityConst.kTLSProtocol13: + raise ssl.SSLError("SecureTransport does not support TLS 1.3") + elif protocol.value == SecurityConst.kTLSProtocol12: + return "TLSv1.2" + elif protocol.value == SecurityConst.kTLSProtocol11: + return "TLSv1.1" + elif protocol.value == SecurityConst.kTLSProtocol1: + return "TLSv1" + elif protocol.value == SecurityConst.kSSLProtocol3: + return "SSLv3" + elif protocol.value == SecurityConst.kSSLProtocol2: + return "SSLv2" + else: + raise ssl.SSLError("Unknown TLS version: %r" % protocol) + + def _reuse(self): + self._makefile_refs += 1 + + def _drop(self): + if self._makefile_refs < 1: + self.close() + else: + self._makefile_refs -= 1 + + +if _fileobject: # Platform-specific: Python 2 + + def makefile(self, mode, bufsize=-1): + self._makefile_refs += 1 + return _fileobject(self, mode, bufsize, close=True) + + +else: # Platform-specific: Python 3 + + def makefile(self, mode="r", buffering=None, *args, **kwargs): + # We disable buffering with SecureTransport because it conflicts with + # the buffering that ST does internally (see issue #1153 for more). + buffering = 0 + return backport_makefile(self, mode, buffering, *args, **kwargs) + + +WrappedSocket.makefile = makefile + + +class SecureTransportContext(object): + """ + I am a wrapper class for the SecureTransport library, to translate the + interface of the standard library ``SSLContext`` object to calls into + SecureTransport. + """ + + def __init__(self, protocol): + self._min_version, self._max_version = _protocol_to_min_max[protocol] + self._options = 0 + self._verify = False + self._trust_bundle = None + self._client_cert = None + self._client_key = None + self._client_key_passphrase = None + self._alpn_protocols = None + + @property + def check_hostname(self): + """ + SecureTransport cannot have its hostname checking disabled. For more, + see the comment on getpeercert() in this file. + """ + return True + + @check_hostname.setter + def check_hostname(self, value): + """ + SecureTransport cannot have its hostname checking disabled. For more, + see the comment on getpeercert() in this file. + """ + pass + + @property + def options(self): + # TODO: Well, crap. + # + # So this is the bit of the code that is the most likely to cause us + # trouble. Essentially we need to enumerate all of the SSL options that + # users might want to use and try to see if we can sensibly translate + # them, or whether we should just ignore them. + return self._options + + @options.setter + def options(self, value): + # TODO: Update in line with above. + self._options = value + + @property + def verify_mode(self): + return ssl.CERT_REQUIRED if self._verify else ssl.CERT_NONE + + @verify_mode.setter + def verify_mode(self, value): + self._verify = True if value == ssl.CERT_REQUIRED else False + + def set_default_verify_paths(self): + # So, this has to do something a bit weird. Specifically, what it does + # is nothing. + # + # This means that, if we had previously had load_verify_locations + # called, this does not undo that. We need to do that because it turns + # out that the rest of the urllib3 code will attempt to load the + # default verify paths if it hasn't been told about any paths, even if + # the context itself was sometime earlier. We resolve that by just + # ignoring it. + pass + + def load_default_certs(self): + return self.set_default_verify_paths() + + def set_ciphers(self, ciphers): + # For now, we just require the default cipher string. + if ciphers != util.ssl_.DEFAULT_CIPHERS: + raise ValueError("SecureTransport doesn't support custom cipher strings") + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + # OK, we only really support cadata and cafile. + if capath is not None: + raise ValueError("SecureTransport does not support cert directories") + + # Raise if cafile does not exist. + if cafile is not None: + with open(cafile): + pass + + self._trust_bundle = cafile or cadata + + def load_cert_chain(self, certfile, keyfile=None, password=None): + self._client_cert = certfile + self._client_key = keyfile + self._client_cert_passphrase = password + + def set_alpn_protocols(self, protocols): + """ + Sets the ALPN protocols that will later be set on the context. + + Raises a NotImplementedError if ALPN is not supported. + """ + if not hasattr(Security, "SSLSetALPNProtocols"): + raise NotImplementedError( + "SecureTransport supports ALPN only in macOS 10.12+" + ) + self._alpn_protocols = [six.ensure_binary(p) for p in protocols] + + def wrap_socket( + self, + sock, + server_side=False, + do_handshake_on_connect=True, + suppress_ragged_eofs=True, + server_hostname=None, + ): + # So, what do we do here? Firstly, we assert some properties. This is a + # stripped down shim, so there is some functionality we don't support. + # See PEP 543 for the real deal. + assert not server_side + assert do_handshake_on_connect + assert suppress_ragged_eofs + + # Ok, we're good to go. Now we want to create the wrapped socket object + # and store it in the appropriate place. + wrapped_socket = WrappedSocket(sock) + + # Now we can handshake + wrapped_socket.handshake( + server_hostname, + self._verify, + self._trust_bundle, + self._min_version, + self._max_version, + self._client_cert, + self._client_key, + self._client_key_passphrase, + self._alpn_protocols, + ) + return wrapped_socket diff --git a/openpype/vendor/python/python_2/urllib3/contrib/socks.py b/openpype/vendor/python/python_2/urllib3/contrib/socks.py new file mode 100644 index 0000000000..c326e80dd1 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/contrib/socks.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- +""" +This module contains provisional support for SOCKS proxies from within +urllib3. This module supports SOCKS4, SOCKS4A (an extension of SOCKS4), and +SOCKS5. To enable its functionality, either install PySocks or install this +module with the ``socks`` extra. + +The SOCKS implementation supports the full range of urllib3 features. It also +supports the following SOCKS features: + +- SOCKS4A (``proxy_url='socks4a://...``) +- SOCKS4 (``proxy_url='socks4://...``) +- SOCKS5 with remote DNS (``proxy_url='socks5h://...``) +- SOCKS5 with local DNS (``proxy_url='socks5://...``) +- Usernames and passwords for the SOCKS proxy + +.. note:: + It is recommended to use ``socks5h://`` or ``socks4a://`` schemes in + your ``proxy_url`` to ensure that DNS resolution is done from the remote + server instead of client-side when connecting to a domain name. + +SOCKS4 supports IPv4 and domain names with the SOCKS4A extension. SOCKS5 +supports IPv4, IPv6, and domain names. + +When connecting to a SOCKS4 proxy the ``username`` portion of the ``proxy_url`` +will be sent as the ``userid`` section of the SOCKS request: + +.. code-block:: python + + proxy_url="socks4a://@proxy-host" + +When connecting to a SOCKS5 proxy the ``username`` and ``password`` portion +of the ``proxy_url`` will be sent as the username/password to authenticate +with the proxy: + +.. code-block:: python + + proxy_url="socks5h://:@proxy-host" + +""" +from __future__ import absolute_import + +try: + import socks +except ImportError: + import warnings + + from ..exceptions import DependencyWarning + + warnings.warn( + ( + "SOCKS support in urllib3 requires the installation of optional " + "dependencies: specifically, PySocks. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/contrib.html#socks-proxies" + ), + DependencyWarning, + ) + raise + +from socket import error as SocketError +from socket import timeout as SocketTimeout + +from ..connection import HTTPConnection, HTTPSConnection +from ..connectionpool import HTTPConnectionPool, HTTPSConnectionPool +from ..exceptions import ConnectTimeoutError, NewConnectionError +from ..poolmanager import PoolManager +from ..util.url import parse_url + +try: + import ssl +except ImportError: + ssl = None + + +class SOCKSConnection(HTTPConnection): + """ + A plain-text HTTP connection that connects via a SOCKS proxy. + """ + + def __init__(self, *args, **kwargs): + self._socks_options = kwargs.pop("_socks_options") + super(SOCKSConnection, self).__init__(*args, **kwargs) + + def _new_conn(self): + """ + Establish a new connection via the SOCKS proxy. + """ + extra_kw = {} + if self.source_address: + extra_kw["source_address"] = self.source_address + + if self.socket_options: + extra_kw["socket_options"] = self.socket_options + + try: + conn = socks.create_connection( + (self.host, self.port), + proxy_type=self._socks_options["socks_version"], + proxy_addr=self._socks_options["proxy_host"], + proxy_port=self._socks_options["proxy_port"], + proxy_username=self._socks_options["username"], + proxy_password=self._socks_options["password"], + proxy_rdns=self._socks_options["rdns"], + timeout=self.timeout, + **extra_kw + ) + + except SocketTimeout: + raise ConnectTimeoutError( + self, + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) + + except socks.ProxyError as e: + # This is fragile as hell, but it seems to be the only way to raise + # useful errors here. + if e.socket_err: + error = e.socket_err + if isinstance(error, SocketTimeout): + raise ConnectTimeoutError( + self, + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) + else: + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % error + ) + else: + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e + ) + + except SocketError as e: # Defensive: PySocks should catch all these. + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e + ) + + return conn + + +# We don't need to duplicate the Verified/Unverified distinction from +# urllib3/connection.py here because the HTTPSConnection will already have been +# correctly set to either the Verified or Unverified form by that module. This +# means the SOCKSHTTPSConnection will automatically be the correct type. +class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection): + pass + + +class SOCKSHTTPConnectionPool(HTTPConnectionPool): + ConnectionCls = SOCKSConnection + + +class SOCKSHTTPSConnectionPool(HTTPSConnectionPool): + ConnectionCls = SOCKSHTTPSConnection + + +class SOCKSProxyManager(PoolManager): + """ + A version of the urllib3 ProxyManager that routes connections via the + defined SOCKS proxy. + """ + + pool_classes_by_scheme = { + "http": SOCKSHTTPConnectionPool, + "https": SOCKSHTTPSConnectionPool, + } + + def __init__( + self, + proxy_url, + username=None, + password=None, + num_pools=10, + headers=None, + **connection_pool_kw + ): + parsed = parse_url(proxy_url) + + if username is None and password is None and parsed.auth is not None: + split = parsed.auth.split(":") + if len(split) == 2: + username, password = split + if parsed.scheme == "socks5": + socks_version = socks.PROXY_TYPE_SOCKS5 + rdns = False + elif parsed.scheme == "socks5h": + socks_version = socks.PROXY_TYPE_SOCKS5 + rdns = True + elif parsed.scheme == "socks4": + socks_version = socks.PROXY_TYPE_SOCKS4 + rdns = False + elif parsed.scheme == "socks4a": + socks_version = socks.PROXY_TYPE_SOCKS4 + rdns = True + else: + raise ValueError("Unable to determine SOCKS version from %s" % proxy_url) + + self.proxy_url = proxy_url + + socks_options = { + "socks_version": socks_version, + "proxy_host": parsed.host, + "proxy_port": parsed.port, + "username": username, + "password": password, + "rdns": rdns, + } + connection_pool_kw["_socks_options"] = socks_options + + super(SOCKSProxyManager, self).__init__( + num_pools, headers, **connection_pool_kw + ) + + self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme diff --git a/openpype/vendor/python/python_2/urllib3/exceptions.py b/openpype/vendor/python/python_2/urllib3/exceptions.py new file mode 100644 index 0000000000..cba6f3f560 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/exceptions.py @@ -0,0 +1,323 @@ +from __future__ import absolute_import + +from .packages.six.moves.http_client import IncompleteRead as httplib_IncompleteRead + +# Base Exceptions + + +class HTTPError(Exception): + """Base exception used by this module.""" + + pass + + +class HTTPWarning(Warning): + """Base warning used by this module.""" + + pass + + +class PoolError(HTTPError): + """Base exception for errors caused within a pool.""" + + def __init__(self, pool, message): + self.pool = pool + HTTPError.__init__(self, "%s: %s" % (pool, message)) + + def __reduce__(self): + # For pickling purposes. + return self.__class__, (None, None) + + +class RequestError(PoolError): + """Base exception for PoolErrors that have associated URLs.""" + + def __init__(self, pool, url, message): + self.url = url + PoolError.__init__(self, pool, message) + + def __reduce__(self): + # For pickling purposes. + return self.__class__, (None, self.url, None) + + +class SSLError(HTTPError): + """Raised when SSL certificate fails in an HTTPS connection.""" + + pass + + +class ProxyError(HTTPError): + """Raised when the connection to a proxy fails.""" + + def __init__(self, message, error, *args): + super(ProxyError, self).__init__(message, error, *args) + self.original_error = error + + +class DecodeError(HTTPError): + """Raised when automatic decoding based on Content-Type fails.""" + + pass + + +class ProtocolError(HTTPError): + """Raised when something unexpected happens mid-request/response.""" + + pass + + +#: Renamed to ProtocolError but aliased for backwards compatibility. +ConnectionError = ProtocolError + + +# Leaf Exceptions + + +class MaxRetryError(RequestError): + """Raised when the maximum number of retries is exceeded. + + :param pool: The connection pool + :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool` + :param string url: The requested Url + :param exceptions.Exception reason: The underlying error + + """ + + def __init__(self, pool, url, reason=None): + self.reason = reason + + message = "Max retries exceeded with url: %s (Caused by %r)" % (url, reason) + + RequestError.__init__(self, pool, url, message) + + +class HostChangedError(RequestError): + """Raised when an existing pool gets a request for a foreign host.""" + + def __init__(self, pool, url, retries=3): + message = "Tried to open a foreign host with url: %s" % url + RequestError.__init__(self, pool, url, message) + self.retries = retries + + +class TimeoutStateError(HTTPError): + """Raised when passing an invalid state to a timeout""" + + pass + + +class TimeoutError(HTTPError): + """Raised when a socket timeout error occurs. + + Catching this error will catch both :exc:`ReadTimeoutErrors + ` and :exc:`ConnectTimeoutErrors `. + """ + + pass + + +class ReadTimeoutError(TimeoutError, RequestError): + """Raised when a socket timeout occurs while receiving data from a server""" + + pass + + +# This timeout error does not have a URL attached and needs to inherit from the +# base HTTPError +class ConnectTimeoutError(TimeoutError): + """Raised when a socket timeout occurs while connecting to a server""" + + pass + + +class NewConnectionError(ConnectTimeoutError, PoolError): + """Raised when we fail to establish a new connection. Usually ECONNREFUSED.""" + + pass + + +class EmptyPoolError(PoolError): + """Raised when a pool runs out of connections and no more are allowed.""" + + pass + + +class ClosedPoolError(PoolError): + """Raised when a request enters a pool after the pool has been closed.""" + + pass + + +class LocationValueError(ValueError, HTTPError): + """Raised when there is something wrong with a given URL input.""" + + pass + + +class LocationParseError(LocationValueError): + """Raised when get_host or similar fails to parse the URL input.""" + + def __init__(self, location): + message = "Failed to parse: %s" % location + HTTPError.__init__(self, message) + + self.location = location + + +class URLSchemeUnknown(LocationValueError): + """Raised when a URL input has an unsupported scheme.""" + + def __init__(self, scheme): + message = "Not supported URL scheme %s" % scheme + super(URLSchemeUnknown, self).__init__(message) + + self.scheme = scheme + + +class ResponseError(HTTPError): + """Used as a container for an error reason supplied in a MaxRetryError.""" + + GENERIC_ERROR = "too many error responses" + SPECIFIC_ERROR = "too many {status_code} error responses" + + +class SecurityWarning(HTTPWarning): + """Warned when performing security reducing actions""" + + pass + + +class SubjectAltNameWarning(SecurityWarning): + """Warned when connecting to a host with a certificate missing a SAN.""" + + pass + + +class InsecureRequestWarning(SecurityWarning): + """Warned when making an unverified HTTPS request.""" + + pass + + +class SystemTimeWarning(SecurityWarning): + """Warned when system time is suspected to be wrong""" + + pass + + +class InsecurePlatformWarning(SecurityWarning): + """Warned when certain TLS/SSL configuration is not available on a platform.""" + + pass + + +class SNIMissingWarning(HTTPWarning): + """Warned when making a HTTPS request without SNI available.""" + + pass + + +class DependencyWarning(HTTPWarning): + """ + Warned when an attempt is made to import a module with missing optional + dependencies. + """ + + pass + + +class ResponseNotChunked(ProtocolError, ValueError): + """Response needs to be chunked in order to read it as chunks.""" + + pass + + +class BodyNotHttplibCompatible(HTTPError): + """ + Body should be :class:`http.client.HTTPResponse` like + (have an fp attribute which returns raw chunks) for read_chunked(). + """ + + pass + + +class IncompleteRead(HTTPError, httplib_IncompleteRead): + """ + Response length doesn't match expected Content-Length + + Subclass of :class:`http.client.IncompleteRead` to allow int value + for ``partial`` to avoid creating large objects on streamed reads. + """ + + def __init__(self, partial, expected): + super(IncompleteRead, self).__init__(partial, expected) + + def __repr__(self): + return "IncompleteRead(%i bytes read, %i more expected)" % ( + self.partial, + self.expected, + ) + + +class InvalidChunkLength(HTTPError, httplib_IncompleteRead): + """Invalid chunk length in a chunked response.""" + + def __init__(self, response, length): + super(InvalidChunkLength, self).__init__( + response.tell(), response.length_remaining + ) + self.response = response + self.length = length + + def __repr__(self): + return "InvalidChunkLength(got length %r, %i bytes read)" % ( + self.length, + self.partial, + ) + + +class InvalidHeader(HTTPError): + """The header provided was somehow invalid.""" + + pass + + +class ProxySchemeUnknown(AssertionError, URLSchemeUnknown): + """ProxyManager does not support the supplied scheme""" + + # TODO(t-8ch): Stop inheriting from AssertionError in v2.0. + + def __init__(self, scheme): + # 'localhost' is here because our URL parser parses + # localhost:8080 -> scheme=localhost, remove if we fix this. + if scheme == "localhost": + scheme = None + if scheme is None: + message = "Proxy URL had no scheme, should start with http:// or https://" + else: + message = ( + "Proxy URL had unsupported scheme %s, should use http:// or https://" + % scheme + ) + super(ProxySchemeUnknown, self).__init__(message) + + +class ProxySchemeUnsupported(ValueError): + """Fetching HTTPS resources through HTTPS proxies is unsupported""" + + pass + + +class HeaderParsingError(HTTPError): + """Raised by assert_header_parsing, but we convert it to a log.warning statement.""" + + def __init__(self, defects, unparsed_data): + message = "%s, unparsed data: %r" % (defects or "Unknown", unparsed_data) + super(HeaderParsingError, self).__init__(message) + + +class UnrewindableBodyError(HTTPError): + """urllib3 encountered an error when trying to rewind a body""" + + pass diff --git a/openpype/vendor/python/python_2/urllib3/fields.py b/openpype/vendor/python/python_2/urllib3/fields.py new file mode 100644 index 0000000000..9d630f491d --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/fields.py @@ -0,0 +1,274 @@ +from __future__ import absolute_import + +import email.utils +import mimetypes +import re + +from .packages import six + + +def guess_content_type(filename, default="application/octet-stream"): + """ + Guess the "Content-Type" of a file. + + :param filename: + The filename to guess the "Content-Type" of using :mod:`mimetypes`. + :param default: + If no "Content-Type" can be guessed, default to `default`. + """ + if filename: + return mimetypes.guess_type(filename)[0] or default + return default + + +def format_header_param_rfc2231(name, value): + """ + Helper function to format and quote a single header parameter using the + strategy defined in RFC 2231. + + Particularly useful for header parameters which might contain + non-ASCII values, like file names. This follows + `RFC 2388 Section 4.4 `_. + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as ``bytes`` or `str``. + :ret: + An RFC-2231-formatted unicode string. + """ + if isinstance(value, six.binary_type): + value = value.decode("utf-8") + + if not any(ch in value for ch in '"\\\r\n'): + result = u'%s="%s"' % (name, value) + try: + result.encode("ascii") + except (UnicodeEncodeError, UnicodeDecodeError): + pass + else: + return result + + if six.PY2: # Python 2: + value = value.encode("utf-8") + + # encode_rfc2231 accepts an encoded string and returns an ascii-encoded + # string in Python 2 but accepts and returns unicode strings in Python 3 + value = email.utils.encode_rfc2231(value, "utf-8") + value = "%s*=%s" % (name, value) + + if six.PY2: # Python 2: + value = value.decode("utf-8") + + return value + + +_HTML5_REPLACEMENTS = { + u"\u0022": u"%22", + # Replace "\" with "\\". + u"\u005C": u"\u005C\u005C", +} + +# All control characters from 0x00 to 0x1F *except* 0x1B. +_HTML5_REPLACEMENTS.update( + { + six.unichr(cc): u"%{:02X}".format(cc) + for cc in range(0x00, 0x1F + 1) + if cc not in (0x1B,) + } +) + + +def _replace_multiple(value, needles_and_replacements): + def replacer(match): + return needles_and_replacements[match.group(0)] + + pattern = re.compile( + r"|".join([re.escape(needle) for needle in needles_and_replacements.keys()]) + ) + + result = pattern.sub(replacer, value) + + return result + + +def format_header_param_html5(name, value): + """ + Helper function to format and quote a single header parameter using the + HTML5 strategy. + + Particularly useful for header parameters which might contain + non-ASCII values, like file names. This follows the `HTML5 Working Draft + Section 4.10.22.7`_ and matches the behavior of curl and modern browsers. + + .. _HTML5 Working Draft Section 4.10.22.7: + https://w3c.github.io/html/sec-forms.html#multipart-form-data + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as ``bytes`` or `str``. + :ret: + A unicode string, stripped of troublesome characters. + """ + if isinstance(value, six.binary_type): + value = value.decode("utf-8") + + value = _replace_multiple(value, _HTML5_REPLACEMENTS) + + return u'%s="%s"' % (name, value) + + +# For backwards-compatibility. +format_header_param = format_header_param_html5 + + +class RequestField(object): + """ + A data container for request body parameters. + + :param name: + The name of this request field. Must be unicode. + :param data: + The data/value body. + :param filename: + An optional filename of the request field. Must be unicode. + :param headers: + An optional dict-like object of headers to initially use for the field. + :param header_formatter: + An optional callable that is used to encode and format the headers. By + default, this is :func:`format_header_param_html5`. + """ + + def __init__( + self, + name, + data, + filename=None, + headers=None, + header_formatter=format_header_param_html5, + ): + self._name = name + self._filename = filename + self.data = data + self.headers = {} + if headers: + self.headers = dict(headers) + self.header_formatter = header_formatter + + @classmethod + def from_tuples(cls, fieldname, value, header_formatter=format_header_param_html5): + """ + A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. + + Supports constructing :class:`~urllib3.fields.RequestField` from + parameter of key/value strings AND key/filetuple. A filetuple is a + (filename, data, MIME type) tuple where the MIME type is optional. + For example:: + + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + + Field names and filenames must be unicode. + """ + if isinstance(value, tuple): + if len(value) == 3: + filename, data, content_type = value + else: + filename, data = value + content_type = guess_content_type(filename) + else: + filename = None + content_type = None + data = value + + request_param = cls( + fieldname, data, filename=filename, header_formatter=header_formatter + ) + request_param.make_multipart(content_type=content_type) + + return request_param + + def _render_part(self, name, value): + """ + Overridable helper function to format a single header parameter. By + default, this calls ``self.header_formatter``. + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as a unicode string. + """ + + return self.header_formatter(name, value) + + def _render_parts(self, header_parts): + """ + Helper function to format and quote a single header. + + Useful for single headers that are composed of multiple items. E.g., + 'Content-Disposition' fields. + + :param header_parts: + A sequence of (k, v) tuples or a :class:`dict` of (k, v) to format + as `k1="v1"; k2="v2"; ...`. + """ + parts = [] + iterable = header_parts + if isinstance(header_parts, dict): + iterable = header_parts.items() + + for name, value in iterable: + if value is not None: + parts.append(self._render_part(name, value)) + + return u"; ".join(parts) + + def render_headers(self): + """ + Renders the headers for this request field. + """ + lines = [] + + sort_keys = ["Content-Disposition", "Content-Type", "Content-Location"] + for sort_key in sort_keys: + if self.headers.get(sort_key, False): + lines.append(u"%s: %s" % (sort_key, self.headers[sort_key])) + + for header_name, header_value in self.headers.items(): + if header_name not in sort_keys: + if header_value: + lines.append(u"%s: %s" % (header_name, header_value)) + + lines.append(u"\r\n") + return u"\r\n".join(lines) + + def make_multipart( + self, content_disposition=None, content_type=None, content_location=None + ): + """ + Makes this request field into a multipart request field. + + This method overrides "Content-Disposition", "Content-Type" and + "Content-Location" headers to the request parameter. + + :param content_type: + The 'Content-Type' of the request body. + :param content_location: + The 'Content-Location' of the request body. + + """ + self.headers["Content-Disposition"] = content_disposition or u"form-data" + self.headers["Content-Disposition"] += u"; ".join( + [ + u"", + self._render_parts( + ((u"name", self._name), (u"filename", self._filename)) + ), + ] + ) + self.headers["Content-Type"] = content_type + self.headers["Content-Location"] = content_location diff --git a/openpype/vendor/python/python_2/urllib3/filepost.py b/openpype/vendor/python/python_2/urllib3/filepost.py new file mode 100644 index 0000000000..36c9252c64 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/filepost.py @@ -0,0 +1,98 @@ +from __future__ import absolute_import + +import binascii +import codecs +import os +from io import BytesIO + +from .fields import RequestField +from .packages import six +from .packages.six import b + +writer = codecs.lookup("utf-8")[3] + + +def choose_boundary(): + """ + Our embarrassingly-simple replacement for mimetools.choose_boundary. + """ + boundary = binascii.hexlify(os.urandom(16)) + if not six.PY2: + boundary = boundary.decode("ascii") + return boundary + + +def iter_field_objects(fields): + """ + Iterate over fields. + + Supports list of (k, v) tuples and dicts, and lists of + :class:`~urllib3.fields.RequestField`. + + """ + if isinstance(fields, dict): + i = six.iteritems(fields) + else: + i = iter(fields) + + for field in i: + if isinstance(field, RequestField): + yield field + else: + yield RequestField.from_tuples(*field) + + +def iter_fields(fields): + """ + .. deprecated:: 1.6 + + Iterate over fields. + + The addition of :class:`~urllib3.fields.RequestField` makes this function + obsolete. Instead, use :func:`iter_field_objects`, which returns + :class:`~urllib3.fields.RequestField` objects. + + Supports list of (k, v) tuples and dicts. + """ + if isinstance(fields, dict): + return ((k, v) for k, v in six.iteritems(fields)) + + return ((k, v) for k, v in fields) + + +def encode_multipart_formdata(fields, boundary=None): + """ + Encode a dictionary of ``fields`` using the multipart/form-data MIME format. + + :param fields: + Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`). + + :param boundary: + If not specified, then a random boundary will be generated using + :func:`urllib3.filepost.choose_boundary`. + """ + body = BytesIO() + if boundary is None: + boundary = choose_boundary() + + for field in iter_field_objects(fields): + body.write(b("--%s\r\n" % (boundary))) + + writer(body).write(field.render_headers()) + data = field.data + + if isinstance(data, int): + data = str(data) # Backwards compatibility + + if isinstance(data, six.text_type): + writer(body).write(data) + else: + body.write(data) + + body.write(b"\r\n") + + body.write(b("--%s--\r\n" % (boundary))) + + content_type = str("multipart/form-data; boundary=%s" % boundary) + + return body.getvalue(), content_type diff --git a/openpype/vendor/python/python_2/urllib3/packages/__init__.py b/openpype/vendor/python/python_2/urllib3/packages/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/vendor/python/python_2/urllib3/packages/backports/__init__.py b/openpype/vendor/python/python_2/urllib3/packages/backports/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/vendor/python/python_2/urllib3/packages/backports/makefile.py b/openpype/vendor/python/python_2/urllib3/packages/backports/makefile.py new file mode 100644 index 0000000000..b8fb2154b6 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/packages/backports/makefile.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +""" +backports.makefile +~~~~~~~~~~~~~~~~~~ + +Backports the Python 3 ``socket.makefile`` method for use with anything that +wants to create a "fake" socket object. +""" +import io +from socket import SocketIO + + +def backport_makefile( + self, mode="r", buffering=None, encoding=None, errors=None, newline=None +): + """ + Backport of ``socket.makefile`` from Python 3.5. + """ + if not set(mode) <= {"r", "w", "b"}: + raise ValueError("invalid mode %r (only r, w, b allowed)" % (mode,)) + writing = "w" in mode + reading = "r" in mode or not writing + assert reading or writing + binary = "b" in mode + rawmode = "" + if reading: + rawmode += "r" + if writing: + rawmode += "w" + raw = SocketIO(self, rawmode) + self._makefile_refs += 1 + if buffering is None: + buffering = -1 + if buffering < 0: + buffering = io.DEFAULT_BUFFER_SIZE + if buffering == 0: + if not binary: + raise ValueError("unbuffered streams must be binary") + return raw + if reading and writing: + buffer = io.BufferedRWPair(raw, raw, buffering) + elif reading: + buffer = io.BufferedReader(raw, buffering) + else: + assert writing + buffer = io.BufferedWriter(raw, buffering) + if binary: + return buffer + text = io.TextIOWrapper(buffer, encoding, errors, newline) + text.mode = mode + return text diff --git a/openpype/vendor/python/python_2/urllib3/packages/six.py b/openpype/vendor/python/python_2/urllib3/packages/six.py new file mode 100644 index 0000000000..ba50acb062 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/packages/six.py @@ -0,0 +1,1077 @@ +# Copyright (c) 2010-2020 Benjamin Peterson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Utilities for writing code that runs on Python 2 and 3""" + +from __future__ import absolute_import + +import functools +import itertools +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.16.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 +PY34 = sys.version_info[0:2] >= (3, 4) + +if PY3: + string_types = (str,) + integer_types = (int,) + class_types = (type,) + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = (basestring,) + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + def __len__(self): + return 1 << 31 + + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + +if PY34: + from importlib.util import spec_from_loader +else: + spec_from_loader = None + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) # Invokes __set__. + try: + # This is a bit ugly, but it avoids running this again by + # removing this descriptor. + delattr(obj.__class__, self.name) + except AttributeError: + pass + return result + + +class MovedModule(_LazyDescr): + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + def __getattr__(self, attr): + _module = self._resolve() + value = getattr(_module, attr) + setattr(self, attr, value) + return value + + +class _LazyModule(types.ModuleType): + def __init__(self, name): + super(_LazyModule, self).__init__(name) + self.__doc__ = self.__class__.__doc__ + + def __dir__(self): + attrs = ["__doc__", "__name__"] + attrs += [attr.name for attr in self._moved_attributes] + return attrs + + # Subclasses should override this + _moved_attributes = [] + + +class MovedAttribute(_LazyDescr): + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + +class _SixMetaPathImporter(object): + + """ + A meta path importer to import six.moves and its submodules. + + This class implements a PEP302 finder and loader. It should be compatible + with Python 2.5 and all existing versions of Python3 + """ + + def __init__(self, six_module_name): + self.name = six_module_name + self.known_modules = {} + + def _add_module(self, mod, *fullnames): + for fullname in fullnames: + self.known_modules[self.name + "." + fullname] = mod + + def _get_module(self, fullname): + return self.known_modules[self.name + "." + fullname] + + def find_module(self, fullname, path=None): + if fullname in self.known_modules: + return self + return None + + def find_spec(self, fullname, path, target=None): + if fullname in self.known_modules: + return spec_from_loader(fullname, self) + return None + + def __get_module(self, fullname): + try: + return self.known_modules[fullname] + except KeyError: + raise ImportError("This loader does not know module " + fullname) + + def load_module(self, fullname): + try: + # in case of a reload + return sys.modules[fullname] + except KeyError: + pass + mod = self.__get_module(fullname) + if isinstance(mod, MovedModule): + mod = mod._resolve() + else: + mod.__loader__ = self + sys.modules[fullname] = mod + return mod + + def is_package(self, fullname): + """ + Return true, if the named module is a package. + + We need this method to get correct spec objects with + Python 3.4 (see PEP451) + """ + return hasattr(self.__get_module(fullname), "__path__") + + def get_code(self, fullname): + """Return None + + Required, if is_package is implemented""" + self.__get_module(fullname) # eventually raises ImportError + return None + + get_source = get_code # same as get_code + + def create_module(self, spec): + return self.load_module(spec.name) + + def exec_module(self, module): + pass + + +_importer = _SixMetaPathImporter(__name__) + + +class _MovedItems(_LazyModule): + + """Lazy loading of moved objects""" + + __path__ = [] # mark as package + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute( + "filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse" + ), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("intern", "__builtin__", "sys"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), + MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("getoutput", "commands", "subprocess"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute( + "reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload" + ), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("UserDict", "UserDict", "collections"), + MovedAttribute("UserList", "UserList", "collections"), + MovedAttribute("UserString", "UserString", "collections"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + MovedAttribute( + "zip_longest", "itertools", "itertools", "izip_longest", "zip_longest" + ), + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule( + "collections_abc", + "collections", + "collections.abc" if sys.version_info >= (3, 3) else "collections", + ), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"), + MovedModule( + "_dummy_thread", + "dummy_thread", + "_dummy_thread" if sys.version_info < (3, 9) else "_thread", + ), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule( + "email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart" + ), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("_thread", "thread", "_thread"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", "tkinter.simpledialog"), + MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), + MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), + MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), + MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), +] +# Add windows specific modules. +if sys.platform == "win32": + _moved_attributes += [ + MovedModule("winreg", "_winreg"), + ] + +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) + if isinstance(attr, MovedModule): + _importer._add_module(attr, "moves." + attr.name) +del attr + +_MovedItems._moved_attributes = _moved_attributes + +moves = _MovedItems(__name__ + ".moves") +_importer._add_module(moves, "moves") + + +class Module_six_moves_urllib_parse(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_parse""" + + +_urllib_parse_moved_attributes = [ + MovedAttribute("ParseResult", "urlparse", "urllib.parse"), + MovedAttribute("SplitResult", "urlparse", "urllib.parse"), + MovedAttribute("parse_qs", "urlparse", "urllib.parse"), + MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), + MovedAttribute("urldefrag", "urlparse", "urllib.parse"), + MovedAttribute("urljoin", "urlparse", "urllib.parse"), + MovedAttribute("urlparse", "urlparse", "urllib.parse"), + MovedAttribute("urlsplit", "urlparse", "urllib.parse"), + MovedAttribute("urlunparse", "urlparse", "urllib.parse"), + MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), + MovedAttribute("quote", "urllib", "urllib.parse"), + MovedAttribute("quote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote", "urllib", "urllib.parse"), + MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute( + "unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes" + ), + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("splitvalue", "urllib", "urllib.parse"), + MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), + MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), + MovedAttribute("uses_params", "urlparse", "urllib.parse"), + MovedAttribute("uses_query", "urlparse", "urllib.parse"), + MovedAttribute("uses_relative", "urlparse", "urllib.parse"), +] +for attr in _urllib_parse_moved_attributes: + setattr(Module_six_moves_urllib_parse, attr.name, attr) +del attr + +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes + +_importer._add_module( + Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", + "moves.urllib.parse", +) + + +class Module_six_moves_urllib_error(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_error""" + + +_urllib_error_moved_attributes = [ + MovedAttribute("URLError", "urllib2", "urllib.error"), + MovedAttribute("HTTPError", "urllib2", "urllib.error"), + MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), +] +for attr in _urllib_error_moved_attributes: + setattr(Module_six_moves_urllib_error, attr.name, attr) +del attr + +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes + +_importer._add_module( + Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", + "moves.urllib.error", +) + + +class Module_six_moves_urllib_request(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_request""" + + +_urllib_request_moved_attributes = [ + MovedAttribute("urlopen", "urllib2", "urllib.request"), + MovedAttribute("install_opener", "urllib2", "urllib.request"), + MovedAttribute("build_opener", "urllib2", "urllib.request"), + MovedAttribute("pathname2url", "urllib", "urllib.request"), + MovedAttribute("url2pathname", "urllib", "urllib.request"), + MovedAttribute("getproxies", "urllib", "urllib.request"), + MovedAttribute("Request", "urllib2", "urllib.request"), + MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), + MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), + MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), + MovedAttribute("BaseHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), + MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), + MovedAttribute("FileHandler", "urllib2", "urllib.request"), + MovedAttribute("FTPHandler", "urllib2", "urllib.request"), + MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), + MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), + MovedAttribute("urlretrieve", "urllib", "urllib.request"), + MovedAttribute("urlcleanup", "urllib", "urllib.request"), + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + MovedAttribute("proxy_bypass", "urllib", "urllib.request"), + MovedAttribute("parse_http_list", "urllib2", "urllib.request"), + MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"), +] +for attr in _urllib_request_moved_attributes: + setattr(Module_six_moves_urllib_request, attr.name, attr) +del attr + +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes + +_importer._add_module( + Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", + "moves.urllib.request", +) + + +class Module_six_moves_urllib_response(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_response""" + + +_urllib_response_moved_attributes = [ + MovedAttribute("addbase", "urllib", "urllib.response"), + MovedAttribute("addclosehook", "urllib", "urllib.response"), + MovedAttribute("addinfo", "urllib", "urllib.response"), + MovedAttribute("addinfourl", "urllib", "urllib.response"), +] +for attr in _urllib_response_moved_attributes: + setattr(Module_six_moves_urllib_response, attr.name, attr) +del attr + +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes + +_importer._add_module( + Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", + "moves.urllib.response", +) + + +class Module_six_moves_urllib_robotparser(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_robotparser""" + + +_urllib_robotparser_moved_attributes = [ + MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), +] +for attr in _urllib_robotparser_moved_attributes: + setattr(Module_six_moves_urllib_robotparser, attr.name, attr) +del attr + +Module_six_moves_urllib_robotparser._moved_attributes = ( + _urllib_robotparser_moved_attributes +) + +_importer._add_module( + Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", + "moves.urllib.robotparser", +) + + +class Module_six_moves_urllib(types.ModuleType): + + """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + + __path__ = [] # mark as package + parse = _importer._get_module("moves.urllib_parse") + error = _importer._get_module("moves.urllib_error") + request = _importer._get_module("moves.urllib_request") + response = _importer._get_module("moves.urllib_response") + robotparser = _importer._get_module("moves.urllib_robotparser") + + def __dir__(self): + return ["parse", "error", "request", "response", "robotparser"] + + +_importer._add_module( + Module_six_moves_urllib(__name__ + ".moves.urllib"), "moves.urllib" +) + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_closure = "__closure__" + _func_code = "__code__" + _func_defaults = "__defaults__" + _func_globals = "__globals__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_closure = "func_closure" + _func_code = "func_code" + _func_defaults = "func_defaults" + _func_globals = "func_globals" + + +try: + advance_iterator = next +except NameError: + + def advance_iterator(it): + return it.next() + + +next = advance_iterator + + +try: + callable = callable +except NameError: + + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + def create_unbound_method(func, cls): + return func + + Iterator = object +else: + + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + def create_unbound_method(func, cls): + return types.MethodType(func, None, cls) + + class Iterator(object): + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc( + get_unbound_function, """Get the function out of a possibly unbound function""" +) + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_closure = operator.attrgetter(_func_closure) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) +get_function_globals = operator.attrgetter(_func_globals) + + +if PY3: + + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) + + viewkeys = operator.methodcaller("keys") + + viewvalues = operator.methodcaller("values") + + viewitems = operator.methodcaller("items") +else: + + def iterkeys(d, **kw): + return d.iterkeys(**kw) + + def itervalues(d, **kw): + return d.itervalues(**kw) + + def iteritems(d, **kw): + return d.iteritems(**kw) + + def iterlists(d, **kw): + return d.iterlists(**kw) + + viewkeys = operator.methodcaller("viewkeys") + + viewvalues = operator.methodcaller("viewvalues") + + viewitems = operator.methodcaller("viewitems") + +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") +_add_doc(itervalues, "Return an iterator over the values of a dictionary.") +_add_doc(iteritems, "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc( + iterlists, "Return an iterator over the (key, [values]) pairs of a dictionary." +) + + +if PY3: + + def b(s): + return s.encode("latin-1") + + def u(s): + return s + + unichr = chr + import struct + + int2byte = struct.Struct(">B").pack + del struct + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + + StringIO = io.StringIO + BytesIO = io.BytesIO + del io + _assertCountEqual = "assertCountEqual" + if sys.version_info[1] <= 1: + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" + else: + _assertRaisesRegex = "assertRaisesRegex" + _assertRegex = "assertRegex" + _assertNotRegex = "assertNotRegex" +else: + + def b(s): + return s + + # Workaround for standalone backslash + + def u(s): + return unicode(s.replace(r"\\", r"\\\\"), "unicode_escape") + + unichr = unichr + int2byte = chr + + def byte2int(bs): + return ord(bs[0]) + + def indexbytes(buf, i): + return ord(buf[i]) + + iterbytes = functools.partial(itertools.imap, ord) + import StringIO + + StringIO = BytesIO = StringIO.StringIO + _assertCountEqual = "assertItemsEqual" + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +def assertCountEqual(self, *args, **kwargs): + return getattr(self, _assertCountEqual)(*args, **kwargs) + + +def assertRaisesRegex(self, *args, **kwargs): + return getattr(self, _assertRaisesRegex)(*args, **kwargs) + + +def assertRegex(self, *args, **kwargs): + return getattr(self, _assertRegex)(*args, **kwargs) + + +def assertNotRegex(self, *args, **kwargs): + return getattr(self, _assertNotRegex)(*args, **kwargs) + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + def reraise(tp, value, tb=None): + try: + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None + tb = None + + +else: + + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec ("""exec _code_ in _globs_, _locs_""") + + exec_( + """def reraise(tp, value, tb=None): + try: + raise tp, value, tb + finally: + tb = None +""" + ) + + +if sys.version_info[:2] > (3,): + exec_( + """def raise_from(value, from_value): + try: + raise value from from_value + finally: + value = None +""" + ) +else: + + def raise_from(value, from_value): + raise value + + +print_ = getattr(moves.builtins, "print", None) +if print_ is None: + + def print_(*args, **kwargs): + """The new-style print function for Python 2.4 and 2.5.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + + def write(data): + if not isinstance(data, basestring): + data = str(data) + # If the file has an encoding, encode unicode with it. + if ( + isinstance(fp, file) + and isinstance(data, unicode) + and fp.encoding is not None + ): + errors = getattr(fp, "errors", None) + if errors is None: + errors = "strict" + data = data.encode(fp.encoding, errors) + fp.write(data) + + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) + + +if sys.version_info[:2] < (3, 3): + _print = print_ + + def print_(*args, **kwargs): + fp = kwargs.get("file", sys.stdout) + flush = kwargs.pop("flush", False) + _print(*args, **kwargs) + if flush and fp is not None: + fp.flush() + + +_add_doc(reraise, """Reraise an exception.""") + +if sys.version_info[0:2] < (3, 4): + # This does exactly the same what the :func:`py3:functools.update_wrapper` + # function does on Python versions after 3.2. It sets the ``__wrapped__`` + # attribute on ``wrapper`` object and it doesn't raise an error if any of + # the attributes mentioned in ``assigned`` and ``updated`` are missing on + # ``wrapped`` object. + def _update_wrapper( + wrapper, + wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES, + ): + for attr in assigned: + try: + value = getattr(wrapped, attr) + except AttributeError: + continue + else: + setattr(wrapper, attr, value) + for attr in updated: + getattr(wrapper, attr).update(getattr(wrapped, attr, {})) + wrapper.__wrapped__ = wrapped + return wrapper + + _update_wrapper.__doc__ = functools.update_wrapper.__doc__ + + def wraps( + wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES, + ): + return functools.partial( + _update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated + ) + + wraps.__doc__ = functools.wraps.__doc__ + +else: + wraps = functools.wraps + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(type): + def __new__(cls, name, this_bases, d): + if sys.version_info[:2] >= (3, 7): + # This version introduced PEP 560 that requires a bit + # of extra care (we mimic what is done by __build_class__). + resolved_bases = types.resolve_bases(bases) + if resolved_bases is not bases: + d["__orig_bases__"] = bases + else: + resolved_bases = bases + return meta(name, resolved_bases, d) + + @classmethod + def __prepare__(cls, name, this_bases): + return meta.__prepare__(name, bases) + + return type.__new__(metaclass, "temporary_class", (), {}) + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get("__slots__") + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop("__dict__", None) + orig_vars.pop("__weakref__", None) + if hasattr(cls, "__qualname__"): + orig_vars["__qualname__"] = cls.__qualname__ + return metaclass(cls.__name__, cls.__bases__, orig_vars) + + return wrapper + + +def ensure_binary(s, encoding="utf-8", errors="strict"): + """Coerce **s** to six.binary_type. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> encoded to `bytes` + - `bytes` -> `bytes` + """ + if isinstance(s, binary_type): + return s + if isinstance(s, text_type): + return s.encode(encoding, errors) + raise TypeError("not expecting type '%s'" % type(s)) + + +def ensure_str(s, encoding="utf-8", errors="strict"): + """Coerce *s* to `str`. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + # Optimization: Fast return for the common case. + if type(s) is str: + return s + if PY2 and isinstance(s, text_type): + return s.encode(encoding, errors) + elif PY3 and isinstance(s, binary_type): + return s.decode(encoding, errors) + elif not isinstance(s, (text_type, binary_type)): + raise TypeError("not expecting type '%s'" % type(s)) + return s + + +def ensure_text(s, encoding="utf-8", errors="strict"): + """Coerce *s* to six.text_type. + + For Python 2: + - `unicode` -> `unicode` + - `str` -> `unicode` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + if isinstance(s, binary_type): + return s.decode(encoding, errors) + elif isinstance(s, text_type): + return s + else: + raise TypeError("not expecting type '%s'" % type(s)) + + +def python_2_unicode_compatible(klass): + """ + A class decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if PY2: + if "__str__" not in klass.__dict__: + raise ValueError( + "@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % klass.__name__ + ) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode("utf-8") + return klass + + +# Complete the moves implementation. +# This code is at the end of this module to speed up module loading. +# Turn this module into a package. +__path__ = [] # required for PEP 302 and PEP 451 +__package__ = __name__ # see PEP 366 @ReservedAssignment +if globals().get("__spec__") is not None: + __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable +# Remove other six meta path importers, since they cause problems. This can +# happen if six is removed from sys.modules and then reloaded. (Setuptools does +# this for some reason.) +if sys.meta_path: + for i, importer in enumerate(sys.meta_path): + # Here's some real nastiness: Another "instance" of the six module might + # be floating around. Therefore, we can't use isinstance() to check for + # the six meta path importer, since the other six instance will have + # inserted an importer with different class. + if ( + type(importer).__name__ == "_SixMetaPathImporter" + and importer.name == __name__ + ): + del sys.meta_path[i] + break + del i, importer +# Finally, add the importer to the meta path import hook. +sys.meta_path.append(_importer) diff --git a/openpype/vendor/python/python_2/urllib3/poolmanager.py b/openpype/vendor/python/python_2/urllib3/poolmanager.py new file mode 100644 index 0000000000..ca4ec34118 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/poolmanager.py @@ -0,0 +1,537 @@ +from __future__ import absolute_import + +import collections +import functools +import logging + +from ._collections import RecentlyUsedContainer +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, port_by_scheme +from .exceptions import ( + LocationValueError, + MaxRetryError, + ProxySchemeUnknown, + ProxySchemeUnsupported, + URLSchemeUnknown, +) +from .packages import six +from .packages.six.moves.urllib.parse import urljoin +from .request import RequestMethods +from .util.proxy import connection_requires_http_tunnel +from .util.retry import Retry +from .util.url import parse_url + +__all__ = ["PoolManager", "ProxyManager", "proxy_from_url"] + + +log = logging.getLogger(__name__) + +SSL_KEYWORDS = ( + "key_file", + "cert_file", + "cert_reqs", + "ca_certs", + "ssl_version", + "ca_cert_dir", + "ssl_context", + "key_password", + "server_hostname", +) + +# All known keyword arguments that could be provided to the pool manager, its +# pools, or the underlying connections. This is used to construct a pool key. +_key_fields = ( + "key_scheme", # str + "key_host", # str + "key_port", # int + "key_timeout", # int or float or Timeout + "key_retries", # int or Retry + "key_strict", # bool + "key_block", # bool + "key_source_address", # str + "key_key_file", # str + "key_key_password", # str + "key_cert_file", # str + "key_cert_reqs", # str + "key_ca_certs", # str + "key_ssl_version", # str + "key_ca_cert_dir", # str + "key_ssl_context", # instance of ssl.SSLContext or urllib3.util.ssl_.SSLContext + "key_maxsize", # int + "key_headers", # dict + "key__proxy", # parsed proxy url + "key__proxy_headers", # dict + "key__proxy_config", # class + "key_socket_options", # list of (level (int), optname (int), value (int or str)) tuples + "key__socks_options", # dict + "key_assert_hostname", # bool or string + "key_assert_fingerprint", # str + "key_server_hostname", # str +) + +#: The namedtuple class used to construct keys for the connection pool. +#: All custom key schemes should include the fields in this key at a minimum. +PoolKey = collections.namedtuple("PoolKey", _key_fields) + +_proxy_config_fields = ("ssl_context", "use_forwarding_for_https") +ProxyConfig = collections.namedtuple("ProxyConfig", _proxy_config_fields) + + +def _default_key_normalizer(key_class, request_context): + """ + Create a pool key out of a request context dictionary. + + According to RFC 3986, both the scheme and host are case-insensitive. + Therefore, this function normalizes both before constructing the pool + key for an HTTPS request. If you wish to change this behaviour, provide + alternate callables to ``key_fn_by_scheme``. + + :param key_class: + The class to use when constructing the key. This should be a namedtuple + with the ``scheme`` and ``host`` keys at a minimum. + :type key_class: namedtuple + :param request_context: + A dictionary-like object that contain the context for a request. + :type request_context: dict + + :return: A namedtuple that can be used as a connection pool key. + :rtype: PoolKey + """ + # Since we mutate the dictionary, make a copy first + context = request_context.copy() + context["scheme"] = context["scheme"].lower() + context["host"] = context["host"].lower() + + # These are both dictionaries and need to be transformed into frozensets + for key in ("headers", "_proxy_headers", "_socks_options"): + if key in context and context[key] is not None: + context[key] = frozenset(context[key].items()) + + # The socket_options key may be a list and needs to be transformed into a + # tuple. + socket_opts = context.get("socket_options") + if socket_opts is not None: + context["socket_options"] = tuple(socket_opts) + + # Map the kwargs to the names in the namedtuple - this is necessary since + # namedtuples can't have fields starting with '_'. + for key in list(context.keys()): + context["key_" + key] = context.pop(key) + + # Default to ``None`` for keys missing from the context + for field in key_class._fields: + if field not in context: + context[field] = None + + return key_class(**context) + + +#: A dictionary that maps a scheme to a callable that creates a pool key. +#: This can be used to alter the way pool keys are constructed, if desired. +#: Each PoolManager makes a copy of this dictionary so they can be configured +#: globally here, or individually on the instance. +key_fn_by_scheme = { + "http": functools.partial(_default_key_normalizer, PoolKey), + "https": functools.partial(_default_key_normalizer, PoolKey), +} + +pool_classes_by_scheme = {"http": HTTPConnectionPool, "https": HTTPSConnectionPool} + + +class PoolManager(RequestMethods): + """ + Allows for arbitrary requests while transparently keeping track of + necessary connection pools for you. + + :param num_pools: + Number of connection pools to cache before discarding the least + recently used pool. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param \\**connection_pool_kw: + Additional parameters are used to create fresh + :class:`urllib3.connectionpool.ConnectionPool` instances. + + Example:: + + >>> manager = PoolManager(num_pools=2) + >>> r = manager.request('GET', 'http://google.com/') + >>> r = manager.request('GET', 'http://google.com/mail') + >>> r = manager.request('GET', 'http://yahoo.com/') + >>> len(manager.pools) + 2 + + """ + + proxy = None + proxy_config = None + + def __init__(self, num_pools=10, headers=None, **connection_pool_kw): + RequestMethods.__init__(self, headers) + self.connection_pool_kw = connection_pool_kw + self.pools = RecentlyUsedContainer(num_pools, dispose_func=lambda p: p.close()) + + # Locally set the pool classes and keys so other PoolManagers can + # override them. + self.pool_classes_by_scheme = pool_classes_by_scheme + self.key_fn_by_scheme = key_fn_by_scheme.copy() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.clear() + # Return False to re-raise any potential exceptions + return False + + def _new_pool(self, scheme, host, port, request_context=None): + """ + Create a new :class:`urllib3.connectionpool.ConnectionPool` based on host, port, scheme, and + any additional pool keyword arguments. + + If ``request_context`` is provided, it is provided as keyword arguments + to the pool class used. This method is used to actually create the + connection pools handed out by :meth:`connection_from_url` and + companion methods. It is intended to be overridden for customization. + """ + pool_cls = self.pool_classes_by_scheme[scheme] + if request_context is None: + request_context = self.connection_pool_kw.copy() + + # Although the context has everything necessary to create the pool, + # this function has historically only used the scheme, host, and port + # in the positional args. When an API change is acceptable these can + # be removed. + for key in ("scheme", "host", "port"): + request_context.pop(key, None) + + if scheme == "http": + for kw in SSL_KEYWORDS: + request_context.pop(kw, None) + + return pool_cls(host, port, **request_context) + + def clear(self): + """ + Empty our store of pools and direct them all to close. + + This will not affect in-flight connections, but they will not be + re-used after completion. + """ + self.pools.clear() + + def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None): + """ + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the host, port, and scheme. + + If ``port`` isn't given, it will be derived from the ``scheme`` using + ``urllib3.connectionpool.port_by_scheme``. If ``pool_kwargs`` is + provided, it is merged with the instance's ``connection_pool_kw`` + variable and used to create the new connection pool, if one is + needed. + """ + + if not host: + raise LocationValueError("No host specified.") + + request_context = self._merge_pool_kwargs(pool_kwargs) + request_context["scheme"] = scheme or "http" + if not port: + port = port_by_scheme.get(request_context["scheme"].lower(), 80) + request_context["port"] = port + request_context["host"] = host + + return self.connection_from_context(request_context) + + def connection_from_context(self, request_context): + """ + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the request context. + + ``request_context`` must at least contain the ``scheme`` key and its + value must be a key in ``key_fn_by_scheme`` instance variable. + """ + scheme = request_context["scheme"].lower() + pool_key_constructor = self.key_fn_by_scheme.get(scheme) + if not pool_key_constructor: + raise URLSchemeUnknown(scheme) + pool_key = pool_key_constructor(request_context) + + return self.connection_from_pool_key(pool_key, request_context=request_context) + + def connection_from_pool_key(self, pool_key, request_context=None): + """ + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the provided pool key. + + ``pool_key`` should be a namedtuple that only contains immutable + objects. At a minimum it must have the ``scheme``, ``host``, and + ``port`` fields. + """ + with self.pools.lock: + # If the scheme, host, or port doesn't match existing open + # connections, open a new ConnectionPool. + pool = self.pools.get(pool_key) + if pool: + return pool + + # Make a fresh ConnectionPool of the desired type + scheme = request_context["scheme"] + host = request_context["host"] + port = request_context["port"] + pool = self._new_pool(scheme, host, port, request_context=request_context) + self.pools[pool_key] = pool + + return pool + + def connection_from_url(self, url, pool_kwargs=None): + """ + Similar to :func:`urllib3.connectionpool.connection_from_url`. + + If ``pool_kwargs`` is not provided and a new pool needs to be + constructed, ``self.connection_pool_kw`` is used to initialize + the :class:`urllib3.connectionpool.ConnectionPool`. If ``pool_kwargs`` + is provided, it is used instead. Note that if a new pool does not + need to be created for the request, the provided ``pool_kwargs`` are + not used. + """ + u = parse_url(url) + return self.connection_from_host( + u.host, port=u.port, scheme=u.scheme, pool_kwargs=pool_kwargs + ) + + def _merge_pool_kwargs(self, override): + """ + Merge a dictionary of override values for self.connection_pool_kw. + + This does not modify self.connection_pool_kw and returns a new dict. + Any keys in the override dictionary with a value of ``None`` are + removed from the merged dictionary. + """ + base_pool_kwargs = self.connection_pool_kw.copy() + if override: + for key, value in override.items(): + if value is None: + try: + del base_pool_kwargs[key] + except KeyError: + pass + else: + base_pool_kwargs[key] = value + return base_pool_kwargs + + def _proxy_requires_url_absolute_form(self, parsed_url): + """ + Indicates if the proxy requires the complete destination URL in the + request. Normally this is only needed when not using an HTTP CONNECT + tunnel. + """ + if self.proxy is None: + return False + + return not connection_requires_http_tunnel( + self.proxy, self.proxy_config, parsed_url.scheme + ) + + def _validate_proxy_scheme_url_selection(self, url_scheme): + """ + Validates that were not attempting to do TLS in TLS connections on + Python2 or with unsupported SSL implementations. + """ + if self.proxy is None or url_scheme != "https": + return + + if self.proxy.scheme != "https": + return + + if six.PY2 and not self.proxy_config.use_forwarding_for_https: + raise ProxySchemeUnsupported( + "Contacting HTTPS destinations through HTTPS proxies " + "'via CONNECT tunnels' is not supported in Python 2" + ) + + def urlopen(self, method, url, redirect=True, **kw): + """ + Same as :meth:`urllib3.HTTPConnectionPool.urlopen` + with custom cross-host redirect logic and only sends the request-uri + portion of the ``url``. + + The given ``url`` parameter must be absolute, such that an appropriate + :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it. + """ + u = parse_url(url) + self._validate_proxy_scheme_url_selection(u.scheme) + + conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme) + + kw["assert_same_host"] = False + kw["redirect"] = False + + if "headers" not in kw: + kw["headers"] = self.headers.copy() + + if self._proxy_requires_url_absolute_form(u): + response = conn.urlopen(method, url, **kw) + else: + response = conn.urlopen(method, u.request_uri, **kw) + + redirect_location = redirect and response.get_redirect_location() + if not redirect_location: + return response + + # Support relative URLs for redirecting. + redirect_location = urljoin(url, redirect_location) + + # RFC 7231, Section 6.4.4 + if response.status == 303: + method = "GET" + + retries = kw.get("retries") + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect) + + # Strip headers marked as unsafe to forward to the redirected location. + # Check remove_headers_on_redirect to avoid a potential network call within + # conn.is_same_host() which may use socket.gethostbyname() in the future. + if retries.remove_headers_on_redirect and not conn.is_same_host( + redirect_location + ): + headers = list(six.iterkeys(kw["headers"])) + for header in headers: + if header.lower() in retries.remove_headers_on_redirect: + kw["headers"].pop(header, None) + + try: + retries = retries.increment(method, url, response=response, _pool=conn) + except MaxRetryError: + if retries.raise_on_redirect: + response.drain_conn() + raise + return response + + kw["retries"] = retries + kw["redirect"] = redirect + + log.info("Redirecting %s -> %s", url, redirect_location) + + response.drain_conn() + return self.urlopen(method, redirect_location, **kw) + + +class ProxyManager(PoolManager): + """ + Behaves just like :class:`PoolManager`, but sends all requests through + the defined proxy, using the CONNECT method for HTTPS URLs. + + :param proxy_url: + The URL of the proxy to be used. + + :param proxy_headers: + A dictionary containing headers that will be sent to the proxy. In case + of HTTP they are being sent with each request, while in the + HTTPS/CONNECT case they are sent only once. Could be used for proxy + authentication. + + :param proxy_ssl_context: + The proxy SSL context is used to establish the TLS connection to the + proxy when using HTTPS proxies. + + :param use_forwarding_for_https: + (Defaults to False) If set to True will forward requests to the HTTPS + proxy to be made on behalf of the client instead of creating a TLS + tunnel via the CONNECT method. **Enabling this flag means that request + and response headers and content will be visible from the HTTPS proxy** + whereas tunneling keeps request and response headers and content + private. IP address, target hostname, SNI, and port are always visible + to an HTTPS proxy even when this flag is disabled. + + Example: + >>> proxy = urllib3.ProxyManager('http://localhost:3128/') + >>> r1 = proxy.request('GET', 'http://google.com/') + >>> r2 = proxy.request('GET', 'http://httpbin.org/') + >>> len(proxy.pools) + 1 + >>> r3 = proxy.request('GET', 'https://httpbin.org/') + >>> r4 = proxy.request('GET', 'https://twitter.com/') + >>> len(proxy.pools) + 3 + + """ + + def __init__( + self, + proxy_url, + num_pools=10, + headers=None, + proxy_headers=None, + proxy_ssl_context=None, + use_forwarding_for_https=False, + **connection_pool_kw + ): + + if isinstance(proxy_url, HTTPConnectionPool): + proxy_url = "%s://%s:%i" % ( + proxy_url.scheme, + proxy_url.host, + proxy_url.port, + ) + proxy = parse_url(proxy_url) + + if proxy.scheme not in ("http", "https"): + raise ProxySchemeUnknown(proxy.scheme) + + if not proxy.port: + port = port_by_scheme.get(proxy.scheme, 80) + proxy = proxy._replace(port=port) + + self.proxy = proxy + self.proxy_headers = proxy_headers or {} + self.proxy_ssl_context = proxy_ssl_context + self.proxy_config = ProxyConfig(proxy_ssl_context, use_forwarding_for_https) + + connection_pool_kw["_proxy"] = self.proxy + connection_pool_kw["_proxy_headers"] = self.proxy_headers + connection_pool_kw["_proxy_config"] = self.proxy_config + + super(ProxyManager, self).__init__(num_pools, headers, **connection_pool_kw) + + def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None): + if scheme == "https": + return super(ProxyManager, self).connection_from_host( + host, port, scheme, pool_kwargs=pool_kwargs + ) + + return super(ProxyManager, self).connection_from_host( + self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs + ) + + def _set_proxy_headers(self, url, headers=None): + """ + Sets headers needed by proxies: specifically, the Accept and Host + headers. Only sets headers not provided by the user. + """ + headers_ = {"Accept": "*/*"} + + netloc = parse_url(url).netloc + if netloc: + headers_["Host"] = netloc + + if headers: + headers_.update(headers) + return headers_ + + def urlopen(self, method, url, redirect=True, **kw): + "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute." + u = parse_url(url) + if not connection_requires_http_tunnel(self.proxy, self.proxy_config, u.scheme): + # For connections using HTTP CONNECT, httplib sets the necessary + # headers on the CONNECT to the proxy. If we're not using CONNECT, + # we'll definitely need to set 'Host' at the very least. + headers = kw.get("headers", self.headers) + kw["headers"] = self._set_proxy_headers(url, headers) + + return super(ProxyManager, self).urlopen(method, url, redirect=redirect, **kw) + + +def proxy_from_url(url, **kw): + return ProxyManager(proxy_url=url, **kw) diff --git a/openpype/vendor/python/python_2/urllib3/request.py b/openpype/vendor/python/python_2/urllib3/request.py new file mode 100644 index 0000000000..398386a5b9 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/request.py @@ -0,0 +1,170 @@ +from __future__ import absolute_import + +from .filepost import encode_multipart_formdata +from .packages.six.moves.urllib.parse import urlencode + +__all__ = ["RequestMethods"] + + +class RequestMethods(object): + """ + Convenience mixin for classes who implement a :meth:`urlopen` method, such + as :class:`urllib3.HTTPConnectionPool` and + :class:`urllib3.PoolManager`. + + Provides behavior for making common types of HTTP request methods and + decides which type of request field encoding to use. + + Specifically, + + :meth:`.request_encode_url` is for sending requests whose fields are + encoded in the URL (such as GET, HEAD, DELETE). + + :meth:`.request_encode_body` is for sending requests whose fields are + encoded in the *body* of the request using multipart or www-form-urlencoded + (such as for POST, PUT, PATCH). + + :meth:`.request` is for making any kind of request, it will look up the + appropriate encoding format and use one of the above two methods to make + the request. + + Initializer parameters: + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + """ + + _encode_url_methods = {"DELETE", "GET", "HEAD", "OPTIONS"} + + def __init__(self, headers=None): + self.headers = headers or {} + + def urlopen( + self, + method, + url, + body=None, + headers=None, + encode_multipart=True, + multipart_boundary=None, + **kw + ): # Abstract + raise NotImplementedError( + "Classes extending RequestMethods must implement " + "their own ``urlopen`` method." + ) + + def request(self, method, url, fields=None, headers=None, **urlopen_kw): + """ + Make a request using :meth:`urlopen` with the appropriate encoding of + ``fields`` based on the ``method`` used. + + This is a convenience method that requires the least amount of manual + effort. It can be used in most situations, while still having the + option to drop down to more specific methods when necessary, such as + :meth:`request_encode_url`, :meth:`request_encode_body`, + or even the lowest level :meth:`urlopen`. + """ + method = method.upper() + + urlopen_kw["request_url"] = url + + if method in self._encode_url_methods: + return self.request_encode_url( + method, url, fields=fields, headers=headers, **urlopen_kw + ) + else: + return self.request_encode_body( + method, url, fields=fields, headers=headers, **urlopen_kw + ) + + def request_encode_url(self, method, url, fields=None, headers=None, **urlopen_kw): + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the url. This is useful for request methods like GET, HEAD, DELETE, etc. + """ + if headers is None: + headers = self.headers + + extra_kw = {"headers": headers} + extra_kw.update(urlopen_kw) + + if fields: + url += "?" + urlencode(fields) + + return self.urlopen(method, url, **extra_kw) + + def request_encode_body( + self, + method, + url, + fields=None, + headers=None, + encode_multipart=True, + multipart_boundary=None, + **urlopen_kw + ): + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the body. This is useful for request methods like POST, PUT, PATCH, etc. + + When ``encode_multipart=True`` (default), then + :func:`urllib3.encode_multipart_formdata` is used to encode + the payload with the appropriate content type. Otherwise + :func:`urllib.parse.urlencode` is used with the + 'application/x-www-form-urlencoded' content type. + + Multipart encoding must be used when posting files, and it's reasonably + safe to use it in other times too. However, it may break request + signing, such as with OAuth. + + Supports an optional ``fields`` parameter of key/value strings AND + key/filetuple. A filetuple is a (filename, data, MIME type) tuple where + the MIME type is optional. For example:: + + fields = { + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), + 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + } + + When uploading a file, providing a filename (the first parameter of the + tuple) is optional but recommended to best mimic behavior of browsers. + + Note that if ``headers`` are supplied, the 'Content-Type' header will + be overwritten because it depends on the dynamic random boundary string + which is used to compose the body of the request. The random boundary + string can be explicitly set with the ``multipart_boundary`` parameter. + """ + if headers is None: + headers = self.headers + + extra_kw = {"headers": {}} + + if fields: + if "body" in urlopen_kw: + raise TypeError( + "request got values for both 'fields' and 'body', can only specify one." + ) + + if encode_multipart: + body, content_type = encode_multipart_formdata( + fields, boundary=multipart_boundary + ) + else: + body, content_type = ( + urlencode(fields), + "application/x-www-form-urlencoded", + ) + + extra_kw["body"] = body + extra_kw["headers"] = {"Content-Type": content_type} + + extra_kw["headers"].update(headers) + extra_kw.update(urlopen_kw) + + return self.urlopen(method, url, **extra_kw) diff --git a/openpype/vendor/python/python_2/urllib3/response.py b/openpype/vendor/python/python_2/urllib3/response.py new file mode 100644 index 0000000000..fdb50ddb2f --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/response.py @@ -0,0 +1,824 @@ +from __future__ import absolute_import + +import io +import logging +import zlib +from contextlib import contextmanager +from socket import error as SocketError +from socket import timeout as SocketTimeout + +try: + try: + import brotlicffi as brotli + except ImportError: + import brotli +except ImportError: + brotli = None + +from ._collections import HTTPHeaderDict +from .connection import BaseSSLError, HTTPException +from .exceptions import ( + BodyNotHttplibCompatible, + DecodeError, + HTTPError, + IncompleteRead, + InvalidChunkLength, + InvalidHeader, + ProtocolError, + ReadTimeoutError, + ResponseNotChunked, + SSLError, +) +from .packages import six +from .util.response import is_fp_closed, is_response_to_head + +log = logging.getLogger(__name__) + + +class DeflateDecoder(object): + def __init__(self): + self._first_try = True + self._data = b"" + self._obj = zlib.decompressobj() + + def __getattr__(self, name): + return getattr(self._obj, name) + + def decompress(self, data): + if not data: + return data + + if not self._first_try: + return self._obj.decompress(data) + + self._data += data + try: + decompressed = self._obj.decompress(data) + if decompressed: + self._first_try = False + self._data = None + return decompressed + except zlib.error: + self._first_try = False + self._obj = zlib.decompressobj(-zlib.MAX_WBITS) + try: + return self.decompress(self._data) + finally: + self._data = None + + +class GzipDecoderState(object): + + FIRST_MEMBER = 0 + OTHER_MEMBERS = 1 + SWALLOW_DATA = 2 + + +class GzipDecoder(object): + def __init__(self): + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + self._state = GzipDecoderState.FIRST_MEMBER + + def __getattr__(self, name): + return getattr(self._obj, name) + + def decompress(self, data): + ret = bytearray() + if self._state == GzipDecoderState.SWALLOW_DATA or not data: + return bytes(ret) + while True: + try: + ret += self._obj.decompress(data) + except zlib.error: + previous_state = self._state + # Ignore data after the first error + self._state = GzipDecoderState.SWALLOW_DATA + if previous_state == GzipDecoderState.OTHER_MEMBERS: + # Allow trailing garbage acceptable in other gzip clients + return bytes(ret) + raise + data = self._obj.unused_data + if not data: + return bytes(ret) + self._state = GzipDecoderState.OTHER_MEMBERS + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + + +if brotli is not None: + + class BrotliDecoder(object): + # Supports both 'brotlipy' and 'Brotli' packages + # since they share an import name. The top branches + # are for 'brotlipy' and bottom branches for 'Brotli' + def __init__(self): + self._obj = brotli.Decompressor() + if hasattr(self._obj, "decompress"): + self.decompress = self._obj.decompress + else: + self.decompress = self._obj.process + + def flush(self): + if hasattr(self._obj, "flush"): + return self._obj.flush() + return b"" + + +class MultiDecoder(object): + """ + From RFC7231: + If one or more encodings have been applied to a representation, the + sender that applied the encodings MUST generate a Content-Encoding + header field that lists the content codings in the order in which + they were applied. + """ + + def __init__(self, modes): + self._decoders = [_get_decoder(m.strip()) for m in modes.split(",")] + + def flush(self): + return self._decoders[0].flush() + + def decompress(self, data): + for d in reversed(self._decoders): + data = d.decompress(data) + return data + + +def _get_decoder(mode): + if "," in mode: + return MultiDecoder(mode) + + if mode == "gzip": + return GzipDecoder() + + if brotli is not None and mode == "br": + return BrotliDecoder() + + return DeflateDecoder() + + +class HTTPResponse(io.IOBase): + """ + HTTP Response container. + + Backwards-compatible with :class:`http.client.HTTPResponse` but the response ``body`` is + loaded and decoded on-demand when the ``data`` property is accessed. This + class is also compatible with the Python standard library's :mod:`io` + module, and can hence be treated as a readable object in the context of that + framework. + + Extra parameters for behaviour not present in :class:`http.client.HTTPResponse`: + + :param preload_content: + If True, the response's body will be preloaded during construction. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param original_response: + When this HTTPResponse wrapper is generated from an :class:`http.client.HTTPResponse` + object, it's convenient to include the original for debug purposes. It's + otherwise unused. + + :param retries: + The retries contains the last :class:`~urllib3.util.retry.Retry` that + was used during the request. + + :param enforce_content_length: + Enforce content length checking. Body returned by server must match + value of Content-Length header, if present. Otherwise, raise error. + """ + + CONTENT_DECODERS = ["gzip", "deflate"] + if brotli is not None: + CONTENT_DECODERS += ["br"] + REDIRECT_STATUSES = [301, 302, 303, 307, 308] + + def __init__( + self, + body="", + headers=None, + status=0, + version=0, + reason=None, + strict=0, + preload_content=True, + decode_content=True, + original_response=None, + pool=None, + connection=None, + msg=None, + retries=None, + enforce_content_length=False, + request_method=None, + request_url=None, + auto_close=True, + ): + + if isinstance(headers, HTTPHeaderDict): + self.headers = headers + else: + self.headers = HTTPHeaderDict(headers) + self.status = status + self.version = version + self.reason = reason + self.strict = strict + self.decode_content = decode_content + self.retries = retries + self.enforce_content_length = enforce_content_length + self.auto_close = auto_close + + self._decoder = None + self._body = None + self._fp = None + self._original_response = original_response + self._fp_bytes_read = 0 + self.msg = msg + self._request_url = request_url + + if body and isinstance(body, (six.string_types, bytes)): + self._body = body + + self._pool = pool + self._connection = connection + + if hasattr(body, "read"): + self._fp = body + + # Are we using the chunked-style of transfer encoding? + self.chunked = False + self.chunk_left = None + tr_enc = self.headers.get("transfer-encoding", "").lower() + # Don't incur the penalty of creating a list and then discarding it + encodings = (enc.strip() for enc in tr_enc.split(",")) + if "chunked" in encodings: + self.chunked = True + + # Determine length of response + self.length_remaining = self._init_length(request_method) + + # If requested, preload the body. + if preload_content and not self._body: + self._body = self.read(decode_content=decode_content) + + def get_redirect_location(self): + """ + Should we redirect and where to? + + :returns: Truthy redirect location string if we got a redirect status + code and valid location. ``None`` if redirect status and no + location. ``False`` if not a redirect status code. + """ + if self.status in self.REDIRECT_STATUSES: + return self.headers.get("location") + + return False + + def release_conn(self): + if not self._pool or not self._connection: + return + + self._pool._put_conn(self._connection) + self._connection = None + + def drain_conn(self): + """ + Read and discard any remaining HTTP response data in the response connection. + + Unread data in the HTTPResponse connection blocks the connection from being released back to the pool. + """ + try: + self.read() + except (HTTPError, SocketError, BaseSSLError, HTTPException): + pass + + @property + def data(self): + # For backwards-compat with earlier urllib3 0.4 and earlier. + if self._body: + return self._body + + if self._fp: + return self.read(cache_content=True) + + @property + def connection(self): + return self._connection + + def isclosed(self): + return is_fp_closed(self._fp) + + def tell(self): + """ + Obtain the number of bytes pulled over the wire so far. May differ from + the amount of content returned by :meth:``urllib3.response.HTTPResponse.read`` + if bytes are encoded on the wire (e.g, compressed). + """ + return self._fp_bytes_read + + def _init_length(self, request_method): + """ + Set initial length value for Response content if available. + """ + length = self.headers.get("content-length") + + if length is not None: + if self.chunked: + # This Response will fail with an IncompleteRead if it can't be + # received as chunked. This method falls back to attempt reading + # the response before raising an exception. + log.warning( + "Received response with both Content-Length and " + "Transfer-Encoding set. This is expressly forbidden " + "by RFC 7230 sec 3.3.2. Ignoring Content-Length and " + "attempting to process response as Transfer-Encoding: " + "chunked." + ) + return None + + try: + # RFC 7230 section 3.3.2 specifies multiple content lengths can + # be sent in a single Content-Length header + # (e.g. Content-Length: 42, 42). This line ensures the values + # are all valid ints and that as long as the `set` length is 1, + # all values are the same. Otherwise, the header is invalid. + lengths = set([int(val) for val in length.split(",")]) + if len(lengths) > 1: + raise InvalidHeader( + "Content-Length contained multiple " + "unmatching values (%s)" % length + ) + length = lengths.pop() + except ValueError: + length = None + else: + if length < 0: + length = None + + # Convert status to int for comparison + # In some cases, httplib returns a status of "_UNKNOWN" + try: + status = int(self.status) + except ValueError: + status = 0 + + # Check for responses that shouldn't include a body + if status in (204, 304) or 100 <= status < 200 or request_method == "HEAD": + length = 0 + + return length + + def _init_decoder(self): + """ + Set-up the _decoder attribute if necessary. + """ + # Note: content-encoding value should be case-insensitive, per RFC 7230 + # Section 3.2 + content_encoding = self.headers.get("content-encoding", "").lower() + if self._decoder is None: + if content_encoding in self.CONTENT_DECODERS: + self._decoder = _get_decoder(content_encoding) + elif "," in content_encoding: + encodings = [ + e.strip() + for e in content_encoding.split(",") + if e.strip() in self.CONTENT_DECODERS + ] + if len(encodings): + self._decoder = _get_decoder(content_encoding) + + DECODER_ERROR_CLASSES = (IOError, zlib.error) + if brotli is not None: + DECODER_ERROR_CLASSES += (brotli.error,) + + def _decode(self, data, decode_content, flush_decoder): + """ + Decode the data passed in and potentially flush the decoder. + """ + if not decode_content: + return data + + try: + if self._decoder: + data = self._decoder.decompress(data) + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( + "Received response with content-encoding: %s, but " + "failed to decode it." % content_encoding, + e, + ) + if flush_decoder: + data += self._flush_decoder() + + return data + + def _flush_decoder(self): + """ + Flushes the decoder. Should only be called if the decoder is actually + being used. + """ + if self._decoder: + buf = self._decoder.decompress(b"") + return buf + self._decoder.flush() + + return b"" + + @contextmanager + def _error_catcher(self): + """ + Catch low-level python exceptions, instead re-raising urllib3 + variants, so that low-level exceptions are not leaked in the + high-level api. + + On exit, release the connection back to the pool. + """ + clean_exit = False + + try: + try: + yield + + except SocketTimeout: + # FIXME: Ideally we'd like to include the url in the ReadTimeoutError but + # there is yet no clean way to get at it from this context. + raise ReadTimeoutError(self._pool, None, "Read timed out.") + + except BaseSSLError as e: + # FIXME: Is there a better way to differentiate between SSLErrors? + if "read operation timed out" not in str(e): + # SSL errors related to framing/MAC get wrapped and reraised here + raise SSLError(e) + + raise ReadTimeoutError(self._pool, None, "Read timed out.") + + except (HTTPException, SocketError) as e: + # This includes IncompleteRead. + raise ProtocolError("Connection broken: %r" % e, e) + + # If no exception is thrown, we should avoid cleaning up + # unnecessarily. + clean_exit = True + finally: + # If we didn't terminate cleanly, we need to throw away our + # connection. + if not clean_exit: + # The response may not be closed but we're not going to use it + # anymore so close it now to ensure that the connection is + # released back to the pool. + if self._original_response: + self._original_response.close() + + # Closing the response may not actually be sufficient to close + # everything, so if we have a hold of the connection close that + # too. + if self._connection: + self._connection.close() + + # If we hold the original response but it's closed now, we should + # return the connection back to the pool. + if self._original_response and self._original_response.isclosed(): + self.release_conn() + + def read(self, amt=None, decode_content=None, cache_content=False): + """ + Similar to :meth:`http.client.HTTPResponse.read`, but with two additional + parameters: ``decode_content`` and ``cache_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param cache_content: + If True, will save the returned data such that the same result is + returned despite of the state of the underlying file object. This + is useful if you want the ``.data`` property to continue working + after having ``.read()`` the file object. (Overridden if ``amt`` is + set.) + """ + self._init_decoder() + if decode_content is None: + decode_content = self.decode_content + + if self._fp is None: + return + + flush_decoder = False + fp_closed = getattr(self._fp, "closed", False) + + with self._error_catcher(): + if amt is None: + # cStringIO doesn't like amt=None + data = self._fp.read() if not fp_closed else b"" + flush_decoder = True + else: + cache_content = False + data = self._fp.read(amt) if not fp_closed else b"" + if ( + amt != 0 and not data + ): # Platform-specific: Buggy versions of Python. + # Close the connection when no data is returned + # + # This is redundant to what httplib/http.client _should_ + # already do. However, versions of python released before + # December 15, 2012 (http://bugs.python.org/issue16298) do + # not properly close the connection in all cases. There is + # no harm in redundantly calling close. + self._fp.close() + flush_decoder = True + if self.enforce_content_length and self.length_remaining not in ( + 0, + None, + ): + # This is an edge case that httplib failed to cover due + # to concerns of backward compatibility. We're + # addressing it here to make sure IncompleteRead is + # raised during streaming, so all calls with incorrect + # Content-Length are caught. + raise IncompleteRead(self._fp_bytes_read, self.length_remaining) + + if data: + self._fp_bytes_read += len(data) + if self.length_remaining is not None: + self.length_remaining -= len(data) + + data = self._decode(data, decode_content, flush_decoder) + + if cache_content: + self._body = data + + return data + + def stream(self, amt=2 ** 16, decode_content=None): + """ + A generator wrapper for the read() method. A call will block until + ``amt`` bytes have been read from the connection or until the + connection is closed. + + :param amt: + How much of the content to read. The generator will return up to + much data per iteration, but may return less. This is particularly + likely when using compressed data. However, the empty string will + never be returned. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + if self.chunked and self.supports_chunked_reads(): + for line in self.read_chunked(amt, decode_content=decode_content): + yield line + else: + while not is_fp_closed(self._fp): + data = self.read(amt=amt, decode_content=decode_content) + + if data: + yield data + + @classmethod + def from_httplib(ResponseCls, r, **response_kw): + """ + Given an :class:`http.client.HTTPResponse` instance ``r``, return a + corresponding :class:`urllib3.response.HTTPResponse` object. + + Remaining parameters are passed to the HTTPResponse constructor, along + with ``original_response=r``. + """ + headers = r.msg + + if not isinstance(headers, HTTPHeaderDict): + if six.PY2: + # Python 2.7 + headers = HTTPHeaderDict.from_httplib(headers) + else: + headers = HTTPHeaderDict(headers.items()) + + # HTTPResponse objects in Python 3 don't have a .strict attribute + strict = getattr(r, "strict", 0) + resp = ResponseCls( + body=r, + headers=headers, + status=r.status, + version=r.version, + reason=r.reason, + strict=strict, + original_response=r, + **response_kw + ) + return resp + + # Backwards-compatibility methods for http.client.HTTPResponse + def getheaders(self): + return self.headers + + def getheader(self, name, default=None): + return self.headers.get(name, default) + + # Backwards compatibility for http.cookiejar + def info(self): + return self.headers + + # Overrides from io.IOBase + def close(self): + if not self.closed: + self._fp.close() + + if self._connection: + self._connection.close() + + if not self.auto_close: + io.IOBase.close(self) + + @property + def closed(self): + if not self.auto_close: + return io.IOBase.closed.__get__(self) + elif self._fp is None: + return True + elif hasattr(self._fp, "isclosed"): + return self._fp.isclosed() + elif hasattr(self._fp, "closed"): + return self._fp.closed + else: + return True + + def fileno(self): + if self._fp is None: + raise IOError("HTTPResponse has no file to get a fileno from") + elif hasattr(self._fp, "fileno"): + return self._fp.fileno() + else: + raise IOError( + "The file-like object this HTTPResponse is wrapped " + "around has no file descriptor" + ) + + def flush(self): + if ( + self._fp is not None + and hasattr(self._fp, "flush") + and not getattr(self._fp, "closed", False) + ): + return self._fp.flush() + + def readable(self): + # This method is required for `io` module compatibility. + return True + + def readinto(self, b): + # This method is required for `io` module compatibility. + temp = self.read(len(b)) + if len(temp) == 0: + return 0 + else: + b[: len(temp)] = temp + return len(temp) + + def supports_chunked_reads(self): + """ + Checks if the underlying file-like object looks like a + :class:`http.client.HTTPResponse` object. We do this by testing for + the fp attribute. If it is present we assume it returns raw chunks as + processed by read_chunked(). + """ + return hasattr(self._fp, "fp") + + def _update_chunk_length(self): + # First, we'll figure out length of a chunk and then + # we'll try to read it from socket. + if self.chunk_left is not None: + return + line = self._fp.fp.readline() + line = line.split(b";", 1)[0] + try: + self.chunk_left = int(line, 16) + except ValueError: + # Invalid chunked protocol response, abort. + self.close() + raise InvalidChunkLength(self, line) + + def _handle_chunk(self, amt): + returned_chunk = None + if amt is None: + chunk = self._fp._safe_read(self.chunk_left) + returned_chunk = chunk + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + elif amt < self.chunk_left: + value = self._fp._safe_read(amt) + self.chunk_left = self.chunk_left - amt + returned_chunk = value + elif amt == self.chunk_left: + value = self._fp._safe_read(amt) + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + returned_chunk = value + else: # amt > self.chunk_left + returned_chunk = self._fp._safe_read(self.chunk_left) + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + return returned_chunk + + def read_chunked(self, amt=None, decode_content=None): + """ + Similar to :meth:`HTTPResponse.read`, but with an additional + parameter: ``decode_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + self._init_decoder() + # FIXME: Rewrite this method and make it a class with a better structured logic. + if not self.chunked: + raise ResponseNotChunked( + "Response is not chunked. " + "Header 'transfer-encoding: chunked' is missing." + ) + if not self.supports_chunked_reads(): + raise BodyNotHttplibCompatible( + "Body should be http.client.HTTPResponse like. " + "It should have have an fp attribute which returns raw chunks." + ) + + with self._error_catcher(): + # Don't bother reading the body of a HEAD request. + if self._original_response and is_response_to_head(self._original_response): + self._original_response.close() + return + + # If a response is already read and closed + # then return immediately. + if self._fp.fp is None: + return + + while True: + self._update_chunk_length() + if self.chunk_left == 0: + break + chunk = self._handle_chunk(amt) + decoded = self._decode( + chunk, decode_content=decode_content, flush_decoder=False + ) + if decoded: + yield decoded + + if decode_content: + # On CPython and PyPy, we should never need to flush the + # decoder. However, on Jython we *might* need to, so + # lets defensively do it anyway. + decoded = self._flush_decoder() + if decoded: # Platform-specific: Jython. + yield decoded + + # Chunk content ends with \r\n: discard it. + while True: + line = self._fp.fp.readline() + if not line: + # Some sites may not end with '\r\n'. + break + if line == b"\r\n": + break + + # We read everything; close the "file". + if self._original_response: + self._original_response.close() + + def geturl(self): + """ + Returns the URL that was the source of this response. + If the request that generated this response redirected, this method + will return the final redirect location. + """ + if self.retries is not None and len(self.retries.history): + return self.retries.history[-1].redirect_location + else: + return self._request_url + + def __iter__(self): + buffer = [] + for chunk in self.stream(decode_content=True): + if b"\n" in chunk: + chunk = chunk.split(b"\n") + yield b"".join(buffer) + chunk[0] + b"\n" + for x in chunk[1:-1]: + yield x + b"\n" + if chunk[-1]: + buffer = [chunk[-1]] + else: + buffer = [] + else: + buffer.append(chunk) + if buffer: + yield b"".join(buffer) diff --git a/openpype/vendor/python/python_2/urllib3/util/__init__.py b/openpype/vendor/python/python_2/urllib3/util/__init__.py new file mode 100644 index 0000000000..4547fc522b --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/util/__init__.py @@ -0,0 +1,49 @@ +from __future__ import absolute_import + +# For backwards compatibility, provide imports that used to be here. +from .connection import is_connection_dropped +from .request import SKIP_HEADER, SKIPPABLE_HEADERS, make_headers +from .response import is_fp_closed +from .retry import Retry +from .ssl_ import ( + ALPN_PROTOCOLS, + HAS_SNI, + IS_PYOPENSSL, + IS_SECURETRANSPORT, + PROTOCOL_TLS, + SSLContext, + assert_fingerprint, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, +) +from .timeout import Timeout, current_time +from .url import Url, get_host, parse_url, split_first +from .wait import wait_for_read, wait_for_write + +__all__ = ( + "HAS_SNI", + "IS_PYOPENSSL", + "IS_SECURETRANSPORT", + "SSLContext", + "PROTOCOL_TLS", + "ALPN_PROTOCOLS", + "Retry", + "Timeout", + "Url", + "assert_fingerprint", + "current_time", + "is_connection_dropped", + "is_fp_closed", + "get_host", + "parse_url", + "make_headers", + "resolve_cert_reqs", + "resolve_ssl_version", + "split_first", + "ssl_wrap_socket", + "wait_for_read", + "wait_for_write", + "SKIP_HEADER", + "SKIPPABLE_HEADERS", +) diff --git a/openpype/vendor/python/python_2/urllib3/util/connection.py b/openpype/vendor/python/python_2/urllib3/util/connection.py new file mode 100644 index 0000000000..6af1138f26 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/util/connection.py @@ -0,0 +1,149 @@ +from __future__ import absolute_import + +import socket + +from ..contrib import _appengine_environ +from ..exceptions import LocationParseError +from ..packages import six +from .wait import NoWayToWaitForSocketError, wait_for_read + + +def is_connection_dropped(conn): # Platform-specific + """ + Returns True if the connection is dropped and should be closed. + + :param conn: + :class:`http.client.HTTPConnection` object. + + Note: For platforms like AppEngine, this will always return ``False`` to + let the platform handle connection recycling transparently for us. + """ + sock = getattr(conn, "sock", False) + if sock is False: # Platform-specific: AppEngine + return False + if sock is None: # Connection already closed (such as by httplib). + return True + try: + # Returns True if readable, which here means it's been dropped + return wait_for_read(sock, timeout=0.0) + except NoWayToWaitForSocketError: # Platform-specific: AppEngine + return False + + +# This function is copied from socket.py in the Python 2.7 standard +# library test suite. Added to its signature is only `socket_options`. +# One additional modification is that we avoid binding to IPv6 servers +# discovered in DNS if the system doesn't have IPv6 functionality. +def create_connection( + address, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + source_address=None, + socket_options=None, +): + """Connect to *address* and return the socket object. + + Convenience function. Connect to *address* (a 2-tuple ``(host, + port)``) and return the socket object. Passing the optional + *timeout* parameter will set the timeout on the socket instance + before attempting to connect. If no *timeout* is supplied, the + global default timeout setting returned by :func:`socket.getdefaulttimeout` + is used. If *source_address* is set it must be a tuple of (host, port) + for the socket to bind as a source address before making the connection. + An host of '' or port 0 tells the OS to use the default. + """ + + host, port = address + if host.startswith("["): + host = host.strip("[]") + err = None + + # Using the value from allowed_gai_family() in the context of getaddrinfo lets + # us select whether to work with IPv4 DNS records, IPv6 records, or both. + # The original create_connection function always returns all records. + family = allowed_gai_family() + + try: + host.encode("idna") + except UnicodeError: + return six.raise_from( + LocationParseError(u"'%s', label empty or too long" % host), None + ) + + for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + sock = None + try: + sock = socket.socket(af, socktype, proto) + + # If provided, set socket level options before connecting. + _set_socket_options(sock, socket_options) + + if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: + sock.settimeout(timeout) + if source_address: + sock.bind(source_address) + sock.connect(sa) + return sock + + except socket.error as e: + err = e + if sock is not None: + sock.close() + sock = None + + if err is not None: + raise err + + raise socket.error("getaddrinfo returns an empty list") + + +def _set_socket_options(sock, options): + if options is None: + return + + for opt in options: + sock.setsockopt(*opt) + + +def allowed_gai_family(): + """This function is designed to work in the context of + getaddrinfo, where family=socket.AF_UNSPEC is the default and + will perform a DNS search for both IPv6 and IPv4 records.""" + + family = socket.AF_INET + if HAS_IPV6: + family = socket.AF_UNSPEC + return family + + +def _has_ipv6(host): + """Returns True if the system can bind an IPv6 address.""" + sock = None + has_ipv6 = False + + # App Engine doesn't support IPV6 sockets and actually has a quota on the + # number of sockets that can be used, so just early out here instead of + # creating a socket needlessly. + # See https://github.com/urllib3/urllib3/issues/1446 + if _appengine_environ.is_appengine_sandbox(): + return False + + if socket.has_ipv6: + # has_ipv6 returns true if cPython was compiled with IPv6 support. + # It does not tell us if the system has IPv6 support enabled. To + # determine that we must bind to an IPv6 address. + # https://github.com/urllib3/urllib3/pull/611 + # https://bugs.python.org/issue658327 + try: + sock = socket.socket(socket.AF_INET6) + sock.bind((host, 0)) + has_ipv6 = True + except Exception: + pass + + if sock: + sock.close() + return has_ipv6 + + +HAS_IPV6 = _has_ipv6("::1") diff --git a/openpype/vendor/python/python_2/urllib3/util/proxy.py b/openpype/vendor/python/python_2/urllib3/util/proxy.py new file mode 100644 index 0000000000..2199cc7b7f --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/util/proxy.py @@ -0,0 +1,57 @@ +from .ssl_ import create_urllib3_context, resolve_cert_reqs, resolve_ssl_version + + +def connection_requires_http_tunnel( + proxy_url=None, proxy_config=None, destination_scheme=None +): + """ + Returns True if the connection requires an HTTP CONNECT through the proxy. + + :param URL proxy_url: + URL of the proxy. + :param ProxyConfig proxy_config: + Proxy configuration from poolmanager.py + :param str destination_scheme: + The scheme of the destination. (i.e https, http, etc) + """ + # If we're not using a proxy, no way to use a tunnel. + if proxy_url is None: + return False + + # HTTP destinations never require tunneling, we always forward. + if destination_scheme == "http": + return False + + # Support for forwarding with HTTPS proxies and HTTPS destinations. + if ( + proxy_url.scheme == "https" + and proxy_config + and proxy_config.use_forwarding_for_https + ): + return False + + # Otherwise always use a tunnel. + return True + + +def create_proxy_ssl_context( + ssl_version, cert_reqs, ca_certs=None, ca_cert_dir=None, ca_cert_data=None +): + """ + Generates a default proxy ssl context if one hasn't been provided by the + user. + """ + ssl_context = create_urllib3_context( + ssl_version=resolve_ssl_version(ssl_version), + cert_reqs=resolve_cert_reqs(cert_reqs), + ) + + if ( + not ca_certs + and not ca_cert_dir + and not ca_cert_data + and hasattr(ssl_context, "load_default_certs") + ): + ssl_context.load_default_certs() + + return ssl_context diff --git a/openpype/vendor/python/python_2/urllib3/util/queue.py b/openpype/vendor/python/python_2/urllib3/util/queue.py new file mode 100644 index 0000000000..41784104ee --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/util/queue.py @@ -0,0 +1,22 @@ +import collections + +from ..packages import six +from ..packages.six.moves import queue + +if six.PY2: + # Queue is imported for side effects on MS Windows. See issue #229. + import Queue as _unused_module_Queue # noqa: F401 + + +class LifoQueue(queue.Queue): + def _init(self, _): + self.queue = collections.deque() + + def _qsize(self, len=len): + return len(self.queue) + + def _put(self, item): + self.queue.append(item) + + def _get(self): + return self.queue.pop() diff --git a/openpype/vendor/python/python_2/urllib3/util/request.py b/openpype/vendor/python/python_2/urllib3/util/request.py new file mode 100644 index 0000000000..b574b081e9 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/util/request.py @@ -0,0 +1,146 @@ +from __future__ import absolute_import + +from base64 import b64encode + +from ..exceptions import UnrewindableBodyError +from ..packages.six import b, integer_types + +# Pass as a value within ``headers`` to skip +# emitting some HTTP headers that are added automatically. +# The only headers that are supported are ``Accept-Encoding``, +# ``Host``, and ``User-Agent``. +SKIP_HEADER = "@@@SKIP_HEADER@@@" +SKIPPABLE_HEADERS = frozenset(["accept-encoding", "host", "user-agent"]) + +ACCEPT_ENCODING = "gzip,deflate" +try: + try: + import brotlicffi as _unused_module_brotli # noqa: F401 + except ImportError: + import brotli as _unused_module_brotli # noqa: F401 +except ImportError: + pass +else: + ACCEPT_ENCODING += ",br" + +_FAILEDTELL = object() + + +def make_headers( + keep_alive=None, + accept_encoding=None, + user_agent=None, + basic_auth=None, + proxy_basic_auth=None, + disable_cache=None, +): + """ + Shortcuts for generating request headers. + + :param keep_alive: + If ``True``, adds 'connection: keep-alive' header. + + :param accept_encoding: + Can be a boolean, list, or string. + ``True`` translates to 'gzip,deflate'. + List will get joined by comma. + String will be used as provided. + + :param user_agent: + String representing the user-agent you want, such as + "python-urllib3/0.6" + + :param basic_auth: + Colon-separated username:password string for 'authorization: basic ...' + auth header. + + :param proxy_basic_auth: + Colon-separated username:password string for 'proxy-authorization: basic ...' + auth header. + + :param disable_cache: + If ``True``, adds 'cache-control: no-cache' header. + + Example:: + + >>> make_headers(keep_alive=True, user_agent="Batman/1.0") + {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} + >>> make_headers(accept_encoding=True) + {'accept-encoding': 'gzip,deflate'} + """ + headers = {} + if accept_encoding: + if isinstance(accept_encoding, str): + pass + elif isinstance(accept_encoding, list): + accept_encoding = ",".join(accept_encoding) + else: + accept_encoding = ACCEPT_ENCODING + headers["accept-encoding"] = accept_encoding + + if user_agent: + headers["user-agent"] = user_agent + + if keep_alive: + headers["connection"] = "keep-alive" + + if basic_auth: + headers["authorization"] = "Basic " + b64encode(b(basic_auth)).decode("utf-8") + + if proxy_basic_auth: + headers["proxy-authorization"] = "Basic " + b64encode( + b(proxy_basic_auth) + ).decode("utf-8") + + if disable_cache: + headers["cache-control"] = "no-cache" + + return headers + + +def set_file_position(body, pos): + """ + If a position is provided, move file to that point. + Otherwise, we'll attempt to record a position for future use. + """ + if pos is not None: + rewind_body(body, pos) + elif getattr(body, "tell", None) is not None: + try: + pos = body.tell() + except (IOError, OSError): + # This differentiates from None, allowing us to catch + # a failed `tell()` later when trying to rewind the body. + pos = _FAILEDTELL + + return pos + + +def rewind_body(body, body_pos): + """ + Attempt to rewind body to a certain position. + Primarily used for request redirects and retries. + + :param body: + File-like object that supports seek. + + :param int pos: + Position to seek to in file. + """ + body_seek = getattr(body, "seek", None) + if body_seek is not None and isinstance(body_pos, integer_types): + try: + body_seek(body_pos) + except (IOError, OSError): + raise UnrewindableBodyError( + "An error occurred when rewinding request body for redirect/retry." + ) + elif body_pos is _FAILEDTELL: + raise UnrewindableBodyError( + "Unable to record file position for rewinding " + "request body during a redirect/retry." + ) + else: + raise ValueError( + "body_pos must be of type integer, instead it was %s." % type(body_pos) + ) diff --git a/openpype/vendor/python/python_2/urllib3/util/response.py b/openpype/vendor/python/python_2/urllib3/util/response.py new file mode 100644 index 0000000000..5ea609cced --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/util/response.py @@ -0,0 +1,107 @@ +from __future__ import absolute_import + +from email.errors import MultipartInvariantViolationDefect, StartBoundaryNotFoundDefect + +from ..exceptions import HeaderParsingError +from ..packages.six.moves import http_client as httplib + + +def is_fp_closed(obj): + """ + Checks whether a given file-like object is closed. + + :param obj: + The file-like object to check. + """ + + try: + # Check `isclosed()` first, in case Python3 doesn't set `closed`. + # GH Issue #928 + return obj.isclosed() + except AttributeError: + pass + + try: + # Check via the official file-like-object way. + return obj.closed + except AttributeError: + pass + + try: + # Check if the object is a container for another file-like object that + # gets released on exhaustion (e.g. HTTPResponse). + return obj.fp is None + except AttributeError: + pass + + raise ValueError("Unable to determine whether fp is closed.") + + +def assert_header_parsing(headers): + """ + Asserts whether all headers have been successfully parsed. + Extracts encountered errors from the result of parsing headers. + + Only works on Python 3. + + :param http.client.HTTPMessage headers: Headers to verify. + + :raises urllib3.exceptions.HeaderParsingError: + If parsing errors are found. + """ + + # This will fail silently if we pass in the wrong kind of parameter. + # To make debugging easier add an explicit check. + if not isinstance(headers, httplib.HTTPMessage): + raise TypeError("expected httplib.Message, got {0}.".format(type(headers))) + + defects = getattr(headers, "defects", None) + get_payload = getattr(headers, "get_payload", None) + + unparsed_data = None + if get_payload: + # get_payload is actually email.message.Message.get_payload; + # we're only interested in the result if it's not a multipart message + if not headers.is_multipart(): + payload = get_payload() + + if isinstance(payload, (bytes, str)): + unparsed_data = payload + if defects: + # httplib is assuming a response body is available + # when parsing headers even when httplib only sends + # header data to parse_headers() This results in + # defects on multipart responses in particular. + # See: https://github.com/urllib3/urllib3/issues/800 + + # So we ignore the following defects: + # - StartBoundaryNotFoundDefect: + # The claimed start boundary was never found. + # - MultipartInvariantViolationDefect: + # A message claimed to be a multipart but no subparts were found. + defects = [ + defect + for defect in defects + if not isinstance( + defect, (StartBoundaryNotFoundDefect, MultipartInvariantViolationDefect) + ) + ] + + if defects or unparsed_data: + raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data) + + +def is_response_to_head(response): + """ + Checks whether the request of a response has been a HEAD-request. + Handles the quirks of AppEngine. + + :param http.client.HTTPResponse response: + Response to check if the originating request + used 'HEAD' as a method. + """ + # FIXME: Can we do this somehow without accessing private httplib _method? + method = response._method + if isinstance(method, int): # Platform-specific: Appengine + return method == 3 + return method.upper() == "HEAD" diff --git a/openpype/vendor/python/python_2/urllib3/util/retry.py b/openpype/vendor/python/python_2/urllib3/util/retry.py new file mode 100644 index 0000000000..3398323fd7 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/util/retry.py @@ -0,0 +1,620 @@ +from __future__ import absolute_import + +import email +import logging +import re +import time +import warnings +from collections import namedtuple +from itertools import takewhile + +from ..exceptions import ( + ConnectTimeoutError, + InvalidHeader, + MaxRetryError, + ProtocolError, + ProxyError, + ReadTimeoutError, + ResponseError, +) +from ..packages import six + +log = logging.getLogger(__name__) + + +# Data structure for representing the metadata of requests that result in a retry. +RequestHistory = namedtuple( + "RequestHistory", ["method", "url", "error", "status", "redirect_location"] +) + + +# TODO: In v2 we can remove this sentinel and metaclass with deprecated options. +_Default = object() + + +class _RetryMeta(type): + @property + def DEFAULT_METHOD_WHITELIST(cls): + warnings.warn( + "Using 'Retry.DEFAULT_METHOD_WHITELIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_ALLOWED_METHODS' instead", + DeprecationWarning, + ) + return cls.DEFAULT_ALLOWED_METHODS + + @DEFAULT_METHOD_WHITELIST.setter + def DEFAULT_METHOD_WHITELIST(cls, value): + warnings.warn( + "Using 'Retry.DEFAULT_METHOD_WHITELIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_ALLOWED_METHODS' instead", + DeprecationWarning, + ) + cls.DEFAULT_ALLOWED_METHODS = value + + @property + def DEFAULT_REDIRECT_HEADERS_BLACKLIST(cls): + warnings.warn( + "Using 'Retry.DEFAULT_REDIRECT_HEADERS_BLACKLIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_REMOVE_HEADERS_ON_REDIRECT' instead", + DeprecationWarning, + ) + return cls.DEFAULT_REMOVE_HEADERS_ON_REDIRECT + + @DEFAULT_REDIRECT_HEADERS_BLACKLIST.setter + def DEFAULT_REDIRECT_HEADERS_BLACKLIST(cls, value): + warnings.warn( + "Using 'Retry.DEFAULT_REDIRECT_HEADERS_BLACKLIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_REMOVE_HEADERS_ON_REDIRECT' instead", + DeprecationWarning, + ) + cls.DEFAULT_REMOVE_HEADERS_ON_REDIRECT = value + + @property + def BACKOFF_MAX(cls): + warnings.warn( + "Using 'Retry.BACKOFF_MAX' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_BACKOFF_MAX' instead", + DeprecationWarning, + ) + return cls.DEFAULT_BACKOFF_MAX + + @BACKOFF_MAX.setter + def BACKOFF_MAX(cls, value): + warnings.warn( + "Using 'Retry.BACKOFF_MAX' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_BACKOFF_MAX' instead", + DeprecationWarning, + ) + cls.DEFAULT_BACKOFF_MAX = value + + +@six.add_metaclass(_RetryMeta) +class Retry(object): + """Retry configuration. + + Each retry attempt will create a new Retry object with updated values, so + they can be safely reused. + + Retries can be defined as a default for a pool:: + + retries = Retry(connect=5, read=2, redirect=5) + http = PoolManager(retries=retries) + response = http.request('GET', 'http://example.com/') + + Or per-request (which overrides the default for the pool):: + + response = http.request('GET', 'http://example.com/', retries=Retry(10)) + + Retries can be disabled by passing ``False``:: + + response = http.request('GET', 'http://example.com/', retries=False) + + Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless + retries are disabled, in which case the causing exception will be raised. + + :param int total: + Total number of retries to allow. Takes precedence over other counts. + + Set to ``None`` to remove this constraint and fall back on other + counts. + + Set to ``0`` to fail on the first retry. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int connect: + How many connection-related errors to retry on. + + These are errors raised before the request is sent to the remote server, + which we assume has not triggered the server to process the request. + + Set to ``0`` to fail on the first retry of this type. + + :param int read: + How many times to retry on read errors. + + These errors are raised after the request was sent to the server, so the + request may have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + :param int redirect: + How many redirects to perform. Limit this to avoid infinite redirect + loops. + + A redirect is a HTTP response with a status code 301, 302, 303, 307 or + 308. + + Set to ``0`` to fail on the first retry of this type. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int status: + How many times to retry on bad status codes. + + These are retries made on responses, where status code matches + ``status_forcelist``. + + Set to ``0`` to fail on the first retry of this type. + + :param int other: + How many times to retry on other errors. + + Other errors are errors that are not connect, read, redirect or status errors. + These errors might be raised after the request was sent to the server, so the + request might have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + If ``total`` is not set, it's a good idea to set this to 0 to account + for unexpected edge cases and avoid infinite retry loops. + + :param iterable allowed_methods: + Set of uppercased HTTP method verbs that we should retry on. + + By default, we only retry on methods which are considered to be + idempotent (multiple requests with the same parameters end with the + same state). See :attr:`Retry.DEFAULT_ALLOWED_METHODS`. + + Set to a ``False`` value to retry on any verb. + + .. warning:: + + Previously this parameter was named ``method_whitelist``, that + usage is deprecated in v1.26.0 and will be removed in v2.0. + + :param iterable status_forcelist: + A set of integer HTTP status codes that we should force a retry on. + A retry is initiated if the request method is in ``allowed_methods`` + and the response status code is in ``status_forcelist``. + + By default, this is disabled with ``None``. + + :param float backoff_factor: + A backoff factor to apply between attempts after the second try + (most errors are resolved immediately by a second try without a + delay). urllib3 will sleep for:: + + {backoff factor} * (2 ** ({number of total retries} - 1)) + + seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep + for [0.0s, 0.2s, 0.4s, ...] between retries. It will never be longer + than :attr:`Retry.DEFAULT_BACKOFF_MAX`. + + By default, backoff is disabled (set to 0). + + :param bool raise_on_redirect: Whether, if the number of redirects is + exhausted, to raise a MaxRetryError, or to return a response with a + response code in the 3xx range. + + :param bool raise_on_status: Similar meaning to ``raise_on_redirect``: + whether we should raise an exception, or return a response, + if status falls in ``status_forcelist`` range and retries have + been exhausted. + + :param tuple history: The history of the request encountered during + each call to :meth:`~Retry.increment`. The list is in the order + the requests occurred. Each list item is of class :class:`RequestHistory`. + + :param bool respect_retry_after_header: + Whether to respect Retry-After header on status codes defined as + :attr:`Retry.RETRY_AFTER_STATUS_CODES` or not. + + :param iterable remove_headers_on_redirect: + Sequence of headers to remove from the request when a response + indicating a redirect is returned before firing off the redirected + request. + """ + + #: Default methods to be used for ``allowed_methods`` + DEFAULT_ALLOWED_METHODS = frozenset( + ["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"] + ) + + #: Default status codes to be used for ``status_forcelist`` + RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) + + #: Default headers to be used for ``remove_headers_on_redirect`` + DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(["Authorization"]) + + #: Maximum backoff time. + DEFAULT_BACKOFF_MAX = 120 + + def __init__( + self, + total=10, + connect=None, + read=None, + redirect=None, + status=None, + other=None, + allowed_methods=_Default, + status_forcelist=None, + backoff_factor=0, + raise_on_redirect=True, + raise_on_status=True, + history=None, + respect_retry_after_header=True, + remove_headers_on_redirect=_Default, + # TODO: Deprecated, remove in v2.0 + method_whitelist=_Default, + ): + + if method_whitelist is not _Default: + if allowed_methods is not _Default: + raise ValueError( + "Using both 'allowed_methods' and " + "'method_whitelist' together is not allowed. " + "Instead only use 'allowed_methods'" + ) + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + stacklevel=2, + ) + allowed_methods = method_whitelist + if allowed_methods is _Default: + allowed_methods = self.DEFAULT_ALLOWED_METHODS + if remove_headers_on_redirect is _Default: + remove_headers_on_redirect = self.DEFAULT_REMOVE_HEADERS_ON_REDIRECT + + self.total = total + self.connect = connect + self.read = read + self.status = status + self.other = other + + if redirect is False or total is False: + redirect = 0 + raise_on_redirect = False + + self.redirect = redirect + self.status_forcelist = status_forcelist or set() + self.allowed_methods = allowed_methods + self.backoff_factor = backoff_factor + self.raise_on_redirect = raise_on_redirect + self.raise_on_status = raise_on_status + self.history = history or tuple() + self.respect_retry_after_header = respect_retry_after_header + self.remove_headers_on_redirect = frozenset( + [h.lower() for h in remove_headers_on_redirect] + ) + + def new(self, **kw): + params = dict( + total=self.total, + connect=self.connect, + read=self.read, + redirect=self.redirect, + status=self.status, + other=self.other, + status_forcelist=self.status_forcelist, + backoff_factor=self.backoff_factor, + raise_on_redirect=self.raise_on_redirect, + raise_on_status=self.raise_on_status, + history=self.history, + remove_headers_on_redirect=self.remove_headers_on_redirect, + respect_retry_after_header=self.respect_retry_after_header, + ) + + # TODO: If already given in **kw we use what's given to us + # If not given we need to figure out what to pass. We decide + # based on whether our class has the 'method_whitelist' property + # and if so we pass the deprecated 'method_whitelist' otherwise + # we use 'allowed_methods'. Remove in v2.0 + if "method_whitelist" not in kw and "allowed_methods" not in kw: + if "method_whitelist" in self.__dict__: + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + params["method_whitelist"] = self.allowed_methods + else: + params["allowed_methods"] = self.allowed_methods + + params.update(kw) + return type(self)(**params) + + @classmethod + def from_int(cls, retries, redirect=True, default=None): + """Backwards-compatibility for the old retries format.""" + if retries is None: + retries = default if default is not None else cls.DEFAULT + + if isinstance(retries, Retry): + return retries + + redirect = bool(redirect) and None + new_retries = cls(retries, redirect=redirect) + log.debug("Converted retries value: %r -> %r", retries, new_retries) + return new_retries + + def get_backoff_time(self): + """Formula for computing the current backoff + + :rtype: float + """ + # We want to consider only the last consecutive errors sequence (Ignore redirects). + consecutive_errors_len = len( + list( + takewhile(lambda x: x.redirect_location is None, reversed(self.history)) + ) + ) + if consecutive_errors_len <= 1: + return 0 + + backoff_value = self.backoff_factor * (2 ** (consecutive_errors_len - 1)) + return min(self.DEFAULT_BACKOFF_MAX, backoff_value) + + def parse_retry_after(self, retry_after): + # Whitespace: https://tools.ietf.org/html/rfc7230#section-3.2.4 + if re.match(r"^\s*[0-9]+\s*$", retry_after): + seconds = int(retry_after) + else: + retry_date_tuple = email.utils.parsedate_tz(retry_after) + if retry_date_tuple is None: + raise InvalidHeader("Invalid Retry-After header: %s" % retry_after) + if retry_date_tuple[9] is None: # Python 2 + # Assume UTC if no timezone was specified + # On Python2.7, parsedate_tz returns None for a timezone offset + # instead of 0 if no timezone is given, where mktime_tz treats + # a None timezone offset as local time. + retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:] + + retry_date = email.utils.mktime_tz(retry_date_tuple) + seconds = retry_date - time.time() + + if seconds < 0: + seconds = 0 + + return seconds + + def get_retry_after(self, response): + """Get the value of Retry-After in seconds.""" + + retry_after = response.getheader("Retry-After") + + if retry_after is None: + return None + + return self.parse_retry_after(retry_after) + + def sleep_for_retry(self, response=None): + retry_after = self.get_retry_after(response) + if retry_after: + time.sleep(retry_after) + return True + + return False + + def _sleep_backoff(self): + backoff = self.get_backoff_time() + if backoff <= 0: + return + time.sleep(backoff) + + def sleep(self, response=None): + """Sleep between retry attempts. + + This method will respect a server's ``Retry-After`` response header + and sleep the duration of the time requested. If that is not present, it + will use an exponential backoff. By default, the backoff factor is 0 and + this method will return immediately. + """ + + if self.respect_retry_after_header and response: + slept = self.sleep_for_retry(response) + if slept: + return + + self._sleep_backoff() + + def _is_connection_error(self, err): + """Errors when we're fairly sure that the server did not receive the + request, so it should be safe to retry. + """ + if isinstance(err, ProxyError): + err = err.original_error + return isinstance(err, ConnectTimeoutError) + + def _is_read_error(self, err): + """Errors that occur after the request has been started, so we should + assume that the server began processing it. + """ + return isinstance(err, (ReadTimeoutError, ProtocolError)) + + def _is_method_retryable(self, method): + """Checks if a given HTTP method should be retried upon, depending if + it is included in the allowed_methods + """ + # TODO: For now favor if the Retry implementation sets its own method_whitelist + # property outside of our constructor to avoid breaking custom implementations. + if "method_whitelist" in self.__dict__: + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + allowed_methods = self.method_whitelist + else: + allowed_methods = self.allowed_methods + + if allowed_methods and method.upper() not in allowed_methods: + return False + return True + + def is_retry(self, method, status_code, has_retry_after=False): + """Is this method/status code retryable? (Based on allowlists and control + variables such as the number of total retries to allow, whether to + respect the Retry-After header, whether this header is present, and + whether the returned status code is on the list of status codes to + be retried upon on the presence of the aforementioned header) + """ + if not self._is_method_retryable(method): + return False + + if self.status_forcelist and status_code in self.status_forcelist: + return True + + return ( + self.total + and self.respect_retry_after_header + and has_retry_after + and (status_code in self.RETRY_AFTER_STATUS_CODES) + ) + + def is_exhausted(self): + """Are we out of retries?""" + retry_counts = ( + self.total, + self.connect, + self.read, + self.redirect, + self.status, + self.other, + ) + retry_counts = list(filter(None, retry_counts)) + if not retry_counts: + return False + + return min(retry_counts) < 0 + + def increment( + self, + method=None, + url=None, + response=None, + error=None, + _pool=None, + _stacktrace=None, + ): + """Return a new Retry object with incremented retry counters. + + :param response: A response object, or None, if the server did not + return a response. + :type response: :class:`~urllib3.response.HTTPResponse` + :param Exception error: An error encountered during the request, or + None if the response was received successfully. + + :return: A new ``Retry`` object. + """ + if self.total is False and error: + # Disabled, indicate to re-raise the error. + raise six.reraise(type(error), error, _stacktrace) + + total = self.total + if total is not None: + total -= 1 + + connect = self.connect + read = self.read + redirect = self.redirect + status_count = self.status + other = self.other + cause = "unknown" + status = None + redirect_location = None + + if error and self._is_connection_error(error): + # Connect retry? + if connect is False: + raise six.reraise(type(error), error, _stacktrace) + elif connect is not None: + connect -= 1 + + elif error and self._is_read_error(error): + # Read retry? + if read is False or not self._is_method_retryable(method): + raise six.reraise(type(error), error, _stacktrace) + elif read is not None: + read -= 1 + + elif error: + # Other retry? + if other is not None: + other -= 1 + + elif response and response.get_redirect_location(): + # Redirect retry? + if redirect is not None: + redirect -= 1 + cause = "too many redirects" + redirect_location = response.get_redirect_location() + status = response.status + + else: + # Incrementing because of a server error like a 500 in + # status_forcelist and the given method is in the allowed_methods + cause = ResponseError.GENERIC_ERROR + if response and response.status: + if status_count is not None: + status_count -= 1 + cause = ResponseError.SPECIFIC_ERROR.format(status_code=response.status) + status = response.status + + history = self.history + ( + RequestHistory(method, url, error, status, redirect_location), + ) + + new_retry = self.new( + total=total, + connect=connect, + read=read, + redirect=redirect, + status=status_count, + other=other, + history=history, + ) + + if new_retry.is_exhausted(): + raise MaxRetryError(_pool, url, error or ResponseError(cause)) + + log.debug("Incremented Retry for (url='%s'): %r", url, new_retry) + + return new_retry + + def __repr__(self): + return ( + "{cls.__name__}(total={self.total}, connect={self.connect}, " + "read={self.read}, redirect={self.redirect}, status={self.status})" + ).format(cls=type(self), self=self) + + def __getattr__(self, item): + if item == "method_whitelist": + # TODO: Remove this deprecated alias in v2.0 + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + return self.allowed_methods + try: + return getattr(super(Retry, self), item) + except AttributeError: + return getattr(Retry, item) + + +# For backwards compatibility (equivalent to pre-v1.9): +Retry.DEFAULT = Retry(3) diff --git a/openpype/vendor/python/python_2/urllib3/util/ssl_.py b/openpype/vendor/python/python_2/urllib3/util/ssl_.py new file mode 100644 index 0000000000..8f867812a5 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/util/ssl_.py @@ -0,0 +1,495 @@ +from __future__ import absolute_import + +import hmac +import os +import sys +import warnings +from binascii import hexlify, unhexlify +from hashlib import md5, sha1, sha256 + +from ..exceptions import ( + InsecurePlatformWarning, + ProxySchemeUnsupported, + SNIMissingWarning, + SSLError, +) +from ..packages import six +from .url import BRACELESS_IPV6_ADDRZ_RE, IPV4_RE + +SSLContext = None +SSLTransport = None +HAS_SNI = False +IS_PYOPENSSL = False +IS_SECURETRANSPORT = False +ALPN_PROTOCOLS = ["http/1.1"] + +# Maps the length of a digest to a possible hash function producing this digest +HASHFUNC_MAP = {32: md5, 40: sha1, 64: sha256} + + +def _const_compare_digest_backport(a, b): + """ + Compare two digests of equal length in constant time. + + The digests must be of type str/bytes. + Returns True if the digests match, and False otherwise. + """ + result = abs(len(a) - len(b)) + for left, right in zip(bytearray(a), bytearray(b)): + result |= left ^ right + return result == 0 + + +_const_compare_digest = getattr(hmac, "compare_digest", _const_compare_digest_backport) + +try: # Test for SSL features + import ssl + from ssl import CERT_REQUIRED, wrap_socket +except ImportError: + pass + +try: + from ssl import HAS_SNI # Has SNI? +except ImportError: + pass + +try: + from .ssltransport import SSLTransport +except ImportError: + pass + + +try: # Platform-specific: Python 3.6 + from ssl import PROTOCOL_TLS + + PROTOCOL_SSLv23 = PROTOCOL_TLS +except ImportError: + try: + from ssl import PROTOCOL_SSLv23 as PROTOCOL_TLS + + PROTOCOL_SSLv23 = PROTOCOL_TLS + except ImportError: + PROTOCOL_SSLv23 = PROTOCOL_TLS = 2 + +try: + from ssl import PROTOCOL_TLS_CLIENT +except ImportError: + PROTOCOL_TLS_CLIENT = PROTOCOL_TLS + + +try: + from ssl import OP_NO_COMPRESSION, OP_NO_SSLv2, OP_NO_SSLv3 +except ImportError: + OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000 + OP_NO_COMPRESSION = 0x20000 + + +try: # OP_NO_TICKET was added in Python 3.6 + from ssl import OP_NO_TICKET +except ImportError: + OP_NO_TICKET = 0x4000 + + +# A secure default. +# Sources for more information on TLS ciphers: +# +# - https://wiki.mozilla.org/Security/Server_Side_TLS +# - https://www.ssllabs.com/projects/best-practices/index.html +# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ +# +# The general intent is: +# - prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE), +# - prefer ECDHE over DHE for better performance, +# - prefer any AES-GCM and ChaCha20 over any AES-CBC for better performance and +# security, +# - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common, +# - disable NULL authentication, MD5 MACs, DSS, and other +# insecure ciphers for security reasons. +# - NOTE: TLS 1.3 cipher suites are managed through a different interface +# not exposed by CPython (yet!) and are enabled by default if they're available. +DEFAULT_CIPHERS = ":".join( + [ + "ECDHE+AESGCM", + "ECDHE+CHACHA20", + "DHE+AESGCM", + "DHE+CHACHA20", + "ECDH+AESGCM", + "DH+AESGCM", + "ECDH+AES", + "DH+AES", + "RSA+AESGCM", + "RSA+AES", + "!aNULL", + "!eNULL", + "!MD5", + "!DSS", + ] +) + +try: + from ssl import SSLContext # Modern SSL? +except ImportError: + + class SSLContext(object): # Platform-specific: Python 2 + def __init__(self, protocol_version): + self.protocol = protocol_version + # Use default values from a real SSLContext + self.check_hostname = False + self.verify_mode = ssl.CERT_NONE + self.ca_certs = None + self.options = 0 + self.certfile = None + self.keyfile = None + self.ciphers = None + + def load_cert_chain(self, certfile, keyfile): + self.certfile = certfile + self.keyfile = keyfile + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + self.ca_certs = cafile + + if capath is not None: + raise SSLError("CA directories not supported in older Pythons") + + if cadata is not None: + raise SSLError("CA data not supported in older Pythons") + + def set_ciphers(self, cipher_suite): + self.ciphers = cipher_suite + + def wrap_socket(self, socket, server_hostname=None, server_side=False): + warnings.warn( + "A true SSLContext object is not available. This prevents " + "urllib3 from configuring SSL appropriately and may cause " + "certain SSL connections to fail. You can upgrade to a newer " + "version of Python to solve this. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings", + InsecurePlatformWarning, + ) + kwargs = { + "keyfile": self.keyfile, + "certfile": self.certfile, + "ca_certs": self.ca_certs, + "cert_reqs": self.verify_mode, + "ssl_version": self.protocol, + "server_side": server_side, + } + return wrap_socket(socket, ciphers=self.ciphers, **kwargs) + + +def assert_fingerprint(cert, fingerprint): + """ + Checks if given fingerprint matches the supplied certificate. + + :param cert: + Certificate as bytes object. + :param fingerprint: + Fingerprint as string of hexdigits, can be interspersed by colons. + """ + + fingerprint = fingerprint.replace(":", "").lower() + digest_length = len(fingerprint) + hashfunc = HASHFUNC_MAP.get(digest_length) + if not hashfunc: + raise SSLError("Fingerprint of invalid length: {0}".format(fingerprint)) + + # We need encode() here for py32; works on py2 and p33. + fingerprint_bytes = unhexlify(fingerprint.encode()) + + cert_digest = hashfunc(cert).digest() + + if not _const_compare_digest(cert_digest, fingerprint_bytes): + raise SSLError( + 'Fingerprints did not match. Expected "{0}", got "{1}".'.format( + fingerprint, hexlify(cert_digest) + ) + ) + + +def resolve_cert_reqs(candidate): + """ + Resolves the argument to a numeric constant, which can be passed to + the wrap_socket function/method from the ssl module. + Defaults to :data:`ssl.CERT_REQUIRED`. + If given a string it is assumed to be the name of the constant in the + :mod:`ssl` module or its abbreviation. + (So you can specify `REQUIRED` instead of `CERT_REQUIRED`. + If it's neither `None` nor a string we assume it is already the numeric + constant which can directly be passed to wrap_socket. + """ + if candidate is None: + return CERT_REQUIRED + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, "CERT_" + candidate) + return res + + return candidate + + +def resolve_ssl_version(candidate): + """ + like resolve_cert_reqs + """ + if candidate is None: + return PROTOCOL_TLS + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, "PROTOCOL_" + candidate) + return res + + return candidate + + +def create_urllib3_context( + ssl_version=None, cert_reqs=None, options=None, ciphers=None +): + """All arguments have the same meaning as ``ssl_wrap_socket``. + + By default, this function does a lot of the same work that + ``ssl.create_default_context`` does on Python 3.4+. It: + + - Disables SSLv2, SSLv3, and compression + - Sets a restricted set of server ciphers + + If you wish to enable SSLv3, you can do:: + + from urllib3.util import ssl_ + context = ssl_.create_urllib3_context() + context.options &= ~ssl_.OP_NO_SSLv3 + + You can do the same to enable compression (substituting ``COMPRESSION`` + for ``SSLv3`` in the last line above). + + :param ssl_version: + The desired protocol version to use. This will default to + PROTOCOL_SSLv23 which will negotiate the highest protocol that both + the server and your installation of OpenSSL support. + :param cert_reqs: + Whether to require the certificate verification. This defaults to + ``ssl.CERT_REQUIRED``. + :param options: + Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, + ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``, and ``ssl.OP_NO_TICKET``. + :param ciphers: + Which cipher suites to allow the server to select. + :returns: + Constructed SSLContext object with specified options + :rtype: SSLContext + """ + # PROTOCOL_TLS is deprecated in Python 3.10 + if not ssl_version or ssl_version == PROTOCOL_TLS: + ssl_version = PROTOCOL_TLS_CLIENT + + context = SSLContext(ssl_version) + + context.set_ciphers(ciphers or DEFAULT_CIPHERS) + + # Setting the default here, as we may have no ssl module on import + cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs + + if options is None: + options = 0 + # SSLv2 is easily broken and is considered harmful and dangerous + options |= OP_NO_SSLv2 + # SSLv3 has several problems and is now dangerous + options |= OP_NO_SSLv3 + # Disable compression to prevent CRIME attacks for OpenSSL 1.0+ + # (issue #309) + options |= OP_NO_COMPRESSION + # TLSv1.2 only. Unless set explicitly, do not request tickets. + # This may save some bandwidth on wire, and although the ticket is encrypted, + # there is a risk associated with it being on wire, + # if the server is not rotating its ticketing keys properly. + options |= OP_NO_TICKET + + context.options |= options + + # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is + # necessary for conditional client cert authentication with TLS 1.3. + # The attribute is None for OpenSSL <= 1.1.0 or does not exist in older + # versions of Python. We only enable on Python 3.7.4+ or if certificate + # verification is enabled to work around Python issue #37428 + # See: https://bugs.python.org/issue37428 + if (cert_reqs == ssl.CERT_REQUIRED or sys.version_info >= (3, 7, 4)) and getattr( + context, "post_handshake_auth", None + ) is not None: + context.post_handshake_auth = True + + def disable_check_hostname(): + if ( + getattr(context, "check_hostname", None) is not None + ): # Platform-specific: Python 3.2 + # We do our own verification, including fingerprints and alternative + # hostnames. So disable it here + context.check_hostname = False + + # The order of the below lines setting verify_mode and check_hostname + # matter due to safe-guards SSLContext has to prevent an SSLContext with + # check_hostname=True, verify_mode=NONE/OPTIONAL. This is made even more + # complex because we don't know whether PROTOCOL_TLS_CLIENT will be used + # or not so we don't know the initial state of the freshly created SSLContext. + if cert_reqs == ssl.CERT_REQUIRED: + context.verify_mode = cert_reqs + disable_check_hostname() + else: + disable_check_hostname() + context.verify_mode = cert_reqs + + # Enable logging of TLS session keys via defacto standard environment variable + # 'SSLKEYLOGFILE', if the feature is available (Python 3.8+). Skip empty values. + if hasattr(context, "keylog_filename"): + sslkeylogfile = os.environ.get("SSLKEYLOGFILE") + if sslkeylogfile: + context.keylog_filename = sslkeylogfile + + return context + + +def ssl_wrap_socket( + sock, + keyfile=None, + certfile=None, + cert_reqs=None, + ca_certs=None, + server_hostname=None, + ssl_version=None, + ciphers=None, + ssl_context=None, + ca_cert_dir=None, + key_password=None, + ca_cert_data=None, + tls_in_tls=False, +): + """ + All arguments except for server_hostname, ssl_context, and ca_cert_dir have + the same meaning as they do when using :func:`ssl.wrap_socket`. + + :param server_hostname: + When SNI is supported, the expected hostname of the certificate + :param ssl_context: + A pre-made :class:`SSLContext` object. If none is provided, one will + be created using :func:`create_urllib3_context`. + :param ciphers: + A string of ciphers we wish the client to support. + :param ca_cert_dir: + A directory containing CA certificates in multiple separate files, as + supported by OpenSSL's -CApath flag or the capath argument to + SSLContext.load_verify_locations(). + :param key_password: + Optional password if the keyfile is encrypted. + :param ca_cert_data: + Optional string containing CA certificates in PEM format suitable for + passing as the cadata parameter to SSLContext.load_verify_locations() + :param tls_in_tls: + Use SSLTransport to wrap the existing socket. + """ + context = ssl_context + if context is None: + # Note: This branch of code and all the variables in it are no longer + # used by urllib3 itself. We should consider deprecating and removing + # this code. + context = create_urllib3_context(ssl_version, cert_reqs, ciphers=ciphers) + + if ca_certs or ca_cert_dir or ca_cert_data: + try: + context.load_verify_locations(ca_certs, ca_cert_dir, ca_cert_data) + except (IOError, OSError) as e: + raise SSLError(e) + + elif ssl_context is None and hasattr(context, "load_default_certs"): + # try to load OS default certs; works well on Windows (require Python3.4+) + context.load_default_certs() + + # Attempt to detect if we get the goofy behavior of the + # keyfile being encrypted and OpenSSL asking for the + # passphrase via the terminal and instead error out. + if keyfile and key_password is None and _is_key_file_encrypted(keyfile): + raise SSLError("Client private key is encrypted, password is required") + + if certfile: + if key_password is None: + context.load_cert_chain(certfile, keyfile) + else: + context.load_cert_chain(certfile, keyfile, key_password) + + try: + if hasattr(context, "set_alpn_protocols"): + context.set_alpn_protocols(ALPN_PROTOCOLS) + except NotImplementedError: # Defensive: in CI, we always have set_alpn_protocols + pass + + # If we detect server_hostname is an IP address then the SNI + # extension should not be used according to RFC3546 Section 3.1 + use_sni_hostname = server_hostname and not is_ipaddress(server_hostname) + # SecureTransport uses server_hostname in certificate verification. + send_sni = (use_sni_hostname and HAS_SNI) or ( + IS_SECURETRANSPORT and server_hostname + ) + # Do not warn the user if server_hostname is an invalid SNI hostname. + if not HAS_SNI and use_sni_hostname: + warnings.warn( + "An HTTPS request has been made, but the SNI (Server Name " + "Indication) extension to TLS is not available on this platform. " + "This may cause the server to present an incorrect TLS " + "certificate, which can cause validation failures. You can upgrade to " + "a newer version of Python to solve this. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings", + SNIMissingWarning, + ) + + if send_sni: + ssl_sock = _ssl_wrap_socket_impl( + sock, context, tls_in_tls, server_hostname=server_hostname + ) + else: + ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls) + return ssl_sock + + +def is_ipaddress(hostname): + """Detects whether the hostname given is an IPv4 or IPv6 address. + Also detects IPv6 addresses with Zone IDs. + + :param str hostname: Hostname to examine. + :return: True if the hostname is an IP address, False otherwise. + """ + if not six.PY2 and isinstance(hostname, bytes): + # IDN A-label bytes are ASCII compatible. + hostname = hostname.decode("ascii") + return bool(IPV4_RE.match(hostname) or BRACELESS_IPV6_ADDRZ_RE.match(hostname)) + + +def _is_key_file_encrypted(key_file): + """Detects if a key file is encrypted or not.""" + with open(key_file, "r") as f: + for line in f: + # Look for Proc-Type: 4,ENCRYPTED + if "ENCRYPTED" in line: + return True + + return False + + +def _ssl_wrap_socket_impl(sock, ssl_context, tls_in_tls, server_hostname=None): + if tls_in_tls: + if not SSLTransport: + # Import error, ssl is not available. + raise ProxySchemeUnsupported( + "TLS in TLS requires support for the 'ssl' module" + ) + + SSLTransport._validate_ssl_context_for_tls_in_tls(ssl_context) + return SSLTransport(sock, ssl_context, server_hostname) + + if server_hostname: + return ssl_context.wrap_socket(sock, server_hostname=server_hostname) + else: + return ssl_context.wrap_socket(sock) diff --git a/openpype/vendor/python/python_2/urllib3/util/ssl_match_hostname.py b/openpype/vendor/python/python_2/urllib3/util/ssl_match_hostname.py new file mode 100644 index 0000000000..1dd950c489 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/util/ssl_match_hostname.py @@ -0,0 +1,159 @@ +"""The match_hostname() function from Python 3.3.3, essential when using SSL.""" + +# Note: This file is under the PSF license as the code comes from the python +# stdlib. http://docs.python.org/3/license.html + +import re +import sys + +# ipaddress has been backported to 2.6+ in pypi. If it is installed on the +# system, use it to handle IPAddress ServerAltnames (this was added in +# python-3.5) otherwise only do DNS matching. This allows +# util.ssl_match_hostname to continue to be used in Python 2.7. +try: + import ipaddress +except ImportError: + ipaddress = None + +__version__ = "3.5.0.1" + + +class CertificateError(ValueError): + pass + + +def _dnsname_match(dn, hostname, max_wildcards=1): + """Matching according to RFC 6125, section 6.4.3 + + http://tools.ietf.org/html/rfc6125#section-6.4.3 + """ + pats = [] + if not dn: + return False + + # Ported from python3-syntax: + # leftmost, *remainder = dn.split(r'.') + parts = dn.split(r".") + leftmost = parts[0] + remainder = parts[1:] + + wildcards = leftmost.count("*") + if wildcards > max_wildcards: + # Issue #17980: avoid denials of service by refusing more + # than one wildcard per fragment. A survey of established + # policy among SSL implementations showed it to be a + # reasonable choice. + raise CertificateError( + "too many wildcards in certificate DNS name: " + repr(dn) + ) + + # speed up common case w/o wildcards + if not wildcards: + return dn.lower() == hostname.lower() + + # RFC 6125, section 6.4.3, subitem 1. + # The client SHOULD NOT attempt to match a presented identifier in which + # the wildcard character comprises a label other than the left-most label. + if leftmost == "*": + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append("[^.]+") + elif leftmost.startswith("xn--") or hostname.startswith("xn--"): + # RFC 6125, section 6.4.3, subitem 3. + # The client SHOULD NOT attempt to match a presented identifier + # where the wildcard character is embedded within an A-label or + # U-label of an internationalized domain name. + pats.append(re.escape(leftmost)) + else: + # Otherwise, '*' matches any dotless string, e.g. www* + pats.append(re.escape(leftmost).replace(r"\*", "[^.]*")) + + # add the remaining fragments, ignore any wildcards + for frag in remainder: + pats.append(re.escape(frag)) + + pat = re.compile(r"\A" + r"\.".join(pats) + r"\Z", re.IGNORECASE) + return pat.match(hostname) + + +def _to_unicode(obj): + if isinstance(obj, str) and sys.version_info < (3,): + # ignored flake8 # F821 to support python 2.7 function + obj = unicode(obj, encoding="ascii", errors="strict") # noqa: F821 + return obj + + +def _ipaddress_match(ipname, host_ip): + """Exact matching of IP addresses. + + RFC 6125 explicitly doesn't define an algorithm for this + (section 1.7.2 - "Out of Scope"). + """ + # OpenSSL may add a trailing newline to a subjectAltName's IP address + # Divergence from upstream: ipaddress can't handle byte str + ip = ipaddress.ip_address(_to_unicode(ipname).rstrip()) + return ip == host_ip + + +def match_hostname(cert, hostname): + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 + rules are followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError( + "empty or no certificate, match_hostname needs a " + "SSL socket or SSL context with either " + "CERT_OPTIONAL or CERT_REQUIRED" + ) + try: + # Divergence from upstream: ipaddress can't handle byte str + host_ip = ipaddress.ip_address(_to_unicode(hostname)) + except (UnicodeError, ValueError): + # ValueError: Not an IP address (common case) + # UnicodeError: Divergence from upstream: Have to deal with ipaddress not taking + # byte strings. addresses should be all ascii, so we consider it not + # an ipaddress in this case + host_ip = None + except AttributeError: + # Divergence from upstream: Make ipaddress library optional + if ipaddress is None: + host_ip = None + else: # Defensive + raise + dnsnames = [] + san = cert.get("subjectAltName", ()) + for key, value in san: + if key == "DNS": + if host_ip is None and _dnsname_match(value, hostname): + return + dnsnames.append(value) + elif key == "IP Address": + if host_ip is not None and _ipaddress_match(value, host_ip): + return + dnsnames.append(value) + if not dnsnames: + # The subject is only checked when there is no dNSName entry + # in subjectAltName + for sub in cert.get("subject", ()): + for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. + if key == "commonName": + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if len(dnsnames) > 1: + raise CertificateError( + "hostname %r " + "doesn't match either of %s" % (hostname, ", ".join(map(repr, dnsnames))) + ) + elif len(dnsnames) == 1: + raise CertificateError("hostname %r doesn't match %r" % (hostname, dnsnames[0])) + else: + raise CertificateError( + "no appropriate commonName or subjectAltName fields were found" + ) diff --git a/openpype/vendor/python/python_2/urllib3/util/ssltransport.py b/openpype/vendor/python/python_2/urllib3/util/ssltransport.py new file mode 100644 index 0000000000..4a7105d179 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/util/ssltransport.py @@ -0,0 +1,221 @@ +import io +import socket +import ssl + +from ..exceptions import ProxySchemeUnsupported +from ..packages import six + +SSL_BLOCKSIZE = 16384 + + +class SSLTransport: + """ + The SSLTransport wraps an existing socket and establishes an SSL connection. + + Contrary to Python's implementation of SSLSocket, it allows you to chain + multiple TLS connections together. It's particularly useful if you need to + implement TLS within TLS. + + The class supports most of the socket API operations. + """ + + @staticmethod + def _validate_ssl_context_for_tls_in_tls(ssl_context): + """ + Raises a ProxySchemeUnsupported if the provided ssl_context can't be used + for TLS in TLS. + + The only requirement is that the ssl_context provides the 'wrap_bio' + methods. + """ + + if not hasattr(ssl_context, "wrap_bio"): + if six.PY2: + raise ProxySchemeUnsupported( + "TLS in TLS requires SSLContext.wrap_bio() which isn't " + "supported on Python 2" + ) + else: + raise ProxySchemeUnsupported( + "TLS in TLS requires SSLContext.wrap_bio() which isn't " + "available on non-native SSLContext" + ) + + def __init__( + self, socket, ssl_context, server_hostname=None, suppress_ragged_eofs=True + ): + """ + Create an SSLTransport around socket using the provided ssl_context. + """ + self.incoming = ssl.MemoryBIO() + self.outgoing = ssl.MemoryBIO() + + self.suppress_ragged_eofs = suppress_ragged_eofs + self.socket = socket + + self.sslobj = ssl_context.wrap_bio( + self.incoming, self.outgoing, server_hostname=server_hostname + ) + + # Perform initial handshake. + self._ssl_io_loop(self.sslobj.do_handshake) + + def __enter__(self): + return self + + def __exit__(self, *_): + self.close() + + def fileno(self): + return self.socket.fileno() + + def read(self, len=1024, buffer=None): + return self._wrap_ssl_read(len, buffer) + + def recv(self, len=1024, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to recv") + return self._wrap_ssl_read(len) + + def recv_into(self, buffer, nbytes=None, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to recv_into") + if buffer and (nbytes is None): + nbytes = len(buffer) + elif nbytes is None: + nbytes = 1024 + return self.read(nbytes, buffer) + + def sendall(self, data, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to sendall") + count = 0 + with memoryview(data) as view, view.cast("B") as byte_view: + amount = len(byte_view) + while count < amount: + v = self.send(byte_view[count:]) + count += v + + def send(self, data, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to send") + response = self._ssl_io_loop(self.sslobj.write, data) + return response + + def makefile( + self, mode="r", buffering=None, encoding=None, errors=None, newline=None + ): + """ + Python's httpclient uses makefile and buffered io when reading HTTP + messages and we need to support it. + + This is unfortunately a copy and paste of socket.py makefile with small + changes to point to the socket directly. + """ + if not set(mode) <= {"r", "w", "b"}: + raise ValueError("invalid mode %r (only r, w, b allowed)" % (mode,)) + + writing = "w" in mode + reading = "r" in mode or not writing + assert reading or writing + binary = "b" in mode + rawmode = "" + if reading: + rawmode += "r" + if writing: + rawmode += "w" + raw = socket.SocketIO(self, rawmode) + self.socket._io_refs += 1 + if buffering is None: + buffering = -1 + if buffering < 0: + buffering = io.DEFAULT_BUFFER_SIZE + if buffering == 0: + if not binary: + raise ValueError("unbuffered streams must be binary") + return raw + if reading and writing: + buffer = io.BufferedRWPair(raw, raw, buffering) + elif reading: + buffer = io.BufferedReader(raw, buffering) + else: + assert writing + buffer = io.BufferedWriter(raw, buffering) + if binary: + return buffer + text = io.TextIOWrapper(buffer, encoding, errors, newline) + text.mode = mode + return text + + def unwrap(self): + self._ssl_io_loop(self.sslobj.unwrap) + + def close(self): + self.socket.close() + + def getpeercert(self, binary_form=False): + return self.sslobj.getpeercert(binary_form) + + def version(self): + return self.sslobj.version() + + def cipher(self): + return self.sslobj.cipher() + + def selected_alpn_protocol(self): + return self.sslobj.selected_alpn_protocol() + + def selected_npn_protocol(self): + return self.sslobj.selected_npn_protocol() + + def shared_ciphers(self): + return self.sslobj.shared_ciphers() + + def compression(self): + return self.sslobj.compression() + + def settimeout(self, value): + self.socket.settimeout(value) + + def gettimeout(self): + return self.socket.gettimeout() + + def _decref_socketios(self): + self.socket._decref_socketios() + + def _wrap_ssl_read(self, len, buffer=None): + try: + return self._ssl_io_loop(self.sslobj.read, len, buffer) + except ssl.SSLError as e: + if e.errno == ssl.SSL_ERROR_EOF and self.suppress_ragged_eofs: + return 0 # eof, return 0. + else: + raise + + def _ssl_io_loop(self, func, *args): + """Performs an I/O loop between incoming/outgoing and the socket.""" + should_loop = True + ret = None + + while should_loop: + errno = None + try: + ret = func(*args) + except ssl.SSLError as e: + if e.errno not in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE): + # WANT_READ, and WANT_WRITE are expected, others are not. + raise e + errno = e.errno + + buf = self.outgoing.read() + self.socket.sendall(buf) + + if errno is None: + should_loop = False + elif errno == ssl.SSL_ERROR_WANT_READ: + buf = self.socket.recv(SSL_BLOCKSIZE) + if buf: + self.incoming.write(buf) + else: + self.incoming.write_eof() + return ret diff --git a/openpype/vendor/python/python_2/urllib3/util/timeout.py b/openpype/vendor/python/python_2/urllib3/util/timeout.py new file mode 100644 index 0000000000..ff69593b05 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/util/timeout.py @@ -0,0 +1,268 @@ +from __future__ import absolute_import + +import time + +# The default socket timeout, used by httplib to indicate that no timeout was +# specified by the user +from socket import _GLOBAL_DEFAULT_TIMEOUT + +from ..exceptions import TimeoutStateError + +# A sentinel value to indicate that no timeout was specified by the user in +# urllib3 +_Default = object() + + +# Use time.monotonic if available. +current_time = getattr(time, "monotonic", time.time) + + +class Timeout(object): + """Timeout configuration. + + Timeouts can be defined as a default for a pool: + + .. code-block:: python + + timeout = Timeout(connect=2.0, read=7.0) + http = PoolManager(timeout=timeout) + response = http.request('GET', 'http://example.com/') + + Or per-request (which overrides the default for the pool): + + .. code-block:: python + + response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) + + Timeouts can be disabled by setting all the parameters to ``None``: + + .. code-block:: python + + no_timeout = Timeout(connect=None, read=None) + response = http.request('GET', 'http://example.com/, timeout=no_timeout) + + + :param total: + This combines the connect and read timeouts into one; the read timeout + will be set to the time leftover from the connect attempt. In the + event that both a connect timeout and a total are specified, or a read + timeout and a total are specified, the shorter timeout will be applied. + + Defaults to None. + + :type total: int, float, or None + + :param connect: + The maximum amount of time (in seconds) to wait for a connection + attempt to a server to succeed. Omitting the parameter will default the + connect timeout to the system default, probably `the global default + timeout in socket.py + `_. + None will set an infinite timeout for connection attempts. + + :type connect: int, float, or None + + :param read: + The maximum amount of time (in seconds) to wait between consecutive + read operations for a response from the server. Omitting the parameter + will default the read timeout to the system default, probably `the + global default timeout in socket.py + `_. + None will set an infinite timeout. + + :type read: int, float, or None + + .. note:: + + Many factors can affect the total amount of time for urllib3 to return + an HTTP response. + + For example, Python's DNS resolver does not obey the timeout specified + on the socket. Other factors that can affect total request time include + high CPU load, high swap, the program running at a low priority level, + or other behaviors. + + In addition, the read and total timeouts only measure the time between + read operations on the socket connecting the client and the server, + not the total amount of time for the request to return a complete + response. For most requests, the timeout is raised because the server + has not sent the first byte in the specified time. This is not always + the case; if a server streams one byte every fifteen seconds, a timeout + of 20 seconds will not trigger, even though the request will take + several minutes to complete. + + If your goal is to cut off any request after a set amount of wall clock + time, consider having a second "watcher" thread to cut off a slow + request. + """ + + #: A sentinel object representing the default timeout value + DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT + + def __init__(self, total=None, connect=_Default, read=_Default): + self._connect = self._validate_timeout(connect, "connect") + self._read = self._validate_timeout(read, "read") + self.total = self._validate_timeout(total, "total") + self._start_connect = None + + def __repr__(self): + return "%s(connect=%r, read=%r, total=%r)" % ( + type(self).__name__, + self._connect, + self._read, + self.total, + ) + + # __str__ provided for backwards compatibility + __str__ = __repr__ + + @classmethod + def _validate_timeout(cls, value, name): + """Check that a timeout attribute is valid. + + :param value: The timeout value to validate + :param name: The name of the timeout attribute to validate. This is + used to specify in error messages. + :return: The validated and casted version of the given value. + :raises ValueError: If it is a numeric value less than or equal to + zero, or the type is not an integer, float, or None. + """ + if value is _Default: + return cls.DEFAULT_TIMEOUT + + if value is None or value is cls.DEFAULT_TIMEOUT: + return value + + if isinstance(value, bool): + raise ValueError( + "Timeout cannot be a boolean value. It must " + "be an int, float or None." + ) + try: + float(value) + except (TypeError, ValueError): + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) + + try: + if value <= 0: + raise ValueError( + "Attempted to set %s timeout to %s, but the " + "timeout cannot be set to a value less " + "than or equal to 0." % (name, value) + ) + except TypeError: + # Python 3 + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) + + return value + + @classmethod + def from_float(cls, timeout): + """Create a new Timeout from a legacy timeout value. + + The timeout value used by httplib.py sets the same timeout on the + connect(), and recv() socket requests. This creates a :class:`Timeout` + object that sets the individual timeouts to the ``timeout`` value + passed to this function. + + :param timeout: The legacy timeout value. + :type timeout: integer, float, sentinel default object, or None + :return: Timeout object + :rtype: :class:`Timeout` + """ + return Timeout(read=timeout, connect=timeout) + + def clone(self): + """Create a copy of the timeout object + + Timeout properties are stored per-pool but each request needs a fresh + Timeout object to ensure each one has its own start/stop configured. + + :return: a copy of the timeout object + :rtype: :class:`Timeout` + """ + # We can't use copy.deepcopy because that will also create a new object + # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to + # detect the user default. + return Timeout(connect=self._connect, read=self._read, total=self.total) + + def start_connect(self): + """Start the timeout clock, used during a connect() attempt + + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to start a timer that has been started already. + """ + if self._start_connect is not None: + raise TimeoutStateError("Timeout timer has already been started.") + self._start_connect = current_time() + return self._start_connect + + def get_connect_duration(self): + """Gets the time elapsed since the call to :meth:`start_connect`. + + :return: Elapsed time in seconds. + :rtype: float + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to get duration for a timer that hasn't been started. + """ + if self._start_connect is None: + raise TimeoutStateError( + "Can't get connect duration for timer that has not started." + ) + return current_time() - self._start_connect + + @property + def connect_timeout(self): + """Get the value to use when setting a connection timeout. + + This will be a positive float or integer, the value None + (never timeout), or the default system timeout. + + :return: Connect timeout. + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + """ + if self.total is None: + return self._connect + + if self._connect is None or self._connect is self.DEFAULT_TIMEOUT: + return self.total + + return min(self._connect, self.total) + + @property + def read_timeout(self): + """Get the value for the read timeout. + + This assumes some time has elapsed in the connection timeout and + computes the read timeout appropriately. + + If self.total is set, the read timeout is dependent on the amount of + time taken by the connect timeout. If the connection time has not been + established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be + raised. + + :return: Value to use for the read timeout. + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` + has not yet been called on this object. + """ + if ( + self.total is not None + and self.total is not self.DEFAULT_TIMEOUT + and self._read is not None + and self._read is not self.DEFAULT_TIMEOUT + ): + # In case the connect timeout has not yet been established. + if self._start_connect is None: + return self._read + return max(0, min(self.total - self.get_connect_duration(), self._read)) + elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: + return max(0, self.total - self.get_connect_duration()) + else: + return self._read diff --git a/openpype/vendor/python/python_2/urllib3/util/url.py b/openpype/vendor/python/python_2/urllib3/util/url.py new file mode 100644 index 0000000000..81a03da9e3 --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/util/url.py @@ -0,0 +1,432 @@ +from __future__ import absolute_import + +import re +from collections import namedtuple + +from ..exceptions import LocationParseError +from ..packages import six + +url_attrs = ["scheme", "auth", "host", "port", "path", "query", "fragment"] + +# We only want to normalize urls with an HTTP(S) scheme. +# urllib3 infers URLs without a scheme (None) to be http. +NORMALIZABLE_SCHEMES = ("http", "https", None) + +# Almost all of these patterns were derived from the +# 'rfc3986' module: https://github.com/python-hyper/rfc3986 +PERCENT_RE = re.compile(r"%[a-fA-F0-9]{2}") +SCHEME_RE = re.compile(r"^(?:[a-zA-Z][a-zA-Z0-9+-]*:|/)") +URI_RE = re.compile( + r"^(?:([a-zA-Z][a-zA-Z0-9+.-]*):)?" + r"(?://([^\\/?#]*))?" + r"([^?#]*)" + r"(?:\?([^#]*))?" + r"(?:#(.*))?$", + re.UNICODE | re.DOTALL, +) + +IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}" +HEX_PAT = "[0-9A-Fa-f]{1,4}" +LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=HEX_PAT, ipv4=IPV4_PAT) +_subs = {"hex": HEX_PAT, "ls32": LS32_PAT} +_variations = [ + # 6( h16 ":" ) ls32 + "(?:%(hex)s:){6}%(ls32)s", + # "::" 5( h16 ":" ) ls32 + "::(?:%(hex)s:){5}%(ls32)s", + # [ h16 ] "::" 4( h16 ":" ) ls32 + "(?:%(hex)s)?::(?:%(hex)s:){4}%(ls32)s", + # [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + "(?:(?:%(hex)s:)?%(hex)s)?::(?:%(hex)s:){3}%(ls32)s", + # [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + "(?:(?:%(hex)s:){0,2}%(hex)s)?::(?:%(hex)s:){2}%(ls32)s", + # [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + "(?:(?:%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s", + # [ *4( h16 ":" ) h16 ] "::" ls32 + "(?:(?:%(hex)s:){0,4}%(hex)s)?::%(ls32)s", + # [ *5( h16 ":" ) h16 ] "::" h16 + "(?:(?:%(hex)s:){0,5}%(hex)s)?::%(hex)s", + # [ *6( h16 ":" ) h16 ] "::" + "(?:(?:%(hex)s:){0,6}%(hex)s)?::", +] + +UNRESERVED_PAT = r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._!\-~" +IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")" +ZONE_ID_PAT = "(?:%25|%)(?:[" + UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+" +IPV6_ADDRZ_PAT = r"\[" + IPV6_PAT + r"(?:" + ZONE_ID_PAT + r")?\]" +REG_NAME_PAT = r"(?:[^\[\]%:/?#]|%[a-fA-F0-9]{2})*" +TARGET_RE = re.compile(r"^(/[^?#]*)(?:\?([^#]*))?(?:#.*)?$") + +IPV4_RE = re.compile("^" + IPV4_PAT + "$") +IPV6_RE = re.compile("^" + IPV6_PAT + "$") +IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT + "$") +BRACELESS_IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT[2:-2] + "$") +ZONE_ID_RE = re.compile("(" + ZONE_ID_PAT + r")\]$") + +_HOST_PORT_PAT = ("^(%s|%s|%s)(?::([0-9]{0,5}))?$") % ( + REG_NAME_PAT, + IPV4_PAT, + IPV6_ADDRZ_PAT, +) +_HOST_PORT_RE = re.compile(_HOST_PORT_PAT, re.UNICODE | re.DOTALL) + +UNRESERVED_CHARS = set( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-~" +) +SUB_DELIM_CHARS = set("!$&'()*+,;=") +USERINFO_CHARS = UNRESERVED_CHARS | SUB_DELIM_CHARS | {":"} +PATH_CHARS = USERINFO_CHARS | {"@", "/"} +QUERY_CHARS = FRAGMENT_CHARS = PATH_CHARS | {"?"} + + +class Url(namedtuple("Url", url_attrs)): + """ + Data structure for representing an HTTP URL. Used as a return value for + :func:`parse_url`. Both the scheme and host are normalized as they are + both case-insensitive according to RFC 3986. + """ + + __slots__ = () + + def __new__( + cls, + scheme=None, + auth=None, + host=None, + port=None, + path=None, + query=None, + fragment=None, + ): + if path and not path.startswith("/"): + path = "/" + path + if scheme is not None: + scheme = scheme.lower() + return super(Url, cls).__new__( + cls, scheme, auth, host, port, path, query, fragment + ) + + @property + def hostname(self): + """For backwards-compatibility with urlparse. We're nice like that.""" + return self.host + + @property + def request_uri(self): + """Absolute path including the query string.""" + uri = self.path or "/" + + if self.query is not None: + uri += "?" + self.query + + return uri + + @property + def netloc(self): + """Network location including host and port""" + if self.port: + return "%s:%d" % (self.host, self.port) + return self.host + + @property + def url(self): + """ + Convert self into a url + + This function should more or less round-trip with :func:`.parse_url`. The + returned url may not be exactly the same as the url inputted to + :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls + with a blank port will have : removed). + + Example: :: + + >>> U = parse_url('http://google.com/mail/') + >>> U.url + 'http://google.com/mail/' + >>> Url('http', 'username:password', 'host.com', 80, + ... '/path', 'query', 'fragment').url + 'http://username:password@host.com:80/path?query#fragment' + """ + scheme, auth, host, port, path, query, fragment = self + url = u"" + + # We use "is not None" we want things to happen with empty strings (or 0 port) + if scheme is not None: + url += scheme + u"://" + if auth is not None: + url += auth + u"@" + if host is not None: + url += host + if port is not None: + url += u":" + str(port) + if path is not None: + url += path + if query is not None: + url += u"?" + query + if fragment is not None: + url += u"#" + fragment + + return url + + def __str__(self): + return self.url + + +def split_first(s, delims): + """ + .. deprecated:: 1.25 + + Given a string and an iterable of delimiters, split on the first found + delimiter. Return two split parts and the matched delimiter. + + If not found, then the first part is the full input string. + + Example:: + + >>> split_first('foo/bar?baz', '?/=') + ('foo', 'bar?baz', '/') + >>> split_first('foo/bar?baz', '123') + ('foo/bar?baz', '', None) + + Scales linearly with number of delims. Not ideal for large number of delims. + """ + min_idx = None + min_delim = None + for d in delims: + idx = s.find(d) + if idx < 0: + continue + + if min_idx is None or idx < min_idx: + min_idx = idx + min_delim = d + + if min_idx is None or min_idx < 0: + return s, "", None + + return s[:min_idx], s[min_idx + 1 :], min_delim + + +def _encode_invalid_chars(component, allowed_chars, encoding="utf-8"): + """Percent-encodes a URI component without reapplying + onto an already percent-encoded component. + """ + if component is None: + return component + + component = six.ensure_text(component) + + # Normalize existing percent-encoded bytes. + # Try to see if the component we're encoding is already percent-encoded + # so we can skip all '%' characters but still encode all others. + component, percent_encodings = PERCENT_RE.subn( + lambda match: match.group(0).upper(), component + ) + + uri_bytes = component.encode("utf-8", "surrogatepass") + is_percent_encoded = percent_encodings == uri_bytes.count(b"%") + encoded_component = bytearray() + + for i in range(0, len(uri_bytes)): + # Will return a single character bytestring on both Python 2 & 3 + byte = uri_bytes[i : i + 1] + byte_ord = ord(byte) + if (is_percent_encoded and byte == b"%") or ( + byte_ord < 128 and byte.decode() in allowed_chars + ): + encoded_component += byte + continue + encoded_component.extend(b"%" + (hex(byte_ord)[2:].encode().zfill(2).upper())) + + return encoded_component.decode(encoding) + + +def _remove_path_dot_segments(path): + # See http://tools.ietf.org/html/rfc3986#section-5.2.4 for pseudo-code + segments = path.split("/") # Turn the path into a list of segments + output = [] # Initialize the variable to use to store output + + for segment in segments: + # '.' is the current directory, so ignore it, it is superfluous + if segment == ".": + continue + # Anything other than '..', should be appended to the output + elif segment != "..": + output.append(segment) + # In this case segment == '..', if we can, we should pop the last + # element + elif output: + output.pop() + + # If the path starts with '/' and the output is empty or the first string + # is non-empty + if path.startswith("/") and (not output or output[0]): + output.insert(0, "") + + # If the path starts with '/.' or '/..' ensure we add one more empty + # string to add a trailing '/' + if path.endswith(("/.", "/..")): + output.append("") + + return "/".join(output) + + +def _normalize_host(host, scheme): + if host: + if isinstance(host, six.binary_type): + host = six.ensure_str(host) + + if scheme in NORMALIZABLE_SCHEMES: + is_ipv6 = IPV6_ADDRZ_RE.match(host) + if is_ipv6: + match = ZONE_ID_RE.search(host) + if match: + start, end = match.span(1) + zone_id = host[start:end] + + if zone_id.startswith("%25") and zone_id != "%25": + zone_id = zone_id[3:] + else: + zone_id = zone_id[1:] + zone_id = "%" + _encode_invalid_chars(zone_id, UNRESERVED_CHARS) + return host[:start].lower() + zone_id + host[end:] + else: + return host.lower() + elif not IPV4_RE.match(host): + return six.ensure_str( + b".".join([_idna_encode(label) for label in host.split(".")]) + ) + return host + + +def _idna_encode(name): + if name and any([ord(x) > 128 for x in name]): + try: + import idna + except ImportError: + six.raise_from( + LocationParseError("Unable to parse URL without the 'idna' module"), + None, + ) + try: + return idna.encode(name.lower(), strict=True, std3_rules=True) + except idna.IDNAError: + six.raise_from( + LocationParseError(u"Name '%s' is not a valid IDNA label" % name), None + ) + return name.lower().encode("ascii") + + +def _encode_target(target): + """Percent-encodes a request target so that there are no invalid characters""" + path, query = TARGET_RE.match(target).groups() + target = _encode_invalid_chars(path, PATH_CHARS) + query = _encode_invalid_chars(query, QUERY_CHARS) + if query is not None: + target += "?" + query + return target + + +def parse_url(url): + """ + Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is + performed to parse incomplete urls. Fields not provided will be None. + This parser is RFC 3986 compliant. + + The parser logic and helper functions are based heavily on + work done in the ``rfc3986`` module. + + :param str url: URL to parse into a :class:`.Url` namedtuple. + + Partly backwards-compatible with :mod:`urlparse`. + + Example:: + + >>> parse_url('http://google.com/mail/') + Url(scheme='http', host='google.com', port=None, path='/mail/', ...) + >>> parse_url('google.com:80') + Url(scheme=None, host='google.com', port=80, path=None, ...) + >>> parse_url('/foo?bar') + Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) + """ + if not url: + # Empty + return Url() + + source_url = url + if not SCHEME_RE.search(url): + url = "//" + url + + try: + scheme, authority, path, query, fragment = URI_RE.match(url).groups() + normalize_uri = scheme is None or scheme.lower() in NORMALIZABLE_SCHEMES + + if scheme: + scheme = scheme.lower() + + if authority: + auth, _, host_port = authority.rpartition("@") + auth = auth or None + host, port = _HOST_PORT_RE.match(host_port).groups() + if auth and normalize_uri: + auth = _encode_invalid_chars(auth, USERINFO_CHARS) + if port == "": + port = None + else: + auth, host, port = None, None, None + + if port is not None: + port = int(port) + if not (0 <= port <= 65535): + raise LocationParseError(url) + + host = _normalize_host(host, scheme) + + if normalize_uri and path: + path = _remove_path_dot_segments(path) + path = _encode_invalid_chars(path, PATH_CHARS) + if normalize_uri and query: + query = _encode_invalid_chars(query, QUERY_CHARS) + if normalize_uri and fragment: + fragment = _encode_invalid_chars(fragment, FRAGMENT_CHARS) + + except (ValueError, AttributeError): + return six.raise_from(LocationParseError(source_url), None) + + # For the sake of backwards compatibility we put empty + # string values for path if there are any defined values + # beyond the path in the URL. + # TODO: Remove this when we break backwards compatibility. + if not path: + if query is not None or fragment is not None: + path = "" + else: + path = None + + # Ensure that each part of the URL is a `str` for + # backwards compatibility. + if isinstance(url, six.text_type): + ensure_func = six.ensure_text + else: + ensure_func = six.ensure_str + + def ensure_type(x): + return x if x is None else ensure_func(x) + + return Url( + scheme=ensure_type(scheme), + auth=ensure_type(auth), + host=ensure_type(host), + port=port, + path=ensure_type(path), + query=ensure_type(query), + fragment=ensure_type(fragment), + ) + + +def get_host(url): + """ + Deprecated. Use :func:`parse_url` instead. + """ + p = parse_url(url) + return p.scheme or "http", p.hostname, p.port diff --git a/openpype/vendor/python/python_2/urllib3/util/wait.py b/openpype/vendor/python/python_2/urllib3/util/wait.py new file mode 100644 index 0000000000..c280646c7b --- /dev/null +++ b/openpype/vendor/python/python_2/urllib3/util/wait.py @@ -0,0 +1,153 @@ +import errno +import select +import sys +from functools import partial + +try: + from time import monotonic +except ImportError: + from time import time as monotonic + +__all__ = ["NoWayToWaitForSocketError", "wait_for_read", "wait_for_write"] + + +class NoWayToWaitForSocketError(Exception): + pass + + +# How should we wait on sockets? +# +# There are two types of APIs you can use for waiting on sockets: the fancy +# modern stateful APIs like epoll/kqueue, and the older stateless APIs like +# select/poll. The stateful APIs are more efficient when you have a lots of +# sockets to keep track of, because you can set them up once and then use them +# lots of times. But we only ever want to wait on a single socket at a time +# and don't want to keep track of state, so the stateless APIs are actually +# more efficient. So we want to use select() or poll(). +# +# Now, how do we choose between select() and poll()? On traditional Unixes, +# select() has a strange calling convention that makes it slow, or fail +# altogether, for high-numbered file descriptors. The point of poll() is to fix +# that, so on Unixes, we prefer poll(). +# +# On Windows, there is no poll() (or at least Python doesn't provide a wrapper +# for it), but that's OK, because on Windows, select() doesn't have this +# strange calling convention; plain select() works fine. +# +# So: on Windows we use select(), and everywhere else we use poll(). We also +# fall back to select() in case poll() is somehow broken or missing. + +if sys.version_info >= (3, 5): + # Modern Python, that retries syscalls by default + def _retry_on_intr(fn, timeout): + return fn(timeout) + + +else: + # Old and broken Pythons. + def _retry_on_intr(fn, timeout): + if timeout is None: + deadline = float("inf") + else: + deadline = monotonic() + timeout + + while True: + try: + return fn(timeout) + # OSError for 3 <= pyver < 3.5, select.error for pyver <= 2.7 + except (OSError, select.error) as e: + # 'e.args[0]' incantation works for both OSError and select.error + if e.args[0] != errno.EINTR: + raise + else: + timeout = deadline - monotonic() + if timeout < 0: + timeout = 0 + if timeout == float("inf"): + timeout = None + continue + + +def select_wait_for_socket(sock, read=False, write=False, timeout=None): + if not read and not write: + raise RuntimeError("must specify at least one of read=True, write=True") + rcheck = [] + wcheck = [] + if read: + rcheck.append(sock) + if write: + wcheck.append(sock) + # When doing a non-blocking connect, most systems signal success by + # marking the socket writable. Windows, though, signals success by marked + # it as "exceptional". We paper over the difference by checking the write + # sockets for both conditions. (The stdlib selectors module does the same + # thing.) + fn = partial(select.select, rcheck, wcheck, wcheck) + rready, wready, xready = _retry_on_intr(fn, timeout) + return bool(rready or wready or xready) + + +def poll_wait_for_socket(sock, read=False, write=False, timeout=None): + if not read and not write: + raise RuntimeError("must specify at least one of read=True, write=True") + mask = 0 + if read: + mask |= select.POLLIN + if write: + mask |= select.POLLOUT + poll_obj = select.poll() + poll_obj.register(sock, mask) + + # For some reason, poll() takes timeout in milliseconds + def do_poll(t): + if t is not None: + t *= 1000 + return poll_obj.poll(t) + + return bool(_retry_on_intr(do_poll, timeout)) + + +def null_wait_for_socket(*args, **kwargs): + raise NoWayToWaitForSocketError("no select-equivalent available") + + +def _have_working_poll(): + # Apparently some systems have a select.poll that fails as soon as you try + # to use it, either due to strange configuration or broken monkeypatching + # from libraries like eventlet/greenlet. + try: + poll_obj = select.poll() + _retry_on_intr(poll_obj.poll, 0) + except (AttributeError, OSError): + return False + else: + return True + + +def wait_for_socket(*args, **kwargs): + # We delay choosing which implementation to use until the first time we're + # called. We could do it at import time, but then we might make the wrong + # decision if someone goes wild with monkeypatching select.poll after + # we're imported. + global wait_for_socket + if _have_working_poll(): + wait_for_socket = poll_wait_for_socket + elif hasattr(select, "select"): + wait_for_socket = select_wait_for_socket + else: # Platform-specific: Appengine. + wait_for_socket = null_wait_for_socket + return wait_for_socket(*args, **kwargs) + + +def wait_for_read(sock, timeout=None): + """Waits for reading to be available on a given socket. + Returns True if the socket is readable, or False if the timeout expired. + """ + return wait_for_socket(sock, read=True, timeout=timeout) + + +def wait_for_write(sock, timeout=None): + """Waits for writing to be available on a given socket. + Returns True if the socket is readable, or False if the timeout expired. + """ + return wait_for_socket(sock, write=True, timeout=timeout) From 63a83dc23a7075bb40f2ea827c24aa0b0dbf3088 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 8 Jun 2022 16:04:55 +0200 Subject: [PATCH 0513/1227] Added set_project method to widget It makes sending of project name to model clearer. --- openpype/modules/sync_server/tray/app.py | 9 +++------ openpype/modules/sync_server/tray/widgets.py | 3 +++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/modules/sync_server/tray/app.py b/openpype/modules/sync_server/tray/app.py index dee3bf0ecc..96fad6a247 100644 --- a/openpype/modules/sync_server/tray/app.py +++ b/openpype/modules/sync_server/tray/app.py @@ -101,8 +101,7 @@ class SyncServerWindow(QtWidgets.QDialog): self.representationWidget = repres def showEvent(self, event): - self.representationWidget.model.set_project( - self.projects.current_project) + self.representationWidget.set_project(self.projects.current_project) self.projects.refresh() self._set_running(True) super().showEvent(event) @@ -115,9 +114,7 @@ class SyncServerWindow(QtWidgets.QDialog): if self.projects.current_project is None: return - self.representationWidget.table_view.model().set_project( - self.projects.current_project - ) + self.representationWidget.set_project(self.projects.current_project) project_name = self.projects.current_project if not self.sync_server.get_sync_project_setting(project_name): @@ -131,7 +128,7 @@ class SyncServerWindow(QtWidgets.QDialog): self.projects.show_only_enabled = \ self.show_only_enabled_chk.isChecked() self.projects.refresh() - self.representationWidget.model.set_project(None) + self.representationWidget.set_project(None) def _set_running(self, running): self.representationWidget.model.is_running = running diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 89ab12ea4d..b4ee447ac4 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -253,6 +253,9 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): active_changed = QtCore.Signal() # active index changed message_generated = QtCore.Signal(str) + def set_project(self, project): + self.model.set_project(project) + def _selection_changed(self, _new_selected, _all_selected): idxs = self.selection_model.selectedRows() self._selected_ids = set() From 1494c09d2aa39a4e8b4e7d480c4b0719b0d2116a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 16:17:03 +0200 Subject: [PATCH 0514/1227] added websocket, socketio and engineio into python 2 vendor --- .../python/python_2/engineio/__init__.py | 25 + .../python/python_2/engineio/async_aiohttp.py | 129 ++++ .../python/python_2/engineio/async_asgi.py | 223 ++++++ .../engineio/async_drivers/__init__.py | 0 .../engineio/async_drivers/aiohttp.py | 128 ++++ .../python_2/engineio/async_drivers/asgi.py | 214 ++++++ .../engineio/async_drivers/eventlet.py | 30 + .../python_2/engineio/async_drivers/gevent.py | 63 ++ .../engineio/async_drivers/gevent_uwsgi.py | 156 ++++ .../python_2/engineio/async_drivers/sanic.py | 144 ++++ .../engineio/async_drivers/threading.py | 17 + .../engineio/async_drivers/tornado.py | 184 +++++ .../python_2/engineio/async_eventlet.py | 30 + .../python/python_2/engineio/async_gevent.py | 63 ++ .../python_2/engineio/async_gevent_uwsgi.py | 155 ++++ .../python/python_2/engineio/async_sanic.py | 145 ++++ .../python_2/engineio/async_threading.py | 17 + .../python/python_2/engineio/async_tornado.py | 154 ++++ .../python_2/engineio/asyncio_client.py | 556 ++++++++++++++ .../python_2/engineio/asyncio_server.py | 444 +++++++++++ .../python_2/engineio/asyncio_socket.py | 235 ++++++ .../vendor/python/python_2/engineio/client.py | 641 ++++++++++++++++ .../python/python_2/engineio/exceptions.py | 22 + .../python/python_2/engineio/middleware.py | 87 +++ .../vendor/python/python_2/engineio/packet.py | 92 +++ .../python/python_2/engineio/payload.py | 80 ++ .../vendor/python/python_2/engineio/server.py | 633 +++++++++++++++ .../vendor/python/python_2/engineio/socket.py | 247 ++++++ .../python/python_2/engineio/static_files.py | 55 ++ .../python/python_2/socketio/__init__.py | 35 + .../vendor/python/python_2/socketio/asgi.py | 36 + .../python_2/socketio/asyncio_client.py | 445 +++++++++++ .../python_2/socketio/asyncio_manager.py | 58 ++ .../python_2/socketio/asyncio_namespace.py | 204 +++++ .../socketio/asyncio_pubsub_manager.py | 163 ++++ .../socketio/asyncio_redis_manager.py | 107 +++ .../python_2/socketio/asyncio_server.py | 515 +++++++++++++ .../python/python_2/socketio/base_manager.py | 178 +++++ .../vendor/python/python_2/socketio/client.py | 590 ++++++++++++++ .../python/python_2/socketio/exceptions.py | 26 + .../python/python_2/socketio/kombu_manager.py | 105 +++ .../python/python_2/socketio/middleware.py | 42 + .../python/python_2/socketio/namespace.py | 191 +++++ .../vendor/python/python_2/socketio/packet.py | 179 +++++ .../python_2/socketio/pubsub_manager.py | 154 ++++ .../python/python_2/socketio/redis_manager.py | 111 +++ .../vendor/python/python_2/socketio/server.py | 719 ++++++++++++++++++ .../python/python_2/socketio/tornado.py | 11 + .../python/python_2/socketio/zmq_manager.py | 111 +++ .../python/python_2/websocket/__init__.py | 28 + .../vendor/python/python_2/websocket/_abnf.py | 458 +++++++++++ .../vendor/python/python_2/websocket/_app.py | 399 ++++++++++ .../python/python_2/websocket/_cookiejar.py | 78 ++ .../vendor/python/python_2/websocket/_core.py | 595 +++++++++++++++ .../python/python_2/websocket/_exceptions.py | 86 +++ .../python/python_2/websocket/_handshake.py | 212 ++++++ .../vendor/python/python_2/websocket/_http.py | 335 ++++++++ .../python/python_2/websocket/_logging.py | 92 +++ .../python/python_2/websocket/_socket.py | 176 +++++ .../python/python_2/websocket/_ssl_compat.py | 53 ++ .../vendor/python/python_2/websocket/_url.py | 178 +++++ .../python/python_2/websocket/_utils.py | 110 +++ .../python_2/websocket/tests/__init__.py | 0 .../websocket/tests/data/header01.txt | 6 + .../websocket/tests/data/header02.txt | 6 + .../websocket/tests/data/header03.txt | 6 + .../python_2/websocket/tests/test_abnf.py | 77 ++ .../python_2/websocket/tests/test_app.py | 137 ++++ .../websocket/tests/test_cookiejar.py | 117 +++ .../python_2/websocket/tests/test_http.py | 109 +++ .../python_2/websocket/tests/test_url.py | 309 ++++++++ .../websocket/tests/test_websocket.py | 433 +++++++++++ 72 files changed, 12949 insertions(+) create mode 100644 openpype/vendor/python/python_2/engineio/__init__.py create mode 100644 openpype/vendor/python/python_2/engineio/async_aiohttp.py create mode 100644 openpype/vendor/python/python_2/engineio/async_asgi.py create mode 100644 openpype/vendor/python/python_2/engineio/async_drivers/__init__.py create mode 100644 openpype/vendor/python/python_2/engineio/async_drivers/aiohttp.py create mode 100644 openpype/vendor/python/python_2/engineio/async_drivers/asgi.py create mode 100644 openpype/vendor/python/python_2/engineio/async_drivers/eventlet.py create mode 100644 openpype/vendor/python/python_2/engineio/async_drivers/gevent.py create mode 100644 openpype/vendor/python/python_2/engineio/async_drivers/gevent_uwsgi.py create mode 100644 openpype/vendor/python/python_2/engineio/async_drivers/sanic.py create mode 100644 openpype/vendor/python/python_2/engineio/async_drivers/threading.py create mode 100644 openpype/vendor/python/python_2/engineio/async_drivers/tornado.py create mode 100644 openpype/vendor/python/python_2/engineio/async_eventlet.py create mode 100644 openpype/vendor/python/python_2/engineio/async_gevent.py create mode 100644 openpype/vendor/python/python_2/engineio/async_gevent_uwsgi.py create mode 100644 openpype/vendor/python/python_2/engineio/async_sanic.py create mode 100644 openpype/vendor/python/python_2/engineio/async_threading.py create mode 100644 openpype/vendor/python/python_2/engineio/async_tornado.py create mode 100644 openpype/vendor/python/python_2/engineio/asyncio_client.py create mode 100644 openpype/vendor/python/python_2/engineio/asyncio_server.py create mode 100644 openpype/vendor/python/python_2/engineio/asyncio_socket.py create mode 100644 openpype/vendor/python/python_2/engineio/client.py create mode 100644 openpype/vendor/python/python_2/engineio/exceptions.py create mode 100644 openpype/vendor/python/python_2/engineio/middleware.py create mode 100644 openpype/vendor/python/python_2/engineio/packet.py create mode 100644 openpype/vendor/python/python_2/engineio/payload.py create mode 100644 openpype/vendor/python/python_2/engineio/server.py create mode 100644 openpype/vendor/python/python_2/engineio/socket.py create mode 100644 openpype/vendor/python/python_2/engineio/static_files.py create mode 100644 openpype/vendor/python/python_2/socketio/__init__.py create mode 100644 openpype/vendor/python/python_2/socketio/asgi.py create mode 100644 openpype/vendor/python/python_2/socketio/asyncio_client.py create mode 100644 openpype/vendor/python/python_2/socketio/asyncio_manager.py create mode 100644 openpype/vendor/python/python_2/socketio/asyncio_namespace.py create mode 100644 openpype/vendor/python/python_2/socketio/asyncio_pubsub_manager.py create mode 100644 openpype/vendor/python/python_2/socketio/asyncio_redis_manager.py create mode 100644 openpype/vendor/python/python_2/socketio/asyncio_server.py create mode 100644 openpype/vendor/python/python_2/socketio/base_manager.py create mode 100644 openpype/vendor/python/python_2/socketio/client.py create mode 100644 openpype/vendor/python/python_2/socketio/exceptions.py create mode 100644 openpype/vendor/python/python_2/socketio/kombu_manager.py create mode 100644 openpype/vendor/python/python_2/socketio/middleware.py create mode 100644 openpype/vendor/python/python_2/socketio/namespace.py create mode 100644 openpype/vendor/python/python_2/socketio/packet.py create mode 100644 openpype/vendor/python/python_2/socketio/pubsub_manager.py create mode 100644 openpype/vendor/python/python_2/socketio/redis_manager.py create mode 100644 openpype/vendor/python/python_2/socketio/server.py create mode 100644 openpype/vendor/python/python_2/socketio/tornado.py create mode 100644 openpype/vendor/python/python_2/socketio/zmq_manager.py create mode 100644 openpype/vendor/python/python_2/websocket/__init__.py create mode 100644 openpype/vendor/python/python_2/websocket/_abnf.py create mode 100644 openpype/vendor/python/python_2/websocket/_app.py create mode 100644 openpype/vendor/python/python_2/websocket/_cookiejar.py create mode 100644 openpype/vendor/python/python_2/websocket/_core.py create mode 100644 openpype/vendor/python/python_2/websocket/_exceptions.py create mode 100644 openpype/vendor/python/python_2/websocket/_handshake.py create mode 100644 openpype/vendor/python/python_2/websocket/_http.py create mode 100644 openpype/vendor/python/python_2/websocket/_logging.py create mode 100644 openpype/vendor/python/python_2/websocket/_socket.py create mode 100644 openpype/vendor/python/python_2/websocket/_ssl_compat.py create mode 100644 openpype/vendor/python/python_2/websocket/_url.py create mode 100644 openpype/vendor/python/python_2/websocket/_utils.py create mode 100644 openpype/vendor/python/python_2/websocket/tests/__init__.py create mode 100644 openpype/vendor/python/python_2/websocket/tests/data/header01.txt create mode 100644 openpype/vendor/python/python_2/websocket/tests/data/header02.txt create mode 100644 openpype/vendor/python/python_2/websocket/tests/data/header03.txt create mode 100644 openpype/vendor/python/python_2/websocket/tests/test_abnf.py create mode 100644 openpype/vendor/python/python_2/websocket/tests/test_app.py create mode 100644 openpype/vendor/python/python_2/websocket/tests/test_cookiejar.py create mode 100644 openpype/vendor/python/python_2/websocket/tests/test_http.py create mode 100644 openpype/vendor/python/python_2/websocket/tests/test_url.py create mode 100644 openpype/vendor/python/python_2/websocket/tests/test_websocket.py diff --git a/openpype/vendor/python/python_2/engineio/__init__.py b/openpype/vendor/python/python_2/engineio/__init__.py new file mode 100644 index 0000000000..888bf40190 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/__init__.py @@ -0,0 +1,25 @@ +import sys + +from .client import Client +from .middleware import WSGIApp, Middleware +from .server import Server +if sys.version_info >= (3, 5): # pragma: no cover + from .asyncio_server import AsyncServer + from .asyncio_client import AsyncClient + from .async_drivers.asgi import ASGIApp + try: + from .async_drivers.tornado import get_tornado_handler + except ImportError: + get_tornado_handler = None +else: # pragma: no cover + AsyncServer = None + AsyncClient = None + get_tornado_handler = None + ASGIApp = None + +__version__ = '3.8.2.post1' + +__all__ = ['__version__', 'Server', 'WSGIApp', 'Middleware', 'Client'] +if AsyncServer is not None: # pragma: no cover + __all__ += ['AsyncServer', 'ASGIApp', 'get_tornado_handler', + 'AsyncClient'], diff --git a/openpype/vendor/python/python_2/engineio/async_aiohttp.py b/openpype/vendor/python/python_2/engineio/async_aiohttp.py new file mode 100644 index 0000000000..ad0252dff9 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/async_aiohttp.py @@ -0,0 +1,129 @@ +import asyncio +import sys +from urllib.parse import urlsplit + +from aiohttp.web import Response, WebSocketResponse +import six + + +def create_route(app, engineio_server, engineio_endpoint): + """This function sets up the engine.io endpoint as a route for the + application. + + Note that both GET and POST requests must be hooked up on the engine.io + endpoint. + """ + app.router.add_get(engineio_endpoint, engineio_server.handle_request) + app.router.add_post(engineio_endpoint, engineio_server.handle_request) + app.router.add_route('OPTIONS', engineio_endpoint, + engineio_server.handle_request) + + +def translate_request(request): + """This function takes the arguments passed to the request handler and + uses them to generate a WSGI compatible environ dictionary. + """ + message = request._message + payload = request._payload + + uri_parts = urlsplit(message.path) + environ = { + 'wsgi.input': payload, + 'wsgi.errors': sys.stderr, + 'wsgi.version': (1, 0), + 'wsgi.async': True, + 'wsgi.multithread': False, + 'wsgi.multiprocess': False, + 'wsgi.run_once': False, + 'SERVER_SOFTWARE': 'aiohttp', + 'REQUEST_METHOD': message.method, + 'QUERY_STRING': uri_parts.query or '', + 'RAW_URI': message.path, + 'SERVER_PROTOCOL': 'HTTP/%s.%s' % message.version, + 'REMOTE_ADDR': '127.0.0.1', + 'REMOTE_PORT': '0', + 'SERVER_NAME': 'aiohttp', + 'SERVER_PORT': '0', + 'aiohttp.request': request + } + + for hdr_name, hdr_value in message.headers.items(): + hdr_name = hdr_name.upper() + if hdr_name == 'CONTENT-TYPE': + environ['CONTENT_TYPE'] = hdr_value + continue + elif hdr_name == 'CONTENT-LENGTH': + environ['CONTENT_LENGTH'] = hdr_value + continue + + key = 'HTTP_%s' % hdr_name.replace('-', '_') + if key in environ: + hdr_value = '%s,%s' % (environ[key], hdr_value) + + environ[key] = hdr_value + + environ['wsgi.url_scheme'] = environ.get('HTTP_X_FORWARDED_PROTO', 'http') + + path_info = uri_parts.path + + environ['PATH_INFO'] = path_info + environ['SCRIPT_NAME'] = '' + + return environ + + +def make_response(status, headers, payload, environ): + """This function generates an appropriate response object for this async + mode. + """ + return Response(body=payload, status=int(status.split()[0]), + headers=headers) + + +class WebSocket(object): # pragma: no cover + """ + This wrapper class provides a aiohttp WebSocket interface that is + somewhat compatible with eventlet's implementation. + """ + def __init__(self, handler): + self.handler = handler + self._sock = None + + async def __call__(self, environ): + request = environ['aiohttp.request'] + self._sock = WebSocketResponse() + await self._sock.prepare(request) + + self.environ = environ + await self.handler(self) + return self._sock + + async def close(self): + await self._sock.close() + + async def send(self, message): + if isinstance(message, bytes): + f = self._sock.send_bytes + else: + f = self._sock.send_str + if asyncio.iscoroutinefunction(f): + await f(message) + else: + f(message) + + async def wait(self): + msg = await self._sock.receive() + if not isinstance(msg.data, six.binary_type) and \ + not isinstance(msg.data, six.text_type): + raise IOError() + return msg.data + + +_async = { + 'asyncio': True, + 'create_route': create_route, + 'translate_request': translate_request, + 'make_response': make_response, + 'websocket': sys.modules[__name__], + 'websocket_class': 'WebSocket' +} diff --git a/openpype/vendor/python/python_2/engineio/async_asgi.py b/openpype/vendor/python/python_2/engineio/async_asgi.py new file mode 100644 index 0000000000..d216f31c77 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/async_asgi.py @@ -0,0 +1,223 @@ +import sys + + +class ASGIApp: + """ASGI application middleware for Engine.IO. + + This middleware dispatches traffic to an Engine.IO application. It can + also serve a list of static files to the client, or forward unrelated + HTTP traffic to another ASGI application. + + :param engineio_server: The Engine.IO server. Must be an instance of the + ``engineio.AsyncServer`` class. + :param static_files: A dictionary where the keys are URLs that should be + served as static files. For each URL, the value is + a dictionary with ``content_type`` and ``filename`` + keys. This option is intended to be used for serving + client files during development. + :param other_asgi_app: A separate ASGI app that receives all other traffic. + :param engineio_path: The endpoint where the Engine.IO application should + be installed. The default value is appropriate for + most cases. + + Example usage:: + + import engineio + import uvicorn + + eio = engineio.AsyncServer() + app = engineio.ASGIApp(eio, static_files={ + '/': {'content_type': 'text/html', 'filename': 'index.html'}, + '/index.html': {'content_type': 'text/html', + 'filename': 'index.html'}, + }) + uvicorn.run(app, '127.0.0.1', 5000) + """ + def __init__(self, engineio_server, other_asgi_app=None, + static_files=None, engineio_path='engine.io'): + self.engineio_server = engineio_server + self.other_asgi_app = other_asgi_app + self.engineio_path = engineio_path.strip('/') + self.static_files = static_files or {} + + def __call__(self, scope): + if scope['type'] in ['http', 'websocket'] and \ + scope['path'].startswith('/{0}/'.format(self.engineio_path)): + return self.engineio_asgi_app(scope) + elif scope['type'] == 'http' and scope['path'] in self.static_files: + return self.serve_static_file(scope) + elif self.other_asgi_app is not None: + return self.other_asgi_app(scope) + elif scope['type'] == 'lifespan': + return self.lifespan + else: + return self.not_found + + def engineio_asgi_app(self, scope): + async def _app(receive, send): + await self.engineio_server.handle_request(scope, receive, send) + return _app + + def serve_static_file(self, scope): + async def _send_static_file(receive, send): # pragma: no cover + event = await receive() + if event['type'] == 'http.request': + if scope['path'] in self.static_files: + content_type = self.static_files[scope['path']][ + 'content_type'].encode('utf-8') + filename = self.static_files[scope['path']]['filename'] + status_code = 200 + with open(filename, 'rb') as f: + payload = f.read() + else: + content_type = b'text/plain' + status_code = 404 + payload = b'not found' + await send({'type': 'http.response.start', + 'status': status_code, + 'headers': [(b'Content-Type', content_type)]}) + await send({'type': 'http.response.body', + 'body': payload}) + return _send_static_file + + async def lifespan(self, receive, send): + event = await receive() + if event['type'] == 'lifespan.startup': + await send({'type': 'lifespan.startup.complete'}) + elif event['type'] == 'lifespan.shutdown': + await send({'type': 'lifespan.shutdown.complete'}) + + async def not_found(self, receive, send): + """Return a 404 Not Found error to the client.""" + await send({'type': 'http.response.start', + 'status': 404, + 'headers': [(b'Content-Type', b'text/plain')]}) + await send({'type': 'http.response.body', + 'body': b'not found'}) + + +async def translate_request(scope, receive, send): + class AwaitablePayload(object): # pragma: no cover + def __init__(self, payload): + self.payload = payload or b'' + + async def read(self, length=None): + if length is None: + r = self.payload + self.payload = b'' + else: + r = self.payload[:length] + self.payload = self.payload[length:] + return r + + event = await receive() + payload = b'' + if event['type'] == 'http.request': + payload += event.get('body') or b'' + while event.get('more_body'): + event = await receive() + if event['type'] == 'http.request': + payload += event.get('body') or b'' + elif event['type'] == 'websocket.connect': + await send({'type': 'websocket.accept'}) + else: + return {} + + raw_uri = scope['path'].encode('utf-8') + if 'query_string' in scope and scope['query_string']: + raw_uri += b'?' + scope['query_string'] + environ = { + 'wsgi.input': AwaitablePayload(payload), + 'wsgi.errors': sys.stderr, + 'wsgi.version': (1, 0), + 'wsgi.async': True, + 'wsgi.multithread': False, + 'wsgi.multiprocess': False, + 'wsgi.run_once': False, + 'SERVER_SOFTWARE': 'asgi', + 'REQUEST_METHOD': scope.get('method', 'GET'), + 'PATH_INFO': scope['path'], + 'QUERY_STRING': scope.get('query_string', b'').decode('utf-8'), + 'RAW_URI': raw_uri.decode('utf-8'), + 'SCRIPT_NAME': '', + 'SERVER_PROTOCOL': 'HTTP/1.1', + 'REMOTE_ADDR': '127.0.0.1', + 'REMOTE_PORT': '0', + 'SERVER_NAME': 'asgi', + 'SERVER_PORT': '0', + 'asgi.receive': receive, + 'asgi.send': send, + } + + for hdr_name, hdr_value in scope['headers']: + hdr_name = hdr_name.upper().decode('utf-8') + hdr_value = hdr_value.decode('utf-8') + if hdr_name == 'CONTENT-TYPE': + environ['CONTENT_TYPE'] = hdr_value + continue + elif hdr_name == 'CONTENT-LENGTH': + environ['CONTENT_LENGTH'] = hdr_value + continue + + key = 'HTTP_%s' % hdr_name.replace('-', '_') + if key in environ: + hdr_value = '%s,%s' % (environ[key], hdr_value) + + environ[key] = hdr_value + + environ['wsgi.url_scheme'] = environ.get('HTTP_X_FORWARDED_PROTO', 'http') + return environ + + +async def make_response(status, headers, payload, environ): + headers = [(h[0].encode('utf-8'), h[1].encode('utf-8')) for h in headers] + await environ['asgi.send']({'type': 'http.response.start', + 'status': int(status.split(' ')[0]), + 'headers': headers}) + await environ['asgi.send']({'type': 'http.response.body', + 'body': payload}) + + +class WebSocket(object): # pragma: no cover + """ + This wrapper class provides an asgi WebSocket interface that is + somewhat compatible with eventlet's implementation. + """ + def __init__(self, handler): + self.handler = handler + self.asgi_receive = None + self.asgi_send = None + + async def __call__(self, environ): + self.asgi_receive = environ['asgi.receive'] + self.asgi_send = environ['asgi.send'] + await self.handler(self) + + async def close(self): + await self.asgi_send({'type': 'websocket.close'}) + + async def send(self, message): + msg_bytes = None + msg_text = None + if isinstance(message, bytes): + msg_bytes = message + else: + msg_text = message + await self.asgi_send({'type': 'websocket.send', + 'bytes': msg_bytes, + 'text': msg_text}) + + async def wait(self): + event = await self.asgi_receive() + if event['type'] != 'websocket.receive': + raise IOError() + return event.get('bytes') or event.get('text') + + +_async = { + 'asyncio': True, + 'translate_request': translate_request, + 'make_response': make_response, + 'websocket': sys.modules[__name__], + 'websocket_class': 'WebSocket' +} diff --git a/openpype/vendor/python/python_2/engineio/async_drivers/__init__.py b/openpype/vendor/python/python_2/engineio/async_drivers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/vendor/python/python_2/engineio/async_drivers/aiohttp.py b/openpype/vendor/python/python_2/engineio/async_drivers/aiohttp.py new file mode 100644 index 0000000000..ad6987649d --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/async_drivers/aiohttp.py @@ -0,0 +1,128 @@ +import asyncio +import sys +from urllib.parse import urlsplit + +from aiohttp.web import Response, WebSocketResponse +import six + + +def create_route(app, engineio_server, engineio_endpoint): + """This function sets up the engine.io endpoint as a route for the + application. + + Note that both GET and POST requests must be hooked up on the engine.io + endpoint. + """ + app.router.add_get(engineio_endpoint, engineio_server.handle_request) + app.router.add_post(engineio_endpoint, engineio_server.handle_request) + app.router.add_route('OPTIONS', engineio_endpoint, + engineio_server.handle_request) + + +def translate_request(request): + """This function takes the arguments passed to the request handler and + uses them to generate a WSGI compatible environ dictionary. + """ + message = request._message + payload = request._payload + + uri_parts = urlsplit(message.path) + environ = { + 'wsgi.input': payload, + 'wsgi.errors': sys.stderr, + 'wsgi.version': (1, 0), + 'wsgi.async': True, + 'wsgi.multithread': False, + 'wsgi.multiprocess': False, + 'wsgi.run_once': False, + 'SERVER_SOFTWARE': 'aiohttp', + 'REQUEST_METHOD': message.method, + 'QUERY_STRING': uri_parts.query or '', + 'RAW_URI': message.path, + 'SERVER_PROTOCOL': 'HTTP/%s.%s' % message.version, + 'REMOTE_ADDR': '127.0.0.1', + 'REMOTE_PORT': '0', + 'SERVER_NAME': 'aiohttp', + 'SERVER_PORT': '0', + 'aiohttp.request': request + } + + for hdr_name, hdr_value in message.headers.items(): + hdr_name = hdr_name.upper() + if hdr_name == 'CONTENT-TYPE': + environ['CONTENT_TYPE'] = hdr_value + continue + elif hdr_name == 'CONTENT-LENGTH': + environ['CONTENT_LENGTH'] = hdr_value + continue + + key = 'HTTP_%s' % hdr_name.replace('-', '_') + if key in environ: + hdr_value = '%s,%s' % (environ[key], hdr_value) + + environ[key] = hdr_value + + environ['wsgi.url_scheme'] = environ.get('HTTP_X_FORWARDED_PROTO', 'http') + + path_info = uri_parts.path + + environ['PATH_INFO'] = path_info + environ['SCRIPT_NAME'] = '' + + return environ + + +def make_response(status, headers, payload, environ): + """This function generates an appropriate response object for this async + mode. + """ + return Response(body=payload, status=int(status.split()[0]), + headers=headers) + + +class WebSocket(object): # pragma: no cover + """ + This wrapper class provides a aiohttp WebSocket interface that is + somewhat compatible with eventlet's implementation. + """ + def __init__(self, handler): + self.handler = handler + self._sock = None + + async def __call__(self, environ): + request = environ['aiohttp.request'] + self._sock = WebSocketResponse() + await self._sock.prepare(request) + + self.environ = environ + await self.handler(self) + return self._sock + + async def close(self): + await self._sock.close() + + async def send(self, message): + if isinstance(message, bytes): + f = self._sock.send_bytes + else: + f = self._sock.send_str + if asyncio.iscoroutinefunction(f): + await f(message) + else: + f(message) + + async def wait(self): + msg = await self._sock.receive() + if not isinstance(msg.data, six.binary_type) and \ + not isinstance(msg.data, six.text_type): + raise IOError() + return msg.data + + +_async = { + 'asyncio': True, + 'create_route': create_route, + 'translate_request': translate_request, + 'make_response': make_response, + 'websocket': WebSocket, +} diff --git a/openpype/vendor/python/python_2/engineio/async_drivers/asgi.py b/openpype/vendor/python/python_2/engineio/async_drivers/asgi.py new file mode 100644 index 0000000000..9f14ef05ff --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/async_drivers/asgi.py @@ -0,0 +1,214 @@ +import os +import sys + +from engineio.static_files import get_static_file + + +class ASGIApp: + """ASGI application middleware for Engine.IO. + + This middleware dispatches traffic to an Engine.IO application. It can + also serve a list of static files to the client, or forward unrelated + HTTP traffic to another ASGI application. + + :param engineio_server: The Engine.IO server. Must be an instance of the + ``engineio.AsyncServer`` class. + :param static_files: A dictionary with static file mapping rules. See the + documentation for details on this argument. + :param other_asgi_app: A separate ASGI app that receives all other traffic. + :param engineio_path: The endpoint where the Engine.IO application should + be installed. The default value is appropriate for + most cases. + + Example usage:: + + import engineio + import uvicorn + + eio = engineio.AsyncServer() + app = engineio.ASGIApp(eio, static_files={ + '/': {'content_type': 'text/html', 'filename': 'index.html'}, + '/index.html': {'content_type': 'text/html', + 'filename': 'index.html'}, + }) + uvicorn.run(app, '127.0.0.1', 5000) + """ + def __init__(self, engineio_server, other_asgi_app=None, + static_files=None, engineio_path='engine.io'): + self.engineio_server = engineio_server + self.other_asgi_app = other_asgi_app + self.engineio_path = engineio_path.strip('/') + self.static_files = static_files or {} + + async def __call__(self, scope, receive, send): + if scope['type'] in ['http', 'websocket'] and \ + scope['path'].startswith('/{0}/'.format(self.engineio_path)): + await self.engineio_server.handle_request(scope, receive, send) + else: + static_file = get_static_file(scope['path'], self.static_files) \ + if scope['type'] == 'http' and self.static_files else None + if static_file: + await self.serve_static_file(static_file, receive, send) + elif self.other_asgi_app is not None: + await self.other_asgi_app(scope, receive, send) + elif scope['type'] == 'lifespan': + await self.lifespan(receive, send) + else: + await self.not_found(receive, send) + + async def serve_static_file(self, static_file, receive, + send): # pragma: no cover + event = await receive() + if event['type'] == 'http.request': + if os.path.exists(static_file['filename']): + with open(static_file['filename'], 'rb') as f: + payload = f.read() + await send({'type': 'http.response.start', + 'status': 200, + 'headers': [(b'Content-Type', static_file[ + 'content_type'].encode('utf-8'))]}) + await send({'type': 'http.response.body', + 'body': payload}) + else: + await self.not_found(receive, send) + + async def lifespan(self, receive, send): + event = await receive() + if event['type'] == 'lifespan.startup': + await send({'type': 'lifespan.startup.complete'}) + elif event['type'] == 'lifespan.shutdown': + await send({'type': 'lifespan.shutdown.complete'}) + + async def not_found(self, receive, send): + """Return a 404 Not Found error to the client.""" + await send({'type': 'http.response.start', + 'status': 404, + 'headers': [(b'Content-Type', b'text/plain')]}) + await send({'type': 'http.response.body', + 'body': b'Not Found'}) + + +async def translate_request(scope, receive, send): + class AwaitablePayload(object): # pragma: no cover + def __init__(self, payload): + self.payload = payload or b'' + + async def read(self, length=None): + if length is None: + r = self.payload + self.payload = b'' + else: + r = self.payload[:length] + self.payload = self.payload[length:] + return r + + event = await receive() + payload = b'' + if event['type'] == 'http.request': + payload += event.get('body') or b'' + while event.get('more_body'): + event = await receive() + if event['type'] == 'http.request': + payload += event.get('body') or b'' + elif event['type'] == 'websocket.connect': + await send({'type': 'websocket.accept'}) + else: + return {} + + raw_uri = scope['path'].encode('utf-8') + if 'query_string' in scope and scope['query_string']: + raw_uri += b'?' + scope['query_string'] + environ = { + 'wsgi.input': AwaitablePayload(payload), + 'wsgi.errors': sys.stderr, + 'wsgi.version': (1, 0), + 'wsgi.async': True, + 'wsgi.multithread': False, + 'wsgi.multiprocess': False, + 'wsgi.run_once': False, + 'SERVER_SOFTWARE': 'asgi', + 'REQUEST_METHOD': scope.get('method', 'GET'), + 'PATH_INFO': scope['path'], + 'QUERY_STRING': scope.get('query_string', b'').decode('utf-8'), + 'RAW_URI': raw_uri.decode('utf-8'), + 'SCRIPT_NAME': '', + 'SERVER_PROTOCOL': 'HTTP/1.1', + 'REMOTE_ADDR': '127.0.0.1', + 'REMOTE_PORT': '0', + 'SERVER_NAME': 'asgi', + 'SERVER_PORT': '0', + 'asgi.receive': receive, + 'asgi.send': send, + } + + for hdr_name, hdr_value in scope['headers']: + hdr_name = hdr_name.upper().decode('utf-8') + hdr_value = hdr_value.decode('utf-8') + if hdr_name == 'CONTENT-TYPE': + environ['CONTENT_TYPE'] = hdr_value + continue + elif hdr_name == 'CONTENT-LENGTH': + environ['CONTENT_LENGTH'] = hdr_value + continue + + key = 'HTTP_%s' % hdr_name.replace('-', '_') + if key in environ: + hdr_value = '%s,%s' % (environ[key], hdr_value) + + environ[key] = hdr_value + + environ['wsgi.url_scheme'] = environ.get('HTTP_X_FORWARDED_PROTO', 'http') + return environ + + +async def make_response(status, headers, payload, environ): + headers = [(h[0].encode('utf-8'), h[1].encode('utf-8')) for h in headers] + await environ['asgi.send']({'type': 'http.response.start', + 'status': int(status.split(' ')[0]), + 'headers': headers}) + await environ['asgi.send']({'type': 'http.response.body', + 'body': payload}) + + +class WebSocket(object): # pragma: no cover + """ + This wrapper class provides an asgi WebSocket interface that is + somewhat compatible with eventlet's implementation. + """ + def __init__(self, handler): + self.handler = handler + self.asgi_receive = None + self.asgi_send = None + + async def __call__(self, environ): + self.asgi_receive = environ['asgi.receive'] + self.asgi_send = environ['asgi.send'] + await self.handler(self) + + async def close(self): + await self.asgi_send({'type': 'websocket.close'}) + + async def send(self, message): + msg_bytes = None + msg_text = None + if isinstance(message, bytes): + msg_bytes = message + else: + msg_text = message + await self.asgi_send({'type': 'websocket.send', + 'bytes': msg_bytes, + 'text': msg_text}) + + async def wait(self): + event = await self.asgi_receive() + if event['type'] != 'websocket.receive': + raise IOError() + return event.get('bytes') or event.get('text') + + +_async = { + 'asyncio': True, + 'translate_request': translate_request, + 'make_response': make_response, + 'websocket': WebSocket, +} diff --git a/openpype/vendor/python/python_2/engineio/async_drivers/eventlet.py b/openpype/vendor/python/python_2/engineio/async_drivers/eventlet.py new file mode 100644 index 0000000000..9be3797cd8 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/async_drivers/eventlet.py @@ -0,0 +1,30 @@ +from __future__ import absolute_import + +from eventlet.green.threading import Thread, Event +from eventlet import queue +from eventlet import sleep +from eventlet.websocket import WebSocketWSGI as _WebSocketWSGI + + +class WebSocketWSGI(_WebSocketWSGI): + def __init__(self, *args, **kwargs): + super(WebSocketWSGI, self).__init__(*args, **kwargs) + self._sock = None + + def __call__(self, environ, start_response): + if 'eventlet.input' not in environ: + raise RuntimeError('You need to use the eventlet server. ' + 'See the Deployment section of the ' + 'documentation for more information.') + self._sock = environ['eventlet.input'].get_socket() + return super(WebSocketWSGI, self).__call__(environ, start_response) + + +_async = { + 'thread': Thread, + 'queue': queue.Queue, + 'queue_empty': queue.Empty, + 'event': Event, + 'websocket': WebSocketWSGI, + 'sleep': sleep, +} diff --git a/openpype/vendor/python/python_2/engineio/async_drivers/gevent.py b/openpype/vendor/python/python_2/engineio/async_drivers/gevent.py new file mode 100644 index 0000000000..024dd0aad5 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/async_drivers/gevent.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import + +import gevent +from gevent import queue +from gevent.event import Event +try: + import geventwebsocket # noqa + _websocket_available = True +except ImportError: + _websocket_available = False + + +class Thread(gevent.Greenlet): # pragma: no cover + """ + This wrapper class provides gevent Greenlet interface that is compatible + with the standard library's Thread class. + """ + def __init__(self, target, args=[], kwargs={}): + super(Thread, self).__init__(target, *args, **kwargs) + + def _run(self): + return self.run() + + +class WebSocketWSGI(object): # pragma: no cover + """ + This wrapper class provides a gevent WebSocket interface that is + compatible with eventlet's implementation. + """ + def __init__(self, app): + self.app = app + + def __call__(self, environ, start_response): + if 'wsgi.websocket' not in environ: + raise RuntimeError('You need to use the gevent-websocket server. ' + 'See the Deployment section of the ' + 'documentation for more information.') + self._sock = environ['wsgi.websocket'] + self.environ = environ + self.version = self._sock.version + self.path = self._sock.path + self.origin = self._sock.origin + self.protocol = self._sock.protocol + return self.app(self) + + def close(self): + return self._sock.close() + + def send(self, message): + return self._sock.send(message) + + def wait(self): + return self._sock.receive() + + +_async = { + 'thread': Thread, + 'queue': queue.JoinableQueue, + 'queue_empty': queue.Empty, + 'event': Event, + 'websocket': WebSocketWSGI if _websocket_available else None, + 'sleep': gevent.sleep, +} diff --git a/openpype/vendor/python/python_2/engineio/async_drivers/gevent_uwsgi.py b/openpype/vendor/python/python_2/engineio/async_drivers/gevent_uwsgi.py new file mode 100644 index 0000000000..07fa2a79df --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/async_drivers/gevent_uwsgi.py @@ -0,0 +1,156 @@ +from __future__ import absolute_import + +import six + +import gevent +from gevent import queue +from gevent.event import Event +import uwsgi +_websocket_available = hasattr(uwsgi, 'websocket_handshake') + + +class Thread(gevent.Greenlet): # pragma: no cover + """ + This wrapper class provides gevent Greenlet interface that is compatible + with the standard library's Thread class. + """ + def __init__(self, target, args=[], kwargs={}): + super(Thread, self).__init__(target, *args, **kwargs) + + def _run(self): + return self.run() + + +class uWSGIWebSocket(object): # pragma: no cover + """ + This wrapper class provides a uWSGI WebSocket interface that is + compatible with eventlet's implementation. + """ + def __init__(self, app): + self.app = app + self._sock = None + + def __call__(self, environ, start_response): + self._sock = uwsgi.connection_fd() + self.environ = environ + + uwsgi.websocket_handshake() + + self._req_ctx = None + if hasattr(uwsgi, 'request_context'): + # uWSGI >= 2.1.x with support for api access across-greenlets + self._req_ctx = uwsgi.request_context() + else: + # use event and queue for sending messages + from gevent.event import Event + from gevent.queue import Queue + from gevent.select import select + self._event = Event() + self._send_queue = Queue() + + # spawn a select greenlet + def select_greenlet_runner(fd, event): + """Sets event when data becomes available to read on fd.""" + while True: + event.set() + try: + select([fd], [], [])[0] + except ValueError: + break + self._select_greenlet = gevent.spawn( + select_greenlet_runner, + self._sock, + self._event) + + self.app(self) + + def close(self): + """Disconnects uWSGI from the client.""" + uwsgi.disconnect() + if self._req_ctx is None: + # better kill it here in case wait() is not called again + self._select_greenlet.kill() + self._event.set() + + def _send(self, msg): + """Transmits message either in binary or UTF-8 text mode, + depending on its type.""" + if isinstance(msg, six.binary_type): + method = uwsgi.websocket_send_binary + else: + method = uwsgi.websocket_send + if self._req_ctx is not None: + method(msg, request_context=self._req_ctx) + else: + method(msg) + + def _decode_received(self, msg): + """Returns either bytes or str, depending on message type.""" + if not isinstance(msg, six.binary_type): + # already decoded - do nothing + return msg + # only decode from utf-8 if message is not binary data + type = six.byte2int(msg[0:1]) + if type >= 48: # no binary + return msg.decode('utf-8') + # binary message, don't try to decode + return msg + + def send(self, msg): + """Queues a message for sending. Real transmission is done in + wait method. + Sends directly if uWSGI version is new enough.""" + if self._req_ctx is not None: + self._send(msg) + else: + self._send_queue.put(msg) + self._event.set() + + def wait(self): + """Waits and returns received messages. + If running in compatibility mode for older uWSGI versions, + it also sends messages that have been queued by send(). + A return value of None means that connection was closed. + This must be called repeatedly. For uWSGI < 2.1.x it must + be called from the main greenlet.""" + while True: + if self._req_ctx is not None: + try: + msg = uwsgi.websocket_recv(request_context=self._req_ctx) + except IOError: # connection closed + return None + return self._decode_received(msg) + else: + # we wake up at least every 3 seconds to let uWSGI + # do its ping/ponging + event_set = self._event.wait(timeout=3) + if event_set: + self._event.clear() + # maybe there is something to send + msgs = [] + while True: + try: + msgs.append(self._send_queue.get(block=False)) + except gevent.queue.Empty: + break + for msg in msgs: + self._send(msg) + # maybe there is something to receive, if not, at least + # ensure uWSGI does its ping/ponging + try: + msg = uwsgi.websocket_recv_nb() + except IOError: # connection closed + self._select_greenlet.kill() + return None + if msg: # message available + return self._decode_received(msg) + + +_async = { + 'thread': Thread, + 'queue': queue.JoinableQueue, + 'queue_empty': queue.Empty, + 'event': Event, + 'websocket': uWSGIWebSocket if _websocket_available else None, + 'sleep': gevent.sleep, +} diff --git a/openpype/vendor/python/python_2/engineio/async_drivers/sanic.py b/openpype/vendor/python/python_2/engineio/async_drivers/sanic.py new file mode 100644 index 0000000000..6929654b92 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/async_drivers/sanic.py @@ -0,0 +1,144 @@ +import sys +from urllib.parse import urlsplit + +from sanic.response import HTTPResponse +try: + from sanic.websocket import WebSocketProtocol +except ImportError: + # the installed version of sanic does not have websocket support + WebSocketProtocol = None +import six + + +def create_route(app, engineio_server, engineio_endpoint): + """This function sets up the engine.io endpoint as a route for the + application. + + Note that both GET and POST requests must be hooked up on the engine.io + endpoint. + """ + app.add_route(engineio_server.handle_request, engineio_endpoint, + methods=['GET', 'POST', 'OPTIONS']) + try: + app.enable_websocket() + except AttributeError: + # ignore, this version does not support websocket + pass + + +def translate_request(request): + """This function takes the arguments passed to the request handler and + uses them to generate a WSGI compatible environ dictionary. + """ + class AwaitablePayload(object): + def __init__(self, payload): + self.payload = payload or b'' + + async def read(self, length=None): + if length is None: + r = self.payload + self.payload = b'' + else: + r = self.payload[:length] + self.payload = self.payload[length:] + return r + + uri_parts = urlsplit(request.url) + environ = { + 'wsgi.input': AwaitablePayload(request.body), + 'wsgi.errors': sys.stderr, + 'wsgi.version': (1, 0), + 'wsgi.async': True, + 'wsgi.multithread': False, + 'wsgi.multiprocess': False, + 'wsgi.run_once': False, + 'SERVER_SOFTWARE': 'sanic', + 'REQUEST_METHOD': request.method, + 'QUERY_STRING': uri_parts.query or '', + 'RAW_URI': request.url, + 'SERVER_PROTOCOL': 'HTTP/' + request.version, + 'REMOTE_ADDR': '127.0.0.1', + 'REMOTE_PORT': '0', + 'SERVER_NAME': 'sanic', + 'SERVER_PORT': '0', + 'sanic.request': request + } + + for hdr_name, hdr_value in request.headers.items(): + hdr_name = hdr_name.upper() + if hdr_name == 'CONTENT-TYPE': + environ['CONTENT_TYPE'] = hdr_value + continue + elif hdr_name == 'CONTENT-LENGTH': + environ['CONTENT_LENGTH'] = hdr_value + continue + + key = 'HTTP_%s' % hdr_name.replace('-', '_') + if key in environ: + hdr_value = '%s,%s' % (environ[key], hdr_value) + + environ[key] = hdr_value + + environ['wsgi.url_scheme'] = environ.get('HTTP_X_FORWARDED_PROTO', 'http') + + path_info = uri_parts.path + + environ['PATH_INFO'] = path_info + environ['SCRIPT_NAME'] = '' + + return environ + + +def make_response(status, headers, payload, environ): + """This function generates an appropriate response object for this async + mode. + """ + headers_dict = {} + content_type = None + for h in headers: + if h[0].lower() == 'content-type': + content_type = h[1] + else: + headers_dict[h[0]] = h[1] + return HTTPResponse(body_bytes=payload, content_type=content_type, + status=int(status.split()[0]), headers=headers_dict) + + +class WebSocket(object): # pragma: no cover + """ + This wrapper class provides a sanic WebSocket interface that is + somewhat compatible with eventlet's implementation. + """ + def __init__(self, handler): + self.handler = handler + self._sock = None + + async def __call__(self, environ): + request = environ['sanic.request'] + protocol = request.transport.get_protocol() + self._sock = await protocol.websocket_handshake(request) + + self.environ = environ + await self.handler(self) + + async def close(self): + await self._sock.close() + + async def send(self, message): + await self._sock.send(message) + + async def wait(self): + data = await self._sock.recv() + if not isinstance(data, six.binary_type) and \ + not isinstance(data, six.text_type): + raise IOError() + return data + + +_async = { + 'asyncio': True, + 'create_route': create_route, + 'translate_request': translate_request, + 'make_response': make_response, + 'websocket': WebSocket if WebSocketProtocol else None, +} diff --git a/openpype/vendor/python/python_2/engineio/async_drivers/threading.py b/openpype/vendor/python/python_2/engineio/async_drivers/threading.py new file mode 100644 index 0000000000..9b53756681 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/async_drivers/threading.py @@ -0,0 +1,17 @@ +from __future__ import absolute_import +import threading +import time + +try: + import queue +except ImportError: # pragma: no cover + import Queue as queue + +_async = { + 'thread': threading.Thread, + 'queue': queue.Queue, + 'queue_empty': queue.Empty, + 'event': threading.Event, + 'websocket': None, + 'sleep': time.sleep, +} diff --git a/openpype/vendor/python/python_2/engineio/async_drivers/tornado.py b/openpype/vendor/python/python_2/engineio/async_drivers/tornado.py new file mode 100644 index 0000000000..adfe18f5a2 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/async_drivers/tornado.py @@ -0,0 +1,184 @@ +import asyncio +import sys +from urllib.parse import urlsplit +from .. import exceptions + +import tornado.web +import tornado.websocket +import six + + +def get_tornado_handler(engineio_server): + class Handler(tornado.websocket.WebSocketHandler): # pragma: no cover + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if isinstance(engineio_server.cors_allowed_origins, + six.string_types): + if engineio_server.cors_allowed_origins == '*': + self.allowed_origins = None + else: + self.allowed_origins = [ + engineio_server.cors_allowed_origins] + else: + self.allowed_origins = engineio_server.cors_allowed_origins + self.receive_queue = asyncio.Queue() + + async def get(self, *args, **kwargs): + if self.request.headers.get('Upgrade', '').lower() == 'websocket': + ret = super().get(*args, **kwargs) + if asyncio.iscoroutine(ret): + await ret + else: + await engineio_server.handle_request(self) + + async def open(self, *args, **kwargs): + # this is the handler for the websocket request + asyncio.ensure_future(engineio_server.handle_request(self)) + + async def post(self, *args, **kwargs): + await engineio_server.handle_request(self) + + async def options(self, *args, **kwargs): + await engineio_server.handle_request(self) + + async def on_message(self, message): + await self.receive_queue.put(message) + + async def get_next_message(self): + return await self.receive_queue.get() + + def on_close(self): + self.receive_queue.put_nowait(None) + + def check_origin(self, origin): + if self.allowed_origins is None or origin in self.allowed_origins: + return True + return super().check_origin(origin) + + def get_compression_options(self): + # enable compression + return {} + + return Handler + + +def translate_request(handler): + """This function takes the arguments passed to the request handler and + uses them to generate a WSGI compatible environ dictionary. + """ + class AwaitablePayload(object): + def __init__(self, payload): + self.payload = payload or b'' + + async def read(self, length=None): + if length is None: + r = self.payload + self.payload = b'' + else: + r = self.payload[:length] + self.payload = self.payload[length:] + return r + + payload = handler.request.body + + uri_parts = urlsplit(handler.request.path) + full_uri = handler.request.path + if handler.request.query: # pragma: no cover + full_uri += '?' + handler.request.query + environ = { + 'wsgi.input': AwaitablePayload(payload), + 'wsgi.errors': sys.stderr, + 'wsgi.version': (1, 0), + 'wsgi.async': True, + 'wsgi.multithread': False, + 'wsgi.multiprocess': False, + 'wsgi.run_once': False, + 'SERVER_SOFTWARE': 'aiohttp', + 'REQUEST_METHOD': handler.request.method, + 'QUERY_STRING': handler.request.query or '', + 'RAW_URI': full_uri, + 'SERVER_PROTOCOL': 'HTTP/%s' % handler.request.version, + 'REMOTE_ADDR': '127.0.0.1', + 'REMOTE_PORT': '0', + 'SERVER_NAME': 'aiohttp', + 'SERVER_PORT': '0', + 'tornado.handler': handler + } + + for hdr_name, hdr_value in handler.request.headers.items(): + hdr_name = hdr_name.upper() + if hdr_name == 'CONTENT-TYPE': + environ['CONTENT_TYPE'] = hdr_value + continue + elif hdr_name == 'CONTENT-LENGTH': + environ['CONTENT_LENGTH'] = hdr_value + continue + + key = 'HTTP_%s' % hdr_name.replace('-', '_') + environ[key] = hdr_value + + environ['wsgi.url_scheme'] = environ.get('HTTP_X_FORWARDED_PROTO', 'http') + + path_info = uri_parts.path + + environ['PATH_INFO'] = path_info + environ['SCRIPT_NAME'] = '' + + return environ + + +def make_response(status, headers, payload, environ): + """This function generates an appropriate response object for this async + mode. + """ + tornado_handler = environ['tornado.handler'] + try: + tornado_handler.set_status(int(status.split()[0])) + except RuntimeError: # pragma: no cover + # for websocket connections Tornado does not accept a response, since + # it already emitted the 101 status code + return + for header, value in headers: + tornado_handler.set_header(header, value) + tornado_handler.write(payload) + tornado_handler.finish() + + +class WebSocket(object): # pragma: no cover + """ + This wrapper class provides a tornado WebSocket interface that is + somewhat compatible with eventlet's implementation. + """ + def __init__(self, handler): + self.handler = handler + self.tornado_handler = None + + async def __call__(self, environ): + self.tornado_handler = environ['tornado.handler'] + self.environ = environ + await self.handler(self) + + async def close(self): + self.tornado_handler.close() + + async def send(self, message): + try: + self.tornado_handler.write_message( + message, binary=isinstance(message, bytes)) + except tornado.websocket.WebSocketClosedError: + raise exceptions.EngineIOError() + + async def wait(self): + msg = await self.tornado_handler.get_next_message() + if not isinstance(msg, six.binary_type) and \ + not isinstance(msg, six.text_type): + raise IOError() + return msg + + +_async = { + 'asyncio': True, + 'translate_request': translate_request, + 'make_response': make_response, + 'websocket': WebSocket, +} diff --git a/openpype/vendor/python/python_2/engineio/async_eventlet.py b/openpype/vendor/python/python_2/engineio/async_eventlet.py new file mode 100644 index 0000000000..042a67499b --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/async_eventlet.py @@ -0,0 +1,30 @@ +import importlib +import sys + +from eventlet import sleep +from eventlet.websocket import WebSocketWSGI as _WebSocketWSGI + + +class WebSocketWSGI(_WebSocketWSGI): + def __init__(self, *args, **kwargs): + super(WebSocketWSGI, self).__init__(*args, **kwargs) + self._sock = None + + def __call__(self, environ, start_response): + if 'eventlet.input' not in environ: + raise RuntimeError('You need to use the eventlet server. ' + 'See the Deployment section of the ' + 'documentation for more information.') + self._sock = environ['eventlet.input'].get_socket() + return super(WebSocketWSGI, self).__call__(environ, start_response) + + +_async = { + 'threading': importlib.import_module('eventlet.green.threading'), + 'thread_class': 'Thread', + 'queue': importlib.import_module('eventlet.queue'), + 'queue_class': 'Queue', + 'websocket': sys.modules[__name__], + 'websocket_class': 'WebSocketWSGI', + 'sleep': sleep +} diff --git a/openpype/vendor/python/python_2/engineio/async_gevent.py b/openpype/vendor/python/python_2/engineio/async_gevent.py new file mode 100644 index 0000000000..34a9e43ce3 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/async_gevent.py @@ -0,0 +1,63 @@ +import importlib +import sys + +import gevent +try: + import geventwebsocket # noqa + _websocket_available = True +except ImportError: + _websocket_available = False + + +class Thread(gevent.Greenlet): # pragma: no cover + """ + This wrapper class provides gevent Greenlet interface that is compatible + with the standard library's Thread class. + """ + def __init__(self, target, args=[], kwargs={}): + super(Thread, self).__init__(target, *args, **kwargs) + + def _run(self): + return self.run() + + +class WebSocketWSGI(object): # pragma: no cover + """ + This wrapper class provides a gevent WebSocket interface that is + compatible with eventlet's implementation. + """ + def __init__(self, app): + self.app = app + + def __call__(self, environ, start_response): + if 'wsgi.websocket' not in environ: + raise RuntimeError('You need to use the gevent-websocket server. ' + 'See the Deployment section of the ' + 'documentation for more information.') + self._sock = environ['wsgi.websocket'] + self.environ = environ + self.version = self._sock.version + self.path = self._sock.path + self.origin = self._sock.origin + self.protocol = self._sock.protocol + return self.app(self) + + def close(self): + return self._sock.close() + + def send(self, message): + return self._sock.send(message) + + def wait(self): + return self._sock.receive() + + +_async = { + 'threading': sys.modules[__name__], + 'thread_class': 'Thread', + 'queue': importlib.import_module('gevent.queue'), + 'queue_class': 'JoinableQueue', + 'websocket': sys.modules[__name__] if _websocket_available else None, + 'websocket_class': 'WebSocketWSGI' if _websocket_available else None, + 'sleep': gevent.sleep +} diff --git a/openpype/vendor/python/python_2/engineio/async_gevent_uwsgi.py b/openpype/vendor/python/python_2/engineio/async_gevent_uwsgi.py new file mode 100644 index 0000000000..d2d6983893 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/async_gevent_uwsgi.py @@ -0,0 +1,155 @@ +import importlib +import sys +import six + +import gevent +import uwsgi +_websocket_available = hasattr(uwsgi, 'websocket_handshake') + + +class Thread(gevent.Greenlet): # pragma: no cover + """ + This wrapper class provides gevent Greenlet interface that is compatible + with the standard library's Thread class. + """ + def __init__(self, target, args=[], kwargs={}): + super(Thread, self).__init__(target, *args, **kwargs) + + def _run(self): + return self.run() + + +class uWSGIWebSocket(object): # pragma: no cover + """ + This wrapper class provides a uWSGI WebSocket interface that is + compatible with eventlet's implementation. + """ + def __init__(self, app): + self.app = app + self._sock = None + + def __call__(self, environ, start_response): + self._sock = uwsgi.connection_fd() + self.environ = environ + + uwsgi.websocket_handshake() + + self._req_ctx = None + if hasattr(uwsgi, 'request_context'): + # uWSGI >= 2.1.x with support for api access across-greenlets + self._req_ctx = uwsgi.request_context() + else: + # use event and queue for sending messages + from gevent.event import Event + from gevent.queue import Queue + from gevent.select import select + self._event = Event() + self._send_queue = Queue() + + # spawn a select greenlet + def select_greenlet_runner(fd, event): + """Sets event when data becomes available to read on fd.""" + while True: + event.set() + try: + select([fd], [], [])[0] + except ValueError: + break + self._select_greenlet = gevent.spawn( + select_greenlet_runner, + self._sock, + self._event) + + self.app(self) + + def close(self): + """Disconnects uWSGI from the client.""" + uwsgi.disconnect() + if self._req_ctx is None: + # better kill it here in case wait() is not called again + self._select_greenlet.kill() + self._event.set() + + def _send(self, msg): + """Transmits message either in binary or UTF-8 text mode, + depending on its type.""" + if isinstance(msg, six.binary_type): + method = uwsgi.websocket_send_binary + else: + method = uwsgi.websocket_send + if self._req_ctx is not None: + method(msg, request_context=self._req_ctx) + else: + method(msg) + + def _decode_received(self, msg): + """Returns either bytes or str, depending on message type.""" + if not isinstance(msg, six.binary_type): + # already decoded - do nothing + return msg + # only decode from utf-8 if message is not binary data + type = six.byte2int(msg[0:1]) + if type >= 48: # no binary + return msg.decode('utf-8') + # binary message, don't try to decode + return msg + + def send(self, msg): + """Queues a message for sending. Real transmission is done in + wait method. + Sends directly if uWSGI version is new enough.""" + if self._req_ctx is not None: + self._send(msg) + else: + self._send_queue.put(msg) + self._event.set() + + def wait(self): + """Waits and returns received messages. + If running in compatibility mode for older uWSGI versions, + it also sends messages that have been queued by send(). + A return value of None means that connection was closed. + This must be called repeatedly. For uWSGI < 2.1.x it must + be called from the main greenlet.""" + while True: + if self._req_ctx is not None: + try: + msg = uwsgi.websocket_recv(request_context=self._req_ctx) + except IOError: # connection closed + return None + return self._decode_received(msg) + else: + # we wake up at least every 3 seconds to let uWSGI + # do its ping/ponging + event_set = self._event.wait(timeout=3) + if event_set: + self._event.clear() + # maybe there is something to send + msgs = [] + while True: + try: + msgs.append(self._send_queue.get(block=False)) + except gevent.queue.Empty: + break + for msg in msgs: + self._send(msg) + # maybe there is something to receive, if not, at least + # ensure uWSGI does its ping/ponging + try: + msg = uwsgi.websocket_recv_nb() + except IOError: # connection closed + self._select_greenlet.kill() + return None + if msg: # message available + return self._decode_received(msg) + + +_async = { + 'threading': sys.modules[__name__], + 'thread_class': 'Thread', + 'queue': importlib.import_module('gevent.queue'), + 'queue_class': 'JoinableQueue', + 'websocket': sys.modules[__name__] if _websocket_available else None, + 'websocket_class': 'uWSGIWebSocket' if _websocket_available else None, + 'sleep': gevent.sleep +} diff --git a/openpype/vendor/python/python_2/engineio/async_sanic.py b/openpype/vendor/python/python_2/engineio/async_sanic.py new file mode 100644 index 0000000000..d150aac0e6 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/async_sanic.py @@ -0,0 +1,145 @@ +import sys +from urllib.parse import urlsplit + +from sanic.response import HTTPResponse +try: + from sanic.websocket import WebSocketProtocol +except ImportError: + # the installed version of sanic does not have websocket support + WebSocketProtocol = None +import six + + +def create_route(app, engineio_server, engineio_endpoint): + """This function sets up the engine.io endpoint as a route for the + application. + + Note that both GET and POST requests must be hooked up on the engine.io + endpoint. + """ + app.add_route(engineio_server.handle_request, engineio_endpoint, + methods=['GET', 'POST', 'OPTIONS']) + try: + app.enable_websocket() + except AttributeError: + # ignore, this version does not support websocket + pass + + +def translate_request(request): + """This function takes the arguments passed to the request handler and + uses them to generate a WSGI compatible environ dictionary. + """ + class AwaitablePayload(object): + def __init__(self, payload): + self.payload = payload or b'' + + async def read(self, length=None): + if length is None: + r = self.payload + self.payload = b'' + else: + r = self.payload[:length] + self.payload = self.payload[length:] + return r + + uri_parts = urlsplit(request.url) + environ = { + 'wsgi.input': AwaitablePayload(request.body), + 'wsgi.errors': sys.stderr, + 'wsgi.version': (1, 0), + 'wsgi.async': True, + 'wsgi.multithread': False, + 'wsgi.multiprocess': False, + 'wsgi.run_once': False, + 'SERVER_SOFTWARE': 'sanic', + 'REQUEST_METHOD': request.method, + 'QUERY_STRING': uri_parts.query or '', + 'RAW_URI': request.url, + 'SERVER_PROTOCOL': 'HTTP/' + request.version, + 'REMOTE_ADDR': '127.0.0.1', + 'REMOTE_PORT': '0', + 'SERVER_NAME': 'sanic', + 'SERVER_PORT': '0', + 'sanic.request': request + } + + for hdr_name, hdr_value in request.headers.items(): + hdr_name = hdr_name.upper() + if hdr_name == 'CONTENT-TYPE': + environ['CONTENT_TYPE'] = hdr_value + continue + elif hdr_name == 'CONTENT-LENGTH': + environ['CONTENT_LENGTH'] = hdr_value + continue + + key = 'HTTP_%s' % hdr_name.replace('-', '_') + if key in environ: + hdr_value = '%s,%s' % (environ[key], hdr_value) + + environ[key] = hdr_value + + environ['wsgi.url_scheme'] = environ.get('HTTP_X_FORWARDED_PROTO', 'http') + + path_info = uri_parts.path + + environ['PATH_INFO'] = path_info + environ['SCRIPT_NAME'] = '' + + return environ + + +def make_response(status, headers, payload, environ): + """This function generates an appropriate response object for this async + mode. + """ + headers_dict = {} + content_type = None + for h in headers: + if h[0].lower() == 'content-type': + content_type = h[1] + else: + headers_dict[h[0]] = h[1] + return HTTPResponse(body_bytes=payload, content_type=content_type, + status=int(status.split()[0]), headers=headers_dict) + + +class WebSocket(object): # pragma: no cover + """ + This wrapper class provides a sanic WebSocket interface that is + somewhat compatible with eventlet's implementation. + """ + def __init__(self, handler): + self.handler = handler + self._sock = None + + async def __call__(self, environ): + request = environ['sanic.request'] + protocol = request.transport.get_protocol() + self._sock = await protocol.websocket_handshake(request) + + self.environ = environ + await self.handler(self) + + async def close(self): + await self._sock.close() + + async def send(self, message): + await self._sock.send(message) + + async def wait(self): + data = await self._sock.recv() + if not isinstance(data, six.binary_type) and \ + not isinstance(data, six.text_type): + raise IOError() + return data + + +_async = { + 'asyncio': True, + 'create_route': create_route, + 'translate_request': translate_request, + 'make_response': make_response, + 'websocket': sys.modules[__name__] if WebSocketProtocol else None, + 'websocket_class': 'WebSocket' if WebSocketProtocol else None +} diff --git a/openpype/vendor/python/python_2/engineio/async_threading.py b/openpype/vendor/python/python_2/engineio/async_threading.py new file mode 100644 index 0000000000..fb458b07af --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/async_threading.py @@ -0,0 +1,17 @@ +import importlib +import time + +try: + queue = importlib.import_module('queue') +except ImportError: # pragma: no cover + queue = importlib.import_module('Queue') # pragma: no cover + +_async = { + 'threading': importlib.import_module('threading'), + 'thread_class': 'Thread', + 'queue': queue, + 'queue_class': 'Queue', + 'websocket': None, + 'websocket_class': None, + 'sleep': time.sleep +} diff --git a/openpype/vendor/python/python_2/engineio/async_tornado.py b/openpype/vendor/python/python_2/engineio/async_tornado.py new file mode 100644 index 0000000000..4b4f9c7572 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/async_tornado.py @@ -0,0 +1,154 @@ +import asyncio +import sys +from urllib.parse import urlsplit + +try: + import tornado.web + import tornado.websocket +except ImportError: # pragma: no cover + pass +import six + + +def get_tornado_handler(engineio_server): + class Handler(tornado.websocket.WebSocketHandler): # pragma: no cover + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.receive_queue = asyncio.Queue() + + async def get(self): + if self.request.headers.get('Upgrade', '').lower() == 'websocket': + super().get() + await engineio_server.handle_request(self) + + async def post(self): + await engineio_server.handle_request(self) + + async def options(self): + await engineio_server.handle_request(self) + + async def on_message(self, message): + await self.receive_queue.put(message) + + async def get_next_message(self): + return await self.receive_queue.get() + + def on_close(self): + self.receive_queue.put_nowait(None) + + return Handler + + +def translate_request(handler): + """This function takes the arguments passed to the request handler and + uses them to generate a WSGI compatible environ dictionary. + """ + class AwaitablePayload(object): + def __init__(self, payload): + self.payload = payload or b'' + + async def read(self, length=None): + if length is None: + r = self.payload + self.payload = b'' + else: + r = self.payload[:length] + self.payload = self.payload[length:] + return r + + payload = handler.request.body + + uri_parts = urlsplit(handler.request.path) + full_uri = handler.request.path + if handler.request.query: # pragma: no cover + full_uri += '?' + handler.request.query + environ = { + 'wsgi.input': AwaitablePayload(payload), + 'wsgi.errors': sys.stderr, + 'wsgi.version': (1, 0), + 'wsgi.async': True, + 'wsgi.multithread': False, + 'wsgi.multiprocess': False, + 'wsgi.run_once': False, + 'SERVER_SOFTWARE': 'aiohttp', + 'REQUEST_METHOD': handler.request.method, + 'QUERY_STRING': handler.request.query or '', + 'RAW_URI': full_uri, + 'SERVER_PROTOCOL': 'HTTP/%s' % handler.request.version, + 'REMOTE_ADDR': '127.0.0.1', + 'REMOTE_PORT': '0', + 'SERVER_NAME': 'aiohttp', + 'SERVER_PORT': '0', + 'tornado.handler': handler + } + + for hdr_name, hdr_value in handler.request.headers.items(): + hdr_name = hdr_name.upper() + if hdr_name == 'CONTENT-TYPE': + environ['CONTENT_TYPE'] = hdr_value + continue + elif hdr_name == 'CONTENT-LENGTH': + environ['CONTENT_LENGTH'] = hdr_value + continue + + key = 'HTTP_%s' % hdr_name.replace('-', '_') + environ[key] = hdr_value + + environ['wsgi.url_scheme'] = environ.get('HTTP_X_FORWARDED_PROTO', 'http') + + path_info = uri_parts.path + + environ['PATH_INFO'] = path_info + environ['SCRIPT_NAME'] = '' + + return environ + + +def make_response(status, headers, payload, environ): + """This function generates an appropriate response object for this async + mode. + """ + tornado_handler = environ['tornado.handler'] + tornado_handler.set_status(int(status.split()[0])) + for header, value in headers: + tornado_handler.set_header(header, value) + tornado_handler.write(payload) + tornado_handler.finish() + + +class WebSocket(object): # pragma: no cover + """ + This wrapper class provides a tornado WebSocket interface that is + somewhat compatible with eventlet's implementation. + """ + def __init__(self, handler): + self.handler = handler + self.tornado_handler = None + + async def __call__(self, environ): + self.tornado_handler = environ['tornado.handler'] + self.environ = environ + await self.handler(self) + + async def close(self): + self.tornado_handler.close() + + async def send(self, message): + self.tornado_handler.write_message( + message, binary=isinstance(message, bytes)) + + async def wait(self): + msg = await self.tornado_handler.get_next_message() + if not isinstance(msg, six.binary_type) and \ + not isinstance(msg, six.text_type): + raise IOError() + return msg + + +_async = { + 'asyncio': True, + 'translate_request': translate_request, + 'make_response': make_response, + 'websocket': sys.modules[__name__], + 'websocket_class': 'WebSocket' +} diff --git a/openpype/vendor/python/python_2/engineio/asyncio_client.py b/openpype/vendor/python/python_2/engineio/asyncio_client.py new file mode 100644 index 0000000000..340cf278b7 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/asyncio_client.py @@ -0,0 +1,556 @@ +import asyncio + +try: + import aiohttp +except ImportError: # pragma: no cover + aiohttp = None +import six +try: + import websockets +except ImportError: # pragma: no cover + websockets = None + +from . import client +from . import exceptions +from . import packet +from . import payload + + +class AsyncClient(client.Client): + """An Engine.IO client for asyncio. + + This class implements a fully compliant Engine.IO web client with support + for websocket and long-polling transports, compatible with the asyncio + framework on Python 3.5 or newer. + + :param logger: To enable logging set to ``True`` or pass a logger object to + use. To disable logging set to ``False``. The default is + ``False``. + :param json: An alternative json module to use for encoding and decoding + packets. Custom json modules must have ``dumps`` and ``loads`` + functions that are compatible with the standard library + versions. + """ + def is_asyncio_based(self): + return True + + async def connect(self, url, headers={}, transports=None, + engineio_path='engine.io'): + """Connect to an Engine.IO server. + + :param url: The URL of the Engine.IO server. It can include custom + query string parameters if required by the server. + :param headers: A dictionary with custom headers to send with the + connection request. + :param transports: The list of allowed transports. Valid transports + are ``'polling'`` and ``'websocket'``. If not + given, the polling transport is connected first, + then an upgrade to websocket is attempted. + :param engineio_path: The endpoint where the Engine.IO server is + installed. The default value is appropriate for + most cases. + + Note: this method is a coroutine. + + Example usage:: + + eio = engineio.Client() + await eio.connect('http://localhost:5000') + """ + if self.state != 'disconnected': + raise ValueError('Client is not in a disconnected state') + valid_transports = ['polling', 'websocket'] + if transports is not None: + if isinstance(transports, six.text_type): + transports = [transports] + transports = [transport for transport in transports + if transport in valid_transports] + if not transports: + raise ValueError('No valid transports provided') + self.transports = transports or valid_transports + self.queue = self.create_queue() + return await getattr(self, '_connect_' + self.transports[0])( + url, headers, engineio_path) + + async def wait(self): + """Wait until the connection with the server ends. + + Client applications can use this function to block the main thread + during the life of the connection. + + Note: this method is a coroutine. + """ + if self.read_loop_task: + await self.read_loop_task + + async def send(self, data, binary=None): + """Send a message to a client. + + :param data: The data to send to the client. Data can be of type + ``str``, ``bytes``, ``list`` or ``dict``. If a ``list`` + or ``dict``, the data will be serialized as JSON. + :param binary: ``True`` to send packet as binary, ``False`` to send + as text. If not given, unicode (Python 2) and str + (Python 3) are sent as text, and str (Python 2) and + bytes (Python 3) are sent as binary. + + Note: this method is a coroutine. + """ + await self._send_packet(packet.Packet(packet.MESSAGE, data=data, + binary=binary)) + + async def disconnect(self, abort=False): + """Disconnect from the server. + + :param abort: If set to ``True``, do not wait for background tasks + associated with the connection to end. + + Note: this method is a coroutine. + """ + if self.state == 'connected': + await self._send_packet(packet.Packet(packet.CLOSE)) + await self.queue.put(None) + self.state = 'disconnecting' + await self._trigger_event('disconnect', run_async=False) + if self.current_transport == 'websocket': + await self.ws.close() + if not abort: + await self.read_loop_task + self.state = 'disconnected' + try: + client.connected_clients.remove(self) + except ValueError: # pragma: no cover + pass + self._reset() + + def start_background_task(self, target, *args, **kwargs): + """Start a background task. + + This is a utility function that applications can use to start a + background task. + + :param target: the target function to execute. + :param args: arguments to pass to the function. + :param kwargs: keyword arguments to pass to the function. + + This function returns an object compatible with the `Thread` class in + the Python standard library. The `start()` method on this object is + already called by this function. + + Note: this method is a coroutine. + """ + return asyncio.ensure_future(target(*args, **kwargs)) + + async def sleep(self, seconds=0): + """Sleep for the requested amount of time. + + Note: this method is a coroutine. + """ + return await asyncio.sleep(seconds) + + def create_queue(self): + """Create a queue object.""" + q = asyncio.Queue() + q.Empty = asyncio.QueueEmpty + return q + + def create_event(self): + """Create an event object.""" + return asyncio.Event() + + def _reset(self): + if self.http: # pragma: no cover + asyncio.ensure_future(self.http.close()) + super()._reset() + + async def _connect_polling(self, url, headers, engineio_path): + """Establish a long-polling connection to the Engine.IO server.""" + if aiohttp is None: # pragma: no cover + self.logger.error('aiohttp not installed -- cannot make HTTP ' + 'requests!') + return + self.base_url = self._get_engineio_url(url, engineio_path, 'polling') + self.logger.info('Attempting polling connection to ' + self.base_url) + r = await self._send_request( + 'GET', self.base_url + self._get_url_timestamp(), headers=headers) + if r is None: + self._reset() + raise exceptions.ConnectionError( + 'Connection refused by the server') + if r.status != 200: + raise exceptions.ConnectionError( + 'Unexpected status code {} in server response'.format( + r.status)) + try: + p = payload.Payload(encoded_payload=await r.read()) + except ValueError: + six.raise_from(exceptions.ConnectionError( + 'Unexpected response from server'), None) + open_packet = p.packets[0] + if open_packet.packet_type != packet.OPEN: + raise exceptions.ConnectionError( + 'OPEN packet not returned by server') + self.logger.info( + 'Polling connection accepted with ' + str(open_packet.data)) + self.sid = open_packet.data['sid'] + self.upgrades = open_packet.data['upgrades'] + self.ping_interval = open_packet.data['pingInterval'] / 1000.0 + self.ping_timeout = open_packet.data['pingTimeout'] / 1000.0 + self.current_transport = 'polling' + self.base_url += '&sid=' + self.sid + + self.state = 'connected' + client.connected_clients.append(self) + await self._trigger_event('connect', run_async=False) + + for pkt in p.packets[1:]: + await self._receive_packet(pkt) + + if 'websocket' in self.upgrades and 'websocket' in self.transports: + # attempt to upgrade to websocket + if await self._connect_websocket(url, headers, engineio_path): + # upgrade to websocket succeeded, we're done here + return + + self.ping_loop_task = self.start_background_task(self._ping_loop) + self.write_loop_task = self.start_background_task(self._write_loop) + self.read_loop_task = self.start_background_task( + self._read_loop_polling) + + async def _connect_websocket(self, url, headers, engineio_path): + """Establish or upgrade to a WebSocket connection with the server.""" + if websockets is None: # pragma: no cover + self.logger.error('websockets package not installed') + return False + websocket_url = self._get_engineio_url(url, engineio_path, + 'websocket') + if self.sid: + self.logger.info( + 'Attempting WebSocket upgrade to ' + websocket_url) + upgrade = True + websocket_url += '&sid=' + self.sid + else: + upgrade = False + self.base_url = websocket_url + self.logger.info( + 'Attempting WebSocket connection to ' + websocket_url) + + # get the cookies from the long-polling connection so that they can + # also be sent the the WebSocket route + cookies = None + if self.http: + cookies = '; '.join(["{}={}".format(cookie.key, cookie.value) + for cookie in self.http._cookie_jar]) + headers = headers.copy() + headers['Cookie'] = cookies + + try: + ws = await websockets.connect( + websocket_url + self._get_url_timestamp(), + extra_headers=headers) + except (websockets.exceptions.InvalidURI, + websockets.exceptions.InvalidHandshake): + if upgrade: + self.logger.warning( + 'WebSocket upgrade failed: connection error') + return False + else: + raise exceptions.ConnectionError('Connection error') + if upgrade: + p = packet.Packet(packet.PING, data='probe').encode( + always_bytes=False) + try: + await ws.send(p) + except Exception as e: # pragma: no cover + self.logger.warning( + 'WebSocket upgrade failed: unexpected send exception: %s', + str(e)) + return False + try: + p = await ws.recv() + except Exception as e: # pragma: no cover + self.logger.warning( + 'WebSocket upgrade failed: unexpected recv exception: %s', + str(e)) + return False + pkt = packet.Packet(encoded_packet=p) + if pkt.packet_type != packet.PONG or pkt.data != 'probe': + self.logger.warning( + 'WebSocket upgrade failed: no PONG packet') + return False + p = packet.Packet(packet.UPGRADE).encode(always_bytes=False) + try: + await ws.send(p) + except Exception as e: # pragma: no cover + self.logger.warning( + 'WebSocket upgrade failed: unexpected send exception: %s', + str(e)) + return False + self.current_transport = 'websocket' + if self.http: # pragma: no cover + await self.http.close() + self.logger.info('WebSocket upgrade was successful') + else: + try: + p = await ws.recv() + except Exception as e: # pragma: no cover + raise exceptions.ConnectionError( + 'Unexpected recv exception: ' + str(e)) + open_packet = packet.Packet(encoded_packet=p) + if open_packet.packet_type != packet.OPEN: + raise exceptions.ConnectionError('no OPEN packet') + self.logger.info( + 'WebSocket connection accepted with ' + str(open_packet.data)) + self.sid = open_packet.data['sid'] + self.upgrades = open_packet.data['upgrades'] + self.ping_interval = open_packet.data['pingInterval'] / 1000.0 + self.ping_timeout = open_packet.data['pingTimeout'] / 1000.0 + self.current_transport = 'websocket' + + self.state = 'connected' + client.connected_clients.append(self) + await self._trigger_event('connect', run_async=False) + + self.ws = ws + self.ping_loop_task = self.start_background_task(self._ping_loop) + self.write_loop_task = self.start_background_task(self._write_loop) + self.read_loop_task = self.start_background_task( + self._read_loop_websocket) + return True + + async def _receive_packet(self, pkt): + """Handle incoming packets from the server.""" + packet_name = packet.packet_names[pkt.packet_type] \ + if pkt.packet_type < len(packet.packet_names) else 'UNKNOWN' + self.logger.info( + 'Received packet %s data %s', packet_name, + pkt.data if not isinstance(pkt.data, bytes) else '') + if pkt.packet_type == packet.MESSAGE: + await self._trigger_event('message', pkt.data, run_async=True) + elif pkt.packet_type == packet.PONG: + self.pong_received = True + elif pkt.packet_type == packet.CLOSE: + await self.disconnect(abort=True) + elif pkt.packet_type == packet.NOOP: + pass + else: + self.logger.error('Received unexpected packet of type %s', + pkt.packet_type) + + async def _send_packet(self, pkt): + """Queue a packet to be sent to the server.""" + if self.state != 'connected': + return + await self.queue.put(pkt) + self.logger.info( + 'Sending packet %s data %s', + packet.packet_names[pkt.packet_type], + pkt.data if not isinstance(pkt.data, bytes) else '') + + async def _send_request( + self, method, url, headers=None, body=None): # pragma: no cover + if self.http is None or self.http.closed: + self.http = aiohttp.ClientSession() + method = getattr(self.http, method.lower()) + try: + return await method(url, headers=headers, data=body) + except aiohttp.ClientError: + return + + async def _trigger_event(self, event, *args, **kwargs): + """Invoke an event handler.""" + run_async = kwargs.pop('run_async', False) + ret = None + if event in self.handlers: + if asyncio.iscoroutinefunction(self.handlers[event]) is True: + if run_async: + return self.start_background_task(self.handlers[event], + *args) + else: + try: + ret = await self.handlers[event](*args) + except asyncio.CancelledError: # pragma: no cover + pass + except: + self.logger.exception(event + ' async handler error') + if event == 'connect': + # if connect handler raised error we reject the + # connection + return False + else: + if run_async: + async def async_handler(): + return self.handlers[event](*args) + + return self.start_background_task(async_handler) + else: + try: + ret = self.handlers[event](*args) + except: + self.logger.exception(event + ' handler error') + if event == 'connect': + # if connect handler raised error we reject the + # connection + return False + return ret + + async def _ping_loop(self): + """This background task sends a PING to the server at the requested + interval. + """ + self.pong_received = True + self.ping_loop_event.clear() + while self.state == 'connected': + if not self.pong_received: + self.logger.info( + 'PONG response has not been received, aborting') + if self.ws: + await self.ws.close() + await self.queue.put(None) + break + self.pong_received = False + await self._send_packet(packet.Packet(packet.PING)) + try: + await asyncio.wait_for(self.ping_loop_event.wait(), + self.ping_interval) + except (asyncio.TimeoutError, + asyncio.CancelledError): # pragma: no cover + pass + self.logger.info('Exiting ping task') + + async def _read_loop_polling(self): + """Read packets by polling the Engine.IO server.""" + while self.state == 'connected': + self.logger.info( + 'Sending polling GET request to ' + self.base_url) + r = await self._send_request( + 'GET', self.base_url + self._get_url_timestamp()) + if r is None: + self.logger.warning( + 'Connection refused by the server, aborting') + await self.queue.put(None) + break + if r.status != 200: + self.logger.warning('Unexpected status code %s in server ' + 'response, aborting', r.status) + await self.queue.put(None) + break + try: + p = payload.Payload(encoded_payload=await r.read()) + except ValueError: + self.logger.warning( + 'Unexpected packet from server, aborting') + await self.queue.put(None) + break + for pkt in p.packets: + await self._receive_packet(pkt) + + self.logger.info('Waiting for write loop task to end') + await self.write_loop_task + self.logger.info('Waiting for ping loop task to end') + self.ping_loop_event.set() + await self.ping_loop_task + if self.state == 'connected': + await self._trigger_event('disconnect', run_async=False) + try: + client.connected_clients.remove(self) + except ValueError: # pragma: no cover + pass + self._reset() + self.logger.info('Exiting read loop task') + + async def _read_loop_websocket(self): + """Read packets from the Engine.IO WebSocket connection.""" + while self.state == 'connected': + p = None + try: + p = await self.ws.recv() + except websockets.exceptions.ConnectionClosed: + self.logger.info( + 'Read loop: WebSocket connection was closed, aborting') + await self.queue.put(None) + break + except Exception as e: + self.logger.info( + 'Unexpected error "%s", aborting', str(e)) + await self.queue.put(None) + break + if isinstance(p, six.text_type): # pragma: no cover + p = p.encode('utf-8') + pkt = packet.Packet(encoded_packet=p) + await self._receive_packet(pkt) + + self.logger.info('Waiting for write loop task to end') + await self.write_loop_task + self.logger.info('Waiting for ping loop task to end') + self.ping_loop_event.set() + await self.ping_loop_task + if self.state == 'connected': + await self._trigger_event('disconnect', run_async=False) + try: + client.connected_clients.remove(self) + except ValueError: # pragma: no cover + pass + self._reset() + self.logger.info('Exiting read loop task') + + async def _write_loop(self): + """This background task sends packages to the server as they are + pushed to the send queue. + """ + while self.state == 'connected': + # to simplify the timeout handling, use the maximum of the + # ping interval and ping timeout as timeout, with an extra 5 + # seconds grace period + timeout = max(self.ping_interval, self.ping_timeout) + 5 + packets = None + try: + packets = [await asyncio.wait_for(self.queue.get(), timeout)] + except (self.queue.Empty, asyncio.TimeoutError, + asyncio.CancelledError): + self.logger.error('packet queue is empty, aborting') + break + if packets == [None]: + self.queue.task_done() + packets = [] + else: + while True: + try: + packets.append(self.queue.get_nowait()) + except self.queue.Empty: + break + if packets[-1] is None: + packets = packets[:-1] + self.queue.task_done() + break + if not packets: + # empty packet list returned -> connection closed + break + if self.current_transport == 'polling': + p = payload.Payload(packets=packets) + r = await self._send_request( + 'POST', self.base_url, body=p.encode(), + headers={'Content-Type': 'application/octet-stream'}) + for pkt in packets: + self.queue.task_done() + if r is None: + self.logger.warning( + 'Connection refused by the server, aborting') + break + if r.status != 200: + self.logger.warning('Unexpected status code %s in server ' + 'response, aborting', r.status) + self._reset() + break + else: + # websocket + try: + for pkt in packets: + await self.ws.send(pkt.encode(always_bytes=False)) + self.queue.task_done() + except websockets.exceptions.ConnectionClosed: + self.logger.info( + 'Write loop: WebSocket connection was closed, ' + 'aborting') + break + self.logger.info('Exiting write loop task') diff --git a/openpype/vendor/python/python_2/engineio/asyncio_server.py b/openpype/vendor/python/python_2/engineio/asyncio_server.py new file mode 100644 index 0000000000..cc5ed8007c --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/asyncio_server.py @@ -0,0 +1,444 @@ +import asyncio + +import six +from six.moves import urllib + +from . import exceptions +from . import packet +from . import server +from . import asyncio_socket + + +class AsyncServer(server.Server): + """An Engine.IO server for asyncio. + + This class implements a fully compliant Engine.IO web server with support + for websocket and long-polling transports, compatible with the asyncio + framework on Python 3.5 or newer. + + :param async_mode: The asynchronous model to use. See the Deployment + section in the documentation for a description of the + available options. Valid async modes are "aiohttp", + "sanic", "tornado" and "asgi". If this argument is not + given, "aiohttp" is tried first, followed by "sanic", + "tornado", and finally "asgi". The first async mode that + has all its dependencies installed is the one that is + chosen. + :param ping_timeout: The time in seconds that the client waits for the + server to respond before disconnecting. + :param ping_interval: The interval in seconds at which the client pings + the server. + :param max_http_buffer_size: The maximum size of a message when using the + polling transport. + :param allow_upgrades: Whether to allow transport upgrades or not. + :param http_compression: Whether to compress packages when using the + polling transport. + :param compression_threshold: Only compress messages when their byte size + is greater than this value. + :param cookie: Name of the HTTP cookie that contains the client session + id. If set to ``None``, a cookie is not sent to the client. + :param cors_allowed_origins: List of origins that are allowed to connect + to this server. All origins are allowed by + default. + :param cors_credentials: Whether credentials (cookies, authentication) are + allowed in requests to this server. + :param logger: To enable logging set to ``True`` or pass a logger object to + use. To disable logging set to ``False``. + :param json: An alternative json module to use for encoding and decoding + packets. Custom json modules must have ``dumps`` and ``loads`` + functions that are compatible with the standard library + versions. + :param async_handlers: If set to ``True``, run message event handlers in + non-blocking threads. To run handlers synchronously, + set to ``False``. The default is ``True``. + :param kwargs: Reserved for future extensions, any additional parameters + given as keyword arguments will be silently ignored. + """ + def is_asyncio_based(self): + return True + + def async_modes(self): + return ['aiohttp', 'sanic', 'tornado', 'asgi'] + + def attach(self, app, engineio_path='engine.io'): + """Attach the Engine.IO server to an application.""" + engineio_path = engineio_path.strip('/') + self._async['create_route'](app, self, '/{}/'.format(engineio_path)) + + async def send(self, sid, data, binary=None): + """Send a message to a client. + + :param sid: The session id of the recipient client. + :param data: The data to send to the client. Data can be of type + ``str``, ``bytes``, ``list`` or ``dict``. If a ``list`` + or ``dict``, the data will be serialized as JSON. + :param binary: ``True`` to send packet as binary, ``False`` to send + as text. If not given, unicode (Python 2) and str + (Python 3) are sent as text, and str (Python 2) and + bytes (Python 3) are sent as binary. + + Note: this method is a coroutine. + """ + try: + socket = self._get_socket(sid) + except KeyError: + # the socket is not available + self.logger.warning('Cannot send to sid %s', sid) + return + await socket.send(packet.Packet(packet.MESSAGE, data=data, + binary=binary)) + + async def get_session(self, sid): + """Return the user session for a client. + + :param sid: The session id of the client. + + The return value is a dictionary. Modifications made to this + dictionary are not guaranteed to be preserved. If you want to modify + the user session, use the ``session`` context manager instead. + """ + socket = self._get_socket(sid) + return socket.session + + async def save_session(self, sid, session): + """Store the user session for a client. + + :param sid: The session id of the client. + :param session: The session dictionary. + """ + socket = self._get_socket(sid) + socket.session = session + + def session(self, sid): + """Return the user session for a client with context manager syntax. + + :param sid: The session id of the client. + + This is a context manager that returns the user session dictionary for + the client. Any changes that are made to this dictionary inside the + context manager block are saved back to the session. Example usage:: + + @eio.on('connect') + def on_connect(sid, environ): + username = authenticate_user(environ) + if not username: + return False + with eio.session(sid) as session: + session['username'] = username + + @eio.on('message') + def on_message(sid, msg): + async with eio.session(sid) as session: + print('received message from ', session['username']) + """ + class _session_context_manager(object): + def __init__(self, server, sid): + self.server = server + self.sid = sid + self.session = None + + async def __aenter__(self): + self.session = await self.server.get_session(sid) + return self.session + + async def __aexit__(self, *args): + await self.server.save_session(sid, self.session) + + return _session_context_manager(self, sid) + + async def disconnect(self, sid=None): + """Disconnect a client. + + :param sid: The session id of the client to close. If this parameter + is not given, then all clients are closed. + + Note: this method is a coroutine. + """ + if sid is not None: + try: + socket = self._get_socket(sid) + except KeyError: # pragma: no cover + # the socket was already closed or gone + pass + else: + await socket.close() + del self.sockets[sid] + else: + await asyncio.wait([client.close() + for client in six.itervalues(self.sockets)]) + self.sockets = {} + + async def handle_request(self, *args, **kwargs): + """Handle an HTTP request from the client. + + This is the entry point of the Engine.IO application. This function + returns the HTTP response to deliver to the client. + + Note: this method is a coroutine. + """ + translate_request = self._async['translate_request'] + if asyncio.iscoroutinefunction(translate_request): + environ = await translate_request(*args, **kwargs) + else: + environ = translate_request(*args, **kwargs) + method = environ['REQUEST_METHOD'] + query = urllib.parse.parse_qs(environ.get('QUERY_STRING', '')) + + sid = query['sid'][0] if 'sid' in query else None + b64 = False + jsonp = False + jsonp_index = None + + if 'b64' in query: + if query['b64'][0] == "1" or query['b64'][0].lower() == "true": + b64 = True + if 'j' in query: + jsonp = True + try: + jsonp_index = int(query['j'][0]) + except (ValueError, KeyError, IndexError): + # Invalid JSONP index number + pass + + if jsonp and jsonp_index is None: + self.logger.warning('Invalid JSONP index number') + r = self._bad_request() + elif method == 'GET': + if sid is None: + transport = query.get('transport', ['polling'])[0] + if transport != 'polling' and transport != 'websocket': + self.logger.warning('Invalid transport %s', transport) + r = self._bad_request() + else: + r = await self._handle_connect(environ, transport, + b64, jsonp_index) + else: + if sid not in self.sockets: + self.logger.warning('Invalid session %s', sid) + r = self._bad_request() + else: + socket = self._get_socket(sid) + try: + packets = await socket.handle_get_request(environ) + if isinstance(packets, list): + r = self._ok(packets, b64=b64, + jsonp_index=jsonp_index) + else: + r = packets + except exceptions.EngineIOError: + if sid in self.sockets: # pragma: no cover + await self.disconnect(sid) + r = self._bad_request() + if sid in self.sockets and self.sockets[sid].closed: + del self.sockets[sid] + elif method == 'POST': + if sid is None or sid not in self.sockets: + self.logger.warning('Invalid session %s', sid) + r = self._bad_request() + else: + socket = self._get_socket(sid) + try: + await socket.handle_post_request(environ) + r = self._ok(jsonp_index=jsonp_index) + except exceptions.EngineIOError: + if sid in self.sockets: # pragma: no cover + await self.disconnect(sid) + r = self._bad_request() + except: # pragma: no cover + # for any other unexpected errors, we log the error + # and keep going + self.logger.exception('post request handler error') + r = self._ok(jsonp_index=jsonp_index) + elif method == 'OPTIONS': + r = self._ok() + else: + self.logger.warning('Method %s not supported', method) + r = self._method_not_found() + if not isinstance(r, dict): + return r + if self.http_compression and \ + len(r['response']) >= self.compression_threshold: + encodings = [e.split(';')[0].strip() for e in + environ.get('HTTP_ACCEPT_ENCODING', '').split(',')] + for encoding in encodings: + if encoding in self.compression_methods: + r['response'] = \ + getattr(self, '_' + encoding)(r['response']) + r['headers'] += [('Content-Encoding', encoding)] + break + cors_headers = self._cors_headers(environ) + make_response = self._async['make_response'] + if asyncio.iscoroutinefunction(make_response): + response = await make_response(r['status'], + r['headers'] + cors_headers, + r['response'], environ) + else: + response = make_response(r['status'], r['headers'] + cors_headers, + r['response'], environ) + return response + + def start_background_task(self, target, *args, **kwargs): + """Start a background task using the appropriate async model. + + This is a utility function that applications can use to start a + background task using the method that is compatible with the + selected async mode. + + :param target: the target function to execute. + :param args: arguments to pass to the function. + :param kwargs: keyword arguments to pass to the function. + + The return value is a ``asyncio.Task`` object. + """ + return asyncio.ensure_future(target(*args, **kwargs)) + + async def sleep(self, seconds=0): + """Sleep for the requested amount of time using the appropriate async + model. + + This is a utility function that applications can use to put a task to + sleep without having to worry about using the correct call for the + selected async mode. + + Note: this method is a coroutine. + """ + return await asyncio.sleep(seconds) + + def create_queue(self, *args, **kwargs): + """Create a queue object using the appropriate async model. + + This is a utility function that applications can use to create a queue + without having to worry about using the correct call for the selected + async mode. For asyncio based async modes, this returns an instance of + ``asyncio.Queue``. + """ + return asyncio.Queue(*args, **kwargs) + + def get_queue_empty_exception(self): + """Return the queue empty exception for the appropriate async model. + + This is a utility function that applications can use to work with a + queue without having to worry about using the correct call for the + selected async mode. For asyncio based async modes, this returns an + instance of ``asyncio.QueueEmpty``. + """ + return asyncio.QueueEmpty + + def create_event(self, *args, **kwargs): + """Create an event object using the appropriate async model. + + This is a utility function that applications can use to create an + event without having to worry about using the correct call for the + selected async mode. For asyncio based async modes, this returns + an instance of ``asyncio.Event``. + """ + return asyncio.Event(*args, **kwargs) + + async def _handle_connect(self, environ, transport, b64=False, + jsonp_index=None): + """Handle a client connection request.""" + if self.start_service_task: + # start the service task to monitor connected clients + self.start_service_task = False + self.start_background_task(self._service_task) + + sid = self._generate_id() + s = asyncio_socket.AsyncSocket(self, sid) + self.sockets[sid] = s + + pkt = packet.Packet( + packet.OPEN, {'sid': sid, + 'upgrades': self._upgrades(sid, transport), + 'pingTimeout': int(self.ping_timeout * 1000), + 'pingInterval': int(self.ping_interval * 1000)}) + await s.send(pkt) + + ret = await self._trigger_event('connect', sid, environ, + run_async=False) + if ret is False: + del self.sockets[sid] + self.logger.warning('Application rejected connection') + return self._unauthorized() + + if transport == 'websocket': + ret = await s.handle_get_request(environ) + if s.closed: + # websocket connection ended, so we are done + del self.sockets[sid] + return ret + else: + s.connected = True + headers = None + if self.cookie: + headers = [('Set-Cookie', self.cookie + '=' + sid)] + try: + return self._ok(await s.poll(), headers=headers, b64=b64, + jsonp_index=jsonp_index) + except exceptions.QueueEmpty: + return self._bad_request() + + async def _trigger_event(self, event, *args, **kwargs): + """Invoke an event handler.""" + run_async = kwargs.pop('run_async', False) + ret = None + if event in self.handlers: + if asyncio.iscoroutinefunction(self.handlers[event]) is True: + if run_async: + return self.start_background_task(self.handlers[event], + *args) + else: + try: + ret = await self.handlers[event](*args) + except asyncio.CancelledError: # pragma: no cover + pass + except: + self.logger.exception(event + ' async handler error') + if event == 'connect': + # if connect handler raised error we reject the + # connection + return False + else: + if run_async: + async def async_handler(): + return self.handlers[event](*args) + + return self.start_background_task(async_handler) + else: + try: + ret = self.handlers[event](*args) + except: + self.logger.exception(event + ' handler error') + if event == 'connect': + # if connect handler raised error we reject the + # connection + return False + return ret + + async def _service_task(self): # pragma: no cover + """Monitor connected clients and clean up those that time out.""" + while True: + if len(self.sockets) == 0: + # nothing to do + await self.sleep(self.ping_timeout) + continue + + # go through the entire client list in a ping interval cycle + sleep_interval = self.ping_timeout / len(self.sockets) + + try: + # iterate over the current clients + for socket in self.sockets.copy().values(): + if not socket.closing and not socket.closed: + await socket.check_ping_timeout() + await self.sleep(sleep_interval) + except (SystemExit, KeyboardInterrupt, asyncio.CancelledError): + self.logger.info('service task canceled') + break + except: + if asyncio.get_event_loop().is_closed(): + self.logger.info('event loop is closed, exiting service ' + 'task') + break + + # an unexpected exception has occurred, log it and continue + self.logger.exception('service task exception') diff --git a/openpype/vendor/python/python_2/engineio/asyncio_socket.py b/openpype/vendor/python/python_2/engineio/asyncio_socket.py new file mode 100644 index 0000000000..c4afcfe309 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/asyncio_socket.py @@ -0,0 +1,235 @@ +import asyncio +import six +import sys +import time + +from . import exceptions +from . import packet +from . import payload +from . import socket + + +class AsyncSocket(socket.Socket): + async def poll(self): + """Wait for packets to send to the client.""" + try: + packets = [await asyncio.wait_for(self.queue.get(), + self.server.ping_timeout)] + self.queue.task_done() + except (asyncio.TimeoutError, asyncio.CancelledError): + raise exceptions.QueueEmpty() + if packets == [None]: + return [] + try: + packets.append(self.queue.get_nowait()) + self.queue.task_done() + except asyncio.QueueEmpty: + pass + return packets + + async def receive(self, pkt): + """Receive packet from the client.""" + self.server.logger.info('%s: Received packet %s data %s', + self.sid, packet.packet_names[pkt.packet_type], + pkt.data if not isinstance(pkt.data, bytes) + else '') + if pkt.packet_type == packet.PING: + self.last_ping = time.time() + await self.send(packet.Packet(packet.PONG, pkt.data)) + elif pkt.packet_type == packet.MESSAGE: + await self.server._trigger_event( + 'message', self.sid, pkt.data, + run_async=self.server.async_handlers) + elif pkt.packet_type == packet.UPGRADE: + await self.send(packet.Packet(packet.NOOP)) + elif pkt.packet_type == packet.CLOSE: + await self.close(wait=False, abort=True) + else: + raise exceptions.UnknownPacketError() + + async def check_ping_timeout(self): + """Make sure the client is still sending pings. + + This helps detect disconnections for long-polling clients. + """ + if self.closed: + raise exceptions.SocketIsClosedError() + if time.time() - self.last_ping > self.server.ping_interval + 5: + self.server.logger.info('%s: Client is gone, closing socket', + self.sid) + # Passing abort=False here will cause close() to write a + # CLOSE packet. This has the effect of updating half-open sockets + # to their correct state of disconnected + await self.close(wait=False, abort=False) + return False + return True + + async def send(self, pkt): + """Send a packet to the client.""" + if not await self.check_ping_timeout(): + return + if self.upgrading: + self.packet_backlog.append(pkt) + else: + await self.queue.put(pkt) + self.server.logger.info('%s: Sending packet %s data %s', + self.sid, packet.packet_names[pkt.packet_type], + pkt.data if not isinstance(pkt.data, bytes) + else '') + + async def handle_get_request(self, environ): + """Handle a long-polling GET request from the client.""" + connections = [ + s.strip() + for s in environ.get('HTTP_CONNECTION', '').lower().split(',')] + transport = environ.get('HTTP_UPGRADE', '').lower() + if 'upgrade' in connections and transport in self.upgrade_protocols: + self.server.logger.info('%s: Received request to upgrade to %s', + self.sid, transport) + return await getattr(self, '_upgrade_' + transport)(environ) + try: + packets = await self.poll() + except exceptions.QueueEmpty: + exc = sys.exc_info() + await self.close(wait=False) + six.reraise(*exc) + return packets + + async def handle_post_request(self, environ): + """Handle a long-polling POST request from the client.""" + length = int(environ.get('CONTENT_LENGTH', '0')) + if length > self.server.max_http_buffer_size: + raise exceptions.ContentTooLongError() + else: + body = await environ['wsgi.input'].read(length) + p = payload.Payload(encoded_payload=body) + for pkt in p.packets: + await self.receive(pkt) + + async def close(self, wait=True, abort=False): + """Close the socket connection.""" + if not self.closed and not self.closing: + self.closing = True + await self.server._trigger_event('disconnect', self.sid) + if not abort: + await self.send(packet.Packet(packet.CLOSE)) + self.closed = True + if wait: + await self.queue.join() + + async def _upgrade_websocket(self, environ): + """Upgrade the connection from polling to websocket.""" + if self.upgraded: + raise IOError('Socket has been upgraded already') + if self.server._async['websocket'] is None: + # the selected async mode does not support websocket + return self.server._bad_request() + ws = self.server._async['websocket'](self._websocket_handler) + return await ws(environ) + + async def _websocket_handler(self, ws): + """Engine.IO handler for websocket transport.""" + if self.connected: + # the socket was already connected, so this is an upgrade + self.upgrading = True # hold packet sends during the upgrade + + try: + pkt = await ws.wait() + except IOError: # pragma: no cover + return + decoded_pkt = packet.Packet(encoded_packet=pkt) + if decoded_pkt.packet_type != packet.PING or \ + decoded_pkt.data != 'probe': + self.server.logger.info( + '%s: Failed websocket upgrade, no PING packet', self.sid) + return + await ws.send(packet.Packet( + packet.PONG, + data=six.text_type('probe')).encode(always_bytes=False)) + await self.queue.put(packet.Packet(packet.NOOP)) # end poll + + try: + pkt = await ws.wait() + except IOError: # pragma: no cover + return + decoded_pkt = packet.Packet(encoded_packet=pkt) + if decoded_pkt.packet_type != packet.UPGRADE: + self.upgraded = False + self.server.logger.info( + ('%s: Failed websocket upgrade, expected UPGRADE packet, ' + 'received %s instead.'), + self.sid, pkt) + return + self.upgraded = True + + # flush any packets that were sent during the upgrade + for pkt in self.packet_backlog: + await self.queue.put(pkt) + self.packet_backlog = [] + self.upgrading = False + else: + self.connected = True + self.upgraded = True + + # start separate writer thread + async def writer(): + while True: + packets = None + try: + packets = await self.poll() + except exceptions.QueueEmpty: + break + if not packets: + # empty packet list returned -> connection closed + break + try: + for pkt in packets: + await ws.send(pkt.encode(always_bytes=False)) + except: + break + writer_task = asyncio.ensure_future(writer()) + + self.server.logger.info( + '%s: Upgrade to websocket successful', self.sid) + + while True: + p = None + wait_task = asyncio.ensure_future(ws.wait()) + try: + p = await asyncio.wait_for(wait_task, self.server.ping_timeout) + except asyncio.CancelledError: # pragma: no cover + # there is a bug (https://bugs.python.org/issue30508) in + # asyncio that causes a "Task exception never retrieved" error + # to appear when wait_task raises an exception before it gets + # cancelled. Calling wait_task.exception() prevents the error + # from being issued in Python 3.6, but causes other errors in + # other versions, so we run it with all errors suppressed and + # hope for the best. + try: + wait_task.exception() + except: + pass + break + except: + break + if p is None: + # connection closed by client + break + if isinstance(p, six.text_type): # pragma: no cover + p = p.encode('utf-8') + pkt = packet.Packet(encoded_packet=p) + try: + await self.receive(pkt) + except exceptions.UnknownPacketError: # pragma: no cover + pass + except exceptions.SocketIsClosedError: # pragma: no cover + self.server.logger.info('Receive error -- socket is closed') + break + except: # pragma: no cover + # if we get an unexpected exception we log the error and exit + # the connection properly + self.server.logger.exception('Unknown receive error') + + await self.queue.put(None) # unlock the writer task so it can exit + await asyncio.wait_for(writer_task, timeout=None) + await self.close(wait=False, abort=True) diff --git a/openpype/vendor/python/python_2/engineio/client.py b/openpype/vendor/python/python_2/engineio/client.py new file mode 100644 index 0000000000..16dcc94e63 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/client.py @@ -0,0 +1,641 @@ +import logging +try: + import queue +except ImportError: # pragma: no cover + import Queue as queue +import signal +import threading +import time + +import six +from six.moves import urllib +try: + import requests +except ImportError: # pragma: no cover + requests = None +try: + import websocket +except ImportError: # pragma: no cover + websocket = None +from . import exceptions +from . import packet +from . import payload + +default_logger = logging.getLogger('engineio.client') +connected_clients = [] + +if six.PY2: # pragma: no cover + ConnectionError = OSError + + +def signal_handler(sig, frame): + """SIGINT handler. + + Disconnect all active clients and then invoke the original signal handler. + """ + for client in connected_clients[:]: + if client.is_asyncio_based(): + client.start_background_task(client.disconnect, abort=True) + else: + client.disconnect(abort=True) + return original_signal_handler(sig, frame) + + +original_signal_handler = signal.signal(signal.SIGINT, signal_handler) + + +class Client(object): + """An Engine.IO client. + + This class implements a fully compliant Engine.IO web client with support + for websocket and long-polling transports. + + :param logger: To enable logging set to ``True`` or pass a logger object to + use. To disable logging set to ``False``. The default is + ``False``. + :param json: An alternative json module to use for encoding and decoding + packets. Custom json modules must have ``dumps`` and ``loads`` + functions that are compatible with the standard library + versions. + """ + event_names = ['connect', 'disconnect', 'message'] + + def __init__(self, logger=False, json=None): + self.handlers = {} + self.base_url = None + self.transports = None + self.current_transport = None + self.sid = None + self.upgrades = None + self.ping_interval = None + self.ping_timeout = None + self.pong_received = True + self.http = None + self.ws = None + self.read_loop_task = None + self.write_loop_task = None + self.ping_loop_task = None + self.ping_loop_event = self.create_event() + self.queue = None + self.state = 'disconnected' + + if json is not None: + packet.Packet.json = json + if not isinstance(logger, bool): + self.logger = logger + else: + self.logger = default_logger + if not logging.root.handlers and \ + self.logger.level == logging.NOTSET: + if logger: + self.logger.setLevel(logging.INFO) + else: + self.logger.setLevel(logging.ERROR) + self.logger.addHandler(logging.StreamHandler()) + + def is_asyncio_based(self): + return False + + def on(self, event, handler=None): + """Register an event handler. + + :param event: The event name. Can be ``'connect'``, ``'message'`` or + ``'disconnect'``. + :param handler: The function that should be invoked to handle the + event. When this parameter is not given, the method + acts as a decorator for the handler function. + + Example usage:: + + # as a decorator: + @eio.on('connect') + def connect_handler(): + print('Connection request') + + # as a method: + def message_handler(msg): + print('Received message: ', msg) + eio.send('response') + eio.on('message', message_handler) + """ + if event not in self.event_names: + raise ValueError('Invalid event') + + def set_handler(handler): + self.handlers[event] = handler + return handler + + if handler is None: + return set_handler + set_handler(handler) + + def connect(self, url, headers={}, transports=None, + engineio_path='engine.io'): + """Connect to an Engine.IO server. + + :param url: The URL of the Engine.IO server. It can include custom + query string parameters if required by the server. + :param headers: A dictionary with custom headers to send with the + connection request. + :param transports: The list of allowed transports. Valid transports + are ``'polling'`` and ``'websocket'``. If not + given, the polling transport is connected first, + then an upgrade to websocket is attempted. + :param engineio_path: The endpoint where the Engine.IO server is + installed. The default value is appropriate for + most cases. + + Example usage:: + + eio = engineio.Client() + eio.connect('http://localhost:5000') + """ + if self.state != 'disconnected': + raise ValueError('Client is not in a disconnected state') + valid_transports = ['polling', 'websocket'] + if transports is not None: + if isinstance(transports, six.string_types): + transports = [transports] + transports = [transport for transport in transports + if transport in valid_transports] + if not transports: + raise ValueError('No valid transports provided') + self.transports = transports or valid_transports + self.queue = self.create_queue() + return getattr(self, '_connect_' + self.transports[0])( + url, headers, engineio_path) + + def wait(self): + """Wait until the connection with the server ends. + + Client applications can use this function to block the main thread + during the life of the connection. + """ + if self.read_loop_task: + self.read_loop_task.join() + + def send(self, data, binary=None): + """Send a message to a client. + + :param data: The data to send to the client. Data can be of type + ``str``, ``bytes``, ``list`` or ``dict``. If a ``list`` + or ``dict``, the data will be serialized as JSON. + :param binary: ``True`` to send packet as binary, ``False`` to send + as text. If not given, unicode (Python 2) and str + (Python 3) are sent as text, and str (Python 2) and + bytes (Python 3) are sent as binary. + """ + self._send_packet(packet.Packet(packet.MESSAGE, data=data, + binary=binary)) + + def disconnect(self, abort=False): + """Disconnect from the server. + + :param abort: If set to ``True``, do not wait for background tasks + associated with the connection to end. + """ + if self.state == 'connected': + self._send_packet(packet.Packet(packet.CLOSE)) + self.queue.put(None) + self.state = 'disconnecting' + self._trigger_event('disconnect', run_async=False) + if self.current_transport == 'websocket': + self.ws.close() + if not abort: + self.read_loop_task.join() + self.state = 'disconnected' + try: + connected_clients.remove(self) + except ValueError: # pragma: no cover + pass + self._reset() + + def transport(self): + """Return the name of the transport currently in use. + + The possible values returned by this function are ``'polling'`` and + ``'websocket'``. + """ + return self.current_transport + + def start_background_task(self, target, *args, **kwargs): + """Start a background task. + + This is a utility function that applications can use to start a + background task. + + :param target: the target function to execute. + :param args: arguments to pass to the function. + :param kwargs: keyword arguments to pass to the function. + + This function returns an object compatible with the `Thread` class in + the Python standard library. The `start()` method on this object is + already called by this function. + """ + th = threading.Thread(target=target, args=args, kwargs=kwargs) + th.start() + return th + + def sleep(self, seconds=0): + """Sleep for the requested amount of time.""" + return time.sleep(seconds) + + def create_queue(self, *args, **kwargs): + """Create a queue object.""" + q = queue.Queue(*args, **kwargs) + q.Empty = queue.Empty + return q + + def create_event(self, *args, **kwargs): + """Create an event object.""" + return threading.Event(*args, **kwargs) + + def _reset(self): + self.state = 'disconnected' + self.sid = None + + def _connect_polling(self, url, headers, engineio_path): + """Establish a long-polling connection to the Engine.IO server.""" + if requests is None: # pragma: no cover + # not installed + self.logger.error('requests package is not installed -- cannot ' + 'send HTTP requests!') + return + self.base_url = self._get_engineio_url(url, engineio_path, 'polling') + self.logger.info('Attempting polling connection to ' + self.base_url) + r = self._send_request( + 'GET', self.base_url + self._get_url_timestamp(), headers=headers) + if r is None: + self._reset() + raise exceptions.ConnectionError( + 'Connection refused by the server') + if r.status_code != 200: + raise exceptions.ConnectionError( + 'Unexpected status code {} in server response'.format( + r.status_code)) + try: + p = payload.Payload(encoded_payload=r.content) + except ValueError: + six.raise_from(exceptions.ConnectionError( + 'Unexpected response from server'), None) + open_packet = p.packets[0] + if open_packet.packet_type != packet.OPEN: + raise exceptions.ConnectionError( + 'OPEN packet not returned by server') + self.logger.info( + 'Polling connection accepted with ' + str(open_packet.data)) + self.sid = open_packet.data['sid'] + self.upgrades = open_packet.data['upgrades'] + self.ping_interval = open_packet.data['pingInterval'] / 1000.0 + self.ping_timeout = open_packet.data['pingTimeout'] / 1000.0 + self.current_transport = 'polling' + self.base_url += '&sid=' + self.sid + + self.state = 'connected' + connected_clients.append(self) + self._trigger_event('connect', run_async=False) + + for pkt in p.packets[1:]: + self._receive_packet(pkt) + + if 'websocket' in self.upgrades and 'websocket' in self.transports: + # attempt to upgrade to websocket + if self._connect_websocket(url, headers, engineio_path): + # upgrade to websocket succeeded, we're done here + return + + # start background tasks associated with this client + self.ping_loop_task = self.start_background_task(self._ping_loop) + self.write_loop_task = self.start_background_task(self._write_loop) + self.read_loop_task = self.start_background_task( + self._read_loop_polling) + + def _connect_websocket(self, url, headers, engineio_path): + """Establish or upgrade to a WebSocket connection with the server.""" + if websocket is None: # pragma: no cover + # not installed + self.logger.warning('websocket-client package not installed, only ' + 'polling transport is available') + return False + websocket_url = self._get_engineio_url(url, engineio_path, 'websocket') + if self.sid: + self.logger.info( + 'Attempting WebSocket upgrade to ' + websocket_url) + upgrade = True + websocket_url += '&sid=' + self.sid + else: + upgrade = False + self.base_url = websocket_url + self.logger.info( + 'Attempting WebSocket connection to ' + websocket_url) + + # get the cookies from the long-polling connection so that they can + # also be sent the the WebSocket route + cookies = None + if self.http: + cookies = '; '.join(["{}={}".format(cookie.name, cookie.value) + for cookie in self.http.cookies]) + try: + ws = websocket.create_connection( + websocket_url + self._get_url_timestamp(), header=headers, + cookie=cookies) + except ConnectionError: + if upgrade: + self.logger.warning( + 'WebSocket upgrade failed: connection error') + return False + else: + raise exceptions.ConnectionError('Connection error') + if upgrade: + p = packet.Packet(packet.PING, + data=six.text_type('probe')).encode() + try: + ws.send(p) + except Exception as e: # pragma: no cover + self.logger.warning( + 'WebSocket upgrade failed: unexpected send exception: %s', + str(e)) + return False + try: + p = ws.recv() + except Exception as e: # pragma: no cover + self.logger.warning( + 'WebSocket upgrade failed: unexpected recv exception: %s', + str(e)) + return False + pkt = packet.Packet(encoded_packet=p) + if pkt.packet_type != packet.PONG or pkt.data != 'probe': + self.logger.warning( + 'WebSocket upgrade failed: no PONG packet') + return False + p = packet.Packet(packet.UPGRADE).encode() + try: + ws.send(p) + except Exception as e: # pragma: no cover + self.logger.warning( + 'WebSocket upgrade failed: unexpected send exception: %s', + str(e)) + return False + self.current_transport = 'websocket' + self.logger.info('WebSocket upgrade was successful') + else: + try: + p = ws.recv() + except Exception as e: # pragma: no cover + raise exceptions.ConnectionError( + 'Unexpected recv exception: ' + str(e)) + open_packet = packet.Packet(encoded_packet=p) + if open_packet.packet_type != packet.OPEN: + raise exceptions.ConnectionError('no OPEN packet') + self.logger.info( + 'WebSocket connection accepted with ' + str(open_packet.data)) + self.sid = open_packet.data['sid'] + self.upgrades = open_packet.data['upgrades'] + self.ping_interval = open_packet.data['pingInterval'] / 1000.0 + self.ping_timeout = open_packet.data['pingTimeout'] / 1000.0 + self.current_transport = 'websocket' + + self.state = 'connected' + connected_clients.append(self) + self._trigger_event('connect', run_async=False) + self.ws = ws + + # start background tasks associated with this client + self.ping_loop_task = self.start_background_task(self._ping_loop) + self.write_loop_task = self.start_background_task(self._write_loop) + self.read_loop_task = self.start_background_task( + self._read_loop_websocket) + return True + + def _receive_packet(self, pkt): + """Handle incoming packets from the server.""" + packet_name = packet.packet_names[pkt.packet_type] \ + if pkt.packet_type < len(packet.packet_names) else 'UNKNOWN' + self.logger.info( + 'Received packet %s data %s', packet_name, + pkt.data if not isinstance(pkt.data, bytes) else '') + if pkt.packet_type == packet.MESSAGE: + self._trigger_event('message', pkt.data, run_async=True) + elif pkt.packet_type == packet.PONG: + self.pong_received = True + elif pkt.packet_type == packet.CLOSE: + self.disconnect(abort=True) + elif pkt.packet_type == packet.NOOP: + pass + else: + self.logger.error('Received unexpected packet of type %s', + pkt.packet_type) + + def _send_packet(self, pkt): + """Queue a packet to be sent to the server.""" + if self.state != 'connected': + return + self.queue.put(pkt) + self.logger.info( + 'Sending packet %s data %s', + packet.packet_names[pkt.packet_type], + pkt.data if not isinstance(pkt.data, bytes) else '') + + def _send_request( + self, method, url, headers=None, body=None): # pragma: no cover + if self.http is None: + self.http = requests.Session() + try: + return self.http.request(method, url, headers=headers, data=body) + except requests.exceptions.RequestException: + pass + + def _trigger_event(self, event, *args, **kwargs): + """Invoke an event handler.""" + run_async = kwargs.pop('run_async', False) + if event in self.handlers: + if run_async: + return self.start_background_task(self.handlers[event], *args) + else: + try: + return self.handlers[event](*args) + except: + self.logger.exception(event + ' handler error') + + def _get_engineio_url(self, url, engineio_path, transport): + """Generate the Engine.IO connection URL.""" + engineio_path = engineio_path.strip('/') + parsed_url = urllib.parse.urlparse(url) + + if transport == 'polling': + scheme = 'http' + elif transport == 'websocket': + scheme = 'ws' + else: # pragma: no cover + raise ValueError('invalid transport') + if parsed_url.scheme in ['https', 'wss']: + scheme += 's' + + return ('{scheme}://{netloc}/{path}/?{query}' + '{sep}transport={transport}&EIO=3').format( + scheme=scheme, netloc=parsed_url.netloc, + path=engineio_path, query=parsed_url.query, + sep='&' if parsed_url.query else '', + transport=transport) + + def _get_url_timestamp(self): + """Generate the Engine.IO query string timestamp.""" + return '&t=' + str(time.time()) + + def _ping_loop(self): + """This background task sends a PING to the server at the requested + interval. + """ + self.pong_received = True + self.ping_loop_event.clear() + while self.state == 'connected': + if not self.pong_received: + self.logger.info( + 'PONG response has not been received, aborting') + if self.ws: + self.ws.close() + self.queue.put(None) + break + self.pong_received = False + self._send_packet(packet.Packet(packet.PING)) + self.ping_loop_event.wait(timeout=self.ping_interval) + self.logger.info('Exiting ping task') + + def _read_loop_polling(self): + """Read packets by polling the Engine.IO server.""" + while self.state == 'connected': + self.logger.info( + 'Sending polling GET request to ' + self.base_url) + r = self._send_request( + 'GET', self.base_url + self._get_url_timestamp()) + if r is None: + self.logger.warning( + 'Connection refused by the server, aborting') + self.queue.put(None) + break + if r.status_code != 200: + self.logger.warning('Unexpected status code %s in server ' + 'response, aborting', r.status_code) + self.queue.put(None) + break + try: + p = payload.Payload(encoded_payload=r.content) + except ValueError: + self.logger.warning( + 'Unexpected packet from server, aborting') + self.queue.put(None) + break + for pkt in p.packets: + self._receive_packet(pkt) + + self.logger.info('Waiting for write loop task to end') + self.write_loop_task.join() + self.logger.info('Waiting for ping loop task to end') + self.ping_loop_event.set() + self.ping_loop_task.join() + if self.state == 'connected': + self._trigger_event('disconnect', run_async=False) + try: + connected_clients.remove(self) + except ValueError: # pragma: no cover + pass + self._reset() + self.logger.info('Exiting read loop task') + + def _read_loop_websocket(self): + """Read packets from the Engine.IO WebSocket connection.""" + while self.state == 'connected': + p = None + try: + p = self.ws.recv() + except websocket.WebSocketConnectionClosedException: + self.logger.warning( + 'WebSocket connection was closed, aborting') + self.queue.put(None) + break + except Exception as e: + self.logger.info( + 'Unexpected error "%s", aborting', str(e)) + self.queue.put(None) + break + if isinstance(p, six.text_type): # pragma: no cover + p = p.encode('utf-8') + pkt = packet.Packet(encoded_packet=p) + self._receive_packet(pkt) + + self.logger.info('Waiting for write loop task to end') + self.write_loop_task.join() + self.logger.info('Waiting for ping loop task to end') + self.ping_loop_event.set() + self.ping_loop_task.join() + if self.state == 'connected': + self._trigger_event('disconnect', run_async=False) + try: + connected_clients.remove(self) + except ValueError: # pragma: no cover + pass + self._reset() + self.logger.info('Exiting read loop task') + + def _write_loop(self): + """This background task sends packages to the server as they are + pushed to the send queue. + """ + while self.state == 'connected': + # to simplify the timeout handling, use the maximum of the + # ping interval and ping timeout as timeout, with an extra 5 + # seconds grace period + timeout = max(self.ping_interval, self.ping_timeout) + 5 + packets = None + try: + packets = [self.queue.get(timeout=timeout)] + except self.queue.Empty: + self.logger.error('packet queue is empty, aborting') + break + if packets == [None]: + self.queue.task_done() + packets = [] + else: + while True: + try: + packets.append(self.queue.get(block=False)) + except self.queue.Empty: + break + if packets[-1] is None: + packets = packets[:-1] + self.queue.task_done() + break + if not packets: + # empty packet list returned -> connection closed + break + if self.current_transport == 'polling': + p = payload.Payload(packets=packets) + r = self._send_request( + 'POST', self.base_url, body=p.encode(), + headers={'Content-Type': 'application/octet-stream'}) + for pkt in packets: + self.queue.task_done() + if r is None: + self.logger.warning( + 'Connection refused by the server, aborting') + break + if r.status_code != 200: + self.logger.warning('Unexpected status code %s in server ' + 'response, aborting', r.status_code) + self._reset() + break + else: + # websocket + try: + for pkt in packets: + encoded_packet = pkt.encode(always_bytes=False) + if pkt.binary: + self.ws.send_binary(encoded_packet) + else: + self.ws.send(encoded_packet) + self.queue.task_done() + except websocket.WebSocketConnectionClosedException: + self.logger.warning( + 'WebSocket connection was closed, aborting') + break + self.logger.info('Exiting write loop task') diff --git a/openpype/vendor/python/python_2/engineio/exceptions.py b/openpype/vendor/python/python_2/engineio/exceptions.py new file mode 100644 index 0000000000..fb0b3e057c --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/exceptions.py @@ -0,0 +1,22 @@ +class EngineIOError(Exception): + pass + + +class ContentTooLongError(EngineIOError): + pass + + +class UnknownPacketError(EngineIOError): + pass + + +class QueueEmpty(EngineIOError): + pass + + +class SocketIsClosedError(EngineIOError): + pass + + +class ConnectionError(EngineIOError): + pass diff --git a/openpype/vendor/python/python_2/engineio/middleware.py b/openpype/vendor/python/python_2/engineio/middleware.py new file mode 100644 index 0000000000..d0bdcc7476 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/middleware.py @@ -0,0 +1,87 @@ +import os +from engineio.static_files import get_static_file + + +class WSGIApp(object): + """WSGI application middleware for Engine.IO. + + This middleware dispatches traffic to an Engine.IO application. It can + also serve a list of static files to the client, or forward unrelated + HTTP traffic to another WSGI application. + + :param engineio_app: The Engine.IO server. Must be an instance of the + ``engineio.Server`` class. + :param wsgi_app: The WSGI app that receives all other traffic. + :param static_files: A dictionary with static file mapping rules. See the + documentation for details on this argument. + :param engineio_path: The endpoint where the Engine.IO application should + be installed. The default value is appropriate for + most cases. + + Example usage:: + + import engineio + import eventlet + + eio = engineio.Server() + app = engineio.WSGIApp(eio, static_files={ + '/': {'content_type': 'text/html', 'filename': 'index.html'}, + '/index.html': {'content_type': 'text/html', + 'filename': 'index.html'}, + }) + eventlet.wsgi.server(eventlet.listen(('', 8000)), app) + """ + def __init__(self, engineio_app, wsgi_app=None, static_files=None, + engineio_path='engine.io'): + self.engineio_app = engineio_app + self.wsgi_app = wsgi_app + self.engineio_path = engineio_path.strip('/') + self.static_files = static_files or {} + + def __call__(self, environ, start_response): + if 'gunicorn.socket' in environ: + # gunicorn saves the socket under environ['gunicorn.socket'], while + # eventlet saves it under environ['eventlet.input']. Eventlet also + # stores the socket inside a wrapper class, while gunicon writes it + # directly into the environment. To give eventlet's WebSocket + # module access to this socket when running under gunicorn, here we + # copy the socket to the eventlet format. + class Input(object): + def __init__(self, socket): + self.socket = socket + + def get_socket(self): + return self.socket + + environ['eventlet.input'] = Input(environ['gunicorn.socket']) + path = environ['PATH_INFO'] + if path is not None and \ + path.startswith('/{0}/'.format(self.engineio_path)): + return self.engineio_app.handle_request(environ, start_response) + else: + static_file = get_static_file(path, self.static_files) \ + if self.static_files else None + if static_file: + if os.path.exists(static_file['filename']): + start_response( + '200 OK', + [('Content-Type', static_file['content_type'])]) + with open(static_file['filename'], 'rb') as f: + return [f.read()] + else: + return self.not_found(start_response) + elif self.wsgi_app is not None: + return self.wsgi_app(environ, start_response) + return self.not_found(start_response) + + def not_found(self, start_response): + start_response("404 Not Found", [('Content-Type', 'text/plain')]) + return [b'Not Found'] + + +class Middleware(WSGIApp): + """This class has been renamed to ``WSGIApp`` and is now deprecated.""" + def __init__(self, engineio_app, wsgi_app=None, + engineio_path='engine.io'): + super(Middleware, self).__init__(engineio_app, wsgi_app, + engineio_path=engineio_path) diff --git a/openpype/vendor/python/python_2/engineio/packet.py b/openpype/vendor/python/python_2/engineio/packet.py new file mode 100644 index 0000000000..a3aa6d4761 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/packet.py @@ -0,0 +1,92 @@ +import base64 +import json as _json + +import six + +(OPEN, CLOSE, PING, PONG, MESSAGE, UPGRADE, NOOP) = (0, 1, 2, 3, 4, 5, 6) +packet_names = ['OPEN', 'CLOSE', 'PING', 'PONG', 'MESSAGE', 'UPGRADE', 'NOOP'] + +binary_types = (six.binary_type, bytearray) + + +class Packet(object): + """Engine.IO packet.""" + + json = _json + + def __init__(self, packet_type=NOOP, data=None, binary=None, + encoded_packet=None): + self.packet_type = packet_type + self.data = data + if binary is not None: + self.binary = binary + elif isinstance(data, six.text_type): + self.binary = False + elif isinstance(data, binary_types): + self.binary = True + else: + self.binary = False + if encoded_packet: + self.decode(encoded_packet) + + def encode(self, b64=False, always_bytes=True): + """Encode the packet for transmission.""" + if self.binary and not b64: + encoded_packet = six.int2byte(self.packet_type) + else: + encoded_packet = six.text_type(self.packet_type) + if self.binary and b64: + encoded_packet = 'b' + encoded_packet + if self.binary: + if b64: + encoded_packet += base64.b64encode(self.data).decode('utf-8') + else: + encoded_packet += self.data + elif isinstance(self.data, six.string_types): + encoded_packet += self.data + elif isinstance(self.data, dict) or isinstance(self.data, list): + encoded_packet += self.json.dumps(self.data, + separators=(',', ':')) + elif self.data is not None: + encoded_packet += str(self.data) + if always_bytes and not isinstance(encoded_packet, binary_types): + encoded_packet = encoded_packet.encode('utf-8') + return encoded_packet + + def decode(self, encoded_packet): + """Decode a transmitted package.""" + b64 = False + if not isinstance(encoded_packet, binary_types): + encoded_packet = encoded_packet.encode('utf-8') + elif not isinstance(encoded_packet, bytes): + encoded_packet = bytes(encoded_packet) + self.packet_type = six.byte2int(encoded_packet[0:1]) + if self.packet_type == 98: # 'b' --> binary base64 encoded packet + self.binary = True + encoded_packet = encoded_packet[1:] + self.packet_type = six.byte2int(encoded_packet[0:1]) + self.packet_type -= 48 + b64 = True + elif self.packet_type >= 48: + self.packet_type -= 48 + self.binary = False + else: + self.binary = True + self.data = None + if len(encoded_packet) > 1: + if self.binary: + if b64: + self.data = base64.b64decode(encoded_packet[1:]) + else: + self.data = encoded_packet[1:] + else: + try: + self.data = self.json.loads( + encoded_packet[1:].decode('utf-8')) + if isinstance(self.data, int): + # do not allow integer payloads, see + # github.com/miguelgrinberg/python-engineio/issues/75 + # for background on this decision + raise ValueError + except ValueError: + self.data = encoded_packet[1:].decode('utf-8') diff --git a/openpype/vendor/python/python_2/engineio/payload.py b/openpype/vendor/python/python_2/engineio/payload.py new file mode 100644 index 0000000000..cfff557f5b --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/payload.py @@ -0,0 +1,80 @@ +import six + +from . import packet + +from six.moves import urllib + + +class Payload(object): + """Engine.IO payload.""" + def __init__(self, packets=None, encoded_payload=None): + self.packets = packets or [] + if encoded_payload is not None: + self.decode(encoded_payload) + + def encode(self, b64=False, jsonp_index=None): + """Encode the payload for transmission.""" + encoded_payload = b'' + for pkt in self.packets: + encoded_packet = pkt.encode(b64=b64) + packet_len = len(encoded_packet) + if b64: + encoded_payload += str(packet_len).encode('utf-8') + b':' + \ + encoded_packet + else: + binary_len = b'' + while packet_len != 0: + binary_len = six.int2byte(packet_len % 10) + binary_len + packet_len = int(packet_len / 10) + if not pkt.binary: + encoded_payload += b'\0' + else: + encoded_payload += b'\1' + encoded_payload += binary_len + b'\xff' + encoded_packet + if jsonp_index is not None: + encoded_payload = b'___eio[' + \ + str(jsonp_index).encode() + \ + b']("' + \ + encoded_payload.replace(b'"', b'\\"') + \ + b'");' + return encoded_payload + + def decode(self, encoded_payload): + """Decode a transmitted payload.""" + self.packets = [] + while encoded_payload: + # JSONP POST payload starts with 'd=' + if encoded_payload.startswith(b'd='): + encoded_payload = urllib.parse.parse_qs( + encoded_payload)[b'd'][0] + + if six.byte2int(encoded_payload[0:1]) <= 1: + packet_len = 0 + i = 1 + while six.byte2int(encoded_payload[i:i + 1]) != 255: + packet_len = packet_len * 10 + six.byte2int( + encoded_payload[i:i + 1]) + i += 1 + self.packets.append(packet.Packet( + encoded_packet=encoded_payload[i + 1:i + 1 + packet_len])) + else: + i = encoded_payload.find(b':') + if i == -1: + raise ValueError('invalid payload') + + # extracting the packet out of the payload is extremely + # inefficient, because the payload needs to be treated as + # binary, but the non-binary packets have to be parsed as + # unicode. Luckily this complication only applies to long + # polling, as the websocket transport sends packets + # individually wrapped. + packet_len = int(encoded_payload[0:i]) + pkt = encoded_payload.decode('utf-8', errors='ignore')[ + i + 1: i + 1 + packet_len].encode('utf-8') + self.packets.append(packet.Packet(encoded_packet=pkt)) + + # the engine.io protocol sends the packet length in + # utf-8 characters, but we need it in bytes to be able to + # jump to the next packet in the payload + packet_len = len(pkt) + encoded_payload = encoded_payload[i + 1 + packet_len:] diff --git a/openpype/vendor/python/python_2/engineio/server.py b/openpype/vendor/python/python_2/engineio/server.py new file mode 100644 index 0000000000..a2ba7d16b4 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/server.py @@ -0,0 +1,633 @@ +import gzip +import importlib +import logging +import uuid +import zlib + +import six +from six.moves import urllib + +from . import exceptions +from . import packet +from . import payload +from . import socket + +default_logger = logging.getLogger('engineio.server') + + +class Server(object): + """An Engine.IO server. + + This class implements a fully compliant Engine.IO web server with support + for websocket and long-polling transports. + + :param async_mode: The asynchronous model to use. See the Deployment + section in the documentation for a description of the + available options. Valid async modes are "threading", + "eventlet", "gevent" and "gevent_uwsgi". If this + argument is not given, "eventlet" is tried first, then + "gevent_uwsgi", then "gevent", and finally "threading". + The first async mode that has all its dependencies + installed is the one that is chosen. + :param ping_timeout: The time in seconds that the client waits for the + server to respond before disconnecting. The default + is 60 seconds. + :param ping_interval: The interval in seconds at which the client pings + the server. The default is 25 seconds. + :param max_http_buffer_size: The maximum size of a message when using the + polling transport. The default is 100,000,000 + bytes. + :param allow_upgrades: Whether to allow transport upgrades or not. The + default is ``True``. + :param http_compression: Whether to compress packages when using the + polling transport. The default is ``True``. + :param compression_threshold: Only compress messages when their byte size + is greater than this value. The default is + 1024 bytes. + :param cookie: Name of the HTTP cookie that contains the client session + id. If set to ``None``, a cookie is not sent to the client. + The default is ``'io'``. + :param cors_allowed_origins: Origin or list of origins that are allowed to + connect to this server. All origins are + allowed by default, which is equivalent to + setting this argument to ``'*'``. + :param cors_credentials: Whether credentials (cookies, authentication) are + allowed in requests to this server. The default + is ``True``. + :param logger: To enable logging set to ``True`` or pass a logger object to + use. To disable logging set to ``False``. The default is + ``False``. + :param json: An alternative json module to use for encoding and decoding + packets. Custom json modules must have ``dumps`` and ``loads`` + functions that are compatible with the standard library + versions. + :param async_handlers: If set to ``True``, run message event handlers in + non-blocking threads. To run handlers synchronously, + set to ``False``. The default is ``True``. + :param monitor_clients: If set to ``True``, a background task will ensure + inactive clients are closed. Set to ``False`` to + disable the monitoring task (not recommended). The + default is ``True``. + :param kwargs: Reserved for future extensions, any additional parameters + given as keyword arguments will be silently ignored. + """ + compression_methods = ['gzip', 'deflate'] + event_names = ['connect', 'disconnect', 'message'] + _default_monitor_clients = True + + def __init__(self, async_mode=None, ping_timeout=60, ping_interval=25, + max_http_buffer_size=100000000, allow_upgrades=True, + http_compression=True, compression_threshold=1024, + cookie='io', cors_allowed_origins=None, + cors_credentials=True, logger=False, json=None, + async_handlers=True, monitor_clients=None, **kwargs): + self.ping_timeout = ping_timeout + self.ping_interval = ping_interval + self.max_http_buffer_size = max_http_buffer_size + self.allow_upgrades = allow_upgrades + self.http_compression = http_compression + self.compression_threshold = compression_threshold + self.cookie = cookie + self.cors_allowed_origins = cors_allowed_origins + self.cors_credentials = cors_credentials + self.async_handlers = async_handlers + self.sockets = {} + self.handlers = {} + self.start_service_task = monitor_clients \ + if monitor_clients is not None else self._default_monitor_clients + if json is not None: + packet.Packet.json = json + if not isinstance(logger, bool): + self.logger = logger + else: + self.logger = default_logger + if not logging.root.handlers and \ + self.logger.level == logging.NOTSET: + if logger: + self.logger.setLevel(logging.INFO) + else: + self.logger.setLevel(logging.ERROR) + self.logger.addHandler(logging.StreamHandler()) + modes = self.async_modes() + if async_mode is not None: + modes = [async_mode] if async_mode in modes else [] + self._async = None + self.async_mode = None + for mode in modes: + try: + self._async = importlib.import_module( + 'engineio.async_drivers.' + mode)._async + asyncio_based = self._async['asyncio'] \ + if 'asyncio' in self._async else False + if asyncio_based != self.is_asyncio_based(): + continue # pragma: no cover + self.async_mode = mode + break + except ImportError: + pass + if self.async_mode is None: + raise ValueError('Invalid async_mode specified') + if self.is_asyncio_based() and \ + ('asyncio' not in self._async or not + self._async['asyncio']): # pragma: no cover + raise ValueError('The selected async_mode is not asyncio ' + 'compatible') + if not self.is_asyncio_based() and 'asyncio' in self._async and \ + self._async['asyncio']: # pragma: no cover + raise ValueError('The selected async_mode requires asyncio and ' + 'must use the AsyncServer class') + self.logger.info('Server initialized for %s.', self.async_mode) + + def is_asyncio_based(self): + return False + + def async_modes(self): + return ['eventlet', 'gevent_uwsgi', 'gevent', 'threading'] + + def on(self, event, handler=None): + """Register an event handler. + + :param event: The event name. Can be ``'connect'``, ``'message'`` or + ``'disconnect'``. + :param handler: The function that should be invoked to handle the + event. When this parameter is not given, the method + acts as a decorator for the handler function. + + Example usage:: + + # as a decorator: + @eio.on('connect') + def connect_handler(sid, environ): + print('Connection request') + if environ['REMOTE_ADDR'] in blacklisted: + return False # reject + + # as a method: + def message_handler(sid, msg): + print('Received message: ', msg) + eio.send(sid, 'response') + eio.on('message', message_handler) + + The handler function receives the ``sid`` (session ID) for the + client as first argument. The ``'connect'`` event handler receives the + WSGI environment as a second argument, and can return ``False`` to + reject the connection. The ``'message'`` handler receives the message + payload as a second argument. The ``'disconnect'`` handler does not + take a second argument. + """ + if event not in self.event_names: + raise ValueError('Invalid event') + + def set_handler(handler): + self.handlers[event] = handler + return handler + + if handler is None: + return set_handler + set_handler(handler) + + def send(self, sid, data, binary=None): + """Send a message to a client. + + :param sid: The session id of the recipient client. + :param data: The data to send to the client. Data can be of type + ``str``, ``bytes``, ``list`` or ``dict``. If a ``list`` + or ``dict``, the data will be serialized as JSON. + :param binary: ``True`` to send packet as binary, ``False`` to send + as text. If not given, unicode (Python 2) and str + (Python 3) are sent as text, and str (Python 2) and + bytes (Python 3) are sent as binary. + """ + try: + socket = self._get_socket(sid) + except KeyError: + # the socket is not available + self.logger.warning('Cannot send to sid %s', sid) + return + socket.send(packet.Packet(packet.MESSAGE, data=data, binary=binary)) + + def get_session(self, sid): + """Return the user session for a client. + + :param sid: The session id of the client. + + The return value is a dictionary. Modifications made to this + dictionary are not guaranteed to be preserved unless + ``save_session()`` is called, or when the ``session`` context manager + is used. + """ + socket = self._get_socket(sid) + return socket.session + + def save_session(self, sid, session): + """Store the user session for a client. + + :param sid: The session id of the client. + :param session: The session dictionary. + """ + socket = self._get_socket(sid) + socket.session = session + + def session(self, sid): + """Return the user session for a client with context manager syntax. + + :param sid: The session id of the client. + + This is a context manager that returns the user session dictionary for + the client. Any changes that are made to this dictionary inside the + context manager block are saved back to the session. Example usage:: + + @eio.on('connect') + def on_connect(sid, environ): + username = authenticate_user(environ) + if not username: + return False + with eio.session(sid) as session: + session['username'] = username + + @eio.on('message') + def on_message(sid, msg): + with eio.session(sid) as session: + print('received message from ', session['username']) + """ + class _session_context_manager(object): + def __init__(self, server, sid): + self.server = server + self.sid = sid + self.session = None + + def __enter__(self): + self.session = self.server.get_session(sid) + return self.session + + def __exit__(self, *args): + self.server.save_session(sid, self.session) + + return _session_context_manager(self, sid) + + def disconnect(self, sid=None): + """Disconnect a client. + + :param sid: The session id of the client to close. If this parameter + is not given, then all clients are closed. + """ + if sid is not None: + try: + socket = self._get_socket(sid) + except KeyError: # pragma: no cover + # the socket was already closed or gone + pass + else: + socket.close() + del self.sockets[sid] + else: + for client in six.itervalues(self.sockets): + client.close() + self.sockets = {} + + def transport(self, sid): + """Return the name of the transport used by the client. + + The two possible values returned by this function are ``'polling'`` + and ``'websocket'``. + + :param sid: The session of the client. + """ + return 'websocket' if self._get_socket(sid).upgraded else 'polling' + + def handle_request(self, environ, start_response): + """Handle an HTTP request from the client. + + This is the entry point of the Engine.IO application, using the same + interface as a WSGI application. For the typical usage, this function + is invoked by the :class:`Middleware` instance, but it can be invoked + directly when the middleware is not used. + + :param environ: The WSGI environment. + :param start_response: The WSGI ``start_response`` function. + + This function returns the HTTP response body to deliver to the client + as a byte sequence. + """ + method = environ['REQUEST_METHOD'] + query = urllib.parse.parse_qs(environ.get('QUERY_STRING', '')) + + sid = query['sid'][0] if 'sid' in query else None + b64 = False + jsonp = False + jsonp_index = None + + if 'b64' in query: + if query['b64'][0] == "1" or query['b64'][0].lower() == "true": + b64 = True + if 'j' in query: + jsonp = True + try: + jsonp_index = int(query['j'][0]) + except (ValueError, KeyError, IndexError): + # Invalid JSONP index number + pass + + if jsonp and jsonp_index is None: + self.logger.warning('Invalid JSONP index number') + r = self._bad_request() + elif method == 'GET': + if sid is None: + transport = query.get('transport', ['polling'])[0] + if transport != 'polling' and transport != 'websocket': + self.logger.warning('Invalid transport %s', transport) + r = self._bad_request() + else: + r = self._handle_connect(environ, start_response, + transport, b64, jsonp_index) + else: + if sid not in self.sockets: + self.logger.warning('Invalid session %s', sid) + r = self._bad_request() + else: + socket = self._get_socket(sid) + try: + packets = socket.handle_get_request( + environ, start_response) + if isinstance(packets, list): + r = self._ok(packets, b64=b64, + jsonp_index=jsonp_index) + else: + r = packets + except exceptions.EngineIOError: + if sid in self.sockets: # pragma: no cover + self.disconnect(sid) + r = self._bad_request() + if sid in self.sockets and self.sockets[sid].closed: + del self.sockets[sid] + elif method == 'POST': + if sid is None or sid not in self.sockets: + self.logger.warning('Invalid session %s', sid) + r = self._bad_request() + else: + socket = self._get_socket(sid) + try: + socket.handle_post_request(environ) + r = self._ok(jsonp_index=jsonp_index) + except exceptions.EngineIOError: + if sid in self.sockets: # pragma: no cover + self.disconnect(sid) + r = self._bad_request() + except: # pragma: no cover + # for any other unexpected errors, we log the error + # and keep going + self.logger.exception('post request handler error') + r = self._ok(jsonp_index=jsonp_index) + elif method == 'OPTIONS': + r = self._ok() + else: + self.logger.warning('Method %s not supported', method) + r = self._method_not_found() + + if not isinstance(r, dict): + return r or [] + if self.http_compression and \ + len(r['response']) >= self.compression_threshold: + encodings = [e.split(';')[0].strip() for e in + environ.get('HTTP_ACCEPT_ENCODING', '').split(',')] + for encoding in encodings: + if encoding in self.compression_methods: + r['response'] = \ + getattr(self, '_' + encoding)(r['response']) + r['headers'] += [('Content-Encoding', encoding)] + break + cors_headers = self._cors_headers(environ) + start_response(r['status'], r['headers'] + cors_headers) + return [r['response']] + + def start_background_task(self, target, *args, **kwargs): + """Start a background task using the appropriate async model. + + This is a utility function that applications can use to start a + background task using the method that is compatible with the + selected async mode. + + :param target: the target function to execute. + :param args: arguments to pass to the function. + :param kwargs: keyword arguments to pass to the function. + + This function returns an object compatible with the `Thread` class in + the Python standard library. The `start()` method on this object is + already called by this function. + """ + th = self._async['thread'](target=target, args=args, kwargs=kwargs) + th.start() + return th # pragma: no cover + + def sleep(self, seconds=0): + """Sleep for the requested amount of time using the appropriate async + model. + + This is a utility function that applications can use to put a task to + sleep without having to worry about using the correct call for the + selected async mode. + """ + return self._async['sleep'](seconds) + + def create_queue(self, *args, **kwargs): + """Create a queue object using the appropriate async model. + + This is a utility function that applications can use to create a queue + without having to worry about using the correct call for the selected + async mode. + """ + return self._async['queue'](*args, **kwargs) + + def get_queue_empty_exception(self): + """Return the queue empty exception for the appropriate async model. + + This is a utility function that applications can use to work with a + queue without having to worry about using the correct call for the + selected async mode. + """ + return self._async['queue_empty'] + + def create_event(self, *args, **kwargs): + """Create an event object using the appropriate async model. + + This is a utility function that applications can use to create an + event without having to worry about using the correct call for the + selected async mode. + """ + return self._async['event'](*args, **kwargs) + + def _generate_id(self): + """Generate a unique session id.""" + return uuid.uuid4().hex + + def _handle_connect(self, environ, start_response, transport, b64=False, + jsonp_index=None): + """Handle a client connection request.""" + if self.start_service_task: + # start the service task to monitor connected clients + self.start_service_task = False + self.start_background_task(self._service_task) + + sid = self._generate_id() + s = socket.Socket(self, sid) + self.sockets[sid] = s + + pkt = packet.Packet( + packet.OPEN, {'sid': sid, + 'upgrades': self._upgrades(sid, transport), + 'pingTimeout': int(self.ping_timeout * 1000), + 'pingInterval': int(self.ping_interval * 1000)}) + s.send(pkt) + + ret = self._trigger_event('connect', sid, environ, run_async=False) + if ret is False: + del self.sockets[sid] + self.logger.warning('Application rejected connection') + return self._unauthorized() + + if transport == 'websocket': + ret = s.handle_get_request(environ, start_response) + if s.closed: + # websocket connection ended, so we are done + del self.sockets[sid] + return ret + else: + s.connected = True + headers = None + if self.cookie: + headers = [('Set-Cookie', self.cookie + '=' + sid)] + try: + return self._ok(s.poll(), headers=headers, b64=b64, + jsonp_index=jsonp_index) + except exceptions.QueueEmpty: + return self._bad_request() + + def _upgrades(self, sid, transport): + """Return the list of possible upgrades for a client connection.""" + if not self.allow_upgrades or self._get_socket(sid).upgraded or \ + self._async['websocket'] is None or transport == 'websocket': + return [] + return ['websocket'] + + def _trigger_event(self, event, *args, **kwargs): + """Invoke an event handler.""" + run_async = kwargs.pop('run_async', False) + if event in self.handlers: + if run_async: + return self.start_background_task(self.handlers[event], *args) + else: + try: + return self.handlers[event](*args) + except: + self.logger.exception(event + ' handler error') + if event == 'connect': + # if connect handler raised error we reject the + # connection + return False + + def _get_socket(self, sid): + """Return the socket object for a given session.""" + try: + s = self.sockets[sid] + except KeyError: + raise KeyError('Session not found') + if s.closed: + del self.sockets[sid] + raise KeyError('Session is disconnected') + return s + + def _ok(self, packets=None, headers=None, b64=False, jsonp_index=None): + """Generate a successful HTTP response.""" + if packets is not None: + if headers is None: + headers = [] + if b64: + headers += [('Content-Type', 'text/plain; charset=UTF-8')] + else: + headers += [('Content-Type', 'application/octet-stream')] + return {'status': '200 OK', + 'headers': headers, + 'response': payload.Payload(packets=packets).encode( + b64=b64, jsonp_index=jsonp_index)} + else: + return {'status': '200 OK', + 'headers': [('Content-Type', 'text/plain')], + 'response': b'OK'} + + def _bad_request(self): + """Generate a bad request HTTP error response.""" + return {'status': '400 BAD REQUEST', + 'headers': [('Content-Type', 'text/plain')], + 'response': b'Bad Request'} + + def _method_not_found(self): + """Generate a method not found HTTP error response.""" + return {'status': '405 METHOD NOT FOUND', + 'headers': [('Content-Type', 'text/plain')], + 'response': b'Method Not Found'} + + def _unauthorized(self): + """Generate a unauthorized HTTP error response.""" + return {'status': '401 UNAUTHORIZED', + 'headers': [('Content-Type', 'text/plain')], + 'response': b'Unauthorized'} + + def _cors_headers(self, environ): + """Return the cross-origin-resource-sharing headers.""" + if isinstance(self.cors_allowed_origins, six.string_types): + if self.cors_allowed_origins == '*': + allowed_origins = None + else: + allowed_origins = [self.cors_allowed_origins] + else: + allowed_origins = self.cors_allowed_origins + if allowed_origins is not None and \ + environ.get('HTTP_ORIGIN', '') not in allowed_origins: + return [] + if 'HTTP_ORIGIN' in environ: + headers = [('Access-Control-Allow-Origin', environ['HTTP_ORIGIN'])] + else: + headers = [('Access-Control-Allow-Origin', '*')] + if environ['REQUEST_METHOD'] == 'OPTIONS': + headers += [('Access-Control-Allow-Methods', 'OPTIONS, GET, POST')] + if 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS' in environ: + headers += [('Access-Control-Allow-Headers', + environ['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])] + if self.cors_credentials: + headers += [('Access-Control-Allow-Credentials', 'true')] + return headers + + def _gzip(self, response): + """Apply gzip compression to a response.""" + bytesio = six.BytesIO() + with gzip.GzipFile(fileobj=bytesio, mode='w') as gz: + gz.write(response) + return bytesio.getvalue() + + def _deflate(self, response): + """Apply deflate compression to a response.""" + return zlib.compress(response) + + def _service_task(self): # pragma: no cover + """Monitor connected clients and clean up those that time out.""" + while True: + if len(self.sockets) == 0: + # nothing to do + self.sleep(self.ping_timeout) + continue + + # go through the entire client list in a ping interval cycle + sleep_interval = self.ping_timeout / len(self.sockets) + + try: + # iterate over the current clients + for s in self.sockets.copy().values(): + if not s.closing and not s.closed: + s.check_ping_timeout() + self.sleep(sleep_interval) + except (SystemExit, KeyboardInterrupt): + self.logger.info('service task canceled') + break + except: + # an unexpected exception has occurred, log it and continue + self.logger.exception('service task exception') diff --git a/openpype/vendor/python/python_2/engineio/socket.py b/openpype/vendor/python/python_2/engineio/socket.py new file mode 100644 index 0000000000..495088ae3e --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/socket.py @@ -0,0 +1,247 @@ +import six +import sys +import time + +from . import exceptions +from . import packet +from . import payload + + +class Socket(object): + """An Engine.IO socket.""" + upgrade_protocols = ['websocket'] + + def __init__(self, server, sid): + self.server = server + self.sid = sid + self.queue = self.server.create_queue() + self.last_ping = time.time() + self.connected = False + self.upgrading = False + self.upgraded = False + self.packet_backlog = [] + self.closing = False + self.closed = False + self.session = {} + + def poll(self): + """Wait for packets to send to the client.""" + queue_empty = self.server.get_queue_empty_exception() + try: + packets = [self.queue.get(timeout=self.server.ping_timeout)] + self.queue.task_done() + except queue_empty: + raise exceptions.QueueEmpty() + if packets == [None]: + return [] + while True: + try: + packets.append(self.queue.get(block=False)) + self.queue.task_done() + except queue_empty: + break + return packets + + def receive(self, pkt): + """Receive packet from the client.""" + packet_name = packet.packet_names[pkt.packet_type] \ + if pkt.packet_type < len(packet.packet_names) else 'UNKNOWN' + self.server.logger.info('%s: Received packet %s data %s', + self.sid, packet_name, + pkt.data if not isinstance(pkt.data, bytes) + else '') + if pkt.packet_type == packet.PING: + self.last_ping = time.time() + self.send(packet.Packet(packet.PONG, pkt.data)) + elif pkt.packet_type == packet.MESSAGE: + self.server._trigger_event('message', self.sid, pkt.data, + run_async=self.server.async_handlers) + elif pkt.packet_type == packet.UPGRADE: + self.send(packet.Packet(packet.NOOP)) + elif pkt.packet_type == packet.CLOSE: + self.close(wait=False, abort=True) + else: + raise exceptions.UnknownPacketError() + + def check_ping_timeout(self): + """Make sure the client is still sending pings. + + This helps detect disconnections for long-polling clients. + """ + if self.closed: + raise exceptions.SocketIsClosedError() + if time.time() - self.last_ping > self.server.ping_interval + 5: + self.server.logger.info('%s: Client is gone, closing socket', + self.sid) + # Passing abort=False here will cause close() to write a + # CLOSE packet. This has the effect of updating half-open sockets + # to their correct state of disconnected + self.close(wait=False, abort=False) + return False + return True + + def send(self, pkt): + """Send a packet to the client.""" + if not self.check_ping_timeout(): + return + if self.upgrading: + self.packet_backlog.append(pkt) + else: + self.queue.put(pkt) + self.server.logger.info('%s: Sending packet %s data %s', + self.sid, packet.packet_names[pkt.packet_type], + pkt.data if not isinstance(pkt.data, bytes) + else '') + + def handle_get_request(self, environ, start_response): + """Handle a long-polling GET request from the client.""" + connections = [ + s.strip() + for s in environ.get('HTTP_CONNECTION', '').lower().split(',')] + transport = environ.get('HTTP_UPGRADE', '').lower() + if 'upgrade' in connections and transport in self.upgrade_protocols: + self.server.logger.info('%s: Received request to upgrade to %s', + self.sid, transport) + return getattr(self, '_upgrade_' + transport)(environ, + start_response) + try: + packets = self.poll() + except exceptions.QueueEmpty: + exc = sys.exc_info() + self.close(wait=False) + six.reraise(*exc) + return packets + + def handle_post_request(self, environ): + """Handle a long-polling POST request from the client.""" + length = int(environ.get('CONTENT_LENGTH', '0')) + if length > self.server.max_http_buffer_size: + raise exceptions.ContentTooLongError() + else: + body = environ['wsgi.input'].read(length) + p = payload.Payload(encoded_payload=body) + for pkt in p.packets: + self.receive(pkt) + + def close(self, wait=True, abort=False): + """Close the socket connection.""" + if not self.closed and not self.closing: + self.closing = True + self.server._trigger_event('disconnect', self.sid, run_async=False) + if not abort: + self.send(packet.Packet(packet.CLOSE)) + self.closed = True + self.queue.put(None) + if wait: + self.queue.join() + + def _upgrade_websocket(self, environ, start_response): + """Upgrade the connection from polling to websocket.""" + if self.upgraded: + raise IOError('Socket has been upgraded already') + if self.server._async['websocket'] is None: + # the selected async mode does not support websocket + return self.server._bad_request() + ws = self.server._async['websocket'](self._websocket_handler) + return ws(environ, start_response) + + def _websocket_handler(self, ws): + """Engine.IO handler for websocket transport.""" + # try to set a socket timeout matching the configured ping interval + for attr in ['_sock', 'socket']: # pragma: no cover + if hasattr(ws, attr) and hasattr(getattr(ws, attr), 'settimeout'): + getattr(ws, attr).settimeout(self.server.ping_timeout) + + if self.connected: + # the socket was already connected, so this is an upgrade + self.upgrading = True # hold packet sends during the upgrade + + pkt = ws.wait() + decoded_pkt = packet.Packet(encoded_packet=pkt) + if decoded_pkt.packet_type != packet.PING or \ + decoded_pkt.data != 'probe': + self.server.logger.info( + '%s: Failed websocket upgrade, no PING packet', self.sid) + return [] + ws.send(packet.Packet( + packet.PONG, + data=six.text_type('probe')).encode(always_bytes=False)) + self.queue.put(packet.Packet(packet.NOOP)) # end poll + + pkt = ws.wait() + decoded_pkt = packet.Packet(encoded_packet=pkt) + if decoded_pkt.packet_type != packet.UPGRADE: + self.upgraded = False + self.server.logger.info( + ('%s: Failed websocket upgrade, expected UPGRADE packet, ' + 'received %s instead.'), + self.sid, pkt) + return [] + self.upgraded = True + + # flush any packets that were sent during the upgrade + for pkt in self.packet_backlog: + self.queue.put(pkt) + self.packet_backlog = [] + self.upgrading = False + else: + self.connected = True + self.upgraded = True + + # start separate writer thread + def writer(): + while True: + packets = None + try: + packets = self.poll() + except exceptions.QueueEmpty: + break + if not packets: + # empty packet list returned -> connection closed + break + try: + for pkt in packets: + ws.send(pkt.encode(always_bytes=False)) + except: + break + writer_task = self.server.start_background_task(writer) + + self.server.logger.info( + '%s: Upgrade to websocket successful', self.sid) + + while True: + p = None + try: + p = ws.wait() + except Exception as e: + # if the socket is already closed, we can assume this is a + # downstream error of that + if not self.closed: # pragma: no cover + self.server.logger.info( + '%s: Unexpected error "%s", closing connection', + self.sid, str(e)) + break + if p is None: + # connection closed by client + break + if isinstance(p, six.text_type): # pragma: no cover + p = p.encode('utf-8') + pkt = packet.Packet(encoded_packet=p) + try: + self.receive(pkt) + except exceptions.UnknownPacketError: # pragma: no cover + pass + except exceptions.SocketIsClosedError: # pragma: no cover + self.server.logger.info('Receive error -- socket is closed') + break + except: # pragma: no cover + # if we get an unexpected exception we log the error and exit + # the connection properly + self.server.logger.exception('Unknown receive error') + break + + self.queue.put(None) # unlock the writer task so that it can exit + writer_task.join() + self.close(wait=False, abort=True) + + return [] diff --git a/openpype/vendor/python/python_2/engineio/static_files.py b/openpype/vendor/python/python_2/engineio/static_files.py new file mode 100644 index 0000000000..3058f6ea42 --- /dev/null +++ b/openpype/vendor/python/python_2/engineio/static_files.py @@ -0,0 +1,55 @@ +content_types = { + 'css': 'text/css', + 'gif': 'image/gif', + 'html': 'text/html', + 'jpg': 'image/jpeg', + 'js': 'application/javascript', + 'json': 'application/json', + 'png': 'image/png', + 'txt': 'text/plain', +} + + +def get_static_file(path, static_files): + """Return the local filename and content type for the requested static + file URL. + + :param path: the path portion of the requested URL. + :param static_files: a static file configuration dictionary. + + This function returns a dictionary with two keys, "filename" and + "content_type". If the requested URL does not match any static file, the + return value is None. + """ + if path in static_files: + f = static_files[path] + else: + f = None + rest = '' + while path != '': + path, last = path.rsplit('/', 1) + rest = '/' + last + rest + if path in static_files: + f = static_files[path] + rest + break + elif path + '/' in static_files: + f = static_files[path + '/'] + rest[1:] + break + if f: + if isinstance(f, str): + f = {'filename': f} + if f['filename'].endswith('/'): + if '' in static_files: + if isinstance(static_files[''], str): + f['filename'] += static_files[''] + else: + f['filename'] += static_files['']['filename'] + if 'content_type' in static_files['']: + f['content_type'] = static_files['']['content_type'] + else: + f['filename'] += 'index.html' + if 'content_type' not in f: + ext = f['filename'].rsplit('.')[-1] + f['content_type'] = content_types.get( + ext, 'application/octet-stream') + return f diff --git a/openpype/vendor/python/python_2/socketio/__init__.py b/openpype/vendor/python/python_2/socketio/__init__.py new file mode 100644 index 0000000000..f0a9541820 --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/__init__.py @@ -0,0 +1,35 @@ +import sys + +from .client import Client +from .base_manager import BaseManager +from .pubsub_manager import PubSubManager +from .kombu_manager import KombuManager +from .redis_manager import RedisManager +from .zmq_manager import ZmqManager +from .server import Server +from .namespace import Namespace, ClientNamespace +from .middleware import WSGIApp, Middleware +from .tornado import get_tornado_handler +if sys.version_info >= (3, 5): # pragma: no cover + from .asyncio_client import AsyncClient + from .asyncio_server import AsyncServer + from .asyncio_manager import AsyncManager + from .asyncio_namespace import AsyncNamespace, AsyncClientNamespace + from .asyncio_redis_manager import AsyncRedisManager + from .asgi import ASGIApp +else: # pragma: no cover + AsyncClient = None + AsyncServer = None + AsyncManager = None + AsyncNamespace = None + AsyncRedisManager = None + +__version__ = '4.2.1' + +__all__ = ['__version__', 'Client', 'Server', 'BaseManager', 'PubSubManager', + 'KombuManager', 'RedisManager', 'ZmqManager', 'Namespace', + 'ClientNamespace', 'WSGIApp', 'Middleware'] +if AsyncServer is not None: # pragma: no cover + __all__ += ['AsyncClient', 'AsyncServer', 'AsyncNamespace', + 'AsyncClientNamespace', 'AsyncManager', 'AsyncRedisManager', + 'ASGIApp', 'get_tornado_handler'] diff --git a/openpype/vendor/python/python_2/socketio/asgi.py b/openpype/vendor/python/python_2/socketio/asgi.py new file mode 100644 index 0000000000..9bcdd03bac --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/asgi.py @@ -0,0 +1,36 @@ +import engineio + + +class ASGIApp(engineio.ASGIApp): # pragma: no cover + """ASGI application middleware for Socket.IO. + + This middleware dispatches traffic to an Socket.IO application. It can + also serve a list of static files to the client, or forward unrelated + HTTP traffic to another ASGI application. + + :param socketio_server: The Socket.IO server. Must be an instance of the + ``socketio.AsyncServer`` class. + :param static_files: A dictionary with static file mapping rules. See the + documentation for details on this argument. + :param other_asgi_app: A separate ASGI app that receives all other traffic. + :param socketio_path: The endpoint where the Socket.IO application should + be installed. The default value is appropriate for + most cases. + + Example usage:: + + import socketio + import uvicorn + + sio = socketio.AsyncServer() + app = engineio.ASGIApp(sio, static_files={ + '/': 'index.html', + '/static': './public', + }) + uvicorn.run(app, host='127.0.0.1', port=5000) + """ + def __init__(self, socketio_server, other_asgi_app=None, + static_files=None, socketio_path='socket.io'): + super().__init__(socketio_server, other_asgi_app, + static_files=static_files, + engineio_path=socketio_path) diff --git a/openpype/vendor/python/python_2/socketio/asyncio_client.py b/openpype/vendor/python/python_2/socketio/asyncio_client.py new file mode 100644 index 0000000000..d71ccecfaa --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/asyncio_client.py @@ -0,0 +1,445 @@ +import asyncio +import logging +import random + +import engineio +import six + +from . import client +from . import exceptions +from . import packet + +default_logger = logging.getLogger('socketio.client') + + +class AsyncClient(client.Client): + """A Socket.IO client for asyncio. + + This class implements a fully compliant Socket.IO web client with support + for websocket and long-polling transports. + + :param reconnection: ``True`` if the client should automatically attempt to + reconnect to the server after an interruption, or + ``False`` to not reconnect. The default is ``True``. + :param reconnection_attempts: How many reconnection attempts to issue + before giving up, or 0 for infinity attempts. + The default is 0. + :param reconnection_delay: How long to wait in seconds before the first + reconnection attempt. Each successive attempt + doubles this delay. + :param reconnection_delay_max: The maximum delay between reconnection + attempts. + :param randomization_factor: Randomization amount for each delay between + reconnection attempts. The default is 0.5, + which means that each delay is randomly + adjusted by +/- 50%. + :param logger: To enable logging set to ``True`` or pass a logger object to + use. To disable logging set to ``False``. The default is + ``False``. + :param binary: ``True`` to support binary payloads, ``False`` to treat all + payloads as text. On Python 2, if this is set to ``True``, + ``unicode`` values are treated as text, and ``str`` and + ``bytes`` values are treated as binary. This option has no + effect on Python 3, where text and binary payloads are + always automatically discovered. + :param json: An alternative json module to use for encoding and decoding + packets. Custom json modules must have ``dumps`` and ``loads`` + functions that are compatible with the standard library + versions. + + The Engine.IO configuration supports the following settings: + + :param engineio_logger: To enable Engine.IO logging set to ``True`` or pass + a logger object to use. To disable logging set to + ``False``. The default is ``False``. + """ + def is_asyncio_based(self): + return True + + async def connect(self, url, headers={}, transports=None, + namespaces=None, socketio_path='socket.io'): + """Connect to a Socket.IO server. + + :param url: The URL of the Socket.IO server. It can include custom + query string parameters if required by the server. + :param headers: A dictionary with custom headers to send with the + connection request. + :param transports: The list of allowed transports. Valid transports + are ``'polling'`` and ``'websocket'``. If not + given, the polling transport is connected first, + then an upgrade to websocket is attempted. + :param namespaces: The list of custom namespaces to connect, in + addition to the default namespace. If not given, + the namespace list is obtained from the registered + event handlers. + :param socketio_path: The endpoint where the Socket.IO server is + installed. The default value is appropriate for + most cases. + + Note: this method is a coroutine. + + Example usage:: + + sio = socketio.Client() + sio.connect('http://localhost:5000') + """ + self.connection_url = url + self.connection_headers = headers + self.connection_transports = transports + self.connection_namespaces = namespaces + self.socketio_path = socketio_path + + if namespaces is None: + namespaces = set(self.handlers.keys()).union( + set(self.namespace_handlers.keys())) + elif isinstance(namespaces, six.string_types): + namespaces = [namespaces] + self.connection_namespaces = namespaces + self.namespaces = [n for n in namespaces if n != '/'] + try: + await self.eio.connect(url, headers=headers, + transports=transports, + engineio_path=socketio_path) + except engineio.exceptions.ConnectionError as exc: + six.raise_from(exceptions.ConnectionError(exc.args[0]), None) + + async def wait(self): + """Wait until the connection with the server ends. + + Client applications can use this function to block the main thread + during the life of the connection. + + Note: this method is a coroutine. + """ + while True: + await self.eio.wait() + await self.sleep(1) # give the reconnect task time to start up + if not self._reconnect_task: + break + await self._reconnect_task + if self.eio.state != 'connected': + break + + async def emit(self, event, data=None, namespace=None, callback=None): + """Emit a custom event to one or more connected clients. + + :param event: The event name. It can be any string. The event names + ``'connect'``, ``'message'`` and ``'disconnect'`` are + reserved and should not be used. + :param data: The data to send to the client or clients. Data can be of + type ``str``, ``bytes``, ``list`` or ``dict``. If a + ``list`` or ``dict``, the data will be serialized as JSON. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the event is emitted to the + default namespace. + :param callback: If given, this function will be called to acknowledge + the the client has received the message. The arguments + that will be passed to the function are those provided + by the client. Callback functions can only be used + when addressing an individual client. + + Note: this method is a coroutine. + """ + namespace = namespace or '/' + self.logger.info('Emitting event "%s" [%s]', event, namespace) + if callback is not None: + id = self._generate_ack_id(namespace, callback) + else: + id = None + if six.PY2 and not self.binary: + binary = False # pragma: nocover + else: + binary = None + # tuples are expanded to multiple arguments, everything else is sent + # as a single argument + if isinstance(data, tuple): + data = list(data) + elif data is not None: + data = [data] + else: + data = [] + await self._send_packet(packet.Packet( + packet.EVENT, namespace=namespace, data=[event] + data, id=id, + binary=binary)) + + async def send(self, data, namespace=None, callback=None): + """Send a message to one or more connected clients. + + This function emits an event with the name ``'message'``. Use + :func:`emit` to issue custom event names. + + :param data: The data to send to the client or clients. Data can be of + type ``str``, ``bytes``, ``list`` or ``dict``. If a + ``list`` or ``dict``, the data will be serialized as JSON. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the event is emitted to the + default namespace. + :param callback: If given, this function will be called to acknowledge + the the client has received the message. The arguments + that will be passed to the function are those provided + by the client. Callback functions can only be used + when addressing an individual client. + + Note: this method is a coroutine. + """ + await self.emit('message', data=data, namespace=namespace, + callback=callback) + + async def call(self, event, data=None, namespace=None, timeout=60): + """Emit a custom event to a client and wait for the response. + + :param event: The event name. It can be any string. The event names + ``'connect'``, ``'message'`` and ``'disconnect'`` are + reserved and should not be used. + :param data: The data to send to the client or clients. Data can be of + type ``str``, ``bytes``, ``list`` or ``dict``. If a + ``list`` or ``dict``, the data will be serialized as JSON. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the event is emitted to the + default namespace. + :param timeout: The waiting timeout. If the timeout is reached before + the client acknowledges the event, then a + ``TimeoutError`` exception is raised. + + Note: this method is a coroutine. + """ + callback_event = self.eio.create_event() + callback_args = [] + + def event_callback(*args): + callback_args.append(args) + callback_event.set() + + await self.emit(event, data=data, namespace=namespace, + callback=event_callback) + try: + await asyncio.wait_for(callback_event.wait(), timeout) + except asyncio.TimeoutError: + six.raise_from(exceptions.TimeoutError(), None) + return callback_args[0] if len(callback_args[0]) > 1 \ + else callback_args[0][0] if len(callback_args[0]) == 1 \ + else None + + async def disconnect(self): + """Disconnect from the server. + + Note: this method is a coroutine. + """ + # here we just request the disconnection + # later in _handle_eio_disconnect we invoke the disconnect handler + for n in self.namespaces: + await self._send_packet(packet.Packet(packet.DISCONNECT, + namespace=n)) + await self._send_packet(packet.Packet( + packet.DISCONNECT, namespace='/')) + await self.eio.disconnect(abort=True) + + def start_background_task(self, target, *args, **kwargs): + """Start a background task using the appropriate async model. + + This is a utility function that applications can use to start a + background task using the method that is compatible with the + selected async mode. + + :param target: the target function to execute. + :param args: arguments to pass to the function. + :param kwargs: keyword arguments to pass to the function. + + This function returns an object compatible with the `Thread` class in + the Python standard library. The `start()` method on this object is + already called by this function. + """ + return self.eio.start_background_task(target, *args, **kwargs) + + async def sleep(self, seconds=0): + """Sleep for the requested amount of time using the appropriate async + model. + + This is a utility function that applications can use to put a task to + sleep without having to worry about using the correct call for the + selected async mode. + + Note: this method is a coroutine. + """ + return await self.eio.sleep(seconds) + + async def _send_packet(self, pkt): + """Send a Socket.IO packet to the server.""" + encoded_packet = pkt.encode() + if isinstance(encoded_packet, list): + binary = False + for ep in encoded_packet: + await self.eio.send(ep, binary=binary) + binary = True + else: + await self.eio.send(encoded_packet, binary=False) + + async def _handle_connect(self, namespace): + namespace = namespace or '/' + self.logger.info('Namespace {} is connected'.format(namespace)) + await self._trigger_event('connect', namespace=namespace) + if namespace == '/': + for n in self.namespaces: + await self._send_packet(packet.Packet(packet.CONNECT, + namespace=n)) + elif namespace not in self.namespaces: + self.namespaces.append(namespace) + + async def _handle_disconnect(self, namespace): + namespace = namespace or '/' + await self._trigger_event('disconnect', namespace=namespace) + if namespace in self.namespaces: + self.namespaces.remove(namespace) + + async def _handle_event(self, namespace, id, data): + namespace = namespace or '/' + self.logger.info('Received event "%s" [%s]', data[0], namespace) + r = await self._trigger_event(data[0], namespace, *data[1:]) + if id is not None: + # send ACK packet with the response returned by the handler + # tuples are expanded as multiple arguments + if r is None: + data = [] + elif isinstance(r, tuple): + data = list(r) + else: + data = [r] + if six.PY2 and not self.binary: + binary = False # pragma: nocover + else: + binary = None + await self._send_packet(packet.Packet( + packet.ACK, namespace=namespace, id=id, data=data, + binary=binary)) + + async def _handle_ack(self, namespace, id, data): + namespace = namespace or '/' + self.logger.info('Received ack [%s]', namespace) + callback = None + try: + callback = self.callbacks[namespace][id] + except KeyError: + # if we get an unknown callback we just ignore it + self.logger.warning('Unknown callback received, ignoring.') + else: + del self.callbacks[namespace][id] + if callback is not None: + if asyncio.iscoroutinefunction(callback): + await callback(*data) + else: + callback(*data) + + def _handle_error(self, namespace): + namespace = namespace or '/' + self.logger.info('Connection to namespace {} was rejected'.format( + namespace)) + if namespace in self.namespaces: + self.namespaces.remove(namespace) + + async def _trigger_event(self, event, namespace, *args): + """Invoke an application event handler.""" + # first see if we have an explicit handler for the event + if namespace in self.handlers and event in self.handlers[namespace]: + if asyncio.iscoroutinefunction(self.handlers[namespace][event]): + try: + ret = await self.handlers[namespace][event](*args) + except asyncio.CancelledError: # pragma: no cover + ret = None + else: + ret = self.handlers[namespace][event](*args) + return ret + + # or else, forward the event to a namepsace handler if one exists + elif namespace in self.namespace_handlers: + return await self.namespace_handlers[namespace].trigger_event( + event, *args) + + async def _handle_reconnect(self): + self._reconnect_abort.clear() + client.reconnecting_clients.append(self) + attempt_count = 0 + current_delay = self.reconnection_delay + while True: + delay = current_delay + current_delay *= 2 + if delay > self.reconnection_delay_max: + delay = self.reconnection_delay_max + delay += self.randomization_factor * (2 * random.random() - 1) + self.logger.info( + 'Connection failed, new attempt in {:.02f} seconds'.format( + delay)) + try: + await asyncio.wait_for(self._reconnect_abort.wait(), delay) + self.logger.info('Reconnect task aborted') + break + except (asyncio.TimeoutError, asyncio.CancelledError): + pass + attempt_count += 1 + try: + await self.connect(self.connection_url, + headers=self.connection_headers, + transports=self.connection_transports, + namespaces=self.connection_namespaces, + socketio_path=self.socketio_path) + except (exceptions.ConnectionError, ValueError): + pass + else: + self.logger.info('Reconnection successful') + self._reconnect_task = None + break + if self.reconnection_attempts and \ + attempt_count >= self.reconnection_attempts: + self.logger.info( + 'Maximum reconnection attempts reached, giving up') + break + client.reconnecting_clients.remove(self) + + def _handle_eio_connect(self): + """Handle the Engine.IO connection event.""" + self.logger.info('Engine.IO connection established') + self.sid = self.eio.sid + + async def _handle_eio_message(self, data): + """Dispatch Engine.IO messages.""" + if self._binary_packet: + pkt = self._binary_packet + if pkt.add_attachment(data): + self._binary_packet = None + if pkt.packet_type == packet.BINARY_EVENT: + await self._handle_event(pkt.namespace, pkt.id, pkt.data) + else: + await self._handle_ack(pkt.namespace, pkt.id, pkt.data) + else: + pkt = packet.Packet(encoded_packet=data) + if pkt.packet_type == packet.CONNECT: + await self._handle_connect(pkt.namespace) + elif pkt.packet_type == packet.DISCONNECT: + await self._handle_disconnect(pkt.namespace) + elif pkt.packet_type == packet.EVENT: + await self._handle_event(pkt.namespace, pkt.id, pkt.data) + elif pkt.packet_type == packet.ACK: + await self._handle_ack(pkt.namespace, pkt.id, pkt.data) + elif pkt.packet_type == packet.BINARY_EVENT or \ + pkt.packet_type == packet.BINARY_ACK: + self._binary_packet = pkt + elif pkt.packet_type == packet.ERROR: + self._handle_error(pkt.namespace) + else: + raise ValueError('Unknown packet type.') + + async def _handle_eio_disconnect(self): + """Handle the Engine.IO disconnection event.""" + self.logger.info('Engine.IO connection dropped') + self._reconnect_abort.set() + for n in self.namespaces: + await self._trigger_event('disconnect', namespace=n) + await self._trigger_event('disconnect', namespace='/') + self.callbacks = {} + self._binary_packet = None + self.sid = None + if self.eio.state == 'connected' and self.reconnection: + self._reconnect_task = self.start_background_task( + self._handle_reconnect) + + def _engineio_client_class(self): + return engineio.AsyncClient diff --git a/openpype/vendor/python/python_2/socketio/asyncio_manager.py b/openpype/vendor/python/python_2/socketio/asyncio_manager.py new file mode 100644 index 0000000000..f4496ec7f4 --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/asyncio_manager.py @@ -0,0 +1,58 @@ +import asyncio + +from .base_manager import BaseManager + + +class AsyncManager(BaseManager): + """Manage a client list for an asyncio server.""" + async def emit(self, event, data, namespace, room=None, skip_sid=None, + callback=None, **kwargs): + """Emit a message to a single client, a room, or all the clients + connected to the namespace. + + Note: this method is a coroutine. + """ + if namespace not in self.rooms or room not in self.rooms[namespace]: + return + tasks = [] + if not isinstance(skip_sid, list): + skip_sid = [skip_sid] + for sid in self.get_participants(namespace, room): + if sid not in skip_sid: + if callback is not None: + id = self._generate_ack_id(sid, namespace, callback) + else: + id = None + tasks.append(self.server._emit_internal(sid, event, data, + namespace, id)) + if tasks == []: # pragma: no cover + return + await asyncio.wait(tasks) + + async def close_room(self, room, namespace): + """Remove all participants from a room. + + Note: this method is a coroutine. + """ + return super().close_room(room, namespace) + + async def trigger_callback(self, sid, namespace, id, data): + """Invoke an application callback. + + Note: this method is a coroutine. + """ + callback = None + try: + callback = self.callbacks[sid][namespace][id] + except KeyError: + # if we get an unknown callback we just ignore it + self._get_logger().warning('Unknown callback received, ignoring.') + else: + del self.callbacks[sid][namespace][id] + if callback is not None: + ret = callback(*data) + if asyncio.iscoroutine(ret): + try: + await ret + except asyncio.CancelledError: # pragma: no cover + pass diff --git a/openpype/vendor/python/python_2/socketio/asyncio_namespace.py b/openpype/vendor/python/python_2/socketio/asyncio_namespace.py new file mode 100644 index 0000000000..12e9c0fe62 --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/asyncio_namespace.py @@ -0,0 +1,204 @@ +import asyncio + +from socketio import namespace + + +class AsyncNamespace(namespace.Namespace): + """Base class for asyncio server-side class-based namespaces. + + A class-based namespace is a class that contains all the event handlers + for a Socket.IO namespace. The event handlers are methods of the class + with the prefix ``on_``, such as ``on_connect``, ``on_disconnect``, + ``on_message``, ``on_json``, and so on. These can be regular functions or + coroutines. + + :param namespace: The Socket.IO namespace to be used with all the event + handlers defined in this class. If this argument is + omitted, the default namespace is used. + """ + def is_asyncio_based(self): + return True + + async def trigger_event(self, event, *args): + """Dispatch an event to the proper handler method. + + In the most common usage, this method is not overloaded by subclasses, + as it performs the routing of events to methods. However, this + method can be overriden if special dispatching rules are needed, or if + having a single method that catches all events is desired. + + Note: this method is a coroutine. + """ + handler_name = 'on_' + event + if hasattr(self, handler_name): + handler = getattr(self, handler_name) + if asyncio.iscoroutinefunction(handler) is True: + try: + ret = await handler(*args) + except asyncio.CancelledError: # pragma: no cover + ret = None + else: + ret = handler(*args) + return ret + + async def emit(self, event, data=None, room=None, skip_sid=None, + namespace=None, callback=None): + """Emit a custom event to one or more connected clients. + + The only difference with the :func:`socketio.Server.emit` method is + that when the ``namespace`` argument is not given the namespace + associated with the class is used. + + Note: this method is a coroutine. + """ + return await self.server.emit(event, data=data, room=room, + skip_sid=skip_sid, + namespace=namespace or self.namespace, + callback=callback) + + async def send(self, data, room=None, skip_sid=None, namespace=None, + callback=None): + """Send a message to one or more connected clients. + + The only difference with the :func:`socketio.Server.send` method is + that when the ``namespace`` argument is not given the namespace + associated with the class is used. + + Note: this method is a coroutine. + """ + return await self.server.send(data, room=room, skip_sid=skip_sid, + namespace=namespace or self.namespace, + callback=callback) + + async def close_room(self, room, namespace=None): + """Close a room. + + The only difference with the :func:`socketio.Server.close_room` method + is that when the ``namespace`` argument is not given the namespace + associated with the class is used. + + Note: this method is a coroutine. + """ + return await self.server.close_room( + room, namespace=namespace or self.namespace) + + async def get_session(self, sid, namespace=None): + """Return the user session for a client. + + The only difference with the :func:`socketio.Server.get_session` + method is that when the ``namespace`` argument is not given the + namespace associated with the class is used. + + Note: this method is a coroutine. + """ + return await self.server.get_session( + sid, namespace=namespace or self.namespace) + + async def save_session(self, sid, session, namespace=None): + """Store the user session for a client. + + The only difference with the :func:`socketio.Server.save_session` + method is that when the ``namespace`` argument is not given the + namespace associated with the class is used. + + Note: this method is a coroutine. + """ + return await self.server.save_session( + sid, session, namespace=namespace or self.namespace) + + def session(self, sid, namespace=None): + """Return the user session for a client with context manager syntax. + + The only difference with the :func:`socketio.Server.session` method is + that when the ``namespace`` argument is not given the namespace + associated with the class is used. + """ + return self.server.session(sid, namespace=namespace or self.namespace) + + async def disconnect(self, sid, namespace=None): + """Disconnect a client. + + The only difference with the :func:`socketio.Server.disconnect` method + is that when the ``namespace`` argument is not given the namespace + associated with the class is used. + + Note: this method is a coroutine. + """ + return await self.server.disconnect( + sid, namespace=namespace or self.namespace) + + +class AsyncClientNamespace(namespace.ClientNamespace): + """Base class for asyncio client-side class-based namespaces. + + A class-based namespace is a class that contains all the event handlers + for a Socket.IO namespace. The event handlers are methods of the class + with the prefix ``on_``, such as ``on_connect``, ``on_disconnect``, + ``on_message``, ``on_json``, and so on. These can be regular functions or + coroutines. + + :param namespace: The Socket.IO namespace to be used with all the event + handlers defined in this class. If this argument is + omitted, the default namespace is used. + """ + def is_asyncio_based(self): + return True + + async def trigger_event(self, event, *args): + """Dispatch an event to the proper handler method. + + In the most common usage, this method is not overloaded by subclasses, + as it performs the routing of events to methods. However, this + method can be overriden if special dispatching rules are needed, or if + having a single method that catches all events is desired. + + Note: this method is a coroutine. + """ + handler_name = 'on_' + event + if hasattr(self, handler_name): + handler = getattr(self, handler_name) + if asyncio.iscoroutinefunction(handler) is True: + try: + ret = await handler(*args) + except asyncio.CancelledError: # pragma: no cover + ret = None + else: + ret = handler(*args) + return ret + + async def emit(self, event, data=None, namespace=None, callback=None): + """Emit a custom event to the server. + + The only difference with the :func:`socketio.Client.emit` method is + that when the ``namespace`` argument is not given the namespace + associated with the class is used. + + Note: this method is a coroutine. + """ + return await self.client.emit(event, data=data, + namespace=namespace or self.namespace, + callback=callback) + + async def send(self, data, namespace=None, callback=None): + """Send a message to the server. + + The only difference with the :func:`socketio.Client.send` method is + that when the ``namespace`` argument is not given the namespace + associated with the class is used. + + Note: this method is a coroutine. + """ + return await self.client.send(data, + namespace=namespace or self.namespace, + callback=callback) + + async def disconnect(self): + """Disconnect a client. + + The only difference with the :func:`socketio.Client.disconnect` method + is that when the ``namespace`` argument is not given the namespace + associated with the class is used. + + Note: this method is a coroutine. + """ + return await self.client.disconnect() diff --git a/openpype/vendor/python/python_2/socketio/asyncio_pubsub_manager.py b/openpype/vendor/python/python_2/socketio/asyncio_pubsub_manager.py new file mode 100644 index 0000000000..6fdba6d0ce --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/asyncio_pubsub_manager.py @@ -0,0 +1,163 @@ +from functools import partial +import uuid + +import json +import pickle +import six + +from .asyncio_manager import AsyncManager + + +class AsyncPubSubManager(AsyncManager): + """Manage a client list attached to a pub/sub backend under asyncio. + + This is a base class that enables multiple servers to share the list of + clients, with the servers communicating events through a pub/sub backend. + The use of a pub/sub backend also allows any client connected to the + backend to emit events addressed to Socket.IO clients. + + The actual backends must be implemented by subclasses, this class only + provides a pub/sub generic framework for asyncio applications. + + :param channel: The channel name on which the server sends and receives + notifications. + """ + name = 'asyncpubsub' + + def __init__(self, channel='socketio', write_only=False, logger=None): + super().__init__() + self.channel = channel + self.write_only = write_only + self.host_id = uuid.uuid4().hex + self.logger = logger + + def initialize(self): + super().initialize() + if not self.write_only: + self.thread = self.server.start_background_task(self._thread) + self._get_logger().info(self.name + ' backend initialized.') + + async def emit(self, event, data, namespace=None, room=None, skip_sid=None, + callback=None, **kwargs): + """Emit a message to a single client, a room, or all the clients + connected to the namespace. + + This method takes care or propagating the message to all the servers + that are connected through the message queue. + + The parameters are the same as in :meth:`.Server.emit`. + + Note: this method is a coroutine. + """ + if kwargs.get('ignore_queue'): + return await super().emit( + event, data, namespace=namespace, room=room, skip_sid=skip_sid, + callback=callback) + namespace = namespace or '/' + if callback is not None: + if self.server is None: + raise RuntimeError('Callbacks can only be issued from the ' + 'context of a server.') + if room is None: + raise ValueError('Cannot use callback without a room set.') + id = self._generate_ack_id(room, namespace, callback) + callback = (room, namespace, id) + else: + callback = None + await self._publish({'method': 'emit', 'event': event, 'data': data, + 'namespace': namespace, 'room': room, + 'skip_sid': skip_sid, 'callback': callback, + 'host_id': self.host_id}) + + async def close_room(self, room, namespace=None): + await self._publish({'method': 'close_room', 'room': room, + 'namespace': namespace or '/'}) + + async def _publish(self, data): + """Publish a message on the Socket.IO channel. + + This method needs to be implemented by the different subclasses that + support pub/sub backends. + """ + raise NotImplementedError('This method must be implemented in a ' + 'subclass.') # pragma: no cover + + async def _listen(self): + """Return the next message published on the Socket.IO channel, + blocking until a message is available. + + This method needs to be implemented by the different subclasses that + support pub/sub backends. + """ + raise NotImplementedError('This method must be implemented in a ' + 'subclass.') # pragma: no cover + + async def _handle_emit(self, message): + # Events with callbacks are very tricky to handle across hosts + # Here in the receiving end we set up a local callback that preserves + # the callback host and id from the sender + remote_callback = message.get('callback') + remote_host_id = message.get('host_id') + if remote_callback is not None and len(remote_callback) == 3: + callback = partial(self._return_callback, remote_host_id, + *remote_callback) + else: + callback = None + await super().emit(message['event'], message['data'], + namespace=message.get('namespace'), + room=message.get('room'), + skip_sid=message.get('skip_sid'), + callback=callback) + + async def _handle_callback(self, message): + if self.host_id == message.get('host_id'): + try: + sid = message['sid'] + namespace = message['namespace'] + id = message['id'] + args = message['args'] + except KeyError: + return + await self.trigger_callback(sid, namespace, id, args) + + async def _return_callback(self, host_id, sid, namespace, callback_id, + *args): + # When an event callback is received, the callback is returned back + # the sender, which is identified by the host_id + await self._publish({'method': 'callback', 'host_id': host_id, + 'sid': sid, 'namespace': namespace, + 'id': callback_id, 'args': args}) + + async def _handle_close_room(self, message): + await super().close_room( + room=message.get('room'), namespace=message.get('namespace')) + + async def _thread(self): + while True: + try: + message = await self._listen() + except: + import traceback + traceback.print_exc() + break + data = None + if isinstance(message, dict): + data = message + else: + if isinstance(message, six.binary_type): # pragma: no cover + try: + data = pickle.loads(message) + except: + pass + if data is None: + try: + data = json.loads(message) + except: + pass + if data and 'method' in data: + if data['method'] == 'emit': + await self._handle_emit(data) + elif data['method'] == 'callback': + await self._handle_callback(data) + elif data['method'] == 'close_room': + await self._handle_close_room(data) diff --git a/openpype/vendor/python/python_2/socketio/asyncio_redis_manager.py b/openpype/vendor/python/python_2/socketio/asyncio_redis_manager.py new file mode 100644 index 0000000000..21499c26c5 --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/asyncio_redis_manager.py @@ -0,0 +1,107 @@ +import asyncio +import pickle +from urllib.parse import urlparse + +try: + import aioredis +except ImportError: + aioredis = None + +from .asyncio_pubsub_manager import AsyncPubSubManager + + +def _parse_redis_url(url): + p = urlparse(url) + if p.scheme not in {'redis', 'rediss'}: + raise ValueError('Invalid redis url') + ssl = p.scheme == 'rediss' + host = p.hostname or 'localhost' + port = p.port or 6379 + password = p.password + if p.path: + db = int(p.path[1:]) + else: + db = 0 + return host, port, password, db, ssl + + +class AsyncRedisManager(AsyncPubSubManager): # pragma: no cover + """Redis based client manager for asyncio servers. + + This class implements a Redis backend for event sharing across multiple + processes. Only kept here as one more example of how to build a custom + backend, since the kombu backend is perfectly adequate to support a Redis + message queue. + + To use a Redis backend, initialize the :class:`Server` instance as + follows:: + + server = socketio.Server(client_manager=socketio.AsyncRedisManager( + 'redis://hostname:port/0')) + + :param url: The connection URL for the Redis server. For a default Redis + store running on the same host, use ``redis://``. To use an + SSL connection, use ``rediss://``. + :param channel: The channel name on which the server sends and receives + notifications. Must be the same in all the servers. + :param write_only: If set ot ``True``, only initialize to emit events. The + default of ``False`` initializes the class for emitting + and receiving. + """ + name = 'aioredis' + + def __init__(self, url='redis://localhost:6379/0', channel='socketio', + write_only=False, logger=None): + if aioredis is None: + raise RuntimeError('Redis package is not installed ' + '(Run "pip install aioredis" in your ' + 'virtualenv).') + ( + self.host, self.port, self.password, self.db, self.ssl + ) = _parse_redis_url(url) + self.pub = None + self.sub = None + super().__init__(channel=channel, write_only=write_only, logger=logger) + + async def _publish(self, data): + retry = True + while True: + try: + if self.pub is None: + self.pub = await aioredis.create_redis( + (self.host, self.port), db=self.db, + password=self.password, ssl=self.ssl + ) + return await self.pub.publish(self.channel, + pickle.dumps(data)) + except (aioredis.RedisError, OSError): + if retry: + self._get_logger().error('Cannot publish to redis... ' + 'retrying') + self.pub = None + retry = False + else: + self._get_logger().error('Cannot publish to redis... ' + 'giving up') + break + + async def _listen(self): + retry_sleep = 1 + while True: + try: + if self.sub is None: + self.sub = await aioredis.create_redis( + (self.host, self.port), db=self.db, + password=self.password, ssl=self.ssl + ) + self.ch = (await self.sub.subscribe(self.channel))[0] + return await self.ch.get() + except (aioredis.RedisError, OSError): + self._get_logger().error('Cannot receive from redis... ' + 'retrying in ' + '{} secs'.format(retry_sleep)) + self.sub = None + await asyncio.sleep(retry_sleep) + retry_sleep *= 2 + if retry_sleep > 60: + retry_sleep = 60 diff --git a/openpype/vendor/python/python_2/socketio/asyncio_server.py b/openpype/vendor/python/python_2/socketio/asyncio_server.py new file mode 100644 index 0000000000..45f5d86bba --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/asyncio_server.py @@ -0,0 +1,515 @@ +import asyncio + +import engineio +import six + +from . import asyncio_manager +from . import exceptions +from . import packet +from . import server + + +class AsyncServer(server.Server): + """A Socket.IO server for asyncio. + + This class implements a fully compliant Socket.IO web server with support + for websocket and long-polling transports, compatible with the asyncio + framework on Python 3.5 or newer. + + :param client_manager: The client manager instance that will manage the + client list. When this is omitted, the client list + is stored in an in-memory structure, so the use of + multiple connected servers is not possible. + :param logger: To enable logging set to ``True`` or pass a logger object to + use. To disable logging set to ``False``. + :param json: An alternative json module to use for encoding and decoding + packets. Custom json modules must have ``dumps`` and ``loads`` + functions that are compatible with the standard library + versions. + :param async_handlers: If set to ``True``, event handlers are executed in + separate threads. To run handlers synchronously, + set to ``False``. The default is ``True``. + :param kwargs: Connection parameters for the underlying Engine.IO server. + + The Engine.IO configuration supports the following settings: + + :param async_mode: The asynchronous model to use. See the Deployment + section in the documentation for a description of the + available options. Valid async modes are "aiohttp". If + this argument is not given, an async mode is chosen + based on the installed packages. + :param ping_timeout: The time in seconds that the client waits for the + server to respond before disconnecting. + :param ping_interval: The interval in seconds at which the client pings + the server. + :param max_http_buffer_size: The maximum size of a message when using the + polling transport. + :param allow_upgrades: Whether to allow transport upgrades or not. + :param http_compression: Whether to compress packages when using the + polling transport. + :param compression_threshold: Only compress messages when their byte size + is greater than this value. + :param cookie: Name of the HTTP cookie that contains the client session + id. If set to ``None``, a cookie is not sent to the client. + :param cors_allowed_origins: List of origins that are allowed to connect + to this server. All origins are allowed by + default. + :param cors_credentials: Whether credentials (cookies, authentication) are + allowed in requests to this server. + :param engineio_logger: To enable Engine.IO logging set to ``True`` or pass + a logger object to use. To disable logging set to + ``False``. + """ + def __init__(self, client_manager=None, logger=False, json=None, + async_handlers=True, **kwargs): + if client_manager is None: + client_manager = asyncio_manager.AsyncManager() + super().__init__(client_manager=client_manager, logger=logger, + binary=False, json=json, + async_handlers=async_handlers, **kwargs) + + def is_asyncio_based(self): + return True + + def attach(self, app, socketio_path='socket.io'): + """Attach the Socket.IO server to an application.""" + self.eio.attach(app, socketio_path) + + async def emit(self, event, data=None, to=None, room=None, skip_sid=None, + namespace=None, callback=None, **kwargs): + """Emit a custom event to one or more connected clients. + + :param event: The event name. It can be any string. The event names + ``'connect'``, ``'message'`` and ``'disconnect'`` are + reserved and should not be used. + :param data: The data to send to the client or clients. Data can be of + type ``str``, ``bytes``, ``list`` or ``dict``. If a + ``list`` or ``dict``, the data will be serialized as JSON. + :param to: The recipient of the message. This can be set to the + session ID of a client to address only that client, or to + to any custom room created by the application to address all + the clients in that room, If this argument is omitted the + event is broadcasted to all connected clients. + :param room: Alias for the ``to`` parameter. + :param skip_sid: The session ID of a client to skip when broadcasting + to a room or to all clients. This can be used to + prevent a message from being sent to the sender. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the event is emitted to the + default namespace. + :param callback: If given, this function will be called to acknowledge + the the client has received the message. The arguments + that will be passed to the function are those provided + by the client. Callback functions can only be used + when addressing an individual client. + :param ignore_queue: Only used when a message queue is configured. If + set to ``True``, the event is emitted to the + clients directly, without going through the queue. + This is more efficient, but only works when a + single server process is used. It is recommended + to always leave this parameter with its default + value of ``False``. + + Note: this method is a coroutine. + """ + namespace = namespace or '/' + room = to or room + self.logger.info('emitting event "%s" to %s [%s]', event, + room or 'all', namespace) + await self.manager.emit(event, data, namespace, room=room, + skip_sid=skip_sid, callback=callback, + **kwargs) + + async def send(self, data, to=None, room=None, skip_sid=None, + namespace=None, callback=None, **kwargs): + """Send a message to one or more connected clients. + + This function emits an event with the name ``'message'``. Use + :func:`emit` to issue custom event names. + + :param data: The data to send to the client or clients. Data can be of + type ``str``, ``bytes``, ``list`` or ``dict``. If a + ``list`` or ``dict``, the data will be serialized as JSON. + :param to: The recipient of the message. This can be set to the + session ID of a client to address only that client, or to + to any custom room created by the application to address all + the clients in that room, If this argument is omitted the + event is broadcasted to all connected clients. + :param room: Alias for the ``to`` parameter. + :param skip_sid: The session ID of a client to skip when broadcasting + to a room or to all clients. This can be used to + prevent a message from being sent to the sender. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the event is emitted to the + default namespace. + :param callback: If given, this function will be called to acknowledge + the the client has received the message. The arguments + that will be passed to the function are those provided + by the client. Callback functions can only be used + when addressing an individual client. + :param ignore_queue: Only used when a message queue is configured. If + set to ``True``, the event is emitted to the + clients directly, without going through the queue. + This is more efficient, but only works when a + single server process is used. It is recommended + to always leave this parameter with its default + value of ``False``. + + Note: this method is a coroutine. + """ + await self.emit('message', data=data, to=to, room=room, + skip_sid=skip_sid, namespace=namespace, + callback=callback, **kwargs) + + async def call(self, event, data=None, to=None, sid=None, namespace=None, + timeout=60, **kwargs): + """Emit a custom event to a client and wait for the response. + + :param event: The event name. It can be any string. The event names + ``'connect'``, ``'message'`` and ``'disconnect'`` are + reserved and should not be used. + :param data: The data to send to the client or clients. Data can be of + type ``str``, ``bytes``, ``list`` or ``dict``. If a + ``list`` or ``dict``, the data will be serialized as JSON. + :param to: The session ID of the recipient client. + :param sid: Alias for the ``to`` parameter. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the event is emitted to the + default namespace. + :param timeout: The waiting timeout. If the timeout is reached before + the client acknowledges the event, then a + ``TimeoutError`` exception is raised. + :param ignore_queue: Only used when a message queue is configured. If + set to ``True``, the event is emitted to the + client directly, without going through the queue. + This is more efficient, but only works when a + single server process is used. It is recommended + to always leave this parameter with its default + value of ``False``. + """ + if not self.async_handlers: + raise RuntimeError( + 'Cannot use call() when async_handlers is False.') + callback_event = self.eio.create_event() + callback_args = [] + + def event_callback(*args): + callback_args.append(args) + callback_event.set() + + await self.emit(event, data=data, room=to or sid, namespace=namespace, + callback=event_callback, **kwargs) + try: + await asyncio.wait_for(callback_event.wait(), timeout) + except asyncio.TimeoutError: + six.raise_from(exceptions.TimeoutError(), None) + return callback_args[0] if len(callback_args[0]) > 1 \ + else callback_args[0][0] if len(callback_args[0]) == 1 \ + else None + + async def close_room(self, room, namespace=None): + """Close a room. + + This function removes all the clients from the given room. + + :param room: Room name. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the default namespace is used. + + Note: this method is a coroutine. + """ + namespace = namespace or '/' + self.logger.info('room %s is closing [%s]', room, namespace) + await self.manager.close_room(room, namespace) + + async def get_session(self, sid, namespace=None): + """Return the user session for a client. + + :param sid: The session id of the client. + :param namespace: The Socket.IO namespace. If this argument is omitted + the default namespace is used. + + The return value is a dictionary. Modifications made to this + dictionary are not guaranteed to be preserved. If you want to modify + the user session, use the ``session`` context manager instead. + """ + namespace = namespace or '/' + eio_session = await self.eio.get_session(sid) + return eio_session.setdefault(namespace, {}) + + async def save_session(self, sid, session, namespace=None): + """Store the user session for a client. + + :param sid: The session id of the client. + :param session: The session dictionary. + :param namespace: The Socket.IO namespace. If this argument is omitted + the default namespace is used. + """ + namespace = namespace or '/' + eio_session = await self.eio.get_session(sid) + eio_session[namespace] = session + + def session(self, sid, namespace=None): + """Return the user session for a client with context manager syntax. + + :param sid: The session id of the client. + + This is a context manager that returns the user session dictionary for + the client. Any changes that are made to this dictionary inside the + context manager block are saved back to the session. Example usage:: + + @eio.on('connect') + def on_connect(sid, environ): + username = authenticate_user(environ) + if not username: + return False + with eio.session(sid) as session: + session['username'] = username + + @eio.on('message') + def on_message(sid, msg): + async with eio.session(sid) as session: + print('received message from ', session['username']) + """ + class _session_context_manager(object): + def __init__(self, server, sid, namespace): + self.server = server + self.sid = sid + self.namespace = namespace + self.session = None + + async def __aenter__(self): + self.session = await self.server.get_session( + sid, namespace=self.namespace) + return self.session + + async def __aexit__(self, *args): + await self.server.save_session(sid, self.session, + namespace=self.namespace) + + return _session_context_manager(self, sid, namespace) + + async def disconnect(self, sid, namespace=None): + """Disconnect a client. + + :param sid: Session ID of the client. + :param namespace: The Socket.IO namespace to disconnect. If this + argument is omitted the default namespace is used. + + Note: this method is a coroutine. + """ + namespace = namespace or '/' + if self.manager.is_connected(sid, namespace=namespace): + self.logger.info('Disconnecting %s [%s]', sid, namespace) + self.manager.pre_disconnect(sid, namespace=namespace) + await self._send_packet(sid, packet.Packet(packet.DISCONNECT, + namespace=namespace)) + await self._trigger_event('disconnect', namespace, sid) + self.manager.disconnect(sid, namespace=namespace) + + async def handle_request(self, *args, **kwargs): + """Handle an HTTP request from the client. + + This is the entry point of the Socket.IO application. This function + returns the HTTP response body to deliver to the client. + + Note: this method is a coroutine. + """ + return await self.eio.handle_request(*args, **kwargs) + + def start_background_task(self, target, *args, **kwargs): + """Start a background task using the appropriate async model. + + This is a utility function that applications can use to start a + background task using the method that is compatible with the + selected async mode. + + :param target: the target function to execute. Must be a coroutine. + :param args: arguments to pass to the function. + :param kwargs: keyword arguments to pass to the function. + + The return value is a ``asyncio.Task`` object. + + Note: this method is a coroutine. + """ + return self.eio.start_background_task(target, *args, **kwargs) + + async def sleep(self, seconds=0): + """Sleep for the requested amount of time using the appropriate async + model. + + This is a utility function that applications can use to put a task to + sleep without having to worry about using the correct call for the + selected async mode. + + Note: this method is a coroutine. + """ + return await self.eio.sleep(seconds) + + async def _emit_internal(self, sid, event, data, namespace=None, id=None): + """Send a message to a client.""" + # tuples are expanded to multiple arguments, everything else is sent + # as a single argument + if isinstance(data, tuple): + data = list(data) + else: + data = [data] + await self._send_packet(sid, packet.Packet( + packet.EVENT, namespace=namespace, data=[event] + data, id=id, + binary=None)) + + async def _send_packet(self, sid, pkt): + """Send a Socket.IO packet to a client.""" + encoded_packet = pkt.encode() + if isinstance(encoded_packet, list): + binary = False + for ep in encoded_packet: + await self.eio.send(sid, ep, binary=binary) + binary = True + else: + await self.eio.send(sid, encoded_packet, binary=False) + + async def _handle_connect(self, sid, namespace): + """Handle a client connection request.""" + namespace = namespace or '/' + self.manager.connect(sid, namespace) + if self.always_connect: + await self._send_packet(sid, packet.Packet(packet.CONNECT, + namespace=namespace)) + fail_reason = None + try: + success = await self._trigger_event('connect', namespace, sid, + self.environ[sid]) + except exceptions.ConnectionRefusedError as exc: + fail_reason = exc.error_args + success = False + + if success is False: + if self.always_connect: + self.manager.pre_disconnect(sid, namespace) + await self._send_packet(sid, packet.Packet( + packet.DISCONNECT, data=fail_reason, namespace=namespace)) + self.manager.disconnect(sid, namespace) + if not self.always_connect: + await self._send_packet(sid, packet.Packet( + packet.ERROR, data=fail_reason, namespace=namespace)) + if sid in self.environ: # pragma: no cover + del self.environ[sid] + return False + elif not self.always_connect: + await self._send_packet(sid, packet.Packet(packet.CONNECT, + namespace=namespace)) + + async def _handle_disconnect(self, sid, namespace): + """Handle a client disconnect.""" + namespace = namespace or '/' + if namespace == '/': + namespace_list = list(self.manager.get_namespaces()) + else: + namespace_list = [namespace] + for n in namespace_list: + if n != '/' and self.manager.is_connected(sid, n): + await self._trigger_event('disconnect', n, sid) + self.manager.disconnect(sid, n) + if namespace == '/' and self.manager.is_connected(sid, namespace): + await self._trigger_event('disconnect', '/', sid) + self.manager.disconnect(sid, '/') + + async def _handle_event(self, sid, namespace, id, data): + """Handle an incoming client event.""" + namespace = namespace or '/' + self.logger.info('received event "%s" from %s [%s]', data[0], sid, + namespace) + if self.async_handlers: + self.start_background_task(self._handle_event_internal, self, sid, + data, namespace, id) + else: + await self._handle_event_internal(self, sid, data, namespace, id) + + async def _handle_event_internal(self, server, sid, data, namespace, id): + r = await server._trigger_event(data[0], namespace, sid, *data[1:]) + if id is not None: + # send ACK packet with the response returned by the handler + # tuples are expanded as multiple arguments + if r is None: + data = [] + elif isinstance(r, tuple): + data = list(r) + else: + data = [r] + await server._send_packet(sid, packet.Packet(packet.ACK, + namespace=namespace, + id=id, data=data, + binary=None)) + + async def _handle_ack(self, sid, namespace, id, data): + """Handle ACK packets from the client.""" + namespace = namespace or '/' + self.logger.info('received ack from %s [%s]', sid, namespace) + await self.manager.trigger_callback(sid, namespace, id, data) + + async def _trigger_event(self, event, namespace, *args): + """Invoke an application event handler.""" + # first see if we have an explicit handler for the event + if namespace in self.handlers and event in self.handlers[namespace]: + if asyncio.iscoroutinefunction(self.handlers[namespace][event]) \ + is True: + try: + ret = await self.handlers[namespace][event](*args) + except asyncio.CancelledError: # pragma: no cover + ret = None + else: + ret = self.handlers[namespace][event](*args) + return ret + + # or else, forward the event to a namepsace handler if one exists + elif namespace in self.namespace_handlers: + return await self.namespace_handlers[namespace].trigger_event( + event, *args) + + async def _handle_eio_connect(self, sid, environ): + """Handle the Engine.IO connection event.""" + if not self.manager_initialized: + self.manager_initialized = True + self.manager.initialize() + self.environ[sid] = environ + return await self._handle_connect(sid, '/') + + async def _handle_eio_message(self, sid, data): + """Dispatch Engine.IO messages.""" + if sid in self._binary_packet: + pkt = self._binary_packet[sid] + if pkt.add_attachment(data): + del self._binary_packet[sid] + if pkt.packet_type == packet.BINARY_EVENT: + await self._handle_event(sid, pkt.namespace, pkt.id, + pkt.data) + else: + await self._handle_ack(sid, pkt.namespace, pkt.id, + pkt.data) + else: + pkt = packet.Packet(encoded_packet=data) + if pkt.packet_type == packet.CONNECT: + await self._handle_connect(sid, pkt.namespace) + elif pkt.packet_type == packet.DISCONNECT: + await self._handle_disconnect(sid, pkt.namespace) + elif pkt.packet_type == packet.EVENT: + await self._handle_event(sid, pkt.namespace, pkt.id, pkt.data) + elif pkt.packet_type == packet.ACK: + await self._handle_ack(sid, pkt.namespace, pkt.id, pkt.data) + elif pkt.packet_type == packet.BINARY_EVENT or \ + pkt.packet_type == packet.BINARY_ACK: + self._binary_packet[sid] = pkt + elif pkt.packet_type == packet.ERROR: + raise ValueError('Unexpected ERROR packet.') + else: + raise ValueError('Unknown packet type.') + + async def _handle_eio_disconnect(self, sid): + """Handle Engine.IO disconnect event.""" + await self._handle_disconnect(sid, '/') + if sid in self.environ: + del self.environ[sid] + + def _engineio_server_class(self): + return engineio.AsyncServer diff --git a/openpype/vendor/python/python_2/socketio/base_manager.py b/openpype/vendor/python/python_2/socketio/base_manager.py new file mode 100644 index 0000000000..3cccb85690 --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/base_manager.py @@ -0,0 +1,178 @@ +import itertools +import logging + +import six + +default_logger = logging.getLogger('socketio') + + +class BaseManager(object): + """Manage client connections. + + This class keeps track of all the clients and the rooms they are in, to + support the broadcasting of messages. The data used by this class is + stored in a memory structure, making it appropriate only for single process + services. More sophisticated storage backends can be implemented by + subclasses. + """ + def __init__(self): + self.logger = None + self.server = None + self.rooms = {} + self.callbacks = {} + self.pending_disconnect = {} + + def set_server(self, server): + self.server = server + + def initialize(self): + """Invoked before the first request is received. Subclasses can add + their initialization code here. + """ + pass + + def get_namespaces(self): + """Return an iterable with the active namespace names.""" + return six.iterkeys(self.rooms) + + def get_participants(self, namespace, room): + """Return an iterable with the active participants in a room.""" + for sid, active in six.iteritems(self.rooms[namespace][room].copy()): + yield sid + + def connect(self, sid, namespace): + """Register a client connection to a namespace.""" + self.enter_room(sid, namespace, None) + self.enter_room(sid, namespace, sid) + + def is_connected(self, sid, namespace): + if namespace in self.pending_disconnect and \ + sid in self.pending_disconnect[namespace]: + # the client is in the process of being disconnected + return False + try: + return self.rooms[namespace][None][sid] + except KeyError: + pass + + def pre_disconnect(self, sid, namespace): + """Put the client in the to-be-disconnected list. + + This allows the client data structures to be present while the + disconnect handler is invoked, but still recognize the fact that the + client is soon going away. + """ + if namespace not in self.pending_disconnect: + self.pending_disconnect[namespace] = [] + self.pending_disconnect[namespace].append(sid) + + def disconnect(self, sid, namespace): + """Register a client disconnect from a namespace.""" + if namespace not in self.rooms: + return + rooms = [] + for room_name, room in six.iteritems(self.rooms[namespace].copy()): + if sid in room: + rooms.append(room_name) + for room in rooms: + self.leave_room(sid, namespace, room) + if sid in self.callbacks and namespace in self.callbacks[sid]: + del self.callbacks[sid][namespace] + if len(self.callbacks[sid]) == 0: + del self.callbacks[sid] + if namespace in self.pending_disconnect and \ + sid in self.pending_disconnect[namespace]: + self.pending_disconnect[namespace].remove(sid) + if len(self.pending_disconnect[namespace]) == 0: + del self.pending_disconnect[namespace] + + def enter_room(self, sid, namespace, room): + """Add a client to a room.""" + if namespace not in self.rooms: + self.rooms[namespace] = {} + if room not in self.rooms[namespace]: + self.rooms[namespace][room] = {} + self.rooms[namespace][room][sid] = True + + def leave_room(self, sid, namespace, room): + """Remove a client from a room.""" + try: + del self.rooms[namespace][room][sid] + if len(self.rooms[namespace][room]) == 0: + del self.rooms[namespace][room] + if len(self.rooms[namespace]) == 0: + del self.rooms[namespace] + except KeyError: + pass + + def close_room(self, room, namespace): + """Remove all participants from a room.""" + try: + for sid in self.get_participants(namespace, room): + self.leave_room(sid, namespace, room) + except KeyError: + pass + + def get_rooms(self, sid, namespace): + """Return the rooms a client is in.""" + r = [] + try: + for room_name, room in six.iteritems(self.rooms[namespace]): + if room_name is not None and sid in room and room[sid]: + r.append(room_name) + except KeyError: + pass + return r + + def emit(self, event, data, namespace, room=None, skip_sid=None, + callback=None, **kwargs): + """Emit a message to a single client, a room, or all the clients + connected to the namespace.""" + if namespace not in self.rooms or room not in self.rooms[namespace]: + return + if not isinstance(skip_sid, list): + skip_sid = [skip_sid] + for sid in self.get_participants(namespace, room): + if sid not in skip_sid: + if callback is not None: + id = self._generate_ack_id(sid, namespace, callback) + else: + id = None + self.server._emit_internal(sid, event, data, namespace, id) + + def trigger_callback(self, sid, namespace, id, data): + """Invoke an application callback.""" + callback = None + try: + callback = self.callbacks[sid][namespace][id] + except KeyError: + # if we get an unknown callback we just ignore it + self._get_logger().warning('Unknown callback received, ignoring.') + else: + del self.callbacks[sid][namespace][id] + if callback is not None: + callback(*data) + + def _generate_ack_id(self, sid, namespace, callback): + """Generate a unique identifier for an ACK packet.""" + namespace = namespace or '/' + if sid not in self.callbacks: + self.callbacks[sid] = {} + if namespace not in self.callbacks[sid]: + self.callbacks[sid][namespace] = {0: itertools.count(1)} + id = six.next(self.callbacks[sid][namespace][0]) + self.callbacks[sid][namespace][id] = callback + return id + + def _get_logger(self): + """Get the appropriate logger + + Prevents uninitialized servers in write-only mode from failing. + """ + + if self.logger: + return self.logger + elif self.server: + return self.server.logger + else: + return default_logger diff --git a/openpype/vendor/python/python_2/socketio/client.py b/openpype/vendor/python/python_2/socketio/client.py new file mode 100644 index 0000000000..c0bdc1ab13 --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/client.py @@ -0,0 +1,590 @@ +import itertools +import logging +import random +import signal + +import engineio +import six + +from . import exceptions +from . import namespace +from . import packet + +default_logger = logging.getLogger('socketio.client') +reconnecting_clients = [] + + +def signal_handler(sig, frame): # pragma: no cover + """SIGINT handler. + + Notify any clients that are in a reconnect loop to abort. Other + disconnection tasks are handled at the engine.io level. + """ + for client in reconnecting_clients[:]: + client._reconnect_abort.set() + return original_signal_handler(sig, frame) + + +original_signal_handler = signal.signal(signal.SIGINT, signal_handler) + + +class Client(object): + """A Socket.IO client. + + This class implements a fully compliant Socket.IO web client with support + for websocket and long-polling transports. + + :param reconnection: ``True`` if the client should automatically attempt to + reconnect to the server after an interruption, or + ``False`` to not reconnect. The default is ``True``. + :param reconnection_attempts: How many reconnection attempts to issue + before giving up, or 0 for infinity attempts. + The default is 0. + :param reconnection_delay: How long to wait in seconds before the first + reconnection attempt. Each successive attempt + doubles this delay. + :param reconnection_delay_max: The maximum delay between reconnection + attempts. + :param randomization_factor: Randomization amount for each delay between + reconnection attempts. The default is 0.5, + which means that each delay is randomly + adjusted by +/- 50%. + :param logger: To enable logging set to ``True`` or pass a logger object to + use. To disable logging set to ``False``. The default is + ``False``. + :param binary: ``True`` to support binary payloads, ``False`` to treat all + payloads as text. On Python 2, if this is set to ``True``, + ``unicode`` values are treated as text, and ``str`` and + ``bytes`` values are treated as binary. This option has no + effect on Python 3, where text and binary payloads are + always automatically discovered. + :param json: An alternative json module to use for encoding and decoding + packets. Custom json modules must have ``dumps`` and ``loads`` + functions that are compatible with the standard library + versions. + + The Engine.IO configuration supports the following settings: + + :param engineio_logger: To enable Engine.IO logging set to ``True`` or pass + a logger object to use. To disable logging set to + ``False``. The default is ``False``. + """ + def __init__(self, reconnection=True, reconnection_attempts=0, + reconnection_delay=1, reconnection_delay_max=5, + randomization_factor=0.5, logger=False, binary=False, + json=None, **kwargs): + self.reconnection = reconnection + self.reconnection_attempts = reconnection_attempts + self.reconnection_delay = reconnection_delay + self.reconnection_delay_max = reconnection_delay_max + self.randomization_factor = randomization_factor + self.binary = binary + + engineio_options = kwargs + engineio_logger = engineio_options.pop('engineio_logger', None) + if engineio_logger is not None: + engineio_options['logger'] = engineio_logger + if json is not None: + packet.Packet.json = json + engineio_options['json'] = json + + self.eio = self._engineio_client_class()(**engineio_options) + self.eio.on('connect', self._handle_eio_connect) + self.eio.on('message', self._handle_eio_message) + self.eio.on('disconnect', self._handle_eio_disconnect) + + if not isinstance(logger, bool): + self.logger = logger + else: + self.logger = default_logger + if not logging.root.handlers and \ + self.logger.level == logging.NOTSET: + if logger: + self.logger.setLevel(logging.INFO) + else: + self.logger.setLevel(logging.ERROR) + self.logger.addHandler(logging.StreamHandler()) + + self.connection_url = None + self.connection_headers = None + self.connection_transports = None + self.connection_namespaces = None + self.socketio_path = None + self.sid = None + + self.namespaces = [] + self.handlers = {} + self.namespace_handlers = {} + self.callbacks = {} + self._binary_packet = None + self._reconnect_task = None + self._reconnect_abort = self.eio.create_event() + + def is_asyncio_based(self): + return False + + def on(self, event, handler=None, namespace=None): + """Register an event handler. + + :param event: The event name. It can be any string. The event names + ``'connect'``, ``'message'`` and ``'disconnect'`` are + reserved and should not be used. + :param handler: The function that should be invoked to handle the + event. When this parameter is not given, the method + acts as a decorator for the handler function. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the handler is associated with + the default namespace. + + Example usage:: + + # as a decorator: + @sio.on('connect') + def connect_handler(): + print('Connected!') + + # as a method: + def message_handler(msg): + print('Received message: ', msg) + sio.send( 'response') + sio.on('message', message_handler) + + The ``'connect'`` event handler receives no arguments. The + ``'message'`` handler and handlers for custom event names receive the + message payload as only argument. Any values returned from a message + handler will be passed to the client's acknowledgement callback + function if it exists. The ``'disconnect'`` handler does not take + arguments. + """ + namespace = namespace or '/' + + def set_handler(handler): + if namespace not in self.handlers: + self.handlers[namespace] = {} + self.handlers[namespace][event] = handler + return handler + + if handler is None: + return set_handler + set_handler(handler) + + def event(self, *args, **kwargs): + """Decorator to register an event handler. + + This is a simplified version of the ``on()`` method that takes the + event name from the decorated function. + + Example usage:: + + @sio.event + def my_event(data): + print('Received data: ', data) + + The above example is equivalent to:: + + @sio.on('my_event') + def my_event(data): + print('Received data: ', data) + + A custom namespace can be given as an argument to the decorator:: + + @sio.event(namespace='/test') + def my_event(data): + print('Received data: ', data) + """ + if len(args) == 1 and len(kwargs) == 0 and callable(args[0]): + # the decorator was invoked without arguments + # args[0] is the decorated function + return self.on(args[0].__name__)(args[0]) + else: + # the decorator was invoked with arguments + def set_handler(handler): + return self.on(handler.__name__, *args, **kwargs)(handler) + + return set_handler + + def register_namespace(self, namespace_handler): + """Register a namespace handler object. + + :param namespace_handler: An instance of a :class:`Namespace` + subclass that handles all the event traffic + for a namespace. + """ + if not isinstance(namespace_handler, namespace.ClientNamespace): + raise ValueError('Not a namespace instance') + if self.is_asyncio_based() != namespace_handler.is_asyncio_based(): + raise ValueError('Not a valid namespace class for this client') + namespace_handler._set_client(self) + self.namespace_handlers[namespace_handler.namespace] = \ + namespace_handler + + def connect(self, url, headers={}, transports=None, + namespaces=None, socketio_path='socket.io'): + """Connect to a Socket.IO server. + + :param url: The URL of the Socket.IO server. It can include custom + query string parameters if required by the server. + :param headers: A dictionary with custom headers to send with the + connection request. + :param transports: The list of allowed transports. Valid transports + are ``'polling'`` and ``'websocket'``. If not + given, the polling transport is connected first, + then an upgrade to websocket is attempted. + :param namespaces: The list of custom namespaces to connect, in + addition to the default namespace. If not given, + the namespace list is obtained from the registered + event handlers. + :param socketio_path: The endpoint where the Socket.IO server is + installed. The default value is appropriate for + most cases. + + Example usage:: + + sio = socketio.Client() + sio.connect('http://localhost:5000') + """ + self.connection_url = url + self.connection_headers = headers + self.connection_transports = transports + self.connection_namespaces = namespaces + self.socketio_path = socketio_path + + if namespaces is None: + namespaces = set(self.handlers.keys()).union( + set(self.namespace_handlers.keys())) + elif isinstance(namespaces, six.string_types): + namespaces = [namespaces] + self.connection_namespaces = namespaces + self.namespaces = [n for n in namespaces if n != '/'] + try: + self.eio.connect(url, headers=headers, transports=transports, + engineio_path=socketio_path) + except engineio.exceptions.ConnectionError as exc: + six.raise_from(exceptions.ConnectionError(exc.args[0]), None) + + def wait(self): + """Wait until the connection with the server ends. + + Client applications can use this function to block the main thread + during the life of the connection. + """ + while True: + self.eio.wait() + self.sleep(1) # give the reconnect task time to start up + if not self._reconnect_task: + break + self._reconnect_task.join() + if self.eio.state != 'connected': + break + + def emit(self, event, data=None, namespace=None, callback=None): + """Emit a custom event to one or more connected clients. + + :param event: The event name. It can be any string. The event names + ``'connect'``, ``'message'`` and ``'disconnect'`` are + reserved and should not be used. + :param data: The data to send to the client or clients. Data can be of + type ``str``, ``bytes``, ``list`` or ``dict``. If a + ``list`` or ``dict``, the data will be serialized as JSON. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the event is emitted to the + default namespace. + :param callback: If given, this function will be called to acknowledge + the the client has received the message. The arguments + that will be passed to the function are those provided + by the client. Callback functions can only be used + when addressing an individual client. + """ + namespace = namespace or '/' + self.logger.info('Emitting event "%s" [%s]', event, namespace) + if callback is not None: + id = self._generate_ack_id(namespace, callback) + else: + id = None + if six.PY2 and not self.binary: + binary = False # pragma: nocover + else: + binary = None + # tuples are expanded to multiple arguments, everything else is sent + # as a single argument + if isinstance(data, tuple): + data = list(data) + elif data is not None: + data = [data] + else: + data = [] + self._send_packet(packet.Packet(packet.EVENT, namespace=namespace, + data=[event] + data, id=id, + binary=binary)) + + def send(self, data, namespace=None, callback=None): + """Send a message to one or more connected clients. + + This function emits an event with the name ``'message'``. Use + :func:`emit` to issue custom event names. + + :param data: The data to send to the client or clients. Data can be of + type ``str``, ``bytes``, ``list`` or ``dict``. If a + ``list`` or ``dict``, the data will be serialized as JSON. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the event is emitted to the + default namespace. + :param callback: If given, this function will be called to acknowledge + the the client has received the message. The arguments + that will be passed to the function are those provided + by the client. Callback functions can only be used + when addressing an individual client. + """ + self.emit('message', data=data, namespace=namespace, + callback=callback) + + def call(self, event, data=None, namespace=None, timeout=60): + """Emit a custom event to a client and wait for the response. + + :param event: The event name. It can be any string. The event names + ``'connect'``, ``'message'`` and ``'disconnect'`` are + reserved and should not be used. + :param data: The data to send to the client or clients. Data can be of + type ``str``, ``bytes``, ``list`` or ``dict``. If a + ``list`` or ``dict``, the data will be serialized as JSON. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the event is emitted to the + default namespace. + :param timeout: The waiting timeout. If the timeout is reached before + the client acknowledges the event, then a + ``TimeoutError`` exception is raised. + """ + callback_event = self.eio.create_event() + callback_args = [] + + def event_callback(*args): + callback_args.append(args) + callback_event.set() + + self.emit(event, data=data, namespace=namespace, + callback=event_callback) + if not callback_event.wait(timeout=timeout): + raise exceptions.TimeoutError() + return callback_args[0] if len(callback_args[0]) > 1 \ + else callback_args[0][0] if len(callback_args[0]) == 1 \ + else None + + def disconnect(self): + """Disconnect from the server.""" + # here we just request the disconnection + # later in _handle_eio_disconnect we invoke the disconnect handler + for n in self.namespaces: + self._send_packet(packet.Packet(packet.DISCONNECT, namespace=n)) + self._send_packet(packet.Packet( + packet.DISCONNECT, namespace='/')) + self.eio.disconnect(abort=True) + + def transport(self): + """Return the name of the transport used by the client. + + The two possible values returned by this function are ``'polling'`` + and ``'websocket'``. + """ + return self.eio.transport() + + def start_background_task(self, target, *args, **kwargs): + """Start a background task using the appropriate async model. + + This is a utility function that applications can use to start a + background task using the method that is compatible with the + selected async mode. + + :param target: the target function to execute. + :param args: arguments to pass to the function. + :param kwargs: keyword arguments to pass to the function. + + This function returns an object compatible with the `Thread` class in + the Python standard library. The `start()` method on this object is + already called by this function. + """ + return self.eio.start_background_task(target, *args, **kwargs) + + def sleep(self, seconds=0): + """Sleep for the requested amount of time using the appropriate async + model. + + This is a utility function that applications can use to put a task to + sleep without having to worry about using the correct call for the + selected async mode. + """ + return self.eio.sleep(seconds) + + def _send_packet(self, pkt): + """Send a Socket.IO packet to the server.""" + encoded_packet = pkt.encode() + if isinstance(encoded_packet, list): + binary = False + for ep in encoded_packet: + self.eio.send(ep, binary=binary) + binary = True + else: + self.eio.send(encoded_packet, binary=False) + + def _generate_ack_id(self, namespace, callback): + """Generate a unique identifier for an ACK packet.""" + namespace = namespace or '/' + if namespace not in self.callbacks: + self.callbacks[namespace] = {0: itertools.count(1)} + id = six.next(self.callbacks[namespace][0]) + self.callbacks[namespace][id] = callback + return id + + def _handle_connect(self, namespace): + namespace = namespace or '/' + self.logger.info('Namespace {} is connected'.format(namespace)) + self._trigger_event('connect', namespace=namespace) + if namespace == '/': + for n in self.namespaces: + self._send_packet(packet.Packet(packet.CONNECT, namespace=n)) + elif namespace not in self.namespaces: + self.namespaces.append(namespace) + + def _handle_disconnect(self, namespace): + namespace = namespace or '/' + self._trigger_event('disconnect', namespace=namespace) + if namespace in self.namespaces: + self.namespaces.remove(namespace) + + def _handle_event(self, namespace, id, data): + namespace = namespace or '/' + self.logger.info('Received event "%s" [%s]', data[0], namespace) + r = self._trigger_event(data[0], namespace, *data[1:]) + if id is not None: + # send ACK packet with the response returned by the handler + # tuples are expanded as multiple arguments + if r is None: + data = [] + elif isinstance(r, tuple): + data = list(r) + else: + data = [r] + if six.PY2 and not self.binary: + binary = False # pragma: nocover + else: + binary = None + self._send_packet(packet.Packet(packet.ACK, namespace=namespace, + id=id, data=data, binary=binary)) + + def _handle_ack(self, namespace, id, data): + namespace = namespace or '/' + self.logger.info('Received ack [%s]', namespace) + callback = None + try: + callback = self.callbacks[namespace][id] + except KeyError: + # if we get an unknown callback we just ignore it + self.logger.warning('Unknown callback received, ignoring.') + else: + del self.callbacks[namespace][id] + if callback is not None: + callback(*data) + + def _handle_error(self, namespace): + namespace = namespace or '/' + self.logger.info('Connection to namespace {} was rejected'.format( + namespace)) + if namespace in self.namespaces: + self.namespaces.remove(namespace) + + def _trigger_event(self, event, namespace, *args): + """Invoke an application event handler.""" + # first see if we have an explicit handler for the event + if namespace in self.handlers and event in self.handlers[namespace]: + return self.handlers[namespace][event](*args) + + # or else, forward the event to a namespace handler if one exists + elif namespace in self.namespace_handlers: + return self.namespace_handlers[namespace].trigger_event( + event, *args) + + def _handle_reconnect(self): + self._reconnect_abort.clear() + reconnecting_clients.append(self) + attempt_count = 0 + current_delay = self.reconnection_delay + while True: + delay = current_delay + current_delay *= 2 + if delay > self.reconnection_delay_max: + delay = self.reconnection_delay_max + delay += self.randomization_factor * (2 * random.random() - 1) + self.logger.info( + 'Connection failed, new attempt in {:.02f} seconds'.format( + delay)) + print('***', self._reconnect_abort.wait) + if self._reconnect_abort.wait(delay): + self.logger.info('Reconnect task aborted') + break + attempt_count += 1 + try: + self.connect(self.connection_url, + headers=self.connection_headers, + transports=self.connection_transports, + namespaces=self.connection_namespaces, + socketio_path=self.socketio_path) + except (exceptions.ConnectionError, ValueError): + pass + else: + self.logger.info('Reconnection successful') + self._reconnect_task = None + break + if self.reconnection_attempts and \ + attempt_count >= self.reconnection_attempts: + self.logger.info( + 'Maximum reconnection attempts reached, giving up') + break + reconnecting_clients.remove(self) + + def _handle_eio_connect(self): + """Handle the Engine.IO connection event.""" + self.logger.info('Engine.IO connection established') + self.sid = self.eio.sid + + def _handle_eio_message(self, data): + """Dispatch Engine.IO messages.""" + if self._binary_packet: + pkt = self._binary_packet + if pkt.add_attachment(data): + self._binary_packet = None + if pkt.packet_type == packet.BINARY_EVENT: + self._handle_event(pkt.namespace, pkt.id, pkt.data) + else: + self._handle_ack(pkt.namespace, pkt.id, pkt.data) + else: + pkt = packet.Packet(encoded_packet=data) + if pkt.packet_type == packet.CONNECT: + self._handle_connect(pkt.namespace) + elif pkt.packet_type == packet.DISCONNECT: + self._handle_disconnect(pkt.namespace) + elif pkt.packet_type == packet.EVENT: + self._handle_event(pkt.namespace, pkt.id, pkt.data) + elif pkt.packet_type == packet.ACK: + self._handle_ack(pkt.namespace, pkt.id, pkt.data) + elif pkt.packet_type == packet.BINARY_EVENT or \ + pkt.packet_type == packet.BINARY_ACK: + self._binary_packet = pkt + elif pkt.packet_type == packet.ERROR: + self._handle_error(pkt.namespace) + else: + raise ValueError('Unknown packet type.') + + def _handle_eio_disconnect(self): + """Handle the Engine.IO disconnection event.""" + self.logger.info('Engine.IO connection dropped') + for n in self.namespaces: + self._trigger_event('disconnect', namespace=n) + self._trigger_event('disconnect', namespace='/') + self.callbacks = {} + self._binary_packet = None + self.sid = None + if self.eio.state == 'connected' and self.reconnection: + self._reconnect_task = self.start_background_task( + self._handle_reconnect) + + def _engineio_client_class(self): + return engineio.Client diff --git a/openpype/vendor/python/python_2/socketio/exceptions.py b/openpype/vendor/python/python_2/socketio/exceptions.py new file mode 100644 index 0000000000..344aabb569 --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/exceptions.py @@ -0,0 +1,26 @@ +class SocketIOError(Exception): + pass + + +class ConnectionError(SocketIOError): + pass + + +class ConnectionRefusedError(ConnectionError): + """Connection refused exception. + + This exception can be raised from a connect handler when the connection + is not accepted. The positional arguments provided with the exception are + returned with the error packet to the client. + """ + def __init__(self, *args): + if len(args) == 0: + self.error_args = None + elif len(args) == 1: + self.error_args = args[0] + else: + self.error_args = args + + +class TimeoutError(SocketIOError): + pass diff --git a/openpype/vendor/python/python_2/socketio/kombu_manager.py b/openpype/vendor/python/python_2/socketio/kombu_manager.py new file mode 100644 index 0000000000..4394a6d3ea --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/kombu_manager.py @@ -0,0 +1,105 @@ +import pickle +import uuid + +try: + import kombu +except ImportError: + kombu = None + +from .pubsub_manager import PubSubManager + + +class KombuManager(PubSubManager): # pragma: no cover + """Client manager that uses kombu for inter-process messaging. + + This class implements a client manager backend for event sharing across + multiple processes, using RabbitMQ, Redis or any other messaging mechanism + supported by `kombu `_. + + To use a kombu backend, initialize the :class:`Server` instance as + follows:: + + url = 'amqp://user:password@hostname:port//' + server = socketio.Server(client_manager=socketio.KombuManager(url)) + + :param url: The connection URL for the backend messaging queue. Example + connection URLs are ``'amqp://guest:guest@localhost:5672//'`` + and ``'redis://localhost:6379/'`` for RabbitMQ and Redis + respectively. Consult the `kombu documentation + `_ for more on how to construct + connection URLs. + :param channel: The channel name on which the server sends and receives + notifications. Must be the same in all the servers. + :param write_only: If set ot ``True``, only initialize to emit events. The + default of ``False`` initializes the class for emitting + and receiving. + """ + name = 'kombu' + + def __init__(self, url='amqp://guest:guest@localhost:5672//', + channel='socketio', write_only=False, logger=None): + if kombu is None: + raise RuntimeError('Kombu package is not installed ' + '(Run "pip install kombu" in your ' + 'virtualenv).') + super(KombuManager, self).__init__(channel=channel, + write_only=write_only, + logger=logger) + self.url = url + self.producer = self._producer() + + def initialize(self): + super(KombuManager, self).initialize() + + monkey_patched = True + if self.server.async_mode == 'eventlet': + from eventlet.patcher import is_monkey_patched + monkey_patched = is_monkey_patched('socket') + elif 'gevent' in self.server.async_mode: + from gevent.monkey import is_module_patched + monkey_patched = is_module_patched('socket') + if not monkey_patched: + raise RuntimeError( + 'Kombu requires a monkey patched socket library to work ' + 'with ' + self.server.async_mode) + + def _connection(self): + return kombu.Connection(self.url) + + def _exchange(self): + return kombu.Exchange(self.channel, type='fanout', durable=False) + + def _queue(self): + queue_name = 'flask-socketio.' + str(uuid.uuid4()) + return kombu.Queue(queue_name, self._exchange(), + durable=False, + queue_arguments={'x-expires': 300000}) + + def _producer(self): + return self._connection().Producer(exchange=self._exchange()) + + def __error_callback(self, exception, interval): + self._get_logger().exception('Sleeping {}s'.format(interval)) + + def _publish(self, data): + connection = self._connection() + publish = connection.ensure(self.producer, self.producer.publish, + errback=self.__error_callback) + publish(pickle.dumps(data)) + + def _listen(self): + reader_queue = self._queue() + + while True: + connection = self._connection().ensure_connection( + errback=self.__error_callback) + try: + with connection.SimpleQueue(reader_queue) as queue: + while True: + message = queue.get(block=True) + message.ack() + yield message.payload + except connection.connection_errors: + self._get_logger().exception("Connection error " + "while reading from queue") diff --git a/openpype/vendor/python/python_2/socketio/middleware.py b/openpype/vendor/python/python_2/socketio/middleware.py new file mode 100644 index 0000000000..1a69740857 --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/middleware.py @@ -0,0 +1,42 @@ +import engineio + + +class WSGIApp(engineio.WSGIApp): + """WSGI middleware for Socket.IO. + + This middleware dispatches traffic to a Socket.IO application. It can also + serve a list of static files to the client, or forward unrelated HTTP + traffic to another WSGI application. + + :param socketio_app: The Socket.IO server. Must be an instance of the + ``socketio.Server`` class. + :param wsgi_app: The WSGI app that receives all other traffic. + :param static_files: A dictionary with static file mapping rules. See the + documentation for details on this argument. + :param socketio_path: The endpoint where the Socket.IO application should + be installed. The default value is appropriate for + most cases. + + Example usage:: + + import socketio + import eventlet + from . import wsgi_app + + sio = socketio.Server() + app = socketio.WSGIApp(sio, wsgi_app) + eventlet.wsgi.server(eventlet.listen(('', 8000)), app) + """ + def __init__(self, socketio_app, wsgi_app=None, static_files=None, + socketio_path='socket.io'): + super(WSGIApp, self).__init__(socketio_app, wsgi_app, + static_files=static_files, + engineio_path=socketio_path) + + +class Middleware(WSGIApp): + """This class has been renamed to WSGIApp and is now deprecated.""" + def __init__(self, socketio_app, wsgi_app=None, + socketio_path='socket.io'): + super(Middleware, self).__init__(socketio_app, wsgi_app, + socketio_path=socketio_path) diff --git a/openpype/vendor/python/python_2/socketio/namespace.py b/openpype/vendor/python/python_2/socketio/namespace.py new file mode 100644 index 0000000000..418615ff8e --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/namespace.py @@ -0,0 +1,191 @@ +class BaseNamespace(object): + def __init__(self, namespace=None): + self.namespace = namespace or '/' + + def is_asyncio_based(self): + return False + + def trigger_event(self, event, *args): + """Dispatch an event to the proper handler method. + + In the most common usage, this method is not overloaded by subclasses, + as it performs the routing of events to methods. However, this + method can be overriden if special dispatching rules are needed, or if + having a single method that catches all events is desired. + """ + handler_name = 'on_' + event + if hasattr(self, handler_name): + return getattr(self, handler_name)(*args) + + +class Namespace(BaseNamespace): + """Base class for server-side class-based namespaces. + + A class-based namespace is a class that contains all the event handlers + for a Socket.IO namespace. The event handlers are methods of the class + with the prefix ``on_``, such as ``on_connect``, ``on_disconnect``, + ``on_message``, ``on_json``, and so on. + + :param namespace: The Socket.IO namespace to be used with all the event + handlers defined in this class. If this argument is + omitted, the default namespace is used. + """ + def __init__(self, namespace=None): + super(Namespace, self).__init__(namespace=namespace) + self.server = None + + def _set_server(self, server): + self.server = server + + def emit(self, event, data=None, room=None, skip_sid=None, namespace=None, + callback=None): + """Emit a custom event to one or more connected clients. + + The only difference with the :func:`socketio.Server.emit` method is + that when the ``namespace`` argument is not given the namespace + associated with the class is used. + """ + return self.server.emit(event, data=data, room=room, skip_sid=skip_sid, + namespace=namespace or self.namespace, + callback=callback) + + def send(self, data, room=None, skip_sid=None, namespace=None, + callback=None): + """Send a message to one or more connected clients. + + The only difference with the :func:`socketio.Server.send` method is + that when the ``namespace`` argument is not given the namespace + associated with the class is used. + """ + return self.server.send(data, room=room, skip_sid=skip_sid, + namespace=namespace or self.namespace, + callback=callback) + + def enter_room(self, sid, room, namespace=None): + """Enter a room. + + The only difference with the :func:`socketio.Server.enter_room` method + is that when the ``namespace`` argument is not given the namespace + associated with the class is used. + """ + return self.server.enter_room(sid, room, + namespace=namespace or self.namespace) + + def leave_room(self, sid, room, namespace=None): + """Leave a room. + + The only difference with the :func:`socketio.Server.leave_room` method + is that when the ``namespace`` argument is not given the namespace + associated with the class is used. + """ + return self.server.leave_room(sid, room, + namespace=namespace or self.namespace) + + def close_room(self, room, namespace=None): + """Close a room. + + The only difference with the :func:`socketio.Server.close_room` method + is that when the ``namespace`` argument is not given the namespace + associated with the class is used. + """ + return self.server.close_room(room, + namespace=namespace or self.namespace) + + def rooms(self, sid, namespace=None): + """Return the rooms a client is in. + + The only difference with the :func:`socketio.Server.rooms` method is + that when the ``namespace`` argument is not given the namespace + associated with the class is used. + """ + return self.server.rooms(sid, namespace=namespace or self.namespace) + + def get_session(self, sid, namespace=None): + """Return the user session for a client. + + The only difference with the :func:`socketio.Server.get_session` + method is that when the ``namespace`` argument is not given the + namespace associated with the class is used. + """ + return self.server.get_session( + sid, namespace=namespace or self.namespace) + + def save_session(self, sid, session, namespace=None): + """Store the user session for a client. + + The only difference with the :func:`socketio.Server.save_session` + method is that when the ``namespace`` argument is not given the + namespace associated with the class is used. + """ + return self.server.save_session( + sid, session, namespace=namespace or self.namespace) + + def session(self, sid, namespace=None): + """Return the user session for a client with context manager syntax. + + The only difference with the :func:`socketio.Server.session` method is + that when the ``namespace`` argument is not given the namespace + associated with the class is used. + """ + return self.server.session(sid, namespace=namespace or self.namespace) + + def disconnect(self, sid, namespace=None): + """Disconnect a client. + + The only difference with the :func:`socketio.Server.disconnect` method + is that when the ``namespace`` argument is not given the namespace + associated with the class is used. + """ + return self.server.disconnect(sid, + namespace=namespace or self.namespace) + + +class ClientNamespace(BaseNamespace): + """Base class for client-side class-based namespaces. + + A class-based namespace is a class that contains all the event handlers + for a Socket.IO namespace. The event handlers are methods of the class + with the prefix ``on_``, such as ``on_connect``, ``on_disconnect``, + ``on_message``, ``on_json``, and so on. + + :param namespace: The Socket.IO namespace to be used with all the event + handlers defined in this class. If this argument is + omitted, the default namespace is used. + """ + def __init__(self, namespace=None): + super(ClientNamespace, self).__init__(namespace=namespace) + self.client = None + + def _set_client(self, client): + self.client = client + + def emit(self, event, data=None, namespace=None, callback=None): + """Emit a custom event to the server. + + The only difference with the :func:`socketio.Client.emit` method is + that when the ``namespace`` argument is not given the namespace + associated with the class is used. + """ + return self.client.emit(event, data=data, + namespace=namespace or self.namespace, + callback=callback) + + def send(self, data, room=None, skip_sid=None, namespace=None, + callback=None): + """Send a message to the server. + + The only difference with the :func:`socketio.Client.send` method is + that when the ``namespace`` argument is not given the namespace + associated with the class is used. + """ + return self.client.send(data, namespace=namespace or self.namespace, + callback=callback) + + def disconnect(self): + """Disconnect from the server. + + The only difference with the :func:`socketio.Client.disconnect` method + is that when the ``namespace`` argument is not given the namespace + associated with the class is used. + """ + return self.client.disconnect() diff --git a/openpype/vendor/python/python_2/socketio/packet.py b/openpype/vendor/python/python_2/socketio/packet.py new file mode 100644 index 0000000000..73b469d6d2 --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/packet.py @@ -0,0 +1,179 @@ +import functools +import json as _json + +import six + +(CONNECT, DISCONNECT, EVENT, ACK, ERROR, BINARY_EVENT, BINARY_ACK) = \ + (0, 1, 2, 3, 4, 5, 6) +packet_names = ['CONNECT', 'DISCONNECT', 'EVENT', 'ACK', 'ERROR', + 'BINARY_EVENT', 'BINARY_ACK'] + + +class Packet(object): + """Socket.IO packet.""" + + # the format of the Socket.IO packet is as follows: + # + # packet type: 1 byte, values 0-6 + # num_attachments: ASCII encoded, only if num_attachments != 0 + # '-': only if num_attachments != 0 + # namespace: only if namespace != '/' + # ',': only if namespace and one of id and data are defined in this packet + # id: ASCII encoded, only if id is not None + # data: JSON dump of data payload + + json = _json + + def __init__(self, packet_type=EVENT, data=None, namespace=None, id=None, + binary=None, encoded_packet=None): + self.packet_type = packet_type + self.data = data + self.namespace = namespace + self.id = id + if binary or (binary is None and self._data_is_binary(self.data)): + if self.packet_type == EVENT: + self.packet_type = BINARY_EVENT + elif self.packet_type == ACK: + self.packet_type = BINARY_ACK + else: + raise ValueError('Packet does not support binary payload.') + self.attachment_count = 0 + self.attachments = [] + if encoded_packet: + self.attachment_count = self.decode(encoded_packet) + + def encode(self): + """Encode the packet for transmission. + + If the packet contains binary elements, this function returns a list + of packets where the first is the original packet with placeholders for + the binary components and the remaining ones the binary attachments. + """ + encoded_packet = six.text_type(self.packet_type) + if self.packet_type == BINARY_EVENT or self.packet_type == BINARY_ACK: + data, attachments = self._deconstruct_binary(self.data) + encoded_packet += six.text_type(len(attachments)) + '-' + else: + data = self.data + attachments = None + needs_comma = False + if self.namespace is not None and self.namespace != '/': + encoded_packet += self.namespace + needs_comma = True + if self.id is not None: + if needs_comma: + encoded_packet += ',' + needs_comma = False + encoded_packet += six.text_type(self.id) + if data is not None: + if needs_comma: + encoded_packet += ',' + encoded_packet += self.json.dumps(data, separators=(',', ':')) + if attachments is not None: + encoded_packet = [encoded_packet] + attachments + return encoded_packet + + def decode(self, encoded_packet): + """Decode a transmitted package. + + The return value indicates how many binary attachment packets are + necessary to fully decode the packet. + """ + ep = encoded_packet + try: + self.packet_type = int(ep[0:1]) + except TypeError: + self.packet_type = ep + ep = '' + self.namespace = None + self.data = None + ep = ep[1:] + dash = ep.find('-') + attachment_count = 0 + if dash > 0 and ep[0:dash].isdigit(): + attachment_count = int(ep[0:dash]) + ep = ep[dash + 1:] + if ep and ep[0:1] == '/': + sep = ep.find(',') + if sep == -1: + self.namespace = ep + ep = '' + else: + self.namespace = ep[0:sep] + ep = ep[sep + 1:] + q = self.namespace.find('?') + if q != -1: + self.namespace = self.namespace[0:q] + if ep and ep[0].isdigit(): + self.id = 0 + while ep and ep[0].isdigit(): + self.id = self.id * 10 + int(ep[0]) + ep = ep[1:] + if ep: + self.data = self.json.loads(ep) + return attachment_count + + def add_attachment(self, attachment): + if self.attachment_count <= len(self.attachments): + raise ValueError('Unexpected binary attachment') + self.attachments.append(attachment) + if self.attachment_count == len(self.attachments): + self.reconstruct_binary(self.attachments) + return True + return False + + def reconstruct_binary(self, attachments): + """Reconstruct a decoded packet using the given list of binary + attachments. + """ + self.data = self._reconstruct_binary_internal(self.data, + self.attachments) + + def _reconstruct_binary_internal(self, data, attachments): + if isinstance(data, list): + return [self._reconstruct_binary_internal(item, attachments) + for item in data] + elif isinstance(data, dict): + if data.get('_placeholder') and 'num' in data: + return attachments[data['num']] + else: + return {key: self._reconstruct_binary_internal(value, + attachments) + for key, value in six.iteritems(data)} + else: + return data + + def _deconstruct_binary(self, data): + """Extract binary components in the packet.""" + attachments = [] + data = self._deconstruct_binary_internal(data, attachments) + return data, attachments + + def _deconstruct_binary_internal(self, data, attachments): + if isinstance(data, six.binary_type): + attachments.append(data) + return {'_placeholder': True, 'num': len(attachments) - 1} + elif isinstance(data, list): + return [self._deconstruct_binary_internal(item, attachments) + for item in data] + elif isinstance(data, dict): + return {key: self._deconstruct_binary_internal(value, attachments) + for key, value in six.iteritems(data)} + else: + return data + + def _data_is_binary(self, data): + """Check if the data contains binary components.""" + if isinstance(data, six.binary_type): + return True + elif isinstance(data, list): + return functools.reduce( + lambda a, b: a or b, [self._data_is_binary(item) + for item in data], False) + elif isinstance(data, dict): + return functools.reduce( + lambda a, b: a or b, [self._data_is_binary(item) + for item in six.itervalues(data)], + False) + else: + return False diff --git a/openpype/vendor/python/python_2/socketio/pubsub_manager.py b/openpype/vendor/python/python_2/socketio/pubsub_manager.py new file mode 100644 index 0000000000..2905b2c325 --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/pubsub_manager.py @@ -0,0 +1,154 @@ +from functools import partial +import uuid + +import json +import pickle +import six + +from .base_manager import BaseManager + + +class PubSubManager(BaseManager): + """Manage a client list attached to a pub/sub backend. + + This is a base class that enables multiple servers to share the list of + clients, with the servers communicating events through a pub/sub backend. + The use of a pub/sub backend also allows any client connected to the + backend to emit events addressed to Socket.IO clients. + + The actual backends must be implemented by subclasses, this class only + provides a pub/sub generic framework. + + :param channel: The channel name on which the server sends and receives + notifications. + """ + name = 'pubsub' + + def __init__(self, channel='socketio', write_only=False, logger=None): + super(PubSubManager, self).__init__() + self.channel = channel + self.write_only = write_only + self.host_id = uuid.uuid4().hex + self.logger = logger + + def initialize(self): + super(PubSubManager, self).initialize() + if not self.write_only: + self.thread = self.server.start_background_task(self._thread) + self._get_logger().info(self.name + ' backend initialized.') + + def emit(self, event, data, namespace=None, room=None, skip_sid=None, + callback=None, **kwargs): + """Emit a message to a single client, a room, or all the clients + connected to the namespace. + + This method takes care or propagating the message to all the servers + that are connected through the message queue. + + The parameters are the same as in :meth:`.Server.emit`. + """ + if kwargs.get('ignore_queue'): + return super(PubSubManager, self).emit( + event, data, namespace=namespace, room=room, skip_sid=skip_sid, + callback=callback) + namespace = namespace or '/' + if callback is not None: + if self.server is None: + raise RuntimeError('Callbacks can only be issued from the ' + 'context of a server.') + if room is None: + raise ValueError('Cannot use callback without a room set.') + id = self._generate_ack_id(room, namespace, callback) + callback = (room, namespace, id) + else: + callback = None + self._publish({'method': 'emit', 'event': event, 'data': data, + 'namespace': namespace, 'room': room, + 'skip_sid': skip_sid, 'callback': callback, + 'host_id': self.host_id}) + + def close_room(self, room, namespace=None): + self._publish({'method': 'close_room', 'room': room, + 'namespace': namespace or '/'}) + + def _publish(self, data): + """Publish a message on the Socket.IO channel. + + This method needs to be implemented by the different subclasses that + support pub/sub backends. + """ + raise NotImplementedError('This method must be implemented in a ' + 'subclass.') # pragma: no cover + + def _listen(self): + """Return the next message published on the Socket.IO channel, + blocking until a message is available. + + This method needs to be implemented by the different subclasses that + support pub/sub backends. + """ + raise NotImplementedError('This method must be implemented in a ' + 'subclass.') # pragma: no cover + + def _handle_emit(self, message): + # Events with callbacks are very tricky to handle across hosts + # Here in the receiving end we set up a local callback that preserves + # the callback host and id from the sender + remote_callback = message.get('callback') + remote_host_id = message.get('host_id') + if remote_callback is not None and len(remote_callback) == 3: + callback = partial(self._return_callback, remote_host_id, + *remote_callback) + else: + callback = None + super(PubSubManager, self).emit(message['event'], message['data'], + namespace=message.get('namespace'), + room=message.get('room'), + skip_sid=message.get('skip_sid'), + callback=callback) + + def _handle_callback(self, message): + if self.host_id == message.get('host_id'): + try: + sid = message['sid'] + namespace = message['namespace'] + id = message['id'] + args = message['args'] + except KeyError: + return + self.trigger_callback(sid, namespace, id, args) + + def _return_callback(self, host_id, sid, namespace, callback_id, *args): + # When an event callback is received, the callback is returned back + # the sender, which is identified by the host_id + self._publish({'method': 'callback', 'host_id': host_id, + 'sid': sid, 'namespace': namespace, 'id': callback_id, + 'args': args}) + + def _handle_close_room(self, message): + super(PubSubManager, self).close_room( + room=message.get('room'), namespace=message.get('namespace')) + + def _thread(self): + for message in self._listen(): + data = None + if isinstance(message, dict): + data = message + else: + if isinstance(message, six.binary_type): # pragma: no cover + try: + data = pickle.loads(message) + except: + pass + if data is None: + try: + data = json.loads(message) + except: + pass + if data and 'method' in data: + if data['method'] == 'emit': + self._handle_emit(data) + elif data['method'] == 'callback': + self._handle_callback(data) + elif data['method'] == 'close_room': + self._handle_close_room(data) diff --git a/openpype/vendor/python/python_2/socketio/redis_manager.py b/openpype/vendor/python/python_2/socketio/redis_manager.py new file mode 100644 index 0000000000..69be586fca --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/redis_manager.py @@ -0,0 +1,111 @@ +import logging +import pickle +import time + +try: + import redis +except ImportError: + redis = None + +from .pubsub_manager import PubSubManager + +logger = logging.getLogger('socketio') + + +class RedisManager(PubSubManager): # pragma: no cover + """Redis based client manager. + + This class implements a Redis backend for event sharing across multiple + processes. Only kept here as one more example of how to build a custom + backend, since the kombu backend is perfectly adequate to support a Redis + message queue. + + To use a Redis backend, initialize the :class:`Server` instance as + follows:: + + url = 'redis://hostname:port/0' + server = socketio.Server(client_manager=socketio.RedisManager(url)) + + :param url: The connection URL for the Redis server. For a default Redis + store running on the same host, use ``redis://``. + :param channel: The channel name on which the server sends and receives + notifications. Must be the same in all the servers. + :param write_only: If set ot ``True``, only initialize to emit events. The + default of ``False`` initializes the class for emitting + and receiving. + """ + name = 'redis' + + def __init__(self, url='redis://localhost:6379/0', channel='socketio', + write_only=False, logger=None): + if redis is None: + raise RuntimeError('Redis package is not installed ' + '(Run "pip install redis" in your ' + 'virtualenv).') + self.redis_url = url + self._redis_connect() + super(RedisManager, self).__init__(channel=channel, + write_only=write_only, + logger=logger) + + def initialize(self): + super(RedisManager, self).initialize() + + monkey_patched = True + if self.server.async_mode == 'eventlet': + from eventlet.patcher import is_monkey_patched + monkey_patched = is_monkey_patched('socket') + elif 'gevent' in self.server.async_mode: + from gevent.monkey import is_module_patched + monkey_patched = is_module_patched('socket') + if not monkey_patched: + raise RuntimeError( + 'Redis requires a monkey patched socket library to work ' + 'with ' + self.server.async_mode) + + def _redis_connect(self): + self.redis = redis.Redis.from_url(self.redis_url) + self.pubsub = self.redis.pubsub() + + def _publish(self, data): + retry = True + while True: + try: + if not retry: + self._redis_connect() + return self.redis.publish(self.channel, pickle.dumps(data)) + except redis.exceptions.ConnectionError: + if retry: + logger.error('Cannot publish to redis... retrying') + retry = False + else: + logger.error('Cannot publish to redis... giving up') + break + + def _redis_listen_with_retries(self): + retry_sleep = 1 + connect = False + while True: + try: + if connect: + self._redis_connect() + self.pubsub.subscribe(self.channel) + for message in self.pubsub.listen(): + yield message + except redis.exceptions.ConnectionError: + logger.error('Cannot receive from redis... ' + 'retrying in {} secs'.format(retry_sleep)) + connect = True + time.sleep(retry_sleep) + retry_sleep *= 2 + if retry_sleep > 60: + retry_sleep = 60 + + def _listen(self): + channel = self.channel.encode('utf-8') + self.pubsub.subscribe(self.channel) + for message in self._redis_listen_with_retries(): + if message['channel'] == channel and \ + message['type'] == 'message' and 'data' in message: + yield message['data'] + self.pubsub.unsubscribe(self.channel) diff --git a/openpype/vendor/python/python_2/socketio/server.py b/openpype/vendor/python/python_2/socketio/server.py new file mode 100644 index 0000000000..f9a74dc51b --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/server.py @@ -0,0 +1,719 @@ +import logging + +import engineio +import six + +from . import base_manager +from . import exceptions +from . import namespace +from . import packet + +default_logger = logging.getLogger('socketio.server') + + +class Server(object): + """A Socket.IO server. + + This class implements a fully compliant Socket.IO web server with support + for websocket and long-polling transports. + + :param client_manager: The client manager instance that will manage the + client list. When this is omitted, the client list + is stored in an in-memory structure, so the use of + multiple connected servers is not possible. + :param logger: To enable logging set to ``True`` or pass a logger object to + use. To disable logging set to ``False``. The default is + ``False``. + :param binary: ``True`` to support binary payloads, ``False`` to treat all + payloads as text. On Python 2, if this is set to ``True``, + ``unicode`` values are treated as text, and ``str`` and + ``bytes`` values are treated as binary. This option has no + effect on Python 3, where text and binary payloads are + always automatically discovered. + :param json: An alternative json module to use for encoding and decoding + packets. Custom json modules must have ``dumps`` and ``loads`` + functions that are compatible with the standard library + versions. + :param async_handlers: If set to ``True``, event handlers for a client are + executed in separate threads. To run handlers for a + client synchronously, set to ``False``. The default + is ``True``. + :param always_connect: When set to ``False``, new connections are + provisory until the connect handler returns + something other than ``False``, at which point they + are accepted. When set to ``True``, connections are + immediately accepted, and then if the connect + handler returns ``False`` a disconnect is issued. + Set to ``True`` if you need to emit events from the + connect handler and your client is confused when it + receives events before the connection acceptance. + In any other case use the default of ``False``. + :param kwargs: Connection parameters for the underlying Engine.IO server. + + The Engine.IO configuration supports the following settings: + + :param async_mode: The asynchronous model to use. See the Deployment + section in the documentation for a description of the + available options. Valid async modes are "threading", + "eventlet", "gevent" and "gevent_uwsgi". If this + argument is not given, "eventlet" is tried first, then + "gevent_uwsgi", then "gevent", and finally "threading". + The first async mode that has all its dependencies + installed is then one that is chosen. + :param ping_timeout: The time in seconds that the client waits for the + server to respond before disconnecting. The default + is 60 seconds. + :param ping_interval: The interval in seconds at which the client pings + the server. The default is 25 seconds. + :param max_http_buffer_size: The maximum size of a message when using the + polling transport. The default is 100,000,000 + bytes. + :param allow_upgrades: Whether to allow transport upgrades or not. The + default is ``True``. + :param http_compression: Whether to compress packages when using the + polling transport. The default is ``True``. + :param compression_threshold: Only compress messages when their byte size + is greater than this value. The default is + 1024 bytes. + :param cookie: Name of the HTTP cookie that contains the client session + id. If set to ``None``, a cookie is not sent to the client. + The default is ``'io'``. + :param cors_allowed_origins: List of origins that are allowed to connect + to this server. All origins are allowed by + default. + :param cors_credentials: Whether credentials (cookies, authentication) are + allowed in requests to this server. The default is + ``True``. + :param engineio_logger: To enable Engine.IO logging set to ``True`` or pass + a logger object to use. To disable logging set to + ``False``. The default is ``False``. + """ + def __init__(self, client_manager=None, logger=False, binary=False, + json=None, async_handlers=True, always_connect=False, + **kwargs): + engineio_options = kwargs + engineio_logger = engineio_options.pop('engineio_logger', None) + if engineio_logger is not None: + engineio_options['logger'] = engineio_logger + if json is not None: + packet.Packet.json = json + engineio_options['json'] = json + engineio_options['async_handlers'] = False + self.eio = self._engineio_server_class()(**engineio_options) + self.eio.on('connect', self._handle_eio_connect) + self.eio.on('message', self._handle_eio_message) + self.eio.on('disconnect', self._handle_eio_disconnect) + self.binary = binary + + self.environ = {} + self.handlers = {} + self.namespace_handlers = {} + + self._binary_packet = {} + + if not isinstance(logger, bool): + self.logger = logger + else: + self.logger = default_logger + if not logging.root.handlers and \ + self.logger.level == logging.NOTSET: + if logger: + self.logger.setLevel(logging.INFO) + else: + self.logger.setLevel(logging.ERROR) + self.logger.addHandler(logging.StreamHandler()) + + if client_manager is None: + client_manager = base_manager.BaseManager() + self.manager = client_manager + self.manager.set_server(self) + self.manager_initialized = False + + self.async_handlers = async_handlers + self.always_connect = always_connect + + self.async_mode = self.eio.async_mode + + def is_asyncio_based(self): + return False + + def on(self, event, handler=None, namespace=None): + """Register an event handler. + + :param event: The event name. It can be any string. The event names + ``'connect'``, ``'message'`` and ``'disconnect'`` are + reserved and should not be used. + :param handler: The function that should be invoked to handle the + event. When this parameter is not given, the method + acts as a decorator for the handler function. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the handler is associated with + the default namespace. + + Example usage:: + + # as a decorator: + @socket_io.on('connect', namespace='/chat') + def connect_handler(sid, environ): + print('Connection request') + if environ['REMOTE_ADDR'] in blacklisted: + return False # reject + + # as a method: + def message_handler(sid, msg): + print('Received message: ', msg) + eio.send(sid, 'response') + socket_io.on('message', namespace='/chat', message_handler) + + The handler function receives the ``sid`` (session ID) for the + client as first argument. The ``'connect'`` event handler receives the + WSGI environment as a second argument, and can return ``False`` to + reject the connection. The ``'message'`` handler and handlers for + custom event names receive the message payload as a second argument. + Any values returned from a message handler will be passed to the + client's acknowledgement callback function if it exists. The + ``'disconnect'`` handler does not take a second argument. + """ + namespace = namespace or '/' + + def set_handler(handler): + if namespace not in self.handlers: + self.handlers[namespace] = {} + self.handlers[namespace][event] = handler + return handler + + if handler is None: + return set_handler + set_handler(handler) + + def event(self, *args, **kwargs): + """Decorator to register an event handler. + + This is a simplified version of the ``on()`` method that takes the + event name from the decorated function. + + Example usage:: + + @sio.event + def my_event(data): + print('Received data: ', data) + + The above example is equivalent to:: + + @sio.on('my_event') + def my_event(data): + print('Received data: ', data) + + A custom namespace can be given as an argument to the decorator:: + + @sio.event(namespace='/test') + def my_event(data): + print('Received data: ', data) + """ + if len(args) == 1 and len(kwargs) == 0 and callable(args[0]): + # the decorator was invoked without arguments + # args[0] is the decorated function + return self.on(args[0].__name__)(args[0]) + else: + # the decorator was invoked with arguments + def set_handler(handler): + return self.on(handler.__name__, *args, **kwargs)(handler) + + return set_handler + + def register_namespace(self, namespace_handler): + """Register a namespace handler object. + + :param namespace_handler: An instance of a :class:`Namespace` + subclass that handles all the event traffic + for a namespace. + """ + if not isinstance(namespace_handler, namespace.Namespace): + raise ValueError('Not a namespace instance') + if self.is_asyncio_based() != namespace_handler.is_asyncio_based(): + raise ValueError('Not a valid namespace class for this server') + namespace_handler._set_server(self) + self.namespace_handlers[namespace_handler.namespace] = \ + namespace_handler + + def emit(self, event, data=None, to=None, room=None, skip_sid=None, + namespace=None, callback=None, **kwargs): + """Emit a custom event to one or more connected clients. + + :param event: The event name. It can be any string. The event names + ``'connect'``, ``'message'`` and ``'disconnect'`` are + reserved and should not be used. + :param data: The data to send to the client or clients. Data can be of + type ``str``, ``bytes``, ``list`` or ``dict``. If a + ``list`` or ``dict``, the data will be serialized as JSON. + :param to: The recipient of the message. This can be set to the + session ID of a client to address only that client, or to + to any custom room created by the application to address all + the clients in that room, If this argument is omitted the + event is broadcasted to all connected clients. + :param room: Alias for the ``to`` parameter. + :param skip_sid: The session ID of a client to skip when broadcasting + to a room or to all clients. This can be used to + prevent a message from being sent to the sender. To + skip multiple sids, pass a list. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the event is emitted to the + default namespace. + :param callback: If given, this function will be called to acknowledge + the the client has received the message. The arguments + that will be passed to the function are those provided + by the client. Callback functions can only be used + when addressing an individual client. + :param ignore_queue: Only used when a message queue is configured. If + set to ``True``, the event is emitted to the + clients directly, without going through the queue. + This is more efficient, but only works when a + single server process is used. It is recommended + to always leave this parameter with its default + value of ``False``. + """ + namespace = namespace or '/' + room = to or room + self.logger.info('emitting event "%s" to %s [%s]', event, + room or 'all', namespace) + self.manager.emit(event, data, namespace, room=room, + skip_sid=skip_sid, callback=callback, **kwargs) + + def send(self, data, to=None, room=None, skip_sid=None, namespace=None, + callback=None, **kwargs): + """Send a message to one or more connected clients. + + This function emits an event with the name ``'message'``. Use + :func:`emit` to issue custom event names. + + :param data: The data to send to the client or clients. Data can be of + type ``str``, ``bytes``, ``list`` or ``dict``. If a + ``list`` or ``dict``, the data will be serialized as JSON. + :param to: The recipient of the message. This can be set to the + session ID of a client to address only that client, or to + to any custom room created by the application to address all + the clients in that room, If this argument is omitted the + event is broadcasted to all connected clients. + :param room: Alias for the ``to`` parameter. + :param skip_sid: The session ID of a client to skip when broadcasting + to a room or to all clients. This can be used to + prevent a message from being sent to the sender. To + skip multiple sids, pass a list. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the event is emitted to the + default namespace. + :param callback: If given, this function will be called to acknowledge + the the client has received the message. The arguments + that will be passed to the function are those provided + by the client. Callback functions can only be used + when addressing an individual client. + :param ignore_queue: Only used when a message queue is configured. If + set to ``True``, the event is emitted to the + clients directly, without going through the queue. + This is more efficient, but only works when a + single server process is used. It is recommended + to always leave this parameter with its default + value of ``False``. + """ + self.emit('message', data=data, to=to, room=room, skip_sid=skip_sid, + namespace=namespace, callback=callback, **kwargs) + + def call(self, event, data=None, to=None, sid=None, namespace=None, + timeout=60, **kwargs): + """Emit a custom event to a client and wait for the response. + + :param event: The event name. It can be any string. The event names + ``'connect'``, ``'message'`` and ``'disconnect'`` are + reserved and should not be used. + :param data: The data to send to the client or clients. Data can be of + type ``str``, ``bytes``, ``list`` or ``dict``. If a + ``list`` or ``dict``, the data will be serialized as JSON. + :param to: The session ID of the recipient client. + :param sid: Alias for the ``to`` parameter. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the event is emitted to the + default namespace. + :param timeout: The waiting timeout. If the timeout is reached before + the client acknowledges the event, then a + ``TimeoutError`` exception is raised. + :param ignore_queue: Only used when a message queue is configured. If + set to ``True``, the event is emitted to the + client directly, without going through the queue. + This is more efficient, but only works when a + single server process is used. It is recommended + to always leave this parameter with its default + value of ``False``. + """ + if not self.async_handlers: + raise RuntimeError( + 'Cannot use call() when async_handlers is False.') + callback_event = self.eio.create_event() + callback_args = [] + + def event_callback(*args): + callback_args.append(args) + callback_event.set() + + self.emit(event, data=data, room=to or sid, namespace=namespace, + callback=event_callback, **kwargs) + if not callback_event.wait(timeout=timeout): + raise exceptions.TimeoutError() + return callback_args[0] if len(callback_args[0]) > 1 \ + else callback_args[0][0] if len(callback_args[0]) == 1 \ + else None + + def enter_room(self, sid, room, namespace=None): + """Enter a room. + + This function adds the client to a room. The :func:`emit` and + :func:`send` functions can optionally broadcast events to all the + clients in a room. + + :param sid: Session ID of the client. + :param room: Room name. If the room does not exist it is created. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the default namespace is used. + """ + namespace = namespace or '/' + self.logger.info('%s is entering room %s [%s]', sid, room, namespace) + self.manager.enter_room(sid, namespace, room) + + def leave_room(self, sid, room, namespace=None): + """Leave a room. + + This function removes the client from a room. + + :param sid: Session ID of the client. + :param room: Room name. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the default namespace is used. + """ + namespace = namespace or '/' + self.logger.info('%s is leaving room %s [%s]', sid, room, namespace) + self.manager.leave_room(sid, namespace, room) + + def close_room(self, room, namespace=None): + """Close a room. + + This function removes all the clients from the given room. + + :param room: Room name. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the default namespace is used. + """ + namespace = namespace or '/' + self.logger.info('room %s is closing [%s]', room, namespace) + self.manager.close_room(room, namespace) + + def rooms(self, sid, namespace=None): + """Return the rooms a client is in. + + :param sid: Session ID of the client. + :param namespace: The Socket.IO namespace for the event. If this + argument is omitted the default namespace is used. + """ + namespace = namespace or '/' + return self.manager.get_rooms(sid, namespace) + + def get_session(self, sid, namespace=None): + """Return the user session for a client. + + :param sid: The session id of the client. + :param namespace: The Socket.IO namespace. If this argument is omitted + the default namespace is used. + + The return value is a dictionary. Modifications made to this + dictionary are not guaranteed to be preserved unless + ``save_session()`` is called, or when the ``session`` context manager + is used. + """ + namespace = namespace or '/' + eio_session = self.eio.get_session(sid) + return eio_session.setdefault(namespace, {}) + + def save_session(self, sid, session, namespace=None): + """Store the user session for a client. + + :param sid: The session id of the client. + :param session: The session dictionary. + :param namespace: The Socket.IO namespace. If this argument is omitted + the default namespace is used. + """ + namespace = namespace or '/' + eio_session = self.eio.get_session(sid) + eio_session[namespace] = session + + def session(self, sid, namespace=None): + """Return the user session for a client with context manager syntax. + + :param sid: The session id of the client. + + This is a context manager that returns the user session dictionary for + the client. Any changes that are made to this dictionary inside the + context manager block are saved back to the session. Example usage:: + + @sio.on('connect') + def on_connect(sid, environ): + username = authenticate_user(environ) + if not username: + return False + with sio.session(sid) as session: + session['username'] = username + + @sio.on('message') + def on_message(sid, msg): + with sio.session(sid) as session: + print('received message from ', session['username']) + """ + class _session_context_manager(object): + def __init__(self, server, sid, namespace): + self.server = server + self.sid = sid + self.namespace = namespace + self.session = None + + def __enter__(self): + self.session = self.server.get_session(sid, + namespace=namespace) + return self.session + + def __exit__(self, *args): + self.server.save_session(sid, self.session, + namespace=namespace) + + return _session_context_manager(self, sid, namespace) + + def disconnect(self, sid, namespace=None): + """Disconnect a client. + + :param sid: Session ID of the client. + :param namespace: The Socket.IO namespace to disconnect. If this + argument is omitted the default namespace is used. + """ + namespace = namespace or '/' + if self.manager.is_connected(sid, namespace=namespace): + self.logger.info('Disconnecting %s [%s]', sid, namespace) + self.manager.pre_disconnect(sid, namespace=namespace) + self._send_packet(sid, packet.Packet(packet.DISCONNECT, + namespace=namespace)) + self._trigger_event('disconnect', namespace, sid) + self.manager.disconnect(sid, namespace=namespace) + + def transport(self, sid): + """Return the name of the transport used by the client. + + The two possible values returned by this function are ``'polling'`` + and ``'websocket'``. + + :param sid: The session of the client. + """ + return self.eio.transport(sid) + + def handle_request(self, environ, start_response): + """Handle an HTTP request from the client. + + This is the entry point of the Socket.IO application, using the same + interface as a WSGI application. For the typical usage, this function + is invoked by the :class:`Middleware` instance, but it can be invoked + directly when the middleware is not used. + + :param environ: The WSGI environment. + :param start_response: The WSGI ``start_response`` function. + + This function returns the HTTP response body to deliver to the client + as a byte sequence. + """ + return self.eio.handle_request(environ, start_response) + + def start_background_task(self, target, *args, **kwargs): + """Start a background task using the appropriate async model. + + This is a utility function that applications can use to start a + background task using the method that is compatible with the + selected async mode. + + :param target: the target function to execute. + :param args: arguments to pass to the function. + :param kwargs: keyword arguments to pass to the function. + + This function returns an object compatible with the `Thread` class in + the Python standard library. The `start()` method on this object is + already called by this function. + """ + return self.eio.start_background_task(target, *args, **kwargs) + + def sleep(self, seconds=0): + """Sleep for the requested amount of time using the appropriate async + model. + + This is a utility function that applications can use to put a task to + sleep without having to worry about using the correct call for the + selected async mode. + """ + return self.eio.sleep(seconds) + + def _emit_internal(self, sid, event, data, namespace=None, id=None): + """Send a message to a client.""" + if six.PY2 and not self.binary: + binary = False # pragma: nocover + else: + binary = None + # tuples are expanded to multiple arguments, everything else is sent + # as a single argument + if isinstance(data, tuple): + data = list(data) + else: + data = [data] + self._send_packet(sid, packet.Packet(packet.EVENT, namespace=namespace, + data=[event] + data, id=id, + binary=binary)) + + def _send_packet(self, sid, pkt): + """Send a Socket.IO packet to a client.""" + encoded_packet = pkt.encode() + if isinstance(encoded_packet, list): + binary = False + for ep in encoded_packet: + self.eio.send(sid, ep, binary=binary) + binary = True + else: + self.eio.send(sid, encoded_packet, binary=False) + + def _handle_connect(self, sid, namespace): + """Handle a client connection request.""" + namespace = namespace or '/' + self.manager.connect(sid, namespace) + if self.always_connect: + self._send_packet(sid, packet.Packet(packet.CONNECT, + namespace=namespace)) + fail_reason = None + try: + success = self._trigger_event('connect', namespace, sid, + self.environ[sid]) + except exceptions.ConnectionRefusedError as exc: + fail_reason = exc.error_args + success = False + + if success is False: + if self.always_connect: + self.manager.pre_disconnect(sid, namespace) + self._send_packet(sid, packet.Packet( + packet.DISCONNECT, data=fail_reason, namespace=namespace)) + self.manager.disconnect(sid, namespace) + if not self.always_connect: + self._send_packet(sid, packet.Packet( + packet.ERROR, data=fail_reason, namespace=namespace)) + if sid in self.environ: # pragma: no cover + del self.environ[sid] + return False + elif not self.always_connect: + self._send_packet(sid, packet.Packet(packet.CONNECT, + namespace=namespace)) + + def _handle_disconnect(self, sid, namespace): + """Handle a client disconnect.""" + namespace = namespace or '/' + if namespace == '/': + namespace_list = list(self.manager.get_namespaces()) + else: + namespace_list = [namespace] + for n in namespace_list: + if n != '/' and self.manager.is_connected(sid, n): + self._trigger_event('disconnect', n, sid) + self.manager.disconnect(sid, n) + if namespace == '/' and self.manager.is_connected(sid, namespace): + self._trigger_event('disconnect', '/', sid) + self.manager.disconnect(sid, '/') + + def _handle_event(self, sid, namespace, id, data): + """Handle an incoming client event.""" + namespace = namespace or '/' + self.logger.info('received event "%s" from %s [%s]', data[0], sid, + namespace) + if self.async_handlers: + self.start_background_task(self._handle_event_internal, self, sid, + data, namespace, id) + else: + self._handle_event_internal(self, sid, data, namespace, id) + + def _handle_event_internal(self, server, sid, data, namespace, id): + r = server._trigger_event(data[0], namespace, sid, *data[1:]) + if id is not None: + # send ACK packet with the response returned by the handler + # tuples are expanded as multiple arguments + if r is None: + data = [] + elif isinstance(r, tuple): + data = list(r) + else: + data = [r] + if six.PY2 and not self.binary: + binary = False # pragma: nocover + else: + binary = None + server._send_packet(sid, packet.Packet(packet.ACK, + namespace=namespace, + id=id, data=data, + binary=binary)) + + def _handle_ack(self, sid, namespace, id, data): + """Handle ACK packets from the client.""" + namespace = namespace or '/' + self.logger.info('received ack from %s [%s]', sid, namespace) + self.manager.trigger_callback(sid, namespace, id, data) + + def _trigger_event(self, event, namespace, *args): + """Invoke an application event handler.""" + # first see if we have an explicit handler for the event + if namespace in self.handlers and event in self.handlers[namespace]: + return self.handlers[namespace][event](*args) + + # or else, forward the event to a namespace handler if one exists + elif namespace in self.namespace_handlers: + return self.namespace_handlers[namespace].trigger_event( + event, *args) + + def _handle_eio_connect(self, sid, environ): + """Handle the Engine.IO connection event.""" + if not self.manager_initialized: + self.manager_initialized = True + self.manager.initialize() + self.environ[sid] = environ + return self._handle_connect(sid, '/') + + def _handle_eio_message(self, sid, data): + """Dispatch Engine.IO messages.""" + if sid in self._binary_packet: + pkt = self._binary_packet[sid] + if pkt.add_attachment(data): + del self._binary_packet[sid] + if pkt.packet_type == packet.BINARY_EVENT: + self._handle_event(sid, pkt.namespace, pkt.id, pkt.data) + else: + self._handle_ack(sid, pkt.namespace, pkt.id, pkt.data) + else: + pkt = packet.Packet(encoded_packet=data) + if pkt.packet_type == packet.CONNECT: + self._handle_connect(sid, pkt.namespace) + elif pkt.packet_type == packet.DISCONNECT: + self._handle_disconnect(sid, pkt.namespace) + elif pkt.packet_type == packet.EVENT: + self._handle_event(sid, pkt.namespace, pkt.id, pkt.data) + elif pkt.packet_type == packet.ACK: + self._handle_ack(sid, pkt.namespace, pkt.id, pkt.data) + elif pkt.packet_type == packet.BINARY_EVENT or \ + pkt.packet_type == packet.BINARY_ACK: + self._binary_packet[sid] = pkt + elif pkt.packet_type == packet.ERROR: + raise ValueError('Unexpected ERROR packet.') + else: + raise ValueError('Unknown packet type.') + + def _handle_eio_disconnect(self, sid): + """Handle Engine.IO disconnect event.""" + self._handle_disconnect(sid, '/') + if sid in self.environ: + del self.environ[sid] + + def _engineio_server_class(self): + return engineio.Server diff --git a/openpype/vendor/python/python_2/socketio/tornado.py b/openpype/vendor/python/python_2/socketio/tornado.py new file mode 100644 index 0000000000..5b2e6f684f --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/tornado.py @@ -0,0 +1,11 @@ +import sys +if sys.version_info >= (3, 5): + try: + from engineio.async_drivers.tornado import get_tornado_handler as \ + get_engineio_handler + except ImportError: # pragma: no cover + get_engineio_handler = None + + +def get_tornado_handler(socketio_server): # pragma: no cover + return get_engineio_handler(socketio_server.eio) diff --git a/openpype/vendor/python/python_2/socketio/zmq_manager.py b/openpype/vendor/python/python_2/socketio/zmq_manager.py new file mode 100644 index 0000000000..f2a2ae5dc6 --- /dev/null +++ b/openpype/vendor/python/python_2/socketio/zmq_manager.py @@ -0,0 +1,111 @@ +import pickle +import re + +try: + import eventlet.green.zmq as zmq +except ImportError: + zmq = None +import six + +from .pubsub_manager import PubSubManager + + +class ZmqManager(PubSubManager): # pragma: no cover + """zmq based client manager. + + NOTE: this zmq implementation should be considered experimental at this + time. At this time, eventlet is required to use zmq. + + This class implements a zmq backend for event sharing across multiple + processes. To use a zmq backend, initialize the :class:`Server` instance as + follows:: + + url = 'zmq+tcp://hostname:port1+port2' + server = socketio.Server(client_manager=socketio.ZmqManager(url)) + + :param url: The connection URL for the zmq message broker, + which will need to be provided and running. + :param channel: The channel name on which the server sends and receives + notifications. Must be the same in all the servers. + :param write_only: If set to ``True``, only initialize to emit events. The + default of ``False`` initializes the class for emitting + and receiving. + + A zmq message broker must be running for the zmq_manager to work. + you can write your own or adapt one from the following simple broker + below:: + + import zmq + + receiver = zmq.Context().socket(zmq.PULL) + receiver.bind("tcp://*:5555") + + publisher = zmq.Context().socket(zmq.PUB) + publisher.bind("tcp://*:5556") + + while True: + publisher.send(receiver.recv()) + """ + name = 'zmq' + + def __init__(self, url='zmq+tcp://localhost:5555+5556', + channel='socketio', + write_only=False, + logger=None): + if zmq is None: + raise RuntimeError('zmq package is not installed ' + '(Run "pip install pyzmq" in your ' + 'virtualenv).') + + r = re.compile(r':\d+\+\d+$') + if not (url.startswith('zmq+tcp://') and r.search(url)): + raise RuntimeError('unexpected connection string: ' + url) + + url = url.replace('zmq+', '') + (sink_url, sub_port) = url.split('+') + sink_port = sink_url.split(':')[-1] + sub_url = sink_url.replace(sink_port, sub_port) + + sink = zmq.Context().socket(zmq.PUSH) + sink.connect(sink_url) + + sub = zmq.Context().socket(zmq.SUB) + sub.setsockopt_string(zmq.SUBSCRIBE, u'') + sub.connect(sub_url) + + self.sink = sink + self.sub = sub + self.channel = channel + super(ZmqManager, self).__init__(channel=channel, + write_only=write_only, + logger=logger) + + def _publish(self, data): + pickled_data = pickle.dumps( + { + 'type': 'message', + 'channel': self.channel, + 'data': data + } + ) + return self.sink.send(pickled_data) + + def zmq_listen(self): + while True: + response = self.sub.recv() + if response is not None: + yield response + + def _listen(self): + for message in self.zmq_listen(): + if isinstance(message, six.binary_type): + try: + message = pickle.loads(message) + except Exception: + pass + if isinstance(message, dict) and \ + message['type'] == 'message' and \ + message['channel'] == self.channel and \ + 'data' in message: + yield message['data'] + return diff --git a/openpype/vendor/python/python_2/websocket/__init__.py b/openpype/vendor/python/python_2/websocket/__init__.py new file mode 100644 index 0000000000..f2c7b44c17 --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/__init__.py @@ -0,0 +1,28 @@ +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" +from ._abnf import * +from ._app import WebSocketApp +from ._core import * +from ._exceptions import * +from ._logging import * +from ._socket import * + +__version__ = "0.59.0" diff --git a/openpype/vendor/python/python_2/websocket/_abnf.py b/openpype/vendor/python/python_2/websocket/_abnf.py new file mode 100644 index 0000000000..80fbe1f9b9 --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/_abnf.py @@ -0,0 +1,458 @@ +""" + +""" + +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" +import array +import os +import struct + +import six + +from ._exceptions import * +from ._utils import validate_utf8 +from threading import Lock + +try: + if six.PY3: + import numpy + else: + numpy = None +except ImportError: + numpy = None + +try: + # If wsaccel is available we use compiled routines to mask data. + if not numpy: + from wsaccel.xormask import XorMaskerSimple + + def _mask(_m, _d): + return XorMaskerSimple(_m).process(_d) +except ImportError: + # wsaccel is not available, we rely on python implementations. + def _mask(_m, _d): + for i in range(len(_d)): + _d[i] ^= _m[i % 4] + + if six.PY3: + return _d.tobytes() + else: + return _d.tostring() + + +__all__ = [ + 'ABNF', 'continuous_frame', 'frame_buffer', + 'STATUS_NORMAL', + 'STATUS_GOING_AWAY', + 'STATUS_PROTOCOL_ERROR', + 'STATUS_UNSUPPORTED_DATA_TYPE', + 'STATUS_STATUS_NOT_AVAILABLE', + 'STATUS_ABNORMAL_CLOSED', + 'STATUS_INVALID_PAYLOAD', + 'STATUS_POLICY_VIOLATION', + 'STATUS_MESSAGE_TOO_BIG', + 'STATUS_INVALID_EXTENSION', + 'STATUS_UNEXPECTED_CONDITION', + 'STATUS_BAD_GATEWAY', + 'STATUS_TLS_HANDSHAKE_ERROR', +] + +# closing frame status codes. +STATUS_NORMAL = 1000 +STATUS_GOING_AWAY = 1001 +STATUS_PROTOCOL_ERROR = 1002 +STATUS_UNSUPPORTED_DATA_TYPE = 1003 +STATUS_STATUS_NOT_AVAILABLE = 1005 +STATUS_ABNORMAL_CLOSED = 1006 +STATUS_INVALID_PAYLOAD = 1007 +STATUS_POLICY_VIOLATION = 1008 +STATUS_MESSAGE_TOO_BIG = 1009 +STATUS_INVALID_EXTENSION = 1010 +STATUS_UNEXPECTED_CONDITION = 1011 +STATUS_BAD_GATEWAY = 1014 +STATUS_TLS_HANDSHAKE_ERROR = 1015 + +VALID_CLOSE_STATUS = ( + STATUS_NORMAL, + STATUS_GOING_AWAY, + STATUS_PROTOCOL_ERROR, + STATUS_UNSUPPORTED_DATA_TYPE, + STATUS_INVALID_PAYLOAD, + STATUS_POLICY_VIOLATION, + STATUS_MESSAGE_TOO_BIG, + STATUS_INVALID_EXTENSION, + STATUS_UNEXPECTED_CONDITION, + STATUS_BAD_GATEWAY, +) + + +class ABNF(object): + """ + ABNF frame class. + See http://tools.ietf.org/html/rfc5234 + and http://tools.ietf.org/html/rfc6455#section-5.2 + """ + + # operation code values. + OPCODE_CONT = 0x0 + OPCODE_TEXT = 0x1 + OPCODE_BINARY = 0x2 + OPCODE_CLOSE = 0x8 + OPCODE_PING = 0x9 + OPCODE_PONG = 0xa + + # available operation code value tuple + OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE, + OPCODE_PING, OPCODE_PONG) + + # opcode human readable string + OPCODE_MAP = { + OPCODE_CONT: "cont", + OPCODE_TEXT: "text", + OPCODE_BINARY: "binary", + OPCODE_CLOSE: "close", + OPCODE_PING: "ping", + OPCODE_PONG: "pong" + } + + # data length threshold. + LENGTH_7 = 0x7e + LENGTH_16 = 1 << 16 + LENGTH_63 = 1 << 63 + + def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0, + opcode=OPCODE_TEXT, mask=1, data=""): + """ + Constructor for ABNF. Please check RFC for arguments. + """ + self.fin = fin + self.rsv1 = rsv1 + self.rsv2 = rsv2 + self.rsv3 = rsv3 + self.opcode = opcode + self.mask = mask + if data is None: + data = "" + self.data = data + self.get_mask_key = os.urandom + + def validate(self, skip_utf8_validation=False): + """ + Validate the ABNF frame. + + Parameters + ---------- + skip_utf8_validation: skip utf8 validation. + """ + if self.rsv1 or self.rsv2 or self.rsv3: + raise WebSocketProtocolException("rsv is not implemented, yet") + + if self.opcode not in ABNF.OPCODES: + raise WebSocketProtocolException("Invalid opcode %r", self.opcode) + + if self.opcode == ABNF.OPCODE_PING and not self.fin: + raise WebSocketProtocolException("Invalid ping frame.") + + if self.opcode == ABNF.OPCODE_CLOSE: + l = len(self.data) + if not l: + return + if l == 1 or l >= 126: + raise WebSocketProtocolException("Invalid close frame.") + if l > 2 and not skip_utf8_validation and not validate_utf8(self.data[2:]): + raise WebSocketProtocolException("Invalid close frame.") + + code = 256 * \ + six.byte2int(self.data[0:1]) + six.byte2int(self.data[1:2]) + if not self._is_valid_close_status(code): + raise WebSocketProtocolException("Invalid close opcode.") + + @staticmethod + def _is_valid_close_status(code): + return code in VALID_CLOSE_STATUS or (3000 <= code < 5000) + + def __str__(self): + return "fin=" + str(self.fin) \ + + " opcode=" + str(self.opcode) \ + + " data=" + str(self.data) + + @staticmethod + def create_frame(data, opcode, fin=1): + """ + Create frame to send text, binary and other data. + + Parameters + ---------- + data: + data to send. This is string value(byte array). + If opcode is OPCODE_TEXT and this value is unicode, + data value is converted into unicode string, automatically. + opcode: + operation code. please see OPCODE_XXX. + fin: + fin flag. if set to 0, create continue fragmentation. + """ + if opcode == ABNF.OPCODE_TEXT and isinstance(data, six.text_type): + data = data.encode("utf-8") + # mask must be set if send data from client + return ABNF(fin, 0, 0, 0, opcode, 1, data) + + def format(self): + """ + Format this object to string(byte array) to send data to server. + """ + if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]): + raise ValueError("not 0 or 1") + if self.opcode not in ABNF.OPCODES: + raise ValueError("Invalid OPCODE") + length = len(self.data) + if length >= ABNF.LENGTH_63: + raise ValueError("data is too long") + + frame_header = chr(self.fin << 7 | + self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 | + self.opcode) + if length < ABNF.LENGTH_7: + frame_header += chr(self.mask << 7 | length) + frame_header = six.b(frame_header) + elif length < ABNF.LENGTH_16: + frame_header += chr(self.mask << 7 | 0x7e) + frame_header = six.b(frame_header) + frame_header += struct.pack("!H", length) + else: + frame_header += chr(self.mask << 7 | 0x7f) + frame_header = six.b(frame_header) + frame_header += struct.pack("!Q", length) + + if not self.mask: + return frame_header + self.data + else: + mask_key = self.get_mask_key(4) + return frame_header + self._get_masked(mask_key) + + def _get_masked(self, mask_key): + s = ABNF.mask(mask_key, self.data) + + if isinstance(mask_key, six.text_type): + mask_key = mask_key.encode('utf-8') + + return mask_key + s + + @staticmethod + def mask(mask_key, data): + """ + Mask or unmask data. Just do xor for each byte + + Parameters + ---------- + mask_key: + 4 byte string(byte). + data: + data to mask/unmask. + """ + if data is None: + data = "" + + if isinstance(mask_key, six.text_type): + mask_key = six.b(mask_key) + + if isinstance(data, six.text_type): + data = six.b(data) + + if numpy: + origlen = len(data) + _mask_key = mask_key[3] << 24 | mask_key[2] << 16 | mask_key[1] << 8 | mask_key[0] + + # We need data to be a multiple of four... + data += bytes(" " * (4 - (len(data) % 4)), "us-ascii") + a = numpy.frombuffer(data, dtype="uint32") + masked = numpy.bitwise_xor(a, [_mask_key]).astype("uint32") + if len(data) > origlen: + return masked.tobytes()[:origlen] + return masked.tobytes() + else: + _m = array.array("B", mask_key) + _d = array.array("B", data) + return _mask(_m, _d) + + +class frame_buffer(object): + _HEADER_MASK_INDEX = 5 + _HEADER_LENGTH_INDEX = 6 + + def __init__(self, recv_fn, skip_utf8_validation): + self.recv = recv_fn + self.skip_utf8_validation = skip_utf8_validation + # Buffers over the packets from the layer beneath until desired amount + # bytes of bytes are received. + self.recv_buffer = [] + self.clear() + self.lock = Lock() + + def clear(self): + self.header = None + self.length = None + self.mask = None + + def has_received_header(self): + return self.header is None + + def recv_header(self): + header = self.recv_strict(2) + b1 = header[0] + + if six.PY2: + b1 = ord(b1) + + fin = b1 >> 7 & 1 + rsv1 = b1 >> 6 & 1 + rsv2 = b1 >> 5 & 1 + rsv3 = b1 >> 4 & 1 + opcode = b1 & 0xf + b2 = header[1] + + if six.PY2: + b2 = ord(b2) + + has_mask = b2 >> 7 & 1 + length_bits = b2 & 0x7f + + self.header = (fin, rsv1, rsv2, rsv3, opcode, has_mask, length_bits) + + def has_mask(self): + if not self.header: + return False + return self.header[frame_buffer._HEADER_MASK_INDEX] + + def has_received_length(self): + return self.length is None + + def recv_length(self): + bits = self.header[frame_buffer._HEADER_LENGTH_INDEX] + length_bits = bits & 0x7f + if length_bits == 0x7e: + v = self.recv_strict(2) + self.length = struct.unpack("!H", v)[0] + elif length_bits == 0x7f: + v = self.recv_strict(8) + self.length = struct.unpack("!Q", v)[0] + else: + self.length = length_bits + + def has_received_mask(self): + return self.mask is None + + def recv_mask(self): + self.mask = self.recv_strict(4) if self.has_mask() else "" + + def recv_frame(self): + + with self.lock: + # Header + if self.has_received_header(): + self.recv_header() + (fin, rsv1, rsv2, rsv3, opcode, has_mask, _) = self.header + + # Frame length + if self.has_received_length(): + self.recv_length() + length = self.length + + # Mask + if self.has_received_mask(): + self.recv_mask() + mask = self.mask + + # Payload + payload = self.recv_strict(length) + if has_mask: + payload = ABNF.mask(mask, payload) + + # Reset for next frame + self.clear() + + frame = ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload) + frame.validate(self.skip_utf8_validation) + + return frame + + def recv_strict(self, bufsize): + shortage = bufsize - sum(len(x) for x in self.recv_buffer) + while shortage > 0: + # Limit buffer size that we pass to socket.recv() to avoid + # fragmenting the heap -- the number of bytes recv() actually + # reads is limited by socket buffer and is relatively small, + # yet passing large numbers repeatedly causes lots of large + # buffers allocated and then shrunk, which results in + # fragmentation. + bytes_ = self.recv(min(16384, shortage)) + self.recv_buffer.append(bytes_) + shortage -= len(bytes_) + + unified = six.b("").join(self.recv_buffer) + + if shortage == 0: + self.recv_buffer = [] + return unified + else: + self.recv_buffer = [unified[bufsize:]] + return unified[:bufsize] + + +class continuous_frame(object): + + def __init__(self, fire_cont_frame, skip_utf8_validation): + self.fire_cont_frame = fire_cont_frame + self.skip_utf8_validation = skip_utf8_validation + self.cont_data = None + self.recving_frames = None + + def validate(self, frame): + if not self.recving_frames and frame.opcode == ABNF.OPCODE_CONT: + raise WebSocketProtocolException("Illegal frame") + if self.recving_frames and \ + frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY): + raise WebSocketProtocolException("Illegal frame") + + def add(self, frame): + if self.cont_data: + self.cont_data[1] += frame.data + else: + if frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY): + self.recving_frames = frame.opcode + self.cont_data = [frame.opcode, frame.data] + + if frame.fin: + self.recving_frames = None + + def is_fire(self, frame): + return frame.fin or self.fire_cont_frame + + def extract(self, frame): + data = self.cont_data + self.cont_data = None + frame.data = data[1] + if not self.fire_cont_frame and data[0] == ABNF.OPCODE_TEXT and not self.skip_utf8_validation and not validate_utf8(frame.data): + raise WebSocketPayloadException( + "cannot decode: " + repr(frame.data)) + + return [data[0], frame] diff --git a/openpype/vendor/python/python_2/websocket/_app.py b/openpype/vendor/python/python_2/websocket/_app.py new file mode 100644 index 0000000000..a3f91a38a3 --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/_app.py @@ -0,0 +1,399 @@ +""" + +""" + +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" +import inspect +import select +import sys +import threading +import time +import traceback + +import six + +from ._abnf import ABNF +from ._core import WebSocket, getdefaulttimeout +from ._exceptions import * +from . import _logging + + +__all__ = ["WebSocketApp"] + + +class Dispatcher: + """ + Dispatcher + """ + def __init__(self, app, ping_timeout): + self.app = app + self.ping_timeout = ping_timeout + + def read(self, sock, read_callback, check_callback): + while self.app.keep_running: + r, w, e = select.select( + (self.app.sock.sock, ), (), (), self.ping_timeout) + if r: + if not read_callback(): + break + check_callback() + + +class SSLDispatcher: + """ + SSLDispatcher + """ + def __init__(self, app, ping_timeout): + self.app = app + self.ping_timeout = ping_timeout + + def read(self, sock, read_callback, check_callback): + while self.app.keep_running: + r = self.select() + if r: + if not read_callback(): + break + check_callback() + + def select(self): + sock = self.app.sock.sock + if sock.pending(): + return [sock,] + + r, w, e = select.select((sock, ), (), (), self.ping_timeout) + return r + + +class WebSocketApp(object): + """ + Higher level of APIs are provided. The interface is like JavaScript WebSocket object. + """ + + def __init__(self, url, header=None, + on_open=None, on_message=None, on_error=None, + on_close=None, on_ping=None, on_pong=None, + on_cont_message=None, + keep_running=True, get_mask_key=None, cookie=None, + subprotocols=None, + on_data=None): + """ + WebSocketApp initialization + + Parameters + ---------- + url: + websocket url. + header: list or dict + custom header for websocket handshake. + on_open: + callable object which is called at opening websocket. + this function has one argument. The argument is this class object. + on_message: + callable object which is called when received data. + on_message has 2 arguments. + The 1st argument is this class object. + The 2nd argument is utf-8 string which we get from the server. + on_error: + callable object which is called when we get error. + on_error has 2 arguments. + The 1st argument is this class object. + The 2nd argument is exception object. + on_close: + callable object which is called when closed the connection. + this function has one argument. The argument is this class object. + on_cont_message: + callback object which is called when receive continued + frame data. + on_cont_message has 3 arguments. + The 1st argument is this class object. + The 2nd argument is utf-8 string which we get from the server. + The 3rd argument is continue flag. if 0, the data continue + to next frame data + on_data: + callback object which is called when a message received. + This is called before on_message or on_cont_message, + and then on_message or on_cont_message is called. + on_data has 4 argument. + The 1st argument is this class object. + The 2nd argument is utf-8 string which we get from the server. + The 3rd argument is data type. ABNF.OPCODE_TEXT or ABNF.OPCODE_BINARY will be came. + The 4th argument is continue flag. if 0, the data continue + keep_running: + this parameter is obsolete and ignored. + get_mask_key: func + a callable to produce new mask keys, + see the WebSocket.set_mask_key's docstring for more information + cookie: str + cookie value. + subprotocols: + array of available sub protocols. default is None. + """ + self.url = url + self.header = header if header is not None else [] + self.cookie = cookie + + self.on_open = on_open + self.on_message = on_message + self.on_data = on_data + self.on_error = on_error + self.on_close = on_close + self.on_ping = on_ping + self.on_pong = on_pong + self.on_cont_message = on_cont_message + self.keep_running = False + self.get_mask_key = get_mask_key + self.sock = None + self.last_ping_tm = 0 + self.last_pong_tm = 0 + self.subprotocols = subprotocols + + def send(self, data, opcode=ABNF.OPCODE_TEXT): + """ + send message + + Parameters + ---------- + data: + Message to send. If you set opcode to OPCODE_TEXT, + data must be utf-8 string or unicode. + opcode: + Operation code of data. default is OPCODE_TEXT. + """ + + if not self.sock or self.sock.send(data, opcode) == 0: + raise WebSocketConnectionClosedException( + "Connection is already closed.") + + def close(self, **kwargs): + """ + Close websocket connection. + """ + self.keep_running = False + if self.sock: + self.sock.close(**kwargs) + self.sock = None + + def _send_ping(self, interval, event, payload): + while not event.wait(interval): + self.last_ping_tm = time.time() + if self.sock: + try: + self.sock.ping(payload) + except Exception as ex: + _logging.warning("send_ping routine terminated: {}".format(ex)) + break + + def run_forever(self, sockopt=None, sslopt=None, + ping_interval=0, ping_timeout=None, + ping_payload="", + http_proxy_host=None, http_proxy_port=None, + http_no_proxy=None, http_proxy_auth=None, + skip_utf8_validation=False, + host=None, origin=None, dispatcher=None, + suppress_origin=False, proxy_type=None): + """ + Run event loop for WebSocket framework. + + This loop is an infinite loop and is alive while websocket is available. + + Parameters + ---------- + sockopt: tuple + values for socket.setsockopt. + sockopt must be tuple + and each element is argument of sock.setsockopt. + sslopt: dict + optional dict object for ssl socket option. + ping_interval: int or float + automatically send "ping" command + every specified period (in seconds) + if set to 0, not send automatically. + ping_timeout: int or float + timeout (in seconds) if the pong message is not received. + ping_payload: str + payload message to send with each ping. + http_proxy_host: + http proxy host name. + http_proxy_port: + http proxy port. If not set, set to 80. + http_no_proxy: + host names, which doesn't use proxy. + skip_utf8_validation: bool + skip utf8 validation. + host: str + update host header. + origin: str + update origin header. + dispatcher: + customize reading data from socket. + suppress_origin: bool + suppress outputting origin header. + + Returns + ------- + teardown: bool + False if caught KeyboardInterrupt, True if other exception was raised during a loop + """ + + if ping_timeout is not None and ping_timeout <= 0: + ping_timeout = None + if ping_timeout and ping_interval and ping_interval <= ping_timeout: + raise WebSocketException("Ensure ping_interval > ping_timeout") + if not sockopt: + sockopt = [] + if not sslopt: + sslopt = {} + if self.sock: + raise WebSocketException("socket is already opened") + thread = None + self.keep_running = True + self.last_ping_tm = 0 + self.last_pong_tm = 0 + + def teardown(close_frame=None): + """ + Tears down the connection. + + If close_frame is set, we will invoke the on_close handler with the + statusCode and reason from there. + """ + if thread and thread.is_alive(): + event.set() + thread.join() + self.keep_running = False + if self.sock: + self.sock.close() + close_args = self._get_close_args( + close_frame.data if close_frame else None) + self._callback(self.on_close, *close_args) + self.sock = None + + try: + self.sock = WebSocket( + self.get_mask_key, sockopt=sockopt, sslopt=sslopt, + fire_cont_frame=self.on_cont_message is not None, + skip_utf8_validation=skip_utf8_validation, + enable_multithread=True if ping_interval else False) + self.sock.settimeout(getdefaulttimeout()) + self.sock.connect( + self.url, header=self.header, cookie=self.cookie, + http_proxy_host=http_proxy_host, + http_proxy_port=http_proxy_port, http_no_proxy=http_no_proxy, + http_proxy_auth=http_proxy_auth, subprotocols=self.subprotocols, + host=host, origin=origin, suppress_origin=suppress_origin, + proxy_type=proxy_type) + if not dispatcher: + dispatcher = self.create_dispatcher(ping_timeout) + + self._callback(self.on_open) + + if ping_interval: + event = threading.Event() + thread = threading.Thread( + target=self._send_ping, args=(ping_interval, event, ping_payload)) + thread.daemon = True + thread.start() + + def read(): + if not self.keep_running: + return teardown() + + op_code, frame = self.sock.recv_data_frame(True) + if op_code == ABNF.OPCODE_CLOSE: + return teardown(frame) + elif op_code == ABNF.OPCODE_PING: + self._callback(self.on_ping, frame.data) + elif op_code == ABNF.OPCODE_PONG: + self.last_pong_tm = time.time() + self._callback(self.on_pong, frame.data) + elif op_code == ABNF.OPCODE_CONT and self.on_cont_message: + self._callback(self.on_data, frame.data, + frame.opcode, frame.fin) + self._callback(self.on_cont_message, + frame.data, frame.fin) + else: + data = frame.data + if six.PY3 and op_code == ABNF.OPCODE_TEXT: + data = data.decode("utf-8") + self._callback(self.on_data, data, frame.opcode, True) + self._callback(self.on_message, data) + + return True + + def check(): + if (ping_timeout): + has_timeout_expired = time.time() - self.last_ping_tm > ping_timeout + has_pong_not_arrived_after_last_ping = self.last_pong_tm - self.last_ping_tm < 0 + has_pong_arrived_too_late = self.last_pong_tm - self.last_ping_tm > ping_timeout + + if (self.last_ping_tm and + has_timeout_expired and + (has_pong_not_arrived_after_last_ping or has_pong_arrived_too_late)): + raise WebSocketTimeoutException("ping/pong timed out") + return True + + dispatcher.read(self.sock.sock, read, check) + except (Exception, KeyboardInterrupt, SystemExit) as e: + self._callback(self.on_error, e) + if isinstance(e, SystemExit): + # propagate SystemExit further + raise + teardown() + return not isinstance(e, KeyboardInterrupt) + + def create_dispatcher(self, ping_timeout): + timeout = ping_timeout or 10 + if self.sock.is_ssl(): + return SSLDispatcher(self, timeout) + + return Dispatcher(self, timeout) + + def _get_close_args(self, data): + """ + _get_close_args extracts the code, reason from the close body + if they exists, and if the self.on_close except three arguments + """ + # if the on_close callback is "old", just return empty list + if sys.version_info < (3, 0): + if not self.on_close or len(inspect.getargspec(self.on_close).args) != 3: + return [] + else: + if not self.on_close or len(inspect.getfullargspec(self.on_close).args) != 3: + return [] + + if data and len(data) >= 2: + code = 256 * six.byte2int(data[0:1]) + six.byte2int(data[1:2]) + reason = data[2:].decode('utf-8') + return [code, reason] + + return [None, None] + + def _callback(self, callback, *args): + if callback: + try: + callback(self, *args) + + except Exception as e: + _logging.error("error from callback {}: {}".format(callback, e)) + if _logging.isEnabledForDebug(): + _, _, tb = sys.exc_info() + traceback.print_tb(tb) diff --git a/openpype/vendor/python/python_2/websocket/_cookiejar.py b/openpype/vendor/python/python_2/websocket/_cookiejar.py new file mode 100644 index 0000000000..bc2891a650 --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/_cookiejar.py @@ -0,0 +1,78 @@ +""" + +""" + +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" +try: + import Cookie +except: + import http.cookies as Cookie + + +class SimpleCookieJar(object): + def __init__(self): + self.jar = dict() + + def add(self, set_cookie): + if set_cookie: + try: + simpleCookie = Cookie.SimpleCookie(set_cookie) + except: + simpleCookie = Cookie.SimpleCookie(set_cookie.encode('ascii', 'ignore')) + + for k, v in simpleCookie.items(): + domain = v.get("domain") + if domain: + if not domain.startswith("."): + domain = "." + domain + cookie = self.jar.get(domain) if self.jar.get(domain) else Cookie.SimpleCookie() + cookie.update(simpleCookie) + self.jar[domain.lower()] = cookie + + def set(self, set_cookie): + if set_cookie: + try: + simpleCookie = Cookie.SimpleCookie(set_cookie) + except: + simpleCookie = Cookie.SimpleCookie(set_cookie.encode('ascii', 'ignore')) + + for k, v in simpleCookie.items(): + domain = v.get("domain") + if domain: + if not domain.startswith("."): + domain = "." + domain + self.jar[domain.lower()] = simpleCookie + + def get(self, host): + if not host: + return "" + + cookies = [] + for domain, simpleCookie in self.jar.items(): + host = host.lower() + if host.endswith(domain) or host == domain[1:]: + cookies.append(self.jar.get(domain)) + + return "; ".join(filter( + None, sorted( + ["%s=%s" % (k, v.value) for cookie in filter(None, cookies) for k, v in cookie.items()] + ))) diff --git a/openpype/vendor/python/python_2/websocket/_core.py b/openpype/vendor/python/python_2/websocket/_core.py new file mode 100644 index 0000000000..1ff80f05d7 --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/_core.py @@ -0,0 +1,595 @@ +from __future__ import print_function +""" +_core.py +==================================== +WebSocket Python client +""" + +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" +import socket +import struct +import threading +import time + +import six + +# websocket modules +from ._abnf import * +from ._exceptions import * +from ._handshake import * +from ._http import * +from ._logging import * +from ._socket import * +from ._ssl_compat import * +from ._utils import * + +__all__ = ['WebSocket', 'create_connection'] + + +class WebSocket(object): + """ + Low level WebSocket interface. + + This class is based on the WebSocket protocol `draft-hixie-thewebsocketprotocol-76 `_ + + We can connect to the websocket server and send/receive data. + The following example is an echo client. + + >>> import websocket + >>> ws = websocket.WebSocket() + >>> ws.connect("ws://echo.websocket.org") + >>> ws.send("Hello, Server") + >>> ws.recv() + 'Hello, Server' + >>> ws.close() + + Parameters + ---------- + get_mask_key: func + a callable to produce new mask keys, see the set_mask_key + function's docstring for more details + sockopt: tuple + values for socket.setsockopt. + sockopt must be tuple and each element is argument of sock.setsockopt. + sslopt: dict + optional dict object for ssl socket option. + fire_cont_frame: bool + fire recv event for each cont frame. default is False + enable_multithread: bool + if set to True, lock send method. + skip_utf8_validation: bool + skip utf8 validation. + """ + + def __init__(self, get_mask_key=None, sockopt=None, sslopt=None, + fire_cont_frame=False, enable_multithread=False, + skip_utf8_validation=False, **_): + """ + Initialize WebSocket object. + + Parameters + ---------- + sslopt: specify ssl certification verification options + """ + self.sock_opt = sock_opt(sockopt, sslopt) + self.handshake_response = None + self.sock = None + + self.connected = False + self.get_mask_key = get_mask_key + # These buffer over the build-up of a single frame. + self.frame_buffer = frame_buffer(self._recv, skip_utf8_validation) + self.cont_frame = continuous_frame( + fire_cont_frame, skip_utf8_validation) + + if enable_multithread: + self.lock = threading.Lock() + self.readlock = threading.Lock() + else: + self.lock = NoLock() + self.readlock = NoLock() + + def __iter__(self): + """ + Allow iteration over websocket, implying sequential `recv` executions. + """ + while True: + yield self.recv() + + def __next__(self): + return self.recv() + + def next(self): + return self.__next__() + + def fileno(self): + return self.sock.fileno() + + def set_mask_key(self, func): + """ + Set function to create mask key. You can customize mask key generator. + Mainly, this is for testing purpose. + + Parameters + ---------- + func: func + callable object. the func takes 1 argument as integer. + The argument means length of mask key. + This func must return string(byte array), + which length is argument specified. + """ + self.get_mask_key = func + + def gettimeout(self): + """ + Get the websocket timeout (in seconds) as an int or float + + Returns + ---------- + timeout: int or float + returns timeout value (in seconds). This value could be either float/integer. + """ + return self.sock_opt.timeout + + def settimeout(self, timeout): + """ + Set the timeout to the websocket. + + Parameters + ---------- + timeout: int or float + timeout time (in seconds). This value could be either float/integer. + """ + self.sock_opt.timeout = timeout + if self.sock: + self.sock.settimeout(timeout) + + timeout = property(gettimeout, settimeout) + + def getsubprotocol(self): + """ + Get subprotocol + """ + if self.handshake_response: + return self.handshake_response.subprotocol + else: + return None + + subprotocol = property(getsubprotocol) + + def getstatus(self): + """ + Get handshake status + """ + if self.handshake_response: + return self.handshake_response.status + else: + return None + + status = property(getstatus) + + def getheaders(self): + """ + Get handshake response header + """ + if self.handshake_response: + return self.handshake_response.headers + else: + return None + + def is_ssl(self): + return isinstance(self.sock, ssl.SSLSocket) + + headers = property(getheaders) + + def connect(self, url, **options): + """ + Connect to url. url is websocket url scheme. + ie. ws://host:port/resource + You can customize using 'options'. + If you set "header" list object, you can set your own custom header. + + >>> ws = WebSocket() + >>> ws.connect("ws://echo.websocket.org/", + ... header=["User-Agent: MyProgram", + ... "x-custom: header"]) + + timeout: + socket timeout time. This value is an integer or float. + if you set None for this value, it means "use default_timeout value" + + Parameters + ---------- + options: + - header: list or dict + custom http header list or dict. + - cookie: str + cookie value. + - origin: str + custom origin url. + - suppress_origin: bool + suppress outputting origin header. + - host: str + custom host header string. + - http_proxy_host: + http proxy host name. + - http_proxy_port: + http proxy port. If not set, set to 80. + - http_no_proxy: + host names, which doesn't use proxy. + - http_proxy_auth: + http proxy auth information. tuple of username and password. default is None + - redirect_limit: + number of redirects to follow. + - subprotocols: + array of available sub protocols. default is None. + - socket: + pre-initialized stream socket. + """ + self.sock_opt.timeout = options.get('timeout', self.sock_opt.timeout) + self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options), + options.pop('socket', None)) + + try: + self.handshake_response = handshake(self.sock, *addrs, **options) + for attempt in range(options.pop('redirect_limit', 3)): + if self.handshake_response.status in SUPPORTED_REDIRECT_STATUSES: + url = self.handshake_response.headers['location'] + self.sock.close() + self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options), + options.pop('socket', None)) + self.handshake_response = handshake(self.sock, *addrs, **options) + self.connected = True + except: + if self.sock: + self.sock.close() + self.sock = None + raise + + def send(self, payload, opcode=ABNF.OPCODE_TEXT): + """ + Send the data as string. + + Parameters + ---------- + payload: + Payload must be utf-8 string or unicode, + if the opcode is OPCODE_TEXT. + Otherwise, it must be string(byte array) + opcode: + operation code to send. Please see OPCODE_XXX. + """ + + frame = ABNF.create_frame(payload, opcode) + return self.send_frame(frame) + + def send_frame(self, frame): + """ + Send the data frame. + + >>> ws = create_connection("ws://echo.websocket.org/") + >>> frame = ABNF.create_frame("Hello", ABNF.OPCODE_TEXT) + >>> ws.send_frame(frame) + >>> cont_frame = ABNF.create_frame("My name is ", ABNF.OPCODE_CONT, 0) + >>> ws.send_frame(frame) + >>> cont_frame = ABNF.create_frame("Foo Bar", ABNF.OPCODE_CONT, 1) + >>> ws.send_frame(frame) + + Parameters + ---------- + frame: + frame data created by ABNF.create_frame + """ + if self.get_mask_key: + frame.get_mask_key = self.get_mask_key + data = frame.format() + length = len(data) + if (isEnabledForTrace()): + trace("send: " + repr(data)) + + with self.lock: + while data: + l = self._send(data) + data = data[l:] + + return length + + def send_binary(self, payload): + return self.send(payload, ABNF.OPCODE_BINARY) + + def ping(self, payload=""): + """ + Send ping data. + + Parameters + ---------- + payload: + data payload to send server. + """ + if isinstance(payload, six.text_type): + payload = payload.encode("utf-8") + self.send(payload, ABNF.OPCODE_PING) + + def pong(self, payload=""): + """ + Send pong data. + + Parameters + ---------- + payload: + data payload to send server. + """ + if isinstance(payload, six.text_type): + payload = payload.encode("utf-8") + self.send(payload, ABNF.OPCODE_PONG) + + def recv(self): + """ + Receive string data(byte array) from the server. + + Returns + ---------- + data: string (byte array) value. + """ + with self.readlock: + opcode, data = self.recv_data() + if six.PY3 and opcode == ABNF.OPCODE_TEXT: + return data.decode("utf-8") + elif opcode == ABNF.OPCODE_TEXT or opcode == ABNF.OPCODE_BINARY: + return data + else: + return '' + + def recv_data(self, control_frame=False): + """ + Receive data with operation code. + + Parameters + ---------- + control_frame: bool + a boolean flag indicating whether to return control frame + data, defaults to False + + Returns + ------- + opcode, frame.data: tuple + tuple of operation code and string(byte array) value. + """ + opcode, frame = self.recv_data_frame(control_frame) + return opcode, frame.data + + def recv_data_frame(self, control_frame=False): + """ + Receive data with operation code. + + Parameters + ---------- + control_frame: bool + a boolean flag indicating whether to return control frame + data, defaults to False + + Returns + ------- + frame.opcode, frame: tuple + tuple of operation code and string(byte array) value. + """ + while True: + frame = self.recv_frame() + if not frame: + # handle error: + # 'NoneType' object has no attribute 'opcode' + raise WebSocketProtocolException( + "Not a valid frame %s" % frame) + elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT): + self.cont_frame.validate(frame) + self.cont_frame.add(frame) + + if self.cont_frame.is_fire(frame): + return self.cont_frame.extract(frame) + + elif frame.opcode == ABNF.OPCODE_CLOSE: + self.send_close() + return frame.opcode, frame + elif frame.opcode == ABNF.OPCODE_PING: + if len(frame.data) < 126: + self.pong(frame.data) + else: + raise WebSocketProtocolException( + "Ping message is too long") + if control_frame: + return frame.opcode, frame + elif frame.opcode == ABNF.OPCODE_PONG: + if control_frame: + return frame.opcode, frame + + def recv_frame(self): + """ + Receive data as frame from server. + + Returns + ------- + self.frame_buffer.recv_frame(): ABNF frame object + """ + return self.frame_buffer.recv_frame() + + def send_close(self, status=STATUS_NORMAL, reason=six.b("")): + """ + Send close data to the server. + + Parameters + ---------- + status: + status code to send. see STATUS_XXX. + reason: str or bytes + the reason to close. This must be string or bytes. + """ + if status < 0 or status >= ABNF.LENGTH_16: + raise ValueError("code is invalid range") + self.connected = False + self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) + + def close(self, status=STATUS_NORMAL, reason=six.b(""), timeout=3): + """ + Close Websocket object + + Parameters + ---------- + status: + status code to send. see STATUS_XXX. + reason: + the reason to close. This must be string. + timeout: int or float + timeout until receive a close frame. + If None, it will wait forever until receive a close frame. + """ + if self.connected: + if status < 0 or status >= ABNF.LENGTH_16: + raise ValueError("code is invalid range") + + try: + self.connected = False + self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) + sock_timeout = self.sock.gettimeout() + self.sock.settimeout(timeout) + start_time = time.time() + while timeout is None or time.time() - start_time < timeout: + try: + frame = self.recv_frame() + if frame.opcode != ABNF.OPCODE_CLOSE: + continue + if isEnabledForError(): + recv_status = struct.unpack("!H", frame.data[0:2])[0] + if recv_status >= 3000 and recv_status <= 4999: + debug("close status: " + repr(recv_status)) + elif recv_status != STATUS_NORMAL: + error("close status: " + repr(recv_status)) + break + except: + break + self.sock.settimeout(sock_timeout) + self.sock.shutdown(socket.SHUT_RDWR) + except: + pass + + self.shutdown() + + def abort(self): + """ + Low-level asynchronous abort, wakes up other threads that are waiting in recv_* + """ + if self.connected: + self.sock.shutdown(socket.SHUT_RDWR) + + def shutdown(self): + """ + close socket, immediately. + """ + if self.sock: + self.sock.close() + self.sock = None + self.connected = False + + def _send(self, data): + return send(self.sock, data) + + def _recv(self, bufsize): + try: + return recv(self.sock, bufsize) + except WebSocketConnectionClosedException: + if self.sock: + self.sock.close() + self.sock = None + self.connected = False + raise + + +def create_connection(url, timeout=None, class_=WebSocket, **options): + """ + Connect to url and return websocket object. + + Connect to url and return the WebSocket object. + Passing optional timeout parameter will set the timeout on the socket. + If no timeout is supplied, + the global default timeout setting returned by getdefaulttimeout() is used. + You can customize using 'options'. + If you set "header" list object, you can set your own custom header. + + >>> conn = create_connection("ws://echo.websocket.org/", + ... header=["User-Agent: MyProgram", + ... "x-custom: header"]) + + Parameters + ---------- + timeout: int or float + socket timeout time. This value could be either float/integer. + if you set None for this value, + it means "use default_timeout value" + class_: + class to instantiate when creating the connection. It has to implement + settimeout and connect. It's __init__ should be compatible with + WebSocket.__init__, i.e. accept all of it's kwargs. + options: + - header: list or dict + custom http header list or dict. + - cookie: str + cookie value. + - origin: str + custom origin url. + - suppress_origin: bool + suppress outputting origin header. + - host: + custom host header string. + - http_proxy_host: + http proxy host name. + - http_proxy_port: + http proxy port. If not set, set to 80. + - http_no_proxy: + host names, which doesn't use proxy. + - http_proxy_auth: + http proxy auth information. tuple of username and password. default is None + - enable_multithread: bool + enable lock for multithread. + - redirect_limit: + number of redirects to follow. + - sockopt: + socket options + - sslopt: + ssl option + - subprotocols: + array of available sub protocols. default is None. + - skip_utf8_validation: bool + skip utf8 validation. + - socket: + pre-initialized stream socket. + """ + sockopt = options.pop("sockopt", []) + sslopt = options.pop("sslopt", {}) + fire_cont_frame = options.pop("fire_cont_frame", False) + enable_multithread = options.pop("enable_multithread", False) + skip_utf8_validation = options.pop("skip_utf8_validation", False) + websock = class_(sockopt=sockopt, sslopt=sslopt, + fire_cont_frame=fire_cont_frame, + enable_multithread=enable_multithread, + skip_utf8_validation=skip_utf8_validation, **options) + websock.settimeout(timeout if timeout is not None else getdefaulttimeout()) + websock.connect(url, **options) + return websock diff --git a/openpype/vendor/python/python_2/websocket/_exceptions.py b/openpype/vendor/python/python_2/websocket/_exceptions.py new file mode 100644 index 0000000000..83c6e42b7d --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/_exceptions.py @@ -0,0 +1,86 @@ +""" +Define WebSocket exceptions +""" + +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" + + +class WebSocketException(Exception): + """ + WebSocket exception class. + """ + pass + + +class WebSocketProtocolException(WebSocketException): + """ + If the WebSocket protocol is invalid, this exception will be raised. + """ + pass + + +class WebSocketPayloadException(WebSocketException): + """ + If the WebSocket payload is invalid, this exception will be raised. + """ + pass + + +class WebSocketConnectionClosedException(WebSocketException): + """ + If remote host closed the connection or some network error happened, + this exception will be raised. + """ + pass + + +class WebSocketTimeoutException(WebSocketException): + """ + WebSocketTimeoutException will be raised at socket timeout during read/write data. + """ + pass + + +class WebSocketProxyException(WebSocketException): + """ + WebSocketProxyException will be raised when proxy error occurred. + """ + pass + + +class WebSocketBadStatusException(WebSocketException): + """ + WebSocketBadStatusException will be raised when we get bad handshake status code. + """ + + def __init__(self, message, status_code, status_message=None, resp_headers=None): + msg = message % (status_code, status_message) + super(WebSocketBadStatusException, self).__init__(msg) + self.status_code = status_code + self.resp_headers = resp_headers + + +class WebSocketAddressException(WebSocketException): + """ + If the websocket address info cannot be found, this exception will be raised. + """ + pass diff --git a/openpype/vendor/python/python_2/websocket/_handshake.py b/openpype/vendor/python/python_2/websocket/_handshake.py new file mode 100644 index 0000000000..c4d9d169da --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/_handshake.py @@ -0,0 +1,212 @@ +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" +import hashlib +import hmac +import os + +import six + +from ._cookiejar import SimpleCookieJar +from ._exceptions import * +from ._http import * +from ._logging import * +from ._socket import * + +if hasattr(six, 'PY3') and six.PY3: + from base64 import encodebytes as base64encode +else: + from base64 import encodestring as base64encode + +if hasattr(six, 'PY3') and six.PY3: + if hasattr(six, 'PY34') and six.PY34: + from http import client as HTTPStatus + else: + from http import HTTPStatus +else: + import httplib as HTTPStatus + +__all__ = ["handshake_response", "handshake", "SUPPORTED_REDIRECT_STATUSES"] + +if hasattr(hmac, "compare_digest"): + compare_digest = hmac.compare_digest +else: + def compare_digest(s1, s2): + return s1 == s2 + +# websocket supported version. +VERSION = 13 + +SUPPORTED_REDIRECT_STATUSES = (HTTPStatus.MOVED_PERMANENTLY, HTTPStatus.FOUND, HTTPStatus.SEE_OTHER,) +SUCCESS_STATUSES = SUPPORTED_REDIRECT_STATUSES + (HTTPStatus.SWITCHING_PROTOCOLS,) + +CookieJar = SimpleCookieJar() + + +class handshake_response(object): + + def __init__(self, status, headers, subprotocol): + self.status = status + self.headers = headers + self.subprotocol = subprotocol + CookieJar.add(headers.get("set-cookie")) + + +def handshake(sock, hostname, port, resource, **options): + headers, key = _get_handshake_headers(resource, hostname, port, options) + + header_str = "\r\n".join(headers) + send(sock, header_str) + dump("request header", header_str) + + status, resp = _get_resp_headers(sock) + if status in SUPPORTED_REDIRECT_STATUSES: + return handshake_response(status, resp, None) + success, subproto = _validate(resp, key, options.get("subprotocols")) + if not success: + raise WebSocketException("Invalid WebSocket Header") + + return handshake_response(status, resp, subproto) + + +def _pack_hostname(hostname): + # IPv6 address + if ':' in hostname: + return '[' + hostname + ']' + + return hostname + + +def _get_handshake_headers(resource, host, port, options): + headers = [ + "GET %s HTTP/1.1" % resource, + "Upgrade: websocket" + ] + if port == 80 or port == 443: + hostport = _pack_hostname(host) + else: + hostport = "%s:%d" % (_pack_hostname(host), port) + if "host" in options and options["host"] is not None: + headers.append("Host: %s" % options["host"]) + else: + headers.append("Host: %s" % hostport) + + if "suppress_origin" not in options or not options["suppress_origin"]: + if "origin" in options and options["origin"] is not None: + headers.append("Origin: %s" % options["origin"]) + else: + headers.append("Origin: http://%s" % hostport) + + key = _create_sec_websocket_key() + + # Append Sec-WebSocket-Key & Sec-WebSocket-Version if not manually specified + if 'header' not in options or 'Sec-WebSocket-Key' not in options['header']: + key = _create_sec_websocket_key() + headers.append("Sec-WebSocket-Key: %s" % key) + else: + key = options['header']['Sec-WebSocket-Key'] + + if 'header' not in options or 'Sec-WebSocket-Version' not in options['header']: + headers.append("Sec-WebSocket-Version: %s" % VERSION) + + if 'connection' not in options or options['connection'] is None: + headers.append('Connection: Upgrade') + else: + headers.append(options['connection']) + + subprotocols = options.get("subprotocols") + if subprotocols: + headers.append("Sec-WebSocket-Protocol: %s" % ",".join(subprotocols)) + + if "header" in options: + header = options["header"] + if isinstance(header, dict): + header = [ + ": ".join([k, v]) + for k, v in header.items() + if v is not None + ] + headers.extend(header) + + server_cookie = CookieJar.get(host) + client_cookie = options.get("cookie", None) + + cookie = "; ".join(filter(None, [server_cookie, client_cookie])) + + if cookie: + headers.append("Cookie: %s" % cookie) + + headers.append("") + headers.append("") + + return headers, key + + +def _get_resp_headers(sock, success_statuses=SUCCESS_STATUSES): + status, resp_headers, status_message = read_headers(sock) + if status not in success_statuses: + raise WebSocketBadStatusException("Handshake status %d %s", status, status_message, resp_headers) + return status, resp_headers + + +_HEADERS_TO_CHECK = { + "upgrade": "websocket", + "connection": "upgrade", +} + + +def _validate(headers, key, subprotocols): + subproto = None + for k, v in _HEADERS_TO_CHECK.items(): + r = headers.get(k, None) + if not r: + return False, None + r = [x.strip().lower() for x in r.split(',')] + if v not in r: + return False, None + + if subprotocols: + subproto = headers.get("sec-websocket-protocol", None) + if not subproto or subproto.lower() not in [s.lower() for s in subprotocols]: + error("Invalid subprotocol: " + str(subprotocols)) + return False, None + subproto = subproto.lower() + + result = headers.get("sec-websocket-accept", None) + if not result: + return False, None + result = result.lower() + + if isinstance(result, six.text_type): + result = result.encode('utf-8') + + value = (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").encode('utf-8') + hashed = base64encode(hashlib.sha1(value).digest()).strip().lower() + success = compare_digest(hashed, result) + + if success: + return True, subproto + else: + return False, None + + +def _create_sec_websocket_key(): + randomness = os.urandom(16) + return base64encode(randomness).decode('utf-8').strip() diff --git a/openpype/vendor/python/python_2/websocket/_http.py b/openpype/vendor/python/python_2/websocket/_http.py new file mode 100644 index 0000000000..b0dad48ce0 --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/_http.py @@ -0,0 +1,335 @@ +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" +import errno +import os +import socket +import sys + +import six + +from ._exceptions import * +from ._logging import * +from ._socket import* +from ._ssl_compat import * +from ._url import * + +if six.PY3: + from base64 import encodebytes as base64encode +else: + from base64 import encodestring as base64encode + +__all__ = ["proxy_info", "connect", "read_headers"] + +try: + import socks + ProxyConnectionError = socks.ProxyConnectionError + HAS_PYSOCKS = True +except: + class ProxyConnectionError(BaseException): + pass + HAS_PYSOCKS = False + + +class proxy_info(object): + + def __init__(self, **options): + self.type = options.get("proxy_type") or "http" + if not(self.type in ['http', 'socks4', 'socks5', 'socks5h']): + raise ValueError("proxy_type must be 'http', 'socks4', 'socks5' or 'socks5h'") + self.host = options.get("http_proxy_host", None) + if self.host: + self.port = options.get("http_proxy_port", 0) + self.auth = options.get("http_proxy_auth", None) + self.no_proxy = options.get("http_no_proxy", None) + else: + self.port = 0 + self.auth = None + self.no_proxy = None + + +def _open_proxied_socket(url, options, proxy): + hostname, port, resource, is_secure = parse_url(url) + + if not HAS_PYSOCKS: + raise WebSocketException("PySocks module not found.") + + ptype = socks.SOCKS5 + rdns = False + if proxy.type == "socks4": + ptype = socks.SOCKS4 + if proxy.type == "http": + ptype = socks.HTTP + if proxy.type[-1] == "h": + rdns = True + + sock = socks.create_connection( + (hostname, port), + proxy_type=ptype, + proxy_addr=proxy.host, + proxy_port=proxy.port, + proxy_rdns=rdns, + proxy_username=proxy.auth[0] if proxy.auth else None, + proxy_password=proxy.auth[1] if proxy.auth else None, + timeout=options.timeout, + socket_options=DEFAULT_SOCKET_OPTION + options.sockopt + ) + + if is_secure: + if HAVE_SSL: + sock = _ssl_socket(sock, options.sslopt, hostname) + else: + raise WebSocketException("SSL not available.") + + return sock, (hostname, port, resource) + + +def connect(url, options, proxy, socket): + if proxy.host and not socket and not (proxy.type == 'http'): + return _open_proxied_socket(url, options, proxy) + + hostname, port, resource, is_secure = parse_url(url) + + if socket: + return socket, (hostname, port, resource) + + addrinfo_list, need_tunnel, auth = _get_addrinfo_list( + hostname, port, is_secure, proxy) + if not addrinfo_list: + raise WebSocketException( + "Host not found.: " + hostname + ":" + str(port)) + + sock = None + try: + sock = _open_socket(addrinfo_list, options.sockopt, options.timeout) + if need_tunnel: + sock = _tunnel(sock, hostname, port, auth) + + if is_secure: + if HAVE_SSL: + sock = _ssl_socket(sock, options.sslopt, hostname) + else: + raise WebSocketException("SSL not available.") + + return sock, (hostname, port, resource) + except: + if sock: + sock.close() + raise + + +def _get_addrinfo_list(hostname, port, is_secure, proxy): + phost, pport, pauth = get_proxy_info( + hostname, is_secure, proxy.host, proxy.port, proxy.auth, proxy.no_proxy) + try: + # when running on windows 10, getaddrinfo without socktype returns a socktype 0. + # This generates an error exception: `_on_error: exception Socket type must be stream or datagram, not 0` + # or `OSError: [Errno 22] Invalid argument` when creating socket. Force the socket type to SOCK_STREAM. + if not phost: + addrinfo_list = socket.getaddrinfo( + hostname, port, 0, socket.SOCK_STREAM, socket.SOL_TCP) + return addrinfo_list, False, None + else: + pport = pport and pport or 80 + # when running on windows 10, the getaddrinfo used above + # returns a socktype 0. This generates an error exception: + # _on_error: exception Socket type must be stream or datagram, not 0 + # Force the socket type to SOCK_STREAM + addrinfo_list = socket.getaddrinfo(phost, pport, 0, socket.SOCK_STREAM, socket.SOL_TCP) + return addrinfo_list, True, pauth + except socket.gaierror as e: + raise WebSocketAddressException(e) + + +def _open_socket(addrinfo_list, sockopt, timeout): + err = None + for addrinfo in addrinfo_list: + family, socktype, proto = addrinfo[:3] + sock = socket.socket(family, socktype, proto) + sock.settimeout(timeout) + for opts in DEFAULT_SOCKET_OPTION: + sock.setsockopt(*opts) + for opts in sockopt: + sock.setsockopt(*opts) + + address = addrinfo[4] + err = None + while not err: + try: + sock.connect(address) + except ProxyConnectionError as error: + err = WebSocketProxyException(str(error)) + err.remote_ip = str(address[0]) + continue + except socket.error as error: + error.remote_ip = str(address[0]) + try: + eConnRefused = (errno.ECONNREFUSED, errno.WSAECONNREFUSED) + except: + eConnRefused = (errno.ECONNREFUSED, ) + if error.errno == errno.EINTR: + continue + elif error.errno in eConnRefused: + err = error + continue + else: + raise error + else: + break + else: + continue + break + else: + if err: + raise err + + return sock + + +def _can_use_sni(): + return six.PY2 and sys.version_info >= (2, 7, 9) or sys.version_info >= (3, 2) + + +def _wrap_sni_socket(sock, sslopt, hostname, check_hostname): + context = ssl.SSLContext(sslopt.get('ssl_version', ssl.PROTOCOL_SSLv23)) + + if sslopt.get('cert_reqs', ssl.CERT_NONE) != ssl.CERT_NONE: + cafile = sslopt.get('ca_certs', None) + capath = sslopt.get('ca_cert_path', None) + if cafile or capath: + context.load_verify_locations(cafile=cafile, capath=capath) + elif hasattr(context, 'load_default_certs'): + context.load_default_certs(ssl.Purpose.SERVER_AUTH) + if sslopt.get('certfile', None): + context.load_cert_chain( + sslopt['certfile'], + sslopt.get('keyfile', None), + sslopt.get('password', None), + ) + # see + # https://github.com/liris/websocket-client/commit/b96a2e8fa765753e82eea531adb19716b52ca3ca#commitcomment-10803153 + context.verify_mode = sslopt['cert_reqs'] + if HAVE_CONTEXT_CHECK_HOSTNAME: + context.check_hostname = check_hostname + if 'ciphers' in sslopt: + context.set_ciphers(sslopt['ciphers']) + if 'cert_chain' in sslopt: + certfile, keyfile, password = sslopt['cert_chain'] + context.load_cert_chain(certfile, keyfile, password) + if 'ecdh_curve' in sslopt: + context.set_ecdh_curve(sslopt['ecdh_curve']) + + return context.wrap_socket( + sock, + do_handshake_on_connect=sslopt.get('do_handshake_on_connect', True), + suppress_ragged_eofs=sslopt.get('suppress_ragged_eofs', True), + server_hostname=hostname, + ) + + +def _ssl_socket(sock, user_sslopt, hostname): + sslopt = dict(cert_reqs=ssl.CERT_REQUIRED) + sslopt.update(user_sslopt) + + certPath = os.environ.get('WEBSOCKET_CLIENT_CA_BUNDLE') + if certPath and os.path.isfile(certPath) \ + and user_sslopt.get('ca_certs', None) is None \ + and user_sslopt.get('ca_cert', None) is None: + sslopt['ca_certs'] = certPath + elif certPath and os.path.isdir(certPath) \ + and user_sslopt.get('ca_cert_path', None) is None: + sslopt['ca_cert_path'] = certPath + + check_hostname = sslopt["cert_reqs"] != ssl.CERT_NONE and sslopt.pop( + 'check_hostname', True) + + if _can_use_sni(): + sock = _wrap_sni_socket(sock, sslopt, hostname, check_hostname) + else: + sslopt.pop('check_hostname', True) + sock = ssl.wrap_socket(sock, **sslopt) + + if not HAVE_CONTEXT_CHECK_HOSTNAME and check_hostname: + match_hostname(sock.getpeercert(), hostname) + + return sock + + +def _tunnel(sock, host, port, auth): + debug("Connecting proxy...") + connect_header = "CONNECT %s:%d HTTP/1.1\r\n" % (host, port) + connect_header += "Host: %s:%d\r\n" % (host, port) + + # TODO: support digest auth. + if auth and auth[0]: + auth_str = auth[0] + if auth[1]: + auth_str += ":" + auth[1] + encoded_str = base64encode(auth_str.encode()).strip().decode().replace('\n', '') + connect_header += "Proxy-Authorization: Basic %s\r\n" % encoded_str + connect_header += "\r\n" + dump("request header", connect_header) + + send(sock, connect_header) + + try: + status, resp_headers, status_message = read_headers(sock) + except Exception as e: + raise WebSocketProxyException(str(e)) + + if status != 200: + raise WebSocketProxyException( + "failed CONNECT via proxy status: %r" % status) + + return sock + + +def read_headers(sock): + status = None + status_message = None + headers = {} + trace("--- response header ---") + + while True: + line = recv_line(sock) + line = line.decode('utf-8').strip() + if not line: + break + trace(line) + if not status: + + status_info = line.split(" ", 2) + status = int(status_info[1]) + if len(status_info) > 2: + status_message = status_info[2] + else: + kv = line.split(":", 1) + if len(kv) == 2: + key, value = kv + if key.lower() == "set-cookie" and headers.get("set-cookie"): + headers["set-cookie"] = headers.get("set-cookie") + "; " + value.strip() + else: + headers[key.lower()] = value.strip() + else: + raise WebSocketException("Invalid header") + + trace("-----------------------") + + return status, headers, status_message diff --git a/openpype/vendor/python/python_2/websocket/_logging.py b/openpype/vendor/python/python_2/websocket/_logging.py new file mode 100644 index 0000000000..07d9009031 --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/_logging.py @@ -0,0 +1,92 @@ +""" + +""" + +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" +import logging + +_logger = logging.getLogger('websocket') +try: + from logging import NullHandler +except ImportError: + class NullHandler(logging.Handler): + def emit(self, record): + pass + +_logger.addHandler(NullHandler()) + +_traceEnabled = False + +__all__ = ["enableTrace", "dump", "error", "warning", "debug", "trace", + "isEnabledForError", "isEnabledForDebug", "isEnabledForTrace"] + + +def enableTrace(traceable, handler=logging.StreamHandler()): + """ + Turn on/off the traceability. + + Parameters + ---------- + traceable: bool + If set to True, traceability is enabled. + """ + global _traceEnabled + _traceEnabled = traceable + if traceable: + _logger.addHandler(handler) + _logger.setLevel(logging.DEBUG) + + +def dump(title, message): + if _traceEnabled: + _logger.debug("--- " + title + " ---") + _logger.debug(message) + _logger.debug("-----------------------") + + +def error(msg): + _logger.error(msg) + + +def warning(msg): + _logger.warning(msg) + + +def debug(msg): + _logger.debug(msg) + + +def trace(msg): + if _traceEnabled: + _logger.debug(msg) + + +def isEnabledForError(): + return _logger.isEnabledFor(logging.ERROR) + + +def isEnabledForDebug(): + return _logger.isEnabledFor(logging.DEBUG) + + +def isEnabledForTrace(): + return _traceEnabled diff --git a/openpype/vendor/python/python_2/websocket/_socket.py b/openpype/vendor/python/python_2/websocket/_socket.py new file mode 100644 index 0000000000..2c383ed4d3 --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/_socket.py @@ -0,0 +1,176 @@ +""" + +""" + +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" +import errno +import select +import socket + +import six + +from ._exceptions import * +from ._ssl_compat import * +from ._utils import * + +DEFAULT_SOCKET_OPTION = [(socket.SOL_TCP, socket.TCP_NODELAY, 1)] +if hasattr(socket, "SO_KEEPALIVE"): + DEFAULT_SOCKET_OPTION.append((socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)) +if hasattr(socket, "TCP_KEEPIDLE"): + DEFAULT_SOCKET_OPTION.append((socket.SOL_TCP, socket.TCP_KEEPIDLE, 30)) +if hasattr(socket, "TCP_KEEPINTVL"): + DEFAULT_SOCKET_OPTION.append((socket.SOL_TCP, socket.TCP_KEEPINTVL, 10)) +if hasattr(socket, "TCP_KEEPCNT"): + DEFAULT_SOCKET_OPTION.append((socket.SOL_TCP, socket.TCP_KEEPCNT, 3)) + +_default_timeout = None + +__all__ = ["DEFAULT_SOCKET_OPTION", "sock_opt", "setdefaulttimeout", "getdefaulttimeout", + "recv", "recv_line", "send"] + + +class sock_opt(object): + + def __init__(self, sockopt, sslopt): + if sockopt is None: + sockopt = [] + if sslopt is None: + sslopt = {} + self.sockopt = sockopt + self.sslopt = sslopt + self.timeout = None + + +def setdefaulttimeout(timeout): + """ + Set the global timeout setting to connect. + + Parameters + ---------- + timeout: int or float + default socket timeout time (in seconds) + """ + global _default_timeout + _default_timeout = timeout + + +def getdefaulttimeout(): + """ + Get default timeout + + Returns + ---------- + _default_timeout: int or float + Return the global timeout setting (in seconds) to connect. + """ + return _default_timeout + + +def recv(sock, bufsize): + if not sock: + raise WebSocketConnectionClosedException("socket is already closed.") + + def _recv(): + try: + return sock.recv(bufsize) + except SSLWantReadError: + pass + except socket.error as exc: + error_code = extract_error_code(exc) + if error_code is None: + raise + if error_code != errno.EAGAIN or error_code != errno.EWOULDBLOCK: + raise + + r, w, e = select.select((sock, ), (), (), sock.gettimeout()) + if r: + return sock.recv(bufsize) + + try: + if sock.gettimeout() == 0: + bytes_ = sock.recv(bufsize) + else: + bytes_ = _recv() + except socket.timeout as e: + message = extract_err_message(e) + raise WebSocketTimeoutException(message) + except SSLError as e: + message = extract_err_message(e) + if isinstance(message, str) and 'timed out' in message: + raise WebSocketTimeoutException(message) + else: + raise + + if not bytes_: + raise WebSocketConnectionClosedException( + "Connection is already closed.") + + return bytes_ + + +def recv_line(sock): + line = [] + while True: + c = recv(sock, 1) + line.append(c) + if c == six.b("\n"): + break + return six.b("").join(line) + + +def send(sock, data): + if isinstance(data, six.text_type): + data = data.encode('utf-8') + + if not sock: + raise WebSocketConnectionClosedException("socket is already closed.") + + def _send(): + try: + return sock.send(data) + except SSLWantWriteError: + pass + except socket.error as exc: + error_code = extract_error_code(exc) + if error_code is None: + raise + if error_code != errno.EAGAIN or error_code != errno.EWOULDBLOCK: + raise + + r, w, e = select.select((), (sock, ), (), sock.gettimeout()) + if w: + return sock.send(data) + + try: + if sock.gettimeout() == 0: + return sock.send(data) + else: + return _send() + except socket.timeout as e: + message = extract_err_message(e) + raise WebSocketTimeoutException(message) + except Exception as e: + message = extract_err_message(e) + if isinstance(message, str) and "timed out" in message: + raise WebSocketTimeoutException(message) + else: + raise diff --git a/openpype/vendor/python/python_2/websocket/_ssl_compat.py b/openpype/vendor/python/python_2/websocket/_ssl_compat.py new file mode 100644 index 0000000000..9e201ddf00 --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/_ssl_compat.py @@ -0,0 +1,53 @@ +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" +__all__ = ["HAVE_SSL", "ssl", "SSLError", "SSLWantReadError", "SSLWantWriteError"] + +try: + import ssl + from ssl import SSLError + from ssl import SSLWantReadError + from ssl import SSLWantWriteError + if hasattr(ssl, 'SSLContext') and hasattr(ssl.SSLContext, 'check_hostname'): + HAVE_CONTEXT_CHECK_HOSTNAME = True + else: + HAVE_CONTEXT_CHECK_HOSTNAME = False + if hasattr(ssl, "match_hostname"): + from ssl import match_hostname + else: + from backports.ssl_match_hostname import match_hostname + __all__.append("match_hostname") + __all__.append("HAVE_CONTEXT_CHECK_HOSTNAME") + + HAVE_SSL = True +except ImportError: + # dummy class of SSLError for ssl none-support environment. + class SSLError(Exception): + pass + + class SSLWantReadError(Exception): + pass + + class SSLWantWriteError(Exception): + pass + + ssl = None + + HAVE_SSL = False diff --git a/openpype/vendor/python/python_2/websocket/_url.py b/openpype/vendor/python/python_2/websocket/_url.py new file mode 100644 index 0000000000..92ff939e39 --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/_url.py @@ -0,0 +1,178 @@ +""" + +""" +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" + +import os +import socket +import struct + +from six.moves.urllib.parse import urlparse + + +__all__ = ["parse_url", "get_proxy_info"] + + +def parse_url(url): + """ + parse url and the result is tuple of + (hostname, port, resource path and the flag of secure mode) + + Parameters + ---------- + url: str + url string. + """ + if ":" not in url: + raise ValueError("url is invalid") + + scheme, url = url.split(":", 1) + + parsed = urlparse(url, scheme="http") + if parsed.hostname: + hostname = parsed.hostname + else: + raise ValueError("hostname is invalid") + port = 0 + if parsed.port: + port = parsed.port + + is_secure = False + if scheme == "ws": + if not port: + port = 80 + elif scheme == "wss": + is_secure = True + if not port: + port = 443 + else: + raise ValueError("scheme %s is invalid" % scheme) + + if parsed.path: + resource = parsed.path + else: + resource = "/" + + if parsed.query: + resource += "?" + parsed.query + + return hostname, port, resource, is_secure + + +DEFAULT_NO_PROXY_HOST = ["localhost", "127.0.0.1"] + + +def _is_ip_address(addr): + try: + socket.inet_aton(addr) + except socket.error: + return False + else: + return True + + +def _is_subnet_address(hostname): + try: + addr, netmask = hostname.split("/") + return _is_ip_address(addr) and 0 <= int(netmask) < 32 + except ValueError: + return False + + +def _is_address_in_network(ip, net): + ipaddr = struct.unpack('!I', socket.inet_aton(ip))[0] + netaddr, netmask = net.split('/') + netaddr = struct.unpack('!I', socket.inet_aton(netaddr))[0] + + netmask = (0xFFFFFFFF << (32 - int(netmask))) & 0xFFFFFFFF + return ipaddr & netmask == netaddr + + +def _is_no_proxy_host(hostname, no_proxy): + if not no_proxy: + v = os.environ.get("no_proxy", "").replace(" ", "") + if v: + no_proxy = v.split(",") + if not no_proxy: + no_proxy = DEFAULT_NO_PROXY_HOST + + if '*' in no_proxy: + return True + if hostname in no_proxy: + return True + if _is_ip_address(hostname): + return any([_is_address_in_network(hostname, subnet) for subnet in no_proxy if _is_subnet_address(subnet)]) + for domain in [domain for domain in no_proxy if domain.startswith('.')]: + if hostname.endswith(domain): + return True + return False + + +def get_proxy_info( + hostname, is_secure, proxy_host=None, proxy_port=0, proxy_auth=None, + no_proxy=None, proxy_type='http'): + """ + Try to retrieve proxy host and port from environment + if not provided in options. + Result is (proxy_host, proxy_port, proxy_auth). + proxy_auth is tuple of username and password + of proxy authentication information. + + Parameters + ---------- + hostname: + websocket server name. + is_secure: + is the connection secure? (wss) looks for "https_proxy" in env + before falling back to "http_proxy" + options: + - http_proxy_host: + http proxy host name. + - http_proxy_port: + http proxy port. + - http_no_proxy: + host names, which doesn't use proxy. + - http_proxy_auth: + http proxy auth information. tuple of username and password. default is None + - proxy_type: + if set to "socks5" PySocks wrapper will be used in place of a http proxy. default is "http" + """ + if _is_no_proxy_host(hostname, no_proxy): + return None, 0, None + + if proxy_host: + port = proxy_port + auth = proxy_auth + return proxy_host, port, auth + + env_keys = ["http_proxy"] + if is_secure: + env_keys.insert(0, "https_proxy") + + for key in env_keys: + value = os.environ.get(key, None) + if value: + proxy = urlparse(value) + auth = (proxy.username, proxy.password) if proxy.username else None + return proxy.hostname, proxy.port, auth + + return None, 0, None diff --git a/openpype/vendor/python/python_2/websocket/_utils.py b/openpype/vendor/python/python_2/websocket/_utils.py new file mode 100644 index 0000000000..0072bce8ac --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/_utils.py @@ -0,0 +1,110 @@ +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" +import six + +__all__ = ["NoLock", "validate_utf8", "extract_err_message", "extract_error_code"] + + +class NoLock(object): + + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_value, traceback): + pass + + +try: + # If wsaccel is available we use compiled routines to validate UTF-8 + # strings. + from wsaccel.utf8validator import Utf8Validator + + def _validate_utf8(utfbytes): + return Utf8Validator().validate(utfbytes)[0] + +except ImportError: + # UTF-8 validator + # python implementation of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + + _UTF8_ACCEPT = 0 + _UTF8_REJECT = 12 + + _UTF8D = [ + # The first part of the table maps bytes to character classes that + # to reduce the size of the transition table and create bitmasks. + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + # The second part is a transition table that maps a combination + # of a state of the automaton and a character class to a state. + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, ] + + def _decode(state, codep, ch): + tp = _UTF8D[ch] + + codep = (ch & 0x3f) | (codep << 6) if ( + state != _UTF8_ACCEPT) else (0xff >> tp) & ch + state = _UTF8D[256 + state + tp] + + return state, codep + + def _validate_utf8(utfbytes): + state = _UTF8_ACCEPT + codep = 0 + for i in utfbytes: + if six.PY2: + i = ord(i) + state, codep = _decode(state, codep, i) + if state == _UTF8_REJECT: + return False + + return True + + +def validate_utf8(utfbytes): + """ + validate utf8 byte string. + utfbytes: utf byte string to check. + return value: if valid utf8 string, return true. Otherwise, return false. + """ + return _validate_utf8(utfbytes) + + +def extract_err_message(exception): + if exception.args: + return exception.args[0] + else: + return None + + +def extract_error_code(exception): + if exception.args and len(exception.args) > 1: + return exception.args[0] if isinstance(exception.args[0], int) else None diff --git a/openpype/vendor/python/python_2/websocket/tests/__init__.py b/openpype/vendor/python/python_2/websocket/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/vendor/python/python_2/websocket/tests/data/header01.txt b/openpype/vendor/python/python_2/websocket/tests/data/header01.txt new file mode 100644 index 0000000000..d44d24c205 --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/tests/data/header01.txt @@ -0,0 +1,6 @@ +HTTP/1.1 101 WebSocket Protocol Handshake +Connection: Upgrade +Upgrade: WebSocket +Sec-WebSocket-Accept: Kxep+hNu9n51529fGidYu7a3wO0= +some_header: something + diff --git a/openpype/vendor/python/python_2/websocket/tests/data/header02.txt b/openpype/vendor/python/python_2/websocket/tests/data/header02.txt new file mode 100644 index 0000000000..f481de928a --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/tests/data/header02.txt @@ -0,0 +1,6 @@ +HTTP/1.1 101 WebSocket Protocol Handshake +Connection: Upgrade +Upgrade WebSocket +Sec-WebSocket-Accept: Kxep+hNu9n51529fGidYu7a3wO0= +some_header: something + diff --git a/openpype/vendor/python/python_2/websocket/tests/data/header03.txt b/openpype/vendor/python/python_2/websocket/tests/data/header03.txt new file mode 100644 index 0000000000..012b7d18dd --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/tests/data/header03.txt @@ -0,0 +1,6 @@ +HTTP/1.1 101 WebSocket Protocol Handshake +Connection: Upgrade, Keep-Alive +Upgrade: WebSocket +Sec-WebSocket-Accept: Kxep+hNu9n51529fGidYu7a3wO0= +some_header: something + diff --git a/openpype/vendor/python/python_2/websocket/tests/test_abnf.py b/openpype/vendor/python/python_2/websocket/tests/test_abnf.py new file mode 100644 index 0000000000..acce020682 --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/tests/test_abnf.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" + +import os +import websocket as ws +from websocket._abnf import * +import sys +sys.path[0:0] = [""] + +if sys.version_info[0] == 2 and sys.version_info[1] < 7: + import unittest2 as unittest +else: + import unittest + + +class ABNFTest(unittest.TestCase): + + def testInit(self): + a = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING) + self.assertEqual(a.fin, 0) + self.assertEqual(a.rsv1, 0) + self.assertEqual(a.rsv2, 0) + self.assertEqual(a.rsv3, 0) + self.assertEqual(a.opcode, 9) + self.assertEqual(a.data, '') + a_bad = ABNF(0,1,0,0, opcode=77) + self.assertEqual(a_bad.rsv1, 1) + self.assertEqual(a_bad.opcode, 77) + + def testValidate(self): + a = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING) + self.assertRaises(ws.WebSocketProtocolException, a.validate) + a_bad = ABNF(0,1,0,0, opcode=77) + self.assertRaises(ws.WebSocketProtocolException, a_bad.validate) + a_close = ABNF(0,1,0,0, opcode=ABNF.OPCODE_CLOSE, data="abcdefgh1234567890abcdefgh1234567890abcdefgh1234567890abcdefgh1234567890") + self.assertRaises(ws.WebSocketProtocolException, a_close.validate) + +# This caused an error in the Python 2.7 Github Actions build +# Uncomment test case when Python 2 support no longer wanted +# def testMask(self): +# ab = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING) +# bytes_val = bytes("aaaa", 'utf-8') +# self.assertEqual(ab._get_masked(bytes_val), bytes_val) + + def testFrameBuffer(self): + fb = frame_buffer(0, True) + self.assertEqual(fb.recv, 0) + self.assertEqual(fb.skip_utf8_validation, True) + fb.clear + self.assertEqual(fb.header, None) + self.assertEqual(fb.length, None) + self.assertEqual(fb.mask, None) + self.assertEqual(fb.has_mask(), False) + + +if __name__ == "__main__": + unittest.main() diff --git a/openpype/vendor/python/python_2/websocket/tests/test_app.py b/openpype/vendor/python/python_2/websocket/tests/test_app.py new file mode 100644 index 0000000000..e5a739008e --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/tests/test_app.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +# +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" + +import os +import os.path +import websocket as ws +import sys +sys.path[0:0] = [""] + +try: + import ssl +except ImportError: + HAVE_SSL = False + +if sys.version_info[0] == 2 and sys.version_info[1] < 7: + import unittest2 as unittest +else: + import unittest + +# Skip test to access the internet. +TEST_WITH_INTERNET = os.environ.get('TEST_WITH_INTERNET', '0') == '1' +TRACEABLE = True + + +class WebSocketAppTest(unittest.TestCase): + + class NotSetYet(object): + """ A marker class for signalling that a value hasn't been set yet. + """ + + def setUp(self): + ws.enableTrace(TRACEABLE) + + WebSocketAppTest.keep_running_open = WebSocketAppTest.NotSetYet() + WebSocketAppTest.keep_running_close = WebSocketAppTest.NotSetYet() + WebSocketAppTest.get_mask_key_id = WebSocketAppTest.NotSetYet() + + def tearDown(self): + WebSocketAppTest.keep_running_open = WebSocketAppTest.NotSetYet() + WebSocketAppTest.keep_running_close = WebSocketAppTest.NotSetYet() + WebSocketAppTest.get_mask_key_id = WebSocketAppTest.NotSetYet() + + @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + def testKeepRunning(self): + """ A WebSocketApp should keep running as long as its self.keep_running + is not False (in the boolean context). + """ + + def on_open(self, *args, **kwargs): + """ Set the keep_running flag for later inspection and immediately + close the connection. + """ + WebSocketAppTest.keep_running_open = self.keep_running + + self.close() + + def on_close(self, *args, **kwargs): + """ Set the keep_running flag for the test to use. + """ + WebSocketAppTest.keep_running_close = self.keep_running + + app = ws.WebSocketApp('ws://echo.websocket.org/', on_open=on_open, on_close=on_close) + app.run_forever() + + # if numpy is installed, this assertion fail + # self.assertFalse(isinstance(WebSocketAppTest.keep_running_open, + # WebSocketAppTest.NotSetYet)) + + # self.assertFalse(isinstance(WebSocketAppTest.keep_running_close, + # WebSocketAppTest.NotSetYet)) + + # self.assertEqual(True, WebSocketAppTest.keep_running_open) + # self.assertEqual(False, WebSocketAppTest.keep_running_close) + + @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + def testSockMaskKey(self): + """ A WebSocketApp should forward the received mask_key function down + to the actual socket. + """ + + def my_mask_key_func(): + pass + + def on_open(self, *args, **kwargs): + """ Set the value so the test can use it later on and immediately + close the connection. + """ + WebSocketAppTest.get_mask_key_id = id(self.get_mask_key) + self.close() + + app = ws.WebSocketApp('ws://echo.websocket.org/', on_open=on_open, get_mask_key=my_mask_key_func) + app.run_forever() + + # if numpy is installed, this assertion fail + # Note: We can't use 'is' for comparing the functions directly, need to use 'id'. + # self.assertEqual(WebSocketAppTest.get_mask_key_id, id(my_mask_key_func)) + + @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + def testPingInterval(self): + """ A WebSocketApp should ping regularly + """ + + def on_ping(app, msg): + print("Got a ping!") + app.close() + + def on_pong(app, msg): + print("Got a pong! No need to respond") + app.close() + + app = ws.WebSocketApp('wss://api-pub.bitfinex.com/ws/1', on_ping=on_ping, on_pong=on_pong) + app.run_forever(ping_interval=2, ping_timeout=1) # , sslopt={"cert_reqs": ssl.CERT_NONE} + self.assertRaises(ws.WebSocketException, app.run_forever, ping_interval=2, ping_timeout=3, sslopt={"cert_reqs": ssl.CERT_NONE}) + + +if __name__ == "__main__": + unittest.main() diff --git a/openpype/vendor/python/python_2/websocket/tests/test_cookiejar.py b/openpype/vendor/python/python_2/websocket/tests/test_cookiejar.py new file mode 100644 index 0000000000..fc66e58b0e --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/tests/test_cookiejar.py @@ -0,0 +1,117 @@ +""" + +""" + +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" +import unittest + +from websocket._cookiejar import SimpleCookieJar + + +class CookieJarTest(unittest.TestCase): + def testAdd(self): + cookie_jar = SimpleCookieJar() + cookie_jar.add("") + self.assertFalse(cookie_jar.jar, "Cookie with no domain should not be added to the jar") + + cookie_jar = SimpleCookieJar() + cookie_jar.add("a=b") + self.assertFalse(cookie_jar.jar, "Cookie with no domain should not be added to the jar") + + cookie_jar = SimpleCookieJar() + cookie_jar.add("a=b; domain=.abc") + self.assertTrue(".abc" in cookie_jar.jar) + + cookie_jar = SimpleCookieJar() + cookie_jar.add("a=b; domain=abc") + self.assertTrue(".abc" in cookie_jar.jar) + self.assertTrue("abc" not in cookie_jar.jar) + + cookie_jar = SimpleCookieJar() + cookie_jar.add("a=b; c=d; domain=abc") + self.assertEqual(cookie_jar.get("abc"), "a=b; c=d") + + cookie_jar = SimpleCookieJar() + cookie_jar.add("a=b; c=d; domain=abc") + cookie_jar.add("e=f; domain=abc") + self.assertEqual(cookie_jar.get("abc"), "a=b; c=d; e=f") + + cookie_jar = SimpleCookieJar() + cookie_jar.add("a=b; c=d; domain=abc") + cookie_jar.add("e=f; domain=.abc") + self.assertEqual(cookie_jar.get("abc"), "a=b; c=d; e=f") + + cookie_jar = SimpleCookieJar() + cookie_jar.add("a=b; c=d; domain=abc") + cookie_jar.add("e=f; domain=xyz") + self.assertEqual(cookie_jar.get("abc"), "a=b; c=d") + self.assertEqual(cookie_jar.get("xyz"), "e=f") + self.assertEqual(cookie_jar.get("something"), "") + + def testSet(self): + cookie_jar = SimpleCookieJar() + cookie_jar.set("a=b") + self.assertFalse(cookie_jar.jar, "Cookie with no domain should not be added to the jar") + + cookie_jar = SimpleCookieJar() + cookie_jar.set("a=b; domain=.abc") + self.assertTrue(".abc" in cookie_jar.jar) + + cookie_jar = SimpleCookieJar() + cookie_jar.set("a=b; domain=abc") + self.assertTrue(".abc" in cookie_jar.jar) + self.assertTrue("abc" not in cookie_jar.jar) + + cookie_jar = SimpleCookieJar() + cookie_jar.set("a=b; c=d; domain=abc") + self.assertEqual(cookie_jar.get("abc"), "a=b; c=d") + + cookie_jar = SimpleCookieJar() + cookie_jar.set("a=b; c=d; domain=abc") + cookie_jar.set("e=f; domain=abc") + self.assertEqual(cookie_jar.get("abc"), "e=f") + + cookie_jar = SimpleCookieJar() + cookie_jar.set("a=b; c=d; domain=abc") + cookie_jar.set("e=f; domain=.abc") + self.assertEqual(cookie_jar.get("abc"), "e=f") + + cookie_jar = SimpleCookieJar() + cookie_jar.set("a=b; c=d; domain=abc") + cookie_jar.set("e=f; domain=xyz") + self.assertEqual(cookie_jar.get("abc"), "a=b; c=d") + self.assertEqual(cookie_jar.get("xyz"), "e=f") + self.assertEqual(cookie_jar.get("something"), "") + + def testGet(self): + cookie_jar = SimpleCookieJar() + cookie_jar.set("a=b; c=d; domain=abc.com") + self.assertEqual(cookie_jar.get("abc.com"), "a=b; c=d") + self.assertEqual(cookie_jar.get("x.abc.com"), "a=b; c=d") + self.assertEqual(cookie_jar.get("abc.com.es"), "") + self.assertEqual(cookie_jar.get("xabc.com"), "") + + cookie_jar.set("a=b; c=d; domain=.abc.com") + self.assertEqual(cookie_jar.get("abc.com"), "a=b; c=d") + self.assertEqual(cookie_jar.get("x.abc.com"), "a=b; c=d") + self.assertEqual(cookie_jar.get("abc.com.es"), "") + self.assertEqual(cookie_jar.get("xabc.com"), "") diff --git a/openpype/vendor/python/python_2/websocket/tests/test_http.py b/openpype/vendor/python/python_2/websocket/tests/test_http.py new file mode 100644 index 0000000000..0336ff7d7f --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/tests/test_http.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +# +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" + +import os +import os.path +import websocket as ws +from websocket._http import proxy_info, read_headers, _open_proxied_socket, _tunnel +import sys +sys.path[0:0] = [""] + +if sys.version_info[0] == 2 and sys.version_info[1] < 7: + import unittest2 as unittest +else: + import unittest + + +class SockMock(object): + def __init__(self): + self.data = [] + self.sent = [] + + def add_packet(self, data): + self.data.append(data) + + def gettimeout(self): + return None + + def recv(self, bufsize): + if self.data: + e = self.data.pop(0) + if isinstance(e, Exception): + raise e + if len(e) > bufsize: + self.data.insert(0, e[bufsize:]) + return e[:bufsize] + + def send(self, data): + self.sent.append(data) + return len(data) + + def close(self): + pass + + +class HeaderSockMock(SockMock): + + def __init__(self, fname): + SockMock.__init__(self) + path = os.path.join(os.path.dirname(__file__), fname) + with open(path, "rb") as f: + self.add_packet(f.read()) + + +class OptsList(): + + def __init__(self): + self.timeout = 0 + self.sockopt = [] + + +class HttpTest(unittest.TestCase): + + def testReadHeader(self): + status, header, status_message = read_headers(HeaderSockMock("data/header01.txt")) + self.assertEqual(status, 101) + self.assertEqual(header["connection"], "Upgrade") + # header02.txt is intentionally malformed + self.assertRaises(ws.WebSocketException, read_headers, HeaderSockMock("data/header02.txt")) + + def testTunnel(self): + self.assertRaises(ws.WebSocketProxyException, _tunnel, HeaderSockMock("data/header01.txt"), "example.com", 80, ("username", "password")) + self.assertRaises(ws.WebSocketProxyException, _tunnel, HeaderSockMock("data/header02.txt"), "example.com", 80, ("username", "password")) + + def testConnect(self): + # Not currently testing an actual proxy connection, so just check whether TypeError is raised + self.assertRaises(TypeError, _open_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="http")) + self.assertRaises(TypeError, _open_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks4")) + self.assertRaises(TypeError, _open_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks5h")) + + def testProxyInfo(self): + self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").type, "http") + self.assertRaises(ValueError, proxy_info, http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="badval") + self.assertEqual(proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="http").host, "example.com") + self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").port, "8080") + self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").auth, None) + + +if __name__ == "__main__": + unittest.main() diff --git a/openpype/vendor/python/python_2/websocket/tests/test_url.py b/openpype/vendor/python/python_2/websocket/tests/test_url.py new file mode 100644 index 0000000000..b1d8e06f23 --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/tests/test_url.py @@ -0,0 +1,309 @@ +# -*- coding: utf-8 -*- +# +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" + +import sys +import os + +from websocket._url import get_proxy_info, parse_url, _is_address_in_network, _is_no_proxy_host + +if sys.version_info[0] == 2 and sys.version_info[1] < 7: + import unittest2 as unittest +else: + import unittest +sys.path[0:0] = [""] + + +class UrlTest(unittest.TestCase): + + def test_address_in_network(self): + self.assertTrue(_is_address_in_network('127.0.0.1', '127.0.0.0/8')) + self.assertTrue(_is_address_in_network('127.1.0.1', '127.0.0.0/8')) + self.assertFalse(_is_address_in_network('127.1.0.1', '127.0.0.0/24')) + + def testParseUrl(self): + p = parse_url("ws://www.example.com/r") + self.assertEqual(p[0], "www.example.com") + self.assertEqual(p[1], 80) + self.assertEqual(p[2], "/r") + self.assertEqual(p[3], False) + + p = parse_url("ws://www.example.com/r/") + self.assertEqual(p[0], "www.example.com") + self.assertEqual(p[1], 80) + self.assertEqual(p[2], "/r/") + self.assertEqual(p[3], False) + + p = parse_url("ws://www.example.com/") + self.assertEqual(p[0], "www.example.com") + self.assertEqual(p[1], 80) + self.assertEqual(p[2], "/") + self.assertEqual(p[3], False) + + p = parse_url("ws://www.example.com") + self.assertEqual(p[0], "www.example.com") + self.assertEqual(p[1], 80) + self.assertEqual(p[2], "/") + self.assertEqual(p[3], False) + + p = parse_url("ws://www.example.com:8080/r") + self.assertEqual(p[0], "www.example.com") + self.assertEqual(p[1], 8080) + self.assertEqual(p[2], "/r") + self.assertEqual(p[3], False) + + p = parse_url("ws://www.example.com:8080/") + self.assertEqual(p[0], "www.example.com") + self.assertEqual(p[1], 8080) + self.assertEqual(p[2], "/") + self.assertEqual(p[3], False) + + p = parse_url("ws://www.example.com:8080") + self.assertEqual(p[0], "www.example.com") + self.assertEqual(p[1], 8080) + self.assertEqual(p[2], "/") + self.assertEqual(p[3], False) + + p = parse_url("wss://www.example.com:8080/r") + self.assertEqual(p[0], "www.example.com") + self.assertEqual(p[1], 8080) + self.assertEqual(p[2], "/r") + self.assertEqual(p[3], True) + + p = parse_url("wss://www.example.com:8080/r?key=value") + self.assertEqual(p[0], "www.example.com") + self.assertEqual(p[1], 8080) + self.assertEqual(p[2], "/r?key=value") + self.assertEqual(p[3], True) + + self.assertRaises(ValueError, parse_url, "http://www.example.com/r") + + if sys.version_info[0] == 2 and sys.version_info[1] < 7: + return + + p = parse_url("ws://[2a03:4000:123:83::3]/r") + self.assertEqual(p[0], "2a03:4000:123:83::3") + self.assertEqual(p[1], 80) + self.assertEqual(p[2], "/r") + self.assertEqual(p[3], False) + + p = parse_url("ws://[2a03:4000:123:83::3]:8080/r") + self.assertEqual(p[0], "2a03:4000:123:83::3") + self.assertEqual(p[1], 8080) + self.assertEqual(p[2], "/r") + self.assertEqual(p[3], False) + + p = parse_url("wss://[2a03:4000:123:83::3]/r") + self.assertEqual(p[0], "2a03:4000:123:83::3") + self.assertEqual(p[1], 443) + self.assertEqual(p[2], "/r") + self.assertEqual(p[3], True) + + p = parse_url("wss://[2a03:4000:123:83::3]:8080/r") + self.assertEqual(p[0], "2a03:4000:123:83::3") + self.assertEqual(p[1], 8080) + self.assertEqual(p[2], "/r") + self.assertEqual(p[3], True) + + +class IsNoProxyHostTest(unittest.TestCase): + def setUp(self): + self.no_proxy = os.environ.get("no_proxy", None) + if "no_proxy" in os.environ: + del os.environ["no_proxy"] + + def tearDown(self): + if self.no_proxy: + os.environ["no_proxy"] = self.no_proxy + elif "no_proxy" in os.environ: + del os.environ["no_proxy"] + + def testMatchAll(self): + self.assertTrue(_is_no_proxy_host("any.websocket.org", ['*'])) + self.assertTrue(_is_no_proxy_host("192.168.0.1", ['*'])) + self.assertTrue(_is_no_proxy_host("any.websocket.org", ['other.websocket.org', '*'])) + os.environ['no_proxy'] = '*' + self.assertTrue(_is_no_proxy_host("any.websocket.org", None)) + self.assertTrue(_is_no_proxy_host("192.168.0.1", None)) + os.environ['no_proxy'] = 'other.websocket.org, *' + self.assertTrue(_is_no_proxy_host("any.websocket.org", None)) + + def testIpAddress(self): + self.assertTrue(_is_no_proxy_host("127.0.0.1", ['127.0.0.1'])) + self.assertFalse(_is_no_proxy_host("127.0.0.2", ['127.0.0.1'])) + self.assertTrue(_is_no_proxy_host("127.0.0.1", ['other.websocket.org', '127.0.0.1'])) + self.assertFalse(_is_no_proxy_host("127.0.0.2", ['other.websocket.org', '127.0.0.1'])) + os.environ['no_proxy'] = '127.0.0.1' + self.assertTrue(_is_no_proxy_host("127.0.0.1", None)) + self.assertFalse(_is_no_proxy_host("127.0.0.2", None)) + os.environ['no_proxy'] = 'other.websocket.org, 127.0.0.1' + self.assertTrue(_is_no_proxy_host("127.0.0.1", None)) + self.assertFalse(_is_no_proxy_host("127.0.0.2", None)) + + def testIpAddressInRange(self): + self.assertTrue(_is_no_proxy_host("127.0.0.1", ['127.0.0.0/8'])) + self.assertTrue(_is_no_proxy_host("127.0.0.2", ['127.0.0.0/8'])) + self.assertFalse(_is_no_proxy_host("127.1.0.1", ['127.0.0.0/24'])) + os.environ['no_proxy'] = '127.0.0.0/8' + self.assertTrue(_is_no_proxy_host("127.0.0.1", None)) + self.assertTrue(_is_no_proxy_host("127.0.0.2", None)) + os.environ['no_proxy'] = '127.0.0.0/24' + self.assertFalse(_is_no_proxy_host("127.1.0.1", None)) + + def testHostnameMatch(self): + self.assertTrue(_is_no_proxy_host("my.websocket.org", ['my.websocket.org'])) + self.assertTrue(_is_no_proxy_host("my.websocket.org", ['other.websocket.org', 'my.websocket.org'])) + self.assertFalse(_is_no_proxy_host("my.websocket.org", ['other.websocket.org'])) + os.environ['no_proxy'] = 'my.websocket.org' + self.assertTrue(_is_no_proxy_host("my.websocket.org", None)) + self.assertFalse(_is_no_proxy_host("other.websocket.org", None)) + os.environ['no_proxy'] = 'other.websocket.org, my.websocket.org' + self.assertTrue(_is_no_proxy_host("my.websocket.org", None)) + + def testHostnameMatchDomain(self): + self.assertTrue(_is_no_proxy_host("any.websocket.org", ['.websocket.org'])) + self.assertTrue(_is_no_proxy_host("my.other.websocket.org", ['.websocket.org'])) + self.assertTrue(_is_no_proxy_host("any.websocket.org", ['my.websocket.org', '.websocket.org'])) + self.assertFalse(_is_no_proxy_host("any.websocket.com", ['.websocket.org'])) + os.environ['no_proxy'] = '.websocket.org' + self.assertTrue(_is_no_proxy_host("any.websocket.org", None)) + self.assertTrue(_is_no_proxy_host("my.other.websocket.org", None)) + self.assertFalse(_is_no_proxy_host("any.websocket.com", None)) + os.environ['no_proxy'] = 'my.websocket.org, .websocket.org' + self.assertTrue(_is_no_proxy_host("any.websocket.org", None)) + + +class ProxyInfoTest(unittest.TestCase): + def setUp(self): + self.http_proxy = os.environ.get("http_proxy", None) + self.https_proxy = os.environ.get("https_proxy", None) + self.no_proxy = os.environ.get("no_proxy", None) + if "http_proxy" in os.environ: + del os.environ["http_proxy"] + if "https_proxy" in os.environ: + del os.environ["https_proxy"] + if "no_proxy" in os.environ: + del os.environ["no_proxy"] + + def tearDown(self): + if self.http_proxy: + os.environ["http_proxy"] = self.http_proxy + elif "http_proxy" in os.environ: + del os.environ["http_proxy"] + + if self.https_proxy: + os.environ["https_proxy"] = self.https_proxy + elif "https_proxy" in os.environ: + del os.environ["https_proxy"] + + if self.no_proxy: + os.environ["no_proxy"] = self.no_proxy + elif "no_proxy" in os.environ: + del os.environ["no_proxy"] + + def testProxyFromArgs(self): + self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost"), ("localhost", 0, None)) + self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_port=3128), + ("localhost", 3128, None)) + self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost"), ("localhost", 0, None)) + self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128), + ("localhost", 3128, None)) + + self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_auth=("a", "b")), + ("localhost", 0, ("a", "b"))) + self.assertEqual( + get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_port=3128, proxy_auth=("a", "b")), + ("localhost", 3128, ("a", "b"))) + self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_auth=("a", "b")), + ("localhost", 0, ("a", "b"))) + self.assertEqual( + get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, proxy_auth=("a", "b")), + ("localhost", 3128, ("a", "b"))) + + self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, + no_proxy=["example.com"], proxy_auth=("a", "b")), + ("localhost", 3128, ("a", "b"))) + self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, + no_proxy=["echo.websocket.org"], proxy_auth=("a", "b")), + (None, 0, None)) + + def testProxyFromEnv(self): + os.environ["http_proxy"] = "http://localhost/" + self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, None)) + os.environ["http_proxy"] = "http://localhost:3128/" + self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, None)) + + os.environ["http_proxy"] = "http://localhost/" + os.environ["https_proxy"] = "http://localhost2/" + self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, None)) + os.environ["http_proxy"] = "http://localhost:3128/" + os.environ["https_proxy"] = "http://localhost2:3128/" + self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, None)) + + os.environ["http_proxy"] = "http://localhost/" + os.environ["https_proxy"] = "http://localhost2/" + self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", None, None)) + os.environ["http_proxy"] = "http://localhost:3128/" + os.environ["https_proxy"] = "http://localhost2:3128/" + self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", 3128, None)) + + os.environ["http_proxy"] = "http://a:b@localhost/" + self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, ("a", "b"))) + os.environ["http_proxy"] = "http://a:b@localhost:3128/" + self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, ("a", "b"))) + + os.environ["http_proxy"] = "http://a:b@localhost/" + os.environ["https_proxy"] = "http://a:b@localhost2/" + self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, ("a", "b"))) + os.environ["http_proxy"] = "http://a:b@localhost:3128/" + os.environ["https_proxy"] = "http://a:b@localhost2:3128/" + self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, ("a", "b"))) + + os.environ["http_proxy"] = "http://a:b@localhost/" + os.environ["https_proxy"] = "http://a:b@localhost2/" + self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", None, ("a", "b"))) + os.environ["http_proxy"] = "http://a:b@localhost:3128/" + os.environ["https_proxy"] = "http://a:b@localhost2:3128/" + self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", 3128, ("a", "b"))) + + os.environ["http_proxy"] = "http://a:b@localhost/" + os.environ["https_proxy"] = "http://a:b@localhost2/" + os.environ["no_proxy"] = "example1.com,example2.com" + self.assertEqual(get_proxy_info("example.1.com", True), ("localhost2", None, ("a", "b"))) + os.environ["http_proxy"] = "http://a:b@localhost:3128/" + os.environ["https_proxy"] = "http://a:b@localhost2:3128/" + os.environ["no_proxy"] = "example1.com,example2.com, echo.websocket.org" + self.assertEqual(get_proxy_info("echo.websocket.org", True), (None, 0, None)) + os.environ["http_proxy"] = "http://a:b@localhost:3128/" + os.environ["https_proxy"] = "http://a:b@localhost2:3128/" + os.environ["no_proxy"] = "example1.com,example2.com, .websocket.org" + self.assertEqual(get_proxy_info("echo.websocket.org", True), (None, 0, None)) + + os.environ["http_proxy"] = "http://a:b@localhost:3128/" + os.environ["https_proxy"] = "http://a:b@localhost2:3128/" + os.environ["no_proxy"] = "127.0.0.0/8, 192.168.0.0/16" + self.assertEqual(get_proxy_info("127.0.0.1", False), (None, 0, None)) + self.assertEqual(get_proxy_info("192.168.1.1", False), (None, 0, None)) + + +if __name__ == "__main__": + unittest.main() diff --git a/openpype/vendor/python/python_2/websocket/tests/test_websocket.py b/openpype/vendor/python/python_2/websocket/tests/test_websocket.py new file mode 100644 index 0000000000..0d1d6395ed --- /dev/null +++ b/openpype/vendor/python/python_2/websocket/tests/test_websocket.py @@ -0,0 +1,433 @@ +# -*- coding: utf-8 -*- +# +""" + +""" + +""" +websocket - WebSocket client library for Python + +Copyright (C) 2010 Hiroki Ohtani(liris) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" + +import sys +sys.path[0:0] = [""] + +import os +import os.path +import socket + +import six + +# websocket-client +import websocket as ws +from websocket._handshake import _create_sec_websocket_key, \ + _validate as _validate_header +from websocket._http import read_headers +from websocket._utils import validate_utf8 + +if six.PY3: + from base64 import decodebytes as base64decode +else: + from base64 import decodestring as base64decode + +if sys.version_info[0] == 2 and sys.version_info[1] < 7: + import unittest2 as unittest +else: + import unittest + +try: + from ssl import SSLError +except ImportError: + # dummy class of SSLError for ssl none-support environment. + class SSLError(Exception): + pass + +# Skip test to access the internet. +TEST_WITH_INTERNET = os.environ.get('TEST_WITH_INTERNET', '0') == '1' +TRACEABLE = True + + +def create_mask_key(_): + return "abcd" + + +class SockMock(object): + def __init__(self): + self.data = [] + self.sent = [] + + def add_packet(self, data): + self.data.append(data) + + def gettimeout(self): + return None + + def recv(self, bufsize): + if self.data: + e = self.data.pop(0) + if isinstance(e, Exception): + raise e + if len(e) > bufsize: + self.data.insert(0, e[bufsize:]) + return e[:bufsize] + + def send(self, data): + self.sent.append(data) + return len(data) + + def close(self): + pass + + +class HeaderSockMock(SockMock): + + def __init__(self, fname): + SockMock.__init__(self) + path = os.path.join(os.path.dirname(__file__), fname) + with open(path, "rb") as f: + self.add_packet(f.read()) + + +class WebSocketTest(unittest.TestCase): + def setUp(self): + ws.enableTrace(TRACEABLE) + + def tearDown(self): + pass + + def testDefaultTimeout(self): + self.assertEqual(ws.getdefaulttimeout(), None) + ws.setdefaulttimeout(10) + self.assertEqual(ws.getdefaulttimeout(), 10) + ws.setdefaulttimeout(None) + + def testWSKey(self): + key = _create_sec_websocket_key() + self.assertTrue(key != 24) + self.assertTrue(six.u("¥n") not in key) + + def testNonce(self): + """ WebSocket key should be a random 16-byte nonce. + """ + key = _create_sec_websocket_key() + nonce = base64decode(key.encode("utf-8")) + self.assertEqual(16, len(nonce)) + + def testWsUtils(self): + key = "c6b8hTg4EeGb2gQMztV1/g==" + required_header = { + "upgrade": "websocket", + "connection": "upgrade", + "sec-websocket-accept": "Kxep+hNu9n51529fGidYu7a3wO0="} + self.assertEqual(_validate_header(required_header, key, None), (True, None)) + + header = required_header.copy() + header["upgrade"] = "http" + self.assertEqual(_validate_header(header, key, None), (False, None)) + del header["upgrade"] + self.assertEqual(_validate_header(header, key, None), (False, None)) + + header = required_header.copy() + header["connection"] = "something" + self.assertEqual(_validate_header(header, key, None), (False, None)) + del header["connection"] + self.assertEqual(_validate_header(header, key, None), (False, None)) + + header = required_header.copy() + header["sec-websocket-accept"] = "something" + self.assertEqual(_validate_header(header, key, None), (False, None)) + del header["sec-websocket-accept"] + self.assertEqual(_validate_header(header, key, None), (False, None)) + + header = required_header.copy() + header["sec-websocket-protocol"] = "sub1" + self.assertEqual(_validate_header(header, key, ["sub1", "sub2"]), (True, "sub1")) + self.assertEqual(_validate_header(header, key, ["sub2", "sub3"]), (False, None)) + + header = required_header.copy() + header["sec-websocket-protocol"] = "sUb1" + self.assertEqual(_validate_header(header, key, ["Sub1", "suB2"]), (True, "sub1")) + + header = required_header.copy() + self.assertEqual(_validate_header(header, key, ["Sub1", "suB2"]), (False, None)) + + def testReadHeader(self): + status, header, status_message = read_headers(HeaderSockMock("data/header01.txt")) + self.assertEqual(status, 101) + self.assertEqual(header["connection"], "Upgrade") + + status, header, status_message = read_headers(HeaderSockMock("data/header03.txt")) + self.assertEqual(status, 101) + self.assertEqual(header["connection"], "Upgrade, Keep-Alive") + + HeaderSockMock("data/header02.txt") + self.assertRaises(ws.WebSocketException, read_headers, HeaderSockMock("data/header02.txt")) + + def testSend(self): + # TODO: add longer frame data + sock = ws.WebSocket() + sock.set_mask_key(create_mask_key) + s = sock.sock = HeaderSockMock("data/header01.txt") + sock.send("Hello") + self.assertEqual(s.sent[0], six.b("\x81\x85abcd)\x07\x0f\x08\x0e")) + + sock.send("こんにちは") + self.assertEqual(s.sent[1], six.b("\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc")) + + sock.send(u"こんにちは") + self.assertEqual(s.sent[1], six.b("\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc")) + +# sock.send("x" * 5000) +# self.assertEqual(s.sent[1], six.b("\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc")) + + self.assertEqual(sock.send_binary(b'1111111111101'), 19) + + def testRecv(self): + # TODO: add longer frame data + sock = ws.WebSocket() + s = sock.sock = SockMock() + something = six.b("\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc") + s.add_packet(something) + data = sock.recv() + self.assertEqual(data, "こんにちは") + + s.add_packet(six.b("\x81\x85abcd)\x07\x0f\x08\x0e")) + data = sock.recv() + self.assertEqual(data, "Hello") + + @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + def testIter(self): + count = 2 + for _ in ws.create_connection('wss://stream.meetup.com/2/rsvps'): + count -= 1 + if count == 0: + break + + @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + def testNext(self): + sock = ws.create_connection('wss://stream.meetup.com/2/rsvps') + self.assertEqual(str, type(next(sock))) + + def testInternalRecvStrict(self): + sock = ws.WebSocket() + s = sock.sock = SockMock() + s.add_packet(six.b("foo")) + s.add_packet(socket.timeout()) + s.add_packet(six.b("bar")) + # s.add_packet(SSLError("The read operation timed out")) + s.add_packet(six.b("baz")) + with self.assertRaises(ws.WebSocketTimeoutException): + sock.frame_buffer.recv_strict(9) + # if six.PY2: + # with self.assertRaises(ws.WebSocketTimeoutException): + # data = sock._recv_strict(9) + # else: + # with self.assertRaises(SSLError): + # data = sock._recv_strict(9) + data = sock.frame_buffer.recv_strict(9) + self.assertEqual(data, six.b("foobarbaz")) + with self.assertRaises(ws.WebSocketConnectionClosedException): + sock.frame_buffer.recv_strict(1) + + def testRecvTimeout(self): + sock = ws.WebSocket() + s = sock.sock = SockMock() + s.add_packet(six.b("\x81")) + s.add_packet(socket.timeout()) + s.add_packet(six.b("\x8dabcd\x29\x07\x0f\x08\x0e")) + s.add_packet(socket.timeout()) + s.add_packet(six.b("\x4e\x43\x33\x0e\x10\x0f\x00\x40")) + with self.assertRaises(ws.WebSocketTimeoutException): + sock.recv() + with self.assertRaises(ws.WebSocketTimeoutException): + sock.recv() + data = sock.recv() + self.assertEqual(data, "Hello, World!") + with self.assertRaises(ws.WebSocketConnectionClosedException): + sock.recv() + + def testRecvWithSimpleFragmentation(self): + sock = ws.WebSocket() + s = sock.sock = SockMock() + # OPCODE=TEXT, FIN=0, MSG="Brevity is " + s.add_packet(six.b("\x01\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C")) + # OPCODE=CONT, FIN=1, MSG="the soul of wit" + s.add_packet(six.b("\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17")) + data = sock.recv() + self.assertEqual(data, "Brevity is the soul of wit") + with self.assertRaises(ws.WebSocketConnectionClosedException): + sock.recv() + + def testRecvWithFireEventOfFragmentation(self): + sock = ws.WebSocket(fire_cont_frame=True) + s = sock.sock = SockMock() + # OPCODE=TEXT, FIN=0, MSG="Brevity is " + s.add_packet(six.b("\x01\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C")) + # OPCODE=CONT, FIN=0, MSG="Brevity is " + s.add_packet(six.b("\x00\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C")) + # OPCODE=CONT, FIN=1, MSG="the soul of wit" + s.add_packet(six.b("\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17")) + + _, data = sock.recv_data() + self.assertEqual(data, six.b("Brevity is ")) + _, data = sock.recv_data() + self.assertEqual(data, six.b("Brevity is ")) + _, data = sock.recv_data() + self.assertEqual(data, six.b("the soul of wit")) + + # OPCODE=CONT, FIN=0, MSG="Brevity is " + s.add_packet(six.b("\x80\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C")) + + with self.assertRaises(ws.WebSocketException): + sock.recv_data() + + with self.assertRaises(ws.WebSocketConnectionClosedException): + sock.recv() + + def testClose(self): + sock = ws.WebSocket() + sock.sock = SockMock() + sock.connected = True + sock.close() + self.assertEqual(sock.connected, False) + + sock = ws.WebSocket() + s = sock.sock = SockMock() + sock.connected = True + s.add_packet(six.b('\x88\x80\x17\x98p\x84')) + sock.recv() + self.assertEqual(sock.connected, False) + + def testRecvContFragmentation(self): + sock = ws.WebSocket() + s = sock.sock = SockMock() + # OPCODE=CONT, FIN=1, MSG="the soul of wit" + s.add_packet(six.b("\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17")) + self.assertRaises(ws.WebSocketException, sock.recv) + + def testRecvWithProlongedFragmentation(self): + sock = ws.WebSocket() + s = sock.sock = SockMock() + # OPCODE=TEXT, FIN=0, MSG="Once more unto the breach, " + s.add_packet(six.b("\x01\x9babcd.\x0c\x00\x01A\x0f\x0c\x16\x04B\x16\n\x15" + "\rC\x10\t\x07C\x06\x13\x07\x02\x07\tNC")) + # OPCODE=CONT, FIN=0, MSG="dear friends, " + s.add_packet(six.b("\x00\x8eabcd\x05\x07\x02\x16A\x04\x11\r\x04\x0c\x07" + "\x17MB")) + # OPCODE=CONT, FIN=1, MSG="once more" + s.add_packet(six.b("\x80\x89abcd\x0e\x0c\x00\x01A\x0f\x0c\x16\x04")) + data = sock.recv() + self.assertEqual( + data, + "Once more unto the breach, dear friends, once more") + with self.assertRaises(ws.WebSocketConnectionClosedException): + sock.recv() + + def testRecvWithFragmentationAndControlFrame(self): + sock = ws.WebSocket() + sock.set_mask_key(create_mask_key) + s = sock.sock = SockMock() + # OPCODE=TEXT, FIN=0, MSG="Too much " + s.add_packet(six.b("\x01\x89abcd5\r\x0cD\x0c\x17\x00\x0cA")) + # OPCODE=PING, FIN=1, MSG="Please PONG this" + s.add_packet(six.b("\x89\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17")) + # OPCODE=CONT, FIN=1, MSG="of a good thing" + s.add_packet(six.b("\x80\x8fabcd\x0e\x04C\x05A\x05\x0c\x0b\x05B\x17\x0c" + "\x08\x0c\x04")) + data = sock.recv() + self.assertEqual(data, "Too much of a good thing") + with self.assertRaises(ws.WebSocketConnectionClosedException): + sock.recv() + self.assertEqual( + s.sent[0], + six.b("\x8a\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17")) + + @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + def testWebSocket(self): + s = ws.create_connection("ws://echo.websocket.org/") + self.assertNotEqual(s, None) + s.send("Hello, World") + result = s.recv() + self.assertEqual(result, "Hello, World") + + s.send(u"こにゃにゃちは、世界") + result = s.recv() + self.assertEqual(result, "こにゃにゃちは、世界") + self.assertRaises(ValueError, s.send_close, -1, "") + s.close() + + @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + def testPingPong(self): + s = ws.create_connection("ws://echo.websocket.org/") + self.assertNotEqual(s, None) + s.ping("Hello") + s.pong("Hi") + s.close() + + @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + def testSecureWebSocket(self): + import ssl + s = ws.create_connection("wss://api.bitfinex.com/ws/2") + self.assertNotEqual(s, None) + self.assertTrue(isinstance(s.sock, ssl.SSLSocket)) + self.assertEqual(s.getstatus(), 101) + self.assertNotEqual(s.getheaders(), None) + s.close() + + @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + def testWebSocketWithCustomHeader(self): + s = ws.create_connection("ws://echo.websocket.org/", + headers={"User-Agent": "PythonWebsocketClient"}) + self.assertNotEqual(s, None) + s.send("Hello, World") + result = s.recv() + self.assertEqual(result, "Hello, World") + self.assertRaises(ValueError, s.close, -1, "") + s.close() + + @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + def testAfterClose(self): + s = ws.create_connection("ws://echo.websocket.org/") + self.assertNotEqual(s, None) + s.close() + self.assertRaises(ws.WebSocketConnectionClosedException, s.send, "Hello") + self.assertRaises(ws.WebSocketConnectionClosedException, s.recv) + + +class SockOptTest(unittest.TestCase): + @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") + def testSockOpt(self): + sockopt = ((socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),) + s = ws.create_connection("ws://echo.websocket.org", sockopt=sockopt) + self.assertNotEqual(s.sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY), 0) + s.close() + + +class UtilsTest(unittest.TestCase): + def testUtf8Validator(self): + state = validate_utf8(six.b('\xf0\x90\x80\x80')) + self.assertEqual(state, True) + state = validate_utf8(six.b('\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5\xed\xa0\x80edited')) + self.assertEqual(state, False) + state = validate_utf8(six.b('')) + self.assertEqual(state, True) + + +if __name__ == "__main__": + unittest.main() From 78c2c651c102601e00c9c042e1490a559bff12e8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 16:17:38 +0200 Subject: [PATCH 0515/1227] update gazu version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 362f6a62d8..63bd08d644 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ clique = "1.6.*" Click = "^7" dnspython = "^2.1.0" ftrack-python-api = "2.0.*" -gazu = "^0.8" +gazu = "^0.8.28" google-api-python-client = "^1.12.8" # sync server google support (should be separate?) jsonschema = "^2.6.0" keyring = "^22.0.1" From ca6d77fff79edd8d7073b6b36ce17990ce22edc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 8 Jun 2022 16:25:51 +0200 Subject: [PATCH 0516/1227] :art: modify camera extractor to allow more data --- .../plugins/publish/extract_camera_alembic.py | 17 +++++++++----- .../publish/extract_camera_mayaScene.py | 22 ++++++++++++++----- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py index 5ad6b79d5c..4110ad474d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py @@ -34,7 +34,6 @@ class ExtractCameraAlembic(openpype.api.Extractor): dag=True, type="camera") # validate required settings - assert len(cameras) == 1, "Not a single camera found in extraction" assert isinstance(step, float), "Step must be a float value" camera = cameras[0] @@ -44,8 +43,12 @@ class ExtractCameraAlembic(openpype.api.Extractor): path = os.path.join(dir_path, filename) # Perform alembic extraction + member_shapes = cmds.ls( + members, leaf=True, shapes=True, long=True, dag=True) with lib.maintained_selection(): - cmds.select(camera, replace=True, noExpand=True) + cmds.select( + member_shapes, + replace=True, noExpand=True) # Enforce forward slashes for AbcExport because we're # embedding it into a job string @@ -57,10 +60,12 @@ class ExtractCameraAlembic(openpype.api.Extractor): job_str += ' -step {0} '.format(step) if bake_to_worldspace: - transform = cmds.listRelatives(camera, - parent=True, - fullPath=True)[0] - job_str += ' -worldSpace -root {0}'.format(transform) + job_str += ' -worldSpace' + for member in member_shapes: + self.log.info(f"processing {member}") + transform = cmds.listRelatives( + member, parent=True, fullPath=True)[0] + job_str += ' -root {0}'.format(transform) job_str += ' -file "{0}"'.format(path) diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py index 49c156f9cd..1cb30e65ea 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py @@ -131,12 +131,12 @@ class ExtractCameraMayaScene(openpype.api.Extractor): "bake to world space is ignored...") # get cameras - members = instance.data['setMembers'] + members = cmds.ls(instance.data['setMembers'], leaf=True, shapes=True, + long=True, dag=True) cameras = cmds.ls(members, leaf=True, shapes=True, long=True, dag=True, type="camera") # validate required settings - assert len(cameras) == 1, "Single camera must be found in extraction" assert isinstance(step, float), "Step must be a float value" camera = cameras[0] transform = cmds.listRelatives(camera, parent=True, fullPath=True) @@ -158,15 +158,24 @@ class ExtractCameraMayaScene(openpype.api.Extractor): frame_range=[start, end], step=step ) - baked_shapes = cmds.ls(baked, + baked_camera_shapes = cmds.ls(baked, type="camera", dag=True, shapes=True, long=True) + + members = members + baked_camera_shapes + members.remove(camera) else: - baked_shapes = cameras + baked_camera_shapes = cmds.ls(cameras, + type="camera", + dag=True, + shapes=True, + long=True) # Fix PLN-178: Don't allow background color to be non-black - for cam in baked_shapes: + for cam in cmds.ls( + baked_camera_shapes, type="camera", dag=True, + shapes=True, long=True): attrs = {"backgroundColorR": 0.0, "backgroundColorG": 0.0, "backgroundColorB": 0.0, @@ -177,7 +186,8 @@ class ExtractCameraMayaScene(openpype.api.Extractor): cmds.setAttr(plug, value) self.log.info("Performing extraction..") - cmds.select(baked_shapes, noExpand=True) + cmds.select(cmds.ls(members, dag=True, + shapes=True, long=True), noExpand=True) cmds.file(path, force=True, typ="mayaAscii" if self.scene_type == "ma" else "mayaBinary", # noqa: E501 From f10caed2cdc4830c4a40c0246c7d37b45f368ba6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 16:56:01 +0200 Subject: [PATCH 0517/1227] added certifi to python 2 vendor --- .../python/python_2/certifi/__init__.py | 3 + .../python/python_2/certifi/__main__.py | 12 + .../vendor/python/python_2/certifi/cacert.pem | 4362 +++++++++++++++++ .../vendor/python/python_2/certifi/core.py | 60 + 4 files changed, 4437 insertions(+) create mode 100644 openpype/vendor/python/python_2/certifi/__init__.py create mode 100644 openpype/vendor/python/python_2/certifi/__main__.py create mode 100644 openpype/vendor/python/python_2/certifi/cacert.pem create mode 100644 openpype/vendor/python/python_2/certifi/core.py diff --git a/openpype/vendor/python/python_2/certifi/__init__.py b/openpype/vendor/python/python_2/certifi/__init__.py new file mode 100644 index 0000000000..8db1a0e554 --- /dev/null +++ b/openpype/vendor/python/python_2/certifi/__init__.py @@ -0,0 +1,3 @@ +from .core import contents, where + +__version__ = "2021.10.08" diff --git a/openpype/vendor/python/python_2/certifi/__main__.py b/openpype/vendor/python/python_2/certifi/__main__.py new file mode 100644 index 0000000000..8945b5da85 --- /dev/null +++ b/openpype/vendor/python/python_2/certifi/__main__.py @@ -0,0 +1,12 @@ +import argparse + +from certifi import contents, where + +parser = argparse.ArgumentParser() +parser.add_argument("-c", "--contents", action="store_true") +args = parser.parse_args() + +if args.contents: + print(contents()) +else: + print(where()) diff --git a/openpype/vendor/python/python_2/certifi/cacert.pem b/openpype/vendor/python/python_2/certifi/cacert.pem new file mode 100644 index 0000000000..6d0ccc0d1c --- /dev/null +++ b/openpype/vendor/python/python_2/certifi/cacert.pem @@ -0,0 +1,4362 @@ + +# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Label: "GlobalSign Root CA" +# Serial: 4835703278459707669005204 +# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a +# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c +# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99 +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG +A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv +b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw +MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT +aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ +jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp +xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp +1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG +snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ +U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 +9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B +AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz +yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE +38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP +AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad +DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME +HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 +# Label: "GlobalSign Root CA - R2" +# Serial: 4835703278459682885658125 +# MD5 Fingerprint: 94:14:77:7e:3e:5e:fd:8f:30:bd:41:b0:cf:e7:d0:30 +# SHA1 Fingerprint: 75:e0:ab:b6:13:85:12:27:1c:04:f8:5f:dd:de:38:e4:b7:24:2e:fe +# SHA256 Fingerprint: ca:42:dd:41:74:5f:d0:b8:1e:b9:02:36:2c:f9:d8:bf:71:9d:a1:bd:1b:1e:fc:94:6f:5b:4c:99:f4:2c:1b:9e +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 +MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL +v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 +eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq +tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd +C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa +zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB +mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH +V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n +bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG +3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs +J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO +291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS +ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd +AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 +TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Label: "Entrust.net Premium 2048 Secure Server CA" +# Serial: 946069240 +# MD5 Fingerprint: ee:29:31:bc:32:7e:9a:e6:e8:b5:f7:51:b4:34:71:90 +# SHA1 Fingerprint: 50:30:06:09:1d:97:d4:f5:ae:39:f7:cb:e7:92:7d:7d:65:2d:34:31 +# SHA256 Fingerprint: 6d:c4:71:72:e0:1c:bc:b0:bf:62:58:0d:89:5f:e2:b8:ac:9a:d4:f8:73:80:1e:0c:10:b9:c8:37:d2:1e:b1:77 +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML +RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp +bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 +IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3 +MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 +LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp +YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG +A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq +K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe +sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX +MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT +XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ +HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH +4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub +j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo +U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b +u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ +bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er +fF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Label: "Baltimore CyberTrust Root" +# Serial: 33554617 +# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4 +# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74 +# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ +RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD +VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX +DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y +ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy +VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr +mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr +IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK +mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu +XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy +dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye +jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 +BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 +DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 +9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx +jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 +Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz +ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS +R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. +# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. +# Label: "Entrust Root Certification Authority" +# Serial: 1164660820 +# MD5 Fingerprint: d6:a5:c3:ed:5d:dd:3e:00:c1:3d:87:92:1f:1d:3f:e4 +# SHA1 Fingerprint: b3:1e:b1:b7:40:e3:6c:84:02:da:dc:37:d4:4d:f5:d4:67:49:52:f9 +# SHA256 Fingerprint: 73:c1:76:43:4f:1b:c6:d5:ad:f4:5b:0e:76:e7:27:28:7c:8d:e5:76:16:c1:e6:e6:14:1a:2b:2c:bc:7d:8e:4c +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 +Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW +KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw +NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw +NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy +ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV +BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo +Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4 +4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9 +KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI +rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi +94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB +sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi +gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo +kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE +vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t +O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua +AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP +9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ +eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m +0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +# Issuer: CN=AAA Certificate Services O=Comodo CA Limited +# Subject: CN=AAA Certificate Services O=Comodo CA Limited +# Label: "Comodo AAA Services root" +# Serial: 1 +# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0 +# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49 +# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4 +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj +YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM +GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua +BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe +3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 +YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR +rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm +ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU +oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v +QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t +b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF +AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q +GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 +G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi +l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 +smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2" +# Serial: 1289 +# MD5 Fingerprint: 5e:39:7b:dd:f8:ba:ec:82:e9:ac:62:ba:0c:54:00:2b +# SHA1 Fingerprint: ca:3a:fb:cf:12:40:36:4b:44:b2:16:20:88:80:48:39:19:93:7c:f7 +# SHA256 Fingerprint: 85:a0:dd:7d:d7:20:ad:b7:ff:05:f8:3d:54:2b:20:9d:c7:ff:45:28:f7:d6:77:b1:83:89:fe:a5:e5:c4:9e:86 +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa +GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg +Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J +WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB +rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp ++ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 +ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i +Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz +PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og +/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH +oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI +yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud +EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 +A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL +MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f +BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn +g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl +fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K +WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha +B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc +hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR +TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD +mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z +ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y +4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza +8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3" +# Serial: 1478 +# MD5 Fingerprint: 31:85:3c:62:94:97:63:b9:aa:fd:89:4e:af:6f:e0:cf +# SHA1 Fingerprint: 1f:49:14:f7:d8:74:95:1d:dd:ae:02:c0:be:fd:3a:2d:82:75:51:85 +# SHA256 Fingerprint: 18:f1:fc:7f:20:5d:f8:ad:dd:eb:7f:e0:07:dd:57:e3:af:37:5a:9c:4d:8d:73:54:6b:f4:f1:fe:d1:e1:8d:35 +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM +V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB +4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr +H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd +8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv +vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT +mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe +btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc +T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt +WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ +c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A +4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD +VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG +CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 +aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu +dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw +czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G +A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg +Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 +7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem +d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd ++LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B +4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN +t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x +DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 +k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s +zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j +Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT +mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK +4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust.net OU=Security Communication RootCA1 +# Subject: O=SECOM Trust.net OU=Security Communication RootCA1 +# Label: "Security Communication Root CA" +# Serial: 0 +# MD5 Fingerprint: f1:bc:63:6a:54:e0:b5:27:f5:cd:e7:1a:e3:4d:6e:4a +# SHA1 Fingerprint: 36:b1:2b:49:f9:81:9e:d7:4c:9e:bc:38:0f:c6:56:8f:5d:ac:b2:f7 +# SHA256 Fingerprint: e7:5e:72:ed:9f:56:0e:ec:6e:b4:80:00:73:a4:3f:c3:ad:19:19:5a:39:22:82:01:78:95:97:4a:99:02:6b:6c +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY +MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t +dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5 +WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD +VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8 +9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ +DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9 +Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N +QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ +xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G +A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG +kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr +Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5 +Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU +JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot +RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== +-----END CERTIFICATE----- + +# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Label: "XRamp Global CA Root" +# Serial: 107108908803651509692980124233745014957 +# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1 +# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6 +# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2 +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB +gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk +MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY +UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx +NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 +dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy +dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 +38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP +KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q +DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 +qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa +JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi +PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P +BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs +jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 +eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR +vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa +IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy +i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ +O+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Label: "Go Daddy Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67 +# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4 +# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4 +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh +MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE +YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 +MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo +ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg +MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN +ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA +PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w +wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi +EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY +avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ +YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE +sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h +/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 +IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy +OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P +TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER +dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf +ReYNnyicsbkqWletNw+vHX/bvZ8= +-----END CERTIFICATE----- + +# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Label: "Starfield Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24 +# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a +# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58 +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl +MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp +U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw +NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE +ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp +ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 +DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf +8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN ++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 +X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa +K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA +1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G +A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR +zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 +YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD +bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 +L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D +eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp +VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY +WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root CA" +# Serial: 17154717934120587862167794914071425081 +# MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72 +# SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43 +# SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c +JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP +mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ +wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 +VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ +AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB +AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun +pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC +dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf +fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm +NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx +H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root CA" +# Serial: 10944719598952040374951832963794454346 +# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e +# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36 +# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61 +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert High Assurance EV Root CA" +# Serial: 3553400076410547919724730734378100087 +# MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a +# SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25 +# SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- + +# Issuer: CN=DST Root CA X3 O=Digital Signature Trust Co. +# Subject: CN=DST Root CA X3 O=Digital Signature Trust Co. +# Label: "DST Root CA X3" +# Serial: 91299735575339953335919266965803778155 +# MD5 Fingerprint: 41:03:52:dc:0f:f7:50:1b:16:f0:02:8e:ba:6f:45:c5 +# SHA1 Fingerprint: da:c9:02:4f:54:d8:f6:df:94:93:5f:b1:73:26:38:ca:6a:d7:7c:13 +# SHA256 Fingerprint: 06:87:26:03:31:a7:24:03:d9:09:f1:05:e6:9b:cf:0d:32:e1:bd:24:93:ff:c6:d9:20:6d:11:bc:d6:77:07:39 +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O +rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq +OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b +xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw +7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD +aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG +SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 +ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr +AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz +R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 +JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo +Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- + +# Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG +# Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG +# Label: "SwissSign Gold CA - G2" +# Serial: 13492815561806991280 +# MD5 Fingerprint: 24:77:d9:a8:91:d1:3b:fa:88:2d:c2:ff:f8:cd:33:93 +# SHA1 Fingerprint: d8:c5:38:8a:b7:30:1b:1b:6e:d4:7a:e6:45:25:3a:6f:9f:1a:27:61 +# SHA256 Fingerprint: 62:dd:0b:e9:b9:f5:0a:16:3e:a0:f8:e7:5c:05:3b:1e:ca:57:ea:55:c8:68:8f:64:7c:68:81:f2:c8:35:7b:95 +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=SwissSign Silver CA - G2 O=SwissSign AG +# Subject: CN=SwissSign Silver CA - G2 O=SwissSign AG +# Label: "SwissSign Silver CA - G2" +# Serial: 5700383053117599563 +# MD5 Fingerprint: e0:06:a1:c9:7d:cf:c9:fc:0d:c0:56:75:96:d8:62:13 +# SHA1 Fingerprint: 9b:aa:e5:9f:56:ee:21:cb:43:5a:be:25:93:df:a7:f0:40:d1:1d:cb +# SHA256 Fingerprint: be:6c:4d:a2:bb:b9:ba:59:b6:f3:93:97:68:37:42:46:c3:c0:05:99:3f:a9:8f:02:0d:1d:ed:be:d4:8a:81:d5 +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=SecureTrust CA O=SecureTrust Corporation +# Subject: CN=SecureTrust CA O=SecureTrust Corporation +# Label: "SecureTrust CA" +# Serial: 17199774589125277788362757014266862032 +# MD5 Fingerprint: dc:32:c3:a7:6d:25:57:c7:68:09:9d:ea:2d:a9:a2:d1 +# SHA1 Fingerprint: 87:82:c6:c3:04:35:3b:cf:d2:96:92:d2:59:3e:7d:44:d9:34:ff:11 +# SHA256 Fingerprint: f1:c1:b5:0a:e5:a2:0d:d8:03:0e:c9:f6:bc:24:82:3d:d3:67:b5:25:57:59:b4:e7:1b:61:fc:e9:f7:37:5d:73 +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz +MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv +cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz +Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO +0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao +wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj +7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS +8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT +BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg +JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 +6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ +3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm +D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS +CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +# Issuer: CN=Secure Global CA O=SecureTrust Corporation +# Subject: CN=Secure Global CA O=SecureTrust Corporation +# Label: "Secure Global CA" +# Serial: 9751836167731051554232119481456978597 +# MD5 Fingerprint: cf:f4:27:0d:d4:ed:dc:65:16:49:6d:3d:da:bf:6e:de +# SHA1 Fingerprint: 3a:44:73:5a:e5:81:90:1f:24:86:61:46:1e:3b:9c:c4:5f:f5:3a:1b +# SHA256 Fingerprint: 42:00:f5:04:3a:c8:59:0e:bb:52:7d:20:9e:d1:50:30:29:fb:cb:d4:1c:a1:b5:06:ec:27:f1:5a:de:7d:ac:69 +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx +MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg +Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ +iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa +/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ +jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI +HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 +sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w +gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw +KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG +AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L +URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO +H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm +I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY +iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO Certification Authority O=COMODO CA Limited +# Label: "COMODO Certification Authority" +# Serial: 104350513648249232941998508985834464573 +# MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75 +# SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b +# SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66 +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB +gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV +BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw +MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl +YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P +RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 +UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI +2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 +Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp ++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ +DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O +nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW +/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g +PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u +QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY +SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv +IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 +zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd +BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB +ZQ== +-----END CERTIFICATE----- + +# Issuer: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. +# Subject: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. +# Label: "Network Solutions Certificate Authority" +# Serial: 116697915152937497490437556386812487904 +# MD5 Fingerprint: d3:f3:a6:16:c0:fa:6b:1d:59:b1:2d:96:4d:0e:11:2e +# SHA1 Fingerprint: 74:f8:a3:c3:ef:e7:b3:90:06:4b:83:90:3c:21:64:60:20:e5:df:ce +# SHA256 Fingerprint: 15:f0:ba:00:a3:ac:7a:f3:ac:88:4c:07:2b:10:11:a0:77:bd:77:c0:97:f4:01:64:b2:f8:59:8a:bd:83:86:0c +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi +MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu +MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp +dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV +UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO +ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz +c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP +OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl +mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF +BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4 +qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw +gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu +bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp +dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8 +6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/ +h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH +/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv +wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN +pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey +-----END CERTIFICATE----- + +# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited +# Label: "COMODO ECC Certification Authority" +# Serial: 41578283867086692638256921589707938090 +# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23 +# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11 +# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7 +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw +MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR +FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J +cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW +BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm +fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv +GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +# Issuer: CN=Certigna O=Dhimyotis +# Subject: CN=Certigna O=Dhimyotis +# Label: "Certigna" +# Serial: 18364802974209362175 +# MD5 Fingerprint: ab:57:a6:5b:7d:42:82:19:b5:d8:58:26:28:5e:fd:ff +# SHA1 Fingerprint: b1:2e:13:63:45:86:a4:6f:1a:b2:60:68:37:58:2d:c4:ac:fd:94:97 +# SHA256 Fingerprint: e3:b6:a2:db:2e:d7:ce:48:84:2f:7a:c5:32:41:c7:b7:1d:54:14:4b:fb:40:c1:1f:3f:1d:0b:42:f5:ee:a1:2d +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV +BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X +DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ +BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 +QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny +gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw +zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q +130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 +JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw +ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT +AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj +AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG +9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h +bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc +fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu +HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w +t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +# Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc +# Subject: CN=Cybertrust Global Root O=Cybertrust, Inc +# Label: "Cybertrust Global Root" +# Serial: 4835703278459682877484360 +# MD5 Fingerprint: 72:e4:4a:87:e3:69:40:80:77:ea:bc:e3:f4:ff:f0:e1 +# SHA1 Fingerprint: 5f:43:e5:b1:bf:f8:78:8c:ac:1c:c7:ca:4a:9a:c6:22:2b:cc:34:c6 +# SHA256 Fingerprint: 96:0a:df:00:63:e9:63:56:75:0c:29:65:dd:0a:08:67:da:0b:9c:bd:6e:77:71:4a:ea:fb:23:49:ab:39:3d:a3 +-----BEGIN CERTIFICATE----- +MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG +A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh +bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE +ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS +b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5 +7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS +J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y +HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP +t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz +FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY +XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw +hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js +MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA +A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj +Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx +XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o +omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc +A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW +WL1WMRJOEcgh4LMRkWXbtKaIOM5V +-----END CERTIFICATE----- + +# Issuer: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority +# Subject: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority +# Label: "ePKI Root Certification Authority" +# Serial: 28956088682735189655030529057352760477 +# MD5 Fingerprint: 1b:2e:00:ca:26:06:90:3d:ad:fe:6f:15:68:d3:6b:b3 +# SHA1 Fingerprint: 67:65:0d:f1:7e:8e:7e:5b:82:40:a4:f4:56:4b:cf:e2:3d:69:c6:f0 +# SHA256 Fingerprint: c0:a6:f4:dc:63:a2:4b:fd:cf:54:ef:2a:6a:08:2a:0a:72:de:35:80:3e:2f:f5:ff:52:7a:e5:d8:72:06:df:d5 +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw +IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL +SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH +SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh +ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X +DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 +TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ +fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA +sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU +WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS +nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH +dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip +NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC +AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF +MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB +uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl +PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP +JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ +gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 +j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 +5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB +o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS +/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z +Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE +W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D +hNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +# Issuer: O=certSIGN OU=certSIGN ROOT CA +# Subject: O=certSIGN OU=certSIGN ROOT CA +# Label: "certSIGN ROOT CA" +# Serial: 35210227249154 +# MD5 Fingerprint: 18:98:c0:d6:e9:3a:fc:f9:b0:f5:0c:f7:4b:01:44:17 +# SHA1 Fingerprint: fa:b7:ee:36:97:26:62:fb:2d:b0:2a:f6:bf:03:fd:e8:7c:4b:2f:9b +# SHA256 Fingerprint: ea:a9:62:c4:fa:4a:6b:af:eb:e4:15:19:6d:35:1c:cd:88:8d:4f:53:f3:fa:8a:e6:d7:c4:66:a9:4e:60:42:bb +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT +AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD +QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP +MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do +0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ +UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d +RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ +OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv +JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C +AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O +BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ +LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY +MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ +44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I +Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw +i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN +9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +# Issuer: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) +# Subject: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) +# Label: "NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny" +# Serial: 80544274841616 +# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88 +# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91 +# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98 +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG +EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 +MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl +cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR +dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB +pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM +b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm +aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz +IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT +lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz +AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 +VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG +ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 +BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG +AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M +U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh +bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C ++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F +uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 +XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +# Issuer: CN=Hongkong Post Root CA 1 O=Hongkong Post +# Subject: CN=Hongkong Post Root CA 1 O=Hongkong Post +# Label: "Hongkong Post Root CA 1" +# Serial: 1000 +# MD5 Fingerprint: a8:0d:6f:39:78:b9:43:6d:77:42:6d:98:5a:cc:23:ca +# SHA1 Fingerprint: d6:da:a8:20:8d:09:d2:15:4d:24:b5:2f:cb:34:6e:b2:58:b2:8a:58 +# SHA256 Fingerprint: f9:e6:7d:33:6c:51:00:2a:c0:54:c6:32:02:2d:66:dd:a2:e7:e3:ff:f1:0a:d0:61:ed:31:d8:bb:b4:10:cf:b2 +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx +FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg +Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG +A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr +b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ +jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn +PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh +ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9 +nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h +q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED +MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC +mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3 +7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB +oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs +EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO +fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi +AmvZWg== +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. +# Subject: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. +# Label: "SecureSign RootCA11" +# Serial: 1 +# MD5 Fingerprint: b7:52:74:e2:92:b4:80:93:f2:75:e4:cc:d7:f2:ea:26 +# SHA1 Fingerprint: 3b:c4:9f:48:f8:f3:73:a0:9c:1e:bd:f8:5b:b1:c3:65:c7:d8:11:b3 +# SHA256 Fingerprint: bf:0f:ee:fb:9e:3a:58:1a:d5:f9:e9:db:75:89:98:57:43:d2:61:08:5c:4d:31:4f:6f:5d:72:59:aa:42:16:12 +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr +MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG +A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0 +MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp +Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD +QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz +i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8 +h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV +MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9 +UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni +8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC +h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD +VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB +AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm +KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ +X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr +QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5 +pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN +QSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Label: "Microsec e-Szigno Root CA 2009" +# Serial: 14014712776195784473 +# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1 +# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e +# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78 +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G +CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y +OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx +FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp +Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP +kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc +cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U +fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 +N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC +xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 ++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM +Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG +SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h +mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk +ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c +2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t +HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Label: "GlobalSign Root CA - R3" +# Serial: 4835703278459759426209954 +# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28 +# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad +# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f +-----END CERTIFICATE----- + +# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068" +# Serial: 6047274297262753887 +# MD5 Fingerprint: 73:3a:74:7a:ec:bb:a3:96:a6:c2:e4:e2:c8:9b:c0:c3 +# SHA1 Fingerprint: ae:c5:fb:3f:c8:e1:bf:c4:e5:4f:03:07:5a:9a:e8:00:b7:f7:b6:fa +# SHA256 Fingerprint: 04:04:80:28:bf:1f:28:64:d4:8f:9a:d4:d8:32:94:36:6a:82:88:56:55:3f:3b:14:30:3f:90:14:7f:5d:40:ef +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE +BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h +cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy +MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg +Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 +thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM +cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG +L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i +NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h +X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b +m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy +Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja +EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T +KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF +6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh +OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD +VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp +cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv +ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl +AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF +661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9 +am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1 +ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481 +PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS +3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k +SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF +3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM +ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g +StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz +Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB +jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V +-----END CERTIFICATE----- + +# Issuer: CN=Izenpe.com O=IZENPE S.A. +# Subject: CN=Izenpe.com O=IZENPE S.A. +# Label: "Izenpe.com" +# Serial: 917563065490389241595536686991402621 +# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73 +# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19 +# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Label: "Go Daddy Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01 +# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b +# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96 +# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e +# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5 +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Services Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2 +# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f +# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5 +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs +ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD +VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy +ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy +dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p +OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 +8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K +Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe +hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk +6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q +AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI +bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB +ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z +qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn +0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN +sSi6 +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Commercial O=AffirmTrust +# Subject: CN=AffirmTrust Commercial O=AffirmTrust +# Label: "AffirmTrust Commercial" +# Serial: 8608355977964138876 +# MD5 Fingerprint: 82:92:ba:5b:ef:cd:8a:6f:a6:3d:55:f9:84:f6:d6:b7 +# SHA1 Fingerprint: f9:b5:b6:32:45:5f:9c:be:ec:57:5f:80:dc:e9:6e:2c:c7:b2:78:b7 +# SHA256 Fingerprint: 03:76:ab:1d:54:c5:f9:80:3c:e4:b2:e2:01:a0:ee:7e:ef:7b:57:b6:36:e8:a9:3c:9b:8d:48:60:c9:6f:5f:a7 +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP +Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr +ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL +MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1 +yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr +VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/ +nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG +XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj +vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt +Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g +N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC +nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Networking O=AffirmTrust +# Subject: CN=AffirmTrust Networking O=AffirmTrust +# Label: "AffirmTrust Networking" +# Serial: 8957382827206547757 +# MD5 Fingerprint: 42:65:ca:be:01:9a:9a:4c:a9:8c:41:49:cd:c0:d5:7f +# SHA1 Fingerprint: 29:36:21:02:8b:20:ed:02:f5:66:c5:32:d1:d6:ed:90:9f:45:00:2f +# SHA256 Fingerprint: 0a:81:ec:5a:92:97:77:f1:45:90:4a:f3:8d:5d:50:9f:66:b5:e2:c5:8f:cd:b5:31:05:8b:0e:17:f3:f0:b4:1b +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y +YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua +kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL +QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp +6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG +yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i +QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO +tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu +QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ +Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u +olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48 +x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Premium O=AffirmTrust +# Subject: CN=AffirmTrust Premium O=AffirmTrust +# Label: "AffirmTrust Premium" +# Serial: 7893706540734352110 +# MD5 Fingerprint: c4:5d:0e:48:b6:ac:28:30:4e:0a:bc:f9:38:16:87:57 +# SHA1 Fingerprint: d8:a6:33:2c:e0:03:6f:b1:85:f6:63:4f:7d:6a:06:65:26:32:28:27 +# SHA256 Fingerprint: 70:a7:3f:7f:37:6b:60:07:42:48:90:45:34:b1:14:82:d5:bf:0e:69:8e:cc:49:8d:f5:25:77:eb:f2:e9:3b:9a +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz +dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG +A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U +cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf +qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ +JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ ++jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS +s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5 +HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7 +70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG +V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S +qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S +5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia +C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX +OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE +FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2 +KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B +8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ +MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc +0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ +u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF +u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH +YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8 +GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO +RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e +KeC2uAloGRwYQw== +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Premium ECC O=AffirmTrust +# Subject: CN=AffirmTrust Premium ECC O=AffirmTrust +# Label: "AffirmTrust Premium ECC" +# Serial: 8401224907861490260 +# MD5 Fingerprint: 64:b0:09:55:cf:b1:d5:99:e2:be:13:ab:a6:5d:ea:4d +# SHA1 Fingerprint: b8:23:6b:00:2f:1d:16:86:53:01:55:6c:11:a4:37:ca:eb:ff:c3:bb +# SHA256 Fingerprint: bd:71:fd:f6:da:97:e4:cf:62:d1:64:7a:dd:25:81:b0:7d:79:ad:f8:39:7e:b4:ec:ba:9c:5e:84:88:82:14:23 +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC +VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ +cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ +BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt +VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D +0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9 +ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G +A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs +aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I +flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA" +# Serial: 279744 +# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78 +# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e +# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM +MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D +ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU +cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 +WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg +Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw +IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH +UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM +TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU +BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM +kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x +AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y +sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL +I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 +J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY +VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Label: "TWCA Root Certification Authority" +# Serial: 1 +# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79 +# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48 +# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44 +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES +MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU +V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz +WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO +LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE +AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH +K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX +RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z +rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx +3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq +hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC +MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls +XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D +lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn +aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ +YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Label: "Security Communication RootCA2" +# Serial: 0 +# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43 +# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74 +# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX +DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy +dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj +YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV +OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr +zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM +VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ +hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO +ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw +awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs +OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF +coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc +okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 +t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy +1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ +SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +# Issuer: CN=EC-ACC O=Agencia Catalana de Certificacio (NIF Q-0801176-I) OU=Serveis Publics de Certificacio/Vegeu https://www.catcert.net/verarrel (c)03/Jerarquia Entitats de Certificacio Catalanes +# Subject: CN=EC-ACC O=Agencia Catalana de Certificacio (NIF Q-0801176-I) OU=Serveis Publics de Certificacio/Vegeu https://www.catcert.net/verarrel (c)03/Jerarquia Entitats de Certificacio Catalanes +# Label: "EC-ACC" +# Serial: -23701579247955709139626555126524820479 +# MD5 Fingerprint: eb:f5:9d:29:0d:61:f9:42:1f:7c:c2:ba:6d:e3:15:09 +# SHA1 Fingerprint: 28:90:3a:63:5b:52:80:fa:e6:77:4c:0b:6d:a7:d6:ba:a6:4a:f2:e8 +# SHA256 Fingerprint: 88:49:7f:01:60:2f:31:54:24:6a:e2:8c:4d:5a:ef:10:f1:d8:7e:bb:76:62:6f:4a:e0:b7:f9:5b:a7:96:87:99 +-----BEGIN CERTIFICATE----- +MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB +8zELMAkGA1UEBhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2Vy +dGlmaWNhY2lvIChOSUYgUS0wODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1 +YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYDVQQLEyxWZWdldSBodHRwczovL3d3 +dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UECxMsSmVyYXJxdWlh +IEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMTBkVD +LUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQG +EwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8g +KE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBD +ZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQu +bmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMg +ZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUNDMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R +85iKw5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm +4CgPukLjbo73FCeTae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaV +HMf5NLWUhdWZXqBIoH7nF2W4onW4HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNd +QlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0aE9jD2z3Il3rucO2n5nzbcc8t +lGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw0JDnJwIDAQAB +o4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4 +opvpXY0wfwYDVR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBo +dHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidW +ZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAwDQYJKoZIhvcN +AQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJlF7W2u++AVtd0x7Y +/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNaAl6k +SBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhy +Rp/7SNVel+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOS +Agu+TGbrIP65y7WZf+a2E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xl +nJ2lYJU6Un/10asIbvPuW/mIPX64b24D5EI= +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions RootCA 2011" +# Serial: 0 +# MD5 Fingerprint: 73:9f:4c:4b:73:5b:79:e9:fa:ba:1c:ef:6e:cb:d5:c9 +# SHA1 Fingerprint: fe:45:65:9b:79:03:5b:98:a1:61:b5:51:2e:ac:da:58:09:48:22:4d +# SHA256 Fingerprint: bc:10:4f:15:a4:8b:e7:09:dc:a5:42:a7:e1:d4:b9:df:6f:05:45:27:e8:02:ea:a9:2d:59:54:44:25:8a:fe:71 +-----BEGIN CERTIFICATE----- +MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix +RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p +YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw +NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK +EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl +cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz +dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ +fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns +bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD +75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP +FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV +HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp +5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu +b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA +A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p +6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 +TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7 +dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys +Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI +l7WdmplNsDz4SgCbZN2fOUvRJ9e4 +-----END CERTIFICATE----- + +# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 +# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 +# Label: "Actalis Authentication Root CA" +# Serial: 6271844772424770508 +# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6 +# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac +# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66 +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE +BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w +MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC +SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 +ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv +UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX +4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 +KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ +gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb +rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ +51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F +be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe +KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F +v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn +fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 +jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz +ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL +e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 +jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz +WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V +SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j +pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX +X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok +fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R +K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU +ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU +LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT +LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 2 Root CA" +# Serial: 2 +# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29 +# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99 +# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48 +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr +6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV +L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 +1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx +MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ +QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB +arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr +Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi +FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS +P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN +9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz +uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h +9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t +OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo ++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 +KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 +DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us +H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ +I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 +5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h +3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz +Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 3 Root CA" +# Serial: 2 +# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec +# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57 +# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y +ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E +N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 +tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX +0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c +/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X +KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY +zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS +O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D +34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP +K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv +Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj +QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS +IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 +HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa +O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv +033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u +dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE +kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 +3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD +u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq +4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 3" +# Serial: 1 +# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef +# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1 +# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN +8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ +RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 +hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 +ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM +EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 +A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy +WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ +1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 +6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT +91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p +TpPDpFQUWw== +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 2009" +# Serial: 623603 +# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f +# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0 +# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1 +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha +ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM +HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 +UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 +tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R +ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM +lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp +/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G +A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy +MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl +cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js +L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL +BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni +acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K +zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 +PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y +Johw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 EV 2009" +# Serial: 623604 +# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6 +# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83 +# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81 +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw +NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV +BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn +ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 +3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z +qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR +p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 +HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw +ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea +HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw +Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh +c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E +RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku +Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp +3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF +CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na +xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX +KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +# Issuer: CN=CA Disig Root R2 O=Disig a.s. +# Subject: CN=CA Disig Root R2 O=Disig a.s. +# Label: "CA Disig Root R2" +# Serial: 10572350602393338211 +# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03 +# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71 +# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03 +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy +MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe +NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH +PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I +x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe +QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR +yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO +QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 +H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ +QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD +i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs +nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 +rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI +hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf +GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb +lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka ++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal +TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i +nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 +gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr +G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os +zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x +L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Label: "ACCVRAIZ1" +# Serial: 6828503384748696800 +# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02 +# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17 +# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13 +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE +AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw +CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ +BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND +VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb +qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY +HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo +G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA +lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr +IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ +0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH +k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 +4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO +m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa +cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl +uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI +KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls +ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG +AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT +VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG +CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA +cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA +QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA +7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA +cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA +QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA +czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu +aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt +aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud +DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF +BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp +D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU +JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m +AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD +vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms +tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH +7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA +h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF +d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H +pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Label: "TWCA Global Root CA" +# Serial: 3262 +# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96 +# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65 +# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx +EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT +VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 +NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT +B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF +10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz +0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh +MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH +zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc +46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 +yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi +laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP +oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA +BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE +qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm +4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL +1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF +H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo +RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ +nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh +15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW +6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW +nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j +wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz +aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy +KwbQBM0= +-----END CERTIFICATE----- + +# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Label: "TeliaSonera Root CA v1" +# Serial: 199041966741090107964904287217786801558 +# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c +# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 +# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw +NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv +b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD +VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F +VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 +7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X +Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ +/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs +81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm +dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe +Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu +sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 +pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs +slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ +arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD +VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG +9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl +dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj +TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed +Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 +Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI +OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 +vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW +t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn +HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx +SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +# Issuer: CN=E-Tugra Certification Authority O=E-Tu\u011fra EBG Bili\u015fim Teknolojileri ve Hizmetleri A.\u015e. OU=E-Tugra Sertifikasyon Merkezi +# Subject: CN=E-Tugra Certification Authority O=E-Tu\u011fra EBG Bili\u015fim Teknolojileri ve Hizmetleri A.\u015e. OU=E-Tugra Sertifikasyon Merkezi +# Label: "E-Tugra Certification Authority" +# Serial: 7667447206703254355 +# MD5 Fingerprint: b8:a1:03:63:b0:bd:21:71:70:8a:6f:13:3a:bb:79:49 +# SHA1 Fingerprint: 51:c6:e7:08:49:06:6e:f3:92:d4:5c:a0:0d:6d:a3:62:8f:c3:52:39 +# SHA256 Fingerprint: b0:bf:d5:2b:b0:d7:d9:bd:92:bf:5d:4d:c1:3d:a2:55:c0:2c:54:2f:37:83:65:ea:89:39:11:f5:5e:55:f2:3c +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV +BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC +aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV +BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1 +Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz +MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+ +BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp +em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY +B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH +D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF +Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo +q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D +k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH +fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut +dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM +ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8 +zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX +U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6 +Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5 +XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF +Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR +HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY +GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c +77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3 ++GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK +vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6 +FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl +yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P +AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD +y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d +NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 2" +# Serial: 1 +# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a +# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 +# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot 2011 O=Atos +# Subject: CN=Atos TrustedRoot 2011 O=Atos +# Label: "Atos TrustedRoot 2011" +# Serial: 6643877497813316402 +# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 +# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 +# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE +AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG +EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM +FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC +REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp +Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM +VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ +SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ +4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L +cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi +eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG +A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 +DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j +vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP +DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc +maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D +lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv +KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 1 G3" +# Serial: 687049649626669250736271037606554624078720034195 +# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab +# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 +# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2 G3" +# Serial: 390156079458959257446133169266079962026824725800 +# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 +# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 +# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3 G3" +# Serial: 268090761170461462463995952157327242137089239581 +# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 +# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d +# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G2" +# Serial: 15385348160840213938643033620894905419 +# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d +# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f +# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G3" +# Serial: 15459312981008553731928384953135426796 +# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb +# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 +# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G2" +# Serial: 4293743540046975378534879503202253541 +# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 +# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 +# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G3" +# Serial: 7089244469030293291760083333884364146 +# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca +# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e +# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Trusted Root G4" +# Serial: 7451500558977370777930084869016614236 +# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 +# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 +# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- + +# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Label: "COMODO RSA Certification Authority" +# Serial: 101909084537582093308941363524873193117 +# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 +# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 +# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Label: "USERTrust RSA Certification Authority" +# Serial: 2645093764781058787591871645665788717 +# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 +# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e +# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Label: "USERTrust ECC Certification Authority" +# Serial: 123013823720199481456569720443997572134 +# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 +# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 +# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Label: "GlobalSign ECC Root CA - R4" +# Serial: 14367148294922964480859022125800977897474 +# MD5 Fingerprint: 20:f0:27:68:d1:7e:a0:9d:0e:e6:2a:ca:df:5c:89:8e +# SHA1 Fingerprint: 69:69:56:2e:40:80:f4:24:a1:e7:19:9f:14:ba:f3:ee:58:ab:6a:bb +# SHA256 Fingerprint: be:c9:49:11:c2:95:56:76:db:6c:0a:55:09:86:d7:6e:3b:a0:05:66:7c:44:2c:97:62:b4:fb:b7:73:de:22:8c +-----BEGIN CERTIFICATE----- +MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ +FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F +uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX +kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs +ewv4n4Q= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Label: "GlobalSign ECC Root CA - R5" +# Serial: 32785792099990507226680698011560947931244 +# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 +# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa +# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden +# Label: "Staat der Nederlanden EV Root CA" +# Serial: 10000013 +# MD5 Fingerprint: fc:06:af:7b:e8:1a:f1:9a:b4:e8:d2:70:1f:c0:f5:ba +# SHA1 Fingerprint: 76:e2:7e:c1:4f:db:82:c1:c0:a6:75:b5:05:be:3d:29:b4:ed:db:bb +# SHA256 Fingerprint: 4d:24:91:41:4c:fe:95:67:46:ec:4c:ef:a6:cf:6f:72:e2:8a:13:29:43:2f:9d:8a:90:7a:c4:cb:5d:ad:c1:5a +-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gRVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0y +MjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIg +TmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBS +b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkkSzrS +M4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nC +UiY4iKTWO0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3d +Z//BYY1jTw+bbRcwJu+r0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46p +rfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13l +pJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gVXJrm0w912fxBmJc+qiXb +j5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr08C+eKxC +KFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS +/ZbV0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0X +cgOPvZuM5l5Tnrmd74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH +1vI4gnPah1vlPNOePqc7nvQDs/nxfRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrP +px9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwaivsnuL8wbqg7 +MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI +eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u +2dfOWBfoqSmuc0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHS +v4ilf0X8rLiltTMMgsT7B/Zq5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTC +wPTxGfARKbalGAKb12NMcIxHowNDXLldRqANb/9Zjr7dn3LDWyvfjFvO5QxGbJKy +CqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tNf1zuacpzEPuKqf2e +vTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi5Dp6 +Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIa +Gl6I6lD4WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeL +eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8 +FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc +7uzXLg== +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Label: "IdenTrust Commercial Root CA 1" +# Serial: 13298821034946342390520003877796839426 +# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7 +# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25 +# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Label: "IdenTrust Public Sector Root CA 1" +# Serial: 13298821034946342390521976156843933698 +# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba +# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd +# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu +VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN +MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 +MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 +ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy +RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS +bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF +/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R +3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw +EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy +9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V +GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ +2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV +WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD +W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN +AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV +DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 +TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G +lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW +mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df +WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 ++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ +tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA +GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv +8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - G2" +# Serial: 1246989352 +# MD5 Fingerprint: 4b:e2:c9:91:96:65:0c:f4:0e:5a:93:92:a0:0a:fe:b2 +# SHA1 Fingerprint: 8c:f4:27:fd:79:0c:3a:d1:66:06:8d:e8:1e:57:ef:bb:93:22:72:d4 +# SHA256 Fingerprint: 43:df:57:74:b0:3e:7f:ef:5f:e4:0d:93:1a:7b:ed:f1:bb:2e:6b:42:73:8c:4e:6d:38:41:10:3d:3a:a7:f3:39 +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 +cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs +IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz +dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy +NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu +dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt +dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 +aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T +RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN +cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW +wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 +U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 +jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN +BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ +jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v +1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R +nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH +VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - EC1" +# Serial: 51543124481930649114116133369 +# MD5 Fingerprint: b6:7e:1d:f0:58:c5:49:6c:24:3b:3d:ed:98:18:ed:bc +# SHA1 Fingerprint: 20:d8:06:40:df:9b:25:f5:12:25:3a:11:ea:f7:59:8a:eb:14:b5:47 +# SHA256 Fingerprint: 02:ed:0e:b2:8c:14:da:45:16:5c:56:67:91:70:0d:64:51:d7:fb:56:f0:b2:ab:1d:3b:8e:b0:70:e5:6e:df:f5 +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG +A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 +d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu +dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq +RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy +MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD +VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g +Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi +A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt +ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH +Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC +R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX +hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority +# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority +# Label: "CFCA EV ROOT" +# Serial: 407555286 +# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30 +# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83 +# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD +TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y +aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx +MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j +aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP +T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 +sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL +TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 +/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp +7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz +EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt +hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP +a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot +aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg +TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV +PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv +cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL +tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT +ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL +jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS +ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy +P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 +xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d +Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN +5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe +/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z +AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ +5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GB CA" +# Serial: 157768595616588414422159278966750757568 +# MD5 Fingerprint: a4:eb:b9:61:28:2e:b7:2f:98:b0:35:26:90:99:51:1d +# SHA1 Fingerprint: 0f:f9:40:76:18:d3:d7:6a:4b:98:f0:a8:35:9e:0c:fd:27:ac:cc:ed +# SHA256 Fingerprint: 6b:9c:08:e8:6e:b0:f7:67:cf:ad:65:cd:98:b6:21:49:e5:49:4a:67:f5:84:5e:7b:d1:ed:01:9f:27:b8:6b:d6 +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +# Issuer: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Subject: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Label: "SZAFIR ROOT CA2" +# Serial: 357043034767186914217277344587386743377558296292 +# MD5 Fingerprint: 11:64:c1:89:b0:24:b1:8c:b1:07:7e:89:9e:51:9e:99 +# SHA1 Fingerprint: e2:52:fa:95:3f:ed:db:24:60:bd:6e:28:f3:9c:cc:cf:5e:b3:3f:de +# SHA256 Fingerprint: a1:33:9d:33:28:1a:0b:56:e5:57:d3:d3:2b:1c:e7:f9:36:7e:b0:94:bd:5f:a7:2a:7e:50:04:c8:de:d7:ca:fe +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 +ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw +NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L +cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg +Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN +QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT +3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw +3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 +3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 +BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN +XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF +AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw +8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG +nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP +oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy +d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg +LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA 2" +# Serial: 44979900017204383099463764357512596969 +# MD5 Fingerprint: 6d:46:9e:d9:25:6d:08:23:5b:5e:74:7d:1e:27:db:f2 +# SHA1 Fingerprint: d3:dd:48:3e:2b:bf:4c:05:e8:af:10:f5:fa:76:26:cf:d3:dc:30:92 +# SHA256 Fingerprint: b6:76:f2:ed:da:e8:77:5c:d3:6c:b0:f6:3c:d1:d4:60:39:61:f4:9e:62:65:ba:01:3a:2f:03:07:b6:d0:b8:04 +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB +gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG +A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz +OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ +VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 +b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA +DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn +0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB +OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE +fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E +Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m +o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i +sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW +OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez +Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS +adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n +3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ +F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf +CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 +XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm +djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ +WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb +AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq +P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko +b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj +XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P +5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi +DrW5viSP +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: ca:ff:e2:db:03:d9:cb:4b:e9:0f:ad:84:fd:7b:18:ce +# SHA1 Fingerprint: 01:0c:06:95:a6:98:19:14:ff:bf:5f:c6:b0:b6:95:ea:29:e9:12:a6 +# SHA256 Fingerprint: a0:40:92:9a:02:ce:53:b4:ac:f4:f2:ff:c6:98:1c:e4:49:6f:75:5e:6d:45:fe:0b:2a:69:2b:cd:52:52:3f:36 +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix +DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k +IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT +N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v +dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG +A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh +ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx +QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA +4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 +AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 +4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C +ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV +9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD +gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 +Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq +NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko +LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd +ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I +XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI +M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot +9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V +Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea +j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh +X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ +l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf +bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 +pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK +e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 +vm9qp/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions ECC RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: 81:e5:b4:17:eb:c2:f5:e1:4b:0d:41:7b:49:92:fe:ef +# SHA1 Fingerprint: 9f:f1:71:8d:92:d5:9a:f3:7d:74:97:b4:bc:6f:84:68:0b:ba:b6:66 +# SHA256 Fingerprint: 44:b5:45:aa:8a:25:e6:5a:73:ca:15:dc:27:fc:36:d2:4c:1c:b9:95:3a:06:65:39:b1:15:82:dc:48:7b:48:33 +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN +BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl +bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv +b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ +BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj +YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 +MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 +dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg +QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa +jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi +C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep +lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof +TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +# Issuer: CN=ISRG Root X1 O=Internet Security Research Group +# Subject: CN=ISRG Root X1 O=Internet Security Research Group +# Label: "ISRG Root X1" +# Serial: 172886928669790476064670243504169061120 +# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e +# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8 +# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6 +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +# Issuer: O=FNMT-RCM OU=AC RAIZ FNMT-RCM +# Subject: O=FNMT-RCM OU=AC RAIZ FNMT-RCM +# Label: "AC RAIZ FNMT-RCM" +# Serial: 485876308206448804701554682760554759 +# MD5 Fingerprint: e2:09:04:b4:d3:bd:d1:a0:14:fd:1a:d2:47:c4:57:1d +# SHA1 Fingerprint: ec:50:35:07:b2:15:c4:95:62:19:e2:a8:9a:5b:42:99:2c:4c:2c:20 +# SHA256 Fingerprint: eb:c5:57:0c:29:01:8c:4d:67:b1:aa:12:7b:af:12:f7:03:b4:61:1e:bc:17:b7:da:b5:57:38:94:17:9b:93:fa +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx +CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ +WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ +BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG +Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ +yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf +BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz +WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF +tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z +374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC +IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL +mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 +wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS +MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 +ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet +UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H +YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 +LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 +RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM +LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf +77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N +JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm +fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp +6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp +1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B +9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok +RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv +uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 1 O=Amazon +# Subject: CN=Amazon Root CA 1 O=Amazon +# Label: "Amazon Root CA 1" +# Serial: 143266978916655856878034712317230054538369994 +# MD5 Fingerprint: 43:c6:bf:ae:ec:fe:ad:2f:18:c6:88:68:30:fc:c8:e6 +# SHA1 Fingerprint: 8d:a7:f9:65:ec:5e:fc:37:91:0f:1c:6e:59:fd:c1:cc:6a:6e:de:16 +# SHA256 Fingerprint: 8e:cd:e6:88:4f:3d:87:b1:12:5b:a3:1a:c3:fc:b1:3d:70:16:de:7f:57:cc:90:4f:e1:cb:97:c6:ae:98:19:6e +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 2 O=Amazon +# Subject: CN=Amazon Root CA 2 O=Amazon +# Label: "Amazon Root CA 2" +# Serial: 143266982885963551818349160658925006970653239 +# MD5 Fingerprint: c8:e5:8d:ce:a8:42:e2:7a:c0:2a:5c:7c:9e:26:bf:66 +# SHA1 Fingerprint: 5a:8c:ef:45:d7:a6:98:59:76:7a:8c:8b:44:96:b5:78:cf:47:4b:1a +# SHA256 Fingerprint: 1b:a5:b2:aa:8c:65:40:1a:82:96:01:18:f8:0b:ec:4f:62:30:4d:83:ce:c4:71:3a:19:c3:9c:01:1e:a4:6d:b4 +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 3 O=Amazon +# Subject: CN=Amazon Root CA 3 O=Amazon +# Label: "Amazon Root CA 3" +# Serial: 143266986699090766294700635381230934788665930 +# MD5 Fingerprint: a0:d4:ef:0b:f7:b5:d8:49:95:2a:ec:f5:c4:fc:81:87 +# SHA1 Fingerprint: 0d:44:dd:8c:3c:8c:1a:1a:58:75:64:81:e9:0f:2e:2a:ff:b3:d2:6e +# SHA256 Fingerprint: 18:ce:6c:fe:7b:f1:4e:60:b2:e3:47:b8:df:e8:68:cb:31:d0:2e:bb:3a:da:27:15:69:f5:03:43:b4:6d:b3:a4 +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 4 O=Amazon +# Subject: CN=Amazon Root CA 4 O=Amazon +# Label: "Amazon Root CA 4" +# Serial: 143266989758080763974105200630763877849284878 +# MD5 Fingerprint: 89:bc:27:d5:eb:17:8d:06:6a:69:d5:fd:89:47:b4:cd +# SHA1 Fingerprint: f6:10:84:07:d6:f8:bb:67:98:0c:c2:e2:44:c2:eb:ae:1c:ef:63:be +# SHA256 Fingerprint: e3:5d:28:41:9e:d0:20:25:cf:a6:90:38:cd:62:39:62:45:8d:a5:c6:95:fb:de:a3:c2:2b:0b:fb:25:89:70:92 +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +# Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM +# Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM +# Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1" +# Serial: 1 +# MD5 Fingerprint: dc:00:81:dc:69:2f:3e:2f:b0:3b:f6:3d:5a:91:8e:49 +# SHA1 Fingerprint: 31:43:64:9b:ec:ce:27:ec:ed:3a:3f:0b:8f:0d:e4:e8:91:dd:ee:ca +# SHA256 Fingerprint: 46:ed:c3:68:90:46:d5:3a:45:3f:b3:10:4a:b8:0d:ca:ec:65:8b:26:60:ea:16:29:dd:7e:86:79:90:64:87:16 +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx +GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp +bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w +KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 +BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy +dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG +EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll +IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU +QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT +TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg +LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 +a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr +LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr +N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X +YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ +iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f +AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH +V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf +IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 +lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c +8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf +lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +# Issuer: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. +# Subject: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. +# Label: "GDCA TrustAUTH R5 ROOT" +# Serial: 9009899650740120186 +# MD5 Fingerprint: 63:cc:d9:3d:34:35:5c:6f:53:a3:e2:08:70:48:1f:b4 +# SHA1 Fingerprint: 0f:36:38:5b:81:1a:25:c3:9b:31:4e:83:ca:e9:34:66:70:cc:74:b4 +# SHA256 Fingerprint: bf:ff:8f:d0:44:33:48:7d:6a:8a:a6:0c:1a:29:76:7a:9f:c2:bb:b0:5e:42:0f:71:3a:13:b9:92:89:1d:38:93 +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE +BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0 +MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV +BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w +HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj +Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj +TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u +KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj +qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm +MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12 +ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP +zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk +L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC +jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA +HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC +AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm +DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5 +COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry +L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf +JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg +IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io +2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV +09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ +XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq +T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe +MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +# Issuer: CN=TrustCor RootCert CA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Subject: CN=TrustCor RootCert CA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Label: "TrustCor RootCert CA-1" +# Serial: 15752444095811006489 +# MD5 Fingerprint: 6e:85:f1:dc:1a:00:d3:22:d5:b2:b2:ac:6b:37:05:45 +# SHA1 Fingerprint: ff:bd:cd:e7:82:c8:43:5e:3c:6f:26:86:5c:ca:a8:3a:45:5b:c3:0a +# SHA256 Fingerprint: d4:0e:9c:86:cd:8f:e4:68:c1:77:69:59:f4:9e:a7:74:fa:54:86:84:b6:c4:06:f3:90:92:61:f4:dc:e2:57:5c +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIJANqb7HHzA7AZMA0GCSqGSIb3DQEBCwUAMIGkMQswCQYD +VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk +MCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U +cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29y +IFJvb3RDZXJ0IENBLTEwHhcNMTYwMjA0MTIzMjE2WhcNMjkxMjMxMTcyMzE2WjCB +pDELMAkGA1UEBhMCUEExDzANBgNVBAgMBlBhbmFtYTEUMBIGA1UEBwwLUGFuYW1h +IENpdHkxJDAiBgNVBAoMG1RydXN0Q29yIFN5c3RlbXMgUy4gZGUgUi5MLjEnMCUG +A1UECwweVHJ1c3RDb3IgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYDVQQDDBZU +cnVzdENvciBSb290Q2VydCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAv463leLCJhJrMxnHQFgKq1mqjQCj/IDHUHuO1CAmujIS2CNUSSUQIpid +RtLByZ5OGy4sDjjzGiVoHKZaBeYei0i/mJZ0PmnK6bV4pQa81QBeCQryJ3pS/C3V +seq0iWEk8xoT26nPUu0MJLq5nux+AHT6k61sKZKuUbS701e/s/OojZz0JEsq1pme +9J7+wH5COucLlVPat2gOkEz7cD+PSiyU8ybdY2mplNgQTsVHCJCZGxdNuWxu72CV +EY4hgLW9oHPY0LJ3xEXqWib7ZnZ2+AYfYW0PVcWDtxBWcgYHpfOxGgMFZA6dWorW +hnAbJN7+KIor0Gqw/Hqi3LJ5DotlDwIDAQABo2MwYTAdBgNVHQ4EFgQU7mtJPHo/ +DeOxCbeKyKsZn3MzUOcwHwYDVR0jBBgwFoAU7mtJPHo/DeOxCbeKyKsZn3MzUOcw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBACUY1JGPE+6PHh0RU9otRCkZoB5rMZ5NDp6tPVxBb5UrJKF5mDo4Nvu7Zp5I +/5CQ7z3UuJu0h3U/IJvOcs+hVcFNZKIZBqEHMwwLKeXx6quj7LUKdJDHfXLy11yf +ke+Ri7fc7Waiz45mO7yfOgLgJ90WmMCV1Aqk5IGadZQ1nJBfiDcGrVmVCrDRZ9MZ +yonnMlo2HD6CqFqTvsbQZJG2z9m2GM/bftJlo6bEjhcxwft+dtvTheNYsnd6djts +L1Ac59v2Z3kf9YKVmgenFK+P3CghZwnS1k1aHBkcjndcw5QkPTJrS37UeJSDvjdN +zl/HHk484IkzlQsPpTLWPFp5LBk= +-----END CERTIFICATE----- + +# Issuer: CN=TrustCor RootCert CA-2 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Subject: CN=TrustCor RootCert CA-2 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Label: "TrustCor RootCert CA-2" +# Serial: 2711694510199101698 +# MD5 Fingerprint: a2:e1:f8:18:0b:ba:45:d5:c7:41:2a:bb:37:52:45:64 +# SHA1 Fingerprint: b8:be:6d:cb:56:f1:55:b9:63:d4:12:ca:4e:06:34:c7:94:b2:1c:c0 +# SHA256 Fingerprint: 07:53:e9:40:37:8c:1b:d5:e3:83:6e:39:5d:ae:a5:cb:83:9e:50:46:f1:bd:0e:ae:19:51:cf:10:fe:c7:c9:65 +-----BEGIN CERTIFICATE----- +MIIGLzCCBBegAwIBAgIIJaHfyjPLWQIwDQYJKoZIhvcNAQELBQAwgaQxCzAJBgNV +BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw +IgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRy +dXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0eTEfMB0GA1UEAwwWVHJ1c3RDb3Ig +Um9vdENlcnQgQ0EtMjAeFw0xNjAyMDQxMjMyMjNaFw0zNDEyMzExNzI2MzlaMIGk +MQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEg +Q2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYD +VQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRy +dXN0Q29yIFJvb3RDZXJ0IENBLTIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCnIG7CKqJiJJWQdsg4foDSq8GbZQWU9MEKENUCrO2fk8eHyLAnK0IMPQo+ +QVqedd2NyuCb7GgypGmSaIwLgQ5WoD4a3SwlFIIvl9NkRvRUqdw6VC0xK5mC8tkq +1+9xALgxpL56JAfDQiDyitSSBBtlVkxs1Pu2YVpHI7TYabS3OtB0PAx1oYxOdqHp +2yqlO/rOsP9+aij9JxzIsekp8VduZLTQwRVtDr4uDkbIXvRR/u8OYzo7cbrPb1nK +DOObXUm4TOJXsZiKQlecdu/vvdFoqNL0Cbt3Nb4lggjEFixEIFapRBF37120Hape +az6LMvYHL1cEksr1/p3C6eizjkxLAjHZ5DxIgif3GIJ2SDpxsROhOdUuxTTCHWKF +3wP+TfSvPd9cW436cOGlfifHhi5qjxLGhF5DUVCcGZt45vz27Ud+ez1m7xMTiF88 +oWP7+ayHNZ/zgp6kPwqcMWmLmaSISo5uZk3vFsQPeSghYA2FFn3XVDjxklb9tTNM +g9zXEJ9L/cb4Qr26fHMC4P99zVvh1Kxhe1fVSntb1IVYJ12/+CtgrKAmrhQhJ8Z3 +mjOAPF5GP/fDsaOGM8boXg25NSyqRsGFAnWAoOsk+xWq5Gd/bnc/9ASKL3x74xdh +8N0JqSDIvgmk0H5Ew7IwSjiqqewYmgeCK9u4nBit2uBGF6zPXQIDAQABo2MwYTAd +BgNVHQ4EFgQU2f4hQG6UnrybPZx9mCAZ5YwwYrIwHwYDVR0jBBgwFoAU2f4hQG6U +nrybPZx9mCAZ5YwwYrIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQELBQADggIBAJ5Fngw7tu/hOsh80QA9z+LqBrWyOrsGS2h60COX +dKcs8AjYeVrXWoSK2BKaG9l9XE1wxaX5q+WjiYndAfrs3fnpkpfbsEZC89NiqpX+ +MWcUaViQCqoL7jcjx1BRtPV+nuN79+TMQjItSQzL/0kMmx40/W5ulop5A7Zv2wnL +/V9lFDfhOPXzYRZY5LVtDQsEGz9QLX+zx3oaFoBg+Iof6Rsqxvm6ARppv9JYx1RX +CI/hOWB3S6xZhBqI8d3LT3jX5+EzLfzuQfogsL7L9ziUwOHQhQ+77Sxzq+3+knYa +ZH9bDTMJBzN7Bj8RpFxwPIXAz+OQqIN3+tvmxYxoZxBnpVIt8MSZj3+/0WvitUfW +2dCFmU2Umw9Lje4AWkcdEQOsQRivh7dvDDqPys/cA8GiCcjl/YBeyGBCARsaU1q7 +N6a3vLqE6R5sGtRk2tRD/pOLS/IseRYQ1JMLiI+h2IYURpFHmygk71dSTlxCnKr3 +Sewn6EAes6aJInKc9Q0ztFijMDvd1GpUk74aTfOTlPf8hAs/hCBcNANExdqtvArB +As8e5ZTZ845b2EzwnexhF7sUMlQMAimTHpKG9n/v55IFDlndmQguLvqcAFLTxWYp +5KeXRKQOKIETNcX2b2TmQcTVL8w0RSXPQQCWPUouwpaYT05KnJe32x+SMsj/D1Fu +1uwJ +-----END CERTIFICATE----- + +# Issuer: CN=TrustCor ECA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Subject: CN=TrustCor ECA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Label: "TrustCor ECA-1" +# Serial: 9548242946988625984 +# MD5 Fingerprint: 27:92:23:1d:0a:f5:40:7c:e9:e6:6b:9d:d8:f5:e7:6c +# SHA1 Fingerprint: 58:d1:df:95:95:67:6b:63:c0:f0:5b:1c:17:4d:8b:84:0b:c8:78:bd +# SHA256 Fingerprint: 5a:88:5d:b1:9c:01:d9:12:c5:75:93:88:93:8c:af:bb:df:03:1a:b2:d4:8e:91:ee:15:58:9b:42:97:1d:03:9c +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIJAISCLF8cYtBAMA0GCSqGSIb3DQEBCwUAMIGcMQswCQYD +VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk +MCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U +cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFzAVBgNVBAMMDlRydXN0Q29y +IEVDQS0xMB4XDTE2MDIwNDEyMzIzM1oXDTI5MTIzMTE3MjgwN1owgZwxCzAJBgNV +BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw +IgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRy +dXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAwwOVHJ1c3RDb3Ig +RUNBLTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPj+ARtZ+odnbb +3w9U73NjKYKtR8aja+3+XzP4Q1HpGjORMRegdMTUpwHmspI+ap3tDvl0mEDTPwOA +BoJA6LHip1GnHYMma6ve+heRK9jGrB6xnhkB1Zem6g23xFUfJ3zSCNV2HykVh0A5 +3ThFEXXQmqc04L/NyFIduUd+Dbi7xgz2c1cWWn5DkR9VOsZtRASqnKmcp0yJF4Ou +owReUoCLHhIlERnXDH19MURB6tuvsBzvgdAsxZohmz3tQjtQJvLsznFhBmIhVE5/ +wZ0+fyCMgMsq2JdiyIMzkX2woloPV+g7zPIlstR8L+xNxqE6FXrntl019fZISjZF +ZtS6mFjBAgMBAAGjYzBhMB0GA1UdDgQWBBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAf +BgNVHSMEGDAWgBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAPBgNVHRMBAf8EBTADAQH/ +MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEABT41XBVwm8nHc2Fv +civUwo/yQ10CzsSUuZQRg2dd4mdsdXa/uwyqNsatR5Nj3B5+1t4u/ukZMjgDfxT2 +AHMsWbEhBuH7rBiVDKP/mZb3Kyeb1STMHd3BOuCYRLDE5D53sXOpZCz2HAF8P11F +hcCF5yWPldwX8zyfGm6wyuMdKulMY/okYWLW2n62HGz1Ah3UKt1VkOsqEUc8Ll50 +soIipX1TH0XsJ5F95yIW6MBoNtjG8U+ARDL54dHRHareqKucBK+tIA5kmE2la8BI +WJZpTdwHjFGTot+fDz2LYLSCjaoITmJF4PkL0uDgPFveXHEnJcLmA4GLEFPjx1Wi +tJ/X5g== +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com Root Certification Authority RSA O=SSL Corporation +# Subject: CN=SSL.com Root Certification Authority RSA O=SSL Corporation +# Label: "SSL.com Root Certification Authority RSA" +# Serial: 8875640296558310041 +# MD5 Fingerprint: 86:69:12:c0:70:f1:ec:ac:ac:c2:d5:bc:a5:5b:a1:29 +# SHA1 Fingerprint: b7:ab:33:08:d1:ea:44:77:ba:14:80:12:5a:6f:bd:a9:36:49:0c:bb +# SHA256 Fingerprint: 85:66:6a:56:2e:e0:be:5c:e9:25:c1:d8:89:0a:6f:76:a8:7e:c1:6d:4d:7d:5f:29:ea:74:19:cf:20:12:3b:69 +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE +BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK +DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz +OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R +xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX +qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC +C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 +6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh +/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF +YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E +JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc +US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 +ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm ++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi +M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV +cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc +Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs +PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ +q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 +cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr +a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I +H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y +K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu +nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf +oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY +Ic2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com Root Certification Authority ECC O=SSL Corporation +# Subject: CN=SSL.com Root Certification Authority ECC O=SSL Corporation +# Label: "SSL.com Root Certification Authority ECC" +# Serial: 8495723813297216424 +# MD5 Fingerprint: 2e:da:e4:39:7f:9c:8f:37:d1:70:9f:26:17:51:3a:8e +# SHA1 Fingerprint: c3:19:7c:39:24:e6:54:af:1b:c4:ab:20:95:7a:e2:c3:0e:13:02:6a +# SHA256 Fingerprint: 34:17:bb:06:cc:60:07:da:1b:96:1c:92:0b:8a:b4:ce:3f:ad:82:0e:4a:a3:0b:9a:cb:c4:a7:4e:bd:ce:bc:65 +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz +WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 +b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS +b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI +7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg +CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD +VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T +kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ +gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation +# Subject: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation +# Label: "SSL.com EV Root Certification Authority RSA R2" +# Serial: 6248227494352943350 +# MD5 Fingerprint: e1:1e:31:58:1a:ae:54:53:02:f6:17:6a:11:7b:4d:95 +# SHA1 Fingerprint: 74:3a:f0:52:9b:d0:32:a0:f4:4a:83:cd:d4:ba:a9:7b:7c:2e:c4:9a +# SHA256 Fingerprint: 2e:7b:f1:6c:c2:24:85:a7:bb:e2:aa:86:96:75:07:61:b0:ae:39:be:3b:2f:e9:d0:cc:6d:4e:f7:34:91:42:5c +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV +BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE +CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy +MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G +A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD +DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq +M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf +OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa +4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 +HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR +aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA +b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ +Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV +PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO +pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu +UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY +MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 +9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW +s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 +Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg +cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM +79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz +/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt +ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm +Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK +QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ +w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi +S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 +mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation +# Subject: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation +# Label: "SSL.com EV Root Certification Authority ECC" +# Serial: 3182246526754555285 +# MD5 Fingerprint: 59:53:22:65:83:42:01:54:c0:ce:42:b9:5a:7c:f2:90 +# SHA1 Fingerprint: 4c:dd:51:a3:d1:f5:20:32:14:b0:c6:c5:32:23:03:91:c7:46:42:6d +# SHA256 Fingerprint: 22:a2:c1:f7:bd:ed:70:4c:c1:e7:01:b5:f4:08:c3:10:88:0f:e9:56:b5:de:2a:4a:44:f9:9c:87:3a:25:a7:c8 +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx +NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv +bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA +VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku +WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX +5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ +ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg +h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 +# Label: "GlobalSign Root CA - R6" +# Serial: 1417766617973444989252670301619537 +# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae +# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1 +# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69 +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg +MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx +MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET +MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI +xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k +ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD +aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw +LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw +1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX +k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2 +SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h +bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n +WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY +rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce +MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu +bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt +Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61 +55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj +vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf +cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz +oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp +nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs +pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v +JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R +8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 +5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GC CA" +# Serial: 44084345621038548146064804565436152554 +# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23 +# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31 +# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw +CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 +bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg +Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ +BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu +ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS +b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni +eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W +p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T +rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV +57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg +Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R1 O=Google Trust Services LLC +# Subject: CN=GTS Root R1 O=Google Trust Services LLC +# Label: "GTS Root R1" +# Serial: 146587175971765017618439757810265552097 +# MD5 Fingerprint: 82:1a:ef:d4:d2:4a:f2:9f:e2:3d:97:06:14:70:72:85 +# SHA1 Fingerprint: e1:c9:50:e6:ef:22:f8:4c:56:45:72:8b:92:20:60:d7:d5:a7:a3:e8 +# SHA256 Fingerprint: 2a:57:54:71:e3:13:40:bc:21:58:1c:bd:2c:f1:3e:15:84:63:20:3e:ce:94:bc:f9:d3:cc:19:6b:f0:9a:54:72 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH +MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM +QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy +MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl +cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM +f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX +mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7 +zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P +fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc +vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4 +Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp +zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO +Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW +k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+ +DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF +lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW +Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1 +d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z +XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR +gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3 +d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv +J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg +DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM ++SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy +F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9 +SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws +E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R2 O=Google Trust Services LLC +# Subject: CN=GTS Root R2 O=Google Trust Services LLC +# Label: "GTS Root R2" +# Serial: 146587176055767053814479386953112547951 +# MD5 Fingerprint: 44:ed:9a:0e:a4:09:3b:00:f2:ae:4c:a3:c6:61:b0:8b +# SHA1 Fingerprint: d2:73:96:2a:2a:5e:39:9f:73:3f:e1:c7:1e:64:3f:03:38:34:fc:4d +# SHA256 Fingerprint: c4:5d:7b:b0:8e:6d:67:e6:2e:42:35:11:0b:56:4e:5f:78:fd:92:ef:05:8c:84:0a:ea:4e:64:55:d7:58:5c:60 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBH +MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM +QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy +MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl +cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv +CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3Kg +GjSY6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9Bu +XvAuMC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOd +re7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXu +PuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1 +mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K +8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqj +x5RWIr9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsR +nTKaG73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0 +kzCqgc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9Ok +twIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBALZp +8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT +vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiT +z9D2PGcDFWEJ+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiA +pJiS4wGWAqoC7o87xdFtCjMwc3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvb +pxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3DaWsYDQvTtN6LwG1BUSw7YhN4ZKJmB +R64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5rn/WkhLx3+WuXrD5R +RaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56GtmwfuNmsk +0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC +5AwiWVIQ7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiF +izoHCBy69Y9Vmhh1fuXsgWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLn +yOd/xCxgXS/Dr55FBcOEArf9LAhST4Ldo/DUhgkC +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R3 O=Google Trust Services LLC +# Subject: CN=GTS Root R3 O=Google Trust Services LLC +# Label: "GTS Root R3" +# Serial: 146587176140553309517047991083707763997 +# MD5 Fingerprint: 1a:79:5b:6b:04:52:9c:5d:c7:74:33:1b:25:9a:f9:25 +# SHA1 Fingerprint: 30:d4:24:6f:07:ff:db:91:89:8a:0b:e9:49:66:11:eb:8c:5e:46:e5 +# SHA256 Fingerprint: 15:d5:b8:77:46:19:ea:7d:54:ce:1c:a6:d0:b0:c4:03:e0:37:a9:17:f1:31:e8:a0:4e:1e:6b:7a:71:ba:bc:e5 +-----BEGIN CERTIFICATE----- +MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout +736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2A +DDL24CejQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFuk +fCPAlaUs3L6JbyO5o91lAFJekazInXJ0glMLfalAvWhgxeG4VDvBNhcl2MG9AjEA +njWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOaKaqW04MjyaR7YbPMAuhd +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R4 O=Google Trust Services LLC +# Subject: CN=GTS Root R4 O=Google Trust Services LLC +# Label: "GTS Root R4" +# Serial: 146587176229350439916519468929765261721 +# MD5 Fingerprint: 5d:b6:6a:c4:60:17:24:6a:1a:99:a8:4b:ee:5e:b4:26 +# SHA1 Fingerprint: 2a:1d:60:27:d9:4a:b1:0a:1c:4d:91:5c:cd:33:a0:cb:3e:2d:54:cb +# SHA256 Fingerprint: 71:cc:a5:39:1f:9e:79:4b:04:80:25:30:b3:63:e1:21:da:8a:30:43:bb:26:66:2f:ea:4d:ca:7f:c9:51:a4:bd +-----BEGIN CERTIFICATE----- +MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu +hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/l +xKvRHYqjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0 +CMRw3J5QdCHojXohw0+WbhXRIjVhLfoIN+4Zba3bssx9BzT1YBkstTTZbyACMANx +sbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11xzPKwTdb+mciUqXWi4w== +-----END CERTIFICATE----- + +# Issuer: CN=UCA Global G2 Root O=UniTrust +# Subject: CN=UCA Global G2 Root O=UniTrust +# Label: "UCA Global G2 Root" +# Serial: 124779693093741543919145257850076631279 +# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8 +# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a +# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9 +MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH +bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x +CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds +b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr +b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9 +kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm +VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R +VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc +C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj +tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY +D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv +j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl +NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6 +iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP +O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV +ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj +L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl +1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU +b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV +PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj +y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb +EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg +DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI ++Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy +YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX +UB+K+wb1whnw0A== +-----END CERTIFICATE----- + +# Issuer: CN=UCA Extended Validation Root O=UniTrust +# Subject: CN=UCA Extended Validation Root O=UniTrust +# Label: "UCA Extended Validation Root" +# Serial: 106100277556486529736699587978573607008 +# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2 +# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a +# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH +MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF +eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx +MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV +BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog +D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS +sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop +O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk +sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi +c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj +VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz +KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/ +TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G +sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs +1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD +fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN +l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ +VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5 +c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp +4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s +t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj +2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO +vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C +xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx +cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM +fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax +-----END CERTIFICATE----- + +# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 +# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 +# Label: "Certigna Root CA" +# Serial: 269714418870597844693661054334862075617 +# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77 +# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43 +# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68 +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw +WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw +MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x +MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD +VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX +BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO +ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M +CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu +I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm +TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh +C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf +ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz +IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT +Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k +JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5 +hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB +GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov +L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo +dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr +aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq +hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L +6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG +HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6 +0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB +lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi +o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1 +gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v +faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63 +Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh +jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw +3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI +# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI +# Label: "emSign Root CA - G1" +# Serial: 235931866688319308814040 +# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac +# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c +# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67 +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD +VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU +ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH +MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO +MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv +Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz +f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO +8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq +d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM +tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt +Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB +o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x +PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM +wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d +GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH +6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby +RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI +# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI +# Label: "emSign ECC Root CA - G3" +# Serial: 287880440101571086945156 +# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40 +# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1 +# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG +EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo +bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ +TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s +b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 +WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS +fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB +zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB +CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD ++JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI +# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI +# Label: "emSign Root CA - C1" +# Serial: 825510296613316004955058 +# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68 +# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01 +# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG +A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg +SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v +dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ +BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ +HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH +3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH +GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c +xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1 +aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq +TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87 +/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4 +kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG +YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT ++xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo +WXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI +# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI +# Label: "emSign ECC Root CA - C3" +# Serial: 582948710642506000014504 +# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5 +# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66 +# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3 +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG +EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx +IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND +IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci +MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti +sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O +BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB +Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c +3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J +0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post +# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post +# Label: "Hongkong Post Root CA 3" +# Serial: 46170865288971385588281144162979347873371282084 +# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0 +# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02 +# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6 +-----BEGIN CERTIFICATE----- +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 +mpv0 +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - G4" +# Serial: 289383649854506086828220374796556676440 +# MD5 Fingerprint: 89:53:f1:83:23:b7:7c:8e:05:f1:8c:71:38:4e:1f:88 +# SHA1 Fingerprint: 14:88:4e:86:26:37:b0:26:af:59:62:5c:40:77:ec:35:29:ba:96:01 +# SHA256 Fingerprint: db:35:17:d1:f6:73:2a:2d:5a:b9:7c:53:3e:c7:07:79:ee:32:70:a6:2f:b4:ac:42:38:37:24:60:e6:f0:1e:88 +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw +gb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL +Ex9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg +MjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAw +BgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0 +MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1 +c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJ +bmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3Qg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3DumSXbcr3DbVZwbPLqGgZ +2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV3imz/f3E +T+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j +5pds8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAM +C1rlLAHGVK/XqsEQe9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73T +DtTUXm6Hnmo9RR3RXRv06QqsYJn7ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNX +wbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5XxNMhIWNlUpEbsZmOeX7m640A +2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV7rtNOzK+mndm +nqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 +dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwl +N4y6mACXi0mWHv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNj +c0kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9nMA0GCSqGSIb3DQEBCwUAA4ICAQAS +5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4QjbRaZIxowLByQzTS +Gwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht7LGr +hFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/ +B7NTeLUKYvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uI +AeV8KEsD+UmDfLJ/fOPtjqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbw +H5Lk6rWS02FREAutp9lfx1/cH6NcjKF+m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+ +b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKWRGhXxNUzzxkvFMSUHHuk +2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjAJOgc47Ol +IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk +5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY +n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw== +-----END CERTIFICATE----- + +# Issuer: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation +# Subject: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation +# Label: "Microsoft ECC Root Certificate Authority 2017" +# Serial: 136839042543790627607696632466672567020 +# MD5 Fingerprint: dd:a1:03:e6:4a:93:10:d1:bf:f0:19:42:cb:fe:ed:67 +# SHA1 Fingerprint: 99:9a:64:c3:7f:f4:7d:9f:ab:95:f1:47:69:89:14:60:ee:c4:c3:c5 +# SHA256 Fingerprint: 35:8d:f3:9d:76:4a:f9:e1:b7:66:e9:c9:72:df:35:2e:e1:5c:fa:c2:27:af:6a:d1:d7:0e:8e:4a:6e:dc:ba:02 +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD +VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw +MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy +b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR +ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb +hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3 +FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV +L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB +iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +# Issuer: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation +# Subject: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation +# Label: "Microsoft RSA Root Certificate Authority 2017" +# Serial: 40975477897264996090493496164228220339 +# MD5 Fingerprint: 10:ff:00:ff:cf:c9:f8:c7:7a:c0:ee:35:8e:c9:0f:47 +# SHA1 Fingerprint: 73:a5:e6:4a:3b:ff:83:16:ff:0e:dc:cc:61:8a:90:6e:4e:ae:4d:74 +# SHA256 Fingerprint: c7:41:f7:0f:4b:2a:8d:88:bf:2e:71:c1:41:22:ef:53:ef:10:eb:a0:cf:a5:e6:4c:fa:20:f4:18:85:30:73:e0 +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N +aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ +Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0 +ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1 +HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm +gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ +jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc +aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG +YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6 +W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K +UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH ++FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q +W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC +LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC +gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6 +tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh +SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2 +TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3 +pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR +xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp +GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9 +dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN +AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB +RA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +# Issuer: CN=e-Szigno Root CA 2017 O=Microsec Ltd. +# Subject: CN=e-Szigno Root CA 2017 O=Microsec Ltd. +# Label: "e-Szigno Root CA 2017" +# Serial: 411379200276854331539784714 +# MD5 Fingerprint: de:1f:f6:9e:84:ae:a7:b4:21:ce:1e:58:7d:d1:84:98 +# SHA1 Fingerprint: 89:d4:83:03:4f:9e:9a:48:80:5f:72:37:d4:a9:a6:ef:cb:7c:1f:d1 +# SHA256 Fingerprint: be:b0:0b:30:83:9b:9b:c3:2c:32:e4:44:79:05:95:06:41:f2:64:21:b1:5e:d0:89:19:8b:51:8a:e2:ea:1b:99 +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV +BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk +LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv +b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ +BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg +THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v +IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv +xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H +Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB +eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo +jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ ++efcMQ== +-----END CERTIFICATE----- + +# Issuer: O=CERTSIGN SA OU=certSIGN ROOT CA G2 +# Subject: O=CERTSIGN SA OU=certSIGN ROOT CA G2 +# Label: "certSIGN Root CA G2" +# Serial: 313609486401300475190 +# MD5 Fingerprint: 8c:f1:75:8a:c6:19:cf:94:b7:f7:65:20:87:c3:97:c7 +# SHA1 Fingerprint: 26:f9:93:b4:ed:3d:28:27:b0:b9:4b:a7:e9:15:1d:a3:8d:92:e5:32 +# SHA256 Fingerprint: 65:7c:fe:2f:a7:3f:aa:38:46:25:71:f3:32:a2:36:3a:46:fc:e7:02:09:51:71:07:02:cd:fb:b6:ee:da:33:05 +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV +BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g +Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ +BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ +R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF +dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw +vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ +uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp +n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs +cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW +xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P +rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF +DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx +DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy +LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C +eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ +d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq +kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl +qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0 +OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c +NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk +ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO +pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj +03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk +PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE +1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX +QRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +# Issuer: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc. +# Subject: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc. +# Label: "Trustwave Global Certification Authority" +# Serial: 1846098327275375458322922162 +# MD5 Fingerprint: f8:1c:18:2d:2f:ba:5f:6d:a1:6c:bc:c7:ab:91:c7:0e +# SHA1 Fingerprint: 2f:8f:36:4f:e1:58:97:44:21:59:87:a5:2a:9a:d0:69:95:26:7f:b5 +# SHA256 Fingerprint: 97:55:20:15:f5:dd:fc:3c:87:88:c0:06:94:45:55:40:88:94:45:00:84:f1:00:86:70:86:bc:1a:2b:b5:8d:c8 +-----BEGIN CERTIFICATE----- +MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw +CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x +ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1 +c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx +OTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI +SWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +ALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn +swuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu +7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8 +1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW +80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP +JqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l +RtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw +hI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10 +coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc +BW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n +twiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud +DwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W +0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe +uyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q +lG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB +aCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE +sLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT +MaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe +qu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh +VicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8 +h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9 +EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK +yeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- + +# Issuer: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc. +# Subject: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc. +# Label: "Trustwave Global ECC P256 Certification Authority" +# Serial: 4151900041497450638097112925 +# MD5 Fingerprint: 5b:44:e3:8d:5d:36:86:26:e8:0d:05:d2:59:a7:83:54 +# SHA1 Fingerprint: b4:90:82:dd:45:0c:be:8b:5b:b1:66:d3:e2:a4:08:26:cd:ed:42:cf +# SHA256 Fingerprint: 94:5b:bc:82:5e:a5:54:f4:89:d1:fd:51:a7:3d:df:2e:a6:24:ac:70:19:a0:52:05:22:5c:22:a7:8c:cf:a8:b4 +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN +FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w +DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw +CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh +DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- + +# Issuer: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc. +# Subject: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc. +# Label: "Trustwave Global ECC P384 Certification Authority" +# Serial: 2704997926503831671788816187 +# MD5 Fingerprint: ea:cf:60:c4:3b:b9:15:29:40:a1:97:ed:78:27:93:d6 +# SHA1 Fingerprint: e7:f3:a3:c8:cf:6f:c3:04:2e:6d:0e:67:32:c5:9e:68:95:0d:5e:d2 +# SHA256 Fingerprint: 55:90:38:59:c8:c0:c3:eb:b8:75:9e:ce:4e:25:57:22:5f:f5:75:8b:bd:38:eb:d4:82:76:60:1e:1b:d5:80:97 +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB +BAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ +j9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF +1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G +A1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3 +AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC +MGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu +Sw== +-----END CERTIFICATE----- + +# Issuer: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. +# Subject: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. +# Label: "NAVER Global Root Certification Authority" +# Serial: 9013692873798656336226253319739695165984492813 +# MD5 Fingerprint: c8:7e:41:f6:25:3b:f5:09:b3:17:e8:46:3d:bf:d0:9b +# SHA1 Fingerprint: 8f:6b:f2:a9:27:4a:da:14:a0:c4:f4:8e:61:27:f9:c0:1e:78:5d:d1 +# SHA256 Fingerprint: 88:f4:38:dc:f8:ff:d1:fa:8f:42:91:15:ff:e5:f8:2a:e1:e0:6e:0c:70:c3:75:fa:ad:71:7b:34:a4:9e:72:65 +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM +BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG +T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx +CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD +b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA +iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH +38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE +HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz +kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP +szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq +vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf +nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG +YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo +0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a +CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K +AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I +36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN +qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj +cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm ++LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL +hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe +lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7 +p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8 +piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR +LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX +5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO +dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul +9XXeifdy +-----END CERTIFICATE----- + +# Issuer: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres +# Subject: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres +# Label: "AC RAIZ FNMT-RCM SERVIDORES SEGUROS" +# Serial: 131542671362353147877283741781055151509 +# MD5 Fingerprint: 19:36:9c:52:03:2f:d2:d1:bb:23:cc:dd:1e:12:55:bb +# SHA1 Fingerprint: 62:ff:d9:9e:c0:65:0d:03:ce:75:93:d2:ed:3f:2d:32:c9:e3:e5:4a +# SHA256 Fingerprint: 55:41:53:b1:3d:2c:f9:dd:b7:53:bf:be:1a:4e:0a:e0:8d:0a:a4:18:70:58:fe:60:a2:b8:62:b2:e4:b8:7b:cb +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw +CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw +FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S +Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5 +MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL +DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS +QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH +sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK +Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu +SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC +MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy +v+c= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign Root R46 O=GlobalSign nv-sa +# Subject: CN=GlobalSign Root R46 O=GlobalSign nv-sa +# Label: "GlobalSign Root R46" +# Serial: 1552617688466950547958867513931858518042577 +# MD5 Fingerprint: c4:14:30:e4:fa:66:43:94:2a:6a:1b:24:5f:19:d0:ef +# SHA1 Fingerprint: 53:a2:b0:4b:ca:6b:d6:45:e6:39:8a:8e:c4:0d:d2:bf:77:c3:a2:90 +# SHA256 Fingerprint: 4f:a3:12:6d:8d:3a:11:d1:c4:85:5a:4f:80:7c:ba:d6:cf:91:9d:3a:5a:88:b0:3b:ea:2c:63:72:d9:3c:40:c9 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA +MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD +VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy +MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt +c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ +OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG +vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud +316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo +0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE +y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF +zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE ++cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN +I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs +x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa +ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC +4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4 +7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti +2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk +pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF +FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt +rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk +ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5 +u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP +4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6 +N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3 +vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6 +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign Root E46 O=GlobalSign nv-sa +# Subject: CN=GlobalSign Root E46 O=GlobalSign nv-sa +# Label: "GlobalSign Root E46" +# Serial: 1552617690338932563915843282459653771421763 +# MD5 Fingerprint: b5:b8:66:ed:de:08:83:e3:c9:e2:01:34:06:ac:51:6f +# SHA1 Fingerprint: 39:b4:6c:d5:fe:80:06:eb:e2:2f:4a:bb:08:33:a0:af:db:b9:dd:84 +# SHA256 Fingerprint: cb:b9:c4:4d:84:b8:04:3e:10:50:ea:31:a6:9f:51:49:55:d7:bf:d2:e2:c6:b4:93:01:01:9a:d6:1d:9f:50:58 +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx +CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD +ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw +MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex +HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq +R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd +yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ +7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8 ++RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +# Issuer: CN=GLOBALTRUST 2020 O=e-commerce monitoring GmbH +# Subject: CN=GLOBALTRUST 2020 O=e-commerce monitoring GmbH +# Label: "GLOBALTRUST 2020" +# Serial: 109160994242082918454945253 +# MD5 Fingerprint: 8a:c7:6f:cb:6d:e3:cc:a2:f1:7c:83:fa:0e:78:d7:e8 +# SHA1 Fingerprint: d0:67:c1:13:51:01:0c:aa:d0:c7:6a:65:37:31:16:26:4f:53:71:a2 +# SHA256 Fingerprint: 9a:29:6a:51:82:d1:d4:51:a2:e3:7f:43:9b:74:da:af:a2:67:52:33:29:f9:0f:9a:0d:20:07:c3:34:e2:3c:9a +-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkG +A1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkw +FwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYx +MDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9u +aXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMIICIjANBgkq +hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWiD59b +RatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9Z +YybNpyrOVPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3 +QWPKzv9pj2gOlTblzLmMCcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPw +yJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCmfecqQjuCgGOlYx8ZzHyyZqjC0203b+J+ +BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKAA1GqtH6qRNdDYfOiaxaJ +SaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9ORJitHHmkH +r96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj0 +4KlGDfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9Me +dKZssCz3AwyIDMvUclOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIw +q7ejMZdnrY8XD2zHc+0klGvIg5rQmjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2 +nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1UdIwQYMBaAFNwu +H9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA +VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJC +XtzoRlgHNQIw4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd +6IwPS3BD0IL/qMy/pJTAvoe9iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf ++I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS8cE54+X1+NZK3TTN+2/BT+MAi1bi +kvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2HcqtbepBEX4tdJP7 +wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxSvTOB +TI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6C +MUO+1918oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn +4rnvyOL2NSl6dPrFf4IFYqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+I +aFvowdlxfv1k7/9nR4hYJS8+hge9+6jlgqispdNpQ80xiEmEU5LAsTkbOYMBMMTy +qfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== +-----END CERTIFICATE----- + +# Issuer: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz +# Subject: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz +# Label: "ANF Secure Server Root CA" +# Serial: 996390341000653745 +# MD5 Fingerprint: 26:a6:44:5a:d9:af:4e:2f:b2:1d:b6:65:b0:4e:e8:96 +# SHA1 Fingerprint: 5b:6e:68:d0:cc:15:b6:a0:5f:1e:c1:5f:ae:02:fc:6b:2f:5d:6f:74 +# SHA256 Fingerprint: fb:8f:ec:75:91:69:b9:10:6b:1e:51:16:44:c6:18:c5:13:04:37:3f:6c:06:43:08:8d:8b:ef:fd:1b:99:75:99 +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Subject: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Label: "Certum EC-384 CA" +# Serial: 160250656287871593594747141429395092468 +# MD5 Fingerprint: b6:65:b3:96:60:97:12:a1:ec:4e:e1:3d:a3:c6:c9:f1 +# SHA1 Fingerprint: f3:3e:78:3c:ac:df:f4:a2:cc:ac:67:55:69:56:d7:e5:16:3c:e1:ed +# SHA256 Fingerprint: 6b:32:80:85:62:53:18:aa:50:d1:73:c9:8d:8b:da:09:d5:7e:27:41:3d:11:4c:f7:87:a0:f5:d0:6c:03:0c:f6 +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw +CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw +JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT +EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0 +WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT +LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX +BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE +KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm +Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8 +EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J +UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn +nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Root CA" +# Serial: 40870380103424195783807378461123655149 +# MD5 Fingerprint: 51:e1:c2:e7:fe:4c:84:af:59:0e:2f:f4:54:6f:ea:29 +# SHA1 Fingerprint: c8:83:44:c0:18:ae:9f:cc:f1:87:b7:8f:22:d1:c5:d7:45:84:ba:e5 +# SHA256 Fingerprint: fe:76:96:57:38:55:77:3e:37:a9:5e:7a:d4:d9:cc:96:c3:01:57:c1:5d:31:76:5b:a9:b1:57:04:e1:ae:78:fd +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique +# Subject: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique +# Label: "TunTrust Root CA" +# Serial: 108534058042236574382096126452369648152337120275 +# MD5 Fingerprint: 85:13:b9:90:5b:36:5c:b6:5e:b8:5a:f8:e0:31:57:b4 +# SHA1 Fingerprint: cf:e9:70:84:0f:e0:73:0f:9d:f6:0c:7f:2c:4b:ee:20:46:34:9c:bb +# SHA256 Fingerprint: 2e:44:10:2a:b5:8c:b8:54:19:45:1c:8e:19:d9:ac:f3:66:2c:af:bc:61:4b:6a:53:96:0a:30:f7:d0:e2:eb:41 +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL +BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg +Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv +b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG +EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u +IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ +n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd +2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF +VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ +GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF +li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU +r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2 +eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb +MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg +jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB +7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW +5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE +ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 +90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z +xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu +QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4 +FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH +22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP +xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn +dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5 +Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b +nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ +CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH +u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj +d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= +-----END CERTIFICATE----- + +# Issuer: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Subject: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Label: "HARICA TLS RSA Root CA 2021" +# Serial: 76817823531813593706434026085292783742 +# MD5 Fingerprint: 65:47:9b:58:86:dd:2c:f0:fc:a2:84:1f:1e:96:c4:91 +# SHA1 Fingerprint: 02:2d:05:82:fa:88:ce:14:0c:06:79:de:7f:14:10:e9:45:d7:a5:6d +# SHA256 Fingerprint: d9:5d:0e:8e:da:79:52:5b:f9:be:b1:1b:14:d2:10:0d:32:94:98:5f:0c:62:d9:fa:bd:9c:d9:99:ec:cb:7b:1d +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs +MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg +Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL +MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l +mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE +4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv +a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M +pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw +Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b +LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY +AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB +AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq +E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr +W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ +CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU +X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3 +f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja +H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP +JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P +zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt +jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0 +/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT +BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79 +aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW +xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU +63ZTGI0RmLo= +-----END CERTIFICATE----- + +# Issuer: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Subject: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Label: "HARICA TLS ECC Root CA 2021" +# Serial: 137515985548005187474074462014555733966 +# MD5 Fingerprint: ae:f7:4c:e5:66:35:d1:b7:9b:8c:22:93:74:d3:4b:b0 +# SHA1 Fingerprint: bc:b0:c1:9d:e9:98:92:70:19:38:57:e9:8d:a7:b4:5d:6e:ee:01:48 +# SHA256 Fingerprint: 3f:99:cc:47:4a:cf:ce:4d:fe:d5:87:94:66:5e:47:8d:15:47:73:9f:2e:78:0f:1b:b4:ca:9b:13:30:97:d4:01 +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw +CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh +cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v +dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG +A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7 +KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y +STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD +AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw +SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN +nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- diff --git a/openpype/vendor/python/python_2/certifi/core.py b/openpype/vendor/python/python_2/certifi/core.py new file mode 100644 index 0000000000..5d2b8cd32f --- /dev/null +++ b/openpype/vendor/python/python_2/certifi/core.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +""" +certifi.py +~~~~~~~~~~ + +This module returns the installation location of cacert.pem or its contents. +""" +import os + +try: + from importlib.resources import path as get_path, read_text + + _CACERT_CTX = None + _CACERT_PATH = None + + def where(): + # This is slightly terrible, but we want to delay extracting the file + # in cases where we're inside of a zipimport situation until someone + # actually calls where(), but we don't want to re-extract the file + # on every call of where(), so we'll do it once then store it in a + # global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you to + # manage the cleanup of this file, so it doesn't actually return a + # path, it returns a context manager that will give you the path + # when you enter it and will do any cleanup when you leave it. In + # the common case of not needing a temporary file, it will just + # return the file system location and the __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = get_path("certifi", "cacert.pem") + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + + return _CACERT_PATH + + +except ImportError: + # This fallback will work for Python versions prior to 3.7 that lack the + # importlib.resources module but relies on the existing `where` function + # so won't address issues with environments like PyOxidizer that don't set + # __file__ on modules. + def read_text(_module, _path, encoding="ascii"): + with open(where(), "r", encoding=encoding) as data: + return data.read() + + # If we don't have importlib.resources, then we will just do the old logic + # of assuming we're on the filesystem and munge the path directly. + def where(): + f = os.path.dirname(__file__) + + return os.path.join(f, "cacert.pem") + + +def contents(): + return read_text("certifi", "cacert.pem", encoding="ascii") From e2717ac9636154f0b8cfc733dd7bc45fd86589db Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 16:56:09 +0200 Subject: [PATCH 0518/1227] updated poetry lock file --- poetry.lock | 870 +++++++++++++++++++++++++++++----------------------- 1 file changed, 493 insertions(+), 377 deletions(-) diff --git a/poetry.lock b/poetry.lock index a79c865a9e..47509f334e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -94,7 +94,7 @@ python-dateutil = ">=2.7.0" [[package]] name = "astroid" -version = "2.9.3" +version = "2.11.5" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -104,7 +104,7 @@ python-versions = ">=3.6.2" lazy-object-proxy = ">=1.4.0" typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} -wrapt = ">=1.11,<1.14" +wrapt = ">=1.11,<2" [[package]] name = "async-timeout" @@ -172,7 +172,7 @@ pytz = ">=2015.7" [[package]] name = "bcrypt" -version = "3.2.0" +version = "3.2.2" description = "Modern password hashing for your software and your servers" category = "main" optional = false @@ -180,7 +180,6 @@ python-versions = ">=3.6" [package.dependencies] cffi = ">=1.1" -six = ">=1.4.1" [package.extras] tests = ["pytest (>=3.2.1,!=3.3.0)"] @@ -201,7 +200,7 @@ wcwidth = ">=0.1.4" [[package]] name = "cachetools" -version = "5.0.0" +version = "5.2.0" description = "Extensible memoizing collections and decorators" category = "main" optional = false @@ -209,11 +208,11 @@ python-versions = "~=3.7" [[package]] name = "certifi" -version = "2021.10.8" +version = "2022.5.18.1" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "cffi" @@ -226,17 +225,9 @@ python-versions = "*" [package.dependencies] pycparser = "*" -[[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - [[package]] name = "charset-normalizer" -version = "2.0.11" +version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -295,21 +286,21 @@ python-versions = "*" [[package]] name = "coverage" -version = "6.3.1" +version = "6.4.1" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] -tomli = {version = "*", optional = true, markers = "extra == \"toml\""} +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "cryptography" -version = "36.0.1" +version = "37.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false @@ -324,7 +315,7 @@ docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] sdist = ["setuptools_rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] name = "cx-freeze" @@ -346,9 +337,34 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "deprecated" +version = "1.2.13" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] + +[[package]] +name = "dill" +version = "0.3.5.1" +description = "serialize all of python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + [[package]] name = "dnspython" -version = "2.2.0" +version = "2.2.1" description = "DNS toolkit" category = "main" optional = false @@ -364,7 +380,7 @@ wmi = ["wmi (>=1.5.1,<2.0.0)"] [[package]] name = "docutils" -version = "0.18.1" +version = "0.17.1" description = "Docutils -- Python Documentation Utilities" category = "dev" optional = false @@ -372,7 +388,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "dropbox" -version = "11.26.0" +version = "11.31.0" description = "Official Dropbox API Client" category = "main" optional = false @@ -397,7 +413,7 @@ prefixed = ">=0.3.2" [[package]] name = "evdev" -version = "1.4.0" +version = "1.5.0" description = "Bindings to the Linux input handling subsystem" category = "main" optional = false @@ -451,6 +467,23 @@ category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "gazu" +version = "0.8.28" +description = "Gazu is a client for Zou, the API to store the data of your CG production." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +deprecated = "1.2.13" +python-socketio = {version = "4.6.1", extras = ["client"], markers = "python_version >= \"3.5\""} +requests = ">=2.25.1,<=2.27.1" + +[package.extras] +dev = ["wheel"] +test = ["pytest-cov (==2.12.1)", "requests-mock (==1.9.3)", "pytest (==4.6.11)", "pytest (==6.1.2)", "pytest (==6.2.5)", "black (==21.12b0)", "pre-commit (==2.17.0)"] + [[package]] name = "gitdb" version = "4.0.9" @@ -464,7 +497,7 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.26" +version = "3.1.27" description = "GitPython is a python library used to interact with Git repositories" category = "dev" optional = false @@ -476,7 +509,7 @@ typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\"" [[package]] name = "google-api-core" -version = "2.4.0" +version = "2.8.1" description = "Google API client core library" category = "main" optional = false @@ -484,18 +517,18 @@ python-versions = ">=3.6" [package.dependencies] google-auth = ">=1.25.0,<3.0dev" -googleapis-common-protos = ">=1.52.0,<2.0dev" -protobuf = ">=3.12.0" +googleapis-common-protos = ">=1.56.2,<2.0dev" +protobuf = ">=3.15.0,<4.0.0dev" requests = ">=2.18.0,<3.0.0dev" [package.extras] grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio-status (>=1.33.2,<2.0dev)"] -grpcgcp = ["grpcio-gcp (>=0.2.2)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2)"] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] [[package]] name = "google-api-python-client" -version = "1.12.10" +version = "1.12.11" description = "Google API Client Library for Python" category = "main" optional = false @@ -511,7 +544,7 @@ uritemplate = ">=3.0.0,<4dev" [[package]] name = "google-auth" -version = "2.6.0" +version = "2.7.0" description = "Google Authentication Library" category = "main" optional = false @@ -525,6 +558,7 @@ six = ">=1.9.0" [package.extras] aiohttp = ["requests (>=2.20.0,<3.0.0dev)", "aiohttp (>=3.6.2,<4.0.0dev)"] +enterprise_cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] pyopenssl = ["pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] @@ -543,21 +577,21 @@ six = "*" [[package]] name = "googleapis-common-protos" -version = "1.54.0" +version = "1.56.2" description = "Common protobufs used in Google APIs" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -protobuf = ">=3.12.0" +protobuf = ">=3.15.0,<4.0.0dev" [package.extras] -grpc = ["grpcio (>=1.0.0)"] +grpc = ["grpcio (>=1.0.0,<2.0.0dev)"] [[package]] name = "httplib2" -version = "0.20.2" +version = "0.20.4" description = "A comprehensive HTTP client library." category = "main" optional = false @@ -568,11 +602,11 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "idna" -version = "2.10" +version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" [[package]] name = "imagesize" @@ -584,7 +618,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.10.1" +version = "4.11.4" description = "Read metadata from Python packages" category = "main" optional = false @@ -595,9 +629,9 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -663,7 +697,7 @@ i18n = ["Babel (>=0.8)"] [[package]] name = "jinxed" -version = "1.1.0" +version = "1.2.0" description = "Jinxed Terminal Library" category = "main" optional = false @@ -777,7 +811,7 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "paramiko" -version = "2.10.1" +version = "2.11.0" description = "SSH2 protocol library" category = "main" optional = false @@ -809,7 +843,7 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pathlib2" -version = "2.3.6" +version = "2.3.7.post1" description = "Object-oriented filesystem paths" category = "main" optional = false @@ -820,23 +854,27 @@ six = "*" [[package]] name = "pillow" -version = "9.0.1" +version = "9.1.1" description = "Python Imaging Library (Fork)" category = "main" optional = false python-versions = ">=3.7" +[package.extras] +docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + [[package]] name = "platformdirs" -version = "2.4.1" +version = "2.5.2" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" [package.extras] -docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] +test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] [[package]] name = "pluggy" @@ -958,29 +996,33 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.11.2" +version = "2.12.0" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [[package]] name = "pylint" -version = "2.12.2" +version = "2.13.9" description = "python code static checker" category = "dev" optional = false python-versions = ">=3.6.2" [package.dependencies] -astroid = ">=2.9.0,<2.10" +astroid = ">=2.11.5,<=2.12.0-dev0" colorama = {version = "*", markers = "sys_platform == \"win32\""} +dill = ">=0.2" isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.7" +mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" -toml = ">=0.9.2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} +[package.extras] +testutil = ["gitpython (>3)"] + [[package]] name = "pymongo" version = "3.12.3" @@ -1031,7 +1073,7 @@ six = "*" [[package]] name = "pyobjc-core" -version = "8.2" +version = "8.5" description = "Python<->ObjC Interoperability Module" category = "main" optional = false @@ -1039,39 +1081,39 @@ python-versions = ">=3.6" [[package]] name = "pyobjc-framework-applicationservices" -version = "8.2" +version = "8.5" description = "Wrappers for the framework ApplicationServices on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.2" -pyobjc-framework-Cocoa = ">=8.2" -pyobjc-framework-Quartz = ">=8.2" +pyobjc-core = ">=8.5" +pyobjc-framework-Cocoa = ">=8.5" +pyobjc-framework-Quartz = ">=8.5" [[package]] name = "pyobjc-framework-cocoa" -version = "8.2" +version = "8.5" description = "Wrappers for the Cocoa frameworks on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.2" +pyobjc-core = ">=8.5" [[package]] name = "pyobjc-framework-quartz" -version = "8.2" +version = "8.5" description = "Wrappers for the Quartz frameworks on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.2" -pyobjc-framework-Cocoa = ">=8.2" +pyobjc-core = ">=8.5" +pyobjc-framework-Cocoa = ">=8.5" [[package]] name = "pyparsing" @@ -1154,6 +1196,39 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" [package.dependencies] six = ">=1.5" +[[package]] +name = "python-engineio" +version = "3.14.2" +description = "Engine.IO server" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = ">=1.9.0" + +[package.extras] +asyncio_client = ["aiohttp (>=3.4)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] + +[[package]] +name = "python-socketio" +version = "4.6.1" +description = "Socket.IO server" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +python-engineio = ">=3.13.0,<4" +requests = {version = ">=2.21.0", optional = true, markers = "extra == \"client\""} +six = ">=1.9.0" +websocket-client = {version = ">=0.54.0", optional = true, markers = "extra == \"client\""} + +[package.extras] +asyncio_client = ["aiohttp (>=3.4)", "websockets (>=7.0)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] + [[package]] name = "python-xlib" version = "0.31" @@ -1175,7 +1250,7 @@ python-versions = "*" [[package]] name = "pytz" -version = "2021.3" +version = "2022.1" description = "World timezone definitions, modern and historical" category = "dev" optional = false @@ -1199,7 +1274,7 @@ python-versions = "*" [[package]] name = "qt.py" -version = "1.3.6" +version = "1.3.7" description = "Python 2 & 3 compatibility wrapper around all Qt bindings - PySide, PySide2, PyQt4 and PyQt5." category = "main" optional = false @@ -1240,21 +1315,21 @@ sphinx = ">=1.3.1" [[package]] name = "requests" -version = "2.25.1" +version = "2.27.1" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<5" -idna = ">=2.5,<3" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "rsa" @@ -1269,7 +1344,7 @@ pyasn1 = ">=0.1.3" [[package]] name = "secretstorage" -version = "3.3.1" +version = "3.3.2" description = "Python bindings to FreeDesktop.org Secret Service API" category = "main" optional = false @@ -1297,7 +1372,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "slack-sdk" -version = "3.13.0" +version = "3.17.0" description = "The Slack API Platform SDK for Python" category = "main" optional = false @@ -1305,7 +1380,7 @@ python-versions = ">=3.6.0" [package.extras] optional = ["aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "SQLAlchemy (>=1,<2)", "websockets (>=10,<11)", "websocket-client (>=1,<2)"] -testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==21.12b0)", "psutil (>=5,<6)", "databases (>=0.3)", "boto3 (<=2)", "moto (<2)"] +testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "itsdangerous (==1.1.0)", "Jinja2 (==3.0.3)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==22.3.0)", "click (==8.0.4)", "psutil (>=5,<6)", "databases (>=0.5)", "boto3 (<=2)", "moto (>=3,<4)"] [[package]] name = "smmap" @@ -1325,7 +1400,7 @@ python-versions = "*" [[package]] name = "speedcopy" -version = "2.1.2" +version = "2.1.4" description = "Replacement or alternative for python copyfile() utilizing server side copy on network shares for faster copying." category = "main" optional = false @@ -1333,18 +1408,19 @@ python-versions = "*" [[package]] name = "sphinx" -version = "3.5.3" +version = "5.0.1" description = "Python documentation generator" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=1.3" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.12" +docutils = ">=0.14,<0.19" imagesize = "*" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} Jinja2 = ">=2.3" packaging = "*" Pygments = ">=2.0" @@ -1352,19 +1428,19 @@ requests = ">=2.5.0" snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"] -test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.950)", "docutils-stubs", "types-typed-ast", "types-requests"] +test = ["pytest (>=4.6)", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-qt-documentation" -version = "0.3" +version = "0.4" description = "Plugin for proper resolve intersphinx references for Qt elements" category = "dev" optional = false @@ -1374,16 +1450,22 @@ python-versions = ">=3.6" docutils = "*" sphinx = "*" +[package.extras] +dev = ["pre-commit"] +lint = ["black", "flake8", "pylint"] +test = ["pytest (>=3.0.0)", "pytest-cov"] + [[package]] name = "sphinx-rtd-theme" -version = "0.5.1" +version = "1.0.0" description = "Read the Docs theme for Sphinx" category = "dev" optional = false -python-versions = "*" +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" [package.dependencies] -sphinx = "*" +docutils = "<0.18" +sphinx = ">=1.6" [package.extras] dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"] @@ -1504,7 +1586,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" -version = "2.0.0" +version = "2.0.1" description = "A lil' TOML parser" category = "dev" optional = false @@ -1512,7 +1594,7 @@ python-versions = ">=3.7" [[package]] name = "typed-ast" -version = "1.5.2" +version = "1.5.4" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false @@ -1536,14 +1618,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "urllib3" -version = "1.26.8" +version = "1.26.9" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -brotli = ["brotlipy (>=0.6.0)"] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] @@ -1568,9 +1650,9 @@ six = "*" [[package]] name = "wrapt" -version = "1.13.3" +version = "1.14.1" description = "Module for decorators, wrappers and monkey patching." -category = "dev" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" @@ -1606,20 +1688,20 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [[package]] name = "zipp" -version = "3.7.0" +version = "3.8.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "1.1" python-versions = "3.7.*" -content-hash = "b02313c8255a1897b0f0617ad4884a5943696c363512921aab1cb2dd8f4fdbe0" +content-hash = "bd8e0a03668c380c6e76c8cd6c71020692f4ea9f32de7a4f09433564faa9dad0" [metadata.files] acre = [] @@ -1722,8 +1804,8 @@ arrow = [ {file = "arrow-0.17.0.tar.gz", hash = "sha256:ff08d10cda1d36c68657d6ad20d74fbea493d980f8b2d45344e00d6ed2bf6ed4"}, ] astroid = [ - {file = "astroid-2.9.3-py3-none-any.whl", hash = "sha256:506daabe5edffb7e696ad82483ad0228245a9742ed7d2d8c9cdb31537decf9f6"}, - {file = "astroid-2.9.3.tar.gz", hash = "sha256:1efdf4e867d4d8ba4a9f6cf9ce07cd182c4c41de77f23814feb27ca93ca9d877"}, + {file = "astroid-2.11.5-py3-none-any.whl", hash = "sha256:14ffbb4f6aa2cf474a0834014005487f7ecd8924996083ab411e7fa0b508ce0b"}, + {file = "astroid-2.11.5.tar.gz", hash = "sha256:f4e4ec5294c4b07ac38bab9ca5ddd3914d4bf46f9006eb5c0ae755755061044e"}, ] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, @@ -1750,28 +1832,29 @@ babel = [ {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] bcrypt = [ - {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b589229207630484aefe5899122fb938a5b017b0f4349f769b8c13e78d99a8fd"}, - {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6"}, - {file = "bcrypt-3.2.0-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7"}, - {file = "bcrypt-3.2.0-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1"}, - {file = "bcrypt-3.2.0-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d"}, - {file = "bcrypt-3.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a0584a92329210fcd75eb8a3250c5a941633f8bfaf2a18f81009b097732839b7"}, - {file = "bcrypt-3.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:56e5da069a76470679f312a7d3d23deb3ac4519991a0361abc11da837087b61d"}, - {file = "bcrypt-3.2.0-cp36-abi3-win32.whl", hash = "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55"}, - {file = "bcrypt-3.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34"}, - {file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"}, + {file = "bcrypt-3.2.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40"}, + {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa"}, + {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa"}, + {file = "bcrypt-3.2.2-cp36-abi3-win32.whl", hash = "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e"}, + {file = "bcrypt-3.2.2-cp36-abi3-win_amd64.whl", hash = "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129"}, + {file = "bcrypt-3.2.2.tar.gz", hash = "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb"}, ] blessed = [ {file = "blessed-1.19.1-py2.py3-none-any.whl", hash = "sha256:63b8554ae2e0e7f43749b6715c734cc8f3883010a809bf16790102563e6cf25b"}, {file = "blessed-1.19.1.tar.gz", hash = "sha256:9a0d099695bf621d4680dd6c73f6ad547f6a3442fbdbe80c4b1daa1edbc492fc"}, ] cachetools = [ - {file = "cachetools-5.0.0-py3-none-any.whl", hash = "sha256:8fecd4203a38af17928be7b90689d8083603073622229ca7077b72d8e5a976e4"}, - {file = "cachetools-5.0.0.tar.gz", hash = "sha256:486471dfa8799eb7ec503a8059e263db000cdda20075ce5e48903087f79d5fd6"}, + {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"}, + {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"}, ] certifi = [ - {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, - {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, + {file = "certifi-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"}, + {file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"}, ] cffi = [ {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, @@ -1825,13 +1908,9 @@ cffi = [ {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, ] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, -] charset-normalizer = [ - {file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"}, - {file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"}, + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, @@ -1854,69 +1933,71 @@ coolname = [ {file = "coolname-1.1.0.tar.gz", hash = "sha256:410fe6ea9999bf96f2856ef0c726d5f38782bbefb7bb1aca0e91e0dc98ed09e3"}, ] coverage = [ - {file = "coverage-6.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525"}, - {file = "coverage-6.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e"}, - {file = "coverage-6.3.1-cp310-cp310-win32.whl", hash = "sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217"}, - {file = "coverage-6.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb"}, - {file = "coverage-6.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8"}, - {file = "coverage-6.3.1-cp37-cp37m-win32.whl", hash = "sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0"}, - {file = "coverage-6.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687"}, - {file = "coverage-6.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320"}, - {file = "coverage-6.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a"}, - {file = "coverage-6.3.1-cp38-cp38-win32.whl", hash = "sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10"}, - {file = "coverage-6.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f"}, - {file = "coverage-6.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d"}, - {file = "coverage-6.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38"}, - {file = "coverage-6.3.1-cp39-cp39-win32.whl", hash = "sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2"}, - {file = "coverage-6.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa"}, - {file = "coverage-6.3.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2"}, - {file = "coverage-6.3.1.tar.gz", hash = "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8"}, + {file = "coverage-6.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1d5aa2703e1dab4ae6cf416eb0095304f49d004c39e9db1d86f57924f43006b"}, + {file = "coverage-6.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ce1b258493cbf8aec43e9b50d89982346b98e9ffdfaae8ae5793bc112fb0068"}, + {file = "coverage-6.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c4e737f60c6936460c5be330d296dd5b48b3963f48634c53b3f7deb0f34ec4"}, + {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e65ef149028516c6d64461b95a8dbcfce95cfd5b9eb634320596173332ea84"}, + {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69718750eaae75efe506406c490d6fc5a6161d047206cc63ce25527e8a3adad"}, + {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e57816f8ffe46b1df8f12e1b348f06d164fd5219beba7d9433ba79608ef011cc"}, + {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:01c5615d13f3dd3aa8543afc069e5319cfa0c7d712f6e04b920431e5c564a749"}, + {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ab269400706fab15981fd4bd5080c56bd5cc07c3bccb86aab5e1d5a88dc8f4"}, + {file = "coverage-6.4.1-cp310-cp310-win32.whl", hash = "sha256:a7f3049243783df2e6cc6deafc49ea123522b59f464831476d3d1448e30d72df"}, + {file = "coverage-6.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2ddcac99b2d2aec413e36d7a429ae9ebcadf912946b13ffa88e7d4c9b712d6"}, + {file = "coverage-6.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb73e0011b8793c053bfa85e53129ba5f0250fdc0392c1591fd35d915ec75c46"}, + {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106c16dfe494de3193ec55cac9640dd039b66e196e4641fa8ac396181578b982"}, + {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f4f3df85aa39da00fd3ec4b5abeb7407e82b68c7c5ad181308b0e2526da5d4"}, + {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:961e2fb0680b4f5ad63234e0bf55dfb90d302740ae9c7ed0120677a94a1590cb"}, + {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cec3a0f75c8f1031825e19cd86ee787e87cf03e4fd2865c79c057092e69e3a3b"}, + {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:129cd05ba6f0d08a766d942a9ed4b29283aff7b2cccf5b7ce279d50796860bb3"}, + {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bf5601c33213d3cb19d17a796f8a14a9eaa5e87629a53979a5981e3e3ae166f6"}, + {file = "coverage-6.4.1-cp37-cp37m-win32.whl", hash = "sha256:269eaa2c20a13a5bf17558d4dc91a8d078c4fa1872f25303dddcbba3a813085e"}, + {file = "coverage-6.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f02cbbf8119db68455b9d763f2f8737bb7db7e43720afa07d8eb1604e5c5ae28"}, + {file = "coverage-6.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffa9297c3a453fba4717d06df579af42ab9a28022444cae7fa605af4df612d54"}, + {file = "coverage-6.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:145f296d00441ca703a659e8f3eb48ae39fb083baba2d7ce4482fb2723e050d9"}, + {file = "coverage-6.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d44996140af8b84284e5e7d398e589574b376fb4de8ccd28d82ad8e3bea13"}, + {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bd9a6fc18aab8d2e18f89b7ff91c0f34ff4d5e0ba0b33e989b3cd4194c81fd9"}, + {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384f2a3652cef289e38100f2d037956194a837221edd520a7ee5b42d00cc605"}, + {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b3e07152b4563722be523e8cd0b209e0d1a373022cfbde395ebb6575bf6790d"}, + {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1480ff858b4113db2718848d7b2d1b75bc79895a9c22e76a221b9d8d62496428"}, + {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:865d69ae811a392f4d06bde506d531f6a28a00af36f5c8649684a9e5e4a85c83"}, + {file = "coverage-6.4.1-cp38-cp38-win32.whl", hash = "sha256:664a47ce62fe4bef9e2d2c430306e1428ecea207ffd68649e3b942fa8ea83b0b"}, + {file = "coverage-6.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:26dff09fb0d82693ba9e6231248641d60ba606150d02ed45110f9ec26404ed1c"}, + {file = "coverage-6.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9c80df769f5ec05ad21ea34be7458d1dc51ff1fb4b2219e77fe24edf462d6df"}, + {file = "coverage-6.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39ee53946bf009788108b4dd2894bf1349b4e0ca18c2016ffa7d26ce46b8f10d"}, + {file = "coverage-6.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b66caa62922531059bc5ac04f836860412f7f88d38a476eda0a6f11d4724f4"}, + {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd180ed867e289964404051a958f7cccabdeed423f91a899829264bb7974d3d3"}, + {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3"}, + {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c08da0bd238f2970230c2a0d28ff0e99961598cb2e810245d7fc5afcf1254e8"}, + {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d42c549a8f41dc103a8004b9f0c433e2086add8a719da00e246e17cbe4056f72"}, + {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:309ce4a522ed5fca432af4ebe0f32b21d6d7ccbb0f5fcc99290e71feba67c264"}, + {file = "coverage-6.4.1-cp39-cp39-win32.whl", hash = "sha256:fdb6f7bd51c2d1714cea40718f6149ad9be6a2ee7d93b19e9f00934c0f2a74d9"}, + {file = "coverage-6.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:342d4aefd1c3e7f620a13f4fe563154d808b69cccef415415aece4c786665397"}, + {file = "coverage-6.4.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:4803e7ccf93230accb928f3a68f00ffa80a88213af98ed338a57ad021ef06815"}, + {file = "coverage-6.4.1.tar.gz", hash = "sha256:4321f075095a096e70aff1d002030ee612b65a205a0a0f5b815280d5dc58100c"}, ] cryptography = [ - {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b"}, - {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2"}, - {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f"}, - {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3"}, - {file = "cryptography-36.0.1-cp36-abi3-win32.whl", hash = "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca"}, - {file = "cryptography-36.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf"}, - {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9"}, - {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1"}, - {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316"}, - {file = "cryptography-36.0.1.tar.gz", hash = "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638"}, + {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181"}, + {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178"}, + {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a"}, + {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15"}, + {file = "cryptography-37.0.2-cp36-abi3-win32.whl", hash = "sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0"}, + {file = "cryptography-37.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d"}, + {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9"}, + {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1"}, + {file = "cryptography-37.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023"}, + {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06"}, + {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717"}, + {file = "cryptography-37.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f"}, + {file = "cryptography-37.0.2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982"}, + {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4"}, + {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de"}, + {file = "cryptography-37.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452"}, + {file = "cryptography-37.0.2.tar.gz", hash = "sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e"}, ] cx-freeze = [ {file = "cx_Freeze-6.9-cp310-cp310-win32.whl", hash = "sha256:776d4fb68a4831691acbd3c374362b9b48ce2e568514a73c3d4cb14d5dcf1470"}, @@ -1946,25 +2027,33 @@ cx-logging = [ {file = "cx_Logging-3.0-cp39-cp39-win_amd64.whl", hash = "sha256:302e9c4f65a936c288a4fa59a90e7e142d9ef994aa29676731acafdcccdbb3f5"}, {file = "cx_Logging-3.0.tar.gz", hash = "sha256:ba8a7465facf7b98d8f494030fb481a2e8aeee29dc191e10383bb54ed42bdb34"}, ] +deprecated = [ + {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, + {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, +] +dill = [ + {file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"}, + {file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"}, +] dnspython = [ - {file = "dnspython-2.2.0-py3-none-any.whl", hash = "sha256:081649da27ced5e75709a1ee542136eaba9842a0fe4c03da4fb0a3d3ed1f3c44"}, - {file = "dnspython-2.2.0.tar.gz", hash = "sha256:e79351e032d0b606b98d38a4b0e6e2275b31a5b85c873e587cc11b73aca026d6"}, + {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"}, + {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"}, ] docutils = [ - {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, - {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, + {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, + {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] dropbox = [ - {file = "dropbox-11.26.0-py2-none-any.whl", hash = "sha256:abd29587fa692bde0c3a48ce8efb56c24d8df92c5f402bbcdd78f3089d5ced5c"}, - {file = "dropbox-11.26.0-py3-none-any.whl", hash = "sha256:84becf043a63007ae67620946acb0fa164669ce691c7f1dbfdabb6712a258b29"}, - {file = "dropbox-11.26.0.tar.gz", hash = "sha256:dd79e3dfc216688020959462aefac54ffce7c07a45e89f4fb258348885195a73"}, + {file = "dropbox-11.31.0-py2-none-any.whl", hash = "sha256:393a99dfe30d42fd73c265b9b7d24bb21c9a961739cd097c3541e709eb2a209c"}, + {file = "dropbox-11.31.0-py3-none-any.whl", hash = "sha256:5f924102fd6464def81573320c6aa4ea9cd3368e1b1c13d838403dd4c9ffc919"}, + {file = "dropbox-11.31.0.tar.gz", hash = "sha256:f483d65b702775b9abf7b9328f702c68c6397fc01770477c6ddbfb1d858a5bcf"}, ] enlighten = [ {file = "enlighten-1.10.2-py2.py3-none-any.whl", hash = "sha256:b237fe562b320bf9f1d4bb76d0c98e0daf914372a76ab87c35cd02f57aa9d8c1"}, {file = "enlighten-1.10.2.tar.gz", hash = "sha256:7a5b83cd0f4d095e59d80c648ebb5f7ffca0cd8bcf7ae6639828ee1ad000632a"}, ] evdev = [ - {file = "evdev-1.4.0.tar.gz", hash = "sha256:8782740eb1a86b187334c07feb5127d3faa0b236e113206dfe3ae8f77fb1aaf1"}, + {file = "evdev-1.5.0.tar.gz", hash = "sha256:5b33b174f7c84576e7dd6071e438bf5ad227da95efd4356a39fe4c8355412fe6"}, ] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, @@ -2038,49 +2127,52 @@ ftrack-python-api = [ future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] +gazu = [ + {file = "gazu-0.8.28-py2.py3-none-any.whl", hash = "sha256:ec4f7c2688a2b37ee8a77737e4e30565ad362428c3ade9046136a998c043e51c"}, +] gitdb = [ {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, ] gitpython = [ - {file = "GitPython-3.1.26-py3-none-any.whl", hash = "sha256:26ac35c212d1f7b16036361ca5cff3ec66e11753a0d677fb6c48fa4e1a9dd8d6"}, - {file = "GitPython-3.1.26.tar.gz", hash = "sha256:fc8868f63a2e6d268fb25f481995ba185a85a66fcad126f039323ff6635669ee"}, + {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, + {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, ] google-api-core = [ - {file = "google-api-core-2.4.0.tar.gz", hash = "sha256:ba8787b7c61632cd0340f095e1c036bef9426b2594f10afb290ba311ae8cb2cb"}, - {file = "google_api_core-2.4.0-py2.py3-none-any.whl", hash = "sha256:58e2c1171a3d51778bf4e428fbb4bf79cbd05007b4b44deaa80cf73c80eebc0f"}, + {file = "google-api-core-2.8.1.tar.gz", hash = "sha256:958024c6aa3460b08f35741231076a4dd9a4c819a6a39d44da9627febe8b28f0"}, + {file = "google_api_core-2.8.1-py3-none-any.whl", hash = "sha256:ce1daa49644b50398093d2a9ad886501aa845e2602af70c3001b9f402a9d7359"}, ] google-api-python-client = [ - {file = "google-api-python-client-1.12.10.tar.gz", hash = "sha256:1cb773647e7d97048d9d1c7fa746247fbad39fd1a3b5040f2cb2645dd7156b11"}, - {file = "google_api_python_client-1.12.10-py2.py3-none-any.whl", hash = "sha256:5a8742b9b604b34e13462cc3d6aedbbf11d3af1ef558eb95defe74a29ebc5c28"}, + {file = "google-api-python-client-1.12.11.tar.gz", hash = "sha256:1b4bd42a46321e13c0542a9e4d96fa05d73626f07b39f83a73a947d70ca706a9"}, + {file = "google_api_python_client-1.12.11-py2.py3-none-any.whl", hash = "sha256:7e0a1a265c8d3088ee1987778c72683fcb376e32bada8d7767162bd9c503fd9b"}, ] google-auth = [ - {file = "google-auth-2.6.0.tar.gz", hash = "sha256:ad160fc1ea8f19e331a16a14a79f3d643d813a69534ba9611d2c80dc10439dad"}, - {file = "google_auth-2.6.0-py2.py3-none-any.whl", hash = "sha256:218ca03d7744ca0c8b6697b6083334be7df49b7bf76a69d555962fd1a7657b5f"}, + {file = "google-auth-2.7.0.tar.gz", hash = "sha256:8a954960f852d5f19e6af14dd8e75c20159609e85d8db37e4013cc8c3824a7e1"}, + {file = "google_auth-2.7.0-py2.py3-none-any.whl", hash = "sha256:df549a1433108801b11bdcc0e312eaf0d5f0500db42f0523e4d65c78722e8475"}, ] google-auth-httplib2 = [ {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, {file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"}, ] googleapis-common-protos = [ - {file = "googleapis-common-protos-1.54.0.tar.gz", hash = "sha256:a4031d6ec6c2b1b6dc3e0be7e10a1bd72fb0b18b07ef9be7b51f2c1004ce2437"}, - {file = "googleapis_common_protos-1.54.0-py2.py3-none-any.whl", hash = "sha256:e54345a2add15dc5e1a7891c27731ff347b4c33765d79b5ed7026a6c0c7cbcae"}, + {file = "googleapis-common-protos-1.56.2.tar.gz", hash = "sha256:b09b56f5463070c2153753ef123f07d2e49235e89148e9b2459ec8ed2f68d7d3"}, + {file = "googleapis_common_protos-1.56.2-py2.py3-none-any.whl", hash = "sha256:023eaea9d8c1cceccd9587c6af6c20f33eeeb05d4148670f2b0322dc1511700c"}, ] httplib2 = [ - {file = "httplib2-0.20.2-py3-none-any.whl", hash = "sha256:6b937120e7d786482881b44b8eec230c1ee1c5c1d06bce8cc865f25abbbf713b"}, - {file = "httplib2-0.20.2.tar.gz", hash = "sha256:e404681d2fbcec7506bcb52c503f2b021e95bee0ef7d01e5c221468a2406d8dc"}, + {file = "httplib2-0.20.4-py3-none-any.whl", hash = "sha256:8b6a905cb1c79eefd03f8669fd993c36dc341f7c558f056cb5a33b5c2f458543"}, + {file = "httplib2-0.20.4.tar.gz", hash = "sha256:58a98e45b4b1a48273073f905d2961666ecf0fbac4250ea5b47aef259eb5c585"}, ] idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] imagesize = [ {file = "imagesize-1.3.0-py2.py3-none-any.whl", hash = "sha256:1db2f82529e53c3e929e8926a1fa9235aa82d0bd0c580359c67ec31b2fddaa8c"}, {file = "imagesize-1.3.0.tar.gz", hash = "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.10.1-py3-none-any.whl", hash = "sha256:899e2a40a8c4a1aec681feef45733de8a6c58f3f6a0dbed2eb6574b4387a77b6"}, - {file = "importlib_metadata-4.10.1.tar.gz", hash = "sha256:951f0d8a5b7260e9db5e41d429285b5f451e928479f19d80818878527d36e95e"}, + {file = "importlib_metadata-4.11.4-py3-none-any.whl", hash = "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec"}, + {file = "importlib_metadata-4.11.4.tar.gz", hash = "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -2103,8 +2195,8 @@ jinja2 = [ {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, ] jinxed = [ - {file = "jinxed-1.1.0-py2.py3-none-any.whl", hash = "sha256:6a61ccf963c16aa885304f27e6e5693783676897cea0c7f223270c8b8e78baf8"}, - {file = "jinxed-1.1.0.tar.gz", hash = "sha256:d8f1731f134e9e6b04d95095845ae6c10eb15cb223a5f0cabdea87d4a279c305"}, + {file = "jinxed-1.2.0-py2.py3-none-any.whl", hash = "sha256:cfc2b2e4e3b4326954d546ba6d6b9a7a796ddcb0aef8d03161d005177eb0d48b"}, + {file = "jinxed-1.2.0.tar.gz", hash = "sha256:032acda92d5c57cd216033cbbd53de731e6ed50deb63eb4781336ca55f72cda5"}, ] jsonschema = [ {file = "jsonschema-2.6.0-py2.py3-none-any.whl", hash = "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08"}, @@ -2298,57 +2390,60 @@ packaging = [ {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] paramiko = [ - {file = "paramiko-2.10.1-py2.py3-none-any.whl", hash = "sha256:f6cbd3e1204abfdbcd40b3ecbc9d32f04027cd3080fe666245e21e7540ccfc1b"}, - {file = "paramiko-2.10.1.tar.gz", hash = "sha256:443f4da23ec24e9a9c0ea54017829c282abdda1d57110bf229360775ccd27a31"}, + {file = "paramiko-2.11.0-py2.py3-none-any.whl", hash = "sha256:655f25dc8baf763277b933dfcea101d636581df8d6b9774d1fb653426b72c270"}, + {file = "paramiko-2.11.0.tar.gz", hash = "sha256:003e6bee7c034c21fbb051bf83dc0a9ee4106204dd3c53054c71452cc4ec3938"}, ] parso = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] pathlib2 = [ - {file = "pathlib2-2.3.6-py2.py3-none-any.whl", hash = "sha256:3a130b266b3a36134dcc79c17b3c7ac9634f083825ca6ea9d8f557ee6195c9c8"}, - {file = "pathlib2-2.3.6.tar.gz", hash = "sha256:7d8bcb5555003cdf4a8d2872c538faa3a0f5d20630cb360e518ca3b981795e5f"}, + {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, + {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, ] pillow = [ - {file = "Pillow-9.0.1-1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4"}, - {file = "Pillow-9.0.1-1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976"}, - {file = "Pillow-9.0.1-1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc"}, - {file = "Pillow-9.0.1-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd"}, - {file = "Pillow-9.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f"}, - {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a"}, - {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049"}, - {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a"}, - {file = "Pillow-9.0.1-cp310-cp310-win32.whl", hash = "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e"}, - {file = "Pillow-9.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b"}, - {file = "Pillow-9.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e"}, - {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360"}, - {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b"}, - {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030"}, - {file = "Pillow-9.0.1-cp37-cp37m-win32.whl", hash = "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669"}, - {file = "Pillow-9.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092"}, - {file = "Pillow-9.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204"}, - {file = "Pillow-9.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e"}, - {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c"}, - {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5"}, - {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae"}, - {file = "Pillow-9.0.1-cp38-cp38-win32.whl", hash = "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c"}, - {file = "Pillow-9.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00"}, - {file = "Pillow-9.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838"}, - {file = "Pillow-9.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28"}, - {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c"}, - {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b"}, - {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7"}, - {file = "Pillow-9.0.1-cp39-cp39-win32.whl", hash = "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7"}, - {file = "Pillow-9.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac"}, - {file = "Pillow-9.0.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97"}, - {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56"}, - {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e"}, - {file = "Pillow-9.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70"}, - {file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"}, + {file = "Pillow-9.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:42dfefbef90eb67c10c45a73a9bc1599d4dac920f7dfcbf4ec6b80cb620757fe"}, + {file = "Pillow-9.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffde4c6fabb52891d81606411cbfaf77756e3b561b566efd270b3ed3791fde4e"}, + {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c857532c719fb30fafabd2371ce9b7031812ff3889d75273827633bca0c4602"}, + {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59789a7d06c742e9d13b883d5e3569188c16acb02eeed2510fd3bfdbc1bd1530"}, + {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d45dbe4b21a9679c3e8b3f7f4f42a45a7d3ddff8a4a16109dff0e1da30a35b2"}, + {file = "Pillow-9.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e9ed59d1b6ee837f4515b9584f3d26cf0388b742a11ecdae0d9237a94505d03a"}, + {file = "Pillow-9.1.1-cp310-cp310-win32.whl", hash = "sha256:b3fe2ff1e1715d4475d7e2c3e8dabd7c025f4410f79513b4ff2de3d51ce0fa9c"}, + {file = "Pillow-9.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5b650dbbc0969a4e226d98a0b440c2f07a850896aed9266b6fedc0f7e7834108"}, + {file = "Pillow-9.1.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:0b4d5ad2cd3a1f0d1df882d926b37dbb2ab6c823ae21d041b46910c8f8cd844b"}, + {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9370d6744d379f2de5d7fa95cdbd3a4d92f0b0ef29609b4b1687f16bc197063d"}, + {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b761727ed7d593e49671d1827044b942dd2f4caae6e51bab144d4accf8244a84"}, + {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a66fe50386162df2da701b3722781cbe90ce043e7d53c1fd6bd801bca6b48d4"}, + {file = "Pillow-9.1.1-cp37-cp37m-win32.whl", hash = "sha256:2b291cab8a888658d72b575a03e340509b6b050b62db1f5539dd5cd18fd50578"}, + {file = "Pillow-9.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1d4331aeb12f6b3791911a6da82de72257a99ad99726ed6b63f481c0184b6fb9"}, + {file = "Pillow-9.1.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8844217cdf66eabe39567118f229e275f0727e9195635a15e0e4b9227458daaf"}, + {file = "Pillow-9.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b6617221ff08fbd3b7a811950b5c3f9367f6e941b86259843eab77c8e3d2b56b"}, + {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20d514c989fa28e73a5adbddd7a171afa5824710d0ab06d4e1234195d2a2e546"}, + {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:088df396b047477dd1bbc7de6e22f58400dae2f21310d9e2ec2933b2ef7dfa4f"}, + {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53c27bd452e0f1bc4bfed07ceb235663a1df7c74df08e37fd6b03eb89454946a"}, + {file = "Pillow-9.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3f6c1716c473ebd1649663bf3b42702d0d53e27af8b64642be0dd3598c761fb1"}, + {file = "Pillow-9.1.1-cp38-cp38-win32.whl", hash = "sha256:c67db410508b9de9c4694c57ed754b65a460e4812126e87f5052ecf23a011a54"}, + {file = "Pillow-9.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:f054b020c4d7e9786ae0404278ea318768eb123403b18453e28e47cdb7a0a4bf"}, + {file = "Pillow-9.1.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c17770a62a71718a74b7548098a74cd6880be16bcfff5f937f900ead90ca8e92"}, + {file = "Pillow-9.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3f6a6034140e9e17e9abc175fc7a266a6e63652028e157750bd98e804a8ed9a"}, + {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f372d0f08eff1475ef426344efe42493f71f377ec52237bf153c5713de987251"}, + {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09e67ef6e430f90caa093528bd758b0616f8165e57ed8d8ce014ae32df6a831d"}, + {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66daa16952d5bf0c9d5389c5e9df562922a59bd16d77e2a276e575d32e38afd1"}, + {file = "Pillow-9.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d78ca526a559fb84faaaf84da2dd4addef5edb109db8b81677c0bb1aad342601"}, + {file = "Pillow-9.1.1-cp39-cp39-win32.whl", hash = "sha256:55e74faf8359ddda43fee01bffbc5bd99d96ea508d8a08c527099e84eb708f45"}, + {file = "Pillow-9.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c150dbbb4a94ea4825d1e5f2c5501af7141ea95825fadd7829f9b11c97aaf6c"}, + {file = "Pillow-9.1.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:769a7f131a2f43752455cc72f9f7a093c3ff3856bf976c5fb53a59d0ccc704f6"}, + {file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:488f3383cf5159907d48d32957ac6f9ea85ccdcc296c14eca1a4e396ecc32098"}, + {file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b525a356680022b0af53385944026d3486fc8c013638cf9900eb87c866afb4c"}, + {file = "Pillow-9.1.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6e760cf01259a1c0a50f3c845f9cad1af30577fd8b670339b1659c6d0e7a41dd"}, + {file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4165205a13b16a29e1ac57efeee6be2dfd5b5408122d59ef2145bc3239fa340"}, + {file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937a54e5694684f74dcbf6e24cc453bfc5b33940216ddd8f4cd8f0f79167f765"}, + {file = "Pillow-9.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:baf3be0b9446a4083cc0c5bb9f9c964034be5374b5bc09757be89f5d2fa247b8"}, + {file = "Pillow-9.1.1.tar.gz", hash = "sha256:7502539939b53d7565f3d11d87c78e7ec900d3c72945d4ee0e2f250d598309a0"}, ] platformdirs = [ - {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, - {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, + {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, + {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, @@ -2449,12 +2544,12 @@ pyflakes = [ {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pygments = [ - {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, - {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, + {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, + {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, ] pylint = [ - {file = "pylint-2.12.2-py3-none-any.whl", hash = "sha256:daabda3f7ed9d1c60f52d563b1b854632fd90035bcf01443e234d3dc794e3b74"}, - {file = "pylint-2.12.2.tar.gz", hash = "sha256:9d945a73640e1fec07ee34b42f5669b770c759acd536ec7b16d7e4b87a9c9ff9"}, + {file = "pylint-2.13.9-py3-none-any.whl", hash = "sha256:705c620d388035bdd9ff8b44c5bcdd235bfb49d276d488dd2c8ff1736aa42526"}, + {file = "pylint-2.13.9.tar.gz", hash = "sha256:095567c96e19e6f57b5b907e67d265ff535e588fe26b12b5ebe1fc5645b2c731"}, ] pymongo = [ {file = "pymongo-3.12.3-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:c164eda0be9048f83c24b9b2656900041e069ddf72de81c17d874d0c32f6079f"}, @@ -2583,40 +2678,40 @@ pynput = [ {file = "pynput-1.7.6.tar.gz", hash = "sha256:3a5726546da54116b687785d38b1db56997ce1d28e53e8d22fc656d8b92e533c"}, ] pyobjc-core = [ - {file = "pyobjc-core-8.2.tar.gz", hash = "sha256:6afb8ee1dd0647cbfaaf99906eca3b43ce045b27e3d4510462d04e7e5361c89b"}, - {file = "pyobjc_core-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:311e45556c3afa8831713b89017e0204562f51f35661d76c07ffe04985f44e1d"}, - {file = "pyobjc_core-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:711f361e83382e405e4273ff085178b0cf730901ccf6801f834e7037e50278f9"}, - {file = "pyobjc_core-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bdd2e2960ec73214bcfe2d86bb4ca94f5f5119db86d129fa32d3c003b6532c50"}, - {file = "pyobjc_core-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b879f91fc614c399aafa1d08cf5e279c267510e904bad5c336c3a6064c0eb3aa"}, - {file = "pyobjc_core-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:b4ef4bdb99a330f5e15cc6273098964276fccbc432453cdba3c2963292bc066c"}, - {file = "pyobjc_core-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fd8e5be0955790ff8f9d17a0f356b6eb7eb1ce4995e0c94355c462dd52d22d6d"}, + {file = "pyobjc-core-8.5.tar.gz", hash = "sha256:704c275439856c0d1287469f0d589a7d808d48b754a93d9ce5415d4eaf06d576"}, + {file = "pyobjc_core-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0c234143b48334443f5adcf26e668945a6d47bc1fa6223e80918c6c735a029d9"}, + {file = "pyobjc_core-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1486ee533f0d76f666804ce89723ada4db56bfde55e56151ba512d3f849857f8"}, + {file = "pyobjc_core-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:412de06dfa728301c04b3e46fd7453320a8ae8b862e85236e547cd797a73b490"}, + {file = "pyobjc_core-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b3e09cccb1be574a82cc9f929ae27fc4283eccc75496cb5d51534caa6bb83a3"}, + {file = "pyobjc_core-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:eeafe21f879666ab7f57efcc6b007c9f5f8733d367b7e380c925203ed83f000d"}, + {file = "pyobjc_core-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c0071686976d7ea8c14690950e504a13cb22b4ebb2bc7b5ec47c1c1c0f6eff41"}, ] pyobjc-framework-applicationservices = [ - {file = "pyobjc-framework-ApplicationServices-8.2.tar.gz", hash = "sha256:f901b2ebb278b7d00033b45cf9ee9d43f651e096ff4c4defa261509238138da8"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b5be1757a8df944a58449c628ed68fc76dd746fcd1e96ebb6db0f734e94cdfc8"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:32657c4b89983a98fbe831a14884954710719e763197968a20ac07e1daa21f1e"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ce2c9701590f752a75151b2fe1e462e7a7ad67dec3119a796711ea93ea01031"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28124bc892045222305cf8953b163495bf662c401e54f48905dafb1104d9c1d1"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d5c60d66c6ed2569cb042a6c14dd5b9f4d01e182a464b893cd6002ce445b4ddf"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e1727fccc1d48922fa28a4cebf4a6d287d633f451cb03779968df60de8cb1bd0"}, + {file = "pyobjc-framework-ApplicationServices-8.5.tar.gz", hash = "sha256:fa3015ef8e3add90af3447d7fdcc7f8dd083cc2a1d58f99a569480a2df10d2b1"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:436b16ebe448a829a8312e10208eec81a2adcae1fff674dbcc3262e1bd76e0ca"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:408958d14aa7fcf46f2163754c211078bc63be1368934d86188202914dce077d"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1d6cd4ce192859a22e208da4d7177a1c3ceb1ef2f64c339fd881102b1210cadd"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0251d092adb1d2d116fd9f147ceef0e53b158a46c21245131c40b9d7b786d0db"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:9742e69fe6d4545d0e02b0ad0a7a2432bc9944569ee07d6e90ffa5ef614df9f7"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16f5677c14ea903c6aaca1dd121521825c39e816cae696d6ae32c0b287252ab2"}, ] pyobjc-framework-cocoa = [ - {file = "pyobjc-framework-Cocoa-8.2.tar.gz", hash = "sha256:f0901998e2f18415ef6d1f8a12b083f69fc93bd56b3e88040002e3c09bd8c304"}, - {file = "pyobjc_framework_Cocoa-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5af73f150e242542735e0663bb5504f88aabaec2a54c60e856cfca3ff6dd9712"}, - {file = "pyobjc_framework_Cocoa-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d1de3763ee01850c311da74de5c82c85ec199120e85ab45acaf203accc37a470"}, - {file = "pyobjc_framework_Cocoa-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:86d69bf667f99f3c43184d8830567195fff94c675fe7f60f899dd90553d9b265"}, - {file = "pyobjc_framework_Cocoa-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9dbadb22826392c48b00087359f66579f8404a4f4f77498f31f9869c54bb0fa9"}, - {file = "pyobjc_framework_Cocoa-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7adf8b57da1c1292c24375b8e74b6dd45f99a4d3c10ba925a9b38f63a97ba782"}, - {file = "pyobjc_framework_Cocoa-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc8279c8c1544087d46a7e99324f093029df89b8c527c5da2a682bad4cb3197e"}, + {file = "pyobjc-framework-Cocoa-8.5.tar.gz", hash = "sha256:569bd3a020f64b536fb2d1c085b37553e50558c9f907e08b73ffc16ae68e1861"}, + {file = "pyobjc_framework_Cocoa-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7a7c160416696bf6035dfcdf0e603aaa52858d6afcddfcc5ab41733619ac2529"}, + {file = "pyobjc_framework_Cocoa-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6ceba444282030be8596b812260e8d28b671254a51052ad778d32da6e17db847"}, + {file = "pyobjc_framework_Cocoa-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f46b2b161b8dd40c7b9e00bc69636c3e6480b2704a69aee22ee0154befbe163a"}, + {file = "pyobjc_framework_Cocoa-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b31d425aee8698cbf62b187338f5ca59427fa4dca2153a73866f7cb410713119"}, + {file = "pyobjc_framework_Cocoa-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:898359ac1f76eedec8aa156847682378a8950824421c40edb89391286e607dc4"}, + {file = "pyobjc_framework_Cocoa-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:baa2947f76b119a3360973d74d57d6dada87ac527bab9a88f31596af392f123c"}, ] pyobjc-framework-quartz = [ - {file = "pyobjc-framework-Quartz-8.2.tar.gz", hash = "sha256:219d8797235bf071723f8b0f30a681de0b12875e2d04ae902a3a269f72de0b66"}, - {file = "pyobjc_framework_Quartz-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e5ab3117201a494b11bb1b80febf5dd288111a196b35731815b656ae30ee6ce3"}, - {file = "pyobjc_framework_Quartz-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4941b3039ab7bcf89cb4255c381358de2383c1ab45c9e00c3b64655f271a3b32"}, - {file = "pyobjc_framework_Quartz-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ab75a50dea84ec794641522d9f035fc6c19ec4eb37a56c9186b9943575fbc7ab"}, - {file = "pyobjc_framework_Quartz-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8cb687b20ebfc467034868b38945b573d1c50f237e379cf86c4f557c9766b759"}, - {file = "pyobjc_framework_Quartz-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:8b5c3ca1fef900fa66aafe82fff07bc352c60ea7dceed2bb9b5b1db0957b4fea"}, - {file = "pyobjc_framework_Quartz-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7bca7e649da77056348d71032def44e345043319d71b0c592197888d92e3eec1"}, + {file = "pyobjc-framework-Quartz-8.5.tar.gz", hash = "sha256:d2bc5467a792ddc04814f12a1e9c2fcaf699a1c3ad3d4264cfdce6b9c7b10624"}, + {file = "pyobjc_framework_Quartz-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e9f0fb663f7872c9de94169031ac42b91ad01bd4cad49a9f1a0164be8f028426"}, + {file = "pyobjc_framework_Quartz-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:567eec91287cfe9a1b6433717192c585935de8f3daa28d82ce72fdd6c7ac00f6"}, + {file = "pyobjc_framework_Quartz-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f910ab41a712ffc7a8c3e3716a2d6f39ea4419004b26a2fd2d2f740ff5c262c"}, + {file = "pyobjc_framework_Quartz-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:29d07066781628278bf0e5278abcfc96ef6724c66c5629a0b4c214d319a82e55"}, + {file = "pyobjc_framework_Quartz-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:72abcde1a3d72be11f2c881c9b9872044c8f2de86d2047b67fe771713638b107"}, + {file = "pyobjc_framework_Quartz-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8809b9a2df2f461697bdb45b6d1b5a4f881f88f09450e3990858e64e3e26c530"}, ] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, @@ -2641,6 +2736,14 @@ python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] +python-engineio = [ + {file = "python-engineio-3.14.2.tar.gz", hash = "sha256:eab4553f2804c1ce97054c8b22cf0d5a9ab23128075248b97e1a5b2f29553085"}, + {file = "python_engineio-3.14.2-py2.py3-none-any.whl", hash = "sha256:5a9e6086d192463b04a1428ff1f85b6ba631bbb19d453b144ffc04f530542b84"}, +] +python-socketio = [ + {file = "python-socketio-4.6.1.tar.gz", hash = "sha256:cd1f5aa492c1eb2be77838e837a495f117e17f686029ebc03d62c09e33f4fa10"}, + {file = "python_socketio-4.6.1-py2.py3-none-any.whl", hash = "sha256:5a21da53fdbdc6bb6c8071f40e13d100e0b279ad997681c2492478e06f370523"}, +] python-xlib = [ {file = "python-xlib-0.31.tar.gz", hash = "sha256:74d83a081f532bc07f6d7afcd6416ec38403d68f68b9b9dc9e1f28fbf2d799e9"}, {file = "python_xlib-0.31-py2.py3-none-any.whl", hash = "sha256:1ec6ce0de73d9e6592ead666779a5732b384e5b8fb1f1886bd0a81cafa477759"}, @@ -2649,8 +2752,8 @@ python3-xlib = [ {file = "python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8"}, ] pytz = [ - {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, - {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, + {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, + {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, ] pywin32 = [ {file = "pywin32-301-cp35-cp35m-win32.whl", hash = "sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7"}, @@ -2669,8 +2772,8 @@ pywin32-ctypes = [ {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, ] "qt.py" = [ - {file = "Qt.py-1.3.6-py2.py3-none-any.whl", hash = "sha256:7edf6048d07a6924707506b5ba34a6e05d66dde9a3f4e3a62f9996ccab0b91c7"}, - {file = "Qt.py-1.3.6.tar.gz", hash = "sha256:0d78656a2f814602eee304521c7bf5da0cec414818b3833712c77524294c404a"}, + {file = "Qt.py-1.3.7-py2.py3-none-any.whl", hash = "sha256:150099d1c6f64c9621a2c9d79d45102ec781c30ee30ee69fc082c6e9be7324fe"}, + {file = "Qt.py-1.3.7.tar.gz", hash = "sha256:803c7bdf4d6230f9a466be19d55934a173eabb61406d21cb91e80c2a3f773b1f"}, ] qtawesome = [ {file = "QtAwesome-0.7.3-py2.py3-none-any.whl", hash = "sha256:ddf4530b4af71cec13b24b88a4cdb56ec85b1e44c43c42d0698804c7137b09b0"}, @@ -2685,16 +2788,16 @@ recommonmark = [ {file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"}, ] requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] rsa = [ {file = "rsa-4.8-py3-none-any.whl", hash = "sha256:95c5d300c4e879ee69708c428ba566c59478fd653cc3a22243eeb8ed846950bb"}, {file = "rsa-4.8.tar.gz", hash = "sha256:5c6bd9dc7a543b7fe4304a631f8a8a3b674e2bbfc49c2ae96200cdbe55df6b17"}, ] secretstorage = [ - {file = "SecretStorage-3.3.1-py3-none-any.whl", hash = "sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f"}, - {file = "SecretStorage-3.3.1.tar.gz", hash = "sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195"}, + {file = "SecretStorage-3.3.2-py3-none-any.whl", hash = "sha256:755dc845b6ad76dcbcbc07ea3da75ae54bb1ea529eb72d15f83d26499a5df319"}, + {file = "SecretStorage-3.3.2.tar.gz", hash = "sha256:0a8eb9645b320881c222e827c26f4cfcf55363e8b374a021981ef886657a912f"}, ] semver = [ {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, @@ -2705,8 +2808,8 @@ six = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] slack-sdk = [ - {file = "slack_sdk-3.13.0-py2.py3-none-any.whl", hash = "sha256:54f2a5f7419f1ab932af9e3200f7f2f93db96e0f0eb8ad7d3b4214aa9f124641"}, - {file = "slack_sdk-3.13.0.tar.gz", hash = "sha256:aae6ce057e286a5e7fe7a9f256e85b886eee556def8e04b82b08f699e64d7f67"}, + {file = "slack_sdk-3.17.0-py2.py3-none-any.whl", hash = "sha256:0816efc43d1d2db8286e8dbcbb2e86fd0f71c206c01c521c2cb054ecb40f9ced"}, + {file = "slack_sdk-3.17.0.tar.gz", hash = "sha256:860cd0e50c454b955f14321c8c5486a47cc1e0e84116acdb009107f836752feb"}, ] smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, @@ -2717,20 +2820,20 @@ snowballstemmer = [ {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] speedcopy = [ - {file = "speedcopy-2.1.2-py3-none-any.whl", hash = "sha256:91e271b84c00952812dbf669d360b2610fd8fa198670373e02acf2a04db89a4c"}, - {file = "speedcopy-2.1.2.tar.gz", hash = "sha256:1b2d779fadebd53a59384f7d286c40b2ef382e0d000fa53011699fcd3190d33f"}, + {file = "speedcopy-2.1.4-py3-none-any.whl", hash = "sha256:e09eb1de67ae0e0b51d5b99a28882009d565a37a3cb3c6bae121e3a5d3cccb17"}, + {file = "speedcopy-2.1.4.tar.gz", hash = "sha256:eff007a97e49ec1934df4fa8074f4bd1cf4a3b14c5499d914988785cff0c199a"}, ] sphinx = [ - {file = "Sphinx-3.5.3-py3-none-any.whl", hash = "sha256:3f01732296465648da43dec8fb40dc451ba79eb3e2cc5c6d79005fd98197107d"}, - {file = "Sphinx-3.5.3.tar.gz", hash = "sha256:ce9c228456131bab09a3d7d10ae58474de562a6f79abb3dc811ae401cf8c1abc"}, + {file = "Sphinx-5.0.1-py3-none-any.whl", hash = "sha256:36aa2a3c2f6d5230be94585bc5d74badd5f9ed8f3388b8eedc1726fe45b1ad30"}, + {file = "Sphinx-5.0.1.tar.gz", hash = "sha256:f4da1187785a5bc7312cc271b0e867a93946c319d106363e102936a3d9857306"}, ] sphinx-qt-documentation = [ - {file = "sphinx_qt_documentation-0.3-py3-none-any.whl", hash = "sha256:bee247cb9e4fc03fc496d07adfdb943100e1103320c3e5e820e0cfa7c790d9b6"}, - {file = "sphinx_qt_documentation-0.3.tar.gz", hash = "sha256:f09a0c9d9e989172ba3e282b92bf55613bb23ad47315ec5b0d38536b343ac6c8"}, + {file = "sphinx_qt_documentation-0.4-py3-none-any.whl", hash = "sha256:fa131093f75cd1bd48699cd132e18e4d46ba9eaadc070e6026867cea75ecdb7b"}, + {file = "sphinx_qt_documentation-0.4.tar.gz", hash = "sha256:f43ba17baa93e353fb94045027fb67f9d935ed158ce8662de93f08b88eec6774"}, ] sphinx-rtd-theme = [ - {file = "sphinx_rtd_theme-0.5.1-py2.py3-none-any.whl", hash = "sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113"}, - {file = "sphinx_rtd_theme-0.5.1.tar.gz", hash = "sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5"}, + {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, + {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, @@ -2773,34 +2876,34 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ - {file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"}, - {file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"}, + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] typed-ast = [ - {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"}, - {file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"}, - {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"}, - {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"}, - {file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"}, - {file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"}, - {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"}, - {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"}, - {file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"}, - {file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"}, - {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"}, - {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"}, - {file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"}, - {file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"}, - {file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"}, - {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"}, - {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"}, - {file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"}, - {file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"}, - {file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"}, - {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"}, - {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"}, - {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, - {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] typing-extensions = [ {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, @@ -2811,8 +2914,8 @@ uritemplate = [ {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, ] urllib3 = [ - {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, - {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, + {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, + {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -2823,57 +2926,70 @@ websocket-client = [ {file = "websocket_client-0.59.0-py2.py3-none-any.whl", hash = "sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32"}, ] wrapt = [ - {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca"}, - {file = "wrapt-1.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44"}, - {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056"}, - {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785"}, - {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096"}, - {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33"}, - {file = "wrapt-1.13.3-cp310-cp310-win32.whl", hash = "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f"}, - {file = "wrapt-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755"}, - {file = "wrapt-1.13.3-cp35-cp35m-win32.whl", hash = "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851"}, - {file = "wrapt-1.13.3-cp35-cp35m-win_amd64.whl", hash = "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13"}, - {file = "wrapt-1.13.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918"}, - {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade"}, - {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc"}, - {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf"}, - {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125"}, - {file = "wrapt-1.13.3-cp36-cp36m-win32.whl", hash = "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36"}, - {file = "wrapt-1.13.3-cp36-cp36m-win_amd64.whl", hash = "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10"}, - {file = "wrapt-1.13.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068"}, - {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709"}, - {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df"}, - {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2"}, - {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b"}, - {file = "wrapt-1.13.3-cp37-cp37m-win32.whl", hash = "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829"}, - {file = "wrapt-1.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"}, - {file = "wrapt-1.13.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9"}, - {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554"}, - {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c"}, - {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b"}, - {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce"}, - {file = "wrapt-1.13.3-cp38-cp38-win32.whl", hash = "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79"}, - {file = "wrapt-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb"}, - {file = "wrapt-1.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb"}, - {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32"}, - {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7"}, - {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e"}, - {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640"}, - {file = "wrapt-1.13.3-cp39-cp39-win32.whl", hash = "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374"}, - {file = "wrapt-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb"}, - {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, + {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, + {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, + {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, + {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, + {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, + {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, + {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, + {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, + {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, + {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, + {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, + {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, + {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, + {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, + {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, + {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, ] wsrpc-aiohttp = [ {file = "wsrpc-aiohttp-3.2.0.tar.gz", hash = "sha256:f467abc51bcdc760fc5aeb7041abdeef46eeca3928dc43dd6e7fa7a533563818"}, @@ -2954,6 +3070,6 @@ yarl = [ {file = "yarl-1.7.2.tar.gz", hash = "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd"}, ] zipp = [ - {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, - {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, + {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, + {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, ] From 93caf11efdc0f4bfe0504bdb5ac973babf06028a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 17:10:55 +0200 Subject: [PATCH 0519/1227] added fake 'charset_normalizer' for python 2 hosts --- openpype/vendor/python/python_2/charset_normalizer.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 openpype/vendor/python/python_2/charset_normalizer.py diff --git a/openpype/vendor/python/python_2/charset_normalizer.py b/openpype/vendor/python/python_2/charset_normalizer.py new file mode 100644 index 0000000000..84ec661759 --- /dev/null +++ b/openpype/vendor/python/python_2/charset_normalizer.py @@ -0,0 +1,8 @@ +"""Fake 'charset_normalizer' for Python 2 to raise ImportError. + +Needed for 'requests' module which first checks for existence of +'charset_normalizer' (Python 3) but does not raise 'ImportError' but +'SyntaxError'. +""" + +raise ImportError("charset_normalizer not available") From 248e38b52d1bfbd14226fbe79a12d4f2f5a8a016 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 8 Jun 2022 17:22:30 +0200 Subject: [PATCH 0520/1227] Deadline: adding condition for `review` instance data key to be able to pass review to metadata json from nuke --- .../deadline/plugins/publish/submit_publish_job.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 78ab935e42..1891731c93 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -640,6 +640,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): def _solve_families(self, instance, preview=False): families = instance.get("families") + + # test also instance data review attribute + preview = preview or instance.get("review") + # if we have one representation with preview tag # flag whole instance for review and for ftrack if preview: @@ -749,6 +753,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "family": "prerender", "families": []}) + # also include review attribute if available + if "review" in data: + instance_skeleton_data["review"] = data["review"] + # skip locking version if we are creating v01 instance_version = instance.data.get("version") # take this if exists if instance_version != 1: From 6758bcc2a2aec0139ed2a448daba3c08ea60a584 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 8 Jun 2022 17:22:55 +0200 Subject: [PATCH 0521/1227] Nuke: wrong name in docstring --- openpype/hosts/nuke/plugins/publish/extract_render_local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_render_local.py b/openpype/hosts/nuke/plugins/publish/extract_render_local.py index 50a5d01483..057bca11ac 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_render_local.py +++ b/openpype/hosts/nuke/plugins/publish/extract_render_local.py @@ -7,7 +7,7 @@ import clique class NukeRenderLocal(openpype.api.Extractor): # TODO: rewrite docstring to nuke - """Render the current Fusion composition locally. + """Render the current Nuke composition locally. Extract the result of savers by starting a comp render This will run the local render of Fusion. From e4cee4a98ddd4150ee371d338350aefc4964df49 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 19:18:20 +0200 Subject: [PATCH 0522/1227] fix loader --- openpype/tools/loader/model.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index e8e0480d9c..f2b7e9a6a4 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -206,9 +206,6 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): if subset_doc_projection: self.subset_doc_projection = subset_doc_projection - self.asset_doc_projection = asset_doc_projection - self.subset_doc_projection = subset_doc_projection - self.repre_icons = {} self.sync_server = None self.active_site = self.active_provider = None From 1cea16b97b416cda2cd12fca9365b046bc05a2eb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 19:18:27 +0200 Subject: [PATCH 0523/1227] fix launcher --- openpype/tools/launcher/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 307f591d96..3f899cc05e 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -651,9 +651,9 @@ class LauncherModel(QtCore.QObject): self._asset_refresh_thread = None def _refresh_assets(self): - asset_docs = get_assets( - self._last_project_name, fields=list(self._asset_projection.keys()) - ) + asset_docs = list(get_assets( + self._last_project_name, fields=self._asset_projection.keys() + )) if not self._refreshing_assets: return self._refreshing_assets = False From 6fcf8722370fcbdf7fabba7dcc1012f8484f836a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 19:20:53 +0200 Subject: [PATCH 0524/1227] fix get_last_version --- openpype/client/entities.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 2459ea3e92..66204a4b19 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -655,6 +655,10 @@ def get_last_versions(project_name, subset_ids, fields=None): doc["_version_id"] for doc in conn.aggregate(_pipeline) ] + fields = _prepare_fields(fields) + if fields and "parent" not in fields: + fields.append("parent") + version_docs = get_versions( project_name, version_ids=version_ids, fields=fields ) From b63ae623f4715401528f61fe8e469f47a70a0e5c Mon Sep 17 00:00:00 2001 From: "felix.wang" Date: Wed, 8 Jun 2022 14:54:42 -0700 Subject: [PATCH 0525/1227] Borrowed child node discovery logic from Colorbleed, to properly support instanced objects. --- .../maya/plugins/publish/collect_instances.py | 49 ++++++++++++++++--- .../plugins/publish/extract_pointcache.py | 11 +++-- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_instances.py b/openpype/hosts/maya/plugins/publish/collect_instances.py index 1d59a68bf6..48cc446541 100644 --- a/openpype/hosts/maya/plugins/publish/collect_instances.py +++ b/openpype/hosts/maya/plugins/publish/collect_instances.py @@ -1,9 +1,50 @@ from maya import cmds +import maya.api.OpenMaya as om import pyblish.api import json +def get_all_children(nodes): + """Return all children of `nodes` including each instanced child. + Using maya.cmds.listRelatives(allDescendents=True) includes only the first + instance. As such, this function acts as an optimal replacement with a + focus on a fast query. + Borrowed from Colorbleed: https://tinyurl.com/bdht6fyh + + """ + + sel = om.MSelectionList() + traversed = set() + iterator = om.MItDag(om.MItDag.kDepthFirst) + for node in nodes: + + if node in traversed: + # Ignore if already processed as a child + # before + continue + + sel.clear() + sel.add(node) + dag = sel.getDagPath(0) + + iterator.reset(dag) + iterator.next() # ignore self + while not iterator.isDone(): + + path = iterator.fullPathName() + + if path in traversed: + iterator.prune() + iterator.next() + continue + + traversed.add(path) + iterator.next() + + return list(traversed) + + class CollectInstances(pyblish.api.ContextPlugin): """Gather instances by objectSet and pre-defined attribute @@ -86,12 +127,8 @@ class CollectInstances(pyblish.api.ContextPlugin): # Collect members members = cmds.ls(members, long=True) or [] - # `maya.cmds.listRelatives(noIntermediate=True)` only works when - # `shapes=True` argument is passed, since we also want to include - # transforms we filter afterwards. - children = cmds.listRelatives(members, - allDescendents=True, - fullPath=True) or [] + dag_members = cmds.ls(members, type="dagNode", long=True) + children = get_all_children(dag_members) children = cmds.ls(children, noIntermediate=True, long=True) parents = [] diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 4aaf223403..33bcdaf92c 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -65,7 +65,7 @@ class ExtractAlembic(openpype.api.Extractor): "writeColorSets": writeColorSets, "writeFaceSets": writeFaceSets, "uvWrite": True, - "selection": False, + "selection": True, "worldSpace": instance.data.get("worldSpace", True) } @@ -80,10 +80,11 @@ class ExtractAlembic(openpype.api.Extractor): options["writeUVSets"] = True with suspended_refresh(): - extract_alembic(file=path, - startFrame=start, - endFrame=end, - **options) + with maintained_selection(): + extract_alembic(file=path, + startFrame=start, + endFrame=end, + **options) if "representations" not in instance.data: instance.data["representations"] = [] From 122536675fb043fda7d62710149c65cfd089d674 Mon Sep 17 00:00:00 2001 From: "felix.wang" Date: Wed, 8 Jun 2022 14:58:50 -0700 Subject: [PATCH 0526/1227] Use next() builtin --- openpype/hosts/maya/plugins/publish/collect_instances.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_instances.py b/openpype/hosts/maya/plugins/publish/collect_instances.py index 48cc446541..c76afe53f6 100644 --- a/openpype/hosts/maya/plugins/publish/collect_instances.py +++ b/openpype/hosts/maya/plugins/publish/collect_instances.py @@ -29,18 +29,18 @@ def get_all_children(nodes): dag = sel.getDagPath(0) iterator.reset(dag) - iterator.next() # ignore self + next(iterator) # ignore self while not iterator.isDone(): path = iterator.fullPathName() if path in traversed: iterator.prune() - iterator.next() + next(iterator) continue traversed.add(path) - iterator.next() + next(iterator) return list(traversed) From ce7b19d51b687f2e5a8abff64c8b741fd933bbad Mon Sep 17 00:00:00 2001 From: "felix.wang" Date: Wed, 8 Jun 2022 15:04:55 -0700 Subject: [PATCH 0527/1227] Change back to do actual selection. --- openpype/hosts/maya/plugins/publish/extract_pointcache.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 33bcdaf92c..c4c8610ebb 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -81,6 +81,7 @@ class ExtractAlembic(openpype.api.Extractor): with suspended_refresh(): with maintained_selection(): + cmds.select(nodes, noExpand=True) extract_alembic(file=path, startFrame=start, endFrame=end, From a1e95dd15ad7c4e73837262b8f2de3bf543213c4 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 9 Jun 2022 10:58:18 +0300 Subject: [PATCH 0528/1227] Fix oiio subprocess arguments. --- openpype/plugins/publish/extract_jpeg_exr.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index f474714780..e509063dca 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -130,9 +130,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): src_path, "-o", dst_path ] - subprocess_exr = " ".join(oiio_cmd) - self.log.info(f"running: {subprocess_exr}") - run_subprocess(subprocess_exr, logger=self.log) + self.log.info(f"running: {oiio_cmd}") + run_subprocess(oiio_cmd, logger=self.log) def create_thumbnail_ffmpeg(self, src_path, dst_path): self.log.info("outputting {}".format(dst_path)) From f79f9347ece5db8001608b595cff4cee67e16164 Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Thu, 9 Jun 2022 11:12:48 +0300 Subject: [PATCH 0529/1227] Update openpype/plugins/publish/extract_jpeg_exr.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/extract_jpeg_exr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index e509063dca..0bbfd95862 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -161,5 +161,5 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.append(path_to_subprocess_arg(dst_path)) subprocess_command = " ".join(jpeg_items) run_subprocess( - subprocess_command, shell=True, logger=self.log - ) + subprocess_command, shell=True, logger=self.log + ) From 012f21ce86e12daeee549830b4cbfdca03991709 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 9 Jun 2022 11:17:01 +0300 Subject: [PATCH 0530/1227] Fix error messages. --- openpype/plugins/publish/extract_jpeg_exr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index e509063dca..874a1dc40d 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -83,12 +83,12 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): self.log.info("Input can be read by OIIO, converting with oiiotool now.") thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa else: - self.log.info("converting with") + self.log.info("Converting with FFMPEG because input can't be read by OIIO.") thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa # Skip the rest of the process if the thumbnail wasn't created if not thumbnail_created: - + self.log.warning("Thumbanil has not been created.") return new_repre = { From 5e817c57e4c46087542fdf85b8866d063e80c933 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 9 Jun 2022 11:18:14 +0300 Subject: [PATCH 0531/1227] Fix style warning. --- openpype/plugins/publish/extract_jpeg_exr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index c40c99b1f8..730d0167c7 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -80,10 +80,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # If the input can read by OIIO then use OIIO method for # conversion otherwise use ffmpeg if returncode == 0: - self.log.info("Input can be read by OIIO, converting with oiiotool now.") + self.log.info("Input can be read by OIIO, converting with oiiotool now.") # noqa thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa else: - self.log.info("Converting with FFMPEG because input can't be read by OIIO.") + self.log.info("Converting with FFMPEG because input can't be read by OIIO.") # noqa thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa # Skip the rest of the process if the thumbnail wasn't created From 5e0589dc57e77f813a32f95afcfc8f508d7cf2ec Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 9 Jun 2022 11:20:27 +0300 Subject: [PATCH 0532/1227] Restore removed logging messaage. --- openpype/plugins/publish/extract_jpeg_exr.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 730d0167c7..c658e17cab 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -130,7 +130,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): src_path, "-o", dst_path ] - self.log.info(f"running: {oiio_cmd}") + subprocess_exr = " ".join(oiio_cmd) + self.log.info(f"running: {subprocess_exr}") run_subprocess(oiio_cmd, logger=self.log) def create_thumbnail_ffmpeg(self, src_path, dst_path): From 886646c0269d3188c9f46deb5bc6f4a7d6a8f131 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 10:34:55 +0200 Subject: [PATCH 0533/1227] modified implemenetation to be able also use review session to download delivery --- .../event_handlers_user/action_delivery.py | 244 ++++++++++++------ 1 file changed, 169 insertions(+), 75 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_delivery.py b/openpype/modules/ftrack/event_handlers_user/action_delivery.py index 9ef2a1668e..86d88ef7cc 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delivery.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delivery.py @@ -8,6 +8,9 @@ from bson.objectid import ObjectId from openpype.api import Anatomy, config from openpype_modules.ftrack.lib import BaseAction, statics_icon from openpype_modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY +from openpype_modules.ftrack.lib.custom_attributes import ( + query_custom_attributes +) from openpype.lib.delivery import ( path_from_representation, get_format_dict, @@ -28,14 +31,14 @@ class Delivery(BaseAction): settings_key = "delivery_action" def __init__(self, *args, **kwargs): - self.db_con = AvalonMongoDB() + self.dbcon = AvalonMongoDB() super(Delivery, self).__init__(*args, **kwargs) def discover(self, session, entities, event): is_valid = False for entity in entities: - if entity.entity_type.lower() == "assetversion": + if entity.entity_type.lower() in ("assetversion", "reviewsession"): is_valid = True break @@ -54,9 +57,9 @@ class Delivery(BaseAction): project_entity = self.get_project_from_entity(entities[0]) project_name = project_entity["full_name"] - self.db_con.install() - self.db_con.Session["AVALON_PROJECT"] = project_name - project_doc = self.db_con.find_one({"type": "project"}) + self.dbcon.install() + self.dbcon.Session["AVALON_PROJECT"] = project_name + project_doc = self.dbcon.find_one({"type": "project"}, {"name": True}) if not project_doc: return { "success": False, @@ -65,8 +68,8 @@ class Delivery(BaseAction): ).format(project_name) } - repre_names = self._get_repre_names(entities) - self.db_con.uninstall() + repre_names = self._get_repre_names(session, entities) + self.dbcon.uninstall() items.append({ "type": "hidden", @@ -195,47 +198,109 @@ class Delivery(BaseAction): "title": title } - def _get_repre_names(self, entities): - version_ids = self._get_interest_version_ids(entities) - repre_docs = self.db_con.find({ + def _get_repre_names(self, session, entities): + version_ids = self._get_interest_version_ids(session, entities) + if not version_ids: + return [] + repre_docs = self.dbcon.find({ "type": "representation", "parent": {"$in": version_ids} }) return list(sorted(repre_docs.distinct("name"))) - def _get_interest_version_ids(self, entities): - parent_ent_by_id = {} + def _get_interest_version_ids(self, session, entities): + # Extract AssetVersion entities + asset_versions = self._extract_asset_versions(session, entities) + # Prepare Asset ids + asset_ids = { + asset_version["asset_id"] + for asset_version in asset_versions + } + # Query Asset entities + assets = session.query(( + "select id, name, context_id from Asset where id in ({})" + ).format(self.join_query_keys(asset_ids))).all() + assets_by_id = { + asset["id"]: asset + for asset in assets + } + parent_ids = set() subset_names = set() version_nums = set() - for entity in entities: - asset = entity["asset"] - parent = asset["parent"] - parent_ent_by_id[parent["id"]] = parent + for asset_version in asset_versions: + asset_id = asset_version["asset_id"] + asset = assets_by_id[asset_id] - subset_name = asset["name"] - subset_names.add(subset_name) + parent_ids.add(asset["context_id"]) + subset_names.add(asset["name"]) + version_nums.add(asset_version["version"]) - version = entity["version"] - version_nums.add(version) - - asset_docs_by_ftrack_id = self._get_asset_docs(parent_ent_by_id) + asset_docs_by_ftrack_id = self._get_asset_docs(session, parent_ids) subset_docs = self._get_subset_docs( - asset_docs_by_ftrack_id, subset_names, entities + asset_docs_by_ftrack_id, + subset_names, + asset_versions, + assets_by_id ) version_docs = self._get_version_docs( - asset_docs_by_ftrack_id, subset_docs, version_nums, entities + asset_docs_by_ftrack_id, + subset_docs, + version_nums, + asset_versions, + assets_by_id ) return [version_doc["_id"] for version_doc in version_docs] + def _extract_asset_versions(self, session, entities): + asset_version_ids = set() + review_session_ids = set() + for entity in entities: + entity_type_low = entity.entity_type.lower() + if entity_type_low == "assetversion": + asset_version_ids.add(entity["id"]) + elif entity_type_low == "reviewsession": + review_session_ids.add(entity["id"]) + + for version_id in self._get_asset_version_ids_from_review_sessions( + session, review_session_ids + ): + asset_version_ids.add(version_id) + + asset_versions = session.query(( + "select id, version, asset_id from AssetVersion where id in ({})" + ).format(self.join_query_keys(asset_version_ids))).all() + + return asset_versions + + def _get_asset_version_ids_from_review_sessions( + self, session, review_session_ids + ): + if not review_session_ids: + return set() + review_session_objects = session.query(( + "select version_id from ReviewSessionObject" + " where review_session_id in ({})" + ).format(self.join_query_keys(review_session_ids))).all() + + return { + review_session_object["version_id"] + for review_session_object in review_session_objects + } + def _get_version_docs( - self, asset_docs_by_ftrack_id, subset_docs, version_nums, entities + self, + asset_docs_by_ftrack_id, + subset_docs, + version_nums, + asset_versions, + assets_by_id ): subset_docs_by_id = { subset_doc["_id"]: subset_doc for subset_doc in subset_docs } - version_docs = list(self.db_con.find({ + version_docs = list(self.dbcon.find({ "type": "version", "parent": {"$in": list(subset_docs_by_id.keys())}, "name": {"$in": list(version_nums)} @@ -255,11 +320,13 @@ class Delivery(BaseAction): ) filtered_versions = [] - for entity in entities: - asset = entity["asset"] - - parent = asset["parent"] - asset_doc = asset_docs_by_ftrack_id[parent["id"]] + for asset_version in asset_versions: + asset_id = asset_version["asset_id"] + asset = assets_by_id[asset_id] + parent_id = asset["context_id"] + asset_doc = asset_docs_by_ftrack_id.get(parent_id) + if not asset_doc: + continue subsets_by_name = version_docs_by_parent_id.get(asset_doc["_id"]) if not subsets_by_name: @@ -270,20 +337,24 @@ class Delivery(BaseAction): if not version_docs_by_version: continue - version = entity["version"] + version = asset_version["version"] version_doc = version_docs_by_version.get(version) if version_doc: filtered_versions.append(version_doc) return filtered_versions def _get_subset_docs( - self, asset_docs_by_ftrack_id, subset_names, entities + self, + asset_docs_by_ftrack_id, + subset_names, + asset_versions, + assets_by_id ): - asset_doc_ids = list() - for asset_doc in asset_docs_by_ftrack_id.values(): - asset_doc_ids.append(asset_doc["_id"]) - - subset_docs = list(self.db_con.find({ + asset_doc_ids = [ + asset_doc["_id"] + for asset_doc in asset_docs_by_ftrack_id.values() + ] + subset_docs = list(self.dbcon.find({ "type": "subset", "parent": {"$in": asset_doc_ids}, "name": {"$in": list(subset_names)} @@ -295,11 +366,14 @@ class Delivery(BaseAction): subset_docs_by_parent_id[asset_id][subset_name] = subset_doc filtered_subsets = [] - for entity in entities: - asset = entity["asset"] + for asset_version in asset_versions: + asset_id = asset_version["asset_id"] + asset = assets_by_id[asset_id] - parent = asset["parent"] - asset_doc = asset_docs_by_ftrack_id[parent["id"]] + parent_id = asset["context_id"] + asset_doc = asset_docs_by_ftrack_id.get(parent_id) + if not asset_doc: + continue subsets_by_name = subset_docs_by_parent_id.get(asset_doc["_id"]) if not subsets_by_name: @@ -311,40 +385,60 @@ class Delivery(BaseAction): filtered_subsets.append(subset_doc) return filtered_subsets - def _get_asset_docs(self, parent_ent_by_id): - asset_docs = list(self.db_con.find({ + def _get_asset_docs(self, session, parent_ids): + asset_docs = list(self.dbcon.find({ "type": "asset", - "data.ftrackId": {"$in": list(parent_ent_by_id.keys())} + "data.ftrackId": {"$in": list(parent_ids)} })) - asset_docs_by_ftrack_id = { - asset_doc["data"]["ftrackId"]: asset_doc - for asset_doc in asset_docs + + asset_docs_by_ftrack_id = {} + for asset_doc in asset_docs: + ftrack_id = asset_doc["data"].get("ftrackId") + if ftrack_id: + asset_docs_by_ftrack_id[ftrack_id] = asset_doc + + attr_def = session.query(( + "select id from CustomAttributeConfiguration where key is \"{}\"" + ).format(CUST_ATTR_ID_KEY)).first() + if attr_def is None: + return asset_docs_by_ftrack_id + + avalon_mongo_id_values = query_custom_attributes( + session, [attr_def["id"]], parent_ids, True + ) + entity_ids_by_mongo_id = { + ObjectId(item["value"]): item["entity_id"] + for item in avalon_mongo_id_values + if item["value"] } - entities_by_mongo_id = {} - entities_by_names = {} - for ftrack_id, entity in parent_ent_by_id.items(): - if ftrack_id not in asset_docs_by_ftrack_id: - parent_mongo_id = entity["custom_attributes"].get( - CUST_ATTR_ID_KEY - ) - if parent_mongo_id: - entities_by_mongo_id[ObjectId(parent_mongo_id)] = entity - else: - entities_by_names[entity["name"]] = entity + missing_ids = set(parent_ids) + for entity_id in set(entity_ids_by_mongo_id.values()): + if entity_id in missing_ids: + missing_ids.remove(entity_id) + + entity_ids_by_name = {} + if missing_ids: + not_found_entities = session.query(( + "select id, name from TypedContext where id in ({})" + ).format(self.join_query_keys(missing_ids))).all() + entity_ids_by_name = { + entity["name"]: entity["id"] + for entity in not_found_entities + } expressions = [] - if entities_by_mongo_id: + if entity_ids_by_mongo_id: expression = { "type": "asset", - "_id": {"$in": list(entities_by_mongo_id.keys())} + "_id": {"$in": list(entity_ids_by_mongo_id.keys())} } expressions.append(expression) - if entities_by_names: + if entity_ids_by_name: expression = { "type": "asset", - "name": {"$in": list(entities_by_names.keys())} + "name": {"$in": list(entity_ids_by_name.keys())} } expressions.append(expression) @@ -354,15 +448,15 @@ class Delivery(BaseAction): else: filter = {"$or": expressions} - asset_docs = self.db_con.find(filter) + asset_docs = self.dbcon.find(filter) for asset_doc in asset_docs: - if asset_doc["_id"] in entities_by_mongo_id: - entity = entities_by_mongo_id[asset_doc["_id"]] - asset_docs_by_ftrack_id[entity["id"]] = asset_doc + if asset_doc["_id"] in entity_ids_by_mongo_id: + entity_id = entity_ids_by_mongo_id[asset_doc["_id"]] + asset_docs_by_ftrack_id[entity_id] = asset_doc - elif asset_doc["name"] in entities_by_names: - entity = entities_by_names[asset_doc["name"]] - asset_docs_by_ftrack_id[entity["id"]] = asset_doc + elif asset_doc["name"] in entity_ids_by_name: + entity_id = entity_ids_by_name[asset_doc["name"]] + asset_docs_by_ftrack_id[entity_id] = asset_doc return asset_docs_by_ftrack_id @@ -396,7 +490,7 @@ class Delivery(BaseAction): session.commit() try: - self.db_con.install() + self.dbcon.install() report = self.real_launch(session, entities, event) except Exception as exc: @@ -422,7 +516,7 @@ class Delivery(BaseAction): else: job["status"] = "failed" session.commit() - self.db_con.uninstall() + self.dbcon.uninstall() if not report["success"]: self.show_interface( @@ -464,11 +558,11 @@ class Delivery(BaseAction): if not os.path.exists(location_path): os.makedirs(location_path) - self.db_con.Session["AVALON_PROJECT"] = project_name + self.dbcon.Session["AVALON_PROJECT"] = project_name self.log.debug("Collecting representations to process.") - version_ids = self._get_interest_version_ids(entities) - repres_to_deliver = list(self.db_con.find({ + version_ids = self._get_interest_version_ids(session, entities) + repres_to_deliver = list(self.dbcon.find({ "type": "representation", "parent": {"$in": version_ids}, "name": {"$in": repre_names} From 6b9a1b834d3de4f1d2b399c916a50ac1282a4b6f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 10:48:29 +0200 Subject: [PATCH 0534/1227] convert queried cursor to list in scene inventory --- openpype/tools/sceneinventory/view.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 8164c48a5d..0b54d40ed7 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -669,11 +669,11 @@ class SceneInventoryView(QtWidgets.QTreeView): fields=["parent"] ) - version_docs = get_versions( + version_docs = list(get_versions( project_name, subset_ids=[repre_version_doc["parent"]], hero=True - ) + )) hero_version = None standard_versions = [] for version_doc in version_docs: From 39ea438d7d8c2010b914c300ae84f6536e71e0a4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 10:51:33 +0200 Subject: [PATCH 0535/1227] fix typo in passed kwarg --- openpype/tools/sceneinventory/view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 0b54d40ed7..63d181b2d6 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -92,7 +92,7 @@ class SceneInventoryView(QtWidgets.QTreeView): project_name = legacy_io.active_project() repre_docs = get_representations( - project_name, representaion_ids=repre_ids, fields=["parent"] + project_name, representation_ids=repre_ids, fields=["parent"] ) version_ids = [] From a48d77e609f84a7a04bf5de95fce12e05461ca6c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 10:53:15 +0200 Subject: [PATCH 0536/1227] conver repres cursor to list --- openpype/tools/sceneinventory/switch_dialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index 92ef2b3553..aa4dcd73cd 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -165,11 +165,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): content_loaders.add(item["loader"]) project_name = self.active_project() - repres = get_representations( + repres = list(get_representations( project_name, representation_ids=repre_ids, archived=True - ) + )) repres_by_id = {repre["_id"]: repre for repre in repres} # stash context values, works only for single representation From bae1e38112cc8a756388b92f1447c5e69112e6ab Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 10:59:04 +0200 Subject: [PATCH 0537/1227] fix kwargs name in switch dialog --- openpype/tools/sceneinventory/switch_dialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index aa4dcd73cd..1d1d5cbb91 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -944,7 +944,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): repre_docs = list( get_representations( project_name, - subset_ids=subset_id_by_version_id.keys(), + version_ids=subset_id_by_version_id.keys(), fields=["name", "parent"] ) ) @@ -1102,7 +1102,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): repre_docs = get_representations( project_name, - subset_ids=subset_id_by_version_id.keys(), + version_ids=subset_id_by_version_id.keys(), fields=["name", "parent"] ) repres_by_subset_name = {} From 57e72c2c3722f232b58a81454b3f46dbd7cba647 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 11:32:49 +0200 Subject: [PATCH 0538/1227] minor changesminor changes in representation widget --- openpype/tools/loader/model.py | 23 +++++++++++------------ openpype/tools/loader/widgets.py | 4 ++-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index f2b7e9a6a4..f030e94256 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -231,7 +231,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): self._doc_fetching_stop = False self._doc_payload = {} - self.doc_fetched.connect(self.on_doc_fetched) + self.doc_fetched.connect(self._on_doc_fetched) self.refresh() @@ -250,7 +250,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): def set_grouping(self, state): self._grouping = state - self.on_doc_fetched() + self._on_doc_fetched() def get_subsets_families(self): return self._doc_payload.get("subset_families") or set() @@ -528,7 +528,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): self.fetch_subset_and_version() - def on_doc_fetched(self): + def _on_doc_fetched(self): self.clear() self._items_by_id = {} self.beginResetModel() @@ -1019,7 +1019,6 @@ class RepresentationSortProxyModel(GroupMemberFilterProxyModel): class RepresentationModel(TreeModel, BaseRepresentationModel): - doc_fetched = QtCore.Signal() refreshed = QtCore.Signal(bool) @@ -1055,12 +1054,12 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): 'files.sites': 1 } - def __init__(self, dbcon, header, version_ids): + def __init__(self, dbcon, header): super(RepresentationModel, self).__init__() self.dbcon = dbcon self._data = [] self._header = header - self.version_ids = version_ids + self._version_ids = [] manager = ModulesManager() sync_server = active_site = remote_site = None @@ -1092,7 +1091,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): self.remote_site = remote_site self.remote_provider = remote_provider - self.doc_fetched.connect(self.on_doc_fetched) + self.doc_fetched.connect(self._on_doc_fetched) self._docs = {} self._icons = lib.get_repre_icons() @@ -1103,7 +1102,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): self._items_by_id = {} def set_version_ids(self, version_ids): - self.version_ids = version_ids + self._version_ids = version_ids self.refresh() def data(self, index, role): @@ -1171,7 +1170,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): return super(RepresentationModel, self).data(index, role) - def on_doc_fetched(self): + def _on_doc_fetched(self): self.clear() self.beginResetModel() subsets = set() @@ -1181,7 +1180,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): group = None self._items_by_id = {} for doc in self._docs: - if len(self.version_ids) > 1: + if len(self._version_ids) > 1: group = repre_groups.get(doc["name"]) if not group: group_item = Item() @@ -1265,12 +1264,12 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): return repre_docs = [] - if self.version_ids: + if self._version_ids: # Simple find here for now, expected to receive lower number of # representations and logic could be in Python repre_docs = list(get_representations( project_name, - version_ids=self.version_ids, + version_ids=self._version_ids, check_site_name=True, fields=self.repre_projection.keys() )) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 921708922e..fd43435b15 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -1164,7 +1164,7 @@ class RepresentationWidget(QtWidgets.QWidget): headers = [item[0] for item in self.default_widths] - model = RepresentationModel(self.dbcon, headers, []) + model = RepresentationModel(self.dbcon, headers) proxy_model = RepresentationSortProxyModel(self) proxy_model.setSourceModel(model) @@ -1231,7 +1231,7 @@ class RepresentationWidget(QtWidgets.QWidget): for item in items: repre_ids.append(item["_id"]) - project_name = self.dbcon.actual_project() + project_name = self.dbcon.active_project() repre_docs = get_representations( project_name, representation_ids=repre_ids, From de2fb5d1ff808d81a56a84c8240cac2465feae42 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 11:38:18 +0200 Subject: [PATCH 0539/1227] convert cursor of representations to list --- openpype/tools/loader/widgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index fd43435b15..0482bad642 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -1232,11 +1232,11 @@ class RepresentationWidget(QtWidgets.QWidget): repre_ids.append(item["_id"]) project_name = self.dbcon.active_project() - repre_docs = get_representations( + repre_docs = list(get_representations( project_name, representation_ids=repre_ids, fields=["name", "parent"] - ) + )) version_ids = [ repre_doc["parent"] From 2df00d5caefd92aad61112b110d8b7a9303f5230 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 11:38:43 +0200 Subject: [PATCH 0540/1227] remove site name check in representations widget --- openpype/client/entities.py | 6 ------ openpype/tools/loader/model.py | 1 - 2 files changed, 7 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 66204a4b19..91646e7a1d 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -789,7 +789,6 @@ def get_representations( version_ids=None, extensions=None, names_by_version_ids=None, - check_site_name=False, archived=False, fields=None ): @@ -809,8 +808,6 @@ def get_representations( file (without dot). names_by_version_ids (dict[ObjectId, list[str]]): Complex filtering using version ids and list of names under the version. - check_site_name (bool): Filter only representation that have existing - site name. archived (bool): Output will also contain archived representations. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -827,9 +824,6 @@ def get_representations( else: query_filter = {"type": {"$in": repre_types}} - if check_site_name: - query_filter["files.site.name"] = {"$exists": True} - if representation_ids is not None: representation_ids = _convert_ids(representation_ids) if not representation_ids: diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index f030e94256..46acc68f67 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -1270,7 +1270,6 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): repre_docs = list(get_representations( project_name, version_ids=self._version_ids, - check_site_name=True, fields=self.repre_projection.keys() )) From 483ca9b301680cdfee8eee7f4270eb5a1654f312 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 11:49:03 +0200 Subject: [PATCH 0541/1227] fix args order --- openpype/tools/loader/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 46acc68f67..b5dc16a680 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -267,7 +267,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): else: project_name = self.dbcon.active_project() version_doc = get_version_by_name( - project_name, subset_id, value + project_name, value, subset_id ) # update availability on active site when version changes From a63e5592ab02c61c3712d6cc0a10716ffb2efb33 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 11:52:59 +0200 Subject: [PATCH 0542/1227] fixed asset links widget --- openpype/tools/assetlinks/widgets.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/openpype/tools/assetlinks/widgets.py b/openpype/tools/assetlinks/widgets.py index 3078585ed1..1b168e542c 100644 --- a/openpype/tools/assetlinks/widgets.py +++ b/openpype/tools/assetlinks/widgets.py @@ -74,13 +74,13 @@ class SimpleLinkView(QtWidgets.QWidget): fields=["name", "parent"] )) - subset_docs = [] versions_by_subset_id = collections.defaultdict(list) - if versions_by_subset_id: - for version_doc in version_docs: - subset_id = version_doc["parent"] - versions_by_subset_id[subset_id].append(version_doc) + for version_doc in version_docs: + subset_id = version_doc["parent"] + versions_by_subset_id[subset_id].append(version_doc) + subset_docs = [] + if versions_by_subset_id: subset_docs = list(get_subsets( self.project_name, subset_ids=versions_by_subset_id.keys(), @@ -117,13 +117,13 @@ class SimpleLinkView(QtWidgets.QWidget): version_doc["_id"], fields=["name", "parent"] )) - subset_docs = [] versions_by_subset_id = collections.defaultdict(list) - if versions_by_subset_id: - for version_doc in version_docs: - subset_id = version_doc["parent"] - versions_by_subset_id[subset_id].append(version_doc) + for version_doc in version_docs: + subset_id = version_doc["parent"] + versions_by_subset_id[subset_id].append(version_doc) + subset_docs = [] + if versions_by_subset_id: subset_docs = list(get_subsets( self.project_name, subset_ids=versions_by_subset_id.keys(), From 5d36e381906d25ae8593c72ee0d409090e842ecf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 15:21:12 +0200 Subject: [PATCH 0543/1227] extractor does not change output frame range --- .../plugins/publish/extract_sequence.py | 34 ++----------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py index d4fd1dff4b..77712347bd 100644 --- a/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py +++ b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py @@ -73,14 +73,8 @@ class ExtractSequence(pyblish.api.Extractor): scene_bg_color = instance.context.data["sceneBgColor"] - # --- Fallbacks ---------------------------------------------------- - # This is required if validations of ranges are ignored. - # - all of this code won't change processing if range to render - # match to range of expected output - # Prepare output frames output_frame_start = frame_start - handle_start - output_frame_end = frame_end + handle_end # Change output frame start to 0 if handles cause it's negative number if output_frame_start < 0: @@ -90,32 +84,8 @@ class ExtractSequence(pyblish.api.Extractor): ).format(frame_start, handle_start)) output_frame_start = 0 - # Check Marks range and output range - output_range = output_frame_end - output_frame_start - marks_range = mark_out - mark_in - - # Lower Mark Out if mark range is bigger than output - # - do not rendered not used frames - if output_range < marks_range: - new_mark_out = mark_out - (marks_range - output_range) - self.log.warning(( - "Lowering render range to {} frames. Changed Mark Out {} -> {}" - ).format(marks_range + 1, mark_out, new_mark_out)) - # Assign new mark out to variable - mark_out = new_mark_out - - # Lower output frame end so representation has right `frameEnd` value - elif output_range > marks_range: - new_output_frame_end = ( - output_frame_end - (output_range - marks_range) - ) - self.log.warning(( - "Lowering representation range to {} frames." - " Changed frame end {} -> {}" - ).format(output_range + 1, mark_out, new_output_frame_end)) - output_frame_end = new_output_frame_end - - # ------------------------------------------------------------------- + # Calculate frame end + output_frame_end = output_frame_start + (mark_out - mark_in) # Save to staging dir output_dir = instance.data.get("stagingDir") From d09697db6b2a8d1cf9b9ff35d4e743f43528ae27 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 15:21:20 +0200 Subject: [PATCH 0544/1227] make sure members are strings --- openpype/hosts/tvpaint/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index f473f51457..60c61a8cbf 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -385,7 +385,7 @@ def ls(): if "objectName" not in item and "members" in item: members = item["members"] if isinstance(members, list): - members = "|".join(members) + members = "|".join([str(member) for member in members]) item["objectName"] = members return output From 34156e280bf7ebbd565e9754ae9f71fdf463d79f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 18:17:38 +0200 Subject: [PATCH 0545/1227] create new action to create daily review sessions --- .../action_create_review_session.py | 286 ++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 openpype/modules/ftrack/event_handlers_server/action_create_review_session.py 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 new file mode 100644 index 0000000000..3b7cb224f0 --- /dev/null +++ b/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py @@ -0,0 +1,286 @@ +import threading +import datetime +import copy +import collections + +import ftrack_api + +from openpype.lib import get_datetime_data +from openpype.api import get_project_settings +from openpype_modules.ftrack.lib import ServerAction + + +class CreateDailyReviewSessionServerAction(ServerAction): + """Create daily review session object per project. + + Action creates review sessions based on settings. Settings define if is + action enabled and what is a template for review session name. Logic works + in a way that if review session with the name already exists then skip + process. If review session for current day does not exist but yesterdays + review exists and is empty then yesterdays is renamed otherwise creates + new review session. + + Also contains cycle creation of dailies which is triggered each morning. + This option must be enabled in project settings. Cycle creation is also + checked on registration of action. + """ + + identifier = "create.daily.review.session" + #: Action label. + label = "OpenPype Admin" + variant = "- Create Daily Review Session (Server)" + #: Action description. + description = "Manually create daily review session" + role_list = {"Pypeclub", "Administrator", "Project Manager"} + + settings_key = "create_daily_review_session" + default_template = "{yy}{mm}{dd}" + + def __init__(self, *args, **kwargs): + super(CreateDailyReviewSessionServerAction, self).__init__(*args, **kwargs) + + self._cycle_timer = None + self._last_cyle_time = None + self._day_delta = datetime.timedelta(days=1) + + def discover(self, session, entities, event): + """Show action only on AssetVersions.""" + + valid_selection = False + for ent in event["data"]["selection"]: + # Ignore entities that are not tasks or projects + if ent["entityType"].lower() in ( + "show", "task", "reviewsession", "assetversion" + ): + valid_selection = True + break + else: + self.log.info(ent["entityType"]) + + if not valid_selection: + return False + return self.valid_roles(session, entities, event) + + def launch(self, session, entities, event): + project_entity = self.get_project_from_entity(entities[0], session) + project_name = project_entity["full_name"] + project_settings = self.get_project_settings_from_event( + event, project_name + ) + action_settings = self._extract_action_settings(project_settings) + project_name_by_id = { + project_entity["id"]: project_name + } + settings_by_project_id = { + project_entity["id"]: action_settings + } + self._process_review_session( + session, settings_by_project_id, project_name_by_id + ) + return True + + def register(self, *args, **kwargs): + """Override register to be able trigger """ + # Register server action as would be normally + super(CreateDailyReviewSessionServerAction, self).register(*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() + # Store cycle time which will be used to create next timer + self._last_cyle_time = next_day_morning + # Create timer thread + self._cycle_timer = threading.Timer(first_delta, self._timer_callback) + self._cycle_timer.start() + + self._check_review_session() + + def _timer_callback(self): + if ( + 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 + + delay = (self._last_cyle_time - now).total_seconds() + + self._cycle_timer = threading.Timer(delay, self._timer_callback) + self._cycle_timer.start() + self._check_review_session() + + def _check_review_session(self): + session = ftrack_api.Session( + server_url=self.session.server_url, + api_key=self.session.api_key, + api_user=self.session.api_user, + auto_connect_event_hub=False + ) + project_entities = session.query( + "select id, full_name from Project" + ).all() + project_names_by_id = { + project_entity["id"]: project_entity["full_name"] + for project_entity in project_entities + } + + action_settings_by_project_id = self._get_action_settings( + project_names_by_id + ) + enabled_action_settings_by_project_id = {} + for item in action_settings_by_project_id.items(): + project_id, action_settings = item + if action_settings.get("cycle_enabled"): + enabled_action_settings_by_project_id[project_id] = ( + action_settings + ) + + if not enabled_action_settings_by_project_id: + self.log.info(( + "There are no projects that have enabled" + " cycle review sesison creation" + )) + + else: + self._process_review_session( + session, + enabled_action_settings_by_project_id, + project_names_by_id + ) + + session.close() + + def _process_review_session( + self, session, settings_by_project_id, project_names_by_id + ): + review_sessions = session.query(( + "select id, name, project_id" + " from ReviewSession where project_id in ({})" + ).format(self.join_query_keys(settings_by_project_id))).all() + + review_sessions_by_project_id = collections.defaultdict(list) + for review_session in review_sessions: + project_id = review_session["project_id"] + review_sessions_by_project_id[project_id].append(review_session) + + # Prepare fill data for today's review sesison and yesterdays + now = datetime.datetime.now() + today_obj = datetime.datetime( + now.year, now.month, now.day, 0, 0, 0 + ) + yesterday_obj = today_obj - self._day_delta + + today_fill_data = get_datetime_data(today_obj) + yesterday_fill_data = get_datetime_data(yesterday_obj) + + # Loop through projects and try to create daily reviews + for project_id, action_settings in settings_by_project_id.items(): + review_session_template = ( + action_settings["review_session_template"] + ).strip() or self.default_template + + today_project_fill_data = copy.deepcopy(today_fill_data) + yesterday_project_fill_data = copy.deepcopy(yesterday_fill_data) + project_name = project_names_by_id[project_id] + today_project_fill_data["project_name"] = project_name + yesterday_project_fill_data["project_name"] = project_name + + today_session_name = self._fill_review_template( + review_session_template, today_project_fill_data + ) + yesterday_session_name = self._fill_review_template( + review_session_template, yesterday_project_fill_data + ) + # Skip if today's session name could not be filled + if not today_session_name: + continue + + # Find matchin review session + project_review_sessions = review_sessions_by_project_id[project_id] + todays_session = None + yesterdays_session = None + for review_session in project_review_sessions: + session_name = review_session["name"] + if session_name == today_session_name: + todays_session = review_session + break + elif session_name == yesterday_session_name: + yesterdays_session = review_session + + # Skip if today's session already exist + if todays_session is not None: + self.log.debug(( + "Todays ReviewSession \"{}\"" + " in project \"{}\" already exists" + ).format(today_session_name, project_name)) + continue + + # Check if there is yesterday's session and is empty + # - in that case just rename it + if ( + yesterdays_session is not None + and len(yesterdays_session["review_session_objects"]) == 0 + ): + self.log.debug(( + "Renaming yesterdays empty review session \"{}\" to \"{}\"" + " in project \"{}\"" + ).format( + yesterday_session_name, today_session_name, project_name + )) + yesterdays_session["name"] = today_session_name + session.commit() + continue + + # Create new review session with new name + self.log.debug(( + "Creating new review session \"{}\" in project \"{}\"" + ).format(today_session_name, project_name)) + session.create("ReviewSession", { + "project_id": project_id, + "name": today_session_name + }) + session.commit() + + def _get_action_settings(self, project_names_by_id): + settings_by_project_id = {} + for project_id, project_name in project_names_by_id.items(): + project_settings = get_project_settings(project_name) + action_settings = self._extract_action_settings(project_settings) + settings_by_project_id[project_id] = action_settings + return settings_by_project_id + + def _extract_action_settings(self, project_settings): + return ( + project_settings + .get("ftrack", {}) + .get(self.settings_frack_subkey, {}) + .get(self.settings_key) + ) or {} + + def _fill_review_template(self, template, data): + output = None + try: + output = template.format(**data) + except Exception: + self.log.warning( + ( + "Failed to fill review session template {} with data {}" + ).format(template, data), + exc_info=True + ) + return output + + +def register(session): + '''Register plugin. Called when used as an plugin.''' + CreateDailyReviewSessionServerAction(session).register() From 08d1f97f1112e7efc0190f146139c366d8f84de8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 18:17:54 +0200 Subject: [PATCH 0546/1227] added settings for new action --- .../defaults/project_settings/ftrack.json | 9 +++++ .../schema_project_ftrack.json | 38 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index 9d59deea3d..831c34835e 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -116,6 +116,15 @@ "Administrator", "Project manager" ] + }, + "create_daily_review_session": { + "enabled": true, + "role_list": [ + "Administrator", + "Project Manager" + ], + "cycle_enabled": false, + "review_session_template": "{yy}{mm}{dd}" } }, "user_handlers": { 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 16cab49d5d..f8f9d5093d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json @@ -388,6 +388,44 @@ "object_type": "text" } ] + }, + { + "key": "create_daily_review_session", + "label": "Create daily review session", + "type": "dict", + "is_group": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled" + }, + { + "type": "list", + "key": "role_list", + "label": "Roles", + "object_type": "text", + "use_label_wrap": true + }, + { + "type": "boolean", + "key": "cycle_enabled", + "label": "Create daily review session" + }, + { + "type": "separator" + }, + { + "type": "text", + "key": "review_session_template", + "label": "ReviewSession template", + "placeholder": "Default: {yy}{mm}{dd}" + }, + { + "type": "label", + "label": "Possible formatting keys in template:
- \"project_name\" - <Name of project>
- \"d\" - <Day of month number> in shortest possible way.
- \"dd\" - <Day of month number> with 2 digits.
- \"ddd\" - <Week day name> shortened week day. e.g.: `Mon`, ...
- \"dddd\" - <Week day name> full name of week day. e.g.: `Monday`, ...
- \"m\" - <Month number> in shortest possible way. e.g.: `1` if January
- \"mm\" - <Month number> with 2 digits.
- \"mmm\" - <Month name> shortened month name. e.g.: `Jan`, ...
- \"mmmm\" -<Month name> full month name. e.g.: `January`, ...
- \"yy\" - <Year number> shortened year. e.g.: `19`, `20`, ...
- \"yyyy\" - <Year number> full year. e.g.: `2019`, `2020`, ..." + } + ] } ] }, From 5999693fdf748025aa79cf9cf232d77406c4c9a6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 18:27:00 +0200 Subject: [PATCH 0547/1227] fix too long lines --- .../event_handlers_server/action_create_review_session.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 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 3b7cb224f0..8a1d898193 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 @@ -37,7 +37,9 @@ class CreateDailyReviewSessionServerAction(ServerAction): default_template = "{yy}{mm}{dd}" def __init__(self, *args, **kwargs): - super(CreateDailyReviewSessionServerAction, self).__init__(*args, **kwargs) + super(CreateDailyReviewSessionServerAction, self).__init__( + *args, **kwargs + ) self._cycle_timer = None self._last_cyle_time = None @@ -82,7 +84,9 @@ class CreateDailyReviewSessionServerAction(ServerAction): def register(self, *args, **kwargs): """Override register to be able trigger """ # Register server action as would be normally - super(CreateDailyReviewSessionServerAction, self).register(*args, **kwargs) + super(CreateDailyReviewSessionServerAction, self).register( + *args, **kwargs + ) # Create threading timer which will trigger creation of report # at the 00:00:01 of next day From 0616db322d92be36cebd96c8d6e020fd7e9590c7 Mon Sep 17 00:00:00 2001 From: Felix Wang Date: Thu, 9 Jun 2022 10:42:20 -0700 Subject: [PATCH 0548/1227] Update openpype/hosts/maya/plugins/publish/collect_instances.py Co-authored-by: Roy Nieterau --- openpype/hosts/maya/plugins/publish/collect_instances.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_instances.py b/openpype/hosts/maya/plugins/publish/collect_instances.py index c76afe53f6..433fa9886d 100644 --- a/openpype/hosts/maya/plugins/publish/collect_instances.py +++ b/openpype/hosts/maya/plugins/publish/collect_instances.py @@ -10,7 +10,6 @@ def get_all_children(nodes): Using maya.cmds.listRelatives(allDescendents=True) includes only the first instance. As such, this function acts as an optimal replacement with a focus on a fast query. - Borrowed from Colorbleed: https://tinyurl.com/bdht6fyh """ From 79b181176ba470c77e690f11c980558e956da97c Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Thu, 9 Jun 2022 22:34:28 +0200 Subject: [PATCH 0549/1227] updated poetry installation source --- tools/create_env.ps1 | 2 +- tools/create_env.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/create_env.ps1 b/tools/create_env.ps1 index f19a98f11b..c307ba2031 100644 --- a/tools/create_env.ps1 +++ b/tools/create_env.ps1 @@ -63,7 +63,7 @@ function Install-Poetry() { } $env:POETRY_HOME="$openpype_root\.poetry" - (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | & $($python) - + (Invoke-WebRequest -Uri https://install.python-poetry.org/ -UseBasicParsing).Content | & $($python) - } diff --git a/tools/create_env.sh b/tools/create_env.sh index 94b11d6776..fba3942b87 100755 --- a/tools/create_env.sh +++ b/tools/create_env.sh @@ -110,7 +110,7 @@ install_poetry () { echo -e "${BIGreen}>>>${RST} Installing Poetry ..." export POETRY_HOME="$openpype_root/.poetry" command -v curl >/dev/null 2>&1 || { echo -e "${BIRed}!!!${RST}${BIYellow} Missing ${RST}${BIBlue}curl${BIYellow} command.${RST}"; return 1; } - curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python - + curl -sSL https://install.python-poetry.org/ | python - } ############################################################################## From 242b9f5f3a9ed734743d7b88cba7191be4841fa3 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 00:49:01 +0200 Subject: [PATCH 0550/1227] added module setting and tray item --- openpype/modules/ftrack/tray/ftrack_tray.py | 44 ++++++++++++++----- .../defaults/system_settings/modules.json | 14 ++++++ .../module_settings/schema_ftrack.json | 37 ++++++++++++++++ tools/run_ftrack_eventserver.ps1 | 39 ++++++++++++++++ 4 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 tools/run_ftrack_eventserver.ps1 diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index c6201a94f6..699b33e187 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -2,6 +2,8 @@ import os import time import datetime import threading +import platform +from subprocess import Popen from Qt import QtCore, QtWidgets, QtGui import ftrack_api @@ -12,6 +14,7 @@ from ..ftrack_module import FTRACK_MODULE_DIR from . import login_dialog from openpype.api import Logger, resources +from openpype.settings import get_system_settings log = Logger().get_logger("FtrackModule") @@ -42,12 +45,25 @@ class FtrackTrayWrapper: self.icon_not_logged = QtGui.QIcon( resources.get_resource("icons", "circle_orange.png") ) + self.icon_ftrackapp = QtGui.QIcon( + resources.get_resource("icons", "inventory.png") + ) def show_login_widget(self): self.widget_login.show() self.widget_login.activateWindow() self.widget_login.raise_() + def show_ftrack_browser(self): + am = get_system_settings() + browser_path = am["modules"]["ftrack"]["ftrack_browser_path"][platform.system().lower()][0] + browser_arg = am["modules"]["ftrack"]["ftrack_browser_arguments"][platform.system().lower()][0] + if "=" not in browser_arg: + browser_arg = '{:1}'.format(browser_arg) + cmd = f"{browser_path} {browser_arg}{self.module.ftrack_url}" + log.info(f"Opening Ftrack Browser: {cmd}") + Popen(cmd) + def validate(self): validation = False cred = credentials.get_credentials() @@ -251,16 +267,12 @@ class FtrackTrayWrapper: # Menu for Tray App tray_menu = QtWidgets.QMenu("Ftrack", parent_menu) - # Actions - basic - action_credentials = QtWidgets.QAction("Credentials", tray_menu) - action_credentials.triggered.connect(self.show_login_widget) - if self.bool_logged: - icon = self.icon_logged - else: - icon = self.icon_not_logged - action_credentials.setIcon(icon) - tray_menu.addAction(action_credentials) - self.action_credentials = action_credentials + # Ftrack Browser + browser_open = QtWidgets.QAction("Open Ftrack...", tray_menu) + browser_open.triggered.connect(self.show_ftrack_browser) + browser_open.setIcon(self.icon_ftrackapp) + tray_menu.addAction(browser_open) + self.browser_open = browser_open # Actions - server tray_server_menu = tray_menu.addMenu("Action server") @@ -284,6 +296,18 @@ class FtrackTrayWrapper: tray_server_menu.addAction(self.action_server_stop) self.tray_server_menu = tray_server_menu + + # Actions - basic + action_credentials = QtWidgets.QAction("Credentials", tray_menu) + action_credentials.triggered.connect(self.show_login_widget) + if self.bool_logged: + icon = self.icon_logged + else: + icon = self.icon_not_logged + action_credentials.setIcon(icon) + tray_menu.addAction(action_credentials) + self.action_credentials = action_credentials + self.bool_logged = False self.set_menu_visibility() diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 537e287366..aaf01b1631 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -15,6 +15,20 @@ "ftrack": { "enabled": false, "ftrack_server": "", + "ftrack_browser_path": { + "windows": [ + "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" + ], + "darwin": [], + "linux": [] + }, + "ftrack_browser_arguments": { + "windows": [ + "--app=" + ], + "darwin": [], + "linux": [] + }, "ftrack_actions_path": { "windows": [], "darwin": [], diff --git a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json index 654ddf2938..f30d536052 100644 --- a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json +++ b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json @@ -19,6 +19,43 @@ { "type": "splitter" }, + { + "type": "path", + "key": "ftrack_browser_path", + "label": "Browser Path", + "use_label_wrap": true, + "multipath": true, + "multiplatform": true + }, + { + "type": "dict", + "key": "ftrack_browser_arguments", + "label": "Browser Arguments", + "use_label_wrap": true, + "children": [ + { + "key": "windows", + "label": "Windows", + "type": "list", + "object_type": "text" + }, + { + "key": "darwin", + "label": "MacOS", + "type": "list", + "object_type": "text" + }, + { + "key": "linux", + "label": "Linux", + "type": "list", + "object_type": "text" + } + ] + }, + { + "type": "splitter" + }, { "type": "label", "label": "Additional Ftrack event handlers paths" diff --git a/tools/run_ftrack_eventserver.ps1 b/tools/run_ftrack_eventserver.ps1 new file mode 100644 index 0000000000..9c22f3d88e --- /dev/null +++ b/tools/run_ftrack_eventserver.ps1 @@ -0,0 +1,39 @@ +<# +.SYNOPSIS + Helper script to start OpenPype Ftrack EventServer without relying on built executables. + +.DESCRIPTION + + +.EXAMPLE + +PS> .\run_eventserver.ps1 + +#> +$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent +$openpype_root = (Get-Item $script_dir).parent.FullName + +$env:_INSIDE_OPENPYPE_TOOL = "1" +$env:OPENPYPE_DEBUG = "1" +# $env:OPENPYPE_MONGO = "mongodb://127.0.0.1:27017" + +# make sure Poetry is in PATH +if (-not (Test-Path 'env:POETRY_HOME')) { + $env:POETRY_HOME = "$openpype_root\.poetry" +} +$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" + +Set-Location -Path $openpype_root + +Write-Host ">>> " -NoNewline -ForegroundColor Green +Write-Host "Reading Poetry ... " -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" +} else { + Write-Host "OK" -ForegroundColor Green +} + +& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" eventserver \ No newline at end of file From 3a7e329e85793962b7b15a70df33e5930147f94e Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Fri, 10 Jun 2022 12:02:30 +0300 Subject: [PATCH 0551/1227] Update openpype/plugins/publish/extract_jpeg_exr.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/extract_jpeg_exr.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index c658e17cab..b336787599 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -161,6 +161,14 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # output file jpeg_items.append(path_to_subprocess_arg(dst_path)) subprocess_command = " ".join(jpeg_items) - run_subprocess( - subprocess_command, shell=True, logger=self.log - ) + try: + run_subprocess( + subprocess_command, shell=True, logger=self.log + ) + return True + except Exception: + self.log.warning( + "Failed to create thubmnail using ffmpeg", + exc_info=True + ) + return False From 28d0ea37a7523c2a4560d21ec07fda68d04bc89f Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Fri, 10 Jun 2022 12:02:42 +0300 Subject: [PATCH 0552/1227] Update openpype/plugins/publish/extract_jpeg_exr.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/extract_jpeg_exr.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index b336787599..c1f7f38024 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -132,7 +132,15 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): ] subprocess_exr = " ".join(oiio_cmd) self.log.info(f"running: {subprocess_exr}") - run_subprocess(oiio_cmd, logger=self.log) + try: + run_subprocess(oiio_cmd, logger=self.log) + return True + except Exception: + self.log.warning( + "Failed to create thubmnail using oiiotool", + exc_info=True + ) + return False def create_thumbnail_ffmpeg(self, src_path, dst_path): self.log.info("outputting {}".format(dst_path)) From 51fb0385f81aefe0ebc71a4f8256b19976e8fce0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 12:16:31 +0200 Subject: [PATCH 0553/1227] mapped hosts queries --- openpype/client/entities.py | 294 +++++++++++++++++++++++++++++++++++- 1 file changed, 293 insertions(+), 1 deletion(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 91646e7a1d..861ac300e5 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1033,10 +1033,302 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): """ -Custom data storage: +## Custom data storage: - Webpublisher - jobs - Ftrack - events +- Maya - Shaders + - openpype/hosts/maya/api/shader_definition_editor.py + - openpype/hosts/maya/plugins/publish/validate_model_name.py +## Global launch hooks +- openpype/hooks/pre_global_host_data.py + Query: + - project + - asset + +## Hosts +### Aftereffects +- openpype/hosts/aftereffects/plugins/create/workfile_creator.py + Query: + - asset + +### Blender +- openpype/hosts/blender/api/pipeline.py + Query: + - asset +- openpype/hosts/blender/plugins/publish/extract_layout.py + Query: + - representation + +### Celaction +- openpype/hosts/celaction/plugins/publish/collect_audio.py + Query: + - subsets + - last versions + - representations + +### Fusion +- openpype/hosts/fusion/api/lib.py + Query: + - asset + - subset + - version + - representation +- openpype/hosts/fusion/plugins/load/load_sequence.py + Query: + - version +- openpype/hosts/fusion/scripts/fusion_switch_shot.py + Query: + - project + - asset + - versions +- openpype/hosts/fusion/utility_scripts/switch_ui.py + Query: + - assets + +### Harmony +- openpype/hosts/harmony/api/pipeline.py + Query: + - representation + +### Hiero +- openpype/hosts/hiero/api/lib.py + Query: + - project + - version + - versions + - representation +- openpype/hosts/hiero/api/tags.py + Query: + - task types + - assets +- openpype/hosts/hiero/plugins/load/load_clip.py + Query: + - version + - versions +- openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py + Query: + - assets + +### Houdini +- openpype/hosts/houdini/api/lib.py + Query: + - asset +- openpype/hosts/houdini/api/usd.py + Query: + - asset +- openpype/hosts/houdini/plugins/create/create_hda.py + Query: + - asset + - subsets +- openpype/hosts/houdini/plugins/publish/collect_usd_bootstrap.py + Query: + - asset + - subset +- openpype/hosts/houdini/plugins/publish/extract_usd_layered.py + Query: + - asset + - subset + - version + - representation +- openpype/hosts/houdini/plugins/publish/validate_usd_shade_model_exists.py + Query: + - asset + - subset +- openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py + Query: + - project + - asset + +### Maya +- openpype/hosts/maya/api/action.py + Query: + - asset +- openpype/hosts/maya/api/commands.py + Query: + - asset + - project +- openpype/hosts/maya/api/lib.py + Query: + - project + - asset + - subset + - subsets + - version + - representation +- openpype/hosts/maya/api/setdress.py + Query: + - version + - representation +- openpype/hosts/maya/plugins/inventory/import_modelrender.py + Query: + - representation +- openpype/hosts/maya/plugins/load/load_audio.py + Query: + - asset + - subset + - version +- openpype/hosts/maya/plugins/load/load_image_plane.py + Query: + - asset + - subset + - version +- openpype/hosts/maya/plugins/load/load_look.py + Query: + - representation +- openpype/hosts/maya/plugins/load/load_vrayproxy.py + Query: + - representation +- openpype/hosts/maya/plugins/load/load_yeti_cache.py + Query: + - representation +- openpype/hosts/maya/plugins/publish/collect_review.py + Query: + - subsets +- openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py + Query: + - assets +- openpype/hosts/maya/plugins/publish/validate_node_ids_related.py + Query: + - asset +- openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py + Query: + - asset + - subset + +### Nuke +- openpype/hosts/nuke/api/command.py + Query: + - project + - asset +- openpype/hosts/nuke/api/lib.py + Query: + - project + - asset + - version + - versions + - representation +- openpype/hosts/nuke/plugins/load/load_backdrop.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_camera_abc.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_clip.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_effects_ip.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_effects.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_gizmo_ip.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_gizmo.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_image.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_model.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_script_precomp.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/publish/collect_reads.py + Query: + - asset +- openpype/hosts/nuke/plugins/publish/precollect_instances.py + Query: + - asset +- openpype/hosts/nuke/plugins/publish/precollect_writes.py + Query: + - representation +- openpype/hosts/nuke/plugins/publish/validate_script.py + Query: + - asset + - project + +### Photoshop +- openpype/hosts/photoshop/plugins/create/workfile_creator.py + Query: + - asset + +### Resolve +- openpype/hosts/resolve/plugins/load/load_clip.py + Query: + - version + - versions + +### Standalone publisher +- openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py + Query: + - asset +- openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py + Query: + - assets +- openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py + Query: + - project + - asset +- openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py + Query: + - assets + +### TVPaint +- openpype/hosts/tvpaint/api/pipeline.py + Query: + - project + - asset +- openpype/hosts/tvpaint/plugins/load/load_workfile.py + Query: + - project + - asset +- openpype/hosts/tvpaint/plugins/publish/collect_instances.py + Query: + - asset +- openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py + Query: + - asset +- openpype/hosts/tvpaint/plugins/publish/collect_workfile.py + Query: + - asset + +### Unreal +- openpype/hosts/unreal/plugins/load/load_camera.py + Query: + - asset + - assets +- openpype/hosts/unreal/plugins/load/load_layout.py + Query: + - asset + - assets +- openpype/hosts/unreal/plugins/publish/extract_layout.py + Query: + - representation + +### Webpublisher +- openpype/hosts/webpublisher/webserver_service/webpublish_routes.py + Query: + - assets +- openpype/hosts/webpublisher/plugins/publish/collect_published_files.py + Query: + - last versions + +## Tools openpype/tools/assetlinks/widgets.py - SimpleLinkView Query: From 79db1b1c4354c95d4732f54f09bf2a2357c59572 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Jun 2022 12:40:57 +0200 Subject: [PATCH 0554/1227] flame: adding staging dir to `cleanupFullPaths` --- .../hosts/flame/plugins/publish/extract_subset_resources.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index f3239af23e..6ad8f8cf96 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -445,4 +445,6 @@ class ExtractSubsetResources(openpype.api.Extractor): ) instance.data['stagingDir'] = staging_dir + instance.context.data["cleanupFullPaths"].append(staging_dir) + return staging_dir From b19fc26cf68043574f704ecc3eac6703734bf33f Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 10 Jun 2022 14:52:54 +0300 Subject: [PATCH 0555/1227] Handle muiltilayered flag --- openpype/plugins/publish/extract_jpeg_exr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index c658e17cab..907b3ea4af 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -126,7 +126,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): def create_thumbnail_oiio(self, src_path, dst_path): self.log.info("outputting {}".format(dst_path)) oiio_tool_path = get_oiio_tools_path() - oiio_cmd = [oiio_tool_path, + oiio_cmd = [oiio_tool_path, "-a", src_path, "-o", dst_path ] From f782cf4f6df2bd1762c662a77d81dd33e54239dc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Jun 2022 14:53:40 +0200 Subject: [PATCH 0556/1227] hiero: otio p3 compatibility issue - metadata on effect use update rather then __setter__ --- openpype/hosts/hiero/api/otio/hiero_export.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/api/otio/hiero_export.py b/openpype/hosts/hiero/api/otio/hiero_export.py index 1e4088d9c0..81cb43fa12 100644 --- a/openpype/hosts/hiero/api/otio/hiero_export.py +++ b/openpype/hosts/hiero/api/otio/hiero_export.py @@ -132,7 +132,7 @@ def create_time_effects(otio_clip, track_item): otio_effect = otio.schema.TimeEffect() otio_effect.name = name otio_effect.effect_name = effect_name - otio_effect.metadata = metadata + otio_effect.metadata.update(metadata) # add otio effect to clip effects otio_clip.effects.append(otio_effect) From b227ba58568a3521ead74da5f2135b6e7645b132 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 14:54:48 +0200 Subject: [PATCH 0557/1227] removed debug log --- .../event_handlers_server/action_create_review_session.py | 2 -- 1 file changed, 2 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 8a1d898193..8a8e86e7b9 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 @@ -56,8 +56,6 @@ class CreateDailyReviewSessionServerAction(ServerAction): ): valid_selection = True break - else: - self.log.info(ent["entityType"]) if not valid_selection: return False From 0ea1032a6114f80cee0a87c1d5736ebe293cefd9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 15:06:30 +0200 Subject: [PATCH 0558/1227] added global plugins --- openpype/client/entities.py | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 861ac300e5..b61577ac70 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1046,6 +1046,54 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): - project - asset +## Global load plugins +- openpype/plugins/load/delete_old_versions.py + Query: + - versions + - representations +- openpype/plugins/load/delivery.py + Query: + - representations + +## Global publish plugins +- openpype/plugins/publish/collect_avalon_entities.py + Query: + - asset + - project +- openpype/plugins/publish/collect_anatomy_instance_data.py + Query: + - assets + - subsets +- openpype/plugins/publish/collect_scene_loaded_versions.py + Query: + - representations +- openpype/plugins/publish/extract_hierarchy_avalon.py + Query: + - asset + - assets + - project + Create: + - asset + Update: + - asset +- openpype/plugins/publish/integrate_hero_version.py + Query: + - version + - hero version + - representations +- openpype/plugins/publish/integrate_new.py + Query: + - asset + - subset + - version + - representations +- openpype/plugins/publish/integrate_thumbnail.py + Query: + - version +- openpype/plugins/publish/validate_editorial_asset_name.py + Query: + - assets + ## Hosts ### Aftereffects - openpype/hosts/aftereffects/plugins/create/workfile_creator.py From 22372361eb29d243ba16df4e83e827d1c8d54f62 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 15:12:50 +0200 Subject: [PATCH 0559/1227] added pipeline queries --- openpype/client/entities.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index b61577ac70..82f16ff032 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1064,6 +1064,7 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): Query: - assets - subsets + - last version - openpype/plugins/publish/collect_scene_loaded_versions.py Query: - representations @@ -1094,6 +1095,23 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): Query: - assets +## Pipeline +- openpype/pipeline/load/utils.py + Query: + - project + - assets + - subsets + - version + - versions + - representation + - representations +- openpype/pipeline/mongodb.py + Query: + - project +- openpype/pipeline/thumbnail.py + Query: + - project + ## Hosts ### Aftereffects - openpype/hosts/aftereffects/plugins/create/workfile_creator.py From 956ad8299b652b38e77215b545454ddba3715d87 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 15:19:18 +0200 Subject: [PATCH 0560/1227] mapped lib --- openpype/client/entities.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 82f16ff032..82caaee7c5 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1095,6 +1095,39 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): Query: - assets +## Lib +- openpype/lib/applications.py + Query: + - project + - asset +- openpype/lib/avalon_context.py + Query: + - project + - asset + - linked assets (new function get_linked_assets?) + - subset + - subsets + - version + - versions + - last version + - representations + - linked representations (new function get_linked_ids_for_representations) + Update: + - workfile data +- openpype/lib/plugin_tools.py + Query: + - asset +- openpype/lib/project_backpack.py + Query: + - project + - everything from mongo + Update: + - project +- openpype/lib/usdlib.py + Query: + - project + - asset + ## Pipeline - openpype/pipeline/load/utils.py Query: From 924535a6ce57c3d07bb3cdb61cde92978edc9236 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 15:30:14 +0200 Subject: [PATCH 0561/1227] removed icon since not the same as other entries --- openpype/modules/ftrack/tray/ftrack_tray.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 699b33e187..54d3f3132f 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -45,9 +45,6 @@ class FtrackTrayWrapper: self.icon_not_logged = QtGui.QIcon( resources.get_resource("icons", "circle_orange.png") ) - self.icon_ftrackapp = QtGui.QIcon( - resources.get_resource("icons", "inventory.png") - ) def show_login_widget(self): self.widget_login.show() @@ -270,7 +267,6 @@ class FtrackTrayWrapper: # Ftrack Browser browser_open = QtWidgets.QAction("Open Ftrack...", tray_menu) browser_open.triggered.connect(self.show_ftrack_browser) - browser_open.setIcon(self.icon_ftrackapp) tray_menu.addAction(browser_open) self.browser_open = browser_open From 5e82c96a3da493de7259ffc122c21ecc617340c7 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 15:53:39 +0200 Subject: [PATCH 0562/1227] info label in settings, handles lists better --- openpype/modules/ftrack/tray/ftrack_tray.py | 17 +++++++++++------ .../module_settings/schema_ftrack.json | 4 ++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 54d3f3132f..30e1d9f983 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -52,12 +52,17 @@ class FtrackTrayWrapper: self.widget_login.raise_() def show_ftrack_browser(self): - am = get_system_settings() - browser_path = am["modules"]["ftrack"]["ftrack_browser_path"][platform.system().lower()][0] - browser_arg = am["modules"]["ftrack"]["ftrack_browser_arguments"][platform.system().lower()][0] - if "=" not in browser_arg: - browser_arg = '{:1}'.format(browser_arg) - cmd = f"{browser_path} {browser_arg}{self.module.ftrack_url}" + settings = get_system_settings() + browser_paths = settings["modules"]["ftrack"]["ftrack_browser_path"][platform.system().lower()] + browser_args = settings["modules"]["ftrack"]["ftrack_browser_arguments"][platform.system().lower()] + browser_args.append(self.module.ftrack_url) + path = "" + for p in browser_paths: + if os.path.exists(p): + path = p + break + args = " ".join(str(item) for item in browser_args).replace("= ", "=") + cmd = f"{path} {args}" log.info(f"Opening Ftrack Browser: {cmd}") Popen(cmd) diff --git a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json index f30d536052..268c5479fe 100644 --- a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json +++ b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json @@ -33,6 +33,10 @@ "label": "Browser Arguments", "use_label_wrap": true, "children": [ + { + "type": "label", + "label": "Any arguent which is used to open Ftrack URL (as in \"app=\" for chrome) needs to be placed last in the list!" + }, { "key": "windows", "label": "Windows", From 29e095e285468c9fafffcdaa004df576cc88d9f7 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Fri, 10 Jun 2022 16:24:04 +0200 Subject: [PATCH 0563/1227] Remove avalon DB path --- openpype/modules/shotgrid/aop/patch.py | 52 ------------------- openpype/modules/shotgrid/shotgrid_module.py | 4 -- .../schemas/projects_schema/schema_main.json | 3 +- 3 files changed, 1 insertion(+), 58 deletions(-) delete mode 100644 openpype/modules/shotgrid/aop/patch.py diff --git a/openpype/modules/shotgrid/aop/patch.py b/openpype/modules/shotgrid/aop/patch.py deleted file mode 100644 index 56477f3bf9..0000000000 --- a/openpype/modules/shotgrid/aop/patch.py +++ /dev/null @@ -1,52 +0,0 @@ -from copy import copy - - -from openpype.api import Logger -from openpype.modules.shotgrid.lib import ( - credentials, - settings, - server, -) - -_LOG = Logger().get_logger("ShotgridModule.patch") - - -def _patched_projects( - self, projection=None, only_active=True -): - all_projects = list(self._prev_projects(projection, only_active)) - if ( - not credentials.get_local_login() - or not settings.filter_projects_by_login() - ): - return all_projects - try: - linked_names = _fetch_linked_project_names() or set() - return [x for x in all_projects if _upper(x["name"]) in linked_names] - except Exception as e: - print(e) - return all_projects - - -def _upper(x): - return str(x).strip().upper() - - -def _fetch_linked_project_names(): - return { - _upper(x["project_name"]) - for x in server.find_linked_projects(credentials.get_local_login()) - } - - -def patch_avalon_db(): - _LOG.debug("Run avalon patching") - try: - from avalon.api import AvalonMongoDB - if AvalonMongoDB.projects is _patched_projects: - return None - _LOG.debug("Patch Avalon.projects method") - AvalonMongoDB._prev_projects = copy(AvalonMongoDB.projects) - AvalonMongoDB.projects = _patched_projects - except Exception as e: - _LOG.error("Unable to patch avalon db {}".format(e)) diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index 6e0cbe987b..dcc8187194 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -46,10 +46,6 @@ class ShotgridModule( def tray_init(self): from .tray.shotgrid_tray import ShotgridTrayWrapper - from .aop.patch import patch_avalon_db - - patch_avalon_db() - threading.Timer(10.0, patch_avalon_db).start() self.tray_wrapper = ShotgridTrayWrapper(self) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json index 0ce3ddbefb..80b1baad1b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_main.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json @@ -64,11 +64,10 @@ }, { "type": "schema", - }, - { "name": "schema_project_shotgrid" }, { + "type": "schema", "name": "schema_project_kitsu" }, { From 8a21439b99e6d43426cad8c47d29631b73e5706c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 16:37:52 +0200 Subject: [PATCH 0564/1227] aded ability to parse value from clipboard that does not come from settings ui --- openpype/tools/settings/settings/base.py | 96 ++++++++++++++++++------ 1 file changed, 71 insertions(+), 25 deletions(-) diff --git a/openpype/tools/settings/settings/base.py b/openpype/tools/settings/settings/base.py index 44ec09b2ca..64e0a95fee 100644 --- a/openpype/tools/settings/settings/base.py +++ b/openpype/tools/settings/settings/base.py @@ -1,3 +1,4 @@ +import os import sys import json import traceback @@ -190,24 +191,29 @@ class BaseWidget(QtWidgets.QWidget): actions_mapping[action] = remove_from_project_override menu.addAction(action) + def _get_mime_data_from_entity(self): + if self.entity.is_dynamic_item or self.entity.is_in_dynamic_item: + entity_path = None + else: + entity_path = "/".join( + [self.entity.root_key, self.entity.path] + ) + + value = self.entity.value + + # Copy for settings tool + return { + "value": value, + "__root_key__": self.entity.root_key, + "__settings_path__": entity_path + } + def _copy_value_actions(self, menu): def copy_value(): mime_data = QtCore.QMimeData() - if self.entity.is_dynamic_item or self.entity.is_in_dynamic_item: - entity_path = None - else: - entity_path = "/".join( - [self.entity.root_key, self.entity.path] - ) - - value = self.entity.value # Copy for settings tool - settings_data = { - "root_key": self.entity.root_key, - "value": value, - "path": entity_path - } + settings_data = self._get_mime_data_from_entity() settings_encoded_data = QtCore.QByteArray() settings_stream = QtCore.QDataStream( settings_encoded_data, QtCore.QIODevice.WriteOnly @@ -218,6 +224,7 @@ class BaseWidget(QtWidgets.QWidget): ) # Copy as json + value = settings_data["value"] json_encoded_data = None if isinstance(value, (dict, list)): json_encoded_data = QtCore.QByteArray() @@ -241,25 +248,64 @@ class BaseWidget(QtWidgets.QWidget): action = QtWidgets.QAction("Copy", menu) return [(action, copy_value)] + def _parse_source_data_for_paste(self, data): + settings_path = None + root_key = None + if isinstance(data, dict): + settings_path = data.pop("__settings_path__", settings_path) + root_key = data.pop("__root_key__", root_key) + data = data.pop("__value__", data) + + return { + "value": data, + "__settings_path__": settings_path, + "__root_key__": root_key + } + + def _get_value_from_clipboard(self): + clipboard = QtWidgets.QApplication.clipboard() + mime_data = clipboard.mimeData() + app_value = mime_data.data("application/copy_settings_value") + if app_value: + settings_stream = QtCore.QDataStream( + app_value, QtCore.QIODevice.ReadOnly + ) + mime_data_value_str = settings_stream.readQString() + return json.loads(mime_data_value_str) + + if mime_data.hasUrls(): + for url in mime_data.urls(): + local_file = url.toLocalFile() + try: + with open(local_file, "r") as stream: + value = json.load(stream) + except Exception: + continue + if value: + return self._parse_source_data_for_paste(value) + + if mime_data.hasText(): + text = mime_data.text() + try: + value = json.loads(text) + except Exception: + try: + value = self.entity.convert_to_valid_type(text) + except Exception: + return None + return self._parse_source_data_for_paste(value) + def _paste_value_actions(self, menu): output = [] # Allow paste of value only if were copied from this UI - clipboard = QtWidgets.QApplication.clipboard() - mime_data = clipboard.mimeData() - mime_value = mime_data.data("application/copy_settings_value") + mime_data_value = self._get_value_from_clipboard() # Skip if there is nothing to do - if not mime_value: + if not mime_data_value: return output - settings_stream = QtCore.QDataStream( - mime_value, QtCore.QIODevice.ReadOnly - ) - mime_data_value_str = settings_stream.readQString() - mime_data_value = json.loads(mime_data_value_str) - value = mime_data_value["value"] - path = mime_data_value["path"] - root_key = mime_data_value["root_key"] + path = mime_data_value["__settings_path__"] + root_key = mime_data_value["__root_key__"] # Try to find matching entity to be able paste values to same spot # - entity can't by dynamic or in dynamic item From 213aa2241f00cdf8524aa95a05a408e59a8f791d Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 10 Jun 2022 16:41:09 +0200 Subject: [PATCH 0565/1227] :sparkles: add pointcache family to gpu cache loader --- openpype/hosts/maya/plugins/load/load_gpucache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_gpucache.py b/openpype/hosts/maya/plugins/load/load_gpucache.py index 6d5e945508..a9df22faaa 100644 --- a/openpype/hosts/maya/plugins/load/load_gpucache.py +++ b/openpype/hosts/maya/plugins/load/load_gpucache.py @@ -10,7 +10,7 @@ from openpype.api import get_project_settings class GpuCacheLoader(load.LoaderPlugin): """Load Alembic as gpuCache""" - families = ["model"] + families = ["model", "pointcache"] representations = ["abc"] label = "Import Gpu Cache" From 4a45225ca27d175adc481a8899a3379bcab77dc1 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 16:55:50 +0200 Subject: [PATCH 0566/1227] moved menu entry to last position --- openpype/modules/ftrack/tray/ftrack_tray.py | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 30e1d9f983..4329b03b45 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -269,11 +269,16 @@ class FtrackTrayWrapper: # Menu for Tray App tray_menu = QtWidgets.QMenu("Ftrack", parent_menu) - # Ftrack Browser - browser_open = QtWidgets.QAction("Open Ftrack...", tray_menu) - browser_open.triggered.connect(self.show_ftrack_browser) - tray_menu.addAction(browser_open) - self.browser_open = browser_open + # Actions - basic + action_credentials = QtWidgets.QAction("Credentials", tray_menu) + action_credentials.triggered.connect(self.show_login_widget) + if self.bool_logged: + icon = self.icon_logged + else: + icon = self.icon_not_logged + action_credentials.setIcon(icon) + tray_menu.addAction(action_credentials) + self.action_credentials = action_credentials # Actions - server tray_server_menu = tray_menu.addMenu("Action server") @@ -298,16 +303,11 @@ class FtrackTrayWrapper: self.tray_server_menu = tray_server_menu - # Actions - basic - action_credentials = QtWidgets.QAction("Credentials", tray_menu) - action_credentials.triggered.connect(self.show_login_widget) - if self.bool_logged: - icon = self.icon_logged - else: - icon = self.icon_not_logged - action_credentials.setIcon(icon) - tray_menu.addAction(action_credentials) - self.action_credentials = action_credentials + # Ftrack Browser + browser_open = QtWidgets.QAction("Open Ftrack...", tray_menu) + browser_open.triggered.connect(self.show_ftrack_browser) + tray_menu.addAction(browser_open) + self.browser_open = browser_open self.bool_logged = False self.set_menu_visibility() From 8add4b588d762d470a9e6138cb9c3c4542b75826 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 17:19:59 +0200 Subject: [PATCH 0567/1227] fixed checks and logged correct info --- openpype/modules/ftrack/tray/ftrack_tray.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 4329b03b45..e3df8eff12 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -3,7 +3,7 @@ import time import datetime import threading import platform -from subprocess import Popen +import subprocess from Qt import QtCore, QtWidgets, QtGui import ftrack_api @@ -60,11 +60,18 @@ class FtrackTrayWrapper: for p in browser_paths: if os.path.exists(p): path = p + log.debug(f"Found valid executable at path: {p}") break + else: + log.warning(f"Path: {p} is not valid, please doublecheck your settings!") + if path == "": + log.warning("Found no valid executables to launch Ftrack with. Feature will not work as expected!") + return args = " ".join(str(item) for item in browser_args).replace("= ", "=") + log.debug(f"Computed arguments: {args}") cmd = f"{path} {args}" - log.info(f"Opening Ftrack Browser: {cmd}") - Popen(cmd) + log.debug(f"Opening Ftrack Browser...") + subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) def validate(self): validation = False From f2c252be4e442136bd98fda4691a62ab23626e71 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 10 Jun 2022 17:32:20 +0200 Subject: [PATCH 0568/1227] :recycle: add animation family there too --- openpype/hosts/maya/plugins/load/load_gpucache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_gpucache.py b/openpype/hosts/maya/plugins/load/load_gpucache.py index a9df22faaa..179819f904 100644 --- a/openpype/hosts/maya/plugins/load/load_gpucache.py +++ b/openpype/hosts/maya/plugins/load/load_gpucache.py @@ -10,7 +10,7 @@ from openpype.api import get_project_settings class GpuCacheLoader(load.LoaderPlugin): """Load Alembic as gpuCache""" - families = ["model", "pointcache"] + families = ["model", "animation", "pointcache"] representations = ["abc"] label = "Import Gpu Cache" From 11fc8d26e70689f76bb6ddfac60923c70cd8bbc9 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 17:36:38 +0200 Subject: [PATCH 0569/1227] Line breaks as per hound's "suggestion" --- openpype/modules/ftrack/tray/ftrack_tray.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index e3df8eff12..ee1e50f7f5 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -53,8 +53,10 @@ class FtrackTrayWrapper: def show_ftrack_browser(self): settings = get_system_settings() - browser_paths = settings["modules"]["ftrack"]["ftrack_browser_path"][platform.system().lower()] - browser_args = settings["modules"]["ftrack"]["ftrack_browser_arguments"][platform.system().lower()] + browser_paths = settings["modules"]["ftrack"]\ + ["ftrack_browser_path"][platform.system().lower()] + browser_args = settings["modules"]["ftrack"]\ + ["ftrack_browser_arguments"][platform.system().lower()] browser_args.append(self.module.ftrack_url) path = "" for p in browser_paths: @@ -63,9 +65,11 @@ class FtrackTrayWrapper: log.debug(f"Found valid executable at path: {p}") break else: - log.warning(f"Path: {p} is not valid, please doublecheck your settings!") + log.warning(f"Path: {p} is not valid, please \ + doublecheck your settings!") if path == "": - log.warning("Found no valid executables to launch Ftrack with. Feature will not work as expected!") + log.warning("Found no valid executables to launch \ + Ftrack with. Feature will not work as expected!") return args = " ".join(str(item) for item in browser_args).replace("= ", "=") log.debug(f"Computed arguments: {args}") From 99bb5250711259a1103bd21bf750a4e332aba8bc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Jun 2022 17:38:14 +0200 Subject: [PATCH 0570/1227] nuke: anatomy compatibility hack --- openpype/hosts/nuke/api/lib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 2c5989309b..505eb19419 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -539,7 +539,9 @@ def get_created_node_imageio_setting_legacy(nodeclass, creator, subset): imageio_nodes = get_nuke_imageio_settings()["nodes"] required_nodes = imageio_nodes["requiredNodes"] - override_nodes = imageio_nodes["overrideNodes"] + + # HACK: for backward compatibility this needs to be optional + override_nodes = imageio_nodes.get("overrideNodes", []) imageio_node = None for node in required_nodes: From 3b97531c15e17d5f4a95842dd07879772ecd2489 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Jun 2022 17:38:51 +0200 Subject: [PATCH 0571/1227] Nuke: do not offer still creator plugin if anatomy is missing its preset --- .../nuke/plugins/create/create_write_still.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/create/create_write_still.py b/openpype/hosts/nuke/plugins/create/create_write_still.py index 4007ccf51e..3fbe288c78 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_still.py +++ b/openpype/hosts/nuke/plugins/create/create_write_still.py @@ -2,7 +2,19 @@ import nuke from openpype.hosts.nuke.api import plugin from openpype.hosts.nuke.api.lib import ( - create_write_node, create_write_node_legacy) + create_write_node, + create_write_node_legacy, + get_created_node_imageio_setting_legacy +) + +# HACK: just to disable still image on projects which +# are not having anatomy imageio preset for CreateWriteStill +imageio_writes = get_created_node_imageio_setting_legacy( + "Write", + "CreateWriteStill", + "stillMain" +) +print(imageio_writes["knobs"]) class CreateWriteStill(plugin.AbstractWriteRender): From 24a31e5e1058705d6d58fc8559fc42cfd8d661b8 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 17:49:16 +0200 Subject: [PATCH 0572/1227] fixed hound complaints hopefully --- openpype/modules/ftrack/tray/ftrack_tray.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index ee1e50f7f5..7ac994e967 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -52,11 +52,10 @@ class FtrackTrayWrapper: self.widget_login.raise_() def show_ftrack_browser(self): - settings = get_system_settings() - browser_paths = settings["modules"]["ftrack"]\ - ["ftrack_browser_path"][platform.system().lower()] - browser_args = settings["modules"]["ftrack"]\ - ["ftrack_browser_arguments"][platform.system().lower()] + cur_os = platform.system().lower() + settings = get_system_settings()["modules"]["ftrack"] + browser_paths = settings["ftrack_browser_path"][cur_os] + browser_args = settings["ftrack_browser_arguments"][cur_os] browser_args.append(self.module.ftrack_url) path = "" for p in browser_paths: From c77f9ce2e52c6f97cb9b4e8cb86ebcfeb5a11153 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 17:51:55 +0200 Subject: [PATCH 0573/1227] added option to extract settings to file --- openpype/tools/settings/settings/base.py | 102 +++++++++++++++++++---- 1 file changed, 86 insertions(+), 16 deletions(-) diff --git a/openpype/tools/settings/settings/base.py b/openpype/tools/settings/settings/base.py index 64e0a95fee..3ade5e5ef8 100644 --- a/openpype/tools/settings/settings/base.py +++ b/openpype/tools/settings/settings/base.py @@ -3,6 +3,7 @@ import sys import json import traceback import functools +import datetime from Qt import QtWidgets, QtGui, QtCore @@ -13,10 +14,26 @@ from .widgets import ExpandingWidget from .lib import create_deffered_value_change_timer from .constants import DEFAULT_PROJECT_LABEL +_MENU_SEPARATOR_REQ = object() +SETTINGS_PATH_KEY = "__settings_path__" +ROOT_KEY = "__root_key__" +VALUE_KEY = "__value__" +SAVE_TIME_KEY = "__extracted__" +PROJECT_NAME_KEY = "__project_name__" + class BaseWidget(QtWidgets.QWidget): + _last_save_dir = os.path.expanduser("~") allow_actions = True + @staticmethod + def get_last_save_dir(): + return BaseWidget._last_save_dir + + @staticmethod + def set_last_save_dir(save_dir): + BaseWidget._last_save_dir = save_dir + def __init__(self, category_widget, entity, entity_widget): self.category_widget = category_widget self.entity = entity @@ -203,9 +220,9 @@ class BaseWidget(QtWidgets.QWidget): # Copy for settings tool return { - "value": value, - "__root_key__": self.entity.root_key, - "__settings_path__": entity_path + VALUE_KEY: value, + ROOT_KEY: self.entity.root_key, + SETTINGS_PATH_KEY: entity_path } def _copy_value_actions(self, menu): @@ -248,18 +265,62 @@ class BaseWidget(QtWidgets.QWidget): action = QtWidgets.QAction("Copy", menu) return [(action, copy_value)] + def _extract_to_file(self): + dialog = QtWidgets.QFileDialog( + self, + "Save settings values", + self.get_last_save_dir(), + "Values (*.json)" + ) + # dialog.setOption(dialog.DontUseNativeDialog) + dialog.setAcceptMode(dialog.AcceptSave) + if dialog.exec() != dialog.Accepted: + return + + selected_urls = dialog.selectedUrls() + if not selected_urls: + return + + filepath = selected_urls[0].toLocalFile() + if not filepath: + return + + if not filepath.lower().endswith(".json"): + filepath += ".json" + + settings_data = self._get_mime_data_from_entity() + now = datetime.datetime.now() + settings_data[SAVE_TIME_KEY] = now.strftime("%Y-%m-%d %H:%M:%S") + if hasattr(self.category_widget, "project_name"): + settings_data[PROJECT_NAME_KEY] = self.category_widget.project_name + + with open(filepath, "w") as stream: + json.dump(settings_data, stream, indent=4) + + new_dir = os.path.dirname(filepath) + self.set_last_save_dir(new_dir) + + def _extract_value_to_file_actions(self, menu): + save_action = QtWidgets.QAction("Extract to file", menu) + return [ + _MENU_SEPARATOR_REQ, + (save_action, self._extract_to_file) + ] + def _parse_source_data_for_paste(self, data): settings_path = None root_key = None if isinstance(data, dict): - settings_path = data.pop("__settings_path__", settings_path) - root_key = data.pop("__root_key__", root_key) - data = data.pop("__value__", data) + data.pop(SAVE_TIME_KEY, None) + data.pop(PROJECT_NAME_KEY, None) + settings_path = data.pop(SETTINGS_PATH_KEY, settings_path) + root_key = data.pop(ROOT_KEY, root_key) + data = data.pop(VALUE_KEY, data) return { - "value": data, - "__settings_path__": settings_path, - "__root_key__": root_key + VALUE_KEY: data, + SETTINGS_PATH_KEY: settings_path, + ROOT_KEY: root_key } def _get_value_from_clipboard(self): @@ -303,9 +364,9 @@ class BaseWidget(QtWidgets.QWidget): if not mime_data_value: return output - value = mime_data_value["value"] - path = mime_data_value["__settings_path__"] - root_key = mime_data_value["__root_key__"] + value = mime_data_value[VALUE_KEY] + path = mime_data_value[SETTINGS_PATH_KEY] + root_key = mime_data_value[ROOT_KEY] # Try to find matching entity to be able paste values to same spot # - entity can't by dynamic or in dynamic item @@ -437,10 +498,19 @@ class BaseWidget(QtWidgets.QWidget): ui_actions.extend(self._copy_value_actions(menu)) ui_actions.extend(self._paste_value_actions(menu)) if ui_actions: - menu.addSeparator() - for action, callback in ui_actions: - menu.addAction(action) - actions_mapping[action] = callback + ui_actions.insert(0, _MENU_SEPARATOR_REQ) + + ui_actions.extend(self._extract_value_to_file_actions(menu)) + + for item in ui_actions: + if item is _MENU_SEPARATOR_REQ: + if len(menu.actions()) > 0: + menu.addSeparator() + continue + + action, callback = item + menu.addAction(action) + actions_mapping[action] = callback if not actions_mapping: action = QtWidgets.QAction("< No action >") From b2ed0292adac2c914f50e95575b3494f43b33086 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Jun 2022 17:53:34 +0200 Subject: [PATCH 0574/1227] Nuke: adding todo comment --- openpype/hosts/nuke/plugins/create/create_write_still.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/nuke/plugins/create/create_write_still.py b/openpype/hosts/nuke/plugins/create/create_write_still.py index 3fbe288c78..bb08e8c2c6 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_still.py +++ b/openpype/hosts/nuke/plugins/create/create_write_still.py @@ -9,6 +9,7 @@ from openpype.hosts.nuke.api.lib import ( # HACK: just to disable still image on projects which # are not having anatomy imageio preset for CreateWriteStill +# TODO: remove this code as soon as it will be obsolete imageio_writes = get_created_node_imageio_setting_legacy( "Write", "CreateWriteStill", From 26b6bbab95c727b5f2dfcc07413c4d71d2d6f7a8 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Fri, 10 Jun 2022 18:18:50 +0200 Subject: [PATCH 0575/1227] update poetry lib --- poetry.lock | 70 ++++++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/poetry.lock b/poetry.lock index 04be8c78f2..010dd57e17 100644 --- a/poetry.lock +++ b/poetry.lock @@ -161,11 +161,11 @@ toml = "*" [[package]] name = "babel" -version = "2.9.1" +version = "2.10.1" description = "Internationalization utilities" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] pytz = ">=2015.7" @@ -909,7 +909,7 @@ python-versions = "*" [[package]] name = "protobuf" -version = "3.20.0" +version = "3.20.1" description = "Protocol Buffers" category = "main" optional = false @@ -1617,11 +1617,11 @@ python-versions = ">=3.6" [[package]] name = "typing-extensions" -version = "4.1.1" -description = "Backported and Experimental Type Hints for Python 3.6+" +version = "4.2.0" +description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "uritemplate" @@ -1716,7 +1716,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "3.7.*" -content-hash = "bd8e0a03668c380c6e76c8cd6c71020692f4ea9f32de7a4f09433564faa9dad0" +content-hash = "f7150d3d8098c26a004db9850ed328efe79695e77e830721a3e04c5941850cc5" [metadata.files] acre = [] @@ -1843,8 +1843,8 @@ autopep8 = [ {file = "autopep8-1.5.7.tar.gz", hash = "sha256:276ced7e9e3cb22e5d7c14748384a5cf5d9002257c0ed50c0e075b68011bb6d0"}, ] babel = [ - {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, - {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, + {file = "Babel-2.10.1-py3-none-any.whl", hash = "sha256:3f349e85ad3154559ac4930c3918247d319f21910d5ce4b25d439ed8693b98d2"}, + {file = "Babel-2.10.1.tar.gz", hash = "sha256:98aeaca086133efb3e1e2aad0396987490c8425929ddbcfe0550184fdc54cd13"}, ] bcrypt = [ {file = "bcrypt-3.2.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e"}, @@ -2444,30 +2444,30 @@ prefixed = [ {file = "prefixed-0.3.2.tar.gz", hash = "sha256:ca48277ba5fa8346dd4b760847da930c7b84416387c39e93affef086add2c029"}, ] protobuf = [ - {file = "protobuf-3.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9d0f3aca8ca51c8b5e204ab92bd8afdb2a8e3df46bd0ce0bd39065d79aabcaa4"}, - {file = "protobuf-3.20.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:001c2160c03b6349c04de39cf1a58e342750da3632f6978a1634a3dcca1ec10e"}, - {file = "protobuf-3.20.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b5860b790498f233cdc8d635a17fc08de62e59d4dcd8cdb6c6c0d38a31edf2b"}, - {file = "protobuf-3.20.0-cp310-cp310-win32.whl", hash = "sha256:0b250c60256c8824219352dc2a228a6b49987e5bf94d3ffcf4c46585efcbd499"}, - {file = "protobuf-3.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:a1eebb6eb0653e594cb86cd8e536b9b083373fca9aba761ade6cd412d46fb2ab"}, - {file = "protobuf-3.20.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:bc14037281db66aa60856cd4ce4541a942040686d290e3f3224dd3978f88f554"}, - {file = "protobuf-3.20.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:47257d932de14a7b6c4ae1b7dbf592388153ee35ec7cae216b87ae6490ed39a3"}, - {file = "protobuf-3.20.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fbcbb068ebe67c4ff6483d2e2aa87079c325f8470b24b098d6bf7d4d21d57a69"}, - {file = "protobuf-3.20.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:542f25a4adf3691a306dcc00bf9a73176554938ec9b98f20f929a044f80acf1b"}, - {file = "protobuf-3.20.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fd7133b885e356fa4920ead8289bb45dc6f185a164e99e10279f33732ed5ce15"}, - {file = "protobuf-3.20.0-cp37-cp37m-win32.whl", hash = "sha256:8d84453422312f8275455d1cb52d850d6a4d7d714b784e41b573c6f5bfc2a029"}, - {file = "protobuf-3.20.0-cp37-cp37m-win_amd64.whl", hash = "sha256:52bae32a147c375522ce09bd6af4d2949aca32a0415bc62df1456b3ad17c6001"}, - {file = "protobuf-3.20.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25d2fcd6eef340082718ec9ad2c58d734429f2b1f7335d989523852f2bba220b"}, - {file = "protobuf-3.20.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:88c8be0558bdfc35e68c42ae5bf785eb9390d25915d4863bbc7583d23da77074"}, - {file = "protobuf-3.20.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:38fd9eb74b852e4ee14b16e9670cd401d147ee3f3ec0d4f7652e0c921d6227f8"}, - {file = "protobuf-3.20.0-cp38-cp38-win32.whl", hash = "sha256:7dcd84dc31ebb35ade755e06d1561d1bd3b85e85dbdbf6278011fc97b22810db"}, - {file = "protobuf-3.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:1eb13f5a5a59ca4973bcfa2fc8fff644bd39f2109c3f7a60bd5860cb6a49b679"}, - {file = "protobuf-3.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d24c81c2310f0063b8fc1c20c8ed01f3331be9374b4b5c2de846f69e11e21fb"}, - {file = "protobuf-3.20.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8be43a91ab66fe995e85ccdbdd1046d9f0443d59e060c0840319290de25b7d33"}, - {file = "protobuf-3.20.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7a53d4035427b9dbfbb397f46642754d294f131e93c661d056366f2a31438263"}, - {file = "protobuf-3.20.0-cp39-cp39-win32.whl", hash = "sha256:32bf4a90c207a0b4e70ca6dd09d43de3cb9898f7d5b69c2e9e3b966a7f342820"}, - {file = "protobuf-3.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:6efe066a7135233f97ce51a1aa007d4fb0be28ef093b4f88dac4ad1b3a2b7b6f"}, - {file = "protobuf-3.20.0-py2.py3-none-any.whl", hash = "sha256:4eda68bd9e2a4879385e6b1ea528c976f59cd9728382005cc54c28bcce8db983"}, - {file = "protobuf-3.20.0.tar.gz", hash = "sha256:71b2c3d1cd26ed1ec7c8196834143258b2ad7f444efff26fdc366c6f5e752702"}, + {file = "protobuf-3.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3cc797c9d15d7689ed507b165cd05913acb992d78b379f6014e013f9ecb20996"}, + {file = "protobuf-3.20.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:ff8d8fa42675249bb456f5db06c00de6c2f4c27a065955917b28c4f15978b9c3"}, + {file = "protobuf-3.20.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd68be2559e2a3b84f517fb029ee611546f7812b1fdd0aa2ecc9bc6ec0e4fdde"}, + {file = "protobuf-3.20.1-cp310-cp310-win32.whl", hash = "sha256:9016d01c91e8e625141d24ec1b20fed584703e527d28512aa8c8707f105a683c"}, + {file = "protobuf-3.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:32ca378605b41fd180dfe4e14d3226386d8d1b002ab31c969c366549e66a2bb7"}, + {file = "protobuf-3.20.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9be73ad47579abc26c12024239d3540e6b765182a91dbc88e23658ab71767153"}, + {file = "protobuf-3.20.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:097c5d8a9808302fb0da7e20edf0b8d4703274d140fd25c5edabddcde43e081f"}, + {file = "protobuf-3.20.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e250a42f15bf9d5b09fe1b293bdba2801cd520a9f5ea2d7fb7536d4441811d20"}, + {file = "protobuf-3.20.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cdee09140e1cd184ba9324ec1df410e7147242b94b5f8b0c64fc89e38a8ba531"}, + {file = "protobuf-3.20.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:af0ebadc74e281a517141daad9d0f2c5d93ab78e9d455113719a45a49da9db4e"}, + {file = "protobuf-3.20.1-cp37-cp37m-win32.whl", hash = "sha256:755f3aee41354ae395e104d62119cb223339a8f3276a0cd009ffabfcdd46bb0c"}, + {file = "protobuf-3.20.1-cp37-cp37m-win_amd64.whl", hash = "sha256:62f1b5c4cd6c5402b4e2d63804ba49a327e0c386c99b1675c8a0fefda23b2067"}, + {file = "protobuf-3.20.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf"}, + {file = "protobuf-3.20.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:cb29edb9eab15742d791e1025dd7b6a8f6fcb53802ad2f6e3adcb102051063ab"}, + {file = "protobuf-3.20.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:69ccfdf3657ba59569c64295b7d51325f91af586f8d5793b734260dfe2e94e2c"}, + {file = "protobuf-3.20.1-cp38-cp38-win32.whl", hash = "sha256:dd5789b2948ca702c17027c84c2accb552fc30f4622a98ab5c51fcfe8c50d3e7"}, + {file = "protobuf-3.20.1-cp38-cp38-win_amd64.whl", hash = "sha256:77053d28427a29987ca9caf7b72ccafee011257561259faba8dd308fda9a8739"}, + {file = "protobuf-3.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f50601512a3d23625d8a85b1638d914a0970f17920ff39cec63aaef80a93fb7"}, + {file = "protobuf-3.20.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:284f86a6207c897542d7e956eb243a36bb8f9564c1742b253462386e96c6b78f"}, + {file = "protobuf-3.20.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7403941f6d0992d40161aa8bb23e12575637008a5a02283a930addc0508982f9"}, + {file = "protobuf-3.20.1-cp39-cp39-win32.whl", hash = "sha256:db977c4ca738dd9ce508557d4fce0f5aebd105e158c725beec86feb1f6bc20d8"}, + {file = "protobuf-3.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:7e371f10abe57cee5021797126c93479f59fccc9693dafd6bd5633ab67808a91"}, + {file = "protobuf-3.20.1-py2.py3-none-any.whl", hash = "sha256:adfc6cf69c7f8c50fd24c793964eef18f0ac321315439d94945820612849c388"}, + {file = "protobuf-3.20.1.tar.gz", hash = "sha256:adc31566d027f45efe3f44eeb5b1f329da43891634d61c75a5944e9be6dd42c9"}, ] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, @@ -2891,8 +2891,8 @@ typed-ast = [ {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] typing-extensions = [ - {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, - {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, + {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, + {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, ] uritemplate = [ {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"}, From 8a06a4afc1866a5837b5cab7c4ad3845eb6ee050 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Fri, 10 Jun 2022 18:25:40 +0200 Subject: [PATCH 0576/1227] remove unused import --- openpype/modules/shotgrid/shotgrid_module.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index dcc8187194..5644f0c35f 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -1,5 +1,4 @@ import os -import threading from openpype_interfaces import ( ITrayModule, From e21caf109fc3405afb36e86ab7f9ff9dbb8b0726 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 18:26:05 +0200 Subject: [PATCH 0577/1227] extraction can be called on whole settings --- openpype/tools/settings/settings/base.py | 110 +++++++++++------- .../tools/settings/settings/categories.py | 35 +++++- openpype/tools/settings/settings/constants.py | 13 +++ 3 files changed, 112 insertions(+), 46 deletions(-) diff --git a/openpype/tools/settings/settings/base.py b/openpype/tools/settings/settings/base.py index 3ade5e5ef8..aa2b448ccf 100644 --- a/openpype/tools/settings/settings/base.py +++ b/openpype/tools/settings/settings/base.py @@ -12,28 +12,71 @@ from openpype.tools.settings import CHILD_OFFSET from .widgets import ExpandingWidget from .lib import create_deffered_value_change_timer -from .constants import DEFAULT_PROJECT_LABEL +from .constants import ( + DEFAULT_PROJECT_LABEL, + SETTINGS_PATH_KEY, + ROOT_KEY, + VALUE_KEY, + SAVE_TIME_KEY, + PROJECT_NAME_KEY, +) _MENU_SEPARATOR_REQ = object() -SETTINGS_PATH_KEY = "__settings_path__" -ROOT_KEY = "__root_key__" -VALUE_KEY = "__value__" -SAVE_TIME_KEY = "__extracted__" -PROJECT_NAME_KEY = "__project_name__" + + +class ExtractHelper: + _last_save_dir = os.path.expanduser("~") + + @classmethod + def get_last_save_dir(cls): + return cls._last_save_dir + + @classmethod + def set_last_save_dir(cls, save_dir): + cls._last_save_dir = save_dir + + @classmethod + def ask_for_save_filepath(cls, parent): + dialog = QtWidgets.QFileDialog( + parent, + "Save settings values", + cls.get_last_save_dir(), + "Values (*.json)" + ) + # dialog.setOption(dialog.DontUseNativeDialog) + dialog.setAcceptMode(dialog.AcceptSave) + if dialog.exec() != dialog.Accepted: + return + + selected_urls = dialog.selectedUrls() + if not selected_urls: + return + + filepath = selected_urls[0].toLocalFile() + if not filepath: + return + + if not filepath.lower().endswith(".json"): + filepath += ".json" + return filepath + + @classmethod + def extract_settings_to_json(cls, filepath, settings_data, project_name): + now = datetime.datetime.now() + settings_data[SAVE_TIME_KEY] = now.strftime("%Y-%m-%d %H:%M:%S") + if project_name != 0: + settings_data[PROJECT_NAME_KEY] = project_name + + with open(filepath, "w") as stream: + json.dump(settings_data, stream, indent=4) + + new_dir = os.path.dirname(filepath) + cls.set_last_save_dir(new_dir) class BaseWidget(QtWidgets.QWidget): - _last_save_dir = os.path.expanduser("~") allow_actions = True - @staticmethod - def get_last_save_dir(): - return BaseWidget._last_save_dir - - @staticmethod - def set_last_save_dir(save_dir): - BaseWidget._last_save_dir = save_dir - def __init__(self, category_widget, entity, entity_widget): self.category_widget = category_widget self.entity = entity @@ -266,45 +309,24 @@ class BaseWidget(QtWidgets.QWidget): return [(action, copy_value)] def _extract_to_file(self): - dialog = QtWidgets.QFileDialog( - self, - "Save settings values", - self.get_last_save_dir(), - "Values (*.json)" - ) - # dialog.setOption(dialog.DontUseNativeDialog) - dialog.setAcceptMode(dialog.AcceptSave) - if dialog.exec() != dialog.Accepted: - return - - selected_urls = dialog.selectedUrls() - if not selected_urls: - return - - filepath = selected_urls[0].toLocalFile() + filepath = ExtractHelper.ask_for_save_filepath(self) if not filepath: return - if not filepath.lower().endswith(".json"): - filepath += ".json" - settings_data = self._get_mime_data_from_entity() - now = datetime.datetime.now() - settings_data[SAVE_TIME_KEY] = now.strftime("%Y-%m-%d %H:%M:%S") + project_name = 0 if hasattr(self.category_widget, "project_name"): - settings_data[PROJECT_NAME_KEY] = self.category_widget.project_name + project_name = self.category_widget.project_name - with open(filepath, "w") as stream: - json.dump(settings_data, stream, indent=4) - - new_dir = os.path.dirname(filepath) - self.set_last_save_dir(new_dir) + ExtractHelper.extract_settings_to_json( + filepath, settings_data, project_name + ) def _extract_value_to_file_actions(self, menu): - save_action = QtWidgets.QAction("Extract to file", menu) + extract_action = QtWidgets.QAction("Extract to file", menu) return [ _MENU_SEPARATOR_REQ, - (save_action, self._extract_to_file) + (extract_action, self._extract_to_file) ] def _parse_source_data_for_paste(self, data): diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index c8ade5fcdb..764f42f1a3 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -45,8 +45,15 @@ from .breadcrumbs_widget import ( SystemSettingsBreadcrumbs, ProjectSettingsBreadcrumbs ) - -from .base import GUIWidget +from .constants import ( + SETTINGS_PATH_KEY, + ROOT_KEY, + VALUE_KEY, +) +from .base import ( + ExtractHelper, + GUIWidget, +) from .list_item_widget import ListWidget from .list_strict_widget import ListStrictWidget from .dict_mutable_widget import DictMutableKeysWidget @@ -627,11 +634,35 @@ class SettingsCategoryWidget(QtWidgets.QWidget): self._on_context_version_trigger ) submenu.addAction(action) + menu.addMenu(submenu) + extract_action = QtWidgets.QAction("Extract to file", menu) + extract_action.triggered.connect(self._on_extract_to_file) + + menu.addAction(extract_action) + def _on_context_version_trigger(self, version): self._on_source_version_change(version) + def _on_extract_to_file(self): + filepath = ExtractHelper.ask_for_save_filepath(self) + if not filepath: + return + + settings_data = { + SETTINGS_PATH_KEY: self.entity.root_key, + ROOT_KEY: self.entity.root_key, + VALUE_KEY: self.entity.value + } + project_name = 0 + if hasattr(self, "project_name"): + project_name = self.project_name + + ExtractHelper.extract_settings_to_json( + filepath, settings_data, project_name + ) + def _on_reset_crash(self): self.save_btn.setEnabled(False) diff --git a/openpype/tools/settings/settings/constants.py b/openpype/tools/settings/settings/constants.py index 9d6d7904d7..d98d18c8bf 100644 --- a/openpype/tools/settings/settings/constants.py +++ b/openpype/tools/settings/settings/constants.py @@ -7,6 +7,12 @@ PROJECT_IS_ACTIVE_ROLE = QtCore.Qt.UserRole + 2 PROJECT_IS_SELECTED_ROLE = QtCore.Qt.UserRole + 3 PROJECT_VERSION_ROLE = QtCore.Qt.UserRole + 4 +# Save/Extract keys +SETTINGS_PATH_KEY = "__settings_path__" +ROOT_KEY = "__root_key__" +VALUE_KEY = "__value__" +SAVE_TIME_KEY = "__extracted__" +PROJECT_NAME_KEY = "__project_name__" __all__ = ( "DEFAULT_PROJECT_LABEL", @@ -15,4 +21,11 @@ __all__ = ( "PROJECT_IS_ACTIVE_ROLE", "PROJECT_IS_SELECTED_ROLE", "PROJECT_VERSION_ROLE", + + "SETTINGS_PATH_KEY", + "ROOT_KEY", + "SETTINGS_PATH_KEY", + "VALUE_KEY", + "SAVE_TIME_KEY", + "PROJECT_NAME_KEY", ) From 7d6ef39352ac63ce1bd3a9901f24ef74b7dd6ce4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 10 Jun 2022 18:31:28 +0200 Subject: [PATCH 0578/1227] Fix #3319: Houdini VDB update wrong file attribute name --- openpype/hosts/houdini/plugins/load/load_vdb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/load/load_vdb.py b/openpype/hosts/houdini/plugins/load/load_vdb.py index 06bb9e45e4..54535446a3 100644 --- a/openpype/hosts/houdini/plugins/load/load_vdb.py +++ b/openpype/hosts/houdini/plugins/load/load_vdb.py @@ -102,7 +102,7 @@ class VdbLoader(load.LoaderPlugin): file_path = get_representation_path(representation) file_path = self.format_path(file_path) - file_node.setParms({"fileName": file_path}) + file_node.setParms({"file": file_path}) # Update attribute node.setParms({"representation": str(representation["_id"])}) @@ -110,4 +110,4 @@ class VdbLoader(load.LoaderPlugin): def remove(self, container): node = container["node"] - node.destroy() + node.destroy() \ No newline at end of file From 86f19dfa5e187906eb3707a172cb86a665ddfa9f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 10 Jun 2022 18:33:33 +0200 Subject: [PATCH 0579/1227] Fix no new line at end of file --- openpype/hosts/houdini/plugins/load/load_vdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/load/load_vdb.py b/openpype/hosts/houdini/plugins/load/load_vdb.py index 54535446a3..5e572c83e5 100644 --- a/openpype/hosts/houdini/plugins/load/load_vdb.py +++ b/openpype/hosts/houdini/plugins/load/load_vdb.py @@ -110,4 +110,4 @@ class VdbLoader(load.LoaderPlugin): def remove(self, container): node = container["node"] - node.destroy() \ No newline at end of file + node.destroy() From f1a5a122cf1801f0b812a3e10936b7336b630a15 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 9 Jun 2022 18:35:54 +0200 Subject: [PATCH 0580/1227] Project Manager: fix tools not being visible until when editable (cherry picked from commit fbdd225c8b0cc579a022d69c179a5f90fc6a387d) --- openpype/tools/project_manager/project_manager/delegates.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index 31487ff132..b066bbb159 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -205,3 +205,9 @@ class ToolsDelegate(QtWidgets.QStyledItemDelegate): def setModelData(self, editor, model, index): model.setData(index, editor.value(), QtCore.Qt.EditRole) + + def displayText(self, value, locale): + if value: + return ", ".join(value) + else: + return From c486fb6f59a0d4ed977d8260c08c7d54bf5e3ba5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 10 Jun 2022 13:06:49 +0200 Subject: [PATCH 0581/1227] Force edit mode on add task button (cherry picked from commit 96ca15b565b952d4e17c2010e8846ca18272d681) --- openpype/tools/project_manager/project_manager/window.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index c281479d4f..10b28f535f 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -245,7 +245,8 @@ class ProjectManagerWindow(QtWidgets.QWidget): self.hierarchy_view.add_asset() def _on_add_task(self): - self.hierarchy_view.add_task() + # Colorbleed edit: force the task to directly be in edit mode + self.hierarchy_view._add_task_and_edit() def _on_create_folders(self): project_name = self._current_project() From d2de1539136ad7d4da5902d78aed56dd204f4a50 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 18:47:42 +0200 Subject: [PATCH 0582/1227] fix copy value to string --- openpype/tools/settings/settings/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/base.py b/openpype/tools/settings/settings/base.py index aa2b448ccf..6def284a83 100644 --- a/openpype/tools/settings/settings/base.py +++ b/openpype/tools/settings/settings/base.py @@ -284,7 +284,7 @@ class BaseWidget(QtWidgets.QWidget): ) # Copy as json - value = settings_data["value"] + value = settings_data[VALUE_KEY] json_encoded_data = None if isinstance(value, (dict, list)): json_encoded_data = QtCore.QByteArray() From 44b1b638e58e7fc3da13fc54ad100d980a44a1ca Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 7 Jun 2022 15:58:30 +0200 Subject: [PATCH 0583/1227] Optimization: speed up validate mesh shader connections for large scenes (cherry picked from commit 46752511e500a54ba9335a325ca09224ef600227) --- .../validate_mesh_shader_connections.py | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py b/openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py index 0969573a90..e0835000f0 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py @@ -12,28 +12,41 @@ def pairs(iterable): yield i, y -def get_invalid_sets(shape): - """Get sets that are considered related but do not contain the shape. +def get_invalid_sets(shapes): + """Return invalid sets for the given shapes. - In some scenarios Maya keeps connections to multiple shaders - even if just a single one is assigned on the full object. + This takes a list of shape nodes to cache the set members for overlapping + sets in the queries. This avoids many Maya set member queries. - These are related sets returned by `maya.cmds.listSets` that don't - actually have the shape as member. + Returns: + dict: Dictionary of shapes and their invalid sets, e.g. + {"pCubeShape": ["set1", "set2"]} """ - invalid = [] - sets = cmds.listSets(object=shape, t=1, extendToShape=False) or [] - for s in sets: - members = cmds.sets(s, query=True, nodesOnly=True) - if not members: - invalid.append(s) - continue + cache = dict() + invalid = dict() - members = set(cmds.ls(members, long=True)) - if shape not in members: - invalid.append(s) + # Collect the sets from the shape + for shape in shapes: + invalid_sets = [] + sets = cmds.listSets(object=shape, t=1, extendToShape=False) or [] + for set_ in sets: + + members = cache.get(set_, None) + if members is None: + members = set(cmds.ls(cmds.sets(set_, + query=True, + nodesOnly=True), long=True)) + cache[set_] = members + + # If the shape is not actually present as a member of the set + # consider it invalid + if shape not in members: + invalid_sets.append(set_) + + if invalid_sets: + invalid[shape] = invalid_sets return invalid @@ -92,15 +105,9 @@ class ValidateMeshShaderConnections(pyblish.api.InstancePlugin): @staticmethod def get_invalid(instance): - shapes = cmds.ls(instance[:], dag=1, leaf=1, shapes=1, long=True) - - # todo: allow to check anything that can have a shader - shapes = cmds.ls(shapes, noIntermediate=True, long=True, type="mesh") - - invalid = [] - for shape in shapes: - if get_invalid_sets(shape): - invalid.append(shape) + nodes = instance[:] + shapes = cmds.ls(nodes, noIntermediate=True, long=True, type="mesh") + invalid = get_invalid_sets(shapes).keys() return invalid @@ -108,7 +115,7 @@ class ValidateMeshShaderConnections(pyblish.api.InstancePlugin): def repair(cls, instance): shapes = cls.get_invalid(instance) - for shape in shapes: - invalid_sets = get_invalid_sets(shape) + invalid = get_invalid_sets(shapes) + for shape, invalid_sets in invalid.items(): for set_node in invalid_sets: disconnect(shape, set_node) From cbc6fb8b330abc88c0e8c64c57a966cd09fd8077 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 10 Jun 2022 20:54:12 +0200 Subject: [PATCH 0584/1227] Make `add_task_and_edit` method public --- openpype/tools/project_manager/project_manager/view.py | 6 +++--- openpype/tools/project_manager/project_manager/window.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 74f5a06b71..f995d45f44 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -386,7 +386,7 @@ class HierarchyView(QtWidgets.QTreeView): self._source_model.delete_indexes(indexes) def _on_ctrl_shift_enter_pressed(self): - self._add_task_and_edit() + self.add_task_and_edit() def add_asset(self, parent_index=None): if parent_index is None: @@ -428,9 +428,9 @@ class HierarchyView(QtWidgets.QTreeView): self.edit(new_index) def _add_task_action(self): - self._add_task_and_edit() + self.add_task_and_edit() - def _add_task_and_edit(self): + def add_task_and_edit(self): new_index = self.add_task() if new_index is None: return diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 10b28f535f..ed3445174d 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -246,7 +246,7 @@ class ProjectManagerWindow(QtWidgets.QWidget): def _on_add_task(self): # Colorbleed edit: force the task to directly be in edit mode - self.hierarchy_view._add_task_and_edit() + self.hierarchy_view.add_task_and_edit() def _on_create_folders(self): project_name = self._current_project() From 46e17b4816b6506c2b4aabe94ec15c2c1e71fb5d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 10 Jun 2022 20:54:33 +0200 Subject: [PATCH 0585/1227] Remove comment --- openpype/tools/project_manager/project_manager/window.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index ed3445174d..458a36ac39 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -245,7 +245,6 @@ class ProjectManagerWindow(QtWidgets.QWidget): self.hierarchy_view.add_asset() def _on_add_task(self): - # Colorbleed edit: force the task to directly be in edit mode self.hierarchy_view.add_task_and_edit() def _on_create_folders(self): From 01bec693275472ef976ffd29df2048aba16f3cf0 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 11 Jun 2022 03:44:47 +0000 Subject: [PATCH 0586/1227] [Automated] Bump version --- CHANGELOG.md | 44 +++++++++++++++++++------------------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d7798cb48..d0d25908cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,39 @@ # Changelog -## [3.11.0-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.11.0-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...HEAD) ### 📖 Documentation +- Documentation: Add app key to template documentation [\#3299](https://github.com/pypeclub/OpenPype/pull/3299) - doc: adding royal render and multiverse to the web site [\#3285](https://github.com/pypeclub/OpenPype/pull/3285) **🚀 Enhancements** +- updated poetry installation source [\#3316](https://github.com/pypeclub/OpenPype/pull/3316) +- TVPaint: Extractor use mark in/out range to render [\#3309](https://github.com/pypeclub/OpenPype/pull/3309) +- Ftrack: Delivery action can work on ReviewSessions [\#3307](https://github.com/pypeclub/OpenPype/pull/3307) +- Maya: Look assigner UI improvements [\#3298](https://github.com/pypeclub/OpenPype/pull/3298) +- Ftrack: Action to transfer values of hierarchical attributes [\#3284](https://github.com/pypeclub/OpenPype/pull/3284) +- Maya: better handling of legacy review subsets names [\#3269](https://github.com/pypeclub/OpenPype/pull/3269) - General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268) - Unreal: add support for skeletalMesh and staticMesh to loaders [\#3267](https://github.com/pypeclub/OpenPype/pull/3267) - Maya: reference loaders could store placeholder in referenced url [\#3264](https://github.com/pypeclub/OpenPype/pull/3264) - TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250) - Nuke: Change default icon path in settings [\#3247](https://github.com/pypeclub/OpenPype/pull/3247) +- Maya: publishing of animation and pointcache on a farm [\#3225](https://github.com/pypeclub/OpenPype/pull/3225) +- Maya: Look assigner UI improvements [\#3208](https://github.com/pypeclub/OpenPype/pull/3208) +- Nuke: add pointcache and animation to loader [\#3186](https://github.com/pypeclub/OpenPype/pull/3186) **🐛 Bug fixes** +- Houdini: Fix Houdini VDB manage update wrong file attribute name [\#3322](https://github.com/pypeclub/OpenPype/pull/3322) +- General: Vendorized modules for Python 2 and update poetry lock [\#3305](https://github.com/pypeclub/OpenPype/pull/3305) +- Fix - added local targets to install host [\#3303](https://github.com/pypeclub/OpenPype/pull/3303) +- Settings: Add missing default settings for nuke gizmo [\#3301](https://github.com/pypeclub/OpenPype/pull/3301) +- Maya: Fix swaped width and height in reviews [\#3300](https://github.com/pypeclub/OpenPype/pull/3300) +- Maya: point cache publish handles Maya instances [\#3297](https://github.com/pypeclub/OpenPype/pull/3297) - Global: extract review slate issues [\#3286](https://github.com/pypeclub/OpenPype/pull/3286) - Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) - Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) @@ -30,24 +46,15 @@ - Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) - Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) - Flame: debugging [\#3224](https://github.com/pypeclub/OpenPype/pull/3224) -- add silent audio to slate [\#3162](https://github.com/pypeclub/OpenPype/pull/3162) **Merged pull requests:** -- Maya: better handling of legacy review subsets names [\#3269](https://github.com/pypeclub/OpenPype/pull/3269) -- Deadline: publishing of animation and pointcache on a farm [\#3225](https://github.com/pypeclub/OpenPype/pull/3225) -- Nuke: add pointcache and animation to loader [\#3186](https://github.com/pypeclub/OpenPype/pull/3186) -- Add a gizmo menu to nuke [\#3172](https://github.com/pypeclub/OpenPype/pull/3172) +- Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.10.0-nightly.6...3.10.0) -**🆕 New features** - -- General: OpenPype modules publish plugins are registered in host [\#3180](https://github.com/pypeclub/OpenPype/pull/3180) -- General: Creator plugins from addons can be registered [\#3179](https://github.com/pypeclub/OpenPype/pull/3179) - **🚀 Enhancements** - Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) @@ -55,10 +62,6 @@ - Project Manager: Allow to paste Tasks into multiple assets at the same time [\#3226](https://github.com/pypeclub/OpenPype/pull/3226) - Project manager: Sped up project load [\#3216](https://github.com/pypeclub/OpenPype/pull/3216) - Loader UI: Speed issues of loader with sync server [\#3199](https://github.com/pypeclub/OpenPype/pull/3199) -- Looks: add basic support for Renderman [\#3190](https://github.com/pypeclub/OpenPype/pull/3190) -- Maya: added clean\_import option to Import loader [\#3181](https://github.com/pypeclub/OpenPype/pull/3181) -- Add the scripts menu definition to nuke [\#3168](https://github.com/pypeclub/OpenPype/pull/3168) -- Maya: add maya 2023 to default applications [\#3167](https://github.com/pypeclub/OpenPype/pull/3167) **🐛 Bug fixes** @@ -76,12 +79,6 @@ - Photoshop: skip collector when automatic testing [\#3202](https://github.com/pypeclub/OpenPype/pull/3202) - Nuke: render/workfile version sync doesn't work on farm [\#3185](https://github.com/pypeclub/OpenPype/pull/3185) - Ftrack: Review image only if there are no mp4 reviews [\#3183](https://github.com/pypeclub/OpenPype/pull/3183) -- Ftrack: Locations deepcopy issue [\#3177](https://github.com/pypeclub/OpenPype/pull/3177) -- General: Avoid creating multiple thumbnails [\#3176](https://github.com/pypeclub/OpenPype/pull/3176) -- General/Hiero: better clip duration calculation [\#3169](https://github.com/pypeclub/OpenPype/pull/3169) -- General: Oiio conversion for ffmpeg checks for invalid characters [\#3166](https://github.com/pypeclub/OpenPype/pull/3166) -- Fix for attaching render to subset [\#3164](https://github.com/pypeclub/OpenPype/pull/3164) -- Harmony: fixed missing task name in render instance [\#3163](https://github.com/pypeclub/OpenPype/pull/3163) **🔀 Refactored code** @@ -92,7 +89,6 @@ - Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) - Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249) - Maya: added jpg to filter for Image Plane Loader [\#3223](https://github.com/pypeclub/OpenPype/pull/3223) -- Webpublisher: replace space by underscore in subset names [\#3160](https://github.com/pypeclub/OpenPype/pull/3160) ## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19) @@ -103,15 +99,13 @@ - nuke: generate publishing nodes inside render group node [\#3206](https://github.com/pypeclub/OpenPype/pull/3206) - Loader UI: Speed issues of loader with sync server [\#3200](https://github.com/pypeclub/OpenPype/pull/3200) - Backport of fix for attaching renders to subsets [\#3195](https://github.com/pypeclub/OpenPype/pull/3195) +- Looks: add basic support for Renderman [\#3190](https://github.com/pypeclub/OpenPype/pull/3190) **🐛 Bug fixes** - Standalone Publisher: Always create new representation for thumbnail [\#3204](https://github.com/pypeclub/OpenPype/pull/3204) - Nuke: render/workfile version sync doesn't work on farm [\#3184](https://github.com/pypeclub/OpenPype/pull/3184) - Ftrack: Review image only if there are no mp4 reviews [\#3182](https://github.com/pypeclub/OpenPype/pull/3182) -- Ftrack: Locations deepcopy issue [\#3175](https://github.com/pypeclub/OpenPype/pull/3175) -- General: Avoid creating multiple thumbnails [\#3174](https://github.com/pypeclub/OpenPype/pull/3174) -- General: TemplateResult can be copied [\#3170](https://github.com/pypeclub/OpenPype/pull/3170) **Merged pull requests:** diff --git a/openpype/version.py b/openpype/version.py index 4c78a6e0a1..4b0a688cbf 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.0-nightly.1" +__version__ = "3.11.0-nightly.2" diff --git a/pyproject.toml b/pyproject.toml index 63bd08d644..4289c74ebe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.0-nightly.1" # OpenPype +version = "3.11.0-nightly.2" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From df26ddd1ebd77282cf0202f7cd4830fd49016165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Marinov?= Date: Sun, 12 Jun 2022 00:18:07 +0200 Subject: [PATCH 0587/1227] Fix Nuke model loader to load full geo hierarchy by default --- openpype/hosts/nuke/plugins/load/load_model.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/hosts/nuke/plugins/load/load_model.py b/openpype/hosts/nuke/plugins/load/load_model.py index 2f54595cb0..37670d8d4e 100644 --- a/openpype/hosts/nuke/plugins/load/load_model.py +++ b/openpype/hosts/nuke/plugins/load/load_model.py @@ -60,6 +60,10 @@ class AlembicModelLoader(load.LoaderPlugin): inpanel=False ) model_node.forceValidate() + + # workaround to load all geo nodes, not just top level ones + model_node.knob('scene_view').setAllItems(model_node.knob('scene_view').getAllItems(), True) + model_node["frame_rate"].setValue(float(fps)) # workaround because nuke's bug is not adding @@ -142,6 +146,9 @@ class AlembicModelLoader(load.LoaderPlugin): model_node["frame_rate"].setValue(float(fps)) model_node["file"].setValue(file) + # workaround to load all geo nodes, not just top level ones + model_node.knob('scene_view').setAllItems(model_node.knob('scene_view').getAllItems(), True) + # workaround because nuke's bug is # not adding animation keys properly xpos = model_node.xpos() From 41ad5e09007eba53e0e91579157f39e29b596587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Marinov?= Date: Sun, 12 Jun 2022 01:04:38 +0200 Subject: [PATCH 0588/1227] PEP-8 --- openpype/hosts/nuke/plugins/load/load_model.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_model.py b/openpype/hosts/nuke/plugins/load/load_model.py index 37670d8d4e..74f0b9731b 100644 --- a/openpype/hosts/nuke/plugins/load/load_model.py +++ b/openpype/hosts/nuke/plugins/load/load_model.py @@ -62,7 +62,9 @@ class AlembicModelLoader(load.LoaderPlugin): model_node.forceValidate() # workaround to load all geo nodes, not just top level ones - model_node.knob('scene_view').setAllItems(model_node.knob('scene_view').getAllItems(), True) + model_node.knob("scene_view").setAllItems( + model_node.knob("scene_view").getAllItems(), True + ) model_node["frame_rate"].setValue(float(fps)) @@ -147,7 +149,9 @@ class AlembicModelLoader(load.LoaderPlugin): model_node["file"].setValue(file) # workaround to load all geo nodes, not just top level ones - model_node.knob('scene_view').setAllItems(model_node.knob('scene_view').getAllItems(), True) + model_node.knob("scene_view").setAllItems( + model_node.knob("scene_view").getAllItems(), True + ) # workaround because nuke's bug is # not adding animation keys properly From 02f9b7729668d5dce9dfee1aebe8177771e67c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Marinov?= Date: Mon, 13 Jun 2022 10:37:43 +0200 Subject: [PATCH 0589/1227] More elegant solution, does not require preferences settings --- openpype/hosts/nuke/plugins/load/load_model.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_model.py b/openpype/hosts/nuke/plugins/load/load_model.py index 74f0b9731b..44e8490d4c 100644 --- a/openpype/hosts/nuke/plugins/load/load_model.py +++ b/openpype/hosts/nuke/plugins/load/load_model.py @@ -61,10 +61,10 @@ class AlembicModelLoader(load.LoaderPlugin): ) model_node.forceValidate() - # workaround to load all geo nodes, not just top level ones - model_node.knob("scene_view").setAllItems( - model_node.knob("scene_view").getAllItems(), True - ) + # Ensure all items are imported and selected. + scene_view = model_node.knob('scene_view') + scene_view.setImportedItems(scene_view.getAllItems()) + scene_view.setSelectedItems(scene_view.getAllItems()) model_node["frame_rate"].setValue(float(fps)) @@ -148,10 +148,10 @@ class AlembicModelLoader(load.LoaderPlugin): model_node["frame_rate"].setValue(float(fps)) model_node["file"].setValue(file) - # workaround to load all geo nodes, not just top level ones - model_node.knob("scene_view").setAllItems( - model_node.knob("scene_view").getAllItems(), True - ) + # Ensure all items are imported and selected. + scene_view = model_node.knob('scene_view') + scene_view.setImportedItems(scene_view.getAllItems()) + scene_view.setSelectedItems(scene_view.getAllItems()) # workaround because nuke's bug is # not adding animation keys properly From 238e3a03216f25141a02fe4637b44b6a09b4523f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 13 Jun 2022 13:28:07 +0200 Subject: [PATCH 0590/1227] add run eventserver launcher to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 28cfb4b1e9..8b268b7f28 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,5 @@ website/.docusaurus .poetry/ .python-version + +tools/run_eventserver.ps1 From 6033304282e7dc4a276ff7558161b66cfff0c3cb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 13 Jun 2022 13:29:18 +0200 Subject: [PATCH 0591/1227] making wider extension filter for run eventserver gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8b268b7f28..7eaef69873 100644 --- a/.gitignore +++ b/.gitignore @@ -103,4 +103,4 @@ website/.docusaurus .poetry/ .python-version -tools/run_eventserver.ps1 +tools/run_eventserver.* From 0163eae3fa80c720a527b134c1030362c4843ae5 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 13 Jun 2022 14:50:26 +0200 Subject: [PATCH 0592/1227] Remove unused code and add sugestions --- .../shotgrid/hooks/post_shotgrid_changes.py | 9 -------- openpype/modules/shotgrid/lib/settings.py | 23 ------------------- .../publish/collect_shotgrid_entities.py | 5 ++-- 3 files changed, 2 insertions(+), 35 deletions(-) delete mode 100644 openpype/modules/shotgrid/hooks/post_shotgrid_changes.py diff --git a/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py b/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py deleted file mode 100644 index e8369ad3cb..0000000000 --- a/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py +++ /dev/null @@ -1,9 +0,0 @@ -from openpype.lib import PostLaunchHook - - -class PostShotgridHook(PostLaunchHook): - order = None - - def execute(self, *args, **kwargs): - print(args, kwargs) - pass diff --git a/openpype/modules/shotgrid/lib/settings.py b/openpype/modules/shotgrid/lib/settings.py index 4a772de5b7..924099f04b 100644 --- a/openpype/modules/shotgrid/lib/settings.py +++ b/openpype/modules/shotgrid/lib/settings.py @@ -1,24 +1,11 @@ -import os - -from pymongo import MongoClient from openpype.api import get_system_settings, get_project_settings from openpype.modules.shotgrid.lib.const import MODULE_NAME -from openpype.modules.shotgrid.lib.tools import memoize -def get_project_list(): - mongo_url = os.getenv("OPENPYPE_MONGO") - client = MongoClient(mongo_url) - db = client['avalon'] - return db.list_collection_names() - - -@memoize def get_shotgrid_project_settings(project): return get_project_settings(project).get(MODULE_NAME, {}) -@memoize def get_shotgrid_settings(): return get_system_settings().get("modules", {}).get(MODULE_NAME, {}) @@ -29,13 +16,3 @@ def get_shotgrid_servers(): def get_leecher_backend_url(): return get_shotgrid_settings().get("leecher_backend_url") - - -def filter_projects_by_login(): - return bool(get_shotgrid_settings().get("filter_projects_by_login", False)) - - -def get_shotgrid_event_mongo_info(): - database_name = os.environ["OPENPYPE_DATABASE_NAME"] - collection_name = "shotgrid_events" - return database_name, collection_name diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py index a770c1eb87..9880425a41 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py @@ -1,7 +1,7 @@ import os import pyblish.api -from pymongo import MongoClient +from openpype.lib.mongo import OpenPypeMongoConnection from openpype.modules.shotgrid.lib.settings import ( get_shotgrid_project_settings, @@ -63,8 +63,7 @@ class CollectShotgridEntities(pyblish.api.ContextPlugin): def _get_shotgrid_collection(project): - mongo_url = os.getenv("OPENPYPE_MONGO") - client = MongoClient(mongo_url) + client = OpenPypeMongoConnection.get_mongo_client() return client.get_database("shotgrid_openpype").get_collection(project) From cef804aa1670b755b5c0072a9f0d5990f6fa92d3 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Mon, 13 Jun 2022 15:37:47 +0200 Subject: [PATCH 0593/1227] added eventserver utoility script for linux/mac --- tools/run_ftrack_eventserver.sh | 99 +++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 tools/run_ftrack_eventserver.sh diff --git a/tools/run_ftrack_eventserver.sh b/tools/run_ftrack_eventserver.sh new file mode 100644 index 0000000000..97daa14c2d --- /dev/null +++ b/tools/run_ftrack_eventserver.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash + +art () { + cat <<-EOF + + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · + +EOF +} + +# Colors for terminal + +RST='\033[0m' # Text Reset + +# Regular Colors +Black='\033[0;30m' # Black +Red='\033[0;31m' # Red +Green='\033[0;32m' # Green +Yellow='\033[0;33m' # Yellow +Blue='\033[0;34m' # Blue +Purple='\033[0;35m' # Purple +Cyan='\033[0;36m' # Cyan +White='\033[0;37m' # White + +# Bold +BBlack='\033[1;30m' # Black +BRed='\033[1;31m' # Red +BGreen='\033[1;32m' # Green +BYellow='\033[1;33m' # Yellow +BBlue='\033[1;34m' # Blue +BPurple='\033[1;35m' # Purple +BCyan='\033[1;36m' # Cyan +BWhite='\033[1;37m' # White + +# Bold High Intensity +BIBlack='\033[1;90m' # Black +BIRed='\033[1;91m' # Red +BIGreen='\033[1;92m' # Green +BIYellow='\033[1;93m' # Yellow +BIBlue='\033[1;94m' # Blue +BIPurple='\033[1;95m' # Purple +BICyan='\033[1;96m' # Cyan +BIWhite='\033[1;97m' # White + + +############################################################################## +# Return absolute path +# Globals: +# None +# Arguments: +# Path to resolve +# Returns: +# None +############################################################################### +realpath () { + echo $(cd $(dirname "$1"); pwd)/$(basename "$1") +} + +# Main +main () { + echo -e "${BGreen}" + art + echo -e "${RST}" + + # Directories + openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) + + if [[ -z $POETRY_HOME ]]; then + export POETRY_HOME="$openpype_root/.poetry" + fi + + echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" + if [ -f "$POETRY_HOME/bin/poetry" ]; then + echo -e "${BIGreen}OK${RST}" + else + echo -e "${BIYellow}NOT FOUND${RST}" + echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..." + . "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } + fi + + pushd "$openpype_root" > /dev/null || return > /dev/null + + echo -e "${BIGreen}>>>${RST} Running Ftrack Eventserver ..." + "$POETRY_HOME/bin/poetry" run python $openpype_root/start.py eventserver +} + +main From 27b8f77855d45446af3b7b742a58d30c2143d32f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 16:06:21 +0200 Subject: [PATCH 0594/1227] fix scene inventory --- openpype/tools/sceneinventory/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 117bdfcba1..0bb9c4a658 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -198,12 +198,12 @@ class InventoryModel(TreeModel): self.clear() if self._hierarchy_view and selected: - if not hasattr(host.pipeline, "update_hierarchy"): # If host doesn't support hierarchical containers, then # cherry-pick only. self.add_items((item for item in items if item["objectName"] in selected)) + return # Update hierarchy info for all containers items_by_name = {item["objectName"]: item From cd6c37ece91caf7af0d40882b1a41f23190ebb38 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 16:06:57 +0200 Subject: [PATCH 0595/1227] added remaining custom mongo calls --- openpype/client/entities.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 82caaee7c5..a56288c1e8 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1034,6 +1034,8 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): """ ## Custom data storage: +- Settings - OP settings overrides and local settings +- Logging - logs from PypeLogger - Webpublisher - jobs - Ftrack - events - Maya - Shaders From 9cb5372d69e30e35a441c952178bb1aa2802e5a7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 16:29:09 +0200 Subject: [PATCH 0596/1227] use client functions to query data in standalone publisher plugins --- .../publish/collect_bulk_mov_instances.py | 11 +++--- .../plugins/publish/collect_hierarchy.py | 36 ++++++++++--------- .../plugins/publish/collect_matching_asset.py | 5 +-- .../publish/validate_task_existence.py | 20 ++++------- 4 files changed, 33 insertions(+), 39 deletions(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py index 3e7fb19c00..052a97af7d 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py @@ -3,7 +3,7 @@ import json import pyblish.api from openpype.lib import get_subset_name_with_asset_doc -from openpype.pipeline import legacy_io +from openpype.client import get_asset_by_name class CollectBulkMovInstances(pyblish.api.InstancePlugin): @@ -24,12 +24,9 @@ class CollectBulkMovInstances(pyblish.api.InstancePlugin): def process(self, instance): context = instance.context + project_name = context.data["projectEntity"]["name"] asset_name = instance.data["asset"] - - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) if not asset_doc: raise AssertionError(( "Couldn't find Asset document with name \"{}\"" @@ -52,7 +49,7 @@ class CollectBulkMovInstances(pyblish.api.InstancePlugin): self.subset_name_variant, task_name, asset_doc, - legacy_io.Session["AVALON_PROJECT"] + project_name ) instance_name = f"{asset_name}_{subset_name}" diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py index 77163651c4..78e63f7ef0 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py @@ -3,7 +3,7 @@ import re from copy import deepcopy import pyblish.api -from openpype.pipeline import legacy_io +from openpype.client import get_asset_by_id, get_project class CollectHierarchyInstance(pyblish.api.ContextPlugin): @@ -61,27 +61,32 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): **instance.data["anatomyData"]) def create_hierarchy(self, instance): - parents = list() - hierarchy = list() - visual_hierarchy = [instance.context.data["assetEntity"]] + asset_doc = instance.context.data["assetEntity"] + project_doc = instance.context.data["projectEntity"] + project_name = project_doc["name"] + visual_hierarchy = [asset_doc] + current_doc = asset_doc while True: - visual_parent = legacy_io.find_one( - {"_id": visual_hierarchy[-1]["data"]["visualParent"]} - ) - if visual_parent: - visual_hierarchy.append(visual_parent) - else: - visual_hierarchy.append( - instance.context.data["projectEntity"]) + visual_parent_id = current_doc["data"]["visualParent"] + visual_parent = None + if visual_parent_id: + visual_parent = get_asset_by_id(project_name, visual_parent_id) + + if not visual_parent: + visual_hierarchy.append(project_doc) break + visual_hierarchy.append(visual_parent) + current_doc = visual_parent # add current selection context hierarchy from standalonepublisher + parents = list() for entity in reversed(visual_hierarchy): parents.append({ "entity_type": entity["data"]["entityType"], "entity_name": entity["name"] }) + hierarchy = list() if self.shot_add_hierarchy: parent_template_patern = re.compile(r"\{([a-z]*?)\}") # fill the parents parts from presets @@ -129,9 +134,8 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): self.log.debug(f"Hierarchy: {hierarchy}") self.log.debug(f"parents: {parents}") + tasks_to_add = dict() if self.shot_add_tasks: - tasks_to_add = dict() - project_doc = legacy_io.find_one({"type": "project"}) project_tasks = project_doc["config"]["tasks"] for task_name, task_data in self.shot_add_tasks.items(): _task_data = deepcopy(task_data) @@ -150,9 +154,7 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): task_name, list(project_tasks.keys()))) - instance.data["tasks"] = tasks_to_add - else: - instance.data["tasks"] = dict() + instance.data["tasks"] = tasks_to_add # updating hierarchy data instance.data["anatomyData"].update({ diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py index 9d94bfdc91..82d7247b2b 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py @@ -4,7 +4,7 @@ import collections import pyblish.api from pprint import pformat -from openpype.pipeline import legacy_io +from openpype.client import get_assets class CollectMatchingAssetToInstance(pyblish.api.InstancePlugin): @@ -119,8 +119,9 @@ class CollectMatchingAssetToInstance(pyblish.api.InstancePlugin): def _asset_docs_by_parent_id(self, instance): # Query all assets for project and store them by parent's id to list + project_name = instance.context.data["projectEntity"]["name"] asset_docs_by_parent_id = collections.defaultdict(list) - for asset_doc in legacy_io.find({"type": "asset"}): + for asset_doc in get_assets(project_name): parent_id = asset_doc["data"]["visualParent"] asset_docs_by_parent_id[parent_id].append(asset_doc) return asset_docs_by_parent_id diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py index 4c761c7a4c..19ea1a4778 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py @@ -1,9 +1,7 @@ import pyblish.api -from openpype.pipeline import ( - PublishXmlValidationError, - legacy_io, -) +from openpype.client import get_assets +from openpype.pipeline import PublishXmlValidationError class ValidateTaskExistence(pyblish.api.ContextPlugin): @@ -20,15 +18,11 @@ class ValidateTaskExistence(pyblish.api.ContextPlugin): for instance in context: asset_names.add(instance.data["asset"]) - asset_docs = legacy_io.find( - { - "type": "asset", - "name": {"$in": list(asset_names)} - }, - { - "name": 1, - "data.tasks": 1 - } + project_name = context.data["projectEntity"]["name"] + asset_docs = get_assets( + project_name, + asset_names=asset_names, + fields=["name", "data.tasks"] ) tasks_by_asset_names = {} for asset_doc in asset_docs: From c01f65a9176413a388248986950162d21428da91 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 17:02:34 +0200 Subject: [PATCH 0597/1227] removed unused import --- .../standalonepublisher/plugins/publish/collect_hierarchy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py index 78e63f7ef0..7922ca7f31 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py @@ -3,7 +3,7 @@ import re from copy import deepcopy import pyblish.api -from openpype.client import get_asset_by_id, get_project +from openpype.client import get_asset_by_id class CollectHierarchyInstance(pyblish.api.ContextPlugin): From 010ee05eff630531a77964c7bfe39460cee9421c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 17:26:46 +0200 Subject: [PATCH 0598/1227] use query functions in blender --- openpype/hosts/blender/api/pipeline.py | 7 ++-- .../blender/plugins/publish/extract_layout.py | 39 +++++++------------ 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 5b81764644..93d81145bc 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -10,6 +10,7 @@ from . import ops import pyblish.api +from openpype.client import get_asset_by_name from openpype.pipeline import ( schema, legacy_io, @@ -83,11 +84,9 @@ def uninstall(): def set_start_end_frames(): + project_name = legacy_io.active_project() asset_name = legacy_io.Session["AVALON_ASSET"] - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) scene = bpy.context.scene diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index 8ecc78a2c6..2b3fa6a608 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -1,13 +1,11 @@ import os import json -from bson.objectid import ObjectId - import bpy import bpy_extras import bpy_extras.anim_utils -from openpype.pipeline import legacy_io +from openpype.client import get_representation_by_name from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY import openpype.api @@ -131,43 +129,32 @@ class ExtractLayout(openpype.api.Extractor): fbx_count = 0 + project_name = instance.context["projectEntity"]["name"] for asset in asset_group.children: metadata = asset.get(AVALON_PROPERTY) - parent = metadata["parent"] + version_id = metadata["parent"] family = metadata["family"] - self.log.debug("Parent: {}".format(parent)) + self.log.debug("Parent: {}".format(version_id)) # Get blend reference - blend = legacy_io.find_one( - { - "type": "representation", - "parent": ObjectId(parent), - "name": "blend" - }, - projection={"_id": True}) + blend = get_representation_by_name( + project_name, "blend", version_id, fields=["_id"] + ) blend_id = None if blend: blend_id = blend["_id"] # Get fbx reference - fbx = legacy_io.find_one( - { - "type": "representation", - "parent": ObjectId(parent), - "name": "fbx" - }, - projection={"_id": True}) + fbx = get_representation_by_name( + project_name, "fbx", version_id, fields=["_id"] + ) fbx_id = None if fbx: fbx_id = fbx["_id"] # Get abc reference - abc = legacy_io.find_one( - { - "type": "representation", - "parent": ObjectId(parent), - "name": "abc" - }, - projection={"_id": True}) + abc = get_representation_by_name( + project_name, "abc", version_id, fields=["_id"] + ) abc_id = None if abc: abc_id = abc["_id"] From 7c7c1486a401f2b260f013bf3c9dce0b3f123575 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 18:15:28 +0200 Subject: [PATCH 0599/1227] use own rest api endpoint instead of using private from avalon --- .../webserver_service/webpublish_routes.py | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index 70324fc39c..fcf9f10ffe 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -17,14 +17,24 @@ from openpype.lib.remote_publish import ( REPROCESS_STATUS ) from openpype.pipeline import AvalonMongoDB -from openpype_modules.avalon_apps.rest_api import _RestApiEndpoint from openpype.settings import get_project_settings +from openpype_modules.webserver.base_routes import RestApiEndpoint log = PypeLogger.get_logger("WebServer") +class ResourceRestApiEndpoint(RestApiEndpoint): + def __init__(self, resource): + self.resource = resource + super(ResourceRestApiEndpoint, self).__init__() + + @property + def dbcon(self): + return self.resource.dbcon + + class RestApiResource: """Resource carrying needed info and Avalon DB connection for publish.""" def __init__(self, server_manager, executable, upload_dir, @@ -67,7 +77,7 @@ class OpenPypeRestApiResource(RestApiResource): self.dbcon = mongo_client[database_name]["webpublishes"] -class ProjectsEndpoint(_RestApiEndpoint): +class ProjectsEndpoint(ResourceRestApiEndpoint): """Returns list of dict with project info (id, name).""" async def get(self) -> Response: output = [] @@ -84,7 +94,7 @@ class ProjectsEndpoint(_RestApiEndpoint): ) -class HiearchyEndpoint(_RestApiEndpoint): +class HiearchyEndpoint(ResourceRestApiEndpoint): """Returns dictionary with context tree from assets.""" async def get(self, project_name) -> Response: query_projection = { @@ -183,7 +193,7 @@ class TaskNode(Node): self["attributes"] = {} -class BatchPublishEndpoint(_RestApiEndpoint): +class BatchPublishEndpoint(ResourceRestApiEndpoint): """Triggers headless publishing of batch.""" async def post(self, request) -> Response: # Validate existence of openpype executable @@ -288,7 +298,7 @@ class BatchPublishEndpoint(_RestApiEndpoint): ) -class TaskPublishEndpoint(_RestApiEndpoint): +class TaskPublishEndpoint(ResourceRestApiEndpoint): """Prepared endpoint triggered after each task - for future development.""" async def post(self, request) -> Response: return Response( @@ -298,7 +308,7 @@ class TaskPublishEndpoint(_RestApiEndpoint): ) -class BatchStatusEndpoint(_RestApiEndpoint): +class BatchStatusEndpoint(ResourceRestApiEndpoint): """Returns dict with info for batch_id.""" async def get(self, batch_id) -> Response: output = self.dbcon.find_one({"batch_id": batch_id}) @@ -318,7 +328,7 @@ class BatchStatusEndpoint(_RestApiEndpoint): ) -class UserReportEndpoint(_RestApiEndpoint): +class UserReportEndpoint(ResourceRestApiEndpoint): """Returns list of dict with batch info for user (email address).""" async def get(self, user) -> Response: output = list(self.dbcon.find({"user": user}, @@ -338,7 +348,7 @@ class UserReportEndpoint(_RestApiEndpoint): ) -class ConfiguredExtensionsEndpoint(_RestApiEndpoint): +class ConfiguredExtensionsEndpoint(ResourceRestApiEndpoint): """Returns dict of extensions which have mapping to family. Returns: @@ -378,7 +388,7 @@ class ConfiguredExtensionsEndpoint(_RestApiEndpoint): ) -class BatchReprocessEndpoint(_RestApiEndpoint): +class BatchReprocessEndpoint(ResourceRestApiEndpoint): """Marks latest 'batch_id' for reprocessing, returns 404 if not found.""" async def post(self, batch_id) -> Response: batches = self.dbcon.find({"batch_id": batch_id, From 00c3554a5c1be7c07f48e6d018e275e2defd714f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 18:18:16 +0200 Subject: [PATCH 0600/1227] renamed 'OpenPypeRestApiResource' to 'WebpublishRestApiResource' --- .../webserver_service/webpublish_routes.py | 9 ++++----- .../webserver_service/webserver_cli.py | 15 +++++++-------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index fcf9f10ffe..d4aca1a797 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -20,9 +20,7 @@ from openpype.pipeline import AvalonMongoDB from openpype.settings import get_project_settings from openpype_modules.webserver.base_routes import RestApiEndpoint - - -log = PypeLogger.get_logger("WebServer") +log = PypeLogger.get_logger("WebpublishRoutes") class ResourceRestApiEndpoint(RestApiEndpoint): @@ -69,9 +67,10 @@ class RestApiResource: ).encode("utf-8") -class OpenPypeRestApiResource(RestApiResource): +class WebpublishRestApiResource: """Resource carrying OP DB connection for storing batch info into DB.""" - def __init__(self, ): + + def __init__(self): mongo_client = OpenPypeMongoConnection.get_mongo_client() database_name = os.environ["OPENPYPE_DATABASE_NAME"] self.dbcon = mongo_client[database_name]["webpublishes"] diff --git a/openpype/hosts/webpublisher/webserver_service/webserver_cli.py b/openpype/hosts/webpublisher/webserver_service/webserver_cli.py index 909ea38bc6..f18aac1168 100644 --- a/openpype/hosts/webpublisher/webserver_service/webserver_cli.py +++ b/openpype/hosts/webpublisher/webserver_service/webserver_cli.py @@ -10,7 +10,7 @@ from openpype.lib import PypeLogger from .webpublish_routes import ( RestApiResource, - OpenPypeRestApiResource, + WebpublishRestApiResource, HiearchyEndpoint, ProjectsEndpoint, ConfiguredExtensionsEndpoint, @@ -27,7 +27,7 @@ from openpype.lib.remote_publish import ( ) -log = PypeLogger().get_logger("webserver_gui") +log = PypeLogger.get_logger("webserver_gui") def run_webserver(*args, **kwargs): @@ -86,27 +86,26 @@ def run_webserver(*args, **kwargs): ) # reporting - openpype_resource = OpenPypeRestApiResource() - batch_status_endpoint = BatchStatusEndpoint(openpype_resource) + webpublish_resource = WebpublishRestApiResource() + batch_status_endpoint = BatchStatusEndpoint(webpublish_resource) server_manager.add_route( "GET", "/api/batch_status/{batch_id}", batch_status_endpoint.dispatch ) - user_status_endpoint = UserReportEndpoint(openpype_resource) + user_status_endpoint = UserReportEndpoint(webpublish_resource) server_manager.add_route( "GET", "/api/publishes/{user}", user_status_endpoint.dispatch ) - webpublisher_batch_reprocess_endpoint = \ - BatchReprocessEndpoint(openpype_resource) + batch_reprocess_endpoint = BatchReprocessEndpoint(webpublish_resource) server_manager.add_route( "POST", "/api/webpublish/reprocess/{batch_id}", - webpublisher_batch_reprocess_endpoint.dispatch + batch_reprocess_endpoint.dispatch ) server_manager.start_server() From 444b8aa67347121b44762120768fa6b84249eb3d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 18:19:35 +0200 Subject: [PATCH 0601/1227] use query functions from client --- .../webserver_service/webpublish_routes.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index d4aca1a797..cfbb0939b5 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -2,11 +2,15 @@ import os import json import datetime -from bson.objectid import ObjectId import collections -from aiohttp.web_response import Response import subprocess +from bson.objectid import ObjectId +from aiohttp.web_response import Response +from openpype.client import ( + get_projects, + get_assets, +) from openpype.lib import ( OpenPypeMongoConnection, PypeLogger, @@ -80,7 +84,7 @@ class ProjectsEndpoint(ResourceRestApiEndpoint): """Returns list of dict with project info (id, name).""" async def get(self) -> Response: output = [] - for project_doc in self.dbcon.projects(): + for project_doc in get_projects(): ret_val = { "id": project_doc["_id"], "name": project_doc["name"] @@ -105,10 +109,7 @@ class HiearchyEndpoint(ResourceRestApiEndpoint): "type": 1, } - asset_docs = self.dbcon.database[project_name].find( - {"type": "asset"}, - query_projection - ) + asset_docs = get_assets(project_name, field=query_projection.keys()) asset_docs_by_id = { asset_doc["_id"]: asset_doc for asset_doc in asset_docs From 840f1a431490f0dea02b8e5f9990cc8f5451e1f3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 18:20:15 +0200 Subject: [PATCH 0602/1227] separated endpoints to those with dbcon and without --- .../webserver_service/webpublish_routes.py | 36 ++++++++++++------- .../webserver_service/webserver_cli.py | 6 ++-- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index cfbb0939b5..b1041bf6cb 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -20,7 +20,6 @@ from openpype.lib.remote_publish import ( ERROR_STATUS, REPROCESS_STATUS ) -from openpype.pipeline import AvalonMongoDB from openpype.settings import get_project_settings from openpype_modules.webserver.base_routes import RestApiEndpoint @@ -32,6 +31,8 @@ class ResourceRestApiEndpoint(RestApiEndpoint): self.resource = resource super(ResourceRestApiEndpoint, self).__init__() + +class WebpublishApiEndpoint(ResourceRestApiEndpoint): @property def dbcon(self): return self.resource.dbcon @@ -49,9 +50,6 @@ class RestApiResource: studio_task_queue = collections.deque().dequeu self.studio_task_queue = studio_task_queue - self.dbcon = AvalonMongoDB() - self.dbcon.install() - @staticmethod def json_dump_handler(value): if isinstance(value, datetime.datetime): @@ -193,7 +191,7 @@ class TaskNode(Node): self["attributes"] = {} -class BatchPublishEndpoint(ResourceRestApiEndpoint): +class BatchPublishEndpoint(WebpublishApiEndpoint): """Triggers headless publishing of batch.""" async def post(self, request) -> Response: # Validate existence of openpype executable @@ -298,7 +296,7 @@ class BatchPublishEndpoint(ResourceRestApiEndpoint): ) -class TaskPublishEndpoint(ResourceRestApiEndpoint): +class TaskPublishEndpoint(WebpublishApiEndpoint): """Prepared endpoint triggered after each task - for future development.""" async def post(self, request) -> Response: return Response( @@ -308,8 +306,12 @@ class TaskPublishEndpoint(ResourceRestApiEndpoint): ) -class BatchStatusEndpoint(ResourceRestApiEndpoint): - """Returns dict with info for batch_id.""" +class BatchStatusEndpoint(WebpublishApiEndpoint): + """Returns dict with info for batch_id. + + Uses 'WebpublishRestApiResource'. + """ + async def get(self, batch_id) -> Response: output = self.dbcon.find_one({"batch_id": batch_id}) @@ -328,8 +330,12 @@ class BatchStatusEndpoint(ResourceRestApiEndpoint): ) -class UserReportEndpoint(ResourceRestApiEndpoint): - """Returns list of dict with batch info for user (email address).""" +class UserReportEndpoint(WebpublishApiEndpoint): + """Returns list of dict with batch info for user (email address). + + Uses 'WebpublishRestApiResource'. + """ + async def get(self, user) -> Response: output = list(self.dbcon.find({"user": user}, projection={"log": False})) @@ -348,7 +354,7 @@ class UserReportEndpoint(ResourceRestApiEndpoint): ) -class ConfiguredExtensionsEndpoint(ResourceRestApiEndpoint): +class ConfiguredExtensionsEndpoint(WebpublishApiEndpoint): """Returns dict of extensions which have mapping to family. Returns: @@ -388,8 +394,12 @@ class ConfiguredExtensionsEndpoint(ResourceRestApiEndpoint): ) -class BatchReprocessEndpoint(ResourceRestApiEndpoint): - """Marks latest 'batch_id' for reprocessing, returns 404 if not found.""" +class BatchReprocessEndpoint(WebpublishApiEndpoint): + """Marks latest 'batch_id' for reprocessing, returns 404 if not found. + + Uses 'WebpublishRestApiResource'. + """ + async def post(self, batch_id) -> Response: batches = self.dbcon.find({"batch_id": batch_id, "status": ERROR_STATUS}).sort("_id", -1) diff --git a/openpype/hosts/webpublisher/webserver_service/webserver_cli.py b/openpype/hosts/webpublisher/webserver_service/webserver_cli.py index f18aac1168..1ed8f22b2c 100644 --- a/openpype/hosts/webpublisher/webserver_service/webserver_cli.py +++ b/openpype/hosts/webpublisher/webserver_service/webserver_cli.py @@ -69,16 +69,14 @@ def run_webserver(*args, **kwargs): ) # triggers publish - webpublisher_task_publish_endpoint = \ - BatchPublishEndpoint(resource) + webpublisher_task_publish_endpoint = BatchPublishEndpoint(resource) server_manager.add_route( "POST", "/api/webpublish/batch", webpublisher_task_publish_endpoint.dispatch ) - webpublisher_batch_publish_endpoint = \ - TaskPublishEndpoint(resource) + webpublisher_batch_publish_endpoint = TaskPublishEndpoint(resource) server_manager.add_route( "POST", "/api/webpublish/task", From d9bde0c3c47012ae3a8ddf090822089344109708 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 18:38:43 +0200 Subject: [PATCH 0603/1227] implemented base classes for host implementation --- openpype/host/__init__.py | 13 ++ openpype/host/host.py | 308 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 321 insertions(+) create mode 100644 openpype/host/__init__.py create mode 100644 openpype/host/host.py diff --git a/openpype/host/__init__.py b/openpype/host/__init__.py new file mode 100644 index 0000000000..fcc9372a99 --- /dev/null +++ b/openpype/host/__init__.py @@ -0,0 +1,13 @@ +from .host import ( + HostImplementation, + IWorkfileHost, + ILoadHost, + INewPublisher, +) + +__all__ = ( + "HostImplementation", + "IWorkfileHost", + "ILoadHost", + "INewPublisher", +) diff --git a/openpype/host/host.py b/openpype/host/host.py new file mode 100644 index 0000000000..d311f752cd --- /dev/null +++ b/openpype/host/host.py @@ -0,0 +1,308 @@ +from abc import ABCMeta, abstractproperty, abstractmethod +import six + + +@six.add_metaclass(ABCMeta) +class HostImplementation(object): + """Base of host implementation class. + + Host is pipeline implementation of DCC application. This class should help + to identify what must/should/can be implemented for specific functionality. + + Compared to 'avalon' concept: + What was before considered as functions in host implementation folder. The + host implementation should primarily care about adding ability of creation + (mark subsets to be published) and optionaly about referencing published + representations as containers. + + Host may need extend some functionality like working with workfiles + or loading. Not all host implementations may allow that for those purposes + can be logic extended with implementing functions for the purpose. There + are prepared interfaces to be able identify what must be implemented to + be able use that functionality. + - current statement is that it is not required to inherit from interfaces + but all of the methods are validated (only their existence!) + + # Installation of host before (avalon concept): + ```python + from openpype.pipeline import install_host + import openpype.hosts.maya.api as host + + install_host(host) + ``` + + # Installation of host now: + ```python + from openpype.pipeline import install_host + from openpype.hosts.maya.api import MayaHost + + host = MayaHost() + install_host(host) + ``` + + # TODOs + - move content of 'install_host' as method of this class + - register host object + - install legacy_io + - install global plugin paths + - store registered plugin paths to this object + - handle current context (project, asset, task) + - this must be done in many separated steps + - have it's object of host tools instead of using globals + + This implementation will probably change over time when more + functionality and responsibility will be added. + """ + + def __init__(self): + """Initialization of host. + + Register DCC callbacks, host specific plugin paths, targets etc. + (Part of what 'install' did in 'avalon' concept.) + + NOTE: + At this moment global "installation" must happen before host + installation. Because of this current limitation it is recommended + to implement 'install' method which is triggered after global + 'install'. + """ + + pass + + @abstractproperty + def name(self): + """Host implementation name.""" + + pass + + def get_current_context(self): + """Get current context information. + + This method should be used to get current context of host. Usage of + this method can be crutial for host implementations in DCCs where + can be opened multiple workfiles at one moment and change of context + can't be catched properly. + + Default implementation returns values from 'legacy_io.Session'. + + Returns: + dict: Context with 3 keys 'project_name', 'asset_name' and + 'task_name'. All of them can be 'None'. + """ + + from openpype.pipeline import legacy_io + + if legacy_io.is_installed(): + legacy_io.install() + + return { + "project_name": legacy_io.Session["AVALON_PROJECT"], + "asset_name": legacy_io.Session["AVALON_ASSET"], + "task_name": legacy_io.Session["AVALON_TASK"] + } + + def get_context_title(self): + """Context title shown for UI purposes. + + Should return current context title if possible. + + NOTE: This method is used only for UI purposes so it is possible to + return some logical title for contextless cases. + + Is not meant for "Context menu" label. + + Returns: + str: Context title. + None: Default title is used based on UI implementation. + """ + + # Use current context to fill the context title + current_context = self.get_current_context() + project_name = current_context["project_name"] + asset_name = current_context["asset_name"] + task_name = current_context["task_name"] + items = [] + if project_name: + items.append(project_name) + if asset_name: + items.append(asset_name) + if task_name: + items.append(task_name) + if items: + return "/".join(items) + return None + + +class ILoadHost: + """Implementation requirements to be able use reference of representations. + + The load plugins can do referencing even without implementation of methods + here, but switch and removement of containers would not be possible. + + QUESTIONS + - Is list container dependency of host or load plugins? + - Should this be directly in HostImplementation? + - how to find out if referencing is available? + - do we need to know that? + """ + + @abstractmethod + def ls(self): + """Retreive referenced containers from scene. + + This can be implemented in hosts where referencing can be used. + + TODO: + Rename function to something more self explanatory. + Suggestion: 'get_referenced_containers' + + Returns: + list[dict]: Information about loaded containers. + """ + return [] + + +@six.add_metaclass(ABCMeta) +class IWorkfileHost: + """Implementation requirements to be able use workfile utils and tool. + + This interface just provides what is needed to implement in host + implementation to support workfiles workflow, but does not have necessarily + to inherit from this interface. + """ + + @abstractmethod + def file_extensions(self): + """Extensions that can be used as save. + + QUESTION: This could potentially use 'HostDefinition'. + + TODO: + Rename to 'get_workfile_extensions'. + """ + + return [] + + @abstractmethod + def save_file(self, dst_path=None): + """Save currently opened scene. + + TODO: + Rename to 'save_current_workfile'. + + Args: + dst_path (str): Where the current scene should be saved. Or use + current path if 'None' is passed. + """ + + pass + + @abstractmethod + def open_file(self, filepath): + """Open passed filepath in the host. + + TODO: + Rename to 'open_workfile'. + + Args: + filepath (str): Path to workfile. + """ + + pass + + @abstractmethod + def current_file(self): + """Retreive path to current opened file. + + TODO: + Rename to 'get_current_workfile'. + + Returns: + str: Path to file which is currently opened. + None: If nothing is opened. + """ + + return None + + def has_unsaved_changes(self): + """Currently opened scene is saved. + + Not all hosts can know if current scene is saved because the API of + DCC does not support it. + + Returns: + bool: True if scene is saved and False if has unsaved + modifications. + None: Can't tell if workfiles has modifications. + """ + + return None + + def work_root(self, session): + """Modify workdir per host. + + WARNING: + We must handle this modification with more sofisticated way because + this can't be called out of DCC so opening of last workfile + (calculated before DCC is launched) is complicated. Also breaking + defined work template is not a good idea. + Only place where it's really used and can make sense is Maya. There + workspace.mel can modify subfolders where to look for maya files. + + Default implementation keeps workdir untouched. + + Args: + session (dict): Session context data. + + Returns: + str: Path to new workdir. + """ + + return session["AVALON_WORKDIR"] + + +class INewPublisher: + """Functions related to new creation system in new publisher. + + New publisher is not storing information only about each created instance + but also some global data. At this moment are data related only to context + publish plugins but that can extend in future. + + HostImplementation does not have to inherit from this interface just have + to imlement mentioned all listed methods. + """ + + @abstractmethod + def get_context_data(self): + """Get global data related to creation-publishing from workfile. + + These data are not related to any created instance but to whole + publishing context. Not saving/returning them will cause that each + reset of publishing resets all values to default ones. + + Context data can contain information about enabled/disabled publish + plugins or other values that can be filled by artist. + + Returns: + dict: Context data stored using 'update_context_data'. + """ + + pass + + @abstractmethod + def update_context_data(self, data, changes): + """Store global context data to workfile. + + Called when some values in context data has changed. + + Without storing the values in a way that 'get_context_data' would + return them will each reset of publishing cause loose of filled values + by artist. Best practice is to store values into workfile, if possible. + + Args: + data (dict): New data as are. + changes (dict): Only data that has been changed. Each value has + tuple with '(, )' value. + """ + + pass From 626e6f9b7d734b072d99b0820c18e58a2880a8fe Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 18:39:41 +0200 Subject: [PATCH 0604/1227] moved import of settings to top --- openpype/hosts/maya/api/pipeline.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index d9276ddf4a..5905251e93 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -8,6 +8,7 @@ import maya.api.OpenMaya as om import pyblish.api +from openpype.settings import get_project_settings import openpype.hosts.maya from openpype.tools.utils import host_tools from openpype.lib import ( @@ -46,8 +47,6 @@ self._events = {} def install(): - from openpype.settings import get_project_settings - project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) # process path mapping dirmap_processor = MayaDirmap("maya", project_settings) From 7f106cad33602b125be17220f2124050c546350e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 18:51:30 +0200 Subject: [PATCH 0605/1227] added maintained_selection to HostImplementation --- openpype/host/host.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openpype/host/host.py b/openpype/host/host.py index d311f752cd..4851ee59bf 100644 --- a/openpype/host/host.py +++ b/openpype/host/host.py @@ -1,3 +1,4 @@ +import contextlib from abc import ABCMeta, abstractproperty, abstractmethod import six @@ -132,6 +133,19 @@ class HostImplementation(object): return "/".join(items) return None + @contextlib.contextmanager + def maintained_selection(self): + """Some functionlity will happen but selection should stay same. + + This is DCC specific. Some may not allow to implement this ability + that is reason why default implementation is empty context manager. + """ + + try: + yield + finally: + pass + class ILoadHost: """Implementation requirements to be able use reference of representations. From 7ac1b6cadc60fab4d32207f6e5fd640f1d492cc8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 19:18:01 +0200 Subject: [PATCH 0606/1227] added logger to HostImplementation --- openpype/host/host.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openpype/host/host.py b/openpype/host/host.py index 4851ee59bf..27b22e4850 100644 --- a/openpype/host/host.py +++ b/openpype/host/host.py @@ -1,3 +1,4 @@ +import logging import contextlib from abc import ABCMeta, abstractproperty, abstractmethod import six @@ -55,6 +56,8 @@ class HostImplementation(object): functionality and responsibility will be added. """ + _log = None + def __init__(self): """Initialization of host. @@ -70,6 +73,12 @@ class HostImplementation(object): pass + @property + def log(self): + if self._log is None: + self._log = logging.getLogger(self.__class__.__name__) + return self._log + @abstractproperty def name(self): """Host implementation name.""" From 0ff8e2bcc686f1dbae795450d9635e0be7c3475d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 19:30:39 +0200 Subject: [PATCH 0607/1227] added validation methods to interfaces --- openpype/host/host.py | 112 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/openpype/host/host.py b/openpype/host/host.py index 27b22e4850..302c181598 100644 --- a/openpype/host/host.py +++ b/openpype/host/host.py @@ -4,6 +4,17 @@ from abc import ABCMeta, abstractproperty, abstractmethod import six +class MissingMethodsError(ValueError): + def __init__(self, host, missing_methods): + joined_missing = ", ".join( + ['"{}"'.format(item) for item in missing_methods] + ) + message = ( + "Host \"{}\" miss methods {}".format(host.name, joined_missing) + ) + super(MissingMethodsError, self).__init__(message) + + @six.add_metaclass(ABCMeta) class HostImplementation(object): """Base of host implementation class. @@ -169,6 +180,36 @@ class ILoadHost: - do we need to know that? """ + @staticmethod + def get_missing_load_methods(host): + """Look for missing methods on host implementation. + + Method is used for validation of implemented functions related to + loading. Checks only existence of methods. + + Args: + list[str]: Missing method implementations for loading workflow. + """ + + required = ["ls"] + missing = [] + for name in required: + if not hasattr(host, name): + missing.append(name) + return missing + + @staticmethod + def validate_load_methods(host): + """Validate implemented methods of host for load workflow. + + Raises: + MissingMethodsError: If there are missing methods on host + implementation. + """ + missing = ILoadHost.get_missing_load_methods(host) + if missing: + raise MissingMethodsError(host, missing) + @abstractmethod def ls(self): """Retreive referenced containers from scene. @@ -194,6 +235,43 @@ class IWorkfileHost: to inherit from this interface. """ + @staticmethod + def get_missing_workfile_methods(host): + """Look for missing methods on host implementation. + + Method is used for validation of implemented functions related to + workfiles. Checks only existence of methods. + + Returns: + list[str]: Missing method implementations for workfiles workflow. + """ + + required = [ + "open_file", + "save_file", + "current_file", + "has_unsaved_changes", + "file_extensions", + "work_root", + ] + missing = [] + for name in required: + if not hasattr(host, name): + missing.append(name) + return missing + + @staticmethod + def validate_workfile_methods(host): + """Validate implemented methods of host for workfiles workflow. + + Raises: + MissingMethodsError: If there are missing methods on host + implementation. + """ + missing = IWorkfileHost.get_missing_workfile_methods(host) + if missing: + raise MissingMethodsError(host, missing) + @abstractmethod def file_extensions(self): """Extensions that can be used as save. @@ -295,6 +373,40 @@ class INewPublisher: to imlement mentioned all listed methods. """ + @staticmethod + def get_missing_publish_methods(host): + """Look for missing methods on host implementation. + + Method is used for validation of implemented functions related to + new publish creation. Checks only existence of methods. + + Args: + list[str]: Missing method implementations for new publsher + workflow. + """ + + required = [ + "get_context_data", + "update_context_data", + ] + missing = [] + for name in required: + if not hasattr(host, name): + missing.append(name) + return missing + + @staticmethod + def validate_publish_methods(host): + """Validate implemented methods of host for create-publish workflow. + + Raises: + MissingMethodsError: If there are missing methods on host + implementation. + """ + missing = INewPublisher.get_missing_publish_methods(host) + if missing: + raise MissingMethodsError(host, missing) + @abstractmethod def get_context_data(self): """Get global data related to creation-publishing from workfile. From b81dbf9ee411a2a02d46da9f144c78eca4ddcf21 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 19:31:37 +0200 Subject: [PATCH 0608/1227] use validations from interfaces --- openpype/pipeline/create/context.py | 15 +++++---------- openpype/tools/utils/host_tools.py | 15 ++++++++++----- openpype/tools/workfiles/__init__.py | 2 -- openpype/tools/workfiles/app.py | 28 ++-------------------------- 4 files changed, 17 insertions(+), 43 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 2f1922c103..4bfa68c9d4 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -6,6 +6,7 @@ import inspect from uuid import uuid4 from contextlib import contextmanager +from openpype.host import INewPublisher from openpype.pipeline import legacy_io from openpype.pipeline.mongodb import ( AvalonMongoDB, @@ -651,12 +652,6 @@ class CreateContext: discover_publish_plugins(bool): Discover publish plugins during reset phase. """ - # Methods required in host implementaion to be able create instances - # or change context data. - required_methods = ( - "get_context_data", - "update_context_data" - ) def __init__( self, host, dbcon=None, headless=False, reset=True, @@ -738,10 +733,10 @@ class CreateContext: Args: host(ModuleType): Host implementaion. """ - missing = set() - for attr_name in cls.required_methods: - if not hasattr(host, attr_name): - missing.add(attr_name) + + missing = set( + INewPublisher.get_missing_publish_methods(host) + ) return missing @property diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 9dbbe25fda..ae23e4d089 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -6,7 +6,7 @@ use singleton approach with global functions (using helper anyway). import os import pyblish.api - +from openpype.host import IWorkfileHost, ILoadHost from openpype.pipeline import ( registered_host, legacy_io, @@ -49,12 +49,11 @@ class HostToolsHelper: def get_workfiles_tool(self, parent): """Create, cache and return workfiles tool window.""" if self._workfiles_tool is None: - from openpype.tools.workfiles.app import ( - Window, validate_host_requirements - ) + from openpype.tools.workfiles.app import Window + # Host validation host = registered_host() - validate_host_requirements(host) + IWorkfileHost.validate_workfile_methods(host) workfiles_window = Window(parent=parent) self._workfiles_tool = workfiles_window @@ -92,6 +91,9 @@ class HostToolsHelper: if self._loader_tool is None: from openpype.tools.loader import LoaderWindow + host = registered_host() + ILoadHost.validate_load_methods(host) + loader_window = LoaderWindow(parent=parent or self._parent) self._loader_tool = loader_window @@ -164,6 +166,9 @@ class HostToolsHelper: if self._scene_inventory_tool is None: from openpype.tools.sceneinventory import SceneInventoryWindow + host = registered_host() + ILoadHost.validate_load_methods(host) + scene_inventory_window = SceneInventoryWindow( parent=parent or self._parent ) diff --git a/openpype/tools/workfiles/__init__.py b/openpype/tools/workfiles/__init__.py index 5fbc71797d..205fd44838 100644 --- a/openpype/tools/workfiles/__init__.py +++ b/openpype/tools/workfiles/__init__.py @@ -1,12 +1,10 @@ from .window import Window from .app import ( show, - validate_host_requirements, ) __all__ = [ "Window", "show", - "validate_host_requirements", ] diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index 352847ede8..2d0d551faf 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -1,6 +1,7 @@ import sys import logging +from openpype.host import IWorkfileHost from openpype.pipeline import ( registered_host, legacy_io, @@ -14,31 +15,6 @@ module = sys.modules[__name__] module.window = None -def validate_host_requirements(host): - if host is None: - raise RuntimeError("No registered host.") - - # Verify the host has implemented the api for Work Files - required = [ - "open_file", - "save_file", - "current_file", - "has_unsaved_changes", - "work_root", - "file_extensions", - ] - missing = [] - for name in required: - if not hasattr(host, name): - missing.append(name) - if missing: - raise RuntimeError( - "Host is missing required Work Files interfaces: " - "%s (host: %s)" % (", ".join(missing), host) - ) - return True - - def show(root=None, debug=False, parent=None, use_context=True, save=True): """Show Work Files GUI""" # todo: remove `root` argument to show() @@ -50,7 +26,7 @@ def show(root=None, debug=False, parent=None, use_context=True, save=True): pass host = registered_host() - validate_host_requirements(host) + IWorkfileHost.validate_workfile_methods(host) if debug: legacy_io.Session["AVALON_ASSET"] = "Mock" From 3aa88814a1f6efbfb0d92ed6119c44e122b2fef4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 19:31:52 +0200 Subject: [PATCH 0609/1227] added is_installed function to legacy_io --- openpype/pipeline/legacy_io.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/legacy_io.py b/openpype/pipeline/legacy_io.py index c8e7e79600..d586756671 100644 --- a/openpype/pipeline/legacy_io.py +++ b/openpype/pipeline/legacy_io.py @@ -18,9 +18,13 @@ _database = database = None log = logging.getLogger(__name__) +def is_installed(): + return module._is_installed + + def install(): """Establish a persistent connection to the database""" - if module._is_installed: + if is_installed(): return session = session_data_from_environment(context_keys=True) @@ -55,7 +59,7 @@ def uninstall(): def requires_install(func): @functools.wraps(func) def decorated(*args, **kwargs): - if not module._is_installed: + if not is_installed(): install() return func(*args, **kwargs) return decorated From 97e6aa4120b24df7738c1635bcfeb14f1136663d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 19:32:31 +0200 Subject: [PATCH 0610/1227] use HostImplementation class in Maya host --- openpype/hosts/maya/api/__init__.py | 4 +- openpype/hosts/maya/api/pipeline.py | 186 ++++++++++++++--------- openpype/hosts/maya/startup/userSetup.py | 5 +- 3 files changed, 120 insertions(+), 75 deletions(-) diff --git a/openpype/hosts/maya/api/__init__.py b/openpype/hosts/maya/api/__init__.py index 5d76bf0f04..6c28c59580 100644 --- a/openpype/hosts/maya/api/__init__.py +++ b/openpype/hosts/maya/api/__init__.py @@ -5,11 +5,11 @@ Anything that isn't defined here is INTERNAL and unreliable for external use. """ from .pipeline import ( - install, uninstall, ls, containerise, + MayaHostImplementation, ) from .plugin import ( Creator, @@ -40,11 +40,11 @@ from .lib import ( __all__ = [ - "install", "uninstall", "ls", "containerise", + "MayaHostImplementation", "Creator", "Loader", diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 5905251e93..4924185f0a 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -1,7 +1,7 @@ import os -import sys import errno import logging +import contextlib from maya import utils, cmds, OpenMaya import maya.api.OpenMaya as om @@ -9,6 +9,7 @@ import maya.api.OpenMaya as om import pyblish.api from openpype.settings import get_project_settings +from openpype.host import HostImplementation import openpype.hosts.maya from openpype.tools.utils import host_tools from openpype.lib import ( @@ -29,6 +30,14 @@ from openpype.pipeline import ( ) from openpype.hosts.maya.lib import copy_workspace_mel from . import menu, lib +from .workio import ( + open_file, + save_file, + file_extensions, + has_unsaved_changes, + work_root, + current_file +) log = logging.getLogger("openpype.hosts.maya") @@ -41,47 +50,121 @@ INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") AVALON_CONTAINERS = ":AVALON_CONTAINERS" -self = sys.modules[__name__] -self._ignore_lock = False -self._events = {} +class MayaHostImplementation(HostImplementation): + name = "maya" -def install(): - project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) - # process path mapping - dirmap_processor = MayaDirmap("maya", project_settings) - dirmap_processor.process_dirmap() + def __init__(self): + super(MayaHostImplementation, self).__init__() + self._events = {} - pyblish.api.register_plugin_path(PUBLISH_PATH) - pyblish.api.register_host("mayabatch") - pyblish.api.register_host("mayapy") - pyblish.api.register_host("maya") + def install(self): + project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) + # process path mapping + dirmap_processor = MayaDirmap("maya", project_settings) + dirmap_processor.process_dirmap() - register_loader_plugin_path(LOAD_PATH) - register_creator_plugin_path(CREATE_PATH) - register_inventory_action_path(INVENTORY_PATH) - log.info(PUBLISH_PATH) + pyblish.api.register_plugin_path(PUBLISH_PATH) + pyblish.api.register_host("mayabatch") + pyblish.api.register_host("mayapy") + pyblish.api.register_host("maya") - log.info("Installing callbacks ... ") - register_event_callback("init", on_init) + register_loader_plugin_path(LOAD_PATH) + register_creator_plugin_path(CREATE_PATH) + register_inventory_action_path(INVENTORY_PATH) + self.log.info(PUBLISH_PATH) - if lib.IS_HEADLESS: - log.info(("Running in headless mode, skipping Maya " - "save/open/new callback installation..")) + self.log.info("Installing callbacks ... ") + register_event_callback("init", on_init) - return + if lib.IS_HEADLESS: + self.log.info(( + "Running in headless mode, skipping Maya save/open/new" + " callback installation.." + )) - _set_project() - _register_callbacks() + return - menu.install() + _set_project() + self._register_callbacks() - register_event_callback("save", on_save) - register_event_callback("open", on_open) - register_event_callback("new", on_new) - register_event_callback("before.save", on_before_save) - register_event_callback("taskChanged", on_task_changed) - register_event_callback("workfile.save.before", before_workfile_save) + menu.install() + + register_event_callback("save", on_save) + register_event_callback("open", on_open) + register_event_callback("new", on_new) + register_event_callback("before.save", on_before_save) + register_event_callback("taskChanged", on_task_changed) + register_event_callback("workfile.save.before", before_workfile_save) + + def open_file(self, filepath): + return open_file(filepath) + + def save_file(self, filepath=None): + return save_file(filepath) + + def work_root(self, session): + return work_root(session) + + def current_file(self): + return current_file() + + def has_unsaved_changes(self): + return has_unsaved_changes() + + def file_extensions(self): + return file_extensions() + + def ls(self): + return ls() + + @contextlib.contextmanager + def maintained_selection(self): + with lib.maintained_selection(): + yield + + def _register_callbacks(self): + for handler, event in self._events.copy().items(): + if event is None: + continue + + try: + OpenMaya.MMessage.removeCallback(event) + self._events[handler] = None + except RuntimeError as exc: + self.log.info(exc) + + self._events[_on_scene_save] = OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kBeforeSave, _on_scene_save + ) + + self._events[_before_scene_save] = ( + OpenMaya.MSceneMessage.addCheckCallback( + OpenMaya.MSceneMessage.kBeforeSaveCheck, + _before_scene_save + ) + ) + + self._events[_on_scene_new] = OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kAfterNew, _on_scene_new + ) + + self._events[_on_maya_initialized] = ( + OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kMayaInitialized, + _on_maya_initialized + ) + ) + + self._events[_on_scene_open] = OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kAfterOpen, _on_scene_open + ) + + self.log.info("Installed event handler _on_scene_save..") + self.log.info("Installed event handler _before_scene_save..") + self.log.info("Installed event handler _on_scene_new..") + self.log.info("Installed event handler _on_maya_initialized..") + self.log.info("Installed event handler _on_scene_open..") def _set_project(): @@ -105,44 +188,6 @@ def _set_project(): cmds.workspace(workdir, openWorkspace=True) -def _register_callbacks(): - for handler, event in self._events.copy().items(): - if event is None: - continue - - try: - OpenMaya.MMessage.removeCallback(event) - self._events[handler] = None - except RuntimeError as e: - log.info(e) - - self._events[_on_scene_save] = OpenMaya.MSceneMessage.addCallback( - OpenMaya.MSceneMessage.kBeforeSave, _on_scene_save - ) - - self._events[_before_scene_save] = OpenMaya.MSceneMessage.addCheckCallback( - OpenMaya.MSceneMessage.kBeforeSaveCheck, _before_scene_save - ) - - self._events[_on_scene_new] = OpenMaya.MSceneMessage.addCallback( - OpenMaya.MSceneMessage.kAfterNew, _on_scene_new - ) - - self._events[_on_maya_initialized] = OpenMaya.MSceneMessage.addCallback( - OpenMaya.MSceneMessage.kMayaInitialized, _on_maya_initialized - ) - - self._events[_on_scene_open] = OpenMaya.MSceneMessage.addCallback( - OpenMaya.MSceneMessage.kAfterOpen, _on_scene_open - ) - - log.info("Installed event handler _on_scene_save..") - log.info("Installed event handler _before_scene_save..") - log.info("Installed event handler _on_scene_new..") - log.info("Installed event handler _on_maya_initialized..") - log.info("Installed event handler _on_scene_open..") - - def _on_maya_initialized(*args): emit_event("init") @@ -474,7 +519,6 @@ def on_task_changed(): workdir = legacy_io.Session["AVALON_WORKDIR"] if os.path.exists(workdir): log.info("Updating Maya workspace for task change to %s", workdir) - _set_project() # Set Maya fileDialog's start-dir to /scenes diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index a3ab483add..daaf305612 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -1,10 +1,11 @@ import os from openpype.api import get_project_settings from openpype.pipeline import install_host -from openpype.hosts.maya import api +from openpype.hosts.maya.api import MayaHostImplementation from maya import cmds -install_host(api) +host = MayaHostImplementation() +install_host(host) print("starting OpenPype usersetup") From df16589120736417862139467ed647c7e8286f11 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 11:07:18 +0200 Subject: [PATCH 0611/1227] added interfaces to inheritance --- openpype/hosts/maya/api/pipeline.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 4924185f0a..f68bed9338 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -9,7 +9,7 @@ import maya.api.OpenMaya as om import pyblish.api from openpype.settings import get_project_settings -from openpype.host import HostImplementation +from openpype.host import HostImplementation, IWorkfileHost, ILoadHost import openpype.hosts.maya from openpype.tools.utils import host_tools from openpype.lib import ( @@ -51,12 +51,12 @@ INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") AVALON_CONTAINERS = ":AVALON_CONTAINERS" -class MayaHostImplementation(HostImplementation): +class MayaHostImplementation(HostImplementation, IWorkfileHost, ILoadHost): name = "maya" def __init__(self): super(MayaHostImplementation, self).__init__() - self._events = {} + self._op_events = {} def install(self): project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) @@ -124,39 +124,39 @@ class MayaHostImplementation(HostImplementation): yield def _register_callbacks(self): - for handler, event in self._events.copy().items(): + for handler, event in self._op_events.copy().items(): if event is None: continue try: OpenMaya.MMessage.removeCallback(event) - self._events[handler] = None + self._op_events[handler] = None except RuntimeError as exc: self.log.info(exc) - self._events[_on_scene_save] = OpenMaya.MSceneMessage.addCallback( + self._op_events[_on_scene_save] = OpenMaya.MSceneMessage.addCallback( OpenMaya.MSceneMessage.kBeforeSave, _on_scene_save ) - self._events[_before_scene_save] = ( + self._op_events[_before_scene_save] = ( OpenMaya.MSceneMessage.addCheckCallback( OpenMaya.MSceneMessage.kBeforeSaveCheck, _before_scene_save ) ) - self._events[_on_scene_new] = OpenMaya.MSceneMessage.addCallback( + self._op_events[_on_scene_new] = OpenMaya.MSceneMessage.addCallback( OpenMaya.MSceneMessage.kAfterNew, _on_scene_new ) - self._events[_on_maya_initialized] = ( + self._op_events[_on_maya_initialized] = ( OpenMaya.MSceneMessage.addCallback( OpenMaya.MSceneMessage.kMayaInitialized, _on_maya_initialized ) ) - self._events[_on_scene_open] = OpenMaya.MSceneMessage.addCallback( + self._op_events[_on_scene_open] = OpenMaya.MSceneMessage.addCallback( OpenMaya.MSceneMessage.kAfterOpen, _on_scene_open ) From c177b60221aac07ecc304db832c8f2199c691d9f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 14 Jun 2022 11:11:51 +0200 Subject: [PATCH 0612/1227] Fix - added OPENPYPE_MONGO to filter Submit job has filter of allowed environment values, it missed OPENPYPE_MONGO. --- .../modules/deadline/plugins/publish/submit_publish_job.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 78ab935e42..39a2aa2761 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -128,7 +128,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "OPENPYPE_LOG_NO_COLORS", "OPENPYPE_USERNAME", "OPENPYPE_RENDER_JOB", - "OPENPYPE_PUBLISH_JOB" + "OPENPYPE_PUBLISH_JOB", + "OPENPYPE_MONGO" ] # custom deadline attributes From fae33600f60555edab8d6187e4754eb946b1e8f4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 14 Jun 2022 11:14:06 +0200 Subject: [PATCH 0613/1227] Nuke: adding empty representations to every instance also clearing obsolete code --- .../plugins/publish/precollect_instances.py | 8 ++--- .../plugins/publish/precollect_workfile.py | 36 ++++++++----------- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_instances.py b/openpype/hosts/nuke/plugins/publish/precollect_instances.py index 1a8fa3e6ad..8bf7280cea 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_instances.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_instances.py @@ -151,15 +151,11 @@ class PreCollectNukeInstances(pyblish.api.ContextPlugin): "resolutionWidth": resolution_width, "resolutionHeight": resolution_height, "pixelAspect": pixel_aspect, - "review": review + "review": review, + "representations": [] }) self.log.info("collected instance: {}".format(instance.data)) instances.append(instance) - # create instances in context data if not are created yet - if not context.data.get("instances"): - context.data["instances"] = list() - - context.data["instances"].extend(instances) self.log.debug("context: {}".format(context)) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_workfile.py b/openpype/hosts/nuke/plugins/publish/precollect_workfile.py index a2d1c80628..7349a8f424 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_workfile.py @@ -17,7 +17,7 @@ class CollectWorkfile(pyblish.api.ContextPlugin): label = "Pre-collect Workfile" hosts = ['nuke'] - def process(self, context): + def process(self, context): # sourcery skip: avoid-builtin-shadow root = nuke.root() current_file = os.path.normpath(nuke.root().name()) @@ -74,20 +74,6 @@ class CollectWorkfile(pyblish.api.ContextPlugin): } context.data.update(script_data) - # creating instance data - instance.data.update({ - "subset": subset, - "label": base_name, - "name": base_name, - "publish": root.knob('publish').value(), - "family": family, - "families": [family], - "representations": list() - }) - - # adding basic script data - instance.data.update(script_data) - # creating representation representation = { 'name': 'nk', @@ -96,12 +82,18 @@ class CollectWorkfile(pyblish.api.ContextPlugin): "stagingDir": staging_dir, } - instance.data["representations"].append(representation) + # creating instance data + instance.data.update({ + "subset": subset, + "label": base_name, + "name": base_name, + "publish": root.knob('publish').value(), + "family": family, + "families": [family], + "representations": [representation] + }) + + # adding basic script data + instance.data.update(script_data) self.log.info('Publishing script version') - - # create instances in context data if not are created yet - if not context.data.get("instances"): - context.data["instances"] = list() - - context.data["instances"].append(instance) From 6e2802cf9280fb00562b8adf25c7114a937783e4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 12:28:56 +0200 Subject: [PATCH 0614/1227] fix project entity access --- openpype/hosts/blender/plugins/publish/extract_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index 2b3fa6a608..75d9cf440d 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -129,7 +129,7 @@ class ExtractLayout(openpype.api.Extractor): fbx_count = 0 - project_name = instance.context["projectEntity"]["name"] + project_name = instance.context.data["projectEntity"]["name"] for asset in asset_group.children: metadata = asset.get(AVALON_PROPERTY) From 73fb899e905efa2f7cfe9a54dcf8f4ccd06e6d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 14 Jun 2022 13:46:45 +0200 Subject: [PATCH 0615/1227] :construction: wip on validation of parent relationships --- .../publish/validate_camera_contents.py | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_camera_contents.py b/openpype/hosts/maya/plugins/publish/validate_camera_contents.py index 38cf65f006..eb93245f93 100644 --- a/openpype/hosts/maya/plugins/publish/validate_camera_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_camera_contents.py @@ -33,7 +33,7 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): invalid = [] cameras = cmds.ls(shapes, type='camera', long=True) if len(cameras) != 1: - cls.log.warning("Camera instance must have a single camera. " + cls.log.error("Camera instance must have a single camera. " "Found {0}: {1}".format(len(cameras), cameras)) invalid.extend(cameras) @@ -51,16 +51,32 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): raise RuntimeError("No cameras found in empty instance.") if not cls.validate_shapes: - return + cls.log.info("not validating shapes in the content") + + for member in members: + parents = cmds.ls(member, long=True)[0].split("|")[1:-1] + cls.log.info(parents) + parents_long_named = [ + "|".join(parents[:i]) for i in range(1, 1 + len(parents)) + ] + cls.log.info(parents_long_named) + if cameras[0] in parents_long_named: + cls.log.error( + "{} is parented under camera {}".format(member, cameras[0])) + invalid.extend(member) + return invalid + # non-camera shapes valid_shapes = cmds.ls(shapes, type=('camera', 'locator'), long=True) shapes = set(shapes) - set(valid_shapes) if shapes: shapes = list(shapes) - cls.log.warning("Camera instance should only contain camera " + cls.log.error("Camera instance should only contain camera " "shapes. Found: {0}".format(shapes)) invalid.extend(shapes) + + invalid = list(set(invalid)) return invalid From d4e78369f019a8ebc46dd3702d8efc676d4f4473 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 14:03:12 +0200 Subject: [PATCH 0616/1227] added new function to retrieve only archived assets --- openpype/client/__init__.py | 2 + openpype/client/entities.py | 83 +++++++++++++++++++++++++++++++++++-- 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 16b1dcf321..e3b4ef5132 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -5,6 +5,7 @@ from .entities import ( get_asset_by_id, get_asset_by_name, get_assets, + get_archived_assets, get_asset_ids_with_subsets, get_subset_by_id, @@ -41,6 +42,7 @@ __all__ = ( "get_asset_by_id", "get_asset_by_name", "get_assets", + "get_archived_assets", "get_asset_ids_with_subsets", "get_subset_by_id", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index a56288c1e8..0827e12288 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -135,8 +135,16 @@ def get_asset_by_name(project_name, asset_name, fields=None): return conn.find_one(query_filter, _prepare_fields(fields)) -def get_assets( - project_name, asset_ids=None, asset_names=None, archived=False, fields=None +# NOTE this could be just public function? +# - any better variable name instead of 'standard'? +# - same approach can be used for rest of types +def _get_assets( + project_name, + asset_ids=None, + asset_names=None, + standard=True, + archived=False, + fields=None ): """Assets for specified project by passed filters. @@ -149,6 +157,8 @@ def get_assets( project_name (str): Name of project where to look for queried entities. asset_ids (list[str|ObjectId]): Asset ids that should be found. asset_names (list[str]): Name assets that should be found. + standard (bool): Query standart assets (type 'asset'). + archived (bool): Query archived assets (type 'archived_asset'). fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -157,10 +167,15 @@ def get_assets( passed filters. """ - asset_types = ["asset"] + asset_types = [] + if standard: + asset_types.append("asset") if archived: asset_types.append("archived_asset") + if not asset_types: + return [] + if len(asset_types) == 1: query_filter = {"type": asset_types[0]} else: @@ -182,6 +197,68 @@ def get_assets( return conn.find(query_filter, _prepare_fields(fields)) +def get_assets( + project_name, + asset_ids=None, + asset_names=None, + archived=False, + fields=None +): + """Assets for specified project by passed filters. + + Passed filters (ids and names) are always combined so all conditions must + match. + + To receive all assets from project just keep filters empty. + + Args: + project_name (str): Name of project where to look for queried entities. + asset_ids (list[str|ObjectId]): Asset ids that should be found. + asset_names (list[str]): Name assets that should be found. + archived (bool): Add also archived assets. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Query cursor as iterable which returns asset documents matching + passed filters. + """ + + return _get_assets( + project_name, asset_ids, asset_names, True, archived, fields + ) + + +def get_archived_assets( + project_name, + asset_ids=None, + asset_names=None, + fields=None +): + """Archived assets for specified project by passed filters. + + Passed filters (ids and names) are always combined so all conditions must + match. + + To receive all archived assets from project just keep filters empty. + + Args: + project_name (str): Name of project where to look for queried entities. + asset_ids (list[str|ObjectId]): Asset ids that should be found. + asset_names (list[str]): Name assets that should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Query cursor as iterable which returns asset documents matching + passed filters. + """ + + return _get_assets( + project_name, asset_ids, asset_names, False, True, fields + ) + + def get_asset_ids_with_subsets(project_name, asset_ids=None): """Find out which assets have existing subsets. From a57318823310729b281ced80390d8dad8f786fb5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 14:03:40 +0200 Subject: [PATCH 0617/1227] use query functions in sync to avalon event --- .../event_sync_to_avalon.py | 53 ++++++++----------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py index b5f199b3e4..a4e791aaf0 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py @@ -12,6 +12,12 @@ from pymongo import UpdateOne import arrow import ftrack_api +from openpype.client import ( + get_project, + get_assets, + get_archived_assets, + get_asset_ids_with_subsets +) from openpype.pipeline import AvalonMongoDB, schema from openpype_modules.ftrack.lib import ( @@ -149,12 +155,11 @@ class SyncToAvalonEvent(BaseEvent): @property def avalon_entities(self): if self._avalon_ents is None: + project_name = self.cur_project["full_name"] self.dbcon.install() - self.dbcon.Session["AVALON_PROJECT"] = ( - self.cur_project["full_name"] - ) - avalon_project = self.dbcon.find_one({"type": "project"}) - avalon_entities = list(self.dbcon.find({"type": "asset"})) + self.dbcon.Session["AVALON_PROJECT"] = project_name + avalon_project = get_project(project_name) + avalon_entities = list(get_assets(project_name)) self._avalon_ents = (avalon_project, avalon_entities) return self._avalon_ents @@ -284,28 +289,21 @@ class SyncToAvalonEvent(BaseEvent): self._avalon_ents_by_ftrack_id[ftrack_id] = doc @property - def avalon_subsets_by_parents(self): - if self._avalon_subsets_by_parents is None: - self._avalon_subsets_by_parents = collections.defaultdict(list) - self.dbcon.install() - self.dbcon.Session["AVALON_PROJECT"] = ( - self.cur_project["full_name"] + def avalon_asset_ids_with_subsets(self): + if self._avalon_asset_ids_with_subsets is None: + project_name = self.cur_project["full_name"] + self._avalon_asset_ids_with_subsets = get_asset_ids_with_subsets( + project_name ) - for subset in self.dbcon.find({"type": "subset"}): - self._avalon_subsets_by_parents[subset["parent"]].append( - subset - ) - return self._avalon_subsets_by_parents + + return self._avalon_asset_ids_with_subsets @property def avalon_archived_by_id(self): if self._avalon_archived_by_id is None: self._avalon_archived_by_id = {} - self.dbcon.install() - self.dbcon.Session["AVALON_PROJECT"] = ( - self.cur_project["full_name"] - ) - for asset in self.dbcon.find({"type": "archived_asset"}): + project_name = self.cur_project["full_name"] + for asset in get_archived_assets(project_name): self._avalon_archived_by_id[asset["_id"]] = asset return self._avalon_archived_by_id @@ -327,7 +325,7 @@ class SyncToAvalonEvent(BaseEvent): avalon_project, avalon_entities = self.avalon_entities self._changeability_by_mongo_id[avalon_project["_id"]] = False self._bubble_changeability( - list(self.avalon_subsets_by_parents.keys()) + list(self.avalon_asset_ids_with_subsets) ) return self._changeability_by_mongo_id @@ -449,14 +447,9 @@ class SyncToAvalonEvent(BaseEvent): if not entity: # if entity is not found then it is subset without parent if entity_id in unchangeable_ids: - _subset_ids = [ - str(sub["_id"]) for sub in - self.avalon_subsets_by_parents[entity_id] - ] - joined_subset_ids = "| ".join(_subset_ids) self.log.warning(( - "Parent <{}> for subsets <{}> does not exist" - ).format(str(entity_id), joined_subset_ids)) + "Parent <{}> with subsets does not exist" + ).format(str(entity_id))) else: self.log.warning(( "In avalon are entities without valid parents that" @@ -483,7 +476,7 @@ class SyncToAvalonEvent(BaseEvent): self._avalon_ents_by_parent_id = None self._avalon_ents_by_ftrack_id = None self._avalon_ents_by_name = None - self._avalon_subsets_by_parents = None + self._avalon_asset_ids_with_subsets = None self._changeability_by_mongo_id = None self._avalon_archived_by_id = None self._avalon_archived_by_name = None From 177be6880f7bcf1de63a39ed07cd26a742345236 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 14:34:31 +0200 Subject: [PATCH 0618/1227] added versions argument to get_versions --- openpype/client/entities.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 0827e12288..6e1f4c556b 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -505,6 +505,7 @@ def _get_versions( project_name, subset_ids=None, version_ids=None, + versions=None, standard=True, hero=False, fields=None @@ -535,6 +536,16 @@ def _get_versions( return [] query_filter["_id"] = {"$in": version_ids} + if versions is not None: + versions = list(versions) + if not versions: + return [] + + if len(versions) == 1: + query_filter["name"] = versions[0] + else: + query_filter["name"] = {"$in": versions} + conn = _get_project_connection(project_name) return conn.find(query_filter, _prepare_fields(fields)) @@ -544,6 +555,7 @@ def get_versions( project_name, version_ids=None, subset_ids=None, + versions=None, hero=False, fields=None ): @@ -557,6 +569,8 @@ def get_versions( Filter ignored if 'None' is passed. subset_ids (list[str]): Subset ids that will be queried. Filter ignored if 'None' is passed. + versions (list[int]): Version names (as integers). + Filter ignored if 'None' is passed. hero (bool): Look also for hero versions. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -569,6 +583,7 @@ def get_versions( project_name, subset_ids, version_ids, + versions, standard=True, hero=hero, fields=fields From e6ddd6797d0ee9689d57b8bfd1e585dc55babffc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 15:30:29 +0200 Subject: [PATCH 0619/1227] use query functions in prepare project --- .../action_prepare_project.py | 14 ++++---------- .../event_handlers_user/action_prepare_project.py | 14 ++++---------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py index 975e49cb28..361aa98d16 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py @@ -1,8 +1,8 @@ import json +from openpype.client import get_project from openpype.api import ProjectSettings from openpype.lib import create_project -from openpype.pipeline import AvalonMongoDB from openpype.settings import SaveWarningExc from openpype_modules.ftrack.lib import ( @@ -363,12 +363,8 @@ class PrepareProjectServer(ServerAction): project_name = project_entity["full_name"] # Try to find project document - dbcon = AvalonMongoDB() - dbcon.install() - dbcon.Session["AVALON_PROJECT"] = project_name - project_doc = dbcon.find_one({ - "type": "project" - }) + project_doc = get_project(project_name) + # Create project if is not available # - creation is required to be able set project anatomy and attributes if not project_doc: @@ -376,9 +372,7 @@ class PrepareProjectServer(ServerAction): self.log.info("Creating project \"{} [{}]\"".format( project_name, project_code )) - create_project(project_name, project_code, dbcon=dbcon) - - dbcon.uninstall() + create_project(project_name, project_code) project_settings = ProjectSettings(project_name) project_anatomy_settings = project_settings["project_anatomy"] diff --git a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py index 0b14e7aa2b..e9dc11de9f 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py @@ -1,8 +1,8 @@ import json +from openpype.client import get_project from openpype.api import ProjectSettings from openpype.lib import create_project -from openpype.pipeline import AvalonMongoDB from openpype.settings import SaveWarningExc from openpype_modules.ftrack.lib import ( @@ -389,12 +389,8 @@ class PrepareProjectLocal(BaseAction): project_name = project_entity["full_name"] # Try to find project document - dbcon = AvalonMongoDB() - dbcon.install() - dbcon.Session["AVALON_PROJECT"] = project_name - project_doc = dbcon.find_one({ - "type": "project" - }) + project_doc = get_project(project_name) + # Create project if is not available # - creation is required to be able set project anatomy and attributes if not project_doc: @@ -402,9 +398,7 @@ class PrepareProjectLocal(BaseAction): self.log.info("Creating project \"{} [{}]\"".format( project_name, project_code )) - create_project(project_name, project_code, dbcon=dbcon) - - dbcon.uninstall() + create_project(project_name, project_code) project_settings = ProjectSettings(project_name) project_anatomy_settings = project_settings["project_anatomy"] From 77d496d88bc1a4c7e0aea436512d695ee72c92bd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:05:51 +0200 Subject: [PATCH 0620/1227] use query in applications action --- .../ftrack/event_handlers_user/action_applications.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_applications.py b/openpype/modules/ftrack/event_handlers_user/action_applications.py index b25bc1b5cb..102f04c956 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_applications.py +++ b/openpype/modules/ftrack/event_handlers_user/action_applications.py @@ -1,5 +1,6 @@ import os +from openpype.client import get_project from openpype_modules.ftrack.lib import BaseAction from openpype.lib.applications import ( ApplicationManager, @@ -7,7 +8,6 @@ from openpype.lib.applications import ( ApplictionExecutableNotFound, CUSTOM_LAUNCH_APP_GROUPS ) -from openpype.pipeline import AvalonMongoDB class AppplicationsAction(BaseAction): @@ -25,7 +25,6 @@ class AppplicationsAction(BaseAction): super(AppplicationsAction, self).__init__(*args, **kwargs) self.application_manager = ApplicationManager() - self.dbcon = AvalonMongoDB() @property def discover_identifier(self): @@ -110,12 +109,7 @@ class AppplicationsAction(BaseAction): if avalon_project_doc is None: ft_project = self.get_project_from_entity(entity) project_name = ft_project["full_name"] - if not self.dbcon.is_installed(): - self.dbcon.install() - self.dbcon.Session["AVALON_PROJECT"] = project_name - avalon_project_doc = self.dbcon.find_one({ - "type": "project" - }) or False + avalon_project_doc = get_project(project_name) or False event["data"]["avalon_project_doc"] = avalon_project_doc if not avalon_project_doc: From 7945ca0011c54eaed02655c64481c8dc2dd6bd6d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:06:12 +0200 Subject: [PATCH 0621/1227] use query functions in delete old versions action --- .../action_delete_old_versions.py | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py b/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py index a0bf6622e9..3400c509ab 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py @@ -5,7 +5,12 @@ import uuid import clique from pymongo import UpdateOne - +from openpype.client import ( + get_assets, + get_subsets, + get_versions, + get_representations +) from openpype.api import Anatomy from openpype.lib import StringTemplate, TemplateUnsolved from openpype.pipeline import AvalonMongoDB @@ -198,10 +203,9 @@ class DeleteOldVersions(BaseAction): self.log.debug("Project is set to {}".format(project_name)) # Get Assets from avalon database - assets = list(self.dbcon.find({ - "type": "asset", - "name": {"$in": avalon_asset_names} - })) + assets = list( + get_assets(project_name, asset_names=avalon_asset_names) + ) asset_id_to_name_map = { asset["_id"]: asset["name"] for asset in assets } @@ -210,10 +214,9 @@ class DeleteOldVersions(BaseAction): self.log.debug("Collected assets ({})".format(len(asset_ids))) # Get Subsets - subsets = list(self.dbcon.find({ - "type": "subset", - "parent": {"$in": asset_ids} - })) + subsets = list( + get_subsets(project_name, asset_ids=asset_ids) + ) subsets_by_id = {} subset_ids = [] for subset in subsets: @@ -230,10 +233,9 @@ class DeleteOldVersions(BaseAction): self.log.debug("Collected subsets ({})".format(len(subset_ids))) # Get Versions - versions = list(self.dbcon.find({ - "type": "version", - "parent": {"$in": subset_ids} - })) + versions = list( + get_versions(project_name, subset_ids=subset_ids) + ) versions_by_parent = collections.defaultdict(list) for ent in versions: @@ -295,10 +297,9 @@ class DeleteOldVersions(BaseAction): "message": msg } - repres = list(self.dbcon.find({ - "type": "representation", - "parent": {"$in": version_ids} - })) + repres = list( + get_representations(project_name, version_ids=version_ids) + ) self.log.debug( "Collected representations to remove ({})".format(len(repres)) From af0b97fafe625ad1094627435c22e33e5881a2d8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:07:47 +0200 Subject: [PATCH 0622/1227] use query functions in delivery action --- .../event_handlers_user/action_delivery.py | 152 ++++++++---------- 1 file changed, 66 insertions(+), 86 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_delivery.py b/openpype/modules/ftrack/event_handlers_user/action_delivery.py index 86d88ef7cc..47f2853820 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delivery.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delivery.py @@ -3,8 +3,13 @@ import copy import json import collections -from bson.objectid import ObjectId - +from openpype.client import ( + get_project, + get_assets, + get_subsets, + get_versions, + get_representations +) from openpype.api import Anatomy, config from openpype_modules.ftrack.lib import BaseAction, statics_icon from openpype_modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY @@ -18,11 +23,9 @@ from openpype.lib.delivery import ( process_single_file, process_sequence ) -from openpype.pipeline import AvalonMongoDB class Delivery(BaseAction): - identifier = "delivery.action" label = "Delivery" description = "Deliver data to client" @@ -30,11 +33,6 @@ class Delivery(BaseAction): icon = statics_icon("ftrack", "action_icons", "Delivery.svg") settings_key = "delivery_action" - def __init__(self, *args, **kwargs): - self.dbcon = AvalonMongoDB() - - super(Delivery, self).__init__(*args, **kwargs) - def discover(self, session, entities, event): is_valid = False for entity in entities: @@ -57,9 +55,7 @@ class Delivery(BaseAction): project_entity = self.get_project_from_entity(entities[0]) project_name = project_entity["full_name"] - self.dbcon.install() - self.dbcon.Session["AVALON_PROJECT"] = project_name - project_doc = self.dbcon.find_one({"type": "project"}, {"name": True}) + project_doc = get_project(project_name, fields=["name"]) if not project_doc: return { "success": False, @@ -68,8 +64,7 @@ class Delivery(BaseAction): ).format(project_name) } - repre_names = self._get_repre_names(session, entities) - self.dbcon.uninstall() + repre_names = self._get_repre_names(project_name, session, entities) items.append({ "type": "hidden", @@ -198,17 +193,21 @@ class Delivery(BaseAction): "title": title } - def _get_repre_names(self, session, entities): - version_ids = self._get_interest_version_ids(session, entities) + def _get_repre_names(self, project_name, session, entities): + version_ids = self._get_interest_version_ids( + project_name, session, entities + ) if not version_ids: return [] - repre_docs = self.dbcon.find({ - "type": "representation", - "parent": {"$in": version_ids} - }) - return list(sorted(repre_docs.distinct("name"))) + repre_docs = get_representations( + project_name, + version_ids=version_ids, + fields=["name"] + ) + repre_names = {repre_doc["name"] for repre_doc in repre_docs} + return list(sorted(repre_names)) - def _get_interest_version_ids(self, session, entities): + def _get_interest_version_ids(self, project_name, session, entities): # Extract AssetVersion entities asset_versions = self._extract_asset_versions(session, entities) # Prepare Asset ids @@ -235,14 +234,18 @@ class Delivery(BaseAction): subset_names.add(asset["name"]) version_nums.add(asset_version["version"]) - asset_docs_by_ftrack_id = self._get_asset_docs(session, parent_ids) + asset_docs_by_ftrack_id = self._get_asset_docs( + project_name, session, parent_ids + ) subset_docs = self._get_subset_docs( + project_name, asset_docs_by_ftrack_id, subset_names, asset_versions, assets_by_id ) version_docs = self._get_version_docs( + project_name, asset_docs_by_ftrack_id, subset_docs, version_nums, @@ -290,6 +293,7 @@ class Delivery(BaseAction): def _get_version_docs( self, + project_name, asset_docs_by_ftrack_id, subset_docs, version_nums, @@ -300,11 +304,11 @@ class Delivery(BaseAction): subset_doc["_id"]: subset_doc for subset_doc in subset_docs } - version_docs = list(self.dbcon.find({ - "type": "version", - "parent": {"$in": list(subset_docs_by_id.keys())}, - "name": {"$in": list(version_nums)} - })) + version_docs = list(get_versions( + project_name, + subset_ids=subset_docs_by_id.keys(), + versions=version_nums + )) version_docs_by_parent_id = collections.defaultdict(dict) for version_doc in version_docs: subset_doc = subset_docs_by_id[version_doc["parent"]] @@ -345,6 +349,7 @@ class Delivery(BaseAction): def _get_subset_docs( self, + project_name, asset_docs_by_ftrack_id, subset_names, asset_versions, @@ -354,11 +359,11 @@ class Delivery(BaseAction): asset_doc["_id"] for asset_doc in asset_docs_by_ftrack_id.values() ] - subset_docs = list(self.dbcon.find({ - "type": "subset", - "parent": {"$in": asset_doc_ids}, - "name": {"$in": list(subset_names)} - })) + subset_docs = list(get_subsets( + project_name, + asset_ids=asset_doc_ids, + subset_names=subset_names + )) subset_docs_by_parent_id = collections.defaultdict(dict) for subset_doc in subset_docs: asset_id = subset_doc["parent"] @@ -385,15 +390,21 @@ class Delivery(BaseAction): filtered_subsets.append(subset_doc) return filtered_subsets - def _get_asset_docs(self, session, parent_ids): - asset_docs = list(self.dbcon.find({ - "type": "asset", - "data.ftrackId": {"$in": list(parent_ids)} - })) + def _get_asset_docs(self, project_name, session, parent_ids): + asset_docs = list(get_assets( + project_name, fields=["_id", "name", "data.ftrackId"] + )) + asset_docs_by_id = {} + asset_docs_by_name = {} asset_docs_by_ftrack_id = {} for asset_doc in asset_docs: + asset_id = str(asset_doc["_id"]) + asset_name = asset_doc["name"] ftrack_id = asset_doc["data"].get("ftrackId") + + asset_docs_by_id[asset_id] = asset_doc + asset_docs_by_name[asset_name] = asset_doc if ftrack_id: asset_docs_by_ftrack_id[ftrack_id] = asset_doc @@ -406,15 +417,15 @@ class Delivery(BaseAction): avalon_mongo_id_values = query_custom_attributes( session, [attr_def["id"]], parent_ids, True ) - entity_ids_by_mongo_id = { - ObjectId(item["value"]): item["entity_id"] - for item in avalon_mongo_id_values - if item["value"] - } - missing_ids = set(parent_ids) - for entity_id in set(entity_ids_by_mongo_id.values()): - if entity_id in missing_ids: + for item in avalon_mongo_id_values: + if not item["value"]: + continue + asset_id = item["value"] + entity_id = item["entity_id"] + asset_doc = asset_docs_by_id.get(asset_id) + if asset_doc: + asset_docs_by_ftrack_id[entity_id] = asset_doc missing_ids.remove(entity_id) entity_ids_by_name = {} @@ -427,36 +438,10 @@ class Delivery(BaseAction): for entity in not_found_entities } - expressions = [] - if entity_ids_by_mongo_id: - expression = { - "type": "asset", - "_id": {"$in": list(entity_ids_by_mongo_id.keys())} - } - expressions.append(expression) - - if entity_ids_by_name: - expression = { - "type": "asset", - "name": {"$in": list(entity_ids_by_name.keys())} - } - expressions.append(expression) - - if expressions: - if len(expressions) == 1: - filter = expressions[0] - else: - filter = {"$or": expressions} - - asset_docs = self.dbcon.find(filter) - for asset_doc in asset_docs: - if asset_doc["_id"] in entity_ids_by_mongo_id: - entity_id = entity_ids_by_mongo_id[asset_doc["_id"]] - asset_docs_by_ftrack_id[entity_id] = asset_doc - - elif asset_doc["name"] in entity_ids_by_name: - entity_id = entity_ids_by_name[asset_doc["name"]] - asset_docs_by_ftrack_id[entity_id] = asset_doc + for asset_name, entity_id in entity_ids_by_name.items(): + asset_doc = asset_docs_by_name.get(asset_name) + if asset_doc: + asset_docs_by_ftrack_id[entity_id] = asset_doc return asset_docs_by_ftrack_id @@ -490,7 +475,6 @@ class Delivery(BaseAction): session.commit() try: - self.dbcon.install() report = self.real_launch(session, entities, event) except Exception as exc: @@ -516,7 +500,6 @@ class Delivery(BaseAction): else: job["status"] = "failed" session.commit() - self.dbcon.uninstall() if not report["success"]: self.show_interface( @@ -558,16 +541,13 @@ class Delivery(BaseAction): if not os.path.exists(location_path): os.makedirs(location_path) - self.dbcon.Session["AVALON_PROJECT"] = project_name - self.log.debug("Collecting representations to process.") version_ids = self._get_interest_version_ids(session, entities) - repres_to_deliver = list(self.dbcon.find({ - "type": "representation", - "parent": {"$in": version_ids}, - "name": {"$in": repre_names} - })) - + repres_to_deliver = list(get_representations( + project_name, + representation_names=repre_names, + version_ids=version_ids + )) anatomy = Anatomy(project_name) format_dict = get_format_dict(anatomy, location_path) From ac48f50fdffb2e1650ce3681679b3ad822b3889c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:07:58 +0200 Subject: [PATCH 0623/1227] use query action in fill workfile attribute --- .../event_handlers_user/action_fill_workfile_attr.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py b/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py index c7237a1150..d30c41a749 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py +++ b/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py @@ -7,6 +7,10 @@ import datetime import ftrack_api +from openpype.client import ( + get_project, + get_assets, +) from openpype.api import get_project_settings from openpype.lib import ( get_workfile_template_key, @@ -14,7 +18,6 @@ from openpype.lib import ( Anatomy, StringTemplate, ) -from openpype.pipeline import AvalonMongoDB from openpype_modules.ftrack.lib import BaseAction, statics_icon from openpype_modules.ftrack.lib.avalon_sync import create_chunks @@ -248,10 +251,8 @@ class FillWorkfileAttributeAction(BaseAction): # Find matchin asset documents and map them by ftrack task entities # - result stored to 'asset_docs_with_task_entities' is list with # tuple `(asset document, [task entitis, ...])` - dbcon = AvalonMongoDB() - dbcon.Session["AVALON_PROJECT"] = project_name # Quety all asset documents - asset_docs = list(dbcon.find({"type": "asset"})) + asset_docs = list(get_assets(project_name)) job_entity["data"] = json.dumps({ "description": "(1/3) Asset documents queried." }) @@ -276,7 +277,7 @@ class FillWorkfileAttributeAction(BaseAction): # Keep placeholders in the template unfilled host_name = "{app}" extension = "{ext}" - project_doc = dbcon.find_one({"type": "project"}) + project_doc = get_project(project_name) project_settings = get_project_settings(project_name) anatomy = Anatomy(project_name) templates_by_key = {} From dd07bbe9b248cf0096e318c25fa73365217222fb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:31:32 +0200 Subject: [PATCH 0624/1227] use query functions in event user assiment --- .../event_user_assigment.py | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/event_user_assigment.py b/openpype/modules/ftrack/event_handlers_server/event_user_assigment.py index 593fc5e596..82b79e986b 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_user_assigment.py +++ b/openpype/modules/ftrack/event_handlers_server/event_user_assigment.py @@ -1,11 +1,9 @@ import re import subprocess +from openpype.client import get_asset_by_id, get_asset_by_name from openpype_modules.ftrack.lib import BaseEvent from openpype_modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY -from openpype.pipeline import AvalonMongoDB - -from bson.objectid import ObjectId from openpype.api import Anatomy, get_project_settings @@ -36,8 +34,6 @@ class UserAssigmentEvent(BaseEvent): 3) path to publish files of task user was (de)assigned to """ - db_con = AvalonMongoDB() - def error(self, *err): for e in err: self.log.error(e) @@ -101,26 +97,16 @@ class UserAssigmentEvent(BaseEvent): :rtype: dict """ parent = task['parent'] - self.db_con.install() - self.db_con.Session['AVALON_PROJECT'] = task['project']['full_name'] - + project_name = task["project"]["full_name"] avalon_entity = None parent_id = parent['custom_attributes'].get(CUST_ATTR_ID_KEY) if parent_id: - parent_id = ObjectId(parent_id) - avalon_entity = self.db_con.find_one({ - '_id': parent_id, - 'type': 'asset' - }) + avalon_entity = get_asset_by_id(project_name, parent_id) if not avalon_entity: - avalon_entity = self.db_con.find_one({ - 'type': 'asset', - 'name': parent['name'] - }) + avalon_entity = get_asset_by_name(project_name, parent["name"]) if not avalon_entity: - self.db_con.uninstall() msg = 'Entity "{}" not found in avalon database'.format( parent['name'] ) @@ -129,7 +115,6 @@ class UserAssigmentEvent(BaseEvent): 'success': False, 'message': msg } - self.db_con.uninstall() return avalon_entity def _get_hierarchy(self, asset): From 951a23484e98c1831063c81306ed9f298f957ce5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:31:51 +0200 Subject: [PATCH 0625/1227] use query functions in RV action --- .../ftrack/event_handlers_user/action_rv.py | 71 +++++++++++-------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_rv.py b/openpype/modules/ftrack/event_handlers_user/action_rv.py index 040ca75582..2480ea7f95 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_rv.py +++ b/openpype/modules/ftrack/event_handlers_user/action_rv.py @@ -5,9 +5,16 @@ import json import ftrack_api +from openpype.client import ( + get_asset_by_name, + get_subset_by_name, + get_version_by_name, + get_representation_by_name +) +from openpype.api import Anatomy from openpype.pipeline import ( get_representation_path, - legacy_io, + AvalonMongoDB, ) from openpype_modules.ftrack.lib import BaseAction, statics_icon @@ -255,9 +262,10 @@ class RVAction(BaseAction): "Component", list(event["data"]["values"].values())[0] )["version"]["asset"]["parent"]["link"][0] project = session.get(link["type"], link["id"]) - os.environ["AVALON_PROJECT"] = project["name"] - legacy_io.Session["AVALON_PROJECT"] = project["name"] - legacy_io.install() + project_name = project["full_name"] + dbcon = AvalonMongoDB() + dbcon.Session["AVALON_PROJECT"] = project_name + anatomy = Anatomy(project_name) location = ftrack_api.Session().pick_location() @@ -281,37 +289,38 @@ class RVAction(BaseAction): if online_source: continue - asset = legacy_io.find_one({"type": "asset", "name": parent_name}) - subset = legacy_io.find_one( - { - "type": "subset", - "name": component["version"]["asset"]["name"], - "parent": asset["_id"] - } + subset_name = component["version"]["asset"]["name"] + version_name = component["version"]["version"] + representation_name = component["file_type"][1:] + + asset_doc = get_asset_by_name( + project_name, parent_name, fields=["_id"] ) - version = legacy_io.find_one( - { - "type": "version", - "name": component["version"]["version"], - "parent": subset["_id"] - } + subset_doc = get_subset_by_name( + project_name, + subset_name=subset_name, + asset_id=asset_doc["_id"] ) - representation = legacy_io.find_one( - { - "type": "representation", - "parent": version["_id"], - "name": component["file_type"][1:] - } + version_doc = get_version_by_name( + project_name, + version=version_name, + subset_id=subset_doc["_id"] ) - if representation is None: - representation = legacy_io.find_one( - { - "type": "representation", - "parent": version["_id"], - "name": "preview" - } + repre_doc = get_representation_by_name( + project_name, + version_id=version_doc["_id"], + representation_name=representation_name + ) + if not repre_doc: + repre_doc = get_representation_by_name( + project_name, + version_id=version_doc["_id"], + representation_name="preview" ) - paths.append(get_representation_path(representation)) + + paths.append(get_representation_path( + repre_doc, root=anatomy.roots, dbcon=dbcon + )) return paths From 38f058fca0ffaeef4cbe888433e3593fb282b048 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:32:34 +0200 Subject: [PATCH 0626/1227] use query functions in delete asset action --- .../action_delete_asset.py | 97 +++++++++---------- 1 file changed, 45 insertions(+), 52 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py index ee5c3d0d97..b5b4ec5ac5 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py @@ -4,6 +4,7 @@ from datetime import datetime from bson.objectid import ObjectId +from openpype.client import get_assets, get_subsets from openpype.pipeline import AvalonMongoDB from openpype_modules.ftrack.lib import BaseAction, statics_icon from openpype_modules.ftrack.lib.avalon_sync import create_chunks @@ -91,11 +92,9 @@ class DeleteAssetSubset(BaseAction): continue ftrack_id = entity.get("entityId") - if not ftrack_id: - continue - - ftrack_ids.append(ftrack_id) - + if ftrack_id: + ftrack_ids.append(ftrack_id) + if project_in_selection: msg = "It is not possible to use this action on project entity." self.show_message(event, msg, True) @@ -120,48 +119,49 @@ class DeleteAssetSubset(BaseAction): "message": "Invalid selection for this action (Bug)" } - if entities[0].entity_type.lower() == "project": - project = entities[0] - else: - project = entities[0]["project"] - + project = self.get_project_from_entity(entities[0], session) project_name = project["full_name"] self.dbcon.Session["AVALON_PROJECT"] = project_name - selected_av_entities = list(self.dbcon.find({ - "type": "asset", - "data.ftrackId": {"$in": ftrack_ids} - })) + asset_docs = list(get_assets( + project_name, + fields=["_id", "name", "data.ftrackId", "data.parents"] + )) + selected_av_entities = [] + found_ftrack_ids = set() + asset_docs_by_name = collections.defaultdict(list) + for asset_doc in asset_docs: + ftrack_id = asset_doc["data"].get("ftrackId") + if ftrack_id: + found_ftrack_ids.add(ftrack_id) + selected_av_entities.append(asset_doc) + + asset_name = asset_doc["name"] + asset_docs_by_name[asset_name].append(asset_doc) + found_without_ftrack_id = {} - if len(selected_av_entities) != len(ftrack_ids): - found_ftrack_ids = [ - ent["data"]["ftrackId"] for ent in selected_av_entities - ] - for ftrack_id, entity in entity_mapping.items(): - if ftrack_id in found_ftrack_ids: + for ftrack_id, entity in entity_mapping.items(): + if ftrack_id in found_ftrack_ids: + continue + + av_ents_by_name = asset_docs_by_name[entity["name"]] + if not av_ents_by_name: + continue + + ent_path_items = [ent["name"] for ent in entity["link"]] + parents = ent_path_items[1:len(ent_path_items)-1:] + # TODO we should say to user that + # few of them are missing in avalon + for av_ent in av_ents_by_name: + if av_ent["data"]["parents"] != parents: continue - av_ents_by_name = list(self.dbcon.find({ - "type": "asset", - "name": entity["name"] - })) - if not av_ents_by_name: - continue - - ent_path_items = [ent["name"] for ent in entity["link"]] - parents = ent_path_items[1:len(ent_path_items)-1:] - # TODO we should say to user that - # few of them are missing in avalon - for av_ent in av_ents_by_name: - if av_ent["data"]["parents"] != parents: - continue - - # TODO we should say to user that found entity - # with same name does not match same ftrack id? - if "ftrackId" not in av_ent["data"]: - selected_av_entities.append(av_ent) - found_without_ftrack_id[str(av_ent["_id"])] = ftrack_id - break + # TODO we should say to user that found entity + # with same name does not match same ftrack id? + if "ftrackId" not in av_ent["data"]: + selected_av_entities.append(av_ent) + found_without_ftrack_id[str(av_ent["_id"])] = ftrack_id + break if not selected_av_entities: return { @@ -206,10 +206,7 @@ class DeleteAssetSubset(BaseAction): items.append(id_item) asset_ids = [ent["_id"] for ent in selected_av_entities] - subsets_for_selection = self.dbcon.find({ - "type": "subset", - "parent": {"$in": asset_ids} - }) + subsets_for_selection = get_subsets(project_name, asset_ids=asset_ids) asset_ending = "" if len(selected_av_entities) > 1: @@ -459,13 +456,9 @@ class DeleteAssetSubset(BaseAction): if len(assets_to_delete) > 0: map_av_ftrack_id = spec_data["without_ftrack_id"] # Prepare data when deleting whole avalon asset - avalon_assets = self.dbcon.find( - {"type": "asset"}, - { - "_id": 1, - "data.visualParent": 1, - "data.ftrackId": 1 - } + avalon_assets = get_assets( + project_name, + fields=["_id", "data.visualParent", "data.ftrackId"] ) avalon_assets_by_parent = collections.defaultdict(list) for asset in avalon_assets: From 46e22241bd8d7d61b427f9d0f4fff930cc7549a2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:32:55 +0200 Subject: [PATCH 0627/1227] use query functions in store thumbnails to avalon --- .../action_store_thumbnails_to_avalon.py | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py b/openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py index 62fdfa2bdd..d655dddcaf 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py @@ -5,6 +5,14 @@ import requests from bson.objectid import ObjectId +from openpype.client import ( + get_project, + get_asset_by_id, + get_assets, + get_subset_by_name, + get_version_by_name, + get_representations +) from openpype_modules.ftrack.lib import BaseAction, statics_icon from openpype.api import Anatomy from openpype.pipeline import AvalonMongoDB @@ -385,7 +393,7 @@ class StoreThumbnailsToAvalon(BaseAction): db_con.Session["AVALON_PROJECT"] = project_name - avalon_project = db_con.find_one({"type": "project"}) + avalon_project = get_project(project_name) output["project"] = avalon_project if not avalon_project: @@ -399,19 +407,17 @@ class StoreThumbnailsToAvalon(BaseAction): asset_mongo_id = parent["custom_attributes"].get(CUST_ATTR_ID_KEY) if asset_mongo_id: try: - asset_mongo_id = ObjectId(asset_mongo_id) - asset_ent = db_con.find_one({ - "type": "asset", - "_id": asset_mongo_id - }) + asset_ent = get_asset_by_id(project_name, asset_mongo_id) except Exception: pass if not asset_ent: - asset_ent = db_con.find_one({ - "type": "asset", - "data.ftrackId": parent["id"] - }) + asset_docs = get_assets(project_name, asset_names=[parent["name"]]) + for asset_doc in asset_docs: + ftrack_id = asset_doc.get("data", {}).get("ftrackId") + if ftrack_id == parent["id"]: + asset_ent = asset_doc + break output["asset"] = asset_ent @@ -422,13 +428,11 @@ class StoreThumbnailsToAvalon(BaseAction): ) return output - asset_mongo_id = asset_ent["_id"] - - subset_ent = db_con.find_one({ - "type": "subset", - "parent": asset_mongo_id, - "name": subset_name - }) + subset_ent = get_subset_by_name( + project_name, + subset_name=subset_name, + asset_id=asset_ent["_id"] + ) output["subset"] = subset_ent @@ -439,11 +443,11 @@ class StoreThumbnailsToAvalon(BaseAction): ).format(subset_name, ent_path) return output - version_ent = db_con.find_one({ - "type": "version", - "name": version, - "parent": subset_ent["_id"] - }) + version_ent = get_version_by_name( + project_name, + version, + subset_ent["_id"] + ) output["version"] = version_ent @@ -454,10 +458,10 @@ class StoreThumbnailsToAvalon(BaseAction): ).format(version, subset_name, ent_path) return output - repre_ents = list(db_con.find({ - "type": "representation", - "parent": version_ent["_id"] - })) + repre_ents = list(get_representations( + project_name, + version_ids=[version_ent["_id"]] + )) output["representations"] = repre_ents return output From be2238718b402b2428335b61643cf0bab2165776 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:46:14 +0200 Subject: [PATCH 0628/1227] use query functions in avalon sync --- openpype/modules/ftrack/lib/avalon_sync.py | 70 +++++++++++----------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index e4ba651bfd..68b5c62c53 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -6,6 +6,14 @@ import numbers import six +from openpype.client import ( + get_project, + get_assets, + get_archived_assets, + get_subsets, + get_versions, + get_representations +) from openpype.api import ( Logger, get_anatomy_settings @@ -576,6 +584,10 @@ class SyncEntitiesFactory: self.ft_project_id = ft_project_id self.entities_dict = entities_dict + @property + def project_name(self): + return self.entities_dict[self.ft_project_id]["name"] + @property def avalon_ents_by_id(self): """ @@ -660,9 +672,9 @@ class SyncEntitiesFactory: (list) of assets """ if self._avalon_archived_ents is None: - self._avalon_archived_ents = [ - ent for ent in self.dbcon.find({"type": "archived_asset"}) - ] + self._avalon_archived_ents = list( + get_archived_assets(self.project_name) + ) return self._avalon_archived_ents @property @@ -730,7 +742,7 @@ class SyncEntitiesFactory: """ if self._subsets_by_parent_id is None: self._subsets_by_parent_id = collections.defaultdict(list) - for subset in self.dbcon.find({"type": "subset"}): + for subset in get_subsets(self.project_name): self._subsets_by_parent_id[str(subset["parent"])].append( subset ) @@ -1421,8 +1433,8 @@ class SyncEntitiesFactory: # Avalon entities self.dbcon.install() self.dbcon.Session["AVALON_PROJECT"] = ft_project_name - avalon_project = self.dbcon.find_one({"type": "project"}) - avalon_entities = self.dbcon.find({"type": "asset"}) + avalon_project = get_project(ft_project_name) + avalon_entities = get_assets(ft_project_name) self.avalon_project = avalon_project self.avalon_entities = avalon_entities @@ -2258,46 +2270,37 @@ class SyncEntitiesFactory: self._delete_subsets_without_asset(subsets_to_remove) def _delete_subsets_without_asset(self, not_existing_parents): - subset_ids = [] - version_ids = [] repre_ids = [] to_delete = [] + subset_ids = [] for parent_id in not_existing_parents: subsets = self.subsets_by_parent_id.get(parent_id) if not subsets: continue for subset in subsets: - if subset.get("type") != "subset": - continue - subset_ids.append(subset["_id"]) + if subset.get("type") == "subset": + subset_ids.append(subset["_id"]) - db_subsets = self.dbcon.find({ - "_id": {"$in": subset_ids}, - "type": "subset" - }) - if not db_subsets: - return - - db_versions = self.dbcon.find({ - "parent": {"$in": subset_ids}, - "type": "version" - }) - if db_versions: - version_ids = [ver["_id"] for ver in db_versions] - - db_repres = self.dbcon.find({ - "parent": {"$in": version_ids}, - "type": "representation" - }) - if db_repres: - repre_ids = [repre["_id"] for repre in db_repres] + db_versions = get_versions( + self.project_name, + subset_ids=subset_ids, + fields=["_id"] + ) + version_ids = [ver["_id"] for ver in db_versions] + db_repres = get_representations( + self.project_name, + version_ids=version_ids, + fields=["_id"] + ) + repre_ids = [repre["_id"] for repre in db_repres] to_delete.extend(subset_ids) to_delete.extend(version_ids) to_delete.extend(repre_ids) - self.dbcon.delete_many({"_id": {"$in": to_delete}}) + if to_delete: + self.dbcon.delete_many({"_id": {"$in": to_delete}}) # Probably deprecated def _check_changeability(self, parent_id=None): @@ -2779,8 +2782,7 @@ class SyncEntitiesFactory: def report(self): items = [] - project_name = self.entities_dict[self.ft_project_id]["name"] - title = "Synchronization report ({}):".format(project_name) + title = "Synchronization report ({}):".format(self.project_name) keys = ["error", "warning", "info"] for key in keys: From ce63c7c96dd5b3c9ab28d766e87390f7c5334eb2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 16:46:27 +0200 Subject: [PATCH 0629/1227] use query functions in integrate ftrack hierarchy --- .../publish/integrate_hierarchy_ftrack.py | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index 73398941eb..1a5d74bf26 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -3,7 +3,8 @@ import collections import six import pyblish.api from copy import deepcopy -from openpype.pipeline import legacy_io +from openpype.client import get_asset_by_id + # Copy of constant `openpype_modules.ftrack.lib.avalon_sync.CUST_ATTR_AUTO_SYNC` CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" @@ -82,9 +83,6 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): auto_sync_state = project[ "custom_attributes"][CUST_ATTR_AUTO_SYNC] - if not legacy_io.Session: - legacy_io.install() - self.ft_project = None # disable termporarily ftrack project's autosyncing @@ -93,14 +91,14 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): try: # import ftrack hierarchy - self.import_to_ftrack(hierarchy_context) + self.import_to_ftrack(project_name, hierarchy_context) except Exception: raise finally: if auto_sync_state: self.auto_sync_on(project) - def import_to_ftrack(self, input_data, parent=None): + def import_to_ftrack(self, project_name, input_data, parent=None): # Prequery hiearchical custom attributes hier_custom_attributes = get_pype_attr(self.session)[1] hier_attr_by_key = { @@ -222,7 +220,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): six.reraise(tp, value, tb) # Incoming links. - self.create_links(entity_data, entity) + self.create_links(project_name, entity_data, entity) try: self.session.commit() except Exception: @@ -255,9 +253,9 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): # Import children. if 'childs' in entity_data: self.import_to_ftrack( - entity_data['childs'], entity) + project_name, entity_data['childs'], entity) - def create_links(self, entity_data, entity): + def create_links(self, project_name, entity_data, entity): # Clear existing links. for link in entity.get("incoming_links", []): self.session.delete(link) @@ -270,9 +268,15 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): six.reraise(tp, value, tb) # Create new links. - for input in entity_data.get("inputs", []): - input_id = legacy_io.find_one({"_id": input})["data"]["ftrackId"] - assetbuild = self.session.get("AssetBuild", input_id) + for asset_id in entity_data.get("inputs", []): + asset_doc = get_asset_by_id(project_name, asset_id) + ftrack_id = None + if asset_doc: + ftrack_id = asset_doc["data"].get("ftrackId") + if not ftrack_id: + continue + + assetbuild = self.session.get("AssetBuild", ftrack_id) self.log.debug( "Creating link from {0} to {1}".format( assetbuild["name"], entity["name"] From 07751a929219f64d5514a42c30e0d466afadf345 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 17:37:29 +0200 Subject: [PATCH 0630/1227] hound fixes --- .../ftrack/event_handlers_user/action_delete_asset.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py index b5b4ec5ac5..6dae3a4ca1 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py @@ -94,7 +94,7 @@ class DeleteAssetSubset(BaseAction): ftrack_id = entity.get("entityId") if ftrack_id: ftrack_ids.append(ftrack_id) - + if project_in_selection: msg = "It is not possible to use this action on project entity." self.show_message(event, msg, True) @@ -149,7 +149,8 @@ class DeleteAssetSubset(BaseAction): continue ent_path_items = [ent["name"] for ent in entity["link"]] - parents = ent_path_items[1:len(ent_path_items)-1:] + end_index = len(ent_path_items) - 1 + parents = ent_path_items[1:end_index:] # TODO we should say to user that # few of them are missing in avalon for av_ent in av_ents_by_name: From 3d294edf6ef08441e71fdaf56c7b84e339de8a5e Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Tue, 14 Jun 2022 17:55:48 +0200 Subject: [PATCH 0631/1227] Fixed logic and settings, uses webbrowser module --- openpype/modules/ftrack/ftrack_module.py | 3 + openpype/modules/ftrack/tray/ftrack_tray.py | 63 +++++++----- .../defaults/system_settings/modules.json | 15 +-- .../module_settings/schema_ftrack.json | 42 +------- tools/run_ftrack_eventserver.ps1 | 39 -------- tools/run_ftrack_eventserver.sh | 99 ------------------- 6 files changed, 47 insertions(+), 214 deletions(-) delete mode 100644 tools/run_ftrack_eventserver.ps1 delete mode 100644 tools/run_ftrack_eventserver.sh diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index f99e189082..048e5ebfb1 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -42,6 +42,9 @@ class FtrackModule( self.ftrack_url = ftrack_url + ftrack_open_as_app = ftrack_settings["ftrack_open_as_app"] + self.ftrack_open_as_app = ftrack_open_as_app + current_dir = os.path.dirname(os.path.abspath(__file__)) low_platform = platform.system().lower() diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 7ac994e967..065528dcff 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -1,9 +1,14 @@ +from hashlib import new +from operator import pos import os import time import datetime import threading import platform import subprocess +import posixpath, ntpath +import webbrowser +import shutil from Qt import QtCore, QtWidgets, QtGui import ftrack_api @@ -14,7 +19,6 @@ from ..ftrack_module import FTRACK_MODULE_DIR from . import login_dialog from openpype.api import Logger, resources -from openpype.settings import get_system_settings log = Logger().get_logger("FtrackModule") @@ -52,29 +56,42 @@ class FtrackTrayWrapper: self.widget_login.raise_() def show_ftrack_browser(self): - cur_os = platform.system().lower() - settings = get_system_settings()["modules"]["ftrack"] - browser_paths = settings["ftrack_browser_path"][cur_os] - browser_args = settings["ftrack_browser_arguments"][cur_os] - browser_args.append(self.module.ftrack_url) - path = "" - for p in browser_paths: - if os.path.exists(p): - path = p - log.debug(f"Found valid executable at path: {p}") - break + env_pf64 = os.environ['ProgramW6432'].replace( + ntpath.sep, posixpath.sep) + env_pf32 = os.environ['ProgramFiles(x86)'].replace( + ntpath.sep, posixpath.sep) + env_loc = os.environ['LocalAppData'].replace( + ntpath.sep, posixpath.sep) + chromium_paths_win = [ + f"{env_pf64}/Google/Chrome/Application/chrome.exe", + f"{env_pf32}/Google/Chrome/Application/chrome.exe", + f"{env_loc}/Google/Chrome/Application/chrome.exe", + f"{env_pf32}/Microsoft/Edge/Application/msedge.exe" + ] + cur_os = cur_os = platform.system().lower() + if cur_os == "windows": + is_chromium = False + for p in chromium_paths_win: + if os.path.exists(p): + is_chromium = True + chromium_path = p + break + if is_chromium and self.module.ftrack_open_as_app: + webbrowser.get(f"{chromium_path} %s").open_new( + f"--app={self.module.ftrack_url}") else: - log.warning(f"Path: {p} is not valid, please \ - doublecheck your settings!") - if path == "": - log.warning("Found no valid executables to launch \ - Ftrack with. Feature will not work as expected!") - return - args = " ".join(str(item) for item in browser_args).replace("= ", "=") - log.debug(f"Computed arguments: {args}") - cmd = f"{path} {args}" - log.debug(f"Opening Ftrack Browser...") - subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + webbrowser.get(using="windows-default").open_new( + self.module.ftrack_url) + + else: + if self.module.ftrack_open_as_app: + try: + webbrowser.get(using='chrome').open_new( + f"--app={self.module.ftrack_url}") + except webbrowser.Error: + webbrowser.open_new(self.module.ftrack_url) + else: + webbrowser.open_new(self.module.ftrack_url) def validate(self): validation = False diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index aaf01b1631..6d09652bb9 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -15,20 +15,7 @@ "ftrack": { "enabled": false, "ftrack_server": "", - "ftrack_browser_path": { - "windows": [ - "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" - ], - "darwin": [], - "linux": [] - }, - "ftrack_browser_arguments": { - "windows": [ - "--app=" - ], - "darwin": [], - "linux": [] - }, + "ftrack_open_as_app": false, "ftrack_actions_path": { "windows": [], "darwin": [], diff --git a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json index 268c5479fe..570d856cf8 100644 --- a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json +++ b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json @@ -17,45 +17,9 @@ "label": "Server" }, { - "type": "splitter" - }, - { - "type": "path", - "key": "ftrack_browser_path", - "label": "Browser Path", - "use_label_wrap": true, - "multipath": true, - "multiplatform": true - }, - { - "type": "dict", - "key": "ftrack_browser_arguments", - "label": "Browser Arguments", - "use_label_wrap": true, - "children": [ - { - "type": "label", - "label": "Any arguent which is used to open Ftrack URL (as in \"app=\" for chrome) needs to be placed last in the list!" - }, - { - "key": "windows", - "label": "Windows", - "type": "list", - "object_type": "text" - }, - { - "key": "darwin", - "label": "MacOS", - "type": "list", - "object_type": "text" - }, - { - "key": "linux", - "label": "Linux", - "type": "list", - "object_type": "text" - } - ] + "type": "boolean", + "key": "ftrack_open_as_app", + "label": "Open in app mode" }, { "type": "splitter" diff --git a/tools/run_ftrack_eventserver.ps1 b/tools/run_ftrack_eventserver.ps1 deleted file mode 100644 index 9c22f3d88e..0000000000 --- a/tools/run_ftrack_eventserver.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -<# -.SYNOPSIS - Helper script to start OpenPype Ftrack EventServer without relying on built executables. - -.DESCRIPTION - - -.EXAMPLE - -PS> .\run_eventserver.ps1 - -#> -$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent -$openpype_root = (Get-Item $script_dir).parent.FullName - -$env:_INSIDE_OPENPYPE_TOOL = "1" -$env:OPENPYPE_DEBUG = "1" -# $env:OPENPYPE_MONGO = "mongodb://127.0.0.1:27017" - -# make sure Poetry is in PATH -if (-not (Test-Path 'env:POETRY_HOME')) { - $env:POETRY_HOME = "$openpype_root\.poetry" -} -$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" - -Set-Location -Path $openpype_root - -Write-Host ">>> " -NoNewline -ForegroundColor Green -Write-Host "Reading Poetry ... " -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" -} else { - Write-Host "OK" -ForegroundColor Green -} - -& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" eventserver \ No newline at end of file diff --git a/tools/run_ftrack_eventserver.sh b/tools/run_ftrack_eventserver.sh deleted file mode 100644 index 97daa14c2d..0000000000 --- a/tools/run_ftrack_eventserver.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env bash - -art () { - cat <<-EOF - - . . .. . .. - _oOOP3OPP3Op_. . - .PPpo~· ·· ~2p. ·· ···· · · - ·Ppo · .pPO3Op.· · O:· · · · - .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · - ·~OP 3PO· .Op3 : · ·· _____ _____ _____ - ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / - O3:· O3p~ · ·:· · ·/____/·/____/ /____/ - 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · - · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · - · '_ .. · . _OP3·· · ·https://openpype.io·· · - ~P3·OPPPO3OP~ · ·· · - · ' '· · ·· · · · ·· · - -EOF -} - -# Colors for terminal - -RST='\033[0m' # Text Reset - -# Regular Colors -Black='\033[0;30m' # Black -Red='\033[0;31m' # Red -Green='\033[0;32m' # Green -Yellow='\033[0;33m' # Yellow -Blue='\033[0;34m' # Blue -Purple='\033[0;35m' # Purple -Cyan='\033[0;36m' # Cyan -White='\033[0;37m' # White - -# Bold -BBlack='\033[1;30m' # Black -BRed='\033[1;31m' # Red -BGreen='\033[1;32m' # Green -BYellow='\033[1;33m' # Yellow -BBlue='\033[1;34m' # Blue -BPurple='\033[1;35m' # Purple -BCyan='\033[1;36m' # Cyan -BWhite='\033[1;37m' # White - -# Bold High Intensity -BIBlack='\033[1;90m' # Black -BIRed='\033[1;91m' # Red -BIGreen='\033[1;92m' # Green -BIYellow='\033[1;93m' # Yellow -BIBlue='\033[1;94m' # Blue -BIPurple='\033[1;95m' # Purple -BICyan='\033[1;96m' # Cyan -BIWhite='\033[1;97m' # White - - -############################################################################## -# Return absolute path -# Globals: -# None -# Arguments: -# Path to resolve -# Returns: -# None -############################################################################### -realpath () { - echo $(cd $(dirname "$1"); pwd)/$(basename "$1") -} - -# Main -main () { - echo -e "${BGreen}" - art - echo -e "${RST}" - - # Directories - openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) - - if [[ -z $POETRY_HOME ]]; then - export POETRY_HOME="$openpype_root/.poetry" - fi - - echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" - if [ -f "$POETRY_HOME/bin/poetry" ]; then - echo -e "${BIGreen}OK${RST}" - else - echo -e "${BIYellow}NOT FOUND${RST}" - echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..." - . "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } - fi - - pushd "$openpype_root" > /dev/null || return > /dev/null - - echo -e "${BIGreen}>>>${RST} Running Ftrack Eventserver ..." - "$POETRY_HOME/bin/poetry" run python $openpype_root/start.py eventserver -} - -main From bf8b64ae3067224d56c986fd2ca3a00ea8943624 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 17:56:21 +0200 Subject: [PATCH 0632/1227] use query functions in TVPaint --- openpype/hosts/tvpaint/api/pipeline.py | 15 ++++++++------- .../hosts/tvpaint/plugins/load/load_workfile.py | 13 +++++-------- .../tvpaint/plugins/publish/collect_instances.py | 11 +++++------ .../plugins/publish/collect_scene_render.py | 11 ++++------- .../tvpaint/plugins/publish/collect_workfile.py | 11 +++++------ 5 files changed, 27 insertions(+), 34 deletions(-) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index 60c61a8cbf..0118c0104b 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -8,6 +8,7 @@ import requests import pyblish.api +from openpype.client import get_project, get_asset_by_name from openpype.hosts import tvpaint from openpype.api import get_current_project_settings from openpype.lib import register_event_callback @@ -442,14 +443,14 @@ def set_context_settings(asset_doc=None): Change fps, resolution and frame start/end. """ - if asset_doc is None: - # Use current session asset if not passed - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": legacy_io.Session["AVALON_ASSET"] - }) - project_doc = legacy_io.find_one({"type": "project"}) + project_name = legacy_io.active_project() + if asset_doc is None: + asset_name = legacy_io.Session["AVALON_ASSET"] + # Use current session asset if not passed + asset_doc = get_asset_by_name(project_name, asset_name) + + project_doc = get_project(project_name) framerate = asset_doc["data"].get("fps") if framerate is None: diff --git a/openpype/hosts/tvpaint/plugins/load/load_workfile.py b/openpype/hosts/tvpaint/plugins/load/load_workfile.py index 0eab083c22..462f12abf0 100644 --- a/openpype/hosts/tvpaint/plugins/load/load_workfile.py +++ b/openpype/hosts/tvpaint/plugins/load/load_workfile.py @@ -1,5 +1,6 @@ import os +from openpype.client import get_project, get_asset_by_name from openpype.lib import ( StringTemplate, get_workfile_template_key_from_context, @@ -44,21 +45,17 @@ class LoadWorkfile(plugin.Loader): # Save workfile. host_name = "tvpaint" + project_name = context.get("project") asset_name = context.get("asset") task_name = context.get("task") # Far cases when there is workfile without context if not asset_name: + project_name = legacy_io.active_project() asset_name = legacy_io.Session["AVALON_ASSET"] task_name = legacy_io.Session["AVALON_TASK"] - project_doc = legacy_io.find_one({ - "type": "project" - }) - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) - project_name = project_doc["name"] + project_doc = get_project(project_name) + asset_doc = get_asset_by_name(project_name, asset_name) template_key = get_workfile_template_key_from_context( asset_name, diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py index 782907b65d..9b6d5c4879 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py @@ -2,6 +2,7 @@ import json import copy import pyblish.api +from openpype.client import get_asset_by_name from openpype.lib import get_subset_name_with_asset_doc from openpype.pipeline import legacy_io @@ -92,17 +93,15 @@ class CollectInstances(pyblish.api.ContextPlugin): if family == "review": # Change subset name of review instance + # Project name from workfile context + project_name = context.data["workfile_context"]["project"] + # Collect asset doc to get asset id # - not sure if it's good idea to require asset id in # get_subset_name? asset_name = context.data["workfile_context"]["asset"] - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) - # Project name from workfile context - project_name = context.data["workfile_context"]["project"] # Host name from environment variable host_name = context.data["hostName"] # Use empty variant value diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py b/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py index 2b8dbdc5b4..20c5bb586a 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py @@ -2,8 +2,8 @@ import json import copy import pyblish.api +from openpype.client import get_asset_by_name from openpype.lib import get_subset_name_with_asset_doc -from openpype.pipeline import legacy_io class CollectRenderScene(pyblish.api.ContextPlugin): @@ -56,14 +56,11 @@ class CollectRenderScene(pyblish.api.ContextPlugin): # - not sure if it's good idea to require asset id in # get_subset_name? workfile_context = context.data["workfile_context"] - asset_name = workfile_context["asset"] - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) - # Project name from workfile context project_name = context.data["workfile_context"]["project"] + asset_name = workfile_context["asset"] + asset_doc = get_asset_by_name(project_name, asset_name) + # Host name from environment variable host_name = context.data["hostName"] # Variant is using render pass name diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py b/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py index 70d92f82e9..88c5f4dbc7 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py @@ -2,6 +2,7 @@ import os import json import pyblish.api +from openpype.client import get_asset_by_name from openpype.lib import get_subset_name_with_asset_doc from openpype.pipeline import legacy_io @@ -22,19 +23,17 @@ class CollectWorkfile(pyblish.api.ContextPlugin): basename, ext = os.path.splitext(filename) instance = context.create_instance(name=basename) + # Project name from workfile context + project_name = context.data["workfile_context"]["project"] + # Get subset name of workfile instance # Collect asset doc to get asset id # - not sure if it's good idea to require asset id in # get_subset_name? family = "workfile" asset_name = context.data["workfile_context"]["asset"] - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) - # Project name from workfile context - project_name = context.data["workfile_context"]["project"] # Host name from environment variable host_name = os.environ["AVALON_APP"] # Use empty variant value From 6a4387a866d52e027d74805a54fe4f8a43004c38 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Tue, 14 Jun 2022 17:58:56 +0200 Subject: [PATCH 0633/1227] finxed hounds --- openpype/modules/ftrack/tray/ftrack_tray.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 065528dcff..70f6e69323 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -1,14 +1,12 @@ -from hashlib import new -from operator import pos import os import time import datetime import threading import platform -import subprocess -import posixpath, ntpath +import posixpath +import ntpath import webbrowser -import shutil + from Qt import QtCore, QtWidgets, QtGui import ftrack_api @@ -82,7 +80,7 @@ class FtrackTrayWrapper: else: webbrowser.get(using="windows-default").open_new( self.module.ftrack_url) - + else: if self.module.ftrack_open_as_app: try: From 780ffefea972a3fb1b0c225136c1327878b95a91 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 18:08:28 +0200 Subject: [PATCH 0634/1227] fix unhandled empty source on instance --- openpype/plugins/publish/integrate_new.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 91f6102501..2471105250 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -940,9 +940,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): families += current_families # create relative source path for DB - if "source" in instance.data: - source = instance.data["source"] - else: + source = instance.data.get("source") + if not source: source = context.data["currentFile"] anatomy = instance.context.data["anatomy"] source = self.get_rootless_path(anatomy, source) From e98f81c70c4d8054f3cd2b8ef4ce6e0e1b7b11ba Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Tue, 14 Jun 2022 19:17:54 +0200 Subject: [PATCH 0635/1227] made the browser opening non blocking --- openpype/modules/ftrack/tray/ftrack_tray.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 70f6e69323..e822fd4639 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -90,6 +90,7 @@ class FtrackTrayWrapper: webbrowser.open_new(self.module.ftrack_url) else: webbrowser.open_new(self.module.ftrack_url) + return def validate(self): validation = False From ae9064ac5960b602c324b8d7aa6105725f4b70ea Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 15 Jun 2022 03:56:39 +0000 Subject: [PATCH 0636/1227] [Automated] Bump version --- CHANGELOG.md | 39 ++++++++++++++++++--------------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0d25908cd..eb71071205 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [3.11.0-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.11.0-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...HEAD) @@ -11,7 +11,9 @@ **🚀 Enhancements** +- Settings: Settings can be extracted from UI [\#3323](https://github.com/pypeclub/OpenPype/pull/3323) - updated poetry installation source [\#3316](https://github.com/pypeclub/OpenPype/pull/3316) +- Ftrack: Action to easily create daily review session [\#3310](https://github.com/pypeclub/OpenPype/pull/3310) - TVPaint: Extractor use mark in/out range to render [\#3309](https://github.com/pypeclub/OpenPype/pull/3309) - Ftrack: Delivery action can work on ReviewSessions [\#3307](https://github.com/pypeclub/OpenPype/pull/3307) - Maya: Look assigner UI improvements [\#3298](https://github.com/pypeclub/OpenPype/pull/3298) @@ -20,15 +22,18 @@ - General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268) - Unreal: add support for skeletalMesh and staticMesh to loaders [\#3267](https://github.com/pypeclub/OpenPype/pull/3267) - Maya: reference loaders could store placeholder in referenced url [\#3264](https://github.com/pypeclub/OpenPype/pull/3264) +- Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) - TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250) - Nuke: Change default icon path in settings [\#3247](https://github.com/pypeclub/OpenPype/pull/3247) - Maya: publishing of animation and pointcache on a farm [\#3225](https://github.com/pypeclub/OpenPype/pull/3225) - Maya: Look assigner UI improvements [\#3208](https://github.com/pypeclub/OpenPype/pull/3208) -- Nuke: add pointcache and animation to loader [\#3186](https://github.com/pypeclub/OpenPype/pull/3186) **🐛 Bug fixes** +- General: Handle empty source key on instance [\#3342](https://github.com/pypeclub/OpenPype/pull/3342) - Houdini: Fix Houdini VDB manage update wrong file attribute name [\#3322](https://github.com/pypeclub/OpenPype/pull/3322) +- Nuke: anatomy compatibility issue hacks [\#3321](https://github.com/pypeclub/OpenPype/pull/3321) +- hiero: otio p3 compatibility issue - metadata on effect use update 3.11 [\#3314](https://github.com/pypeclub/OpenPype/pull/3314) - General: Vendorized modules for Python 2 and update poetry lock [\#3305](https://github.com/pypeclub/OpenPype/pull/3305) - Fix - added local targets to install host [\#3303](https://github.com/pypeclub/OpenPype/pull/3303) - Settings: Add missing default settings for nuke gizmo [\#3301](https://github.com/pypeclub/OpenPype/pull/3301) @@ -38,18 +43,24 @@ - Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) - Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) - General: Fix Oiio tool path resolving [\#3278](https://github.com/pypeclub/OpenPype/pull/3278) -- Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) - Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) - Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) -- Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) - Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240) - Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) - Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) +- TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) - Flame: debugging [\#3224](https://github.com/pypeclub/OpenPype/pull/3224) +**🔀 Refactored code** + +- Blender: Use client query functions [\#3331](https://github.com/pypeclub/OpenPype/pull/3331) +- General: Define query functions [\#3288](https://github.com/pypeclub/OpenPype/pull/3288) + **Merged pull requests:** - Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) +- Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) +- Harmony: message length in 21.1 [\#3258](https://github.com/pypeclub/OpenPype/pull/3258) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -57,32 +68,26 @@ **🚀 Enhancements** -- Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) +- TVPaint: Init file for TVPaint worker also handle guideline images [\#3251](https://github.com/pypeclub/OpenPype/pull/3251) - General: updating common vendor `scriptmenu` to 1.5.2 [\#3246](https://github.com/pypeclub/OpenPype/pull/3246) - Project Manager: Allow to paste Tasks into multiple assets at the same time [\#3226](https://github.com/pypeclub/OpenPype/pull/3226) - Project manager: Sped up project load [\#3216](https://github.com/pypeclub/OpenPype/pull/3216) -- Loader UI: Speed issues of loader with sync server [\#3199](https://github.com/pypeclub/OpenPype/pull/3199) **🐛 Bug fixes** +- Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) +- Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) - nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244) - Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) - Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) - Maya: Fix support for multiple resolutions [\#3236](https://github.com/pypeclub/OpenPype/pull/3236) -- TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) - Hiero: debugging frame range and other 3.10 [\#3222](https://github.com/pypeclub/OpenPype/pull/3222) - Project Manager: Fix persistent editors on project change [\#3218](https://github.com/pypeclub/OpenPype/pull/3218) - Deadline: instance data overwrite fix [\#3214](https://github.com/pypeclub/OpenPype/pull/3214) - Ftrack: Push hierarchical attributes action works [\#3210](https://github.com/pypeclub/OpenPype/pull/3210) - Standalone Publisher: Always create new representation for thumbnail [\#3203](https://github.com/pypeclub/OpenPype/pull/3203) - Photoshop: skip collector when automatic testing [\#3202](https://github.com/pypeclub/OpenPype/pull/3202) -- Nuke: render/workfile version sync doesn't work on farm [\#3185](https://github.com/pypeclub/OpenPype/pull/3185) -- Ftrack: Review image only if there are no mp4 reviews [\#3183](https://github.com/pypeclub/OpenPype/pull/3183) - -**🔀 Refactored code** - -- Avalon repo removed from Jobs workflow [\#3193](https://github.com/pypeclub/OpenPype/pull/3193) **Merged pull requests:** @@ -98,18 +103,10 @@ - nuke: generate publishing nodes inside render group node [\#3206](https://github.com/pypeclub/OpenPype/pull/3206) - Loader UI: Speed issues of loader with sync server [\#3200](https://github.com/pypeclub/OpenPype/pull/3200) -- Backport of fix for attaching renders to subsets [\#3195](https://github.com/pypeclub/OpenPype/pull/3195) -- Looks: add basic support for Renderman [\#3190](https://github.com/pypeclub/OpenPype/pull/3190) **🐛 Bug fixes** - Standalone Publisher: Always create new representation for thumbnail [\#3204](https://github.com/pypeclub/OpenPype/pull/3204) -- Nuke: render/workfile version sync doesn't work on farm [\#3184](https://github.com/pypeclub/OpenPype/pull/3184) -- Ftrack: Review image only if there are no mp4 reviews [\#3182](https://github.com/pypeclub/OpenPype/pull/3182) - -**Merged pull requests:** - -- hiero: otio p3 compatibility issue - metadata on effect use update [\#3194](https://github.com/pypeclub/OpenPype/pull/3194) ## [3.9.7](https://github.com/pypeclub/OpenPype/tree/3.9.7) (2022-05-11) diff --git a/openpype/version.py b/openpype/version.py index 4b0a688cbf..2f4d180983 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.0-nightly.2" +__version__ = "3.11.0-nightly.3" diff --git a/pyproject.toml b/pyproject.toml index 4289c74ebe..e1b5c37289 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.0-nightly.2" # OpenPype +version = "3.11.0-nightly.3" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 180fd1f843b9a7d7fa9046964c1f155b17412c94 Mon Sep 17 00:00:00 2001 From: DMO Date: Wed, 15 Jun 2022 13:40:57 +0900 Subject: [PATCH 0637/1227] We want to strip namespaces by default for mvUsd. --- openpype/hosts/maya/plugins/create/create_multiverse_usd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py index 034714d51b..adf0acc7c2 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py @@ -16,7 +16,7 @@ class CreateMultiverseUsd(plugin.Creator): self.data.update(lib.collect_animation_data(True)) self.data["fileFormat"] = ["usd", "usda", "usdz"] - self.data["stripNamespaces"] = False + self.data["stripNamespaces"] = True self.data["mergeTransformAndShape"] = False self.data["writeAncestors"] = True self.data["flattenParentXforms"] = False From 5af098d18afc6156165964fcd696e35afd0bf6be Mon Sep 17 00:00:00 2001 From: DMO Date: Wed, 15 Jun 2022 16:14:39 +0900 Subject: [PATCH 0638/1227] Adding Options to the main Studio Settings. --- .../defaults/project_settings/maya.json | 8 ++++ .../schemas/schema_maya_create.json | 38 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 4cdfe1ca5d..aaf47479b8 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -159,6 +159,14 @@ "defaults": [ "Main" ] + }, + "CreateMultiverseLook": { + "enabled": true, + "publish_mip_map": true + }, + "CreateMultiverseUsd": { + "enabled": true, + "strip_namespaces": true } }, "publish": { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json index 6dc10ed2a5..4dd54c81a0 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json @@ -124,6 +124,44 @@ ] }, + { + "type": "dict", + "collapsible": true, + "key": "CreateMultiverseLook", + "label": "Create Multiverse Look", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "publish_mip_map", + "label": "Publish Mip Maps" + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "CreateMultiverseUsd", + "label": "Create Multiverse USD", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "strip_namespaces", + "label": "Strip Namespaces" + } + ] + }, { "type": "schema_template", "name": "template_create_plugin", From c14b8323dbc40cc4bf41712c52642e75ae391546 Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Fri, 6 May 2022 17:06:32 +0200 Subject: [PATCH 0639/1227] Add menu quad in hiero --- .../defaults/project_settings/hiero.json | 32 +++++++++++++++++++ .../projects_schema/schema_project_hiero.json | 4 +++ .../common/scriptsmenu/launchfornuke.py | 2 +- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index 1dff3aac51..eb75aff6c0 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -1,4 +1,36 @@ { + "ext_mapping": { + "model": "hrox", + "mayaAscii": "hrox", + "camera": "hrox", + "rig": "hrox", + "workfile": "hrox", + "yetiRig": "hrox" + }, + "heiro-dirmap": { + "enabled": false, + "paths": { + "source-path": [], + "destination-path": [] + } + }, + "scriptsmenu": { + "name": "OpenPype Tools", + "definition": [ + { + "type": "action", + "command": "import openpype.hosts.hiero.api.commands as hiero; hiero.edit_shader_definitions()", + "sourcetype": "python", + "title": "Edit shader name definitions", + "tooltip": "Edit shader name definitions used in validation and renaming.", + "tags": [ + "pipeline", + "shader" + ] + } + ] + }, + "create": { "CreateShotClip": { "hierarchy": "{folder}/{sequence}", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index f717eff7dd..39721c641a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -206,6 +206,10 @@ { "type": "schema", "name": "schema_publish_gui_filter" + }, + { + "type": "schema", + "name": "schema_maya_scriptsmenu" } ] } diff --git a/openpype/vendor/python/common/scriptsmenu/launchfornuke.py b/openpype/vendor/python/common/scriptsmenu/launchfornuke.py index 72302a79a6..4f65a8e3ae 100644 --- a/openpype/vendor/python/common/scriptsmenu/launchfornuke.py +++ b/openpype/vendor/python/common/scriptsmenu/launchfornuke.py @@ -6,7 +6,7 @@ def _nuke_main_window(): """Return Nuke's main window""" for obj in QtWidgets.QApplication.topLevelWidgets(): if (obj.inherits('QMainWindow') and - obj.metaObject().className() == 'Foundry::UI::DockMainWindow'): + obj.metaObject().className() == 'Foundry::UI::DockMainWindow'): return obj raise RuntimeError('Could not find Nuke MainWindow instance') From 174c04a7f26dbe3c41c8c459222f97135fb4268d Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Fri, 6 May 2022 17:16:12 +0200 Subject: [PATCH 0640/1227] menu hiero --- .../common/scriptsmenu/launchforhiero.py | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 openpype/vendor/python/common/scriptsmenu/launchforhiero.py diff --git a/openpype/vendor/python/common/scriptsmenu/launchforhiero.py b/openpype/vendor/python/common/scriptsmenu/launchforhiero.py new file mode 100644 index 0000000000..8c3aa35126 --- /dev/null +++ b/openpype/vendor/python/common/scriptsmenu/launchforhiero.py @@ -0,0 +1,84 @@ +import logging + +import scriptsmenu +from .vendor.Qt import QtWidgets + +log = logging.getLogger(__name__) + + +def _hiero_main_window(): + """Return Nuke's main window""" + for obj in QtWidgets.QApplication.topLevelWidgets(): + if (obj.inherits('QMainWindow') and + obj.metaObject().className() == 'Foundry::UI::DockMainWindow'): + return obj + raise RuntimeError('Could not find HieroWindow instance') + + +def _hiero_main_menubar(): + """Retrieve the main menubar of the Nuke window""" + hiero_window = _hiero_main_window() + menubar = [i for i in hiero_window.children() if isinstance( + i, + QtWidgets.QMenuBar + )] + + assert len(menubar) == 1, "Error, could not find menu bar!" + return menubar[0] + + +def find_scripts_menu(title, parent): + """ + Check if the menu exists with the given title in the parent + + Args: + title (str): the title name of the scripts menu + + parent (QtWidgets.QMenuBar): the menubar to check + + Returns: + QtWidgets.QMenu or None + + """ + + menu = None + search = [i for i in parent.children() if + isinstance(i, scriptsmenu.ScriptsMenu) + and i.title() == title] + if search: + assert len(search) < 2, ("Multiple instances of menu '{}' " + "in menu bar".format(title)) + menu = search[0] + + return menu + + +def main(title="Scripts", parent=None, objectName=None): + """Build the main scripts menu in Maya + + Args: + title (str): name of the menu in the application + + parent (QtWidgets.QtMenuBar): the parent object for the menu + + objectName (str): custom objectName for scripts menu + + Returns: + scriptsmenu.ScriptsMenu instance + + """ + hieromainbar = parent or _hiero_main_menubar() + try: + # check menu already exists + menu = find_scripts_menu(title, hieromainbar) + if not menu: + log.info("Attempting to build menu ...") + object_name = objectName or title.lower() + menu = scriptsmenu.ScriptsMenu(title=title, + parent=hieromainbar, + objectName=object_name) + except Exception as e: + log.error(e) + return + + return menu From 3ea514f029085653a104b37dcae8b6d2cd6bf9cc Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Mon, 23 May 2022 15:11:56 +0200 Subject: [PATCH 0641/1227] add script menu --- openpype/hosts/hiero/api/menu.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/openpype/hosts/hiero/api/menu.py b/openpype/hosts/hiero/api/menu.py index e262abec00..d4e62c9e8a 100644 --- a/openpype/hosts/hiero/api/menu.py +++ b/openpype/hosts/hiero/api/menu.py @@ -9,6 +9,7 @@ from openpype.pipeline import legacy_io from openpype.tools.utils import host_tools from . import tags +from openpype.settings import get_project_settings log = Logger.get_logger(__name__) @@ -41,6 +42,7 @@ def menu_install(): Installing menu into Hiero """ + print("YOLOOOOOOOOOOOOOOOOOO") from Qt import QtGui from . import ( publish, launch_workfiles_app, reload_config, @@ -138,3 +140,32 @@ def menu_install(): exeprimental_action.triggered.connect( lambda: host_tools.show_experimental_tools_dialog(parent=main_window) ) + + +def add_scripts_menu(): + try: + from scriptsmenu import launchforhiero + except ImportError: + log.warning( + "Skipping studio.menu install, because " + "'scriptsmenu' module seems unavailable." + ) + return + + # load configuration of custom menu + project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) + config = project_settings["hiero"]["scriptsmenu"]["definition"] + _menu = project_settings["hiero"]["scriptsmenu"]["name"] + + if not config: + log.warning("Skipping studio menu, no definition found.") + return + + # run the launcher for Maya menu + studio_menu = launchforhiero.main(title=_menu.title()) + + # apply configuration + studio_menu.build_from_configuration(studio_menu, config) + + +add_scripts_menu() From 4c9cd18f1cce331d4e434c531775450cab5b0b71 Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Mon, 23 May 2022 15:13:36 +0200 Subject: [PATCH 0642/1227] clen print --- openpype/hosts/hiero/api/menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/api/menu.py b/openpype/hosts/hiero/api/menu.py index d4e62c9e8a..9e999da2f6 100644 --- a/openpype/hosts/hiero/api/menu.py +++ b/openpype/hosts/hiero/api/menu.py @@ -42,7 +42,7 @@ def menu_install(): Installing menu into Hiero """ - print("YOLOOOOOOOOOOOOOOOOOO") + from Qt import QtGui from . import ( publish, launch_workfiles_app, reload_config, From 0e3607805925eaa1fc7ad9bc0e17f0144081a71d Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Mon, 23 May 2022 15:47:29 +0200 Subject: [PATCH 0643/1227] nothing change --- openpype/settings/defaults/project_settings/hiero.json | 2 +- openpype/vendor/python/common/scriptsmenu/launchforhiero.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index eb75aff6c0..cec4bca8fd 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -7,7 +7,7 @@ "workfile": "hrox", "yetiRig": "hrox" }, - "heiro-dirmap": { + "hiero-dirmap": { "enabled": false, "paths": { "source-path": [], diff --git a/openpype/vendor/python/common/scriptsmenu/launchforhiero.py b/openpype/vendor/python/common/scriptsmenu/launchforhiero.py index 8c3aa35126..3f8e726083 100644 --- a/openpype/vendor/python/common/scriptsmenu/launchforhiero.py +++ b/openpype/vendor/python/common/scriptsmenu/launchforhiero.py @@ -7,6 +7,7 @@ log = logging.getLogger(__name__) def _hiero_main_window(): + print("YEAAAAAAAAAAAAAAAAAAAH") """Return Nuke's main window""" for obj in QtWidgets.QApplication.topLevelWidgets(): if (obj.inherits('QMainWindow') and From 6ee781c8a6b4701f6166a877eda4e43b9a76e454 Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Tue, 7 Jun 2022 17:00:53 +0200 Subject: [PATCH 0644/1227] change launcher for api folder --- openpype/hosts/hiero/api/launchforhiero.py | 85 ++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 openpype/hosts/hiero/api/launchforhiero.py diff --git a/openpype/hosts/hiero/api/launchforhiero.py b/openpype/hosts/hiero/api/launchforhiero.py new file mode 100644 index 0000000000..3f8e726083 --- /dev/null +++ b/openpype/hosts/hiero/api/launchforhiero.py @@ -0,0 +1,85 @@ +import logging + +import scriptsmenu +from .vendor.Qt import QtWidgets + +log = logging.getLogger(__name__) + + +def _hiero_main_window(): + print("YEAAAAAAAAAAAAAAAAAAAH") + """Return Nuke's main window""" + for obj in QtWidgets.QApplication.topLevelWidgets(): + if (obj.inherits('QMainWindow') and + obj.metaObject().className() == 'Foundry::UI::DockMainWindow'): + return obj + raise RuntimeError('Could not find HieroWindow instance') + + +def _hiero_main_menubar(): + """Retrieve the main menubar of the Nuke window""" + hiero_window = _hiero_main_window() + menubar = [i for i in hiero_window.children() if isinstance( + i, + QtWidgets.QMenuBar + )] + + assert len(menubar) == 1, "Error, could not find menu bar!" + return menubar[0] + + +def find_scripts_menu(title, parent): + """ + Check if the menu exists with the given title in the parent + + Args: + title (str): the title name of the scripts menu + + parent (QtWidgets.QMenuBar): the menubar to check + + Returns: + QtWidgets.QMenu or None + + """ + + menu = None + search = [i for i in parent.children() if + isinstance(i, scriptsmenu.ScriptsMenu) + and i.title() == title] + if search: + assert len(search) < 2, ("Multiple instances of menu '{}' " + "in menu bar".format(title)) + menu = search[0] + + return menu + + +def main(title="Scripts", parent=None, objectName=None): + """Build the main scripts menu in Maya + + Args: + title (str): name of the menu in the application + + parent (QtWidgets.QtMenuBar): the parent object for the menu + + objectName (str): custom objectName for scripts menu + + Returns: + scriptsmenu.ScriptsMenu instance + + """ + hieromainbar = parent or _hiero_main_menubar() + try: + # check menu already exists + menu = find_scripts_menu(title, hieromainbar) + if not menu: + log.info("Attempting to build menu ...") + object_name = objectName or title.lower() + menu = scriptsmenu.ScriptsMenu(title=title, + parent=hieromainbar, + objectName=object_name) + except Exception as e: + log.error(e) + return + + return menu From 35a2c7dd13ddd3f0d780c60c0e492de676e92dcc Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Tue, 7 Jun 2022 17:27:33 +0200 Subject: [PATCH 0645/1227] revert vendor files --- .../common/scriptsmenu/launchforhiero.py | 85 ------------------- 1 file changed, 85 deletions(-) delete mode 100644 openpype/vendor/python/common/scriptsmenu/launchforhiero.py diff --git a/openpype/vendor/python/common/scriptsmenu/launchforhiero.py b/openpype/vendor/python/common/scriptsmenu/launchforhiero.py deleted file mode 100644 index 3f8e726083..0000000000 --- a/openpype/vendor/python/common/scriptsmenu/launchforhiero.py +++ /dev/null @@ -1,85 +0,0 @@ -import logging - -import scriptsmenu -from .vendor.Qt import QtWidgets - -log = logging.getLogger(__name__) - - -def _hiero_main_window(): - print("YEAAAAAAAAAAAAAAAAAAAH") - """Return Nuke's main window""" - for obj in QtWidgets.QApplication.topLevelWidgets(): - if (obj.inherits('QMainWindow') and - obj.metaObject().className() == 'Foundry::UI::DockMainWindow'): - return obj - raise RuntimeError('Could not find HieroWindow instance') - - -def _hiero_main_menubar(): - """Retrieve the main menubar of the Nuke window""" - hiero_window = _hiero_main_window() - menubar = [i for i in hiero_window.children() if isinstance( - i, - QtWidgets.QMenuBar - )] - - assert len(menubar) == 1, "Error, could not find menu bar!" - return menubar[0] - - -def find_scripts_menu(title, parent): - """ - Check if the menu exists with the given title in the parent - - Args: - title (str): the title name of the scripts menu - - parent (QtWidgets.QMenuBar): the menubar to check - - Returns: - QtWidgets.QMenu or None - - """ - - menu = None - search = [i for i in parent.children() if - isinstance(i, scriptsmenu.ScriptsMenu) - and i.title() == title] - if search: - assert len(search) < 2, ("Multiple instances of menu '{}' " - "in menu bar".format(title)) - menu = search[0] - - return menu - - -def main(title="Scripts", parent=None, objectName=None): - """Build the main scripts menu in Maya - - Args: - title (str): name of the menu in the application - - parent (QtWidgets.QtMenuBar): the parent object for the menu - - objectName (str): custom objectName for scripts menu - - Returns: - scriptsmenu.ScriptsMenu instance - - """ - hieromainbar = parent or _hiero_main_menubar() - try: - # check menu already exists - menu = find_scripts_menu(title, hieromainbar) - if not menu: - log.info("Attempting to build menu ...") - object_name = objectName or title.lower() - menu = scriptsmenu.ScriptsMenu(title=title, - parent=hieromainbar, - objectName=object_name) - except Exception as e: - log.error(e) - return - - return menu From 4cf1edc3c39603be582d4e3a5013687d24718bf2 Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Tue, 7 Jun 2022 17:31:00 +0200 Subject: [PATCH 0646/1227] revert vendor files --- openpype/vendor/python/common/scriptsmenu/launchfornuke.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/vendor/python/common/scriptsmenu/launchfornuke.py b/openpype/vendor/python/common/scriptsmenu/launchfornuke.py index 4f65a8e3ae..72302a79a6 100644 --- a/openpype/vendor/python/common/scriptsmenu/launchfornuke.py +++ b/openpype/vendor/python/common/scriptsmenu/launchfornuke.py @@ -6,7 +6,7 @@ def _nuke_main_window(): """Return Nuke's main window""" for obj in QtWidgets.QApplication.topLevelWidgets(): if (obj.inherits('QMainWindow') and - obj.metaObject().className() == 'Foundry::UI::DockMainWindow'): + obj.metaObject().className() == 'Foundry::UI::DockMainWindow'): return obj raise RuntimeError('Could not find Nuke MainWindow instance') From 5b83675a9b0684517d5bf0c0e5b47365bb1e2e67 Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Tue, 7 Jun 2022 17:54:18 +0200 Subject: [PATCH 0647/1227] change import in launch script --- openpype/hosts/hiero/api/launchforhiero.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/hiero/api/launchforhiero.py b/openpype/hosts/hiero/api/launchforhiero.py index 3f8e726083..3d9328dc34 100644 --- a/openpype/hosts/hiero/api/launchforhiero.py +++ b/openpype/hosts/hiero/api/launchforhiero.py @@ -1,14 +1,13 @@ import logging import scriptsmenu -from .vendor.Qt import QtWidgets +from .api.Qt import QtWidgets log = logging.getLogger(__name__) def _hiero_main_window(): - print("YEAAAAAAAAAAAAAAAAAAAH") - """Return Nuke's main window""" + """Return Hiero's main window""" for obj in QtWidgets.QApplication.topLevelWidgets(): if (obj.inherits('QMainWindow') and obj.metaObject().className() == 'Foundry::UI::DockMainWindow'): @@ -17,7 +16,7 @@ def _hiero_main_window(): def _hiero_main_menubar(): - """Retrieve the main menubar of the Nuke window""" + """Retrieve the main menubar of the Hiero window""" hiero_window = _hiero_main_window() menubar = [i for i in hiero_window.children() if isinstance( i, @@ -55,7 +54,7 @@ def find_scripts_menu(title, parent): def main(title="Scripts", parent=None, objectName=None): - """Build the main scripts menu in Maya + """Build the main scripts menu in Hiero Args: title (str): name of the menu in the application From 6cd20e110f6613b1f8c246eb0b489852f3d9507e Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Tue, 7 Jun 2022 18:03:05 +0200 Subject: [PATCH 0648/1227] change path launch script --- openpype/hosts/hiero/api/launchforhiero.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/api/launchforhiero.py b/openpype/hosts/hiero/api/launchforhiero.py index 3d9328dc34..8b63b47c10 100644 --- a/openpype/hosts/hiero/api/launchforhiero.py +++ b/openpype/hosts/hiero/api/launchforhiero.py @@ -1,7 +1,7 @@ import logging import scriptsmenu -from .api.Qt import QtWidgets +from .openpype.vendor.python.common.scriptsmenu.vendor.Qt import QtWidgets log = logging.getLogger(__name__) From 6741b9e6336eb4a1b5d1fc837f021c4d45942b5d Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Wed, 8 Jun 2022 17:33:10 +0200 Subject: [PATCH 0649/1227] modif launch menu hiero --- openpype/hosts/hiero/api/launchforhiero.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/hiero/api/launchforhiero.py b/openpype/hosts/hiero/api/launchforhiero.py index 8b63b47c10..5c230eb9fe 100644 --- a/openpype/hosts/hiero/api/launchforhiero.py +++ b/openpype/hosts/hiero/api/launchforhiero.py @@ -1,7 +1,8 @@ import logging -import scriptsmenu -from .openpype.vendor.python.common.scriptsmenu.vendor.Qt import QtWidgets +from openpype.vendor.python.common.scriptsmenu import scriptsmenu +from openpype.vendor.python.common.scriptsmenu.vendor.Qt import QtWidgets + log = logging.getLogger(__name__) From 373524b9dfd40592be4904694681c2ab508b5653 Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Wed, 8 Jun 2022 17:33:40 +0200 Subject: [PATCH 0650/1227] modif menu hiero --- openpype/hosts/hiero/api/menu.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/api/menu.py b/openpype/hosts/hiero/api/menu.py index 9e999da2f6..412f08272e 100644 --- a/openpype/hosts/hiero/api/menu.py +++ b/openpype/hosts/hiero/api/menu.py @@ -144,8 +144,9 @@ def menu_install(): def add_scripts_menu(): try: - from scriptsmenu import launchforhiero + from . import launchforhiero except ImportError: + log.warning( "Skipping studio.menu install, because " "'scriptsmenu' module seems unavailable." From 1eb3a55cc3610e7f12b1fa28e61deb942ee57cf4 Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Wed, 15 Jun 2022 09:58:56 +0200 Subject: [PATCH 0651/1227] change name string --- .../entities/schemas/projects_schema/schema_project_hiero.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index 39721c641a..3108d2197e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -209,7 +209,7 @@ }, { "type": "schema", - "name": "schema_maya_scriptsmenu" + "name": "schema_scriptsmenu" } ] } From 1d22b862d1e95b56e64b8ae8db7234f7ed6eeb5e Mon Sep 17 00:00:00 2001 From: DMO Date: Wed, 15 Jun 2022 18:39:31 +0900 Subject: [PATCH 0652/1227] Adding ExtractAlembic to settings and setting the defaults to what they were, so nothing changes here except the option to remove certain items from families. --- .../defaults/project_settings/maya.json | 24 ++++++++++++------- .../schemas/schema_maya_publish.json | 24 +++++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index aaf47479b8..df54e44c56 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -65,6 +65,14 @@ "defaults": [], "joint_hints": "jnt_org" }, + "CreateMultiverseLook": { + "enabled": true, + "publish_mip_map": true + }, + "CreateMultiverseUsd": { + "enabled": true, + "strip_namespaces": true + }, "CreateAnimation": { "enabled": true, "defaults": [ @@ -159,14 +167,6 @@ "defaults": [ "Main" ] - }, - "CreateMultiverseLook": { - "enabled": true, - "publish_mip_map": true - }, - "CreateMultiverseUsd": { - "enabled": true, - "strip_namespaces": true } }, "publish": { @@ -383,6 +383,14 @@ "optional": true, "active": true }, + "ExtractAlembic": { + "enabled": true, + "families": [ + "pointcache", + "model", + "vrayproxy" + ] + }, "ValidateRigContents": { "enabled": false, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 2e5bc64e1c..bbe0418139 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -490,6 +490,30 @@ "label": "ValidateUniqueNames" } ] + }, + { + "type": "label", + "label": "Extractors" + }, + { + "type": "dict", + "collapsible": true, + "key": "ExtractAlembic", + "label": "Extract Alembic", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + } + ] } ] }, From 3977c25a804671e04d6c688346e1be5a485187e9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Jun 2022 14:31:36 +0200 Subject: [PATCH 0653/1227] Nuke: improving logic for `useSequenceForReview` argument --- openpype/hosts/nuke/plugins/publish/precollect_writes.py | 5 +++++ .../modules/deadline/plugins/publish/submit_publish_job.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py index 8669f4f485..a0b4b77a2d 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -175,6 +175,11 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): "frameEndHandle": last_frame, }) + # make sure rendered sequence on farm will + # be used for exctract review + if instance.data["review"]: + instance.data["useSequenceForReview"] = True + # * Add audio to instance if exists. # Find latest versions document version_doc = pype.get_latest_version( diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 0583c25b57..3c036510b3 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -746,7 +746,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "resolutionHeight": data.get("resolutionHeight", 1080), "multipartExr": data.get("multipartExr", False), "jobBatchName": data.get("jobBatchName", ""), - "useSequenceForReview": data.get("useSequenceForReview", True) + "useSequenceForReview": data.get("useSequenceForReview") } if "prerender" in instance.data["families"]: From 21d68b0d13914a13bbfc12040ee342b260a116ce Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Jun 2022 14:35:40 +0200 Subject: [PATCH 0654/1227] use get last version for subset in collect published files --- .../publish/collect_published_files.py | 74 ++++++------------- 1 file changed, 22 insertions(+), 52 deletions(-) diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index bdd3caccfd..20e277d794 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -13,9 +13,13 @@ import tempfile import math import pyblish.api + +from openpype.client import ( + get_asset_by_name, + get_last_version_by_subset_name +) from openpype.lib import ( prepare_template_data, - get_asset, get_ffprobe_streams, convert_ffprobe_fps_value, ) @@ -23,7 +27,6 @@ from openpype.lib.plugin_tools import ( parse_json, get_subset_name_with_asset_doc ) -from openpype.pipeline import legacy_io class CollectPublishedFiles(pyblish.api.ContextPlugin): @@ -56,8 +59,9 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): self.log.info("task_sub:: {}".format(task_subfolders)) + project_name = context.data["project_name"] asset_name = context.data["asset"] - asset_doc = get_asset() + asset_doc = get_asset_by_name(project_name, asset_name) task_name = context.data["task"] task_type = context.data["taskType"] project_name = context.data["project_name"] @@ -80,7 +84,9 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): family, variant, task_name, asset_doc, project_name=project_name, host_name="webpublisher" ) - version = self._get_last_version(asset_name, subset_name) + 1 + version = self._get_next_version( + project_name, asset_doc, subset_name + ) instance = context.create_instance(subset_name) instance.data["asset"] = asset_name @@ -219,55 +225,19 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): config["families"], config["tags"]) - def _get_last_version(self, asset_name, subset_name): - """Returns version number or 0 for 'asset' and 'subset'""" - query = [ - { - "$match": {"type": "asset", "name": asset_name} - }, - { - "$lookup": - { - "from": os.environ["AVALON_PROJECT"], - "localField": "_id", - "foreignField": "parent", - "as": "subsets" - } - }, - { - "$unwind": "$subsets" - }, - { - "$match": {"subsets.type": "subset", - "subsets.name": subset_name}}, - { - "$lookup": - { - "from": os.environ["AVALON_PROJECT"], - "localField": "subsets._id", - "foreignField": "parent", - "as": "versions" - } - }, - { - "$unwind": "$versions" - }, - { - "$group": { - "_id": { - "asset_name": "$name", - "subset_name": "$subsets.name" - }, - 'version': {'$max': "$versions.name"} - } - } - ] - version = list(legacy_io.aggregate(query)) + def _get_next_version(self, project_name, asset_doc, subset_name): + """Returns version number or 1 for 'asset' and 'subset'""" - if version: - return version[0].get("version") or 0 - else: - return 0 + version_doc = get_last_version_by_subset_name( + project_name, + subset_name, + asset_doc["_id"], + fields=["name"] + ) + version = 1 + if version_doc: + version += int(version_doc["name"]) + return version def _get_number_of_frames(self, file_url): """Return duration in frames""" From a7f2587483ed5ea31090f1658937cb3904043fb6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Jun 2022 14:45:57 +0200 Subject: [PATCH 0655/1227] Nuke: reversing logic so it will not break other hosts --- openpype/hosts/nuke/plugins/publish/precollect_writes.py | 4 ++-- .../modules/deadline/plugins/publish/submit_publish_job.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py index a0b4b77a2d..e050fc8c52 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -177,8 +177,8 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): # make sure rendered sequence on farm will # be used for exctract review - if instance.data["review"]: - instance.data["useSequenceForReview"] = True + if not instance.data["review"]: + instance.data["useSequenceForReview"] = False # * Add audio to instance if exists. # Find latest versions document diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 3c036510b3..0583c25b57 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -746,7 +746,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "resolutionHeight": data.get("resolutionHeight", 1080), "multipartExr": data.get("multipartExr", False), "jobBatchName": data.get("jobBatchName", ""), - "useSequenceForReview": data.get("useSequenceForReview") + "useSequenceForReview": data.get("useSequenceForReview", True) } if "prerender" in instance.data["families"]: From 65a22a1ae98fb2489bd32f6f122fa60f84261162 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Jun 2022 14:55:02 +0200 Subject: [PATCH 0656/1227] removing KnobScripter also default mov colorspace --- .../nuke/startup/KnobScripter/__init__.py | 1 - .../KnobScripter/icons/icon_clearConsole.png | Bin 1860 -> 0 bytes .../KnobScripter/icons/icon_download.png | Bin 1225 -> 0 bytes .../KnobScripter/icons/icon_exitnode.png | Bin 1883 -> 0 bytes .../startup/KnobScripter/icons/icon_pick.png | Bin 2184 -> 0 bytes .../startup/KnobScripter/icons/icon_prefs.png | Bin 2277 -> 0 bytes .../KnobScripter/icons/icon_prefs2.png | Bin 2758 -> 0 bytes .../KnobScripter/icons/icon_refresh.png | Bin 1778 -> 0 bytes .../startup/KnobScripter/icons/icon_run.png | Bin 2341 -> 0 bytes .../startup/KnobScripter/icons/icon_save.png | Bin 1784 -> 0 bytes .../KnobScripter/icons/icon_search.png | Bin 2400 -> 0 bytes .../KnobScripter/icons/icon_snippets.png | Bin 1415 -> 0 bytes .../startup/KnobScripter/knob_scripter.py | 4196 ----------------- openpype/hosts/nuke/startup/init.py | 4 - 14 files changed, 4201 deletions(-) delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/__init__.py delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_clearConsole.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_download.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_exitnode.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_pick.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_prefs.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_prefs2.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_refresh.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_run.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_save.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_search.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_snippets.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/knob_scripter.py delete mode 100644 openpype/hosts/nuke/startup/init.py diff --git a/openpype/hosts/nuke/startup/KnobScripter/__init__.py b/openpype/hosts/nuke/startup/KnobScripter/__init__.py deleted file mode 100644 index 8fe91d63f5..0000000000 --- a/openpype/hosts/nuke/startup/KnobScripter/__init__.py +++ /dev/null @@ -1 +0,0 @@ -import knob_scripter \ No newline at end of file diff --git a/openpype/hosts/nuke/startup/KnobScripter/icons/icon_clearConsole.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_clearConsole.png deleted file mode 100644 index 75ac04ef84b7235c071b512d1dc60410c32c5834..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1860 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 VW=lh7gf9R9002ovPDHLkV1jt?fvf-k diff --git a/openpype/hosts/nuke/startup/KnobScripter/icons/icon_pick.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_pick.png deleted file mode 100644 index 239553755060aa93162d8748796d7ea29bb7b07e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2184 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 M07*qoM6N<$f-?anq5uE@ diff --git a/openpype/hosts/nuke/startup/KnobScripter/icons/icon_refresh.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_refresh.png deleted file mode 100644 index 559bfd74ab8b550503194ba2409795e77a52c83b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1778 zcmV%no+>XdU+wjJ?^&>5L4iOZ zg#>9CnO#2f)tS$>ZQbU}7r>G$mr6ni(b(9yZR4hmL2a?Nbf%*}g|Y^57rBdfwD0Jv ztE+n&LB@Fr^Ok3;2KYPR$f#rmA`qf|SNmW6wf=xWAf*HWQc47Zsi`TZ{x^k?#t6;# z_v+QFPWYc2NMOT;wz^t>KngG!xO8cZXf#SJ9w#w6>Zo(sNExKGwDhh`n>Gd|fCM4n z?Aal9?b^lo__+PmYrv0+&Hh*2)%m3ivVQ&g-+MeBF8~5ACN8pb=T0UjCIJ2e>;c{b zhVyl*va<5AbLYdDI{&g@nx38Da=WBQ+^h(s_autDL!?LFeqdc|d&?Vqv9`ykOA`}XxArKhx#uiEI z{B<{lQ3Men%!aLq4Oen=JtLpb*AVImMO}p^1Oj1>`pmN5?{5qSJE8>z1#|ASiXakc z0tl|}t-K+MTt!H8H$)=SeP%BkJ?Hn=Hg<$M-kA$VeQKEoG6RyIs;a7c?iWA$=)+@3NTpJQ z!(n3aIGDK@z~tm4t!=FajDV4mf7905<{29s>t;qUR|auvB+~6kDgWHr`TgO;hY9Zv z6N|+_kTOQy%z=Q3@rf_n+FHN);`1+Pd+8+-i3GqJV5z-5P zfh3YYf!JZp;k31j;}a9VZEtTs4y*#c2Y!K(7NwQb!OB5N;00g`hyypu?ME@3-w(6^ z|C;%N8i){?9&&zYB(TM7``76wvo(s54$UdGH6k-}*73!1ogG_L&r z_qGEuQj+ZJ-4MjR(7ni8=Dpi0TWQJ66lKYhCHIQ-0?JcXD>-Z?bAl{fxNyhA9P)a&({Fd3AUD_0hEb#)Gum6a_E1Olxsn_If_9vR#1Ox_?(O-+AzWbGrrvPO5s ziskpe@y4E`W-bZ{N=rR-c6Kf+)4a0}FetQXa%0g!HVNGNoK)zJQp9iN#}P zHyQhqMAF~qJj7y6Taq3R3F8yv^H;FxW|4hb*Y)ObBy#9e^YkXAxMd^pU}fdwlFS;p zaNz<#Ql+j+8b6)X_2%8-@PQ6!I)DEBErHN9ZE1adeY1TU4-O6jTu`a6da#;KCX>J0 z{p#)mDVb_ApE(90gb>?b-u|Yi#G^@!8;(RG!P%j+0RL2JBbyWAbV{b0fdjzTI0WG> z^WMGvqwVAgh0QsU97s@AT<3~0iZH_a>q zEMBZFUA5|gM{8^Sf#Tvto=gCS!Bc~LeB>w?xBFkJIOf<{ue`i`bx%*v8LQRnu069T z4-XHsb?a8^lKeTKSH-?_=MmdYq;+I9d{i%=Jb98Wo3~gHp8)-;<{iI9kygGm<3s7J za{9VYGBPqkZ*MOH0|Ul;aE=>H;ad)J<;s=GSS)r_NU`o_Uv!f)$=Iba5~B%Doj%34 z-+XIt>X->J=9F)_Xf?(p%jNeS@GLn_cc-V;B=D}{yGe}O!eJl=3_9$~9sQ*GKkH)C Uw!?IYy#N3J07*qoM6N<$f)br{CIA2c diff --git a/openpype/hosts/nuke/startup/KnobScripter/icons/icon_run.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_run.png deleted file mode 100644 index 6b2e4ddc2348254824b75b9d14b03c5eb28430fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2341 zcmV+=3EK9FP)@28X)BXK4_L%0;0i^ZWhi?r}hV z*v}W9YceC#urMm2nF3QtEQ$hS=7ZC_J@-hGib}X~Arr76AbjgV-n) zZOk#|FV0?U@^GH1!GXc+9LG(?HrsGyW@J8-zcRn1udlabe0<`LT3u023Q3x&0OeyT zj%C@U-5R$X0v(3q36f(l!~@Av!9wVpeE^!G=9HZ?glraA)#mOQ&8@9>escD+t- z7@HWIuvS~k@7}%J9bu|b=|>^bq9I|0n+q1Is;b`p<^KJjvLPAx1f{7l-mxTtt@R#w&$3LrIAn^Ia@)&gLaSbpWo)pH!@@rqHE z(PVqSh&!Ttz2Ho0x0{p=N+!WMl*Yo`(>c zj*N`BcJHqLacODk@f$a8XzX@709=ud(bm@1ym8Z}@AH0_!eX)D*s)`;0O*}5E(!W) z8kTZKDX=`2mUoblkeDc@b`*Sa0l*=A;Og(UyZZZo0H9jZ>`Q4M-QC?My1Tna0Bi+t z2|%l~ytA{j#mjL%tyY^%2!YXPgvn&uF+4oHS3X~t)O0;PEnWX$`az(suI@)pr}Kn-9Iw~wvDs{AH*VVaviLNjqM{yS)jg9`=H{L)`Pfq~AdDdKnAmlS3WV*Szxp@FqvL~)6 ztFpk5q=#zj_0BuL-uT{;BUd?&n^Cc=>pEV0?WeeP>((gv>z9DE?a0W;g;S?aecS1D z?gp?PD)(ilPoF*%2uuekrMP$R9vlt_02DIk9gOAKP>8f>xSc47OhIXLbMx9WXU^;? zDk|EOot>@qcsy`8dU5mSFuAf~eaG^>{o7E?>U<8vvUDm;p=z_$PqANWw%5m&^4Bm&IsVOO_tgK82NKK^I7zmN3XfiIotOgdJ+1Kn|p-{IDwDTF9QpsBmat?uzrs(B^It-^E93X3?xCx@ z3-|r^zW^VC`lA;(RK6a-jK?AK7!v?40`NWQiNp~{9C5@EM+E#Ie<;2-)aFSU00000 LNkvXXu0mjfRQGr7 diff --git a/openpype/hosts/nuke/startup/KnobScripter/icons/icon_save.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_save.png deleted file mode 100644 index e29c667f3455f4afa92f63ba8de4ce3c4b53a866..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1784 zcmV+f?nK~v}qSZF0fhEyVF|mpGAYx!?s%GY?F1qL{o1(s$SZj9$F?o=Z*azEe z5&XkG>C)YIg^qROn{~A<2#Zi#vx3;hAVph<+Lq2Fo%#82&$)ljJ(IbU%ox}07l!0} z&)jo=|IY9He&-Hc!4+IVBcbdISEbV}PmhjH-+SMEkF~eAUk?hDpajI+QiUrehy;&V z4WF5r`TPF;`=2V8%cWWrYql@!*zwFO5B=f6XVd9)tM?og5;{4@$pa%K<+t2=^POFt zn{GUE^jKa*!ZR<6lzrj;`|o|syIlc-LIO`9%)CFt8?V2?#fuk{KBH!2G8y*l*~6wy zT_|8Uo82xbH9fueiBc(<=VBfk8y%fCmID*39xs(j?0R+=mo8nZivp?U6m7q0Bb~mA zRB9bdOG^MOEG$qg78xA8$9pu8$=p29xvBG}V@HlA@?4XBK>(%jV=K%Hg#yK55#RuD zt*7zIJ{vb~{O!T~!5{PaJo)^)y!Wp|jE#={)r_AzcW!4i%!O<=H!?mx{*R`nrkF#j zDuz-7D_NT>YM{%&4&aAslP`zP&d#3J*0y$K(w9JYPxnAbkki33K(AF{IGg>`_{7A4 z<|@j?6%#>B0BUoIQP9Ihpf0hf5JZ$A06AdVHKsyx<+8&gFN{wdSk7~C#n7Qg;HYqD z))QWs3!uXD_qoQxTm-V&;gN}n%F4wQ^CM5WdEFt}YS#STbAvG#A0Ez*j8BX^!?Hs1 zFe_GByxvfV(g9}8FJ?~z5T(V5^W1QDPGZf>gQ8mb8rebl;A0E?NkAQ^a zGW>=cI-bDVy4;FIG9zZMZU{tq8^3FQdrWj@bjW!t@O=2*3LevoN?E+#@UtC3#tIKx zW3lf>vK!sudn)NlUz%)t>&>@*clPYrZT9q4JFq0apwxpa zrj&%LM4jy;%gW-emEx#8yl+YkJXES$d|Ytz7&~+dL}~Pm=CUa`y}wBvqTAIdg{d=g-F`tQVA0bai#n*4CyYBz&x7^EezE zv%u=+Bj5=7L&gZWHvZ{Ib!T+Wv(R$4h+=G>r7)Qg{uJYHN{Vs>^G;0xd(;Pb|4 z6oEQ^{P;g}x!j1^#Lb58^CaoszY%;F8@{x-NV!}Fc#@w-DF9hqEY3t%Ql&yFfK#zZ zGF7n-2v!eIh(HvZ#}ThQtexEEYl9$p zdCJ&44#$BhwT5%Uu@aG-B~`q*Tw9k#JCk@nk*K87HHyB+uBO)pri1q@`pB_(h=<_z zn6FTXuc6btc8JP>fd2me4JbDFub6a4ZzC3pWs!1_cJx1M zoEd{-ZCxq)JCH7hlXTEu&&|#D^z__rl<_AONTpH?4Gpam%BWj*4|-K@JdPedTu^$4Ew_1;NZ5Ym!|UTn%4!{MnU}8Idqt!4|=Ev_l=p1&&|y- zHa13~Q1~10Ohv^2GMUVmTeoh_v|rQivh-zy!}ah)HSPt9pgi6QY&N`1nfbYS4!w7X zLZJY#3wYh7PZ-;PMS_+2OaGh%+QJGfmlEy(9sxE~v%(sp4Ez^(6YqyLKHRTre+Br} aD)tkwJPu`6Ec-S90000yBjY@lTK?(Xe}yLVYEo0pr= zD#QQVvv=<~&u{N}&U5a07H;T+INX�v)R0;sIYMD(p#66WSO%Tr>3PYNSu{8 z8yQAMd;{nH)z{P2-R*X{T!)SvJ#ygUg^MFI;deY~2Gi+u`n&G>-n#Yc%b!`aDEAH_ zK$caLGV#WeB>C-j`<|U|zWL(GFTZS`8I1pyCa@)omlRb!UG=Ih$CgiI@&XxIVVKky z!t%TCzSrE;)U?It^9{}fOsu8^CWH_b6%TK%tbC+Sr_<{rvy42A$gBkj83K}o!C*w_ zl(Ec}*VXaBz(DV|ZQC}sx3@e0FJOAT-mrP|mgY70lvOAbi`*_`!od(Ow;QL^iQDbL z>m6Wtcm#vNKuSsq3l=P-pkM_hCB<8>XAJ7jNfq6gfE{M%11R}^0pNb%w}5vFe!l>kdU|?vUaxo6%9SgH5Xvu<^6iwH z0)aq^$K!cV8&YKM<6SH*E!|+V+45!8s~o1jp@F`$X8}F~3V=fzeTRVOTU%Q5#p{O$52|abALU7XIUVN_{q?m!zE(9lIvN?u zBqt~9@4Ij9pQmFqE|}SD$;->jy#tgzDijLR{QB#PXV}2ZKmjm%Vf2-qJ9i#cQi$T< zo_p4m0V!H-#sw=XDqJhe%H|6)pML7*tP+B6p*{TK>VpqHs0@cg$jTm$)n?VFrKLTs z)m~h%Wy_Y8#3s!nN7?`+;CZdi#?#Qy(20L^bexP6Fe6n#JoWeYACE3FVBWlW z0C#G!byYASgfN**iAt6iNPz%8pAW9V+KScC(9k(qmLuyLX3a_hSggg?Rl#IgR>K?s zys7MtTva@Ve1)yQ%r{esPEO{l2&>0h)sil@ZH#H7ueH#h5a%6@_@^Tml1 zYNhi&5YS?G+$`tydQYXNr&r!`OU@j%ZMJ-Q{;elZp6obv>Qtu|hp|$7DYxH#dzo5F z$zhqg`a1gi`vHCd^k}g?Vft=wZ}*gyt*M+ndzLyKN=iz8G%zr5rmL&VHSW#hC@~>1 zsivmpjneOzmaCr$1bg@I<(+rl0r&){(Q1D}Fu&j5bNu-6!n^NYontU4(W2Mub;ZRc z52mEtl-kkJaWoJJj7`O<&1TErv17;Ex7~JInOgCx+t>N?{WzVDa5yZl1I|nxNztS> z+;iv7z2Db&_VME4l0>2A4gqblp_Ws90;ZUgepJ%@C`~8>3vy+{jz4VDEp7_l# ztEzsVHg|4Dw0lYvkF0D97PF<>>2&@&91e%If=#6pd}qn3RUg)F-D)r>s{IMiSQ_hT2 zt^L})UT=GAYwLrOBpJS&n@fVx7`Y^3BMa31FV6VHi4(l|(n};IC1EyOh$#`H-?dmQ zc@~Rhsk5zZUpO2NT}v>4&qJY5tHZs4h&8ex(Nl8)hh$V;}OOk}s;pEjn zyh>A36TQ8?IP4CrHY=HCbJT%AD3Oi=mu0amZJQ$8bPoO!foFkV0XNP6&V2Hh-XAN>OZbar%b{PWMbG!TX*VR2xTwHwh@J8Tp9Nxce zdt-TUl3_U$Cz^ABO+W>(bQ0h9qqG?Q1pE!<(VC0~Mgm5!*R2w4;N{=EB<;{0?1Y{8C1&#tAqtt+3mC0x{?5?Y;SKzKV3B4TJPN(zj zhK7a>!Ql9CGYvLfnT$r`?z%`=s#1@wlmWCk+FI%x>er74cTL}jH7FDghxgbWj^#G1 zHP>u5U%AJjJc&%RIoD>hEq6E^d*UUOuNxRd;2aLea;w#vdquVsBDy7*&E{OI)fyLW z#^0@GNC8Qb)-^OV9B6B6i+-rI<}RZe%CS^bUuQ5-;p~p(R+}}~Y*AW_ZnvB2>T0#`cn;Wwa<1@=lgVH-wlp?2 z$}hkCvYeQZAfx1zL%>x-@tY)*(P(T*xXz$dnNa?faER*&N|lN05bB1mTm1)mZLGb( S3=G}?0000A0%^ diff --git a/openpype/hosts/nuke/startup/KnobScripter/icons/icon_snippets.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_snippets.png deleted file mode 100644 index 479c44f19e3c0c824c0491b4c176123eb1547197..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1415 zcmV;21$g?2P)KAK4h#sKNeKn0cL4TArF8tM(uGMt}G2RbOnLZ=zHx zSMSNqJt>xm{eAib042}P^L*}=0s!LuM`SWtz*40$z45-m)Ya8xnAi~>9v-dHXnvPU zrQFeD$Nn7X>l<6YVZ*ypsgyrFJnX)F`EtjVD_1(6JbB_AbB(#>tXvo~H%7i@^_n@c zF|h%4cj~(K74B;+DlS?a8yzdB0sH{rv+@mgOV>#pu|m zr|EuE&%%U-K_-*2f6JDv#mwHld1u4I!lGJRTWz;**Y)^B0D_vDs}`ft*bX2Yz##yw z02}~D0Camio*U)mWs5pH%{G>0CEgMeFoYm4KdKiV!w6y45F4riicMTaI zJ32ZZewmT6-DEN?gD)+BNF@>hUmzR-s6Y9xprB^Shsh~6TbENTmbj%-sm$kmLMXgt zd^N7uH{4XI_Gefumd7C>A#atJm){Hu3Jz;*)ITMDe7>-xxVSm~{RMNp-lflMJcWC+ z^x2qBvvy!$V5qRLaQ(O0*~LLKgB7hUZT9Wkx7{HCz_M(xTCHwYC=@eZ_kzBNTc277 zGdE|}_1JrwCtikdg<`=E2M^Y9GMTX5WE{M7@sg31v%&y*fPiH=ft+InLb2G-Un=2A zB>sG{SmY-Z3K*Y5y}k9_U4o$@himKBZMWOn+S&l*OnHLh;^NXuN=km_^ZCp{wffdC z=g+GEumHI6Q3gN+Kng&@Fg#IIRAf-nlBAGzYuCNwC-7rH0k_LdcjxY|Z>VoD0@w}U zFWzJ!tJV6@Y&H)kCnqmUNJxm_@pwo~Oq3)gC51#rMh*-O4OQCh_M-qU0Qi+sdakFZ z=h%Y>53VVfDp$!lIZFsZsjBpkT3u}ifIFMbe8#iwJ8a@k87>nR~P0fXy zw{D#n931RtSvD{xCT7tGNlBlF&YCrc0yuu+M9Y!Ghg$%Y1DJSJpDP3aSy@?CX=!OI zynQ8@koy;Zc9_b_%J#CdvL*m$0i2o~Z%XMVL$BALi;j*?3k?ko27nv2wU*)0Q8z;f zal2f!%Vrxk7z{mOVPXCZ!;pLT?%4{9iW)s0PZfY8)5U!*ghGM8T7A%@(`o7H)2E#r z#}xqB58${+BtBPBQPHi{YN%yH=;8DwV2}$KwSu z48tobD!QoCYU!CXX9fai%%}!X3P3nroM}tv!^p_U-v)!>#4c^AT z{j&Wp&BzC<)!N+N-rg4*8~c&N;c%6fmNwPb*P8+4188~U3|@dl06zivaeV&Y!av`O VU4IwAq!s`G002ovPDHLkV1g(>ppyUq diff --git a/openpype/hosts/nuke/startup/KnobScripter/knob_scripter.py b/openpype/hosts/nuke/startup/KnobScripter/knob_scripter.py deleted file mode 100644 index 368ee64e32..0000000000 --- a/openpype/hosts/nuke/startup/KnobScripter/knob_scripter.py +++ /dev/null @@ -1,4196 +0,0 @@ -# ------------------------------------------------- -# KnobScripter by Adrian Pueyo -# Complete python script editor for Nuke -# adrianpueyo.com, 2016-2019 -import string -import traceback -from webbrowser import open as openUrl -from threading import Event, Thread -import platform -import subprocess -from functools import partial -import re -import sys -from nukescripts import panels -import json -import os -import nuke -version = "2.3 wip" -date = "Aug 12 2019" -# ------------------------------------------------- - - -# Symlinks on windows... -if os.name == "nt": - def symlink_ms(source, link_name): - import ctypes - csl = ctypes.windll.kernel32.CreateSymbolicLinkW - csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32) - csl.restype = ctypes.c_ubyte - flags = 1 if os.path.isdir(source) else 0 - try: - if csl(link_name, source.replace('/', '\\'), flags) == 0: - raise ctypes.WinError() - except: - pass - os.symlink = symlink_ms - -try: - if nuke.NUKE_VERSION_MAJOR < 11: - from PySide import QtCore, QtGui, QtGui as QtWidgets - from PySide.QtCore import Qt - else: - from PySide2 import QtWidgets, QtGui, QtCore - from PySide2.QtCore import Qt -except ImportError: - from Qt import QtCore, QtGui, QtWidgets - -KS_DIR = os.path.dirname(__file__) -icons_path = KS_DIR + "/icons/" -DebugMode = False -AllKnobScripters = [] # All open instances at a given time - -PrefsPanel = "" -SnippetEditPanel = "" - -nuke.tprint('KnobScripter v{}, built {}.\nCopyright (c) 2016-2019 Adrian Pueyo. All Rights Reserved.'.format(version, date)) - - -class KnobScripter(QtWidgets.QWidget): - - def __init__(self, node="", knob="knobChanged"): - super(KnobScripter, self).__init__() - - # Autosave the other knobscripters and add this one - for ks in AllKnobScripters: - try: - ks.autosave() - except: - pass - if self not in AllKnobScripters: - AllKnobScripters.append(self) - - self.nodeMode = (node != "") - if node == "": - self.node = nuke.toNode("root") - else: - self.node = node - - self.isPane = False - self.knob = knob - # For the option to also display the knob labels on the knob dropdown - self.show_labels = False - self.unsavedKnobs = {} - self.modifiedKnobs = set() - self.scrollPos = {} - self.cursorPos = {} - self.fontSize = 10 - self.font = "Monospace" - self.tabSpaces = 4 - self.windowDefaultSize = [500, 300] - self.color_scheme = "sublime" # Can be nuke or sublime - self.pinned = 1 - self.toLoadKnob = True - self.frw_open = False # Find replace widget closed by default - self.icon_size = 17 - self.btn_size = 24 - self.qt_icon_size = QtCore.QSize(self.icon_size, self.icon_size) - self.qt_btn_size = QtCore.QSize(self.btn_size, self.btn_size) - self.origConsoleText = "" - self.nukeSE = self.findSE() - self.nukeSEOutput = self.findSEOutput(self.nukeSE) - self.nukeSEInput = self.findSEInput(self.nukeSE) - self.nukeSERunBtn = self.findSERunBtn(self.nukeSE) - - self.scripts_dir = os.path.expandvars( - os.path.expanduser("~/.nuke/KnobScripter_Scripts")) - self.current_folder = "scripts" - self.folder_index = 0 - self.current_script = "Untitled.py" - self.current_script_modified = False - self.script_index = 0 - self.toAutosave = False - - # Load prefs - self.prefs_txt = os.path.expandvars( - os.path.expanduser("~/.nuke/KnobScripter_Prefs.txt")) - self.loadedPrefs = self.loadPrefs() - if self.loadedPrefs != []: - try: - if "font_size" in self.loadedPrefs: - self.fontSize = self.loadedPrefs['font_size'] - self.windowDefaultSize = [ - self.loadedPrefs['window_default_w'], self.loadedPrefs['window_default_h']] - self.tabSpaces = self.loadedPrefs['tab_spaces'] - self.pinned = self.loadedPrefs['pin_default'] - if "font" in self.loadedPrefs: - self.font = self.loadedPrefs['font'] - if "color_scheme" in self.loadedPrefs: - self.color_scheme = self.loadedPrefs['color_scheme'] - if "show_labels" in self.loadedPrefs: - self.show_labels = self.loadedPrefs['show_labels'] - except TypeError: - log("KnobScripter: Failed to load preferences.") - - # Load snippets - self.snippets_txt_path = os.path.expandvars( - os.path.expanduser("~/.nuke/KnobScripter_Snippets.txt")) - self.snippets = self.loadSnippets(maxDepth=5) - - # Current state of script (loaded when exiting node mode) - self.state_txt_path = os.path.expandvars( - os.path.expanduser("~/.nuke/KnobScripter_State.txt")) - - # Init UI - self.initUI() - - # Talk to Nuke's Script Editor - self.setSEOutputEvent() # Make the output windowS listen! - self.clearConsole() - - def initUI(self): - ''' Initializes the tool UI''' - # ------------------- - # 1. MAIN WINDOW - # ------------------- - self.resize(self.windowDefaultSize[0], self.windowDefaultSize[1]) - self.setWindowTitle("KnobScripter - %s %s" % - (self.node.fullName(), self.knob)) - self.setObjectName("com.adrianpueyo.knobscripter") - self.move(QtGui.QCursor().pos() - QtCore.QPoint(32, 74)) - - # --------------------- - # 2. TOP BAR - # --------------------- - # --- - # 2.1. Left buttons - self.change_btn = QtWidgets.QToolButton() - # self.exit_node_btn.setIcon(QtGui.QIcon(KS_DIR+"/KnobScripter/icons/icons8-delete-26.png")) - self.change_btn.setIcon(QtGui.QIcon(icons_path + "icon_pick.png")) - self.change_btn.setIconSize(self.qt_icon_size) - self.change_btn.setFixedSize(self.qt_btn_size) - self.change_btn.setToolTip( - "Change to node if selected. Otherwise, change to Script Mode.") - self.change_btn.clicked.connect(self.changeClicked) - - # --- - # 2.2.A. Node mode UI - self.exit_node_btn = QtWidgets.QToolButton() - self.exit_node_btn.setIcon(QtGui.QIcon( - icons_path + "icon_exitnode.png")) - self.exit_node_btn.setIconSize(self.qt_icon_size) - self.exit_node_btn.setFixedSize(self.qt_btn_size) - self.exit_node_btn.setToolTip( - "Exit the node, and change to Script Mode.") - self.exit_node_btn.clicked.connect(self.exitNodeMode) - self.current_node_label_node = QtWidgets.QLabel(" Node:") - self.current_node_label_name = QtWidgets.QLabel(self.node.fullName()) - self.current_node_label_name.setStyleSheet("font-weight:bold;") - self.current_knob_label = QtWidgets.QLabel("Knob: ") - self.current_knob_dropdown = QtWidgets.QComboBox() - self.current_knob_dropdown.setSizeAdjustPolicy( - QtWidgets.QComboBox.AdjustToContents) - self.updateKnobDropdown() - self.current_knob_dropdown.currentIndexChanged.connect( - lambda: self.loadKnobValue(False, updateDict=True)) - - # Layout - self.node_mode_bar_layout = QtWidgets.QHBoxLayout() - self.node_mode_bar_layout.addWidget(self.exit_node_btn) - self.node_mode_bar_layout.addSpacing(2) - self.node_mode_bar_layout.addWidget(self.current_node_label_node) - self.node_mode_bar_layout.addWidget(self.current_node_label_name) - self.node_mode_bar_layout.addSpacing(2) - self.node_mode_bar_layout.addWidget(self.current_knob_dropdown) - self.node_mode_bar = QtWidgets.QWidget() - self.node_mode_bar.setLayout(self.node_mode_bar_layout) - - self.node_mode_bar_layout.setContentsMargins(0, 0, 0, 0) - - # --- - # 2.2.B. Script mode UI - self.script_label = QtWidgets.QLabel("Script: ") - - self.current_folder_dropdown = QtWidgets.QComboBox() - self.current_folder_dropdown.setSizeAdjustPolicy( - QtWidgets.QComboBox.AdjustToContents) - self.current_folder_dropdown.currentIndexChanged.connect( - self.folderDropdownChanged) - # self.current_folder_dropdown.setEditable(True) - # self.current_folder_dropdown.lineEdit().setReadOnly(True) - # self.current_folder_dropdown.lineEdit().setAlignment(Qt.AlignRight) - - self.current_script_dropdown = QtWidgets.QComboBox() - self.current_script_dropdown.setSizeAdjustPolicy( - QtWidgets.QComboBox.AdjustToContents) - self.updateFoldersDropdown() - self.updateScriptsDropdown() - self.current_script_dropdown.currentIndexChanged.connect( - self.scriptDropdownChanged) - - # Layout - self.script_mode_bar_layout = QtWidgets.QHBoxLayout() - self.script_mode_bar_layout.addWidget(self.script_label) - self.script_mode_bar_layout.addSpacing(2) - self.script_mode_bar_layout.addWidget(self.current_folder_dropdown) - self.script_mode_bar_layout.addWidget(self.current_script_dropdown) - self.script_mode_bar = QtWidgets.QWidget() - self.script_mode_bar.setLayout(self.script_mode_bar_layout) - - self.script_mode_bar_layout.setContentsMargins(0, 0, 0, 0) - - # --- - # 2.3. File-system buttons - # Refresh dropdowns - self.refresh_btn = QtWidgets.QToolButton() - self.refresh_btn.setIcon(QtGui.QIcon(icons_path + "icon_refresh.png")) - self.refresh_btn.setIconSize(QtCore.QSize(50, 50)) - self.refresh_btn.setIconSize(self.qt_icon_size) - self.refresh_btn.setFixedSize(self.qt_btn_size) - self.refresh_btn.setToolTip("Refresh the dropdowns.\nShortcut: F5") - self.refresh_btn.setShortcut('F5') - self.refresh_btn.clicked.connect(self.refreshClicked) - - # Reload script - self.reload_btn = QtWidgets.QToolButton() - self.reload_btn.setIcon(QtGui.QIcon(icons_path + "icon_download.png")) - self.reload_btn.setIconSize(QtCore.QSize(50, 50)) - self.reload_btn.setIconSize(self.qt_icon_size) - self.reload_btn.setFixedSize(self.qt_btn_size) - self.reload_btn.setToolTip( - "Reload the current script. Will overwrite any changes made to it.\nShortcut: Ctrl+R") - self.reload_btn.setShortcut('Ctrl+R') - self.reload_btn.clicked.connect(self.reloadClicked) - - # Save script - self.save_btn = QtWidgets.QToolButton() - self.save_btn.setIcon(QtGui.QIcon(icons_path + "icon_save.png")) - self.save_btn.setIconSize(QtCore.QSize(50, 50)) - self.save_btn.setIconSize(self.qt_icon_size) - self.save_btn.setFixedSize(self.qt_btn_size) - self.save_btn.setToolTip( - "Save the script into the selected knob or python file.\nShortcut: Ctrl+S") - self.save_btn.setShortcut('Ctrl+S') - self.save_btn.clicked.connect(self.saveClicked) - - # Layout - self.top_file_bar_layout = QtWidgets.QHBoxLayout() - self.top_file_bar_layout.addWidget(self.refresh_btn) - self.top_file_bar_layout.addWidget(self.reload_btn) - self.top_file_bar_layout.addWidget(self.save_btn) - - # --- - # 2.4. Right Side buttons - - # Run script - self.run_script_button = QtWidgets.QToolButton() - self.run_script_button.setIcon( - QtGui.QIcon(icons_path + "icon_run.png")) - self.run_script_button.setIconSize(self.qt_icon_size) - # self.run_script_button.setIconSize(self.qt_icon_size) - self.run_script_button.setFixedSize(self.qt_btn_size) - self.run_script_button.setToolTip( - "Execute the current selection on the KnobScripter, or the whole script if no selection.\nShortcut: Ctrl+Enter") - self.run_script_button.clicked.connect(self.runScript) - - # Clear console - self.clear_console_button = QtWidgets.QToolButton() - self.clear_console_button.setIcon( - QtGui.QIcon(icons_path + "icon_clearConsole.png")) - self.clear_console_button.setIconSize(QtCore.QSize(50, 50)) - self.clear_console_button.setIconSize(self.qt_icon_size) - self.clear_console_button.setFixedSize(self.qt_btn_size) - self.clear_console_button.setToolTip( - "Clear the text in the console window.\nShortcut: Click Backspace on the console.") - self.clear_console_button.clicked.connect(self.clearConsole) - - # FindReplace button - self.find_button = QtWidgets.QToolButton() - self.find_button.setIcon(QtGui.QIcon(icons_path + "icon_search.png")) - self.find_button.setIconSize(self.qt_icon_size) - self.find_button.setFixedSize(self.qt_btn_size) - self.find_button.setToolTip( - "Call the snippets by writing the shortcut and pressing Tab.\nShortcut: Ctrl+F") - self.find_button.setShortcut('Ctrl+F') - #self.find_button.setMaximumWidth(self.find_button.fontMetrics().boundingRect("Find").width() + 20) - self.find_button.setCheckable(True) - self.find_button.setFocusPolicy(QtCore.Qt.NoFocus) - self.find_button.clicked[bool].connect(self.toggleFRW) - if self.frw_open: - self.find_button.toggle() - - # Snippets - self.snippets_button = QtWidgets.QToolButton() - self.snippets_button.setIcon( - QtGui.QIcon(icons_path + "icon_snippets.png")) - self.snippets_button.setIconSize(QtCore.QSize(50, 50)) - self.snippets_button.setIconSize(self.qt_icon_size) - self.snippets_button.setFixedSize(self.qt_btn_size) - self.snippets_button.setToolTip( - "Call the snippets by writing the shortcut and pressing Tab.") - self.snippets_button.clicked.connect(self.openSnippets) - - # PIN - ''' - self.pin_button = QtWidgets.QPushButton("P") - self.pin_button.setCheckable(True) - if self.pinned: - self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) - self.pin_button.toggle() - self.pin_button.setToolTip("Toggle 'Always On Top'. Keeps the KnobScripter on top of all other windows.") - self.pin_button.setFocusPolicy(QtCore.Qt.NoFocus) - self.pin_button.setFixedSize(self.qt_btn_size) - self.pin_button.clicked[bool].connect(self.pin) - ''' - - # Prefs - self.createPrefsMenu() - self.prefs_button = QtWidgets.QPushButton() - self.prefs_button.setIcon(QtGui.QIcon(icons_path + "icon_prefs.png")) - self.prefs_button.setIconSize(self.qt_icon_size) - self.prefs_button.setFixedSize( - QtCore.QSize(self.btn_size + 10, self.btn_size)) - # self.prefs_button.clicked.connect(self.openPrefs) - self.prefs_button.setMenu(self.prefsMenu) - self.prefs_button.setStyleSheet("text-align:left;padding-left:2px;") - #self.prefs_button.setMaximumWidth(self.prefs_button.fontMetrics().boundingRect("Prefs").width() + 12) - - # Layout - self.top_right_bar_layout = QtWidgets.QHBoxLayout() - self.top_right_bar_layout.addWidget(self.run_script_button) - self.top_right_bar_layout.addWidget(self.clear_console_button) - self.top_right_bar_layout.addWidget(self.find_button) - # self.top_right_bar_layout.addWidget(self.snippets_button) - # self.top_right_bar_layout.addWidget(self.pin_button) - # self.top_right_bar_layout.addSpacing(10) - self.top_right_bar_layout.addWidget(self.prefs_button) - - # --- - # Layout - self.top_layout = QtWidgets.QHBoxLayout() - self.top_layout.setContentsMargins(0, 0, 0, 0) - # self.top_layout.setSpacing(10) - self.top_layout.addWidget(self.change_btn) - self.top_layout.addWidget(self.node_mode_bar) - self.top_layout.addWidget(self.script_mode_bar) - self.node_mode_bar.setVisible(False) - # self.top_layout.addSpacing(10) - self.top_layout.addLayout(self.top_file_bar_layout) - self.top_layout.addStretch() - self.top_layout.addLayout(self.top_right_bar_layout) - - # ---------------------- - # 3. SCRIPTING SECTION - # ---------------------- - # Splitter - self.splitter = QtWidgets.QSplitter(Qt.Vertical) - - # Output widget - self.script_output = ScriptOutputWidget(parent=self) - self.script_output.setReadOnly(1) - self.script_output.setAcceptRichText(0) - self.script_output.setTabStopWidth( - self.script_output.tabStopWidth() / 4) - self.script_output.setFocusPolicy(Qt.ClickFocus) - self.script_output.setAutoFillBackground(0) - self.script_output.installEventFilter(self) - - # Script Editor - self.script_editor = KnobScripterTextEditMain(self, self.script_output) - self.script_editor.setMinimumHeight(30) - self.script_editor.setStyleSheet( - 'background:#282828;color:#EEE;') # Main Colors - self.script_editor.textChanged.connect(self.setModified) - self.highlighter = KSScriptEditorHighlighter( - self.script_editor.document(), self) - self.script_editor.cursorPositionChanged.connect(self.setTextSelection) - self.script_editor_font = QtGui.QFont() - self.script_editor_font.setFamily(self.font) - self.script_editor_font.setStyleHint(QtGui.QFont.Monospace) - self.script_editor_font.setFixedPitch(True) - self.script_editor_font.setPointSize(self.fontSize) - self.script_editor.setFont(self.script_editor_font) - self.script_editor.setTabStopWidth( - self.tabSpaces * QtGui.QFontMetrics(self.script_editor_font).width(' ')) - - # Add input and output to splitter - self.splitter.addWidget(self.script_output) - self.splitter.addWidget(self.script_editor) - self.splitter.setStretchFactor(0, 0) - - # FindReplace widget - self.frw = FindReplaceWidget(self) - self.frw.setVisible(self.frw_open) - - # --- - # Layout - self.scripting_layout = QtWidgets.QVBoxLayout() - self.scripting_layout.setContentsMargins(0, 0, 0, 0) - self.scripting_layout.setSpacing(0) - self.scripting_layout.addWidget(self.splitter) - self.scripting_layout.addWidget(self.frw) - - # --------------- - # MASTER LAYOUT - # --------------- - self.master_layout = QtWidgets.QVBoxLayout() - self.master_layout.setSpacing(5) - self.master_layout.setContentsMargins(8, 8, 8, 8) - self.master_layout.addLayout(self.top_layout) - self.master_layout.addLayout(self.scripting_layout) - # self.master_layout.addLayout(self.bottom_layout) - self.setLayout(self.master_layout) - - # ---------------- - # MAIN WINDOW UI - # ---------------- - size_policy = QtWidgets.QSizePolicy( - QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) - self.setSizePolicy(size_policy) - self.setMinimumWidth(160) - - if self.pinned: - self.setWindowFlags(self.windowFlags() | - QtCore.Qt.WindowStaysOnTopHint) - - # Set default values based on mode - if self.nodeMode: - self.current_knob_dropdown.blockSignals(True) - self.node_mode_bar.setVisible(True) - self.script_mode_bar.setVisible(False) - self.setCurrentKnob(self.knob) - self.loadKnobValue(check=False) - self.setKnobModified(False) - self.current_knob_dropdown.blockSignals(False) - self.splitter.setSizes([0, 1]) - else: - self.exitNodeMode() - self.script_editor.setFocus() - - # Preferences submenus - def createPrefsMenu(self): - - # Actions - self.echoAct = QtWidgets.QAction("Echo python commands", self, checkable=True, - statusTip="Toggle nuke's 'Echo all python commands to ScriptEditor'", triggered=self.toggleEcho) - if nuke.toNode("preferences").knob("echoAllCommands").value(): - self.echoAct.toggle() - self.pinAct = QtWidgets.QAction("Always on top", self, checkable=True, - statusTip="Keeps the KnobScripter window always on top or not.", triggered=self.togglePin) - if self.pinned: - self.setWindowFlags(self.windowFlags() | - QtCore.Qt.WindowStaysOnTopHint) - self.pinAct.toggle() - self.helpAct = QtWidgets.QAction( - "&Help", self, statusTip="Open the KnobScripter help in your browser.", shortcut="F1", triggered=self.showHelp) - self.nukepediaAct = QtWidgets.QAction( - "Show in Nukepedia", self, statusTip="Open the KnobScripter download page on Nukepedia.", triggered=self.showInNukepedia) - self.githubAct = QtWidgets.QAction( - "Show in GitHub", self, statusTip="Open the KnobScripter repo on GitHub.", triggered=self.showInGithub) - self.snippetsAct = QtWidgets.QAction( - "Snippets", self, statusTip="Open the Snippets editor.", triggered=self.openSnippets) - self.snippetsAct.setIcon(QtGui.QIcon(icons_path + "icon_snippets.png")) - # self.snippetsAct = QtWidgets.QAction("Keywords", self, statusTip="Add custom keywords.", triggered=self.openSnippets) #TODO THIS - self.prefsAct = QtWidgets.QAction( - "Preferences", self, statusTip="Open the Preferences panel.", triggered=self.openPrefs) - self.prefsAct.setIcon(QtGui.QIcon(icons_path + "icon_prefs.png")) - - # Menus - self.prefsMenu = QtWidgets.QMenu("Preferences") - self.prefsMenu.addAction(self.echoAct) - self.prefsMenu.addAction(self.pinAct) - self.prefsMenu.addSeparator() - self.prefsMenu.addAction(self.nukepediaAct) - self.prefsMenu.addAction(self.githubAct) - self.prefsMenu.addSeparator() - self.prefsMenu.addAction(self.helpAct) - self.prefsMenu.addSeparator() - self.prefsMenu.addAction(self.snippetsAct) - self.prefsMenu.addAction(self.prefsAct) - - def initEcho(self): - ''' Initializes the echo chechable QAction based on nuke's state ''' - echo_knob = nuke.toNode("preferences").knob("echoAllCommands") - self.echoAct.setChecked(echo_knob.value()) - - def toggleEcho(self): - ''' Toggle the "Echo python commands" from Nuke ''' - echo_knob = nuke.toNode("preferences").knob("echoAllCommands") - echo_knob.setValue(self.echoAct.isChecked()) - - def togglePin(self): - ''' Toggle "always on top" based on the submenu button ''' - self.pin(self.pinAct.isChecked()) - - def showInNukepedia(self): - openUrl("http://www.nukepedia.com/python/ui/knobscripter") - - def showInGithub(self): - openUrl("https://github.com/adrianpueyo/KnobScripter") - - def showHelp(self): - openUrl("https://vimeo.com/adrianpueyo/knobscripter2") - - # Node Mode - - def updateKnobDropdown(self): - ''' Populate knob dropdown list ''' - self.current_knob_dropdown.clear() # First remove all items - defaultKnobs = ["knobChanged", "onCreate", "onScriptLoad", "onScriptSave", "onScriptClose", "onDestroy", - "updateUI", "autolabel", "beforeRender", "beforeFrameRender", "afterFrameRender", "afterRender"] - permittedKnobClasses = ["PyScript_Knob", "PythonCustomKnob"] - counter = 0 - for i in self.node.knobs(): - if i not in defaultKnobs and self.node.knob(i).Class() in permittedKnobClasses: - if self.show_labels: - i_full = "{} ({})".format(self.node.knob(i).label(), i) - else: - i_full = i - - if i in self.unsavedKnobs.keys(): - self.current_knob_dropdown.addItem(i_full + "(*)", i) - else: - self.current_knob_dropdown.addItem(i_full, i) - - counter += 1 - if counter > 0: - self.current_knob_dropdown.insertSeparator(counter) - counter += 1 - self.current_knob_dropdown.insertSeparator(counter) - counter += 1 - for i in self.node.knobs(): - if i in defaultKnobs: - if i in self.unsavedKnobs.keys(): - self.current_knob_dropdown.addItem(i + "(*)", i) - else: - self.current_knob_dropdown.addItem(i, i) - counter += 1 - return - - def loadKnobValue(self, check=True, updateDict=False): - ''' Get the content of the knob value and populate the editor ''' - if self.toLoadKnob == False: - return - dropdown_value = self.current_knob_dropdown.itemData( - self.current_knob_dropdown.currentIndex()) # knobChanged... - try: - obtained_knobValue = str(self.node[dropdown_value].value()) - obtained_scrollValue = 0 - edited_knobValue = self.script_editor.toPlainText() - except: - error_message = QtWidgets.QMessageBox.information( - None, "", "Unable to find %s.%s" % (self.node.name(), dropdown_value)) - error_message.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - error_message.exec_() - return - # If there were changes to the previous knob, update the dictionary - if updateDict == True: - self.unsavedKnobs[self.knob] = edited_knobValue - self.scrollPos[self.knob] = self.script_editor.verticalScrollBar( - ).value() - prev_knob = self.knob # knobChanged... - - self.knob = self.current_knob_dropdown.itemData( - self.current_knob_dropdown.currentIndex()) # knobChanged... - - if check and obtained_knobValue != edited_knobValue: - msgBox = QtWidgets.QMessageBox() - msgBox.setText("The Script Editor has been modified.") - msgBox.setInformativeText( - "Do you want to overwrite the current code on this editor?") - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - self.setCurrentKnob(prev_knob) - return - # If order comes from a dropdown update, update value from dictionary if possible, otherwise update normally - self.setWindowTitle("KnobScripter - %s %s" % - (self.node.name(), self.knob)) - if updateDict: - if self.knob in self.unsavedKnobs: - if self.unsavedKnobs[self.knob] == obtained_knobValue: - self.script_editor.setPlainText(obtained_knobValue) - self.setKnobModified(False) - else: - obtained_knobValue = self.unsavedKnobs[self.knob] - self.script_editor.setPlainText(obtained_knobValue) - self.setKnobModified(True) - else: - self.script_editor.setPlainText(obtained_knobValue) - self.setKnobModified(False) - - if self.knob in self.scrollPos: - obtained_scrollValue = self.scrollPos[self.knob] - else: - self.script_editor.setPlainText(obtained_knobValue) - - cursor = self.script_editor.textCursor() - self.script_editor.setTextCursor(cursor) - self.script_editor.verticalScrollBar().setValue(obtained_scrollValue) - return - - def loadAllKnobValues(self): - ''' Load all knobs button's function ''' - if len(self.unsavedKnobs) >= 1: - msgBox = QtWidgets.QMessageBox() - msgBox.setText( - "Do you want to reload all python and callback knobs?") - msgBox.setInformativeText( - "Unsaved changes on this editor will be lost.") - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - return - self.unsavedKnobs = {} - return - - def saveKnobValue(self, check=True): - ''' Save the text from the editor to the node's knobChanged knob ''' - dropdown_value = self.current_knob_dropdown.itemData( - self.current_knob_dropdown.currentIndex()) - try: - obtained_knobValue = str(self.node[dropdown_value].value()) - self.knob = dropdown_value - except: - error_message = QtWidgets.QMessageBox.information( - None, "", "Unable to find %s.%s" % (self.node.name(), dropdown_value)) - error_message.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - error_message.exec_() - return - edited_knobValue = self.script_editor.toPlainText() - if check and obtained_knobValue != edited_knobValue: - msgBox = QtWidgets.QMessageBox() - msgBox.setText("Do you want to overwrite %s.%s?" % - (self.node.name(), dropdown_value)) - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - return - self.node[dropdown_value].setValue(edited_knobValue) - self.setKnobModified( - modified=False, knob=dropdown_value, changeTitle=True) - nuke.tcl("modified 1") - if self.knob in self.unsavedKnobs: - del self.unsavedKnobs[self.knob] - return - - def saveAllKnobValues(self, check=True): - ''' Save all knobs button's function ''' - if self.updateUnsavedKnobs() > 0 and check: - msgBox = QtWidgets.QMessageBox() - msgBox.setText( - "Do you want to save all modified python and callback knobs?") - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - return - saveErrors = 0 - savedCount = 0 - for k in self.unsavedKnobs.copy(): - try: - self.node.knob(k).setValue(self.unsavedKnobs[k]) - del self.unsavedKnobs[k] - savedCount += 1 - nuke.tcl("modified 1") - except: - saveErrors += 1 - if saveErrors > 0: - errorBox = QtWidgets.QMessageBox() - errorBox.setText("Error saving %s knob%s." % - (str(saveErrors), int(saveErrors > 1) * "s")) - errorBox.setIcon(QtWidgets.QMessageBox.Warning) - errorBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - errorBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = errorBox.exec_() - else: - log("KnobScripter: %s knobs saved" % str(savedCount)) - return - - def setCurrentKnob(self, knobToSet): - ''' Set current knob ''' - KnobDropdownItems = [] - for i in range(self.current_knob_dropdown.count()): - if self.current_knob_dropdown.itemData(i) is not None: - KnobDropdownItems.append( - self.current_knob_dropdown.itemData(i)) - else: - KnobDropdownItems.append("---") - if knobToSet in KnobDropdownItems: - index = KnobDropdownItems.index(knobToSet) - self.current_knob_dropdown.setCurrentIndex(index) - return - - def updateUnsavedKnobs(self, first_time=False): - ''' Clear unchanged knobs from the dict and return the number of unsaved knobs ''' - if not self.node: - # Node has been deleted, so simply return 0. Who cares. - return 0 - edited_knobValue = self.script_editor.toPlainText() - self.unsavedKnobs[self.knob] = edited_knobValue - if len(self.unsavedKnobs) > 0: - for k in self.unsavedKnobs.copy(): - if self.node.knob(k): - if str(self.node.knob(k).value()) == str(self.unsavedKnobs[k]): - del self.unsavedKnobs[k] - else: - del self.unsavedKnobs[k] - # Set appropriate knobs modified... - knobs_dropdown = self.current_knob_dropdown - all_knobs = [knobs_dropdown.itemData(i) - for i in range(knobs_dropdown.count())] - for key in all_knobs: - if key in self.unsavedKnobs.keys(): - self.setKnobModified( - modified=True, knob=key, changeTitle=False) - else: - self.setKnobModified( - modified=False, knob=key, changeTitle=False) - - return len(self.unsavedKnobs) - - def setKnobModified(self, modified=True, knob="", changeTitle=True): - ''' Sets the current knob modified, title and whatever else we need ''' - if knob == "": - knob = self.knob - if modified: - self.modifiedKnobs.add(knob) - else: - self.modifiedKnobs.discard(knob) - - if changeTitle: - title_modified_string = " [modified]" - windowTitle = self.windowTitle().split(title_modified_string)[0] - if modified == True: - windowTitle += title_modified_string - self.setWindowTitle(windowTitle) - - try: - knobs_dropdown = self.current_knob_dropdown - kd_index = knobs_dropdown.currentIndex() - kd_data = knobs_dropdown.itemData(kd_index) - if self.show_labels and i not in defaultKnobs: - kd_data = "{} ({})".format( - self.node.knob(kd_data).label(), kd_data) - if modified == False: - knobs_dropdown.setItemText(kd_index, kd_data) - else: - knobs_dropdown.setItemText(kd_index, kd_data + "(*)") - except: - pass - - # Script Mode - def updateFoldersDropdown(self): - ''' Populate folders dropdown list ''' - self.current_folder_dropdown.blockSignals(True) - self.current_folder_dropdown.clear() # First remove all items - defaultFolders = ["scripts"] - scriptFolders = [] - counter = 0 - for f in defaultFolders: - self.makeScriptFolder(f) - self.current_folder_dropdown.addItem(f + "/", f) - counter += 1 - - try: - scriptFolders = sorted([f for f in os.listdir(self.scripts_dir) if os.path.isdir( - os.path.join(self.scripts_dir, f))]) # Accepts symlinks!!! - except: - log("Couldn't read any script folders.") - - for f in scriptFolders: - fname = f.split("/")[-1] - if fname in defaultFolders: - continue - self.current_folder_dropdown.addItem(fname + "/", fname) - counter += 1 - - # print scriptFolders - if counter > 0: - self.current_folder_dropdown.insertSeparator(counter) - counter += 1 - # self.current_folder_dropdown.insertSeparator(counter) - #counter += 1 - self.current_folder_dropdown.addItem("New", "create new") - self.current_folder_dropdown.addItem("Open...", "open in browser") - self.current_folder_dropdown.addItem("Add custom", "add custom path") - self.folder_index = self.current_folder_dropdown.currentIndex() - self.current_folder = self.current_folder_dropdown.itemData( - self.folder_index) - self.current_folder_dropdown.blockSignals(False) - return - - def updateScriptsDropdown(self): - ''' Populate py scripts dropdown list ''' - self.current_script_dropdown.blockSignals(True) - self.current_script_dropdown.clear() # First remove all items - QtWidgets.QApplication.processEvents() - log("# Updating scripts dropdown...") - log("scripts dir:" + self.scripts_dir) - log("current folder:" + self.current_folder) - log("previous current script:" + self.current_script) - #current_folder = self.current_folder_dropdown.itemData(self.current_folder_dropdown.currentIndex()) - current_folder_path = os.path.join( - self.scripts_dir, self.current_folder) - defaultScripts = ["Untitled.py"] - found_scripts = [] - counter = 0 - # All files and folders inside of the folder - dir_list = os.listdir(current_folder_path) - try: - found_scripts = sorted([f for f in dir_list if f.endswith(".py")]) - found_temp_scripts = [ - f for f in dir_list if f.endswith(".py.autosave")] - except: - log("Couldn't find any scripts in the selected folder.") - if not len(found_scripts): - for s in defaultScripts: - if s + ".autosave" in found_temp_scripts: - self.current_script_dropdown.addItem(s + "(*)", s) - else: - self.current_script_dropdown.addItem(s, s) - counter += 1 - else: - for s in defaultScripts: - if s + ".autosave" in found_temp_scripts: - self.current_script_dropdown.addItem(s + "(*)", s) - elif s in found_scripts: - self.current_script_dropdown.addItem(s, s) - for s in found_scripts: - if s in defaultScripts: - continue - sname = s.split("/")[-1] - if s + ".autosave" in found_temp_scripts: - self.current_script_dropdown.addItem(sname + "(*)", sname) - else: - self.current_script_dropdown.addItem(sname, sname) - counter += 1 - # else: #Add the found scripts to the dropdown - if counter > 0: - counter += 1 - self.current_script_dropdown.insertSeparator(counter) - counter += 1 - self.current_script_dropdown.insertSeparator(counter) - self.current_script_dropdown.addItem("New", "create new") - self.current_script_dropdown.addItem("Duplicate", "create duplicate") - self.current_script_dropdown.addItem("Delete", "delete script") - self.current_script_dropdown.addItem("Open", "open in browser") - #self.script_index = self.current_script_dropdown.currentIndex() - self.script_index = 0 - self.current_script = self.current_script_dropdown.itemData( - self.script_index) - log("Finished updating scripts dropdown.") - log("current_script:" + self.current_script) - self.current_script_dropdown.blockSignals(False) - return - - def makeScriptFolder(self, name="scripts"): - folder_path = os.path.join(self.scripts_dir, name) - if not os.path.exists(folder_path): - try: - os.makedirs(folder_path) - return True - except: - print "Couldn't create the scripting folders.\nPlease check your OS write permissions." - return False - - def makeScriptFile(self, name="Untitled.py", folder="scripts", empty=True): - script_path = os.path.join(self.scripts_dir, self.current_folder, name) - if not os.path.isfile(script_path): - try: - self.current_script_file = open(script_path, 'w') - return True - except: - print "Couldn't create the scripting folders.\nPlease check your OS write permissions." - return False - - def setCurrentFolder(self, folderName): - ''' Set current folder ON THE DROPDOWN ONLY''' - folderList = [self.current_folder_dropdown.itemData( - i) for i in range(self.current_folder_dropdown.count())] - if folderName in folderList: - index = folderList.index(folderName) - self.current_folder_dropdown.setCurrentIndex(index) - self.current_folder = folderName - self.folder_index = self.current_folder_dropdown.currentIndex() - self.current_folder = self.current_folder_dropdown.itemData( - self.folder_index) - return - - def setCurrentScript(self, scriptName): - ''' Set current script ON THE DROPDOWN ONLY ''' - scriptList = [self.current_script_dropdown.itemData( - i) for i in range(self.current_script_dropdown.count())] - if scriptName in scriptList: - index = scriptList.index(scriptName) - self.current_script_dropdown.setCurrentIndex(index) - self.current_script = scriptName - self.script_index = self.current_script_dropdown.currentIndex() - self.current_script = self.current_script_dropdown.itemData( - self.script_index) - return - - def loadScriptContents(self, check=False, pyOnly=False, folder=""): - ''' Get the contents of the selected script and populate the editor ''' - log("# About to load script contents now.") - obtained_scrollValue = 0 - obtained_cursorPosValue = [0, 0] # Position, anchor - if folder == "": - folder = self.current_folder - script_path = os.path.join( - self.scripts_dir, folder, self.current_script) - script_path_temp = script_path + ".autosave" - if (self.current_folder + "/" + self.current_script) in self.scrollPos: - obtained_scrollValue = self.scrollPos[self.current_folder + - "/" + self.current_script] - if (self.current_folder + "/" + self.current_script) in self.cursorPos: - obtained_cursorPosValue = self.cursorPos[self.current_folder + - "/" + self.current_script] - - # 1: If autosave exists and pyOnly is false, load it - if os.path.isfile(script_path_temp) and not pyOnly: - log("Loading .py.autosave file\n---") - with open(script_path_temp, 'r') as script: - content = script.read() - self.script_editor.setPlainText(content) - self.setScriptModified(True) - self.script_editor.verticalScrollBar().setValue(obtained_scrollValue) - - # 2: Try to load the .py as first priority, if it exists - elif os.path.isfile(script_path): - log("Loading .py file\n---") - with open(script_path, 'r') as script: - content = script.read() - current_text = self.script_editor.toPlainText().encode("utf8") - if check and current_text != content and current_text.strip() != "": - msgBox = QtWidgets.QMessageBox() - msgBox.setText("The script has been modified.") - msgBox.setInformativeText( - "Do you want to overwrite the current code on this editor?") - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - return - # Clear trash - if os.path.isfile(script_path_temp): - os.remove(script_path_temp) - log("Removed " + script_path_temp) - self.setScriptModified(False) - self.script_editor.setPlainText(content) - self.script_editor.verticalScrollBar().setValue(obtained_scrollValue) - self.setScriptModified(False) - self.loadScriptState() - self.setScriptState() - - # 3: If .py doesn't exist... only then stick to the autosave - elif os.path.isfile(script_path_temp): - with open(script_path_temp, 'r') as script: - content = script.read() - - msgBox = QtWidgets.QMessageBox() - msgBox.setText("The .py file hasn't been found.") - msgBox.setInformativeText( - "Do you want to clear the current code on this editor?") - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - return - - # Clear trash - os.remove(script_path_temp) - log("Removed " + script_path_temp) - self.script_editor.setPlainText("") - self.updateScriptsDropdown() - self.loadScriptContents(check=False) - self.loadScriptState() - self.setScriptState() - - else: - content = "" - self.script_editor.setPlainText(content) - self.setScriptModified(False) - if self.current_folder + "/" + self.current_script in self.scrollPos: - del self.scrollPos[self.current_folder + - "/" + self.current_script] - if self.current_folder + "/" + self.current_script in self.cursorPos: - del self.cursorPos[self.current_folder + - "/" + self.current_script] - - self.setWindowTitle("KnobScripter - %s/%s" % - (self.current_folder, self.current_script)) - return - - def saveScriptContents(self, temp=True): - ''' Save the current contents of the editor into the python file. If temp == True, saves a .py.autosave file ''' - log("\n# About to save script contents now.") - log("Temp mode is: " + str(temp)) - log("self.current_folder: " + self.current_folder) - log("self.current_script: " + self.current_script) - script_path = os.path.join( - self.scripts_dir, self.current_folder, self.current_script) - script_path_temp = script_path + ".autosave" - orig_content = "" - content = self.script_editor.toPlainText().encode('utf8') - - if temp == True: - if os.path.isfile(script_path): - with open(script_path, 'r') as script: - orig_content = script.read() - # If script path doesn't exist and autosave does but the script is empty... - elif content == "" and os.path.isfile(script_path_temp): - os.remove(script_path_temp) - return - if content != orig_content: - with open(script_path_temp, 'w') as script: - script.write(content) - else: - if os.path.isfile(script_path_temp): - os.remove(script_path_temp) - log("Nothing to save") - return - else: - with open(script_path, 'w') as script: - script.write(self.script_editor.toPlainText().encode('utf8')) - # Clear trash - if os.path.isfile(script_path_temp): - os.remove(script_path_temp) - log("Removed " + script_path_temp) - self.setScriptModified(False) - self.saveScrollValue() - self.saveCursorPosValue() - log("Saved " + script_path + "\n---") - return - - def deleteScript(self, check=True, folder=""): - ''' Get the contents of the selected script and populate the editor ''' - log("# About to delete the .py and/or autosave script now.") - if folder == "": - folder = self.current_folder - script_path = os.path.join( - self.scripts_dir, folder, self.current_script) - script_path_temp = script_path + ".autosave" - if check: - msgBox = QtWidgets.QMessageBox() - msgBox.setText("You're about to delete this script.") - msgBox.setInformativeText( - "Are you sure you want to delete {}?".format(self.current_script)) - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.No) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - return False - - if os.path.isfile(script_path_temp): - os.remove(script_path_temp) - log("Removed " + script_path_temp) - - if os.path.isfile(script_path): - os.remove(script_path) - log("Removed " + script_path) - - return True - - def folderDropdownChanged(self): - '''Executed when the current folder dropdown is changed''' - self.saveScriptState() - log("# folder dropdown changed") - folders_dropdown = self.current_folder_dropdown - fd_value = folders_dropdown.currentText() - fd_index = folders_dropdown.currentIndex() - fd_data = folders_dropdown.itemData(fd_index) - if fd_data == "create new": - panel = FileNameDialog(self, mode="folder") - # panel.setWidth(260) - # panel.addSingleLineInput("Name:","") - if panel.exec_(): - # Accepted - folder_name = panel.text - if os.path.isdir(os.path.join(self.scripts_dir, folder_name)): - self.messageBox("Folder already exists.") - self.setCurrentFolder(self.current_folder) - if self.makeScriptFolder(name=folder_name): - self.saveScriptContents(temp=True) - # Success creating the folder - self.current_folder = folder_name - self.updateFoldersDropdown() - self.setCurrentFolder(folder_name) - self.updateScriptsDropdown() - self.loadScriptContents(check=False) - else: - self.messageBox("There was a problem creating the folder.") - self.current_folder_dropdown.blockSignals(True) - self.current_folder_dropdown.setCurrentIndex( - self.folder_index) - self.current_folder_dropdown.blockSignals(False) - else: - # Canceled/rejected - self.current_folder_dropdown.blockSignals(True) - self.current_folder_dropdown.setCurrentIndex(self.folder_index) - self.current_folder_dropdown.blockSignals(False) - return - - elif fd_data == "open in browser": - current_folder_path = os.path.join( - self.scripts_dir, self.current_folder) - self.openInFileBrowser(current_folder_path) - self.current_folder_dropdown.blockSignals(True) - self.current_folder_dropdown.setCurrentIndex(self.folder_index) - self.current_folder_dropdown.blockSignals(False) - return - - elif fd_data == "add custom path": - folder_path = nuke.getFilename('Select custom folder.') - if folder_path is not None: - if folder_path.endswith("/"): - aliasName = folder_path.split("/")[-2] - else: - aliasName = folder_path.split("/")[-1] - if not os.path.isdir(folder_path): - self.messageBox( - "Folder not found. Please try again with the full path to a folder.") - elif not len(aliasName): - self.messageBox( - "Folder with the same name already exists. Please delete or rename it first.") - else: - # All good - os.symlink(folder_path, os.path.join( - self.scripts_dir, aliasName)) - self.saveScriptContents(temp=True) - self.current_folder = aliasName - self.updateFoldersDropdown() - self.setCurrentFolder(aliasName) - self.updateScriptsDropdown() - self.loadScriptContents(check=False) - self.script_editor.setFocus() - return - self.current_folder_dropdown.blockSignals(True) - self.current_folder_dropdown.setCurrentIndex(self.folder_index) - self.current_folder_dropdown.blockSignals(False) - else: - # 1: Save current script as temp if needed - self.saveScriptContents(temp=True) - # 2: Set the new folder in the variables - self.current_folder = fd_data - self.folder_index = fd_index - # 3: Update the scripts dropdown - self.updateScriptsDropdown() - # 4: Load the current script! - self.loadScriptContents() - self.script_editor.setFocus() - - self.loadScriptState() - self.setScriptState() - - return - - def scriptDropdownChanged(self): - '''Executed when the current script dropdown is changed. Should only be called by the manual dropdown change. Not by other functions.''' - self.saveScriptState() - scripts_dropdown = self.current_script_dropdown - sd_value = scripts_dropdown.currentText() - sd_index = scripts_dropdown.currentIndex() - sd_data = scripts_dropdown.itemData(sd_index) - if sd_data == "create new": - self.current_script_dropdown.blockSignals(True) - panel = FileNameDialog(self, mode="script") - if panel.exec_(): - # Accepted - script_name = panel.text + ".py" - script_path = os.path.join( - self.scripts_dir, self.current_folder, script_name) - log(script_name) - log(script_path) - if os.path.isfile(script_path): - self.messageBox("Script already exists.") - self.current_script_dropdown.setCurrentIndex( - self.script_index) - if self.makeScriptFile(name=script_name, folder=self.current_folder): - # Success creating the folder - self.saveScriptContents(temp=True) - self.updateScriptsDropdown() - if self.current_script != "Untitled.py": - self.script_editor.setPlainText("") - self.current_script = script_name - self.setCurrentScript(script_name) - self.saveScriptContents(temp=False) - # self.loadScriptContents() - else: - self.messageBox("There was a problem creating the script.") - self.current_script_dropdown.setCurrentIndex( - self.script_index) - else: - # Canceled/rejected - self.current_script_dropdown.setCurrentIndex(self.script_index) - return - self.current_script_dropdown.blockSignals(False) - - elif sd_data == "create duplicate": - self.current_script_dropdown.blockSignals(True) - current_folder_path = os.path.join( - self.scripts_dir, self.current_folder, self.current_script) - current_script_path = os.path.join( - self.scripts_dir, self.current_folder, self.current_script) - - current_name = self.current_script - if self.current_script.endswith(".py"): - current_name = current_name[:-3] - - test_name = current_name - while True: - test_name += "_copy" - new_script_path = os.path.join( - self.scripts_dir, self.current_folder, test_name + ".py") - if not os.path.isfile(new_script_path): - break - - script_name = test_name + ".py" - - if self.makeScriptFile(name=script_name, folder=self.current_folder): - # Success creating the folder - self.saveScriptContents(temp=True) - self.updateScriptsDropdown() - # self.script_editor.setPlainText("") - self.current_script = script_name - self.setCurrentScript(script_name) - self.script_editor.setFocus() - else: - self.messageBox("There was a problem duplicating the script.") - self.current_script_dropdown.setCurrentIndex(self.script_index) - - self.current_script_dropdown.blockSignals(False) - - elif sd_data == "open in browser": - current_script_path = os.path.join( - self.scripts_dir, self.current_folder, self.current_script) - self.openInFileBrowser(current_script_path) - self.current_script_dropdown.blockSignals(True) - self.current_script_dropdown.setCurrentIndex(self.script_index) - self.current_script_dropdown.blockSignals(False) - return - - elif sd_data == "delete script": - if self.deleteScript(): - self.updateScriptsDropdown() - self.loadScriptContents() - else: - self.current_script_dropdown.blockSignals(True) - self.current_script_dropdown.setCurrentIndex(self.script_index) - self.current_script_dropdown.blockSignals(False) - - else: - self.saveScriptContents() - self.current_script = sd_data - self.script_index = sd_index - self.setCurrentScript(self.current_script) - self.loadScriptContents() - self.script_editor.setFocus() - self.loadScriptState() - self.setScriptState() - return - - def setScriptModified(self, modified=True): - ''' Sets self.current_script_modified, title and whatever else we need ''' - self.current_script_modified = modified - title_modified_string = " [modified]" - windowTitle = self.windowTitle().split(title_modified_string)[0] - if modified == True: - windowTitle += title_modified_string - self.setWindowTitle(windowTitle) - try: - scripts_dropdown = self.current_script_dropdown - sd_index = scripts_dropdown.currentIndex() - sd_data = scripts_dropdown.itemData(sd_index) - if modified == False: - scripts_dropdown.setItemText(sd_index, sd_data) - else: - scripts_dropdown.setItemText(sd_index, sd_data + "(*)") - except: - pass - - def openInFileBrowser(self, path=""): - OS = platform.system() - if not os.path.exists(path): - path = KS_DIR - if OS == "Windows": - os.startfile(path) - elif OS == "Darwin": - subprocess.Popen(["open", path]) - else: - subprocess.Popen(["xdg-open", path]) - - def loadScriptState(self): - ''' - Loads the last state of the script from a file inside the SE directory's root. - SAVES self.scroll_pos, self.cursor_pos, self.last_open_script - ''' - self.state_dict = {} - if not os.path.isfile(self.state_txt_path): - return False - else: - with open(self.state_txt_path, "r") as f: - self.state_dict = json.load(f) - - log("Loading script state into self.state_dict, self.scrollPos, self.cursorPos") - log(self.state_dict) - - if "scroll_pos" in self.state_dict: - self.scrollPos = self.state_dict["scroll_pos"] - if "cursor_pos" in self.state_dict: - self.cursorPos = self.state_dict["cursor_pos"] - - def setScriptState(self): - ''' - Sets the already script state from self.state_dict into the current script if applicable - ''' - script_fullname = self.current_folder + "/" + self.current_script - - if "scroll_pos" in self.state_dict: - if script_fullname in self.state_dict["scroll_pos"]: - self.script_editor.verticalScrollBar().setValue( - int(self.state_dict["scroll_pos"][script_fullname])) - - if "cursor_pos" in self.state_dict: - if script_fullname in self.state_dict["cursor_pos"]: - cursor = self.script_editor.textCursor() - cursor.setPosition(int( - self.state_dict["cursor_pos"][script_fullname][1]), QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(int( - self.state_dict["cursor_pos"][script_fullname][0]), QtGui.QTextCursor.KeepAnchor) - self.script_editor.setTextCursor(cursor) - - if 'splitter_sizes' in self.state_dict: - self.splitter.setSizes(self.state_dict['splitter_sizes']) - - def setLastScript(self): - if 'last_folder' in self.state_dict and 'last_script' in self.state_dict: - self.updateFoldersDropdown() - self.setCurrentFolder(self.state_dict['last_folder']) - self.updateScriptsDropdown() - self.setCurrentScript(self.state_dict['last_script']) - self.loadScriptContents() - self.script_editor.setFocus() - - def saveScriptState(self): - ''' Stores the current state of the script into a file inside the SE directory's root ''' - log("About to save script state...") - ''' - # self.state_dict = {} - if os.path.isfile(self.state_txt_path): - with open(self.state_txt_path, "r") as f: - self.state_dict = json.load(f) - - if "scroll_pos" in self.state_dict: - self.scrollPos = self.state_dict["scroll_pos"] - if "cursor_pos" in self.state_dict: - self.cursorPos = self.state_dict["cursor_pos"] - - ''' - self.loadScriptState() - - # Overwrite current values into the scriptState - self.saveScrollValue() - self.saveCursorPosValue() - - self.state_dict['scroll_pos'] = self.scrollPos - self.state_dict['cursor_pos'] = self.cursorPos - self.state_dict['last_folder'] = self.current_folder - self.state_dict['last_script'] = self.current_script - self.state_dict['splitter_sizes'] = self.splitter.sizes() - - with open(self.state_txt_path, "w") as f: - state = json.dump(self.state_dict, f, sort_keys=True, indent=4) - return state - - # Autosave background loop - def autosave(self): - if self.toAutosave: - # Save the script... - self.saveScriptContents() - self.toAutosave = False - self.saveScriptState() - log("autosaving...") - return - - # Global stuff - def setTextSelection(self): - self.highlighter.selected_text = self.script_editor.textCursor().selection().toPlainText() - return - - def eventFilter(self, object, event): - if event.type() == QtCore.QEvent.KeyPress: - return QtWidgets.QWidget.eventFilter(self, object, event) - else: - return QtWidgets.QWidget.eventFilter(self, object, event) - - def resizeEvent(self, res_event): - w = self.frameGeometry().width() - self.current_node_label_node.setVisible(w > 460) - self.script_label.setVisible(w > 460) - return super(KnobScripter, self).resizeEvent(res_event) - - def changeClicked(self, newNode=""): - ''' Change node ''' - try: - print "Changing from " + self.node.name() - except: - self.node = None - if not len(nuke.selectedNodes()): - self.exitNodeMode() - return - nuke.menu("Nuke").findItem( - "Edit/Node/Update KnobScripter Context").invoke() - selection = knobScripterSelectedNodes - if self.nodeMode: # Only update the number of unsaved knobs if we were already in node mode - if self.node is not None: - updatedCount = self.updateUnsavedKnobs() - else: - updatedCount = 0 - else: - updatedCount = 0 - self.autosave() - if newNode != "" and nuke.exists(newNode): - selection = [newNode] - elif not len(selection): - node_dialog = ChooseNodeDialog(self) - if node_dialog.exec_(): - # Accepted - selection = [nuke.toNode(node_dialog.name)] - else: - return - - # Change to node mode... - self.node_mode_bar.setVisible(True) - self.script_mode_bar.setVisible(False) - if not self.nodeMode: - self.saveScriptContents() - self.toAutosave = False - self.saveScriptState() - self.splitter.setSizes([0, 1]) - self.nodeMode = True - - # If already selected, pass - if self.node is not None and selection[0].fullName() == self.node.fullName(): - self.messageBox("Please select a different node first!") - return - elif updatedCount > 0: - msgBox = QtWidgets.QMessageBox() - msgBox.setText( - "Save changes to %s knob%s before changing the node?" % (str(updatedCount), int(updatedCount > 1) * "s")) - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.Yes: - self.saveAllKnobValues(check=False) - elif reply == QtWidgets.QMessageBox.Cancel: - return - if len(selection) > 1: - self.messageBox( - "More than one node selected.\nChanging knobChanged editor to %s" % selection[0].fullName()) - # Reinitialise everything, wooo! - self.current_knob_dropdown.blockSignals(True) - self.node = selection[0] - - self.script_editor.setPlainText("") - self.unsavedKnobs = {} - self.scrollPos = {} - self.setWindowTitle("KnobScripter - %s %s" % - (self.node.fullName(), self.knob)) - self.current_node_label_name.setText(self.node.fullName()) - - self.toLoadKnob = False - self.updateKnobDropdown() # onee - # self.current_knob_dropdown.repaint() - # self.current_knob_dropdown.setMinimumWidth(self.current_knob_dropdown.minimumSizeHint().width()) - self.toLoadKnob = True - self.setCurrentKnob(self.knob) - self.loadKnobValue(False) - self.script_editor.setFocus() - self.setKnobModified(False) - self.current_knob_dropdown.blockSignals(False) - # self.current_knob_dropdown.setMinimumContentsLength(80) - return - - def exitNodeMode(self): - self.nodeMode = False - self.setWindowTitle("KnobScripter - Script Mode") - self.node_mode_bar.setVisible(False) - self.script_mode_bar.setVisible(True) - self.node = nuke.toNode("root") - # self.updateFoldersDropdown() - # self.updateScriptsDropdown() - self.splitter.setSizes([1, 1]) - self.loadScriptState() - self.setLastScript() - - self.loadScriptContents(check=False) - self.setScriptState() - - def clearConsole(self): - self.origConsoleText = self.nukeSEOutput.document().toPlainText().encode("utf8") - self.script_output.setPlainText("") - - def toggleFRW(self, frw_pressed): - self.frw_open = frw_pressed - self.frw.setVisible(self.frw_open) - if self.frw_open: - self.frw.find_lineEdit.setFocus() - self.frw.find_lineEdit.selectAll() - else: - self.script_editor.setFocus() - return - - def openSnippets(self): - ''' Whenever the 'snippets' button is pressed... open the panel ''' - global SnippetEditPanel - if SnippetEditPanel == "": - SnippetEditPanel = SnippetsPanel(self) - - if not SnippetEditPanel.isVisible(): - SnippetEditPanel.reload() - - if SnippetEditPanel.show(): - self.snippets = self.loadSnippets(maxDepth=5) - SnippetEditPanel = "" - - def loadSnippets(self, path="", maxDepth=5, depth=0): - ''' - Load prefs recursive. When maximum recursion depth, ignores paths. - ''' - max_depth = maxDepth - cur_depth = depth - if path == "": - path = self.snippets_txt_path - if not os.path.isfile(path): - return {} - else: - loaded_snippets = {} - with open(path, "r") as f: - file = json.load(f) - for i, (key, val) in enumerate(file.items()): - if re.match(r"\[custom-path-[0-9]+\]$", key): - if cur_depth < max_depth: - new_dict = self.loadSnippets( - path=val, maxDepth=max_depth, depth=cur_depth + 1) - loaded_snippets.update(new_dict) - else: - loaded_snippets[key] = val - return loaded_snippets - - def messageBox(self, the_text=""): - ''' Just a simple message box ''' - if self.isPane: - msgBox = QtWidgets.QMessageBox() - else: - msgBox = QtWidgets.QMessageBox(self) - msgBox.setText(the_text) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.exec_() - - def openPrefs(self): - ''' Open the preferences panel ''' - global PrefsPanel - if PrefsPanel == "": - PrefsPanel = KnobScripterPrefs(self) - - if PrefsPanel.show(): - PrefsPanel = "" - - def loadPrefs(self): - ''' Load prefs ''' - if not os.path.isfile(self.prefs_txt): - return [] - else: - with open(self.prefs_txt, "r") as f: - prefs = json.load(f) - return prefs - - def runScript(self): - ''' Run the current script... ''' - self.script_editor.runScript() - - def saveScrollValue(self): - ''' Save scroll values ''' - if self.nodeMode: - self.scrollPos[self.knob] = self.script_editor.verticalScrollBar( - ).value() - else: - self.scrollPos[self.current_folder + "/" + - self.current_script] = self.script_editor.verticalScrollBar().value() - - def saveCursorPosValue(self): - ''' Save cursor pos and anchor values ''' - self.cursorPos[self.current_folder + "/" + self.current_script] = [ - self.script_editor.textCursor().position(), self.script_editor.textCursor().anchor()] - - def closeEvent(self, close_event): - if self.nodeMode: - updatedCount = self.updateUnsavedKnobs() - if updatedCount > 0: - msgBox = QtWidgets.QMessageBox() - msgBox.setText("Save changes to %s knob%s before closing?" % ( - str(updatedCount), int(updatedCount > 1) * "s")) - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.Yes: - self.saveAllKnobValues(check=False) - close_event.accept() - return - elif reply == QtWidgets.QMessageBox.Cancel: - close_event.ignore() - return - else: - close_event.accept() - else: - self.autosave() - if self in AllKnobScripters: - AllKnobScripters.remove(self) - close_event.accept() - - # Landing functions - - def refreshClicked(self): - ''' Function to refresh the dropdowns ''' - if self.nodeMode: - knob = self.current_knob_dropdown.itemData( - self.current_knob_dropdown.currentIndex()).encode('UTF8') - self.current_knob_dropdown.blockSignals(True) - self.current_knob_dropdown.clear() # First remove all items - self.updateKnobDropdown() - availableKnobs = [] - for i in range(self.current_knob_dropdown.count()): - if self.current_knob_dropdown.itemData(i) is not None: - availableKnobs.append( - self.current_knob_dropdown.itemData(i).encode('UTF8')) - if knob in availableKnobs: - self.setCurrentKnob(knob) - self.current_knob_dropdown.blockSignals(False) - else: - folder = self.current_folder - script = self.current_script - self.autosave() - self.updateFoldersDropdown() - self.setCurrentFolder(folder) - self.updateScriptsDropdown() - self.setCurrentScript(script) - self.script_editor.setFocus() - - def reloadClicked(self): - if self.nodeMode: - self.loadKnobValue() - else: - log("Node mode is off") - self.loadScriptContents(check=True, pyOnly=True) - - def saveClicked(self): - if self.nodeMode: - self.saveKnobValue(False) - else: - self.saveScriptContents(temp=False) - - def setModified(self): - if self.nodeMode: - self.setKnobModified(True) - elif not self.current_script_modified: - self.setScriptModified(True) - if not self.nodeMode: - self.toAutosave = True - - def pin(self, pressed): - if pressed: - self.setWindowFlags(self.windowFlags() | - QtCore.Qt.WindowStaysOnTopHint) - self.pinned = True - self.show() - else: - self.setWindowFlags(self.windowFlags() & ~ - QtCore.Qt.WindowStaysOnTopHint) - self.pinned = False - self.show() - - def findSE(self): - for widget in QtWidgets.QApplication.allWidgets(): - if "Script Editor" in widget.windowTitle(): - return widget - - # FunctiosaveScrollValuens for Nuke's Script Editor - def findScriptEditors(self): - script_editors = [] - for widget in QtWidgets.QApplication.allWidgets(): - if "Script Editor" in widget.windowTitle() and len(widget.children()) > 5: - script_editors.append(widget) - return script_editors - - def findSEInput(self, se): - return se.children()[-1].children()[0] - - def findSEOutput(self, se): - return se.children()[-1].children()[1] - - def findSERunBtn(self, se): - for btn in se.children(): - try: - if "Run the current script" in btn.toolTip(): - return btn - except: - pass - return False - - def setSEOutputEvent(self): - nukeScriptEditors = self.findScriptEditors() - # Take the console from the first script editor found... - self.origConsoleText = self.nukeSEOutput.document().toPlainText().encode("utf8") - for se in nukeScriptEditors: - se_output = self.findSEOutput(se) - se_output.textChanged.connect( - partial(consoleChanged, se_output, self)) - consoleChanged(se_output, self) # Initialise. - - -class KnobScripterPane(KnobScripter): - def __init__(self, node="", knob="knobChanged"): - super(KnobScripterPane, self).__init__() - self.isPane = True - - def showEvent(self, the_event): - try: - killPaneMargins(self) - except: - pass - return KnobScripter.showEvent(self, the_event) - - def hideEvent(self, the_event): - self.autosave() - return KnobScripter.hideEvent(self, the_event) - - -def consoleChanged(self, ks): - ''' This will be called every time the ScriptEditor Output text is changed ''' - try: - if ks: # KS exists - ksOutput = ks.script_output # The console TextEdit widget - ksText = self.document().toPlainText().encode("utf8") - # The text from the console that will be omitted - origConsoleText = ks.origConsoleText - if ksText.startswith(origConsoleText): - ksText = ksText[len(origConsoleText):] - else: - ks.origConsoleText = "" - ksOutput.setPlainText(ksText) - ksOutput.verticalScrollBar().setValue(ksOutput.verticalScrollBar().maximum()) - except: - pass - - -def killPaneMargins(widget_object): - if widget_object: - target_widgets = set() - target_widgets.add(widget_object.parentWidget().parentWidget()) - target_widgets.add(widget_object.parentWidget( - ).parentWidget().parentWidget().parentWidget()) - - for widget_layout in target_widgets: - try: - widget_layout.layout().setContentsMargins(0, 0, 0, 0) - except: - pass - - -def debug(lev=0): - ''' Convenience function to set the KnobScripter on debug mode''' - # levels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL] - # for handler in logging.root.handlers[:]: - # logging.root.removeHandler(handler) - # logging.basicConfig(level=levels[lev]) - # Changed to a shitty way for now - global DebugMode - DebugMode = True - - -def log(text): - ''' Display a debug info message. Yes, in a stupid way. I know.''' - global DebugMode - if DebugMode: - print(text) - - -# --------------------------------------------------------------------- -# Dialogs -# --------------------------------------------------------------------- -class FileNameDialog(QtWidgets.QDialog): - ''' - Dialog for creating new... (mode = "folder", "script" or "knob"). - ''' - - def __init__(self, parent=None, mode="folder", text=""): - if parent.isPane: - super(FileNameDialog, self).__init__() - else: - super(FileNameDialog, self).__init__(parent) - #self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) - self.mode = mode - self.text = text - - title = "Create new {}.".format(self.mode) - self.setWindowTitle(title) - - self.initUI() - - def initUI(self): - # Widgets - self.name_label = QtWidgets.QLabel("Name: ") - self.name_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.name_lineEdit = QtWidgets.QLineEdit() - self.name_lineEdit.setText(self.text) - self.name_lineEdit.textChanged.connect(self.nameChanged) - - # Buttons - self.button_box = QtWidgets.QDialogButtonBox( - QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) - self.button_box.button( - QtWidgets.QDialogButtonBox.Ok).setEnabled(self.text != "") - self.button_box.accepted.connect(self.clickedOk) - self.button_box.rejected.connect(self.clickedCancel) - - # Layout - self.master_layout = QtWidgets.QVBoxLayout() - self.name_layout = QtWidgets.QHBoxLayout() - self.name_layout.addWidget(self.name_label) - self.name_layout.addWidget(self.name_lineEdit) - self.master_layout.addLayout(self.name_layout) - self.master_layout.addWidget(self.button_box) - self.setLayout(self.master_layout) - - self.name_lineEdit.setFocus() - self.setMinimumWidth(250) - - def nameChanged(self): - txt = self.name_lineEdit.text() - m = r"[\w]*$" - if self.mode == "knob": # Knobs can't start with a number... - m = r"[a-zA-Z_]+" + m - - if re.match(m, txt) or txt == "": - self.text = txt - else: - self.name_lineEdit.setText(self.text) - - self.button_box.button( - QtWidgets.QDialogButtonBox.Ok).setEnabled(self.text != "") - return - - def clickedOk(self): - self.accept() - return - - def clickedCancel(self): - self.reject() - return - - -class TextInputDialog(QtWidgets.QDialog): - ''' - Simple dialog for a text input. - ''' - - def __init__(self, parent=None, name="", text="", title=""): - if parent.isPane: - super(TextInputDialog, self).__init__() - else: - super(TextInputDialog, self).__init__(parent) - #self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) - - self.name = name # title of textinput - self.text = text # default content of textinput - - self.setWindowTitle(title) - - self.initUI() - - def initUI(self): - # Widgets - self.name_label = QtWidgets.QLabel(self.name + ": ") - self.name_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.name_lineEdit = QtWidgets.QLineEdit() - self.name_lineEdit.setText(self.text) - self.name_lineEdit.textChanged.connect(self.nameChanged) - - # Buttons - self.button_box = QtWidgets.QDialogButtonBox( - QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) - #self.button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(self.text != "") - self.button_box.accepted.connect(self.clickedOk) - self.button_box.rejected.connect(self.clickedCancel) - - # Layout - self.master_layout = QtWidgets.QVBoxLayout() - self.name_layout = QtWidgets.QHBoxLayout() - self.name_layout.addWidget(self.name_label) - self.name_layout.addWidget(self.name_lineEdit) - self.master_layout.addLayout(self.name_layout) - self.master_layout.addWidget(self.button_box) - self.setLayout(self.master_layout) - - self.name_lineEdit.setFocus() - self.setMinimumWidth(250) - - def nameChanged(self): - self.text = self.name_lineEdit.text() - - def clickedOk(self): - self.accept() - return - - def clickedCancel(self): - self.reject() - return - - -class ChooseNodeDialog(QtWidgets.QDialog): - ''' - Dialog for selecting a node by its name. Only admits nodes that exist (including root, preferences...) - ''' - - def __init__(self, parent=None, name=""): - if parent.isPane: - super(ChooseNodeDialog, self).__init__() - else: - super(ChooseNodeDialog, self).__init__(parent) - - self.name = name # Name of node (will be "" by default) - self.allNodes = [] - - self.setWindowTitle("Enter the node's name...") - - self.initUI() - - def initUI(self): - # Widgets - self.name_label = QtWidgets.QLabel("Name: ") - self.name_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.name_lineEdit = QtWidgets.QLineEdit() - self.name_lineEdit.setText(self.name) - self.name_lineEdit.textChanged.connect(self.nameChanged) - - self.allNodes = self.getAllNodes() - completer = QtWidgets.QCompleter(self.allNodes, self) - completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) - self.name_lineEdit.setCompleter(completer) - - # Buttons - self.button_box = QtWidgets.QDialogButtonBox( - QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) - self.button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled( - nuke.exists(self.name)) - self.button_box.accepted.connect(self.clickedOk) - self.button_box.rejected.connect(self.clickedCancel) - - # Layout - self.master_layout = QtWidgets.QVBoxLayout() - self.name_layout = QtWidgets.QHBoxLayout() - self.name_layout.addWidget(self.name_label) - self.name_layout.addWidget(self.name_lineEdit) - self.master_layout.addLayout(self.name_layout) - self.master_layout.addWidget(self.button_box) - self.setLayout(self.master_layout) - - self.name_lineEdit.setFocus() - self.setMinimumWidth(250) - - def getAllNodes(self): - self.allNodes = [n.fullName() for n in nuke.allNodes( - recurseGroups=True)] # if parent is in current context?? - self.allNodes.extend(["root", "preferences"]) - return self.allNodes - - def nameChanged(self): - self.name = self.name_lineEdit.text() - self.button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled( - self.name in self.allNodes) - - def clickedOk(self): - self.accept() - return - - def clickedCancel(self): - self.reject() - return - - -# ------------------------------------------------------------------------------------------------------ -# Script Editor Widget -# Wouter Gilsing built an incredibly useful python script editor for his Hotbox Manager, so I had it -# really easy for this part! -# Starting from his script editor, I changed the style and added the sublime-like functionality. -# I think this bit of code has the potential to get used in many nuke tools. -# Credit to him: http://www.woutergilsing.com/ -# Originally used on W_Hotbox v1.5: http://www.nukepedia.com/python/ui/w_hotbox -# ------------------------------------------------------------------------------------------------------ -class KnobScripterTextEdit(QtWidgets.QPlainTextEdit): - # Signal that will be emitted when the user has changed the text - userChangedEvent = QtCore.Signal() - - def __init__(self, knobScripter=""): - super(KnobScripterTextEdit, self).__init__() - - self.knobScripter = knobScripter - self.selected_text = "" - - # Setup line numbers - if self.knobScripter != "": - self.tabSpaces = self.knobScripter.tabSpaces - else: - self.tabSpaces = 4 - self.lineNumberArea = KSLineNumberArea(self) - self.blockCountChanged.connect(self.updateLineNumberAreaWidth) - self.updateRequest.connect(self.updateLineNumberArea) - self.updateLineNumberAreaWidth() - - # Highlight line - self.cursorPositionChanged.connect(self.highlightCurrentLine) - - # -------------------------------------------------------------------------------------------------- - # This is adapted from an original version by Wouter Gilsing. - # Extract from his original comments: - # While researching the implementation of line number, I had a look at Nuke's Blinkscript node. [..] - # thefoundry.co.uk/products/nuke/developers/100/pythonreference/nukescripts.blinkscripteditor-pysrc.html - # I stripped and modified the useful bits of the line number related parts of the code [..] - # Credits to theFoundry for writing the blinkscripteditor, best example code I could wish for. - # -------------------------------------------------------------------------------------------------- - - def lineNumberAreaWidth(self): - digits = 1 - maxNum = max(1, self.blockCount()) - while (maxNum >= 10): - maxNum /= 10 - digits += 1 - - space = 7 + self.fontMetrics().width('9') * digits - return space - - def updateLineNumberAreaWidth(self): - self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0) - - def updateLineNumberArea(self, rect, dy): - - if (dy): - self.lineNumberArea.scroll(0, dy) - else: - self.lineNumberArea.update( - 0, rect.y(), self.lineNumberArea.width(), rect.height()) - - if (rect.contains(self.viewport().rect())): - self.updateLineNumberAreaWidth() - - def resizeEvent(self, event): - QtWidgets.QPlainTextEdit.resizeEvent(self, event) - - cr = self.contentsRect() - self.lineNumberArea.setGeometry(QtCore.QRect( - cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height())) - - def lineNumberAreaPaintEvent(self, event): - - if self.isReadOnly(): - return - - painter = QtGui.QPainter(self.lineNumberArea) - painter.fillRect(event.rect(), QtGui.QColor(36, 36, 36)) # Number bg - - block = self.firstVisibleBlock() - blockNumber = block.blockNumber() - top = int(self.blockBoundingGeometry( - block).translated(self.contentOffset()).top()) - bottom = top + int(self.blockBoundingRect(block).height()) - currentLine = self.document().findBlock( - self.textCursor().position()).blockNumber() - - painter.setPen(self.palette().color(QtGui.QPalette.Text)) - - painterFont = QtGui.QFont() - painterFont.setFamily("Courier") - painterFont.setStyleHint(QtGui.QFont.Monospace) - painterFont.setFixedPitch(True) - if self.knobScripter != "": - painterFont.setPointSize(self.knobScripter.fontSize) - painter.setFont(self.knobScripter.script_editor_font) - - while (block.isValid() and top <= event.rect().bottom()): - - textColor = QtGui.QColor(110, 110, 110) # Numbers - - if blockNumber == currentLine and self.hasFocus(): - textColor = QtGui.QColor(255, 170, 0) # Number highlighted - - painter.setPen(textColor) - - number = "%s" % str(blockNumber + 1) - painter.drawText(-3, top, self.lineNumberArea.width(), - self.fontMetrics().height(), QtCore.Qt.AlignRight, number) - - # Move to the next block - block = block.next() - top = bottom - bottom = top + int(self.blockBoundingRect(block).height()) - blockNumber += 1 - - def keyPressEvent(self, event): - ''' - Custom actions for specific keystrokes - ''' - key = event.key() - ctrl = bool(event.modifiers() & Qt.ControlModifier) - alt = bool(event.modifiers() & Qt.AltModifier) - shift = bool(event.modifiers() & Qt.ShiftModifier) - pre_scroll = self.verticalScrollBar().value() - #modifiers = QtWidgets.QApplication.keyboardModifiers() - #ctrl = (modifiers == Qt.ControlModifier) - #shift = (modifiers == Qt.ShiftModifier) - - up_arrow = 16777235 - down_arrow = 16777237 - - # if Tab convert to Space - if key == 16777217: - self.indentation('indent') - - # if Shift+Tab remove indent - elif key == 16777218: - self.indentation('unindent') - - # if BackSpace try to snap to previous indent level - elif key == 16777219: - if not self.unindentBackspace(): - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - else: - # COOL BEHAVIORS SIMILAR TO SUBLIME GO NEXT! - cursor = self.textCursor() - cpos = cursor.position() - apos = cursor.anchor() - text_before_cursor = self.toPlainText()[:min(cpos, apos)] - text_after_cursor = self.toPlainText()[max(cpos, apos):] - text_all = self.toPlainText() - to_line_start = text_before_cursor[::-1].find("\n") - if to_line_start == -1: - # Position of the start of the line that includes the cursor selection start - linestart_pos = 0 - else: - linestart_pos = len(text_before_cursor) - to_line_start - - to_line_end = text_after_cursor.find("\n") - if to_line_end == -1: - # Position of the end of the line that includes the cursor selection end - lineend_pos = len(text_all) - else: - lineend_pos = max(cpos, apos) + to_line_end - - text_before_lines = text_all[:linestart_pos] - text_after_lines = text_all[lineend_pos:] - if len(text_after_lines) and text_after_lines.startswith("\n"): - text_after_lines = text_after_lines[1:] - text_lines = text_all[linestart_pos:lineend_pos] - - if cursor.hasSelection(): - selection = cursor.selection().toPlainText() - else: - selection = "" - if key == Qt.Key_ParenLeft and (len(selection) > 0 or re.match(r"[\s)}\];]+", text_after_cursor) or not len(text_after_cursor)): # ( - cursor.insertText("(" + selection + ")") - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - # ) - elif key == Qt.Key_ParenRight and text_after_cursor.startswith(")"): - cursor.movePosition(QtGui.QTextCursor.NextCharacter) - self.setTextCursor(cursor) - elif key == Qt.Key_BracketLeft and (len(selection) > 0 or re.match(r"[\s)}\];]+", text_after_cursor) or not len(text_after_cursor)): # [ - cursor.insertText("[" + selection + "]") - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - # ] - elif key in [Qt.Key_BracketRight, 43] and text_after_cursor.startswith("]"): - cursor.movePosition(QtGui.QTextCursor.NextCharacter) - self.setTextCursor(cursor) - elif key == Qt.Key_BraceLeft and (len(selection) > 0 or re.match(r"[\s)}\];]+", text_after_cursor) or not len(text_after_cursor)): # { - cursor.insertText("{" + selection + "}") - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - # } - elif key in [199, Qt.Key_BraceRight] and text_after_cursor.startswith("}"): - cursor.movePosition(QtGui.QTextCursor.NextCharacter) - self.setTextCursor(cursor) - elif key == 34: # " - if len(selection) > 0: - cursor.insertText('"' + selection + '"') - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - # and not re.search(r"(?:[\s)\]]+|$)",text_before_cursor): - elif text_after_cursor.startswith('"') and '"' in text_before_cursor.split("\n")[-1]: - cursor.movePosition(QtGui.QTextCursor.NextCharacter) - # If chars after cursor, act normal - elif not re.match(r"(?:[\s)\]]+|$)", text_after_cursor): - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - # If chars before cursor, act normal - elif not re.search(r"[\s.({\[,]$", text_before_cursor) and text_before_cursor != "": - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - else: - cursor.insertText('"' + selection + '"') - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - elif key == 39: # ' - if len(selection) > 0: - cursor.insertText("'" + selection + "'") - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - # and not re.search(r"(?:[\s)\]]+|$)",text_before_cursor): - elif text_after_cursor.startswith("'") and "'" in text_before_cursor.split("\n")[-1]: - cursor.movePosition(QtGui.QTextCursor.NextCharacter) - # If chars after cursor, act normal - elif not re.match(r"(?:[\s)\]]+|$)", text_after_cursor): - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - # If chars before cursor, act normal - elif not re.search(r"[\s.({\[,]$", text_before_cursor) and text_before_cursor != "": - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - else: - cursor.insertText("'" + selection + "'") - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - elif key == 35 and len(selection): # (yes, a hash) - # If there's a selection, insert a hash at the start of each line.. how the fuck? - if selection != "": - selection_split = selection.split("\n") - if all(i.startswith("#") for i in selection_split): - selection_commented = "\n".join( - [s[1:] for s in selection_split]) # Uncommented - else: - selection_commented = "#" + "\n#".join(selection_split) - cursor.insertText(selection_commented) - if apos > cpos: - cursor.setPosition( - apos + len(selection_commented) - len(selection), QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos, QtGui.QTextCursor.KeepAnchor) - else: - cursor.setPosition(apos, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition( - cpos + len(selection_commented) - len(selection), QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - - elif key == 68 and ctrl and shift: # Ctrl+Shift+D, to duplicate text or line/s - - if not len(selection): - self.setPlainText( - text_before_lines + text_lines + "\n" + text_lines + "\n" + text_after_lines) - cursor.setPosition( - apos + len(text_lines) + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition( - cpos + len(text_lines) + 1, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - self.verticalScrollBar().setValue(pre_scroll) - self.scrollToCursor() - else: - if text_before_cursor.endswith("\n") and not selection.startswith("\n"): - cursor.insertText(selection + "\n" + selection) - cursor.setPosition( - apos + len(selection) + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition( - cpos + len(selection) + 1, QtGui.QTextCursor.KeepAnchor) - else: - cursor.insertText(selection + selection) - cursor.setPosition( - apos + len(selection), QtGui.QTextCursor.MoveAnchor) - cursor.setPosition( - cpos + len(selection), QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - - # Ctrl+Shift+Up, to move the selected line/s up - elif key == up_arrow and ctrl and shift and len(text_before_lines): - prev_line_start_distance = text_before_lines[:-1][::-1].find( - "\n") - if prev_line_start_distance == -1: - prev_line_start_pos = 0 # Position of the start of the previous line - else: - prev_line_start_pos = len( - text_before_lines) - 1 - prev_line_start_distance - prev_line = text_before_lines[prev_line_start_pos:] - - text_before_prev_line = text_before_lines[:prev_line_start_pos] - - if prev_line.endswith("\n"): - prev_line = prev_line[:-1] - - if len(text_after_lines): - text_after_lines = "\n" + text_after_lines - - self.setPlainText( - text_before_prev_line + text_lines + "\n" + prev_line + text_after_lines) - cursor.setPosition(apos - len(prev_line) - 1, - QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos - len(prev_line) - 1, - QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - self.verticalScrollBar().setValue(pre_scroll) - self.scrollToCursor() - return - - elif key == down_arrow and ctrl and shift: # Ctrl+Shift+Up, to move the selected line/s up - if not len(text_after_lines): - text_after_lines = "" - next_line_end_distance = text_after_lines.find("\n") - if next_line_end_distance == -1: - next_line_end_pos = len(text_all) - else: - next_line_end_pos = next_line_end_distance - next_line = text_after_lines[:next_line_end_pos] - text_after_next_line = text_after_lines[next_line_end_pos:] - - self.setPlainText(text_before_lines + next_line + - "\n" + text_lines + text_after_next_line) - cursor.setPosition(apos + len(next_line) + 1, - QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + len(next_line) + 1, - QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - self.verticalScrollBar().setValue(pre_scroll) - self.scrollToCursor() - return - - # If up key and nothing happens, go to start - elif key == up_arrow and not len(text_before_lines): - if not shift: - cursor.setPosition(0, QtGui.QTextCursor.MoveAnchor) - self.setTextCursor(cursor) - else: - cursor.setPosition(0, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - - # If up key and nothing happens, go to start - elif key == down_arrow and not len(text_after_lines): - if not shift: - cursor.setPosition( - len(text_all), QtGui.QTextCursor.MoveAnchor) - self.setTextCursor(cursor) - else: - cursor.setPosition( - len(text_all), QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - - # if enter or return, match indent level - elif key in [16777220, 16777221]: - self.indentNewLine() - else: - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - - self.scrollToCursor() - - def scrollToCursor(self): - self.cursor = self.textCursor() - # Does nothing, but makes the scroll go to the right place... - self.cursor.movePosition(QtGui.QTextCursor.NoMove) - self.setTextCursor(self.cursor) - - def getCursorInfo(self): - - self.cursor = self.textCursor() - - self.firstChar = self.cursor.selectionStart() - self.lastChar = self.cursor.selectionEnd() - - self.noSelection = False - if self.firstChar == self.lastChar: - self.noSelection = True - - self.originalPosition = self.cursor.position() - self.cursorBlockPos = self.cursor.positionInBlock() - - def unindentBackspace(self): - ''' - #snap to previous indent level - ''' - self.getCursorInfo() - - if not self.noSelection or self.cursorBlockPos == 0: - return False - - # check text in front of cursor - textInFront = self.document().findBlock( - self.firstChar).text()[:self.cursorBlockPos] - - # check whether solely spaces - if textInFront != ' ' * self.cursorBlockPos: - return False - - # snap to previous indent level - spaces = len(textInFront) - for space in range(spaces - ((spaces - 1) / self.tabSpaces) * self.tabSpaces - 1): - self.cursor.deletePreviousChar() - - def indentNewLine(self): - - # in case selection covers multiple line, make it one line first - self.insertPlainText('') - - self.getCursorInfo() - - # check how many spaces after cursor - text = self.document().findBlock(self.firstChar).text() - - textInFront = text[:self.cursorBlockPos] - - if len(textInFront) == 0: - self.insertPlainText('\n') - return - - indentLevel = 0 - for i in textInFront: - if i == ' ': - indentLevel += 1 - else: - break - - indentLevel /= self.tabSpaces - - # find out whether textInFront's last character was a ':' - # if that's the case add another indent. - # ignore any spaces at the end, however also - # make sure textInFront is not just an indent - if textInFront.count(' ') != len(textInFront): - while textInFront[-1] == ' ': - textInFront = textInFront[:-1] - - if textInFront[-1] == ':': - indentLevel += 1 - - # new line - self.insertPlainText('\n') - # match indent - self.insertPlainText(' ' * (self.tabSpaces * indentLevel)) - - def indentation(self, mode): - - pre_scroll = self.verticalScrollBar().value() - self.getCursorInfo() - - # if nothing is selected and mode is set to indent, simply insert as many - # space as needed to reach the next indentation level. - if self.noSelection and mode == 'indent': - - remainingSpaces = self.tabSpaces - \ - (self.cursorBlockPos % self.tabSpaces) - self.insertPlainText(' ' * remainingSpaces) - return - - selectedBlocks = self.findBlocks(self.firstChar, self.lastChar) - beforeBlocks = self.findBlocks( - last=self.firstChar - 1, exclude=selectedBlocks) - afterBlocks = self.findBlocks( - first=self.lastChar + 1, exclude=selectedBlocks) - - beforeBlocksText = self.blocks2list(beforeBlocks) - selectedBlocksText = self.blocks2list(selectedBlocks, mode) - afterBlocksText = self.blocks2list(afterBlocks) - - combinedText = '\n'.join( - beforeBlocksText + selectedBlocksText + afterBlocksText) - - # make sure the line count stays the same - originalBlockCount = len(self.toPlainText().split('\n')) - combinedText = '\n'.join(combinedText.split('\n')[:originalBlockCount]) - - self.clear() - self.setPlainText(combinedText) - - if self.noSelection: - self.cursor.setPosition(self.lastChar) - - # check whether the the original selection was from top to bottom or vice versa - else: - if self.originalPosition == self.firstChar: - first = self.lastChar - last = self.firstChar - firstBlockSnap = QtGui.QTextCursor.EndOfBlock - lastBlockSnap = QtGui.QTextCursor.StartOfBlock - else: - first = self.firstChar - last = self.lastChar - firstBlockSnap = QtGui.QTextCursor.StartOfBlock - lastBlockSnap = QtGui.QTextCursor.EndOfBlock - - self.cursor.setPosition(first) - self.cursor.movePosition( - firstBlockSnap, QtGui.QTextCursor.MoveAnchor) - self.cursor.setPosition(last, QtGui.QTextCursor.KeepAnchor) - self.cursor.movePosition( - lastBlockSnap, QtGui.QTextCursor.KeepAnchor) - - self.setTextCursor(self.cursor) - self.verticalScrollBar().setValue(pre_scroll) - - def findBlocks(self, first=0, last=None, exclude=[]): - blocks = [] - if last == None: - last = self.document().characterCount() - for pos in range(first, last + 1): - block = self.document().findBlock(pos) - if block not in blocks and block not in exclude: - blocks.append(block) - return blocks - - def blocks2list(self, blocks, mode=None): - text = [] - for block in blocks: - blockText = block.text() - if mode == 'unindent': - if blockText.startswith(' ' * self.tabSpaces): - blockText = blockText[self.tabSpaces:] - self.lastChar -= self.tabSpaces - elif blockText.startswith('\t'): - blockText = blockText[1:] - self.lastChar -= 1 - - elif mode == 'indent': - blockText = ' ' * self.tabSpaces + blockText - self.lastChar += self.tabSpaces - - text.append(blockText) - - return text - - def highlightCurrentLine(self): - ''' - Highlight currently selected line - ''' - extraSelections = [] - - selection = QtWidgets.QTextEdit.ExtraSelection() - - lineColor = QtGui.QColor(62, 62, 62, 255) - - selection.format.setBackground(lineColor) - selection.format.setProperty( - QtGui.QTextFormat.FullWidthSelection, True) - selection.cursor = self.textCursor() - selection.cursor.clearSelection() - - extraSelections.append(selection) - - self.setExtraSelections(extraSelections) - self.scrollToCursor() - - def format(self, rgb, style=''): - ''' - Return a QtWidgets.QTextCharFormat with the given attributes. - ''' - color = QtGui.QColor(*rgb) - textFormat = QtGui.QTextCharFormat() - textFormat.setForeground(color) - - if 'bold' in style: - textFormat.setFontWeight(QtGui.QFont.Bold) - if 'italic' in style: - textFormat.setFontItalic(True) - if 'underline' in style: - textFormat.setUnderlineStyle(QtGui.QTextCharFormat.SingleUnderline) - - return textFormat - - -class KSLineNumberArea(QtWidgets.QWidget): - def __init__(self, scriptEditor): - super(KSLineNumberArea, self).__init__(scriptEditor) - - self.scriptEditor = scriptEditor - self.setStyleSheet("text-align: center;") - - def paintEvent(self, event): - self.scriptEditor.lineNumberAreaPaintEvent(event) - return - - -class KSScriptEditorHighlighter(QtGui.QSyntaxHighlighter): - ''' - This is also adapted from an original version by Wouter Gilsing. His comments: - - Modified, simplified version of some code found I found when researching: - wiki.python.org/moin/PyQt/Python%20syntax%20highlighting - They did an awesome job, so credits to them. I only needed to make some - modifications to make it fit my needs. - ''' - - def __init__(self, document, parent=None): - - super(KSScriptEditorHighlighter, self).__init__(document) - self.knobScripter = parent - self.script_editor = self.knobScripter.script_editor - self.selected_text = "" - self.selected_text_prev = "" - self.rules_sublime = "" - - self.styles = { - 'keyword': self.format([238, 117, 181], 'bold'), - 'string': self.format([242, 136, 135]), - 'comment': self.format([143, 221, 144]), - 'numbers': self.format([174, 129, 255]), - 'custom': self.format([255, 170, 0], 'italic'), - 'selected': self.format([255, 255, 255], 'bold underline'), - 'underline': self.format([240, 240, 240], 'underline'), - } - - self.keywords = [ - 'and', 'assert', 'break', 'class', 'continue', 'def', - 'del', 'elif', 'else', 'except', 'exec', 'finally', - 'for', 'from', 'global', 'if', 'import', 'in', - 'is', 'lambda', 'not', 'or', 'pass', 'print', - 'raise', 'return', 'try', 'while', 'yield', 'with', 'as' - ] - - self.operatorKeywords = [ - '=', '==', '!=', '<', '<=', '>', '>=', - '\+', '-', '\*', '/', '//', '\%', '\*\*', - '\+=', '-=', '\*=', '/=', '\%=', - '\^', '\|', '\&', '\~', '>>', '<<' - ] - - self.variableKeywords = ['int', 'str', - 'float', 'bool', 'list', 'dict', 'set'] - - self.numbers = ['True', 'False', 'None'] - self.loadAltStyles() - - self.tri_single = (QtCore.QRegExp("'''"), 1, self.styles['comment']) - self.tri_double = (QtCore.QRegExp('"""'), 2, self.styles['comment']) - - # rules - rules = [] - - rules += [(r'\b%s\b' % i, 0, self.styles['keyword']) - for i in self.keywords] - rules += [(i, 0, self.styles['keyword']) - for i in self.operatorKeywords] - rules += [(r'\b%s\b' % i, 0, self.styles['numbers']) - for i in self.numbers] - - rules += [ - - # integers - (r'\b[0-9]+\b', 0, self.styles['numbers']), - # Double-quoted string, possibly containing escape sequences - (r'"[^"\\]*(\\.[^"\\]*)*"', 0, self.styles['string']), - # Single-quoted string, possibly containing escape sequences - (r"'[^'\\]*(\\.[^'\\]*)*'", 0, self.styles['string']), - # From '#' until a newline - (r'#[^\n]*', 0, self.styles['comment']), - ] - - # Build a QRegExp for each pattern - self.rules_nuke = [(QtCore.QRegExp(pat), index, fmt) - for (pat, index, fmt) in rules] - self.rules = self.rules_nuke - - def loadAltStyles(self): - ''' Loads other color styles apart from Nuke's default. ''' - self.styles_sublime = { - 'base': self.format([255, 255, 255]), - 'keyword': self.format([237, 36, 110]), - 'string': self.format([237, 229, 122]), - 'comment': self.format([125, 125, 125]), - 'numbers': self.format([165, 120, 255]), - 'functions': self.format([184, 237, 54]), - 'blue': self.format([130, 226, 255], 'italic'), - 'arguments': self.format([255, 170, 10], 'italic'), - 'custom': self.format([200, 200, 200], 'italic'), - 'underline': self.format([240, 240, 240], 'underline'), - 'selected': self.format([255, 255, 255], 'bold underline'), - } - - self.keywords_sublime = [ - 'and', 'assert', 'break', 'continue', - 'del', 'elif', 'else', 'except', 'exec', 'finally', - 'for', 'from', 'global', 'if', 'import', 'in', - 'is', 'lambda', 'not', 'or', 'pass', 'print', - 'raise', 'return', 'try', 'while', 'yield', 'with', 'as' - ] - self.operatorKeywords_sublime = [ - '=', '==', '!=', '<', '<=', '>', '>=', - '\+', '-', '\*', '/', '//', '\%', '\*\*', - '\+=', '-=', '\*=', '/=', '\%=', - '\^', '\|', '\&', '\~', '>>', '<<' - ] - - self.baseKeywords_sublime = [ - ',', - ] - - self.customKeywords_sublime = [ - 'nuke', - ] - - self.blueKeywords_sublime = [ - 'def', 'class', 'int', 'str', 'float', 'bool', 'list', 'dict', 'set' - ] - - self.argKeywords_sublime = [ - 'self', - ] - - self.tri_single_sublime = (QtCore.QRegExp( - "'''"), 1, self.styles_sublime['comment']) - self.tri_double_sublime = (QtCore.QRegExp( - '"""'), 2, self.styles_sublime['comment']) - self.numbers_sublime = ['True', 'False', 'None'] - - # rules - - rules = [] - # First turn everything inside parentheses orange - rules += [(r"def [\w]+[\s]*\((.*)\)", 1, - self.styles_sublime['arguments'])] - # Now restore unwanted stuff... - rules += [(i, 0, self.styles_sublime['base']) - for i in self.baseKeywords_sublime] - rules += [(r"[^\(\w),.][\s]*[\w]+", 0, self.styles_sublime['base'])] - - # Everything else - rules += [(r'\b%s\b' % i, 0, self.styles_sublime['keyword']) - for i in self.keywords_sublime] - rules += [(i, 0, self.styles_sublime['keyword']) - for i in self.operatorKeywords_sublime] - rules += [(i, 0, self.styles_sublime['custom']) - for i in self.customKeywords_sublime] - rules += [(r'\b%s\b' % i, 0, self.styles_sublime['blue']) - for i in self.blueKeywords_sublime] - rules += [(i, 0, self.styles_sublime['arguments']) - for i in self.argKeywords_sublime] - rules += [(r'\b%s\b' % i, 0, self.styles_sublime['numbers']) - for i in self.numbers_sublime] - - rules += [ - - # integers - (r'\b[0-9]+\b', 0, self.styles_sublime['numbers']), - # Double-quoted string, possibly containing escape sequences - (r'"[^"\\]*(\\.[^"\\]*)*"', 0, self.styles_sublime['string']), - # Single-quoted string, possibly containing escape sequences - (r"'[^'\\]*(\\.[^'\\]*)*'", 0, self.styles_sublime['string']), - # From '#' until a newline - (r'#[^\n]*', 0, self.styles_sublime['comment']), - # Function definitions - (r"def[\s]+([\w\.]+)", 1, self.styles_sublime['functions']), - # Class definitions - (r"class[\s]+([\w\.]+)", 1, self.styles_sublime['functions']), - # Class argument (which is also a class so must be green) - (r"class[\s]+[\w\.]+[\s]*\((.*)\)", - 1, self.styles_sublime['functions']), - # Function arguments also pick their style... - (r"def[\s]+[\w]+[\s]*\(([\w]+)", 1, - self.styles_sublime['arguments']), - ] - - # Build a QRegExp for each pattern - self.rules_sublime = [(QtCore.QRegExp(pat), index, fmt) - for (pat, index, fmt) in rules] - - def format(self, rgb, style=''): - ''' - Return a QtWidgets.QTextCharFormat with the given attributes. - ''' - - color = QtGui.QColor(*rgb) - textFormat = QtGui.QTextCharFormat() - textFormat.setForeground(color) - - if 'bold' in style: - textFormat.setFontWeight(QtGui.QFont.Bold) - if 'italic' in style: - textFormat.setFontItalic(True) - if 'underline' in style: - textFormat.setUnderlineStyle(QtGui.QTextCharFormat.SingleUnderline) - - return textFormat - - def highlightBlock(self, text): - ''' - Apply syntax highlighting to the given block of text. - ''' - # Do other syntax formatting - - if self.knobScripter.color_scheme: - self.color_scheme = self.knobScripter.color_scheme - else: - self.color_scheme = "nuke" - - if self.color_scheme == "nuke": - self.rules = self.rules_nuke - elif self.color_scheme == "sublime": - self.rules = self.rules_sublime - - for expression, nth, format in self.rules: - index = expression.indexIn(text, 0) - - while index >= 0: - # We actually want the index of the nth match - index = expression.pos(nth) - length = len(expression.cap(nth)) - self.setFormat(index, length, format) - index = expression.indexIn(text, index + length) - - self.setCurrentBlockState(0) - - # Multi-line strings etc. based on selected scheme - if self.color_scheme == "nuke": - in_multiline = self.match_multiline(text, *self.tri_single) - if not in_multiline: - in_multiline = self.match_multiline(text, *self.tri_double) - elif self.color_scheme == "sublime": - in_multiline = self.match_multiline(text, *self.tri_single_sublime) - if not in_multiline: - in_multiline = self.match_multiline( - text, *self.tri_double_sublime) - - # TODO if there's a selection, highlight same occurrences in the full document. If no selection but something highlighted, unhighlight full document. (do it thru regex or sth) - - def match_multiline(self, text, delimiter, in_state, style): - ''' - Check whether highlighting requires multiple lines. - ''' - # If inside triple-single quotes, start at 0 - if self.previousBlockState() == in_state: - start = 0 - add = 0 - # Otherwise, look for the delimiter on this line - else: - start = delimiter.indexIn(text) - # Move past this match - add = delimiter.matchedLength() - - # As long as there's a delimiter match on this line... - while start >= 0: - # Look for the ending delimiter - end = delimiter.indexIn(text, start + add) - # Ending delimiter on this line? - if end >= add: - length = end - start + add + delimiter.matchedLength() - self.setCurrentBlockState(0) - # No; multi-line string - else: - self.setCurrentBlockState(in_state) - length = len(text) - start + add - # Apply formatting - self.setFormat(start, length, style) - # Look for the next match - start = delimiter.indexIn(text, start + length) - - # Return True if still inside a multi-line string, False otherwise - if self.currentBlockState() == in_state: - return True - else: - return False - -# -------------------------------------------------------------------------------------- -# Script Output Widget -# The output logger works the same way as Nuke's python script editor output window -# -------------------------------------------------------------------------------------- - - -class ScriptOutputWidget(QtWidgets.QTextEdit): - def __init__(self, parent=None): - super(ScriptOutputWidget, self).__init__(parent) - self.knobScripter = parent - self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Expanding) - self.setMinimumHeight(20) - - def keyPressEvent(self, event): - ctrl = ((event.modifiers() and (Qt.ControlModifier)) != 0) - alt = ((event.modifiers() and (Qt.AltModifier)) != 0) - shift = ((event.modifiers() and (Qt.ShiftModifier)) != 0) - key = event.key() - if type(event) == QtGui.QKeyEvent: - # print event.key() - if key in [32]: # Space - return KnobScripter.keyPressEvent(self.knobScripter, event) - elif key in [Qt.Key_Backspace, Qt.Key_Delete]: - self.knobScripter.clearConsole() - return QtWidgets.QTextEdit.keyPressEvent(self, event) - - # def mousePressEvent(self, QMouseEvent): - # if QMouseEvent.button() == Qt.RightButton: - # self.knobScripter.clearConsole() - # QtWidgets.QTextEdit.mousePressEvent(self, QMouseEvent) - -# --------------------------------------------------------------------- -# Modified KnobScripterTextEdit to include snippets etc. -# --------------------------------------------------------------------- - - -class KnobScripterTextEditMain(KnobScripterTextEdit): - def __init__(self, knobScripter, output=None, parent=None): - super(KnobScripterTextEditMain, self).__init__(knobScripter) - self.knobScripter = knobScripter - self.script_output = output - self.nukeCompleter = None - self.currentNukeCompletion = None - - ######## - # FROM NUKE's SCRIPT EDITOR START - ######## - self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Expanding) - - # Setup completer - self.nukeCompleter = QtWidgets.QCompleter(self) - self.nukeCompleter.setWidget(self) - self.nukeCompleter.setCompletionMode( - QtWidgets.QCompleter.UnfilteredPopupCompletion) - self.nukeCompleter.setCaseSensitivity(Qt.CaseSensitive) - try: - self.nukeCompleter.setModel(QtGui.QStringListModel()) - except: - self.nukeCompleter.setModel(QtCore.QStringListModel()) - - self.nukeCompleter.activated.connect(self.insertNukeCompletion) - self.nukeCompleter.highlighted.connect(self.completerHighlightChanged) - ######## - # FROM NUKE's SCRIPT EDITOR END - ######## - - def findLongestEndingMatch(self, text, dic): - ''' - If the text ends with a key in the dictionary, it returns the key and value. - If there are several matches, returns the longest one. - False if no matches. - ''' - longest = 0 # len of longest match - match_key = None - match_snippet = "" - for key, val in dic.items(): - #match = re.search(r"[\s\.({\[,;=+-]"+key+r"(?:[\s)\]\"]+|$)",text) - match = re.search(r"[\s\.({\[,;=+-]" + key + r"$", text) - if match or text == key: - if len(key) > longest: - longest = len(key) - match_key = key - match_snippet = val - if match_key is None: - return False - return match_key, match_snippet - - def placeholderToEnd(self, text, placeholder): - '''Returns distance (int) from the first occurrence of the placeholder, to the end of the string with placeholders removed''' - search = re.search(placeholder, text) - if not search: - return -1 - from_start = search.start() - total = len(re.sub(placeholder, "", text)) - to_end = total - from_start - return to_end - - def addSnippetText(self, snippet_text): - ''' Adds the selected text as a snippet (taking care of $$, $name$ etc) to the script editor ''' - cursor_placeholder_find = r"(? 1: - cursor_len = positions[1] - positions[0] - 2 - - text = re.sub(cursor_placeholder_find, "", text) - self.cursor.insertText(text) - if placeholder_to_end >= 0: - for i in range(placeholder_to_end): - self.cursor.movePosition(QtGui.QTextCursor.PreviousCharacter) - for i in range(cursor_len): - self.cursor.movePosition( - QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(self.cursor) - - def keyPressEvent(self, event): - - ctrl = bool(event.modifiers() & Qt.ControlModifier) - alt = bool(event.modifiers() & Qt.AltModifier) - shift = bool(event.modifiers() & Qt.ShiftModifier) - key = event.key() - - # ADAPTED FROM NUKE's SCRIPT EDITOR: - # Get completer state - self.nukeCompleterShowing = self.nukeCompleter.popup().isVisible() - - # BEFORE ANYTHING ELSE, IF SPECIAL MODIFIERS SIMPLY IGNORE THE REST - if not self.nukeCompleterShowing and (ctrl or shift or alt): - # Bypassed! - if key not in [Qt.Key_Return, Qt.Key_Enter, Qt.Key_Tab]: - KnobScripterTextEdit.keyPressEvent(self, event) - return - - # If the completer is showing - if self.nukeCompleterShowing: - tc = self.textCursor() - # If we're hitting enter, do completion - if key in [Qt.Key_Return, Qt.Key_Enter, Qt.Key_Tab]: - if not self.currentNukeCompletion: - self.nukeCompleter.setCurrentRow(0) - self.currentNukeCompletion = self.nukeCompleter.currentCompletion() - # print str(self.nukeCompleter.completionModel[0]) - self.insertNukeCompletion(self.currentNukeCompletion) - self.nukeCompleter.popup().hide() - self.nukeCompleterShowing = False - # If you're hitting right or escape, hide the popup - elif key == Qt.Key_Right or key == Qt.Key_Escape: - self.nukeCompleter.popup().hide() - self.nukeCompleterShowing = False - # If you hit tab, escape or ctrl-space, hide the completer - elif key == Qt.Key_Tab or key == Qt.Key_Escape or (ctrl and key == Qt.Key_Space): - self.currentNukeCompletion = "" - self.nukeCompleter.popup().hide() - self.nukeCompleterShowing = False - # If none of the above, update the completion model - else: - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - # Edit completion model - colNum = tc.columnNumber() - posNum = tc.position() - inputText = self.toPlainText() - inputTextSplit = inputText.splitlines() - runningLength = 0 - currentLine = None - for line in inputTextSplit: - length = len(line) - runningLength += length - if runningLength >= posNum: - currentLine = line - break - runningLength += 1 - if currentLine: - completionPart = currentLine.split(" ")[-1] - if "(" in completionPart: - completionPart = completionPart.split("(")[-1] - self.completeNukePartUnderCursor(completionPart) - return - - if type(event) == QtGui.QKeyEvent: - if key == Qt.Key_Escape: # Close the knobscripter... - self.knobScripter.close() - elif not ctrl and not alt and not shift and event.key() == Qt.Key_Tab: - self.placeholder = "$$" - # 1. Set the cursor - self.cursor = self.textCursor() - - # 2. Save text before and after - cpos = self.cursor.position() - text_before_cursor = self.toPlainText()[:cpos] - line_before_cursor = text_before_cursor.split('\n')[-1] - text_after_cursor = self.toPlainText()[cpos:] - - # 3. Check coincidences in snippets dicts - try: # Meaning snippet found - match_key, match_snippet = self.findLongestEndingMatch( - line_before_cursor, self.knobScripter.snippets) - for i in range(len(match_key)): - self.cursor.deletePreviousChar() - # This function takes care of adding the appropriate snippet and moving the cursor... - self.addSnippetText(match_snippet) - except: # Meaning snippet not found... - # ADAPTED FROM NUKE's SCRIPT EDITOR: - tc = self.textCursor() - allCode = self.toPlainText() - colNum = tc.columnNumber() - posNum = tc.position() - - # ...and if there's text in the editor - if len(allCode.split()) > 0: - # There is text in the editor - currentLine = tc.block().text() - - # If you're not at the end of the line just add a tab - if colNum < len(currentLine): - # If there isn't a ')' directly to the right of the cursor add a tab - if currentLine[colNum:colNum + 1] != ')': - KnobScripterTextEdit.keyPressEvent(self, event) - return - # Else show the completer - else: - completionPart = currentLine[:colNum].split( - " ")[-1] - if "(" in completionPart: - completionPart = completionPart.split( - "(")[-1] - - self.completeNukePartUnderCursor( - completionPart) - - return - - # If you are at the end of the line, - else: - # If there's nothing to the right of you add a tab - if currentLine[colNum - 1:] == "" or currentLine.endswith(" "): - KnobScripterTextEdit.keyPressEvent(self, event) - return - # Else update completionPart and show the completer - completionPart = currentLine.split(" ")[-1] - if "(" in completionPart: - completionPart = completionPart.split("(")[-1] - - self.completeNukePartUnderCursor(completionPart) - return - - KnobScripterTextEdit.keyPressEvent(self, event) - elif event.key() in [Qt.Key_Enter, Qt.Key_Return]: - modifiers = QtWidgets.QApplication.keyboardModifiers() - if modifiers == QtCore.Qt.ControlModifier: - self.runScript() - else: - KnobScripterTextEdit.keyPressEvent(self, event) - else: - KnobScripterTextEdit.keyPressEvent(self, event) - - def getPyObjects(self, text): - ''' Returns a list containing all the functions, classes and variables found within the selected python text (code) ''' - matches = [] - # 1: Remove text inside triple quotes (leaving the quotes) - text_clean = '""'.join(text.split('"""')[::2]) - text_clean = '""'.join(text_clean.split("'''")[::2]) - - # 2: Remove text inside of quotes (leaving the quotes) except if \" - lines = text_clean.split("\n") - text_clean = "" - for line in lines: - line_clean = '""'.join(line.split('"')[::2]) - line_clean = '""'.join(line_clean.split("'")[::2]) - line_clean = line_clean.split("#")[0] - text_clean += line_clean + "\n" - - # 3. Split into segments (lines plus ";") - segments = re.findall(r"[^\n;]+", text_clean) - - # 4. Go case by case. - for s in segments: - # Declared vars - matches += re.findall(r"([\w\.]+)(?=[,\s\w]*=[^=]+$)", s) - # Def functions and arguments - function = re.findall(r"[\s]*def[\s]+([\w\.]+)[\s]*\([\s]*", s) - if len(function): - matches += function - args = re.split(r"[\s]*def[\s]+([\w\.]+)[\s]*\([\s]*", s) - if len(args) > 1: - args = args[-1] - matches += re.findall( - r"(?adrianpueyo.com, 2016-2019') - kspSignature.setOpenExternalLinks(True) - kspSignature.setStyleSheet('''color:#555;font-size:9px;''') - kspSignature.setAlignment(QtCore.Qt.AlignRight) - - fontLabel = QtWidgets.QLabel("Font:") - self.fontBox = QtWidgets.QFontComboBox() - self.fontBox.setCurrentFont(QtGui.QFont(self.font)) - self.fontBox.currentFontChanged.connect(self.fontChanged) - - fontSizeLabel = QtWidgets.QLabel("Font size:") - self.fontSizeBox = QtWidgets.QSpinBox() - self.fontSizeBox.setValue(self.oldFontSize) - self.fontSizeBox.setMinimum(6) - self.fontSizeBox.setMaximum(100) - self.fontSizeBox.valueChanged.connect(self.fontSizeChanged) - - windowWLabel = QtWidgets.QLabel("Width (px):") - windowWLabel.setToolTip("Default window width in pixels") - self.windowWBox = QtWidgets.QSpinBox() - self.windowWBox.setValue(self.knobScripter.windowDefaultSize[0]) - self.windowWBox.setMinimum(200) - self.windowWBox.setMaximum(4000) - self.windowWBox.setToolTip("Default window width in pixels") - - windowHLabel = QtWidgets.QLabel("Height (px):") - windowHLabel.setToolTip("Default window height in pixels") - self.windowHBox = QtWidgets.QSpinBox() - self.windowHBox.setValue(self.knobScripter.windowDefaultSize[1]) - self.windowHBox.setMinimum(100) - self.windowHBox.setMaximum(2000) - self.windowHBox.setToolTip("Default window height in pixels") - - # TODO: "Grab current dimensions" button - - tabSpaceLabel = QtWidgets.QLabel("Tab spaces:") - tabSpaceLabel.setToolTip("Number of spaces to add with the tab key.") - self.tabSpace2 = QtWidgets.QRadioButton("2") - self.tabSpace4 = QtWidgets.QRadioButton("4") - tabSpaceButtonGroup = QtWidgets.QButtonGroup(self) - tabSpaceButtonGroup.addButton(self.tabSpace2) - tabSpaceButtonGroup.addButton(self.tabSpace4) - self.tabSpace2.setChecked(self.knobScripter.tabSpaces == 2) - self.tabSpace4.setChecked(self.knobScripter.tabSpaces == 4) - - pinDefaultLabel = QtWidgets.QLabel("Always on top:") - pinDefaultLabel.setToolTip("Default mode of the PIN toggle.") - self.pinDefaultOn = QtWidgets.QRadioButton("On") - self.pinDefaultOff = QtWidgets.QRadioButton("Off") - pinDefaultButtonGroup = QtWidgets.QButtonGroup(self) - pinDefaultButtonGroup.addButton(self.pinDefaultOn) - pinDefaultButtonGroup.addButton(self.pinDefaultOff) - self.pinDefaultOn.setChecked(self.knobScripter.pinned == True) - self.pinDefaultOff.setChecked(self.knobScripter.pinned == False) - self.pinDefaultOn.clicked.connect(lambda: self.knobScripter.pin(True)) - self.pinDefaultOff.clicked.connect( - lambda: self.knobScripter.pin(False)) - - colorSchemeLabel = QtWidgets.QLabel("Color scheme:") - colorSchemeLabel.setToolTip("Syntax highlighting text style.") - self.colorSchemeSublime = QtWidgets.QRadioButton("subl") - self.colorSchemeNuke = QtWidgets.QRadioButton("nuke") - colorSchemeButtonGroup = QtWidgets.QButtonGroup(self) - colorSchemeButtonGroup.addButton(self.colorSchemeSublime) - colorSchemeButtonGroup.addButton(self.colorSchemeNuke) - colorSchemeButtonGroup.buttonClicked.connect(self.colorSchemeChanged) - self.colorSchemeSublime.setChecked( - self.knobScripter.color_scheme == "sublime") - self.colorSchemeNuke.setChecked( - self.knobScripter.color_scheme == "nuke") - - showLabelsLabel = QtWidgets.QLabel("Show labels:") - showLabelsLabel.setToolTip( - "Display knob labels on the knob dropdown\nOtherwise, shows the internal name only.") - self.showLabelsOn = QtWidgets.QRadioButton("On") - self.showLabelsOff = QtWidgets.QRadioButton("Off") - showLabelsButtonGroup = QtWidgets.QButtonGroup(self) - showLabelsButtonGroup.addButton(self.showLabelsOn) - showLabelsButtonGroup.addButton(self.showLabelsOff) - self.showLabelsOn.setChecked(self.knobScripter.pinned == True) - self.showLabelsOff.setChecked(self.knobScripter.pinned == False) - self.showLabelsOn.clicked.connect(lambda: self.knobScripter.pin(True)) - self.showLabelsOff.clicked.connect( - lambda: self.knobScripter.pin(False)) - - self.buttonBox = QtWidgets.QDialogButtonBox( - QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) - self.buttonBox.accepted.connect(self.savePrefs) - self.buttonBox.rejected.connect(self.cancelPrefs) - - # Loaded custom values - self.ksPrefs = self.knobScripter.loadPrefs() - if self.ksPrefs != []: - try: - self.fontSizeBox.setValue(self.ksPrefs['font_size']) - self.windowWBox.setValue(self.ksPrefs['window_default_w']) - self.windowHBox.setValue(self.ksPrefs['window_default_h']) - self.tabSpace2.setChecked(self.ksPrefs['tab_spaces'] == 2) - self.tabSpace4.setChecked(self.ksPrefs['tab_spaces'] == 4) - self.pinDefaultOn.setChecked(self.ksPrefs['pin_default'] == 1) - self.pinDefaultOff.setChecked(self.ksPrefs['pin_default'] == 0) - self.showLabelsOn.setChecked(self.ksPrefs['show_labels'] == 1) - self.showLabelsOff.setChecked(self.ksPrefs['show_labels'] == 0) - self.colorSchemeSublime.setChecked( - self.ksPrefs['color_scheme'] == "sublime") - self.colorSchemeNuke.setChecked( - self.ksPrefs['color_scheme'] == "nuke") - except: - pass - - # Layouts - font_layout = QtWidgets.QHBoxLayout() - font_layout.addWidget(fontLabel) - font_layout.addWidget(self.fontBox) - - fontSize_layout = QtWidgets.QHBoxLayout() - fontSize_layout.addWidget(fontSizeLabel) - fontSize_layout.addWidget(self.fontSizeBox) - - windowW_layout = QtWidgets.QHBoxLayout() - windowW_layout.addWidget(windowWLabel) - windowW_layout.addWidget(self.windowWBox) - - windowH_layout = QtWidgets.QHBoxLayout() - windowH_layout.addWidget(windowHLabel) - windowH_layout.addWidget(self.windowHBox) - - tabSpacesButtons_layout = QtWidgets.QHBoxLayout() - tabSpacesButtons_layout.addWidget(self.tabSpace2) - tabSpacesButtons_layout.addWidget(self.tabSpace4) - tabSpaces_layout = QtWidgets.QHBoxLayout() - tabSpaces_layout.addWidget(tabSpaceLabel) - tabSpaces_layout.addLayout(tabSpacesButtons_layout) - - pinDefaultButtons_layout = QtWidgets.QHBoxLayout() - pinDefaultButtons_layout.addWidget(self.pinDefaultOn) - pinDefaultButtons_layout.addWidget(self.pinDefaultOff) - pinDefault_layout = QtWidgets.QHBoxLayout() - pinDefault_layout.addWidget(pinDefaultLabel) - pinDefault_layout.addLayout(pinDefaultButtons_layout) - - showLabelsButtons_layout = QtWidgets.QHBoxLayout() - showLabelsButtons_layout.addWidget(self.showLabelsOn) - showLabelsButtons_layout.addWidget(self.showLabelsOff) - showLabels_layout = QtWidgets.QHBoxLayout() - showLabels_layout.addWidget(showLabelsLabel) - showLabels_layout.addLayout(showLabelsButtons_layout) - - colorSchemeButtons_layout = QtWidgets.QHBoxLayout() - colorSchemeButtons_layout.addWidget(self.colorSchemeSublime) - colorSchemeButtons_layout.addWidget(self.colorSchemeNuke) - colorScheme_layout = QtWidgets.QHBoxLayout() - colorScheme_layout.addWidget(colorSchemeLabel) - colorScheme_layout.addLayout(colorSchemeButtons_layout) - - self.master_layout = QtWidgets.QVBoxLayout() - self.master_layout.addWidget(kspTitle) - self.master_layout.addWidget(kspSignature) - self.master_layout.addWidget(kspLine) - self.master_layout.addLayout(font_layout) - self.master_layout.addLayout(fontSize_layout) - self.master_layout.addLayout(windowW_layout) - self.master_layout.addLayout(windowH_layout) - self.master_layout.addLayout(tabSpaces_layout) - self.master_layout.addLayout(pinDefault_layout) - self.master_layout.addLayout(showLabels_layout) - self.master_layout.addLayout(colorScheme_layout) - self.master_layout.addWidget(self.buttonBox) - self.setLayout(self.master_layout) - self.setFixedSize(self.minimumSize()) - - def savePrefs(self): - self.font = self.fontBox.currentFont().family() - ks_prefs = { - 'font_size': self.fontSizeBox.value(), - 'window_default_w': self.windowWBox.value(), - 'window_default_h': self.windowHBox.value(), - 'tab_spaces': self.tabSpaceValue(), - 'pin_default': self.pinDefaultValue(), - 'show_labels': self.showLabelsValue(), - 'font': self.font, - 'color_scheme': self.colorSchemeValue(), - } - self.knobScripter.script_editor_font.setFamily(self.font) - self.knobScripter.script_editor.setFont( - self.knobScripter.script_editor_font) - self.knobScripter.font = self.font - self.knobScripter.color_scheme = self.colorSchemeValue() - self.knobScripter.tabSpaces = self.tabSpaceValue() - self.knobScripter.script_editor.tabSpaces = self.tabSpaceValue() - with open(self.prefs_txt, "w") as f: - prefs = json.dump(ks_prefs, f, sort_keys=True, indent=4) - self.accept() - self.knobScripter.highlighter.rehighlight() - self.knobScripter.show_labels = self.showLabelsValue() - if self.knobScripter.nodeMode: - self.knobScripter.refreshClicked() - return prefs - - def cancelPrefs(self): - self.knobScripter.script_editor_font.setPointSize(self.oldFontSize) - self.knobScripter.script_editor.setFont( - self.knobScripter.script_editor_font) - self.knobScripter.color_scheme = self.oldScheme - self.knobScripter.highlighter.rehighlight() - self.reject() - - def fontSizeChanged(self): - self.knobScripter.script_editor_font.setPointSize( - self.fontSizeBox.value()) - self.knobScripter.script_editor.setFont( - self.knobScripter.script_editor_font) - return - - def fontChanged(self): - self.font = self.fontBox.currentFont().family() - self.knobScripter.script_editor_font.setFamily(self.font) - self.knobScripter.script_editor.setFont( - self.knobScripter.script_editor_font) - return - - def colorSchemeChanged(self): - self.knobScripter.color_scheme = self.colorSchemeValue() - self.knobScripter.highlighter.rehighlight() - return - - def tabSpaceValue(self): - return 2 if self.tabSpace2.isChecked() else 4 - - def pinDefaultValue(self): - return 1 if self.pinDefaultOn.isChecked() else 0 - - def showLabelsValue(self): - return 1 if self.showLabelsOn.isChecked() else 0 - - def colorSchemeValue(self): - return "nuke" if self.colorSchemeNuke.isChecked() else "sublime" - - def closeEvent(self, event): - self.cancelPrefs() - self.close() - - -def updateContext(): - ''' - Get the current selection of nodes with their appropriate context - Doing this outside the KnobScripter -> forces context update inside groups when needed - ''' - global knobScripterSelectedNodes - knobScripterSelectedNodes = nuke.selectedNodes() - return - -# -------------------------------- -# FindReplace -# -------------------------------- - - -class FindReplaceWidget(QtWidgets.QWidget): - ''' SearchReplace Widget for the knobscripter. FindReplaceWidget(editor = QPlainTextEdit) ''' - - def __init__(self, parent): - super(FindReplaceWidget, self).__init__(parent) - - self.editor = parent.script_editor - - self.initUI() - - def initUI(self): - - # -------------- - # Find Row - # -------------- - - # Widgets - self.find_label = QtWidgets.QLabel("Find:") - # self.find_label.setSizePolicy(QtWidgets.QSizePolicy.Fixed,QtWidgets.QSizePolicy.Fixed) - self.find_label.setFixedWidth(50) - self.find_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.find_lineEdit = QtWidgets.QLineEdit() - self.find_next_button = QtWidgets.QPushButton("Next") - self.find_next_button.clicked.connect(self.find) - self.find_prev_button = QtWidgets.QPushButton("Previous") - self.find_prev_button.clicked.connect(self.findBack) - self.find_lineEdit.returnPressed.connect(self.find_next_button.click) - - # Layout - self.find_layout = QtWidgets.QHBoxLayout() - self.find_layout.addWidget(self.find_label) - self.find_layout.addWidget(self.find_lineEdit, stretch=1) - self.find_layout.addWidget(self.find_next_button) - self.find_layout.addWidget(self.find_prev_button) - - # -------------- - # Replace Row - # -------------- - - # Widgets - self.replace_label = QtWidgets.QLabel("Replace:") - # self.replace_label.setSizePolicy(QtWidgets.QSizePolicy.Fixed,QtWidgets.QSizePolicy.Fixed) - self.replace_label.setFixedWidth(50) - self.replace_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.replace_lineEdit = QtWidgets.QLineEdit() - self.replace_button = QtWidgets.QPushButton("Replace") - self.replace_button.clicked.connect(self.replace) - self.replace_all_button = QtWidgets.QPushButton("Replace All") - self.replace_all_button.clicked.connect( - lambda: self.replace(rep_all=True)) - self.replace_lineEdit.returnPressed.connect(self.replace_button.click) - - # Layout - self.replace_layout = QtWidgets.QHBoxLayout() - self.replace_layout.addWidget(self.replace_label) - self.replace_layout.addWidget(self.replace_lineEdit, stretch=1) - self.replace_layout.addWidget(self.replace_button) - self.replace_layout.addWidget(self.replace_all_button) - - # Info text - self.info_text = QtWidgets.QLabel("") - self.info_text.setVisible(False) - self.info_text.mousePressEvent = lambda x: self.info_text.setVisible( - False) - #f = self.info_text.font() - # f.setItalic(True) - # self.info_text.setFont(f) - # self.info_text.clicked.connect(lambda:self.info_text.setVisible(False)) - - # Divider line - line = QtWidgets.QFrame() - line.setFrameShape(QtWidgets.QFrame.HLine) - line.setFrameShadow(QtWidgets.QFrame.Sunken) - line.setLineWidth(0) - line.setMidLineWidth(1) - line.setFrameShadow(QtWidgets.QFrame.Sunken) - - # -------------- - # Main Layout - # -------------- - - self.layout = QtWidgets.QVBoxLayout() - self.layout.addSpacing(4) - self.layout.addWidget(self.info_text) - self.layout.addLayout(self.find_layout) - self.layout.addLayout(self.replace_layout) - self.layout.setSpacing(4) - try: # >n11 - self.layout.setMargin(2) - except: # 0: # If not found but there are matches, start over - cursor.movePosition(QtGui.QTextCursor.Start) - self.editor.setTextCursor(cursor) - self.editor.find(find_str, flags) - else: - cursor.insertText(rep_str) - self.editor.find( - rep_str, flags | QtGui.QTextDocument.FindBackward) - - cursor.endEditBlock() - self.replace_lineEdit.setFocus() - return - - -# -------------------------------- -# Snippets -# -------------------------------- -class SnippetsPanel(QtWidgets.QDialog): - def __init__(self, parent): - super(SnippetsPanel, self).__init__(parent) - - self.knobScripter = parent - - self.setWindowFlags(self.windowFlags() | - QtCore.Qt.WindowStaysOnTopHint) - self.setWindowTitle("Snippet editor") - - self.snippets_txt_path = self.knobScripter.snippets_txt_path - self.snippets_dict = self.loadSnippetsDict(path=self.snippets_txt_path) - #self.snippets_dict = snippets_dic - - # self.saveSnippets(snippets_dic) - - self.initUI() - self.resize(500, 300) - - def initUI(self): - self.layout = QtWidgets.QVBoxLayout() - - # First Area (Titles) - title_layout = QtWidgets.QHBoxLayout() - shortcuts_label = QtWidgets.QLabel("Shortcut") - code_label = QtWidgets.QLabel("Code snippet") - title_layout.addWidget(shortcuts_label, stretch=1) - title_layout.addWidget(code_label, stretch=2) - self.layout.addLayout(title_layout) - - # Main Scroll area - self.scroll_content = QtWidgets.QWidget() - self.scroll_layout = QtWidgets.QVBoxLayout() - - self.buildSnippetWidgets() - - self.scroll_content.setLayout(self.scroll_layout) - - # Scroll Area Properties - self.scroll = QtWidgets.QScrollArea() - self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) - self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.scroll.setWidgetResizable(True) - self.scroll.setWidget(self.scroll_content) - - self.layout.addWidget(self.scroll) - - # File knob test - #self.filePath_lineEdit = SnippetFilePath(self) - # self.filePath_lineEdit - # self.layout.addWidget(self.filePath_lineEdit) - - # Lower buttons - self.bottom_layout = QtWidgets.QHBoxLayout() - - self.add_btn = QtWidgets.QPushButton("Add snippet") - self.add_btn.setToolTip("Create empty fields for an extra snippet.") - self.add_btn.clicked.connect(self.addSnippet) - self.bottom_layout.addWidget(self.add_btn) - - self.addPath_btn = QtWidgets.QPushButton("Add custom path") - self.addPath_btn.setToolTip( - "Add a custom path to an external snippets .txt file.") - self.addPath_btn.clicked.connect(self.addCustomPath) - self.bottom_layout.addWidget(self.addPath_btn) - - self.bottom_layout.addStretch() - - self.save_btn = QtWidgets.QPushButton('OK') - self.save_btn.setToolTip( - "Save the snippets into a json file and close the panel.") - self.save_btn.clicked.connect(self.okPressed) - self.bottom_layout.addWidget(self.save_btn) - - self.cancel_btn = QtWidgets.QPushButton("Cancel") - self.cancel_btn.setToolTip("Cancel any new snippets or modifications.") - self.cancel_btn.clicked.connect(self.close) - self.bottom_layout.addWidget(self.cancel_btn) - - self.apply_btn = QtWidgets.QPushButton('Apply') - self.apply_btn.setToolTip("Save the snippets into a json file.") - self.apply_btn.setShortcut('Ctrl+S') - self.apply_btn.clicked.connect(self.applySnippets) - self.bottom_layout.addWidget(self.apply_btn) - - self.help_btn = QtWidgets.QPushButton('Help') - self.help_btn.setShortcut('F1') - self.help_btn.clicked.connect(self.showHelp) - self.bottom_layout.addWidget(self.help_btn) - - self.layout.addLayout(self.bottom_layout) - - self.setLayout(self.layout) - - def reload(self): - ''' - Clears everything without saving and redoes the widgets etc. - Only to be called if the panel isn't shown meaning it's closed. - ''' - for i in reversed(range(self.scroll_layout.count())): - self.scroll_layout.itemAt(i).widget().deleteLater() - - self.snippets_dict = self.loadSnippetsDict(path=self.snippets_txt_path) - - self.buildSnippetWidgets() - - def buildSnippetWidgets(self): - for i, (key, val) in enumerate(self.snippets_dict.items()): - if re.match(r"\[custom-path-[0-9]+\]$", key): - file_edit = SnippetFilePath(val) - self.scroll_layout.insertWidget(-1, file_edit) - else: - snippet_edit = SnippetEdit(key, val, parent=self) - self.scroll_layout.insertWidget(-1, snippet_edit) - - def loadSnippetsDict(self, path=""): - ''' Load prefs. TO REMOVE ''' - if path == "": - path = self.knobScripter.snippets_txt_path - if not os.path.isfile(self.snippets_txt_path): - return {} - else: - with open(self.snippets_txt_path, "r") as f: - self.snippets = json.load(f) - return self.snippets - - def getSnippetsAsDict(self): - dic = {} - num_snippets = self.scroll_layout.count() - path_i = 1 - for s in range(num_snippets): - se = self.scroll_layout.itemAt(s).widget() - if se.__class__.__name__ == "SnippetEdit": - key = se.shortcut_editor.text() - val = se.script_editor.toPlainText() - if key != "": - dic[key] = val - else: - path = se.filepath_lineEdit.text() - if path != "": - dic["[custom-path-{}]".format(str(path_i))] = path - path_i += 1 - return dic - - def saveSnippets(self, snippets=""): - if snippets == "": - snippets = self.getSnippetsAsDict() - with open(self.snippets_txt_path, "w") as f: - prefs = json.dump(snippets, f, sort_keys=True, indent=4) - return prefs - - def applySnippets(self): - self.saveSnippets() - self.knobScripter.snippets = self.knobScripter.loadSnippets(maxDepth=5) - self.knobScripter.loadSnippets() - - def okPressed(self): - self.applySnippets() - self.accept() - - def addSnippet(self, key="", val=""): - se = SnippetEdit(key, val, parent=self) - self.scroll_layout.insertWidget(0, se) - self.show() - return se - - def addCustomPath(self, path=""): - cpe = SnippetFilePath(path) - self.scroll_layout.insertWidget(0, cpe) - self.show() - cpe.browseSnippets() - return cpe - - def showHelp(self): - ''' Create a new snippet, auto-completed with the help ''' - help_key = "help" - help_val = """Snippets are a convenient way to have code blocks that you can call through a shortcut.\n\n1. Simply write a shortcut on the text input field on the left. You can see this one is set to "test".\n\n2. Then, write a code or whatever in this script editor. You can include $$ as the placeholder for where you'll want the mouse cursor to appear.\n\n3. Finally, click OK or Apply to save the snippets. On the main script editor, you'll be able to call any snippet by writing the shortcut (in this example: help) and pressing the Tab key.\n\nIn order to remove a snippet, simply leave the shortcut and contents blank, and save the snippets.""" - help_se = self.addSnippet(help_key, help_val) - help_se.script_editor.resize(160, 160) - - -class SnippetEdit(QtWidgets.QWidget): - ''' Simple widget containing two fields, for the snippet shortcut and content ''' - - def __init__(self, key="", val="", parent=None): - super(SnippetEdit, self).__init__(parent) - - self.knobScripter = parent.knobScripter - self.color_scheme = self.knobScripter.color_scheme - self.layout = QtWidgets.QHBoxLayout() - - self.shortcut_editor = QtWidgets.QLineEdit(self) - f = self.shortcut_editor.font() - f.setWeight(QtGui.QFont.Bold) - self.shortcut_editor.setFont(f) - self.shortcut_editor.setText(str(key)) - #self.script_editor = QtWidgets.QTextEdit(self) - self.script_editor = KnobScripterTextEdit() - self.script_editor.setMinimumHeight(100) - self.script_editor.setStyleSheet( - 'background:#282828;color:#EEE;') # Main Colors - self.highlighter = KSScriptEditorHighlighter( - self.script_editor.document(), self) - self.script_editor_font = self.knobScripter.script_editor_font - self.script_editor.setFont(self.script_editor_font) - self.script_editor.resize(90, 90) - self.script_editor.setPlainText(str(val)) - self.layout.addWidget(self.shortcut_editor, - stretch=1, alignment=Qt.AlignTop) - self.layout.addWidget(self.script_editor, stretch=2) - self.layout.setContentsMargins(0, 0, 0, 0) - - self.setLayout(self.layout) - - -class SnippetFilePath(QtWidgets.QWidget): - ''' Simple widget containing a filepath lineEdit and a button to open the file browser ''' - - def __init__(self, path="", parent=None): - super(SnippetFilePath, self).__init__(parent) - - self.layout = QtWidgets.QHBoxLayout() - - self.custompath_label = QtWidgets.QLabel(self) - self.custompath_label.setText("Custom path: ") - - self.filepath_lineEdit = QtWidgets.QLineEdit(self) - self.filepath_lineEdit.setText(str(path)) - #self.script_editor = QtWidgets.QTextEdit(self) - self.filepath_lineEdit.setStyleSheet( - 'background:#282828;color:#EEE;') # Main Colors - self.script_editor_font = QtGui.QFont() - self.script_editor_font.setFamily("Courier") - self.script_editor_font.setStyleHint(QtGui.QFont.Monospace) - self.script_editor_font.setFixedPitch(True) - self.script_editor_font.setPointSize(11) - self.filepath_lineEdit.setFont(self.script_editor_font) - - self.file_button = QtWidgets.QPushButton(self) - self.file_button.setText("Browse...") - self.file_button.clicked.connect(self.browseSnippets) - - self.layout.addWidget(self.custompath_label) - self.layout.addWidget(self.filepath_lineEdit) - self.layout.addWidget(self.file_button) - self.layout.setContentsMargins(0, 10, 0, 10) - - self.setLayout(self.layout) - - def browseSnippets(self): - ''' Opens file panel for ...snippets.txt ''' - browseLocation = nuke.getFilename('Select snippets file', '*.txt') - - if not browseLocation: - return - - self.filepath_lineEdit.setText(browseLocation) - return - - -# -------------------------------- -# Implementation -# -------------------------------- - -def showKnobScripter(knob="knobChanged"): - selection = nuke.selectedNodes() - if not len(selection): - pan = KnobScripter() - else: - pan = KnobScripter(selection[0], knob) - pan.show() - - -def addKnobScripterPanel(): - global knobScripterPanel - try: - knobScripterPanel = panels.registerWidgetAsPanel('nuke.KnobScripterPane', 'Knob Scripter', - 'com.adrianpueyo.KnobScripterPane') - knobScripterPanel.addToPane(nuke.getPaneFor('Properties.1')) - - except: - knobScripterPanel = panels.registerWidgetAsPanel( - 'nuke.KnobScripterPane', 'Knob Scripter', 'com.adrianpueyo.KnobScripterPane') - - -nuke.KnobScripterPane = KnobScripterPane -log("KS LOADED") -ksShortcut = "alt+z" -addKnobScripterPanel() -nuke.menu('Nuke').addCommand( - 'Edit/Node/Open Floating Knob Scripter', showKnobScripter, ksShortcut) -nuke.menu('Nuke').addCommand('Edit/Node/Update KnobScripter Context', - updateContext).setVisible(False) diff --git a/openpype/hosts/nuke/startup/init.py b/openpype/hosts/nuke/startup/init.py deleted file mode 100644 index d7560814bf..0000000000 --- a/openpype/hosts/nuke/startup/init.py +++ /dev/null @@ -1,4 +0,0 @@ -import nuke - -# default write mov -nuke.knobDefault('Write.mov.colorspace', 'sRGB') From 09e92ebad87c09d0186f759ac738fc2389270ec3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Jun 2022 15:20:05 +0200 Subject: [PATCH 0657/1227] flame: make sure `representations` key is always on instance data --- .../hosts/flame/plugins/publish/collect_timeline_instances.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py index 0aca7c38d5..aa19b78bf1 100644 --- a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py @@ -125,7 +125,8 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): "flameAddTasks": self.add_tasks, "tasks": { task["name"]: {"type": task["type"]} - for task in self.add_tasks} + for task in self.add_tasks}, + "representations": [] }) self.log.debug("__ inst_data: {}".format(pformat(inst_data))) From be328e5396760f683c42ed21ca01385e42ec2cf0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Jun 2022 15:20:34 +0200 Subject: [PATCH 0658/1227] flame: implementing `keep_original_representation` switch --- .../hosts/flame/plugins/publish/extract_subset_resources.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 0bad3f7cfc..255d57a8ee 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -22,6 +22,8 @@ class ExtractSubsetResources(openpype.api.Extractor): hosts = ["flame"] # plugin defaults + keep_original_representation = False + default_presets = { "thumbnail": { "active": True, @@ -44,7 +46,9 @@ class ExtractSubsetResources(openpype.api.Extractor): export_presets_mapping = {} def process(self, instance): - if "representations" not in instance.data: + + if not self.keep_original_representation: + # remove previeous representation if not needed instance.data["representations"] = [] # flame objects From c25cb9e1f5749b4dd071c873b94fd1e5be1193bd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Jun 2022 16:03:02 +0200 Subject: [PATCH 0659/1227] fixed missing "parent" in fields --- openpype/client/entities.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index a56288c1e8..1bfab5ad57 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -23,7 +23,7 @@ def _get_project_connection(project_name=None): return mongodb -def _prepare_fields(fields): +def _prepare_fields(fields, ensure_fields=None): if not fields: return None @@ -33,6 +33,10 @@ def _prepare_fields(fields): } if "_id" not in output: output["_id"] = True + + if ensure_fields: + for key in ensure_fields: + output[key] = True return output @@ -655,9 +659,8 @@ def get_last_versions(project_name, subset_ids, fields=None): doc["_version_id"] for doc in conn.aggregate(_pipeline) ] - fields = _prepare_fields(fields) - if fields and "parent" not in fields: - fields.append("parent") + + fields = _prepare_fields(fields, ["parent"]) version_docs = get_versions( project_name, version_ids=version_ids, fields=fields From 67532139b989f6e831400a12c21dab33516d3004 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Jun 2022 16:05:00 +0200 Subject: [PATCH 0660/1227] changed variable name to required_fields --- openpype/client/entities.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 1bfab5ad57..cc4032712c 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -23,7 +23,7 @@ def _get_project_connection(project_name=None): return mongodb -def _prepare_fields(fields, ensure_fields=None): +def _prepare_fields(fields, required_fields=None): if not fields: return None @@ -34,8 +34,8 @@ def _prepare_fields(fields, ensure_fields=None): if "_id" not in output: output["_id"] = True - if ensure_fields: - for key in ensure_fields: + if required_fields: + for key in required_fields: output[key] = True return output From ca926cf3102d0ddb40d17e88c117415773314278 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 5 May 2022 17:05:23 +0200 Subject: [PATCH 0661/1227] nuke: adding extract thumbnail settings --- .../defaults/project_settings/nuke.json | 3 ++ .../schemas/schema_nuke_publish.json | 28 +++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 16348bec85..6c45e2a9c1 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -166,6 +166,9 @@ }, "ExtractThumbnail": { "enabled": true, + "use_rendered": true, + "bake_viewer_process": true, + "bake_viewer_input_process": true, "nodes": { "Reformat": [ [ diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json index 04df957d67..575bfe79e7 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json @@ -135,9 +135,31 @@ "label": "Enabled" }, { - "type": "raw-json", - "key": "nodes", - "label": "Nodes" + "type": "boolean", + "key": "use_rendered", + "label": "Use rendered images" + }, + { + "type": "boolean", + "key": "bake_viewer_process", + "label": "Bake viewer process" + }, + { + "type": "boolean", + "key": "bake_viewer_input_process", + "label": "Bake viewer input process" + }, + { + "type": "collapsible-wrap", + "label": "Nodes", + "collapsible": true, + "children": [ + { + "type": "raw-json", + "key": "nodes", + "label": "Nodes" + } + ] } ] }, From e21106aa9250d5317a157adcac7b38cffab7d990 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 5 May 2022 17:06:19 +0200 Subject: [PATCH 0662/1227] nuke: adding new attributes to extract thumnail --- .../nuke/plugins/publish/extract_thumbnail.py | 96 +++++++++++-------- 1 file changed, 56 insertions(+), 40 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index ef6d486ca2..ce01f12a41 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -23,9 +23,13 @@ class ExtractThumbnail(openpype.api.Extractor): families = ["review"] hosts = ["nuke"] - # presets + # settings + use_rendered = False + bake_viewer_process = True + bake_viewer_input_process = True nodes = {} + def process(self, instance): if "render.farm" in instance.data["families"]: return @@ -53,48 +57,58 @@ class ExtractThumbnail(openpype.api.Extractor): "StagingDir `{0}`...".format(instance.data["stagingDir"])) temporary_nodes = [] - collection = instance.data.get("collection", None) - if collection: - # get path - fname = os.path.basename(collection.format( - "{head}{padding}{tail}")) - fhead = collection.format("{head}") + # try to connect already rendered images + if self.use_rendered: + collection = instance.data.get("collection", None) + self.log.debug("__ collection: `{}`".format(collection)) - # get first and last frame - first_frame = min(collection.indexes) - last_frame = max(collection.indexes) - else: - fname = os.path.basename(instance.data.get("path", None)) - fhead = os.path.splitext(fname)[0] + "." - first_frame = instance.data.get("frameStart", None) - last_frame = instance.data.get("frameEnd", None) + if collection: + # get path + fname = os.path.basename(collection.format( + "{head}{padding}{tail}")) + fhead = collection.format("{head}") - if "#" in fhead: - fhead = fhead.replace("#", "")[:-1] + # get first and last frame + first_frame = min(collection.indexes) + last_frame = max(collection.indexes) + else: + fname = os.path.basename(instance.data.get("path", None)) + fhead = os.path.splitext(fname)[0] + "." + first_frame = instance.data.get("frameStart", None) + last_frame = instance.data.get("frameEnd", None) - path_render = os.path.join(staging_dir, fname).replace("\\", "/") - # check if file exist otherwise connect to write node - if os.path.isfile(path_render): - rnode = nuke.createNode("Read") + self.log.debug("__ fhead: `{}`".format(fhead)) - rnode["file"].setValue(path_render) + if "#" in fhead: + fhead = fhead.replace("#", "")[:-1] - rnode["first"].setValue(first_frame) - rnode["origfirst"].setValue(first_frame) - rnode["last"].setValue(last_frame) - rnode["origlast"].setValue(last_frame) - temporary_nodes.append(rnode) - previous_node = rnode - else: - previous_node = node + path_render = os.path.join(staging_dir, fname).replace("\\", "/") + self.log.debug("__ path_render: `{}`".format(path_render)) - # get input process and connect it to baking - ipn = self.get_view_process_node() - if ipn is not None: - ipn.setInput(0, previous_node) - previous_node = ipn - temporary_nodes.append(ipn) + # check if file exist otherwise connect to write node + if os.path.isfile(path_render): + rnode = nuke.createNode("Read") + + rnode["file"].setValue(path_render) + + rnode["first"].setValue(first_frame) + rnode["origfirst"].setValue(first_frame) + rnode["last"].setValue(last_frame) + rnode["origlast"].setValue(last_frame) + temporary_nodes.append(rnode) + previous_node = rnode + else: + previous_node = node + + # bake viewer input look node into thumbnail image + if self.bake_viewer_input_process: + # get input process and connect it to baking + ipn = self.get_view_process_node() + if ipn is not None: + ipn.setInput(0, previous_node) + previous_node = ipn + temporary_nodes.append(ipn) reformat_node = nuke.createNode("Reformat") @@ -110,10 +124,12 @@ class ExtractThumbnail(openpype.api.Extractor): previous_node = reformat_node temporary_nodes.append(reformat_node) - dag_node = nuke.createNode("OCIODisplay") - dag_node.setInput(0, previous_node) - previous_node = dag_node - temporary_nodes.append(dag_node) + # bake viewer colorspace into thumbnail image + if self.bake_viewer_process: + dag_node = nuke.createNode("OCIODisplay") + dag_node.setInput(0, previous_node) + previous_node = dag_node + temporary_nodes.append(dag_node) # create write node write_node = nuke.createNode("Write") From 79f81b6b36bb94d077f1b3ef7e35db06e68b002f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 5 May 2022 21:09:47 +0200 Subject: [PATCH 0663/1227] nuke: refactory extract thumbnail for new settings attributes --- .../nuke/plugins/publish/extract_thumbnail.py | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index ce01f12a41..092fc07d6c 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -42,11 +42,17 @@ class ExtractThumbnail(openpype.api.Extractor): self.render_thumbnail(instance) def render_thumbnail(self, instance): + first_frame = instance.data["frameStartHandle"] + last_frame = instance.data["frameEndHandle"] + + # find frame range and define middle thumb frame + mid_frame = int((last_frame - first_frame) / 2) + node = instance[0] # group node self.log.info("Creating staging dir...") if "representations" not in instance.data: - instance.data["representations"] = list() + instance.data["representations"] = [] staging_dir = os.path.normpath( os.path.dirname(instance.data['path'])) @@ -69,21 +75,19 @@ class ExtractThumbnail(openpype.api.Extractor): "{head}{padding}{tail}")) fhead = collection.format("{head}") - # get first and last frame - first_frame = min(collection.indexes) - last_frame = max(collection.indexes) + thumb_fname = list(collection)[mid_frame] else: - fname = os.path.basename(instance.data.get("path", None)) + fname = thumb_fname = os.path.basename( + instance.data.get("path", None)) fhead = os.path.splitext(fname)[0] + "." - first_frame = instance.data.get("frameStart", None) - last_frame = instance.data.get("frameEnd", None) self.log.debug("__ fhead: `{}`".format(fhead)) if "#" in fhead: fhead = fhead.replace("#", "")[:-1] - path_render = os.path.join(staging_dir, fname).replace("\\", "/") + path_render = os.path.join( + staging_dir, thumb_fname).replace("\\", "/") self.log.debug("__ path_render: `{}`".format(path_render)) # check if file exist otherwise connect to write node @@ -92,10 +96,13 @@ class ExtractThumbnail(openpype.api.Extractor): rnode["file"].setValue(path_render) - rnode["first"].setValue(first_frame) - rnode["origfirst"].setValue(first_frame) - rnode["last"].setValue(last_frame) - rnode["origlast"].setValue(last_frame) + # turn it raw if none of baking is ON + if all([ + not self.bake_viewer_input_process, + not self.bake_viewer_process + ]): + rnode["raw"].setValue(True) + temporary_nodes.append(rnode) previous_node = rnode else: @@ -144,26 +151,18 @@ class ExtractThumbnail(openpype.api.Extractor): temporary_nodes.append(write_node) tags = ["thumbnail", "publish_on_farm"] - # retime for - mid_frame = int((int(last_frame) - int(first_frame)) / 2) \ - + int(first_frame) - first_frame = int(last_frame) / 2 - last_frame = int(last_frame) / 2 - repre = { 'name': name, 'ext': "jpg", "outputName": "thumb", 'files': file, "stagingDir": staging_dir, - "frameStart": first_frame, - "frameEnd": last_frame, "tags": tags } instance.data["representations"].append(repre) # Render frames - nuke.execute(write_node.name(), int(mid_frame), int(mid_frame)) + nuke.execute(write_node.name(), mid_frame, mid_frame) self.log.debug( "representations: {}".format(instance.data["representations"])) From 75b6acb0a4af5491e5a38a287f8ead9f88381123 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Jun 2022 18:47:58 +0200 Subject: [PATCH 0664/1227] OP-3214 - removed wrong targets on Extractor This caused missing extractors that should be running on a farm (Slate extractor for Nuke...). Explicitly set all plugins creating jobs on DL to be triggered only on local. --- openpype/api.py | 2 -- .../plugins/publish/submit_aftereffects_deadline.py | 1 + .../plugins/publish/submit_harmony_deadline.py | 1 + .../deadline/plugins/publish/submit_maya_deadline.py | 1 + .../publish/submit_maya_remote_publish_deadline.py | 3 ++- .../deadline/plugins/publish/submit_nuke_deadline.py | 1 + .../deadline/plugins/publish/submit_publish_job.py | 1 + openpype/plugin.py | 12 ------------ 8 files changed, 7 insertions(+), 15 deletions(-) diff --git a/openpype/api.py b/openpype/api.py index e049a683c6..9ce745b653 100644 --- a/openpype/api.py +++ b/openpype/api.py @@ -44,7 +44,6 @@ from . import resources from .plugin import ( Extractor, - Integrator, ValidatePipelineOrder, ValidateContentsOrder, @@ -87,7 +86,6 @@ __all__ = [ # plugin classes "Extractor", - "Integrator", # ordering "ValidatePipelineOrder", "ValidateContentsOrder", diff --git a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py index b6584f239e..de8df3dd9e 100644 --- a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py @@ -33,6 +33,7 @@ class AfterEffectsSubmitDeadline( hosts = ["aftereffects"] families = ["render.farm"] # cannot be "render' as that is integrated use_published = True + targets = ["local"] priority = 50 chunk_size = 1000000 diff --git a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py index 912f0f4026..2cf502224f 100644 --- a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py @@ -238,6 +238,7 @@ class HarmonySubmitDeadline( order = pyblish.api.IntegratorOrder + 0.1 hosts = ["harmony"] families = ["render.farm"] + targets = ["local"] optional = True use_published = False diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 8562c85f7d..9964e3c646 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -287,6 +287,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder + 0.1 hosts = ["maya"] families = ["renderlayer"] + targets = ["local"] use_published = True tile_assembler_plugin = "OpenPypeTileAssembler" diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py index 4f82818d6d..57572fcb24 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py @@ -10,7 +10,7 @@ import openpype.api import pyblish.api -class MayaSubmitRemotePublishDeadline(openpype.api.Integrator): +class MayaSubmitRemotePublishDeadline(pyblish.api.InstancePlugin): """Submit Maya scene to perform a local publish in Deadline. Publishing in Deadline can be helpful for scenes that publish very slow. @@ -31,6 +31,7 @@ class MayaSubmitRemotePublishDeadline(openpype.api.Integrator): order = pyblish.api.IntegratorOrder hosts = ["maya"] families = ["publish.farm"] + targets = ["local"] def process(self, instance): settings = get_project_settings(os.getenv("AVALON_PROJECT")) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 94c703d66d..ca68c87f9a 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -23,6 +23,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): hosts = ["nuke", "nukestudio"] families = ["render.farm", "prerender.farm"] optional = True + targets = ["local"] # presets priority = 50 diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 0583c25b57..860d9fd01b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -103,6 +103,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder + 0.2 icon = "tractor" deadline_plugin = "OpenPype" + targets = ["local"] hosts = ["fusion", "maya", "nuke", "celaction", "aftereffects", "harmony"] diff --git a/openpype/plugin.py b/openpype/plugin.py index 6637ad1d8b..bb9bc2ff85 100644 --- a/openpype/plugin.py +++ b/openpype/plugin.py @@ -18,16 +18,6 @@ class InstancePlugin(pyblish.api.InstancePlugin): super(InstancePlugin, cls).process(cls, *args, **kwargs) -class Integrator(InstancePlugin): - """Integrator base class. - - Wraps pyblish instance plugin. Targets set to "local" which means all - integrators should run on "local" publishes, by default. - "remote" targets could be used for integrators that should run externally. - """ - targets = ["local"] - - class Extractor(InstancePlugin): """Extractor base class. @@ -38,8 +28,6 @@ class Extractor(InstancePlugin): """ - targets = ["local"] - order = 2.0 def staging_dir(self, instance): From db1316dd688dc2bba62395aa968ab8af7b5ce552 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Wed, 15 Jun 2022 20:56:33 +0200 Subject: [PATCH 0665/1227] Did this the easy way and let's go! --- openpype/modules/ftrack/ftrack_module.py | 3 -- openpype/modules/ftrack/tray/ftrack_tray.py | 42 +------------------ .../defaults/system_settings/modules.json | 1 - .../module_settings/schema_ftrack.json | 5 --- 4 files changed, 1 insertion(+), 50 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 048e5ebfb1..f99e189082 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -42,9 +42,6 @@ class FtrackModule( self.ftrack_url = ftrack_url - ftrack_open_as_app = ftrack_settings["ftrack_open_as_app"] - self.ftrack_open_as_app = ftrack_open_as_app - current_dir = os.path.dirname(os.path.abspath(__file__)) low_platform = platform.system().lower() diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index e822fd4639..2919ae22fb 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -2,10 +2,6 @@ import os import time import datetime import threading -import platform -import posixpath -import ntpath -import webbrowser from Qt import QtCore, QtWidgets, QtGui @@ -54,43 +50,7 @@ class FtrackTrayWrapper: self.widget_login.raise_() def show_ftrack_browser(self): - env_pf64 = os.environ['ProgramW6432'].replace( - ntpath.sep, posixpath.sep) - env_pf32 = os.environ['ProgramFiles(x86)'].replace( - ntpath.sep, posixpath.sep) - env_loc = os.environ['LocalAppData'].replace( - ntpath.sep, posixpath.sep) - chromium_paths_win = [ - f"{env_pf64}/Google/Chrome/Application/chrome.exe", - f"{env_pf32}/Google/Chrome/Application/chrome.exe", - f"{env_loc}/Google/Chrome/Application/chrome.exe", - f"{env_pf32}/Microsoft/Edge/Application/msedge.exe" - ] - cur_os = cur_os = platform.system().lower() - if cur_os == "windows": - is_chromium = False - for p in chromium_paths_win: - if os.path.exists(p): - is_chromium = True - chromium_path = p - break - if is_chromium and self.module.ftrack_open_as_app: - webbrowser.get(f"{chromium_path} %s").open_new( - f"--app={self.module.ftrack_url}") - else: - webbrowser.get(using="windows-default").open_new( - self.module.ftrack_url) - - else: - if self.module.ftrack_open_as_app: - try: - webbrowser.get(using='chrome').open_new( - f"--app={self.module.ftrack_url}") - except webbrowser.Error: - webbrowser.open_new(self.module.ftrack_url) - else: - webbrowser.open_new(self.module.ftrack_url) - return + QtGui.QDesktopServices.openUrl(self.module.ftrack_url) def validate(self): validation = False diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 6d09652bb9..537e287366 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -15,7 +15,6 @@ "ftrack": { "enabled": false, "ftrack_server": "", - "ftrack_open_as_app": false, "ftrack_actions_path": { "windows": [], "darwin": [], diff --git a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json index 570d856cf8..654ddf2938 100644 --- a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json +++ b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json @@ -16,11 +16,6 @@ "key": "ftrack_server", "label": "Server" }, - { - "type": "boolean", - "key": "ftrack_open_as_app", - "label": "Open in app mode" - }, { "type": "splitter" }, From f87f461578b4eb2308aaff68d5576ff1743156cf Mon Sep 17 00:00:00 2001 From: pberto Date: Thu, 16 Jun 2022 15:07:01 +0900 Subject: [PATCH 0666/1227] docs: updated / simplified docs in light of upcoming changes --- website/docs/artist_hosts_maya_multiverse.md | 21 +++++++++++++++++- website/docs/assets/maya-multiverse_setup.png | Bin 164855 -> 458197 bytes 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/website/docs/artist_hosts_maya_multiverse.md b/website/docs/artist_hosts_maya_multiverse.md index e6520bafa0..a173e79125 100644 --- a/website/docs/artist_hosts_maya_multiverse.md +++ b/website/docs/artist_hosts_maya_multiverse.md @@ -65,6 +65,25 @@ the one depicted here: ![Maya - Multiverse Setup](assets/maya-multiverse_setup.png) + +``` +{ + "MULTIVERSE_PATH": "/Path/to/Multiverse-{MULTIVERSE_VERSION}", + "MAYA_MODULE_PATH": "{MULTIVERSE}/Maya;{MAYA_MODULE_PATH}" +} + +{ + "MULTIVERSE_VERSION": "7.1.0-py27" +} + +``` + +The Multiverse Maya module file (.mod) pointed above contains all the necessary +environment variables to run Multiverse. + +The OpenPype settings will contain blocks to enable/disable the Multiverse +Creators and Loader, along with sensible studio setting. + For more information about setup of Multiverse please refer to the relative page on the [Multiverse official documentation](https://multi-verse.io/docs). @@ -94,7 +113,7 @@ You can choose the USD file format in the Creators' set nodes: - Assets: `.usd` (default) or `.usda` or `.usdz` - Compositions: `.usda` (default) or `.usd` - Overrides: `.usda` (default) or `.usd` -- Looks: `.ma` +- Looks: `.ma` (default) ![Maya - Multiverse Asset Creator](assets/maya-multiverse_openpype_asset_creator.png) diff --git a/website/docs/assets/maya-multiverse_setup.png b/website/docs/assets/maya-multiverse_setup.png index 8aa89ef7e504272e7b5f9abbfea0ec0ca3d1d783..72bdb0d3798f5e7234812ae3d8d8e70e2dac1bdf 100644 GIT binary patch literal 458197 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

HTBs}V9(6pl^D4!Z% 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�I+5`6?ezxjcdNB~pt+w%?zc5fPz%bdYAu~y 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�i2|0OrHG^$E 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%0DG^)#N*7t@wNCzvF#Dix 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%0Am_x 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

4B`UV(w{DUt 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 literal 164855 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

?;P|97R-)#?HUS+R~+heNwi? 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

=26e?*z3RuFR6Ga0 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 CH}}2( From d5bd1a17eb85846cfc13ef22729523ee0670f101 Mon Sep 17 00:00:00 2001 From: DMO Date: Thu, 16 Jun 2022 16:36:17 +0900 Subject: [PATCH 0667/1227] Setting defaults of CreateMultiverseUsd to reasonable values. Creating a schema entry for multiverse creators and setting defaults. --- .../plugins/create/create_multiverse_usd.py | 6 ++-- .../defaults/project_settings/deadline.json | 2 +- .../defaults/project_settings/maya.json | 16 +++++++++- .../schemas/schema_maya_create.json | 31 +++++++------------ 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py index adf0acc7c2..5290d5143f 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py @@ -37,15 +37,15 @@ class CreateMultiverseUsd(plugin.Creator): self.data["writeUVs"] = True self.data["writeColorSets"] = False self.data["writeTangents"] = False - self.data["writeRefPositions"] = False + self.data["writeRefPositions"] = True self.data["writeBlendShapes"] = False - self.data["writeDisplayColor"] = False + self.data["writeDisplayColor"] = True self.data["writeSkinWeights"] = False self.data["writeMaterialAssignment"] = False self.data["writeHardwareShader"] = False self.data["writeShadingNetworks"] = False self.data["writeTransformMatrix"] = True - self.data["writeUsdAttributes"] = False + self.data["writeUsdAttributes"] = True self.data["writeInstancesAsReferences"] = False self.data["timeVaryingTopology"] = False self.data["customMaterialNamespace"] = '' diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index f0b2a7e555..5c5a14bf21 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -98,4 +98,4 @@ } } } -} +} \ No newline at end of file diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index df54e44c56..7e404188cf 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -71,7 +71,21 @@ }, "CreateMultiverseUsd": { "enabled": true, - "strip_namespaces": true + "defaults": [ + "Main" + ] + }, + "CreateMultiverseUsdComp": { + "enabled": true, + "defaults": [ + "Main" + ] + }, + "CreateMultiverseUsdOver": { + "enabled": true, + "defaults": [ + "Main" + ] }, "CreateAnimation": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json index 4dd54c81a0..09287a8b50 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json @@ -143,29 +143,22 @@ } ] }, - { - "type": "dict", - "collapsible": true, - "key": "CreateMultiverseUsd", - "label": "Create Multiverse USD", - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "strip_namespaces", - "label": "Strip Namespaces" - } - ] - }, { "type": "schema_template", "name": "template_create_plugin", "template_data": [ + { + "key": "CreateMultiverseUsd", + "label": "Create Multiverse USD" + }, + { + "key": "CreateMultiverseUsdComp", + "label": "Create Multiverse USD Composition" + }, + { + "key": "CreateMultiverseUsdOver", + "label": "Create Multiverse USD Override" + }, { "key": "CreateAnimation", "label": "Create Animation" From eb3e0d626565697ff3fa7029cca9a4e318a61a8d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 11:35:57 +0200 Subject: [PATCH 0668/1227] add new representation only if was created --- openpype/plugins/publish/extract_jpeg_exr.py | 22 +++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 3017656621..de01ebd2de 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -92,17 +92,19 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): return new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) + # adding representation + self.log.debug( + "Adding thumbnail representation: {}".format(new_repre) + ) + instance.data["representations"].append(new_repre) def _get_filtered_repres(self, instance): filtered_repres = [] From 5bdbebf4cc163d182d599561967088382ba0adc7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 11:36:03 +0200 Subject: [PATCH 0669/1227] create only one thumbnail --- openpype/plugins/publish/extract_jpeg_exr.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index de01ebd2de..a467728e77 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -105,6 +105,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "Adding thumbnail representation: {}".format(new_repre) ) instance.data["representations"].append(new_repre) + # There is no need to create more then one thumbnail + break def _get_filtered_repres(self, instance): filtered_repres = [] From b722062928b081f9d7a7e51ec37d7118834459c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 16 Jun 2022 12:35:06 +0200 Subject: [PATCH 0670/1227] Fix: Kitsu module first synchronization Add a `None` to `pop(...)` to avoid process abortion at early stage of production --- openpype/modules/kitsu/utils/update_op_with_zou.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 673a195747..08e50d959b 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -85,7 +85,7 @@ def update_op_assets( # Frame in, fallback on 0 frame_in = int(item_data.get("frame_in") or 0) item_data["frameStart"] = frame_in - item_data.pop("frame_in") + item_data.pop("frame_in", None) # Frame out, fallback on frame_in + duration frames_duration = int(item.get("nb_frames") or 1) frame_out = ( @@ -94,7 +94,7 @@ def update_op_assets( else frame_in + frames_duration ) item_data["frameEnd"] = int(frame_out) - item_data.pop("frame_out") + item_data.pop("frame_out", None) # Fps, fallback to project's value when entity fps is deleted if not item_data.get("fps") and item_doc["data"].get("fps"): item_data["fps"] = project_doc["data"]["fps"] From edf13e877f7416c2a64e1a123003bdfffce10d64 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 14:45:06 +0200 Subject: [PATCH 0671/1227] removed requirement of pypeclub role in default settings --- .../defaults/system_settings/modules.json | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 537e287366..8cd4114cb0 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -59,13 +59,11 @@ "applications": { "write_security_roles": [ "API", - "Administrator", - "Pypeclub" + "Administrator" ], "read_security_roles": [ "API", - "Administrator", - "Pypeclub" + "Administrator" ] } }, @@ -73,25 +71,21 @@ "tools_env": { "write_security_roles": [ "API", - "Administrator", - "Pypeclub" + "Administrator" ], "read_security_roles": [ "API", - "Administrator", - "Pypeclub" + "Administrator" ] }, "avalon_mongo_id": { "write_security_roles": [ "API", - "Administrator", - "Pypeclub" + "Administrator" ], "read_security_roles": [ "API", - "Administrator", - "Pypeclub" + "Administrator" ] }, "fps": { From 8857423828377a319fd80292f9b0ca0e49e5b2ad Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 14:49:52 +0200 Subject: [PATCH 0672/1227] Changed label of custom attribute action --- .../ftrack/event_handlers_user/action_create_cust_attrs.py | 4 ++-- website/docs/module_ftrack.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py b/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py index 88dc8213bd..d04440a564 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py +++ b/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py @@ -140,9 +140,9 @@ class CustomAttributes(BaseAction): identifier = 'create.update.attributes' #: Action label. label = "OpenPype Admin" - variant = '- Create/Update Avalon Attributes' + variant = '- Create/Update Custom Attributes' #: Action description. - description = 'Creates Avalon/Mongo ID for double check' + description = 'Creates required custom attributes in ftrack' icon = statics_icon("ftrack", "action_icons", "OpenPypeAdmin.svg") settings_key = "create_update_attributes" diff --git a/website/docs/module_ftrack.md b/website/docs/module_ftrack.md index fd9687ed9d..667782754f 100644 --- a/website/docs/module_ftrack.md +++ b/website/docs/module_ftrack.md @@ -26,7 +26,7 @@ You can only use our Ftrack Actions and publish to Ftrack if each artist is logg ### Custom Attributes After successfully connecting OpenPype with you Ftrack, you can right click on any project in Ftrack and you should see a bunch of actions available. The most important one is called `OpenPype Admin` and contains multiple options inside. -To prepare Ftrack for working with OpenPype you'll need to run [OpenPype Admin - Create/Update Avalon Attributes](manager_ftrack_actions.md#create-update-avalon-attributes), which creates and sets the Custom Attributes necessary for OpenPype to function. +To prepare Ftrack for working with OpenPype you'll need to run [OpenPype Admin - Create/Update Custom Attributes](manager_ftrack_actions.md#create-update-avalon-attributes), which creates and sets the Custom Attributes necessary for OpenPype to function. From bcb52309517350de2ad0aa91f28ab317dfffdbce Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 16 Jun 2022 15:05:46 +0200 Subject: [PATCH 0673/1227] Fix - add default target for New Publisher --- openpype/pipeline/create/context.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 2f1922c103..7931ea400a 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -829,9 +829,10 @@ class CreateContext: discover_result = publish_plugins_discover() publish_plugins = discover_result.plugins - targets = pyblish.logic.registered_targets() or ["default"] + targets = set(pyblish.logic.registered_targets()) + targets.add("default") plugins_by_targets = pyblish.logic.plugins_by_targets( - publish_plugins, targets + publish_plugins, list(targets) ) # Collect plugins that can have attribute definitions for plugin in publish_plugins: From 98646b9c0ad95aed664c6b5e10fe266cdba9040b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 16 Jun 2022 15:32:05 +0200 Subject: [PATCH 0674/1227] standalone: adding rename shot switch and hierarchy switch --- .../plugins/publish/collect_hierarchy.py | 19 ++++++++++++------- .../project_settings/standalonepublisher.json | 6 ++++-- .../schema_project_standalonepublisher.json | 15 +++++++++++++-- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py index 77163651c4..2452f77e56 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py @@ -1,4 +1,5 @@ import os +from pprint import pformat import re from copy import deepcopy import pyblish.api @@ -21,6 +22,7 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): families = ["shot"] # presets + shot_rename = True shot_rename_template = None shot_rename_search_patterns = None shot_add_hierarchy = None @@ -46,7 +48,7 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): parent_name = instance.context.data["assetEntity"]["name"] clip = instance.data["item"] clip_name = os.path.splitext(clip.name)[0].lower() - if self.shot_rename_search_patterns: + if self.shot_rename_search_patterns and self.shot_rename: search_text += parent_name + clip_name instance.data["anatomyData"].update({"clip_name": clip_name}) for type, pattern in self.shot_rename_search_patterns.items(): @@ -56,9 +58,9 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): continue instance.data["anatomyData"][type] = match[-1] - # format to new shot name - instance.data["asset"] = self.shot_rename_template.format( - **instance.data["anatomyData"]) + # format to new shot name + instance.data["asset"] = self.shot_rename_template.format( + **instance.data["anatomyData"]) def create_hierarchy(self, instance): parents = list() @@ -82,7 +84,7 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): "entity_name": entity["name"] }) - if self.shot_add_hierarchy: + if self.shot_add_hierarchy.get("enabled"): parent_template_patern = re.compile(r"\{([a-z]*?)\}") # fill the parents parts from presets shot_add_hierarchy = self.shot_add_hierarchy.copy() @@ -126,8 +128,8 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): instance.data["parents"] = parents # print - self.log.debug(f"Hierarchy: {hierarchy}") - self.log.debug(f"parents: {parents}") + self.log.warning(f"Hierarchy: {hierarchy}") + self.log.info(f"parents: {parents}") if self.shot_add_tasks: tasks_to_add = dict() @@ -161,6 +163,9 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): }) def process(self, context): + self.log.info("self.shot_add_hierarchy: {}".format( + pformat(self.shot_add_hierarchy) + )) for instance in context: if instance.data["family"] in self.families: self.processing_instance(instance) diff --git a/openpype/settings/defaults/project_settings/standalonepublisher.json b/openpype/settings/defaults/project_settings/standalonepublisher.json index e36232d3f7..b6e2e056a1 100644 --- a/openpype/settings/defaults/project_settings/standalonepublisher.json +++ b/openpype/settings/defaults/project_settings/standalonepublisher.json @@ -257,12 +257,14 @@ ] }, "CollectHierarchyInstance": { + "shot_rename": true, "shot_rename_template": "{project[code]}_{_sequence_}_{_shot_}", "shot_rename_search_patterns": { - "_sequence_": "(\\d{4})(?=_\\d{4})", - "_shot_": "(\\d{4})(?!_\\d{4})" + "_sequence_": "(sc\\d{3})", + "_shot_": "(sh\\d{3})" }, "shot_add_hierarchy": { + "enabled": true, "parents_path": "{project}/{folder}/{sequence}", "parents": { "project": "{project[name]}", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_standalonepublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_standalonepublisher.json index 37fcaac69f..ae25007683 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_standalonepublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_standalonepublisher.json @@ -271,6 +271,11 @@ "label": "Collect Instance Hierarchy", "is_group": true, "children": [ + { + "type": "boolean", + "key": "shot_rename", + "label": "Shot Rename" + }, { "type": "text", "key": "shot_rename_template", @@ -289,7 +294,13 @@ "type": "dict", "key": "shot_add_hierarchy", "label": "Shot hierarchy", + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "text", "key": "parents_path", @@ -343,8 +354,8 @@ "type": "number", "key": "timeline_frame_start", "label": "Timeline start frame", - "default": 900000, - "minimum": 1, + "default": 90000, + "minimum": 0, "maximum": 10000000 }, { From 2da7261abff69322970ee6daafcc0068ce04216d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 16 Jun 2022 15:32:37 +0200 Subject: [PATCH 0675/1227] standalone: ensure extension with dot at start --- .../plugins/publish/extract_trim_video_audio.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py b/openpype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py index f327895b83..51dc84e9a2 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py @@ -39,11 +39,14 @@ class ExtractTrimVideoAudio(openpype.api.Extractor): # Generate mov file. fps = instance.data["fps"] video_file_path = instance.data["editorialSourcePath"] - extensions = instance.data.get("extensions", [".mov"]) + extensions = instance.data.get("extensions", ["mov"]) for ext in extensions: self.log.info("Processing ext: `{}`".format(ext)) + if not ext.startswith("."): + ext = "." + ext + clip_trimed_path = os.path.join( staging_dir, instance.data["name"] + ext) # # check video file metadata From 84134acb7c4291d1bf864e34f68a5a47f3da513c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 15:35:03 +0200 Subject: [PATCH 0676/1227] copied editorial to openpype.pipeline --- openpype/pipeline/editorial.py | 282 +++++++++++++++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 openpype/pipeline/editorial.py diff --git a/openpype/pipeline/editorial.py b/openpype/pipeline/editorial.py new file mode 100644 index 0000000000..f62a1842e0 --- /dev/null +++ b/openpype/pipeline/editorial.py @@ -0,0 +1,282 @@ +import os +import re +import clique + +import opentimelineio as otio +from opentimelineio import opentime as _ot + + +def otio_range_to_frame_range(otio_range): + start = _ot.to_frames( + otio_range.start_time, otio_range.start_time.rate) + end = start + _ot.to_frames( + otio_range.duration, otio_range.duration.rate) + return start, end + + +def otio_range_with_handles(otio_range, instance): + handle_start = instance.data["handleStart"] + handle_end = instance.data["handleEnd"] + handles_duration = handle_start + handle_end + fps = float(otio_range.start_time.rate) + start = _ot.to_frames(otio_range.start_time, fps) + duration = _ot.to_frames(otio_range.duration, fps) + + return _ot.TimeRange( + start_time=_ot.RationalTime((start - handle_start), fps), + duration=_ot.RationalTime((duration + handles_duration), fps) + ) + + +def is_overlapping_otio_ranges(test_otio_range, main_otio_range, strict=False): + test_start, test_end = otio_range_to_frame_range(test_otio_range) + main_start, main_end = otio_range_to_frame_range(main_otio_range) + covering_exp = bool( + (test_start <= main_start) and (test_end >= main_end) + ) + inside_exp = bool( + (test_start >= main_start) and (test_end <= main_end) + ) + overlaying_right_exp = bool( + (test_start <= main_end) and (test_end >= main_end) + ) + overlaying_left_exp = bool( + (test_end >= main_start) and (test_start <= main_start) + ) + + if not strict: + return any(( + covering_exp, + inside_exp, + overlaying_right_exp, + overlaying_left_exp + )) + else: + return covering_exp + + +def convert_to_padded_path(path, padding): + """ + Return correct padding in sequence string + + Args: + path (str): path url or simple file name + padding (int): number of padding + + Returns: + type: string with reformated path + + Example: + convert_to_padded_path("plate.%d.exr") > plate.%04d.exr + + """ + if "%d" in path: + path = re.sub("%d", "%0{padding}d".format(padding=padding), path) + return path + + +def trim_media_range(media_range, source_range): + """ + Trim input media range with clip source range. + + Args: + media_range (otio._ot._ot.TimeRange): available range of media + source_range (otio._ot._ot.TimeRange): clip required range + + Returns: + otio._ot._ot.TimeRange: trimmed media range + + """ + rw_media_start = _ot.RationalTime( + media_range.start_time.value + source_range.start_time.value, + media_range.start_time.rate + ) + rw_media_duration = _ot.RationalTime( + source_range.duration.value, + media_range.duration.rate + ) + return _ot.TimeRange( + rw_media_start, rw_media_duration) + + +def range_from_frames(start, duration, fps): + """ + Returns otio time range. + + Args: + start (int): frame start + duration (int): frame duration + fps (float): frame range + + Returns: + otio._ot._ot.TimeRange: created range + + """ + return _ot.TimeRange( + _ot.RationalTime(start, fps), + _ot.RationalTime(duration, fps) + ) + + +def frames_to_seconds(frames, framerate): + """ + Returning seconds. + + Args: + frames (int): frame + framerate (float): frame rate + + Returns: + float: second value + """ + + rt = _ot.from_frames(frames, framerate) + return _ot.to_seconds(rt) + + +def frames_to_timecode(frames, framerate): + rt = _ot.from_frames(frames, framerate) + return _ot.to_timecode(rt) + + +def make_sequence_collection(path, otio_range, metadata): + """ + Make collection from path otio range and otio metadata. + + Args: + path (str): path to image sequence with `%d` + otio_range (otio._ot._ot.TimeRange): range to be used + metadata (dict): data where padding value can be found + + Returns: + list: dir_path (str): path to sequence, collection object + + """ + if "%" not in path: + return None + file_name = os.path.basename(path) + dir_path = os.path.dirname(path) + head = file_name.split("%")[0] + tail = os.path.splitext(file_name)[-1] + first, last = otio_range_to_frame_range(otio_range) + collection = clique.Collection( + head=head, tail=tail, padding=metadata["padding"]) + collection.indexes.update([i for i in range(first, last)]) + return dir_path, collection + + +def _sequence_resize(source, length): + step = float(len(source) - 1) / (length - 1) + for i in range(length): + low, ratio = divmod(i * step, 1) + high = low + 1 if ratio > 0 else low + yield (1 - ratio) * source[int(low)] + ratio * source[int(high)] + + +def get_media_range_with_retimes(otio_clip, handle_start, handle_end): + source_range = otio_clip.source_range + available_range = otio_clip.available_range() + media_in = available_range.start_time.value + media_out = available_range.end_time_inclusive().value + + # modifiers + time_scalar = 1. + offset_in = 0 + offset_out = 0 + time_warp_nodes = [] + + # Check for speed effects and adjust playback speed accordingly + for effect in otio_clip.effects: + if isinstance(effect, otio.schema.LinearTimeWarp): + time_scalar = effect.time_scalar + + elif isinstance(effect, otio.schema.FreezeFrame): + # For freeze frame, playback speed must be set after range + time_scalar = 0. + + elif isinstance(effect, otio.schema.TimeEffect): + # For freeze frame, playback speed must be set after range + name = effect.name + effect_name = effect.effect_name + if "TimeWarp" not in effect_name: + continue + metadata = effect.metadata + lookup = metadata.get("lookup") + if not lookup: + continue + + # time warp node + tw_node = { + "Class": "TimeWarp", + "name": name + } + tw_node.update(metadata) + tw_node["lookup"] = list(lookup) + + # get first and last frame offsets + offset_in += lookup[0] + offset_out += lookup[-1] + + # add to timewarp nodes + time_warp_nodes.append(tw_node) + + # multiply by time scalar + offset_in *= time_scalar + offset_out *= time_scalar + + # filip offset if reversed speed + if time_scalar < 0: + _offset_in = offset_out + _offset_out = offset_in + offset_in = _offset_in + offset_out = _offset_out + + # scale handles + handle_start *= abs(time_scalar) + handle_end *= abs(time_scalar) + + # filip handles if reversed speed + if time_scalar < 0: + _handle_start = handle_end + _handle_end = handle_start + handle_start = _handle_start + handle_end = _handle_end + + source_in = source_range.start_time.value + + media_in_trimmed = ( + media_in + source_in + offset_in) + media_out_trimmed = ( + media_in + source_in + ( + ((source_range.duration.value - 1) * abs( + time_scalar)) + offset_out)) + + # calculate available handles + if (media_in_trimmed - media_in) < handle_start: + handle_start = (media_in_trimmed - media_in) + if (media_out - media_out_trimmed) < handle_end: + handle_end = (media_out - media_out_trimmed) + + # create version data + version_data = { + "versionData": { + "retime": True, + "speed": time_scalar, + "timewarps": time_warp_nodes, + "handleStart": round(handle_start), + "handleEnd": round(handle_end) + } + } + + returning_dict = { + "mediaIn": media_in_trimmed, + "mediaOut": media_out_trimmed, + "handleStart": round(handle_start), + "handleEnd": round(handle_end) + } + + # add version data only if retime + if time_warp_nodes or time_scalar != 1.: + returning_dict.update(version_data) + + return returning_dict From 228c84af83d111918b5366be2625d1a869584058 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 15:36:17 +0200 Subject: [PATCH 0677/1227] import editorial functions from openpype.pipeline.editorial --- .../publish/collect_timeline_instances.py | 9 +++++--- .../plugins/publish/precollect_instances.py | 6 ++--- openpype/hosts/resolve/api/lib.py | 4 ++-- .../publish/collect_otio_frame_ranges.py | 11 ++++++---- .../publish/collect_otio_subset_resources.py | 12 ++++++---- .../plugins/publish/extract_otio_review.py | 22 ++++++++++++------- .../publish/extract_otio_trimming_video.py | 5 +++-- 7 files changed, 43 insertions(+), 26 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py index 0aca7c38d5..8c2d172732 100644 --- a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py @@ -2,7 +2,10 @@ import re import pyblish import openpype.hosts.flame.api as opfapi from openpype.hosts.flame.otio import flame_export -import openpype.lib as oplib +from openpype.pipeline.editorial import ( + is_overlapping_otio_ranges, + get_media_range_with_retimes +) # # developer reload modules from pprint import pformat @@ -271,7 +274,7 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): # HACK: it is here to serve for versions bellow 2021.1 if not any([head, tail]): - retimed_attributes = oplib.get_media_range_with_retimes( + retimed_attributes = get_media_range_with_retimes( otio_clip, handle_start, handle_end) self.log.debug( ">> retimed_attributes: {}".format(retimed_attributes)) @@ -370,7 +373,7 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): continue if otio_clip.name not in segment.name.get_value(): continue - if oplib.is_overlapping_otio_ranges( + if is_overlapping_otio_ranges( parent_range, timeline_range, strict=True): # add pypedata marker to otio_clip metadata diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index b891a37d9d..2d0ec6fc99 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -1,5 +1,5 @@ import pyblish -import openpype +from openpype.pipeline.editorial import is_overlapping_otio_ranges from openpype.hosts.hiero import api as phiero from openpype.hosts.hiero.api.otio import hiero_export import hiero @@ -275,7 +275,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin): parent_range = otio_audio.range_in_parent() # if any overaling clip found then return True - if openpype.lib.is_overlapping_otio_ranges( + if is_overlapping_otio_ranges( parent_range, timeline_range, strict=False): return True @@ -304,7 +304,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin): continue self.log.debug("__ parent_range: {}".format(parent_range)) self.log.debug("__ timeline_range: {}".format(timeline_range)) - if openpype.lib.is_overlapping_otio_ranges( + if is_overlapping_otio_ranges( parent_range, timeline_range, strict=True): # add pypedata marker to otio_clip metadata diff --git a/openpype/hosts/resolve/api/lib.py b/openpype/hosts/resolve/api/lib.py index 22f83c6eed..c4717bd370 100644 --- a/openpype/hosts/resolve/api/lib.py +++ b/openpype/hosts/resolve/api/lib.py @@ -4,7 +4,7 @@ import re import os import contextlib from opentimelineio import opentime -import openpype +from openpype.pipeline.editorial import is_overlapping_otio_ranges from ..otio import davinci_export as otio_export @@ -824,7 +824,7 @@ def get_otio_clip_instance_data(otio_timeline, timeline_item_data): continue if otio_clip.name not in timeline_item.GetName(): continue - if openpype.lib.is_overlapping_otio_ranges( + if is_overlapping_otio_ranges( parent_range, timeline_range, strict=True): # add pypedata marker to otio_clip metadata diff --git a/openpype/plugins/publish/collect_otio_frame_ranges.py b/openpype/plugins/publish/collect_otio_frame_ranges.py index 8eaf9d6f29..c86e777850 100644 --- a/openpype/plugins/publish/collect_otio_frame_ranges.py +++ b/openpype/plugins/publish/collect_otio_frame_ranges.py @@ -8,8 +8,11 @@ Requires: # import os import opentimelineio as otio import pyblish.api -import openpype.lib from pprint import pformat +from openpype.pipeline.editorial import ( + otio_range_to_frame_range, + otio_range_with_handles +) class CollectOtioFrameRanges(pyblish.api.InstancePlugin): @@ -31,9 +34,9 @@ class CollectOtioFrameRanges(pyblish.api.InstancePlugin): otio_tl_range = otio_clip.range_in_parent() otio_src_range = otio_clip.source_range otio_avalable_range = otio_clip.available_range() - otio_tl_range_handles = openpype.lib.otio_range_with_handles( + otio_tl_range_handles = otio_range_with_handles( otio_tl_range, instance) - otio_src_range_handles = openpype.lib.otio_range_with_handles( + otio_src_range_handles = otio_range_with_handles( otio_src_range, instance) # get source avalable start frame @@ -42,7 +45,7 @@ class CollectOtioFrameRanges(pyblish.api.InstancePlugin): otio_avalable_range.start_time.rate) # convert to frames - range_convert = openpype.lib.otio_range_to_frame_range + range_convert = otio_range_to_frame_range tl_start, tl_end = range_convert(otio_tl_range) tl_start_h, tl_end_h = range_convert(otio_tl_range_handles) src_start, src_end = range_convert(otio_src_range) diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index 40d4f35bdc..fc6a9b50f2 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -10,7 +10,11 @@ import os import clique import opentimelineio as otio import pyblish.api -import openpype.lib as oplib +from openpype.pipeline.editorial import ( + get_media_range_with_retimes, + range_from_frames, + make_sequence_collection +) class CollectOtioSubsetResources(pyblish.api.InstancePlugin): @@ -42,7 +46,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): available_duration = otio_avalable_range.duration.value # get available range trimmed with processed retimes - retimed_attributes = oplib.get_media_range_with_retimes( + retimed_attributes = get_media_range_with_retimes( otio_clip, handle_start, handle_end) self.log.debug( ">> retimed_attributes: {}".format(retimed_attributes)) @@ -64,7 +68,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): a_frame_end_h = media_out + handle_end # create trimmed otio time range - trimmed_media_range_h = oplib.range_from_frames( + trimmed_media_range_h = range_from_frames( a_frame_start_h, (a_frame_end_h - a_frame_start_h) + 1, media_fps ) @@ -144,7 +148,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): # in case it is file sequence but not new OTIO schema # `ImageSequenceReference` path = media_ref.target_url - collection_data = oplib.make_sequence_collection( + collection_data = make_sequence_collection( path, trimmed_media_range_h, metadata) self.staging_dir, collection = collection_data diff --git a/openpype/plugins/publish/extract_otio_review.py b/openpype/plugins/publish/extract_otio_review.py index 35adc97442..2ce5323468 100644 --- a/openpype/plugins/publish/extract_otio_review.py +++ b/openpype/plugins/publish/extract_otio_review.py @@ -19,6 +19,13 @@ import clique import opentimelineio as otio from pyblish import api import openpype +from openpype.pipeline.editorial import ( + otio_range_to_frame_range, + trim_media_range, + range_from_frames, + frames_to_seconds, + make_sequence_collection +) class ExtractOTIOReview(openpype.api.Extractor): @@ -161,7 +168,7 @@ class ExtractOTIOReview(openpype.api.Extractor): dirname = media_ref.target_url_base head = media_ref.name_prefix tail = media_ref.name_suffix - first, last = openpype.lib.otio_range_to_frame_range( + first, last = otio_range_to_frame_range( available_range) collection = clique.Collection( head=head, @@ -180,7 +187,7 @@ class ExtractOTIOReview(openpype.api.Extractor): # in case it is file sequence but not new OTIO schema # `ImageSequenceReference` path = media_ref.target_url - collection_data = openpype.lib.make_sequence_collection( + collection_data = make_sequence_collection( path, available_range, metadata) dir_path, collection = collection_data @@ -305,8 +312,8 @@ class ExtractOTIOReview(openpype.api.Extractor): duration = avl_durtation # return correct trimmed range - return openpype.lib.trim_media_range( - avl_range, openpype.lib.range_from_frames(start, duration, fps) + return trim_media_range( + avl_range, range_from_frames(start, duration, fps) ) def _render_seqment(self, sequence=None, @@ -357,8 +364,8 @@ class ExtractOTIOReview(openpype.api.Extractor): frame_start = otio_range.start_time.value input_fps = otio_range.start_time.rate frame_duration = otio_range.duration.value - sec_start = openpype.lib.frames_to_secons(frame_start, input_fps) - sec_duration = openpype.lib.frames_to_secons( + sec_start = frames_to_seconds(frame_start, input_fps) + sec_duration = frames_to_seconds( frame_duration, input_fps ) @@ -370,8 +377,7 @@ class ExtractOTIOReview(openpype.api.Extractor): ]) elif gap: - sec_duration = openpype.lib.frames_to_secons( - gap, self.actual_fps) + sec_duration = frames_to_seconds(gap, self.actual_fps) # form command for rendering gap files command.extend([ diff --git a/openpype/plugins/publish/extract_otio_trimming_video.py b/openpype/plugins/publish/extract_otio_trimming_video.py index e8e2994f36..19625fa568 100644 --- a/openpype/plugins/publish/extract_otio_trimming_video.py +++ b/openpype/plugins/publish/extract_otio_trimming_video.py @@ -9,6 +9,7 @@ import os from pyblish import api import openpype from copy import deepcopy +from openpype.pipeline.editorial import frames_to_seconds class ExtractOTIOTrimmingVideo(openpype.api.Extractor): @@ -81,8 +82,8 @@ class ExtractOTIOTrimmingVideo(openpype.api.Extractor): frame_start = otio_range.start_time.value input_fps = otio_range.start_time.rate frame_duration = otio_range.duration.value - 1 - sec_start = openpype.lib.frames_to_secons(frame_start, input_fps) - sec_duration = openpype.lib.frames_to_secons(frame_duration, input_fps) + sec_start = frames_to_seconds(frame_start, input_fps) + sec_duration = frames_to_seconds(frame_duration, input_fps) # form command for rendering gap files command.extend([ From c4f3ad901b8bea13735bf3a2308d7c3ee957d8a2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 15:36:57 +0200 Subject: [PATCH 0678/1227] kept editorial functions in openpype.lib with deprecated decorator --- openpype/lib/editorial.py | 315 +++++++------------------------------- 1 file changed, 59 insertions(+), 256 deletions(-) diff --git a/openpype/lib/editorial.py b/openpype/lib/editorial.py index 32d6dc3688..2730ba1f3c 100644 --- a/openpype/lib/editorial.py +++ b/openpype/lib/editorial.py @@ -1,289 +1,92 @@ -import os -import re -import clique -from .import_utils import discover_host_vendor_module - -try: - import opentimelineio as otio - from opentimelineio import opentime as _ot -except ImportError: - if not os.environ.get("AVALON_APP"): - raise - otio = discover_host_vendor_module("opentimelineio") - _ot = discover_host_vendor_module("opentimelineio.opentime") +import warnings +import functools -def otio_range_to_frame_range(otio_range): - start = _ot.to_frames( - otio_range.start_time, otio_range.start_time.rate) - end = start + _ot.to_frames( - otio_range.duration, otio_range.duration.rate) - return start, end +def editorial_deprecated(func): + """Mark functions as deprecated. - -def otio_range_with_handles(otio_range, instance): - handle_start = instance.data["handleStart"] - handle_end = instance.data["handleEnd"] - handles_duration = handle_start + handle_end - fps = float(otio_range.start_time.rate) - start = _ot.to_frames(otio_range.start_time, fps) - duration = _ot.to_frames(otio_range.duration, fps) - - return _ot.TimeRange( - start_time=_ot.RationalTime((start - handle_start), fps), - duration=_ot.RationalTime((duration + handles_duration), fps) - ) - - -def is_overlapping_otio_ranges(test_otio_range, main_otio_range, strict=False): - test_start, test_end = otio_range_to_frame_range(test_otio_range) - main_start, main_end = otio_range_to_frame_range(main_otio_range) - covering_exp = bool( - (test_start <= main_start) and (test_end >= main_end) - ) - inside_exp = bool( - (test_start >= main_start) and (test_end <= main_end) - ) - overlaying_right_exp = bool( - (test_start <= main_end) and (test_end >= main_end) - ) - overlaying_left_exp = bool( - (test_end >= main_start) and (test_start <= main_start) - ) - - if not strict: - return any(( - covering_exp, - inside_exp, - overlaying_right_exp, - overlaying_left_exp - )) - else: - return covering_exp - - -def convert_to_padded_path(path, padding): + It will result in a warning being emitted when the function is used. """ - Return correct padding in sequence string - Args: - path (str): path url or simple file name - padding (int): number of padding - - Returns: - type: string with reformated path - - Example: - convert_to_padded_path("plate.%d.exr") > plate.%04d.exr - - """ - if "%d" in path: - path = re.sub("%d", "%0{padding}d".format(padding=padding), path) - return path + @functools.wraps(func) + def new_func(*args, **kwargs): + warnings.warn( + ( + "Call to deprecated function '{}'." + " Function was moved to 'openpype.pipeline.editorial'." + ).format(func.__name__), + category=DeprecationWarning, + stacklevel=2 + ) + return func(*args, **kwargs) + return new_func -def trim_media_range(media_range, source_range): - """ - Trim input media range with clip source range. +@editorial_deprecated +def otio_range_to_frame_range(*args, **kwargs): + from openpype.pipeline.editorial import otio_range_to_frame_range - Args: - media_range (otio._ot._ot.TimeRange): available range of media - source_range (otio._ot._ot.TimeRange): clip required range - - Returns: - otio._ot._ot.TimeRange: trimmed media range - - """ - rw_media_start = _ot.RationalTime( - media_range.start_time.value + source_range.start_time.value, - media_range.start_time.rate - ) - rw_media_duration = _ot.RationalTime( - source_range.duration.value, - media_range.duration.rate - ) - return _ot.TimeRange( - rw_media_start, rw_media_duration) + return otio_range_to_frame_range(*args, **kwargs) -def range_from_frames(start, duration, fps): - """ - Returns otio time range. +@editorial_deprecated +def otio_range_with_handles(*args, **kwargs): + from openpype.pipeline.editorial import otio_range_with_handles - Args: - start (int): frame start - duration (int): frame duration - fps (float): frame range - - Returns: - otio._ot._ot.TimeRange: created range - - """ - return _ot.TimeRange( - _ot.RationalTime(start, fps), - _ot.RationalTime(duration, fps) - ) + return otio_range_with_handles(*args, **kwargs) -def frames_to_secons(frames, framerate): - """ - Returning secons. +@editorial_deprecated +def is_overlapping_otio_ranges(*args, **kwargs): + from openpype.pipeline.editorial import is_overlapping_otio_ranges - Args: - frames (int): frame - framerate (float): frame rate - - Returns: - float: second value - - """ - rt = _ot.from_frames(frames, framerate) - return _ot.to_seconds(rt) + return is_overlapping_otio_ranges(*args, **kwargs) -def frames_to_timecode(frames, framerate): - rt = _ot.from_frames(frames, framerate) - return _ot.to_timecode(rt) +@editorial_deprecated +def convert_to_padded_path(*args, **kwargs): + from openpype.pipeline.editorial import convert_to_padded_path + + return convert_to_padded_path(*args, **kwargs) -def make_sequence_collection(path, otio_range, metadata): - """ - Make collection from path otio range and otio metadata. +@editorial_deprecated +def trim_media_range(*args, **kwargs): + from openpype.pipeline.editorial import trim_media_range - Args: - path (str): path to image sequence with `%d` - otio_range (otio._ot._ot.TimeRange): range to be used - metadata (dict): data where padding value can be found - - Returns: - list: dir_path (str): path to sequence, collection object - - """ - if "%" not in path: - return None - file_name = os.path.basename(path) - dir_path = os.path.dirname(path) - head = file_name.split("%")[0] - tail = os.path.splitext(file_name)[-1] - first, last = otio_range_to_frame_range(otio_range) - collection = clique.Collection( - head=head, tail=tail, padding=metadata["padding"]) - collection.indexes.update([i for i in range(first, last)]) - return dir_path, collection + return trim_media_range(*args, **kwargs) -def _sequence_resize(source, length): - step = float(len(source) - 1) / (length - 1) - for i in range(length): - low, ratio = divmod(i * step, 1) - high = low + 1 if ratio > 0 else low - yield (1 - ratio) * source[int(low)] + ratio * source[int(high)] +@editorial_deprecated +def range_from_frames(*args, **kwargs): + from openpype.pipeline.editorial import range_from_frames + + return range_from_frames(*args, **kwargs) -def get_media_range_with_retimes(otio_clip, handle_start, handle_end): - source_range = otio_clip.source_range - available_range = otio_clip.available_range() - media_in = available_range.start_time.value - media_out = available_range.end_time_inclusive().value +@editorial_deprecated +def frames_to_seconds(*args, **kwargs): + from openpype.pipeline.editorial import frames_to_seconds - # modifiers - time_scalar = 1. - offset_in = 0 - offset_out = 0 - time_warp_nodes = [] + return frames_to_seconds(*args, **kwargs) - # Check for speed effects and adjust playback speed accordingly - for effect in otio_clip.effects: - if isinstance(effect, otio.schema.LinearTimeWarp): - time_scalar = effect.time_scalar - elif isinstance(effect, otio.schema.FreezeFrame): - # For freeze frame, playback speed must be set after range - time_scalar = 0. +@editorial_deprecated +def frames_to_timecode(*args, **kwargs): + from openpype.pipeline.editorial import frames_to_timecode - elif isinstance(effect, otio.schema.TimeEffect): - # For freeze frame, playback speed must be set after range - name = effect.name - effect_name = effect.effect_name - if "TimeWarp" not in effect_name: - continue - metadata = effect.metadata - lookup = metadata.get("lookup") - if not lookup: - continue + return frames_to_timecode(*args, **kwargs) - # time warp node - tw_node = { - "Class": "TimeWarp", - "name": name - } - tw_node.update(metadata) - tw_node["lookup"] = list(lookup) - # get first and last frame offsets - offset_in += lookup[0] - offset_out += lookup[-1] +@editorial_deprecated +def make_sequence_collection(*args, **kwargs): + from openpype.pipeline.editorial import make_sequence_collection - # add to timewarp nodes - time_warp_nodes.append(tw_node) + return make_sequence_collection(*args, **kwargs) - # multiply by time scalar - offset_in *= time_scalar - offset_out *= time_scalar - # filip offset if reversed speed - if time_scalar < 0: - _offset_in = offset_out - _offset_out = offset_in - offset_in = _offset_in - offset_out = _offset_out +@editorial_deprecated +def get_media_range_with_retimes(*args, **kwargs): + from openpype.pipeline.editorial import get_media_range_with_retimes - # scale handles - handle_start *= abs(time_scalar) - handle_end *= abs(time_scalar) - - # filip handles if reversed speed - if time_scalar < 0: - _handle_start = handle_end - _handle_end = handle_start - handle_start = _handle_start - handle_end = _handle_end - - source_in = source_range.start_time.value - - media_in_trimmed = ( - media_in + source_in + offset_in) - media_out_trimmed = ( - media_in + source_in + ( - ((source_range.duration.value - 1) * abs( - time_scalar)) + offset_out)) - - # calculate available handles - if (media_in_trimmed - media_in) < handle_start: - handle_start = (media_in_trimmed - media_in) - if (media_out - media_out_trimmed) < handle_end: - handle_end = (media_out - media_out_trimmed) - - # create version data - version_data = { - "versionData": { - "retime": True, - "speed": time_scalar, - "timewarps": time_warp_nodes, - "handleStart": round(handle_start), - "handleEnd": round(handle_end) - } - } - - returning_dict = { - "mediaIn": media_in_trimmed, - "mediaOut": media_out_trimmed, - "handleStart": round(handle_start), - "handleEnd": round(handle_end) - } - - # add version data only if retime - if time_warp_nodes or time_scalar != 1.: - returning_dict.update(version_data) - - return returning_dict + return get_media_range_with_retimes(*args, **kwargs) From 4c046b442aef143a065b84c7c933fba14d1a34f5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 15:37:14 +0200 Subject: [PATCH 0679/1227] removed unused discover_host_vendor_module --- openpype/lib/import_utils.py | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 openpype/lib/import_utils.py diff --git a/openpype/lib/import_utils.py b/openpype/lib/import_utils.py deleted file mode 100644 index e88c07fca6..0000000000 --- a/openpype/lib/import_utils.py +++ /dev/null @@ -1,25 +0,0 @@ -import os -import sys -import importlib -from .log import PypeLogger as Logger - -log = Logger().get_logger(__name__) - - -def discover_host_vendor_module(module_name): - host = os.environ["AVALON_APP"] - pype_root = os.environ["OPENPYPE_REPOS_ROOT"] - main_module = module_name.split(".")[0] - module_path = os.path.join( - pype_root, "hosts", host, "vendor", main_module) - - log.debug( - "Importing module from host vendor path: `{}`".format(module_path)) - - if not os.path.exists(module_path): - log.warning( - "Path not existing: `{}`".format(module_path)) - return None - - sys.path.insert(1, module_path) - return importlib.import_module(module_name) From 5b9d2f2915f59e4b98f00bdf5855a7f093a7c46a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 16 Jun 2022 16:41:09 +0200 Subject: [PATCH 0680/1227] Fix - add AE validation scene on render.local It was missing for local rendering. --- .../aftereffects/plugins/publish/validate_scene_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py b/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py index 14e224fdc2..6fe63fc41e 100644 --- a/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py +++ b/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py @@ -54,7 +54,7 @@ class ValidateSceneSettings(OptionalPyblishPluginMixin, order = pyblish.api.ValidatorOrder label = "Validate Scene Settings" - families = ["render.farm", "render"] + families = ["render.farm", "render.local", "render"] hosts = ["aftereffects"] optional = True From ab0bc2108c5d9a954e7fb9adde3cda732ace9afe Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 16 Jun 2022 16:44:18 +0200 Subject: [PATCH 0681/1227] deadline: fixing misidentification of revieables --- .../deadline/plugins/publish/submit_publish_job.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 0583c25b57..d2f709825c 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -642,9 +642,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): def _solve_families(self, instance, preview=False): families = instance.get("families") - # test also instance data review attribute - preview = preview or instance.get("review") - # if we have one representation with preview tag # flag whole instance for review and for ftrack if preview: @@ -726,6 +723,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): families = ["render"] + # pass review to families if marked as review + if data.get("review"): + families.append("review") + instance_skeleton_data = { "family": "render", "subset": subset, @@ -754,10 +755,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "family": "prerender", "families": []}) - # also include review attribute if available - if "review" in data: - instance_skeleton_data["review"] = data["review"] - # skip locking version if we are creating v01 instance_version = instance.data.get("version") # take this if exists if instance_version != 1: From 2890c950f5b58fd7654497aae22016d2d1df56ca Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 16:46:31 +0200 Subject: [PATCH 0682/1227] trigger open events --- openpype/tools/workfiles/files_widget.py | 45 ++++++++++++++++++++---- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 68fe8301c9..709f05b26b 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -90,7 +90,9 @@ class FilesWidget(QtWidgets.QWidget): self._task_type = None # Pype's anatomy object for current project - self.anatomy = Anatomy(legacy_io.Session["AVALON_PROJECT"]) + project_name = legacy_io.Session["AVALON_PROJECT"] + self.anatomy = Anatomy(project_name) + self.project_name = project_name # Template key used to get work template from anatomy templates self.template_key = "work" @@ -98,6 +100,7 @@ class FilesWidget(QtWidgets.QWidget): self._workfiles_root = None self._workdir_path = None self.host = registered_host() + self.host_name = os.environ["AVALON_APP"] # Whether to automatically select the latest modified # file on a refresh of the files model. @@ -385,8 +388,9 @@ class FilesWidget(QtWidgets.QWidget): return None if self._asset_doc is None: - project_name = legacy_io.active_project() - self._asset_doc = get_asset_by_id(project_name, self._asset_id) + self._asset_doc = get_asset_by_id( + self.project_name, self._asset_id + ) return self._asset_doc @@ -396,8 +400,8 @@ class FilesWidget(QtWidgets.QWidget): session = legacy_io.Session.copy() self.template_key = get_workfile_template_key( self._task_type, - session["AVALON_APP"], - project_name=session["AVALON_PROJECT"] + self.host_name, + project_name=self.project_name ) changes = compute_session_changes( session, @@ -453,8 +457,35 @@ class FilesWidget(QtWidgets.QWidget): # Save current scene, continue to open file host.save_file(current_file) + asset_name = None + asset_doc = self._get_asset_doc() + if asset_doc: + asset_name = asset_doc["name"] + + emit_event( + "workfile.open.before", + { + "filepath": filepath, + "project_name": self.project_name, + "asset_name": asset_name, + "task_name": self._task_name, + "host_name": self.host_name + }, + source="workfiles.tool" + ) self._enter_session() host.open_file(filepath) + emit_event( + "workfile.open.after", + { + "filepath": filepath, + "project_name": self.project_name, + "asset_name": asset_name, + "task_name": self._task_name, + "host_name": self.host_name + }, + source="workfiles.tool" + ) self.file_opened.emit() def save_changes_prompt(self): @@ -602,10 +633,10 @@ class FilesWidget(QtWidgets.QWidget): # Create extra folders create_workdir_extra_folders( self._workdir_path, - legacy_io.Session["AVALON_APP"], + self.host_name, self._task_type, self._task_name, - legacy_io.Session["AVALON_PROJECT"] + self.project_name ) # Trigger after save events emit_event( From ee4b635edd51523e46864e00c5ec873e477e3e3b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 16:51:55 +0200 Subject: [PATCH 0683/1227] trigger open workfile events in workfiles tool --- openpype/tools/workfiles/files_widget.py | 54 +++++++++++++++--------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 709f05b26b..a7e54471dc 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -1,6 +1,7 @@ import os import logging import shutil +import copy import Qt from Qt import QtWidgets, QtCore @@ -434,6 +435,21 @@ class FilesWidget(QtWidgets.QWidget): template_key=self.template_key ) + def _get_event_context_data(self): + asset_id = None + asset_name = None + asset_doc = self._get_asset_doc() + if asset_doc: + asset_id = asset_doc["_id"] + asset_name = asset_doc["name"] + return { + "project_name": self.project_name, + "asset_id": asset_id, + "asset_name": asset_name, + "task_name": self._task_name, + "host_name": self.host_name + } + def open_file(self, filepath): host = self.host if host.has_unsaved_changes(): @@ -457,33 +473,19 @@ class FilesWidget(QtWidgets.QWidget): # Save current scene, continue to open file host.save_file(current_file) - asset_name = None - asset_doc = self._get_asset_doc() - if asset_doc: - asset_name = asset_doc["name"] - + event_data_before = self._get_event_context_data() + event_data_before["filepath"] = filepath + event_data_after = copy.deepcopy(event_data_before) emit_event( "workfile.open.before", - { - "filepath": filepath, - "project_name": self.project_name, - "asset_name": asset_name, - "task_name": self._task_name, - "host_name": self.host_name - }, + event_data_before, source="workfiles.tool" ) self._enter_session() host.open_file(filepath) emit_event( "workfile.open.after", - { - "filepath": filepath, - "project_name": self.project_name, - "asset_name": asset_name, - "task_name": self._task_name, - "host_name": self.host_name - }, + event_data_after, source="workfiles.tool" ) self.file_opened.emit() @@ -598,9 +600,14 @@ class FilesWidget(QtWidgets.QWidget): src_path = self._get_selected_filepath() # Trigger before save event + event_data_before = self._get_event_context_data() + event_data_before.update({ + "filename": work_filename, + "workdir_path": self._workdir_path + }) emit_event( "workfile.save.before", - {"filename": work_filename, "workdir_path": self._workdir_path}, + event_data_before, source="workfiles.tool" ) @@ -638,10 +645,15 @@ class FilesWidget(QtWidgets.QWidget): self._task_name, self.project_name ) + event_data_after = self._get_event_context_data() + event_data_after.update({ + "filename": work_filename, + "workdir_path": self._workdir_path + }) # Trigger after save events emit_event( "workfile.save.after", - {"filename": work_filename, "workdir_path": self._workdir_path}, + event_data_after, source="workfiles.tool" ) From 94f9d6309822431dc00218687207cefcc4397b11 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 17:05:26 +0200 Subject: [PATCH 0684/1227] modules have method that can be called on host installation --- openpype/modules/base.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index bca64b19f8..b9ccec13cc 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -463,6 +463,25 @@ class OpenPypeModule: pass + def on_host_install(self, host, host_name, project_name): + """Host was installed which gives option to handle in-host logic. + + It is a good option to register in-host event callbacks which are + specific for the module. The module is kept in memory for rest of + the process. + + Arguments may change in future. E.g. 'host_name' should be possible + to receive from 'host' object. + + Args: + host (ModuleType): Access to installed/registered host object. + host_name (str): Name of host. + project_name (str): Project name which is main part of host + context. + """ + + pass + def cli(self, module_click_group): """Add commands to click group. From 0c28a29650491603a04ad9f9178e7cdc81c9f73b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 17:05:48 +0200 Subject: [PATCH 0685/1227] trigger on_host_install method on host installation --- openpype/pipeline/context_tools.py | 33 ++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index c6e09cfba1..8643c3d69d 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -33,6 +33,9 @@ from . import ( _is_installed = False _registered_root = {"_": ""} _registered_host = {"_": None} +# Keep modules manager (and it's modules) in memory +# - that gives option to register modules' callbacks +_modules_manager = None log = logging.getLogger(__name__) @@ -44,6 +47,23 @@ PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") +def _get_modules_manager(): + """Get or create modules manager for host installation. + + This is not meant for public usage. Reason is to keep modules + in memory of process to be able trigger their event callbacks if they + need any. + + Returns: + ModulesManager: Manager wrapping discovered modules. + """ + + global _modules_manager + if _modules_manager is None: + _modules_manager = ModulesManager() + return _modules_manager + + def register_root(path): """Register currently active root""" log.info("Registering root: %s" % path) @@ -70,10 +90,12 @@ def install_host(host): avalon host-interface. """ global _is_installed + global _modules_manager _is_installed = True legacy_io.install() + modules_manager = _get_modules_manager() missing = list() for key in ("AVALON_PROJECT", "AVALON_ASSET"): @@ -112,7 +134,14 @@ def install_host(host): else: pyblish.api.register_target("local") - install_openpype_plugins() + project_name = os.environ.get("AVALON_PROJECT") + host_name = os.environ.get("AVALON_APP") + + # Give option to handle host installation + for module in modules_manager.get_enabled_modules(): + module.on_host_install(host, host_name, project_name) + + install_openpype_plugins(project_name, host_name) def install_openpype_plugins(project_name=None, host_name=None): @@ -124,7 +153,7 @@ def install_openpype_plugins(project_name=None, host_name=None): pyblish.api.register_discovery_filter(filter_pyblish_plugins) register_loader_plugin_path(LOAD_PATH) - modules_manager = ModulesManager() + modules_manager = _get_modules_manager() publish_plugin_dirs = modules_manager.collect_plugin_paths()["publish"] for path in publish_plugin_dirs: pyblish.api.register_plugin_path(path) From 89a7d438b4b37e0903ae33f54de58bb04a03b9c4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 17:42:46 +0200 Subject: [PATCH 0686/1227] added human readable keys into taskChanged topic data --- openpype/lib/avalon_context.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 9d8a92cfe9..243e89e00d 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -797,8 +797,14 @@ def update_current_task(task=None, asset=None, app=None, template_key=None): else: os.environ[key] = value + data = changes.copy() + # Convert env keys to human readable keys + data["project_name"] = legacy_io.Session["AVALON_PROJECT"] + data["asset_name"] = legacy_io.Session["AVALON_ASSET"] + data["task_name"] = legacy_io.Session["AVALON_TASK"] + # Emit session change - emit_event("taskChanged", changes.copy()) + emit_event("taskChanged", data) return changes From cbb876c155fb6f31356d74404f7763689e09a6d7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 17:43:18 +0200 Subject: [PATCH 0687/1227] moved timer change on task change to timers manager module --- .../modules/timers_manager/timers_manager.py | 18 ++++++++++++++++++ openpype/pipeline/context_tools.py | 8 -------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/openpype/modules/timers_manager/timers_manager.py b/openpype/modules/timers_manager/timers_manager.py index 3f77a2b7dc..3cf1614316 100644 --- a/openpype/modules/timers_manager/timers_manager.py +++ b/openpype/modules/timers_manager/timers_manager.py @@ -7,6 +7,7 @@ from openpype_interfaces import ( ITrayService, ILaunchHookPaths ) +from openpype.lib.events import register_event_callback from openpype.pipeline import AvalonMongoDB from .exceptions import InvalidContextError @@ -422,3 +423,20 @@ class TimersManager(OpenPypeModule, ITrayService, ILaunchHookPaths): } return requests.post(rest_api_url, json=data) + + def on_host_install(self, host, host_name, project_name): + self.log.debug("Installing task changed callback") + register_event_callback("taskChanged", self._on_host_task_change) + + def _on_host_task_change(self, event): + project_name = event["project_name"] + asset_name = event["asset_name"] + task_name = event["task_name"] + self.log.debug(( + "Sending message that timer should change to" + " Project: {} Asset: {} Task: {}" + ).format(project_name, asset_name, task_name)) + + self.start_timer_with_webserver( + project_name, asset_name, task_name, self.log + ) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index 8643c3d69d..3e63eeba27 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -16,9 +16,7 @@ from openpype.modules import load_modules, ModulesManager from openpype.settings import get_project_settings from openpype.lib import ( Anatomy, - register_event_callback, filter_pyblish_plugins, - change_timer_to_current_context, ) from . import ( @@ -117,8 +115,6 @@ def install_host(host): register_host(host) - register_event_callback("taskChanged", _on_task_change) - def modified_emit(obj, record): """Method replacing `emit` in Pyblish's MessageHandler.""" record.msg = record.getMessage() @@ -197,10 +193,6 @@ def install_openpype_plugins(project_name=None, host_name=None): register_inventory_action(path) -def _on_task_change(): - change_timer_to_current_context() - - def uninstall_host(): """Undo all of what `install()` did""" host = registered_host() From 3da1720702a2f21350e8a9cb4f17bbca4e11db23 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 16 Jun 2022 18:04:08 +0200 Subject: [PATCH 0688/1227] Fix - validate_scene_settings was failing for legacy instances Fix - renderLocal is published to 'render' folder same as render farm --- .../hosts/aftereffects/plugins/publish/collect_render.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/publish/collect_render.py b/openpype/hosts/aftereffects/plugins/publish/collect_render.py index fa23bf92b0..97b3175c57 100644 --- a/openpype/hosts/aftereffects/plugins/publish/collect_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/collect_render.py @@ -21,7 +21,7 @@ class AERenderInstance(RenderInstance): projectEntity = attr.ib(default=None) stagingDir = attr.ib(default=None) app_version = attr.ib(default=None) - publish_attributes = attr.ib(default=None) + publish_attributes = attr.ib(default={}) file_name = attr.ib(default=None) @@ -90,7 +90,7 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): subset_name = inst.data["subset"] instance = AERenderInstance( - family=family, + family="render", families=inst.data.get("families", []), version=version, time="", @@ -116,7 +116,7 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): toBeRenderedOn='deadline', fps=fps, app_version=app_version, - publish_attributes=inst.data.get("publish_attributes"), + publish_attributes=inst.data.get("publish_attributes", {}), file_name=render_q.file_name ) From d4df16bb87aac5b667bed8b5ecdd9f6afd52f20b Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 17 Jun 2022 08:08:05 +0300 Subject: [PATCH 0689/1227] Test resolution values against settings --- .../maya/plugins/publish/extract_playblast.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index bb1ecf279d..ce1bb8ae83 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -51,7 +51,21 @@ class ExtractPlayblast(openpype.api.Extractor): ) preset = lib.load_capture_preset(data=self.capture_preset) + capture_presets = openpype.api.get_current_project_settings()["maya"]["publish"]["ExtractPlayblast"]["capture_preset"] # noqa + width_preset = capture_presets["Resolution"]["width"] + height_preset = capture_presets["Resolution"]["height"] preset['camera'] = camera + + if width_preset == 0: + preset['width'] = instance.data.get("resolutionWidth") + else: + preset["width"] = width_preset + + if height_preset == 0: + preset['width'] = instance.data.get("resolutionHeight") + else: + preset['height'] = height_preset + preset['start_frame'] = start preset['end_frame'] = end camera_option = preset.get("camera_option", {}) From e9168118d73cb265a4f5597b5d577197b41db4e1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 10:25:36 +0200 Subject: [PATCH 0690/1227] handle prerender earlier prerender --- .../deadline/plugins/publish/submit_publish_job.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index ae4ada709a..6d08e72839 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -722,14 +722,17 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): " This may cause issues." ).format(source)) - families = ["render"] + family = "render" + if "prerender" in instance.data["families"]: + family = "prerender" + families = [family] # pass review to families if marked as review if data.get("review"): families.append("review") instance_skeleton_data = { - "family": "render", + "family": family, "subset": subset, "families": families, "asset": asset, @@ -751,11 +754,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "useSequenceForReview": data.get("useSequenceForReview", True) } - if "prerender" in instance.data["families"]: - instance_skeleton_data.update({ - "family": "prerender", - "families": []}) - # skip locking version if we are creating v01 instance_version = instance.data.get("version") # take this if exists if instance_version != 1: From a1676d5c44de04d880996afc738d245bcd65e66d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 10:58:26 +0200 Subject: [PATCH 0691/1227] create representation variable all the time --- .../hosts/nuke/plugins/publish/precollect_writes.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py index e050fc8c52..7e50679ed5 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -72,12 +72,12 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): if "representations" not in instance.data: instance.data["representations"] = list() - representation = { - 'name': ext, - 'ext': ext, - "stagingDir": output_dir, - "tags": list() - } + representation = { + 'name': ext, + 'ext': ext, + "stagingDir": output_dir, + "tags": list() + } try: collected_frames = [f for f in os.listdir(output_dir) From 7c7ae5d3dccefb409870b91ad8ef50097268c815 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 17 Jun 2022 11:38:14 +0200 Subject: [PATCH 0692/1227] Fix Maya 2019 support `__iter__` is only implemented in Maya 2020+ for Maya Python 2.0 iterators. --- openpype/hosts/maya/plugins/publish/collect_instances.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_instances.py b/openpype/hosts/maya/plugins/publish/collect_instances.py index 433fa9886d..6cc9b9d7e4 100644 --- a/openpype/hosts/maya/plugins/publish/collect_instances.py +++ b/openpype/hosts/maya/plugins/publish/collect_instances.py @@ -28,18 +28,18 @@ def get_all_children(nodes): dag = sel.getDagPath(0) iterator.reset(dag) - next(iterator) # ignore self + iterator.next() # ignore self while not iterator.isDone(): path = iterator.fullPathName() if path in traversed: iterator.prune() - next(iterator) + iterator.next() continue traversed.add(path) - next(iterator) + iterator.next() return list(traversed) From 5b1ab9e7d3694f127eb3457f5e8e2dab767008dd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 17 Jun 2022 11:52:35 +0200 Subject: [PATCH 0693/1227] Shush hound --- openpype/hosts/maya/plugins/publish/collect_instances.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_instances.py b/openpype/hosts/maya/plugins/publish/collect_instances.py index 6cc9b9d7e4..ad1f794680 100644 --- a/openpype/hosts/maya/plugins/publish/collect_instances.py +++ b/openpype/hosts/maya/plugins/publish/collect_instances.py @@ -28,18 +28,19 @@ def get_all_children(nodes): dag = sel.getDagPath(0) iterator.reset(dag) - iterator.next() # ignore self + # ignore self + iterator.next() # noqa: B305 while not iterator.isDone(): path = iterator.fullPathName() if path in traversed: iterator.prune() - iterator.next() + iterator.next() # noqa: B305 continue traversed.add(path) - iterator.next() + iterator.next() # noqa: B305 return list(traversed) From b9abbcd61629d5103413399b5048ca5ad2d23545 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 12:11:00 +0200 Subject: [PATCH 0694/1227] fixed extract thumbnail in nuke --- .../nuke/plugins/publish/extract_thumbnail.py | 62 +++++++++---------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index 092fc07d6c..a622271855 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -65,48 +65,46 @@ class ExtractThumbnail(openpype.api.Extractor): temporary_nodes = [] # try to connect already rendered images - if self.use_rendered: - collection = instance.data.get("collection", None) - self.log.debug("__ collection: `{}`".format(collection)) + previous_node = node + collection = instance.data.get("collection", None) + self.log.debug("__ collection: `{}`".format(collection)) - if collection: - # get path - fname = os.path.basename(collection.format( - "{head}{padding}{tail}")) - fhead = collection.format("{head}") + if collection: + # get path + fname = os.path.basename(collection.format( + "{head}{padding}{tail}")) + fhead = collection.format("{head}") - thumb_fname = list(collection)[mid_frame] - else: - fname = thumb_fname = os.path.basename( - instance.data.get("path", None)) - fhead = os.path.splitext(fname)[0] + "." + thumb_fname = list(collection)[mid_frame] + else: + fname = thumb_fname = os.path.basename( + instance.data.get("path", None)) + fhead = os.path.splitext(fname)[0] + "." - self.log.debug("__ fhead: `{}`".format(fhead)) + self.log.debug("__ fhead: `{}`".format(fhead)) - if "#" in fhead: - fhead = fhead.replace("#", "")[:-1] + if "#" in fhead: + fhead = fhead.replace("#", "")[:-1] - path_render = os.path.join( - staging_dir, thumb_fname).replace("\\", "/") - self.log.debug("__ path_render: `{}`".format(path_render)) + path_render = os.path.join( + staging_dir, thumb_fname).replace("\\", "/") + self.log.debug("__ path_render: `{}`".format(path_render)) + if self.use_rendered and os.path.isfile(path_render): # check if file exist otherwise connect to write node - if os.path.isfile(path_render): - rnode = nuke.createNode("Read") + rnode = nuke.createNode("Read") - rnode["file"].setValue(path_render) + rnode["file"].setValue(path_render) - # turn it raw if none of baking is ON - if all([ - not self.bake_viewer_input_process, - not self.bake_viewer_process - ]): - rnode["raw"].setValue(True) + # turn it raw if none of baking is ON + if all([ + not self.bake_viewer_input_process, + not self.bake_viewer_process + ]): + rnode["raw"].setValue(True) - temporary_nodes.append(rnode) - previous_node = rnode - else: - previous_node = node + temporary_nodes.append(rnode) + previous_node = rnode # bake viewer input look node into thumbnail image if self.bake_viewer_input_process: From d230cb7d49170d71eeaf8b3f201b3e966817132b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 17 Jun 2022 12:39:44 +0200 Subject: [PATCH 0695/1227] Fix - audio validator for Harmony has wrong logic Wrong condition stayed after change from asset to raise (it should be negated). --- openpype/hosts/harmony/plugins/publish/validate_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/harmony/plugins/publish/validate_audio.py b/openpype/hosts/harmony/plugins/publish/validate_audio.py index cb6b2307cd..e9b8609803 100644 --- a/openpype/hosts/harmony/plugins/publish/validate_audio.py +++ b/openpype/hosts/harmony/plugins/publish/validate_audio.py @@ -47,6 +47,6 @@ class ValidateAudio(pyblish.api.InstancePlugin): formatting_data = { "audio_url": audio_path } - if os.path.isfile(audio_path): + if not os.path.isfile(audio_path): raise PublishXmlValidationError(self, msg, formatting_data=formatting_data) From 29def2f2679ca95fa645ef6afe16a1b48bf1b9ad Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 13:17:05 +0200 Subject: [PATCH 0696/1227] fix kwarg key --- .../hosts/webpublisher/webserver_service/webpublish_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index b1041bf6cb..457bcd7f93 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -107,7 +107,7 @@ class HiearchyEndpoint(ResourceRestApiEndpoint): "type": 1, } - asset_docs = get_assets(project_name, field=query_projection.keys()) + asset_docs = get_assets(project_name, fields=query_projection.keys()) asset_docs_by_id = { asset_doc["_id"]: asset_doc for asset_doc in asset_docs From 434e6bf78a8595eeb7c444d72711e547970b1d17 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 17 Jun 2022 13:51:41 +0200 Subject: [PATCH 0697/1227] Fix - added json support to all resources encode method was missing for WebpublishRestApiResource --- .../webserver_service/webpublish_routes.py | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index 457bcd7f93..4cb3cee8e1 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -38,18 +38,11 @@ class WebpublishApiEndpoint(ResourceRestApiEndpoint): return self.resource.dbcon -class RestApiResource: - """Resource carrying needed info and Avalon DB connection for publish.""" - def __init__(self, server_manager, executable, upload_dir, - studio_task_queue=None): - self.server_manager = server_manager - self.upload_dir = upload_dir - self.executable = executable - - if studio_task_queue is None: - studio_task_queue = collections.deque().dequeu - self.studio_task_queue = studio_task_queue +class JsonApiResource: + """Resource for json manipulation. + All resources handling sending output to REST should inherit from + """ @staticmethod def json_dump_handler(value): if isinstance(value, datetime.datetime): @@ -69,7 +62,20 @@ class RestApiResource: ).encode("utf-8") -class WebpublishRestApiResource: +class RestApiResource(JsonApiResource): + """Resource carrying needed info and Avalon DB connection for publish.""" + def __init__(self, server_manager, executable, upload_dir, + studio_task_queue=None): + self.server_manager = server_manager + self.upload_dir = upload_dir + self.executable = executable + + if studio_task_queue is None: + studio_task_queue = collections.deque().dequeu + self.studio_task_queue = studio_task_queue + + +class WebpublishRestApiResource(JsonApiResource): """Resource carrying OP DB connection for storing batch info into DB.""" def __init__(self): From 0dfe3b69745fc9d9fb94143aa0cc3039640ab669 Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Fri, 17 Jun 2022 15:04:46 +0300 Subject: [PATCH 0698/1227] Update openpype/hosts/maya/plugins/publish/extract_playblast.py Simplify settings capture statement. Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index ce1bb8ae83..9e1970780b 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -51,7 +51,7 @@ class ExtractPlayblast(openpype.api.Extractor): ) preset = lib.load_capture_preset(data=self.capture_preset) - capture_presets = openpype.api.get_current_project_settings()["maya"]["publish"]["ExtractPlayblast"]["capture_preset"] # noqa + capture_presets = self.capture_preset width_preset = capture_presets["Resolution"]["width"] height_preset = capture_presets["Resolution"]["height"] preset['camera'] = camera From a28f76374e583e6bf5d2f8490a39945a63da418f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 14:33:26 +0200 Subject: [PATCH 0699/1227] use get_openpype_username to fill data for templates filling --- openpype/lib/avalon_context.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 9d8a92cfe9..c27c5f572e 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -7,7 +7,6 @@ import platform import logging import collections import functools -import getpass from bson.objectid import ObjectId @@ -19,6 +18,7 @@ from .anatomy import Anatomy from .profiles_filtering import filter_profiles from .events import emit_event from .path_templates import StringTemplate +from .local_settings import get_openpype_username legacy_io = None @@ -550,7 +550,7 @@ def get_workdir_data(project_doc, asset_doc, task_name, host_name): "asset": asset_doc["name"], "parent": parent_name, "app": host_name, - "user": getpass.getuser(), + "user": get_openpype_username(), "hierarchy": hierarchy, } From cd90fb603ea3815445b9e181374428c2a5c4a18d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 14:37:38 +0200 Subject: [PATCH 0700/1227] remove unused imports --- openpype/plugins/publish/collect_current_pype_user.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/plugins/publish/collect_current_pype_user.py b/openpype/plugins/publish/collect_current_pype_user.py index 1a52a59012..2d507ba292 100644 --- a/openpype/plugins/publish/collect_current_pype_user.py +++ b/openpype/plugins/publish/collect_current_pype_user.py @@ -1,5 +1,3 @@ -import os -import getpass import pyblish.api from openpype.lib import get_openpype_username From 1f42006f571343cf832b078d453e6804d68a3cbd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 14:38:03 +0200 Subject: [PATCH 0701/1227] added user key to available template keys --- website/docs/admin_settings_project_anatomy.md | 1 + 1 file changed, 1 insertion(+) diff --git a/website/docs/admin_settings_project_anatomy.md b/website/docs/admin_settings_project_anatomy.md index 6e0b49f152..106faeb806 100644 --- a/website/docs/admin_settings_project_anatomy.md +++ b/website/docs/admin_settings_project_anatomy.md @@ -68,6 +68,7 @@ We have a few required anatomy templates for OpenPype to work properly, however | `representation` | Representation name | | `frame` | Frame number for sequence files. | | `app` | Application Name | +| `user` | User's login name (can be overridden in local settings) | | `output` | | | `comment` | | From 34a79bfec9bd448986957885eaedfeae9424eab0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 15:31:51 +0200 Subject: [PATCH 0702/1227] fix showing after plugin hide and don't ignore close events --- openpype/tools/pyblish_pype/window.py | 89 ++++++++++----------------- 1 file changed, 34 insertions(+), 55 deletions(-) diff --git a/openpype/tools/pyblish_pype/window.py b/openpype/tools/pyblish_pype/window.py index d27ec34345..78590259bc 100644 --- a/openpype/tools/pyblish_pype/window.py +++ b/openpype/tools/pyblish_pype/window.py @@ -468,10 +468,8 @@ class Window(QtWidgets.QDialog): current_page == "terminal" ) - self.state = { - "is_closing": False, - "current_page": current_page - } + self._current_page = current_page + self._hidden_for_plugin_process = False self.tabs[current_page].setChecked(True) @@ -590,14 +588,14 @@ class Window(QtWidgets.QDialog): target_page = page if direction is None: direction = -1 - elif name == self.state["current_page"]: + elif name == self._current_page: previous_page = page if direction is None: direction = 1 else: page.setVisible(False) - self.state["current_page"] = target + self._current_page = target self.slide_page(previous_page, target_page, direction) def slide_page(self, previous_page, target_page, direction): @@ -684,7 +682,7 @@ class Window(QtWidgets.QDialog): comment_visible=None, terminal_filters_visibile=None ): - target = self.state["current_page"] + target = self._current_page comment_visibility = ( not self.perspective_widget.isVisible() and not target == "terminal" @@ -845,7 +843,7 @@ class Window(QtWidgets.QDialog): def apply_log_suspend_value(self, value): self._suspend_logs = value - if self.state["current_page"] == "terminal": + if self._current_page == "terminal": self.tabs["overview"].setChecked(True) self.tabs["terminal"].setVisible(not self._suspend_logs) @@ -882,9 +880,21 @@ class Window(QtWidgets.QDialog): visibility = True if hasattr(plugin, "hide_ui_on_process") and plugin.hide_ui_on_process: visibility = False + self._hidden_for_plugin_process = not visibility - if self.isVisible() != visibility: - self.setVisible(visibility) + self._ensure_visible(visibility) + + def _ensure_visible(self, visible): + if self.isVisible() == visible: + return + + if not visible: + self.setVisible(visible) + else: + self.show() + self.raise_() + self.activateWindow() + self.showNormal() def on_plugin_action_menu_requested(self, pos): """The user right-clicked on a plug-in @@ -955,7 +965,7 @@ class Window(QtWidgets.QDialog): self.intent_box.setEnabled(True) # Refresh tab - self.on_tab_changed(self.state["current_page"]) + self.on_tab_changed(self._current_page) self.update_compatibility() self.button_suspend_logs.setEnabled(False) @@ -1027,8 +1037,9 @@ class Window(QtWidgets.QDialog): self._update_state() - if not self.isVisible(): - self.setVisible(True) + if self._hidden_for_plugin_process: + self._hidden_for_plugin_process = False + self._ensure_visible(True) def on_was_skipped(self, plugin): plugin_item = self.plugin_model.plugin_items[plugin.id] @@ -1103,8 +1114,9 @@ class Window(QtWidgets.QDialog): plugin_item, instance_item ) - if not self.isVisible(): - self.setVisible(True) + if self._hidden_for_plugin_process: + self._hidden_for_plugin_process = False + self._ensure_visible(True) # ------------------------------------------------------------------------- # @@ -1223,53 +1235,20 @@ class Window(QtWidgets.QDialog): """ - # Make it snappy, but take care to clean it all up. - # TODO(marcus): Enable GUI to return on problem, such - # as asking whether or not the user really wants to quit - # given there are things currently running. - self.hide() + self.info(self.tr("Closing..")) - if self.state["is_closing"]: + if self.controller.is_running: + self.info(self.tr("..as soon as processing is finished..")) + self.controller.stop() - # Explicitly clear potentially referenced data - self.info(self.tr("Cleaning up models..")) - self.intent_model.deleteLater() - self.plugin_model.deleteLater() - self.terminal_model.deleteLater() - self.terminal_proxy.deleteLater() - self.plugin_proxy.deleteLater() + self.info(self.tr("Cleaning up controller..")) + self.controller.cleanup() self.overview_instance_view.setModel(None) self.overview_plugin_view.setModel(None) self.terminal_view.setModel(None) - self.info(self.tr("Cleaning up controller..")) - self.controller.cleanup() - - self.info(self.tr("All clean!")) - self.info(self.tr("Good bye")) - return super(Window, self).closeEvent(event) - - self.info(self.tr("Closing..")) - - def on_problem(): - self.heads_up( - "Warning", "Had trouble closing down. " - "Please tell someone and try again." - ) - self.show() - - if self.controller.is_running: - self.info(self.tr("..as soon as processing is finished..")) - self.controller.stop() - self.finished.connect(self.close) - util.defer(200, on_problem) - return event.ignore() - - self.state["is_closing"] = True - - util.defer(200, self.close) - return event.ignore() + event.accept() def reject(self): """Handle ESC key""" From e54073608b59c4fe814a0fa3a1e49faef8e5a551 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 17 Jun 2022 15:48:28 +0200 Subject: [PATCH 0703/1227] Nuke: multiple bake stream correct frame range on farm --- .../plugins/publish/submit_nuke_deadline.py | 70 ++++++++++++------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index ca68c87f9a..93fb511a34 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -55,8 +55,8 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): self._ver = re.search(r"\d+\.\d+", context.data.get("hostVersion")) self._deadline_user = context.data.get( "deadlineUser", getpass.getuser()) - self._frame_start = int(instance.data["frameStartHandle"]) - self._frame_end = int(instance.data["frameEndHandle"]) + submit_frame_start = int(instance.data["frameStartHandle"]) + submit_frame_end = int(instance.data["frameEndHandle"]) # get output path render_path = instance.data['path'] @@ -82,13 +82,16 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): # exception for slate workflow if "slate" in instance.data["families"]: - self._frame_start -= 1 + submit_frame_start -= 1 - response = self.payload_submit(instance, - script_path, - render_path, - node.name() - ) + response = self.payload_submit( + instance, + script_path, + render_path, + node.name(), + submit_frame_start, + submit_frame_end + ) # Store output dir for unified publisher (filesequence) instance.data["deadlineSubmissionJob"] = response.json() instance.data["outputDir"] = os.path.dirname( @@ -96,20 +99,22 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): instance.data["publishJobState"] = "Suspended" if instance.data.get("bakingNukeScripts"): + # exception for slate workflow + if "slate" in instance.data["families"]: + submit_frame_start += 1 + for baking_script in instance.data["bakingNukeScripts"]: render_path = baking_script["bakeRenderPath"] script_path = baking_script["bakeScriptPath"] exe_node_name = baking_script["bakeWriteNodeName"] - # exception for slate workflow - if "slate" in instance.data["families"]: - self._frame_start += 1 - resp = self.payload_submit( instance, script_path, render_path, exe_node_name, + submit_frame_start, + submit_frame_end, response.json() ) @@ -126,13 +131,16 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): families.insert(0, "prerender") instance.data["families"] = families - def payload_submit(self, - instance, - script_path, - render_path, - exe_node_name, - responce_data=None - ): + def payload_submit( + self, + instance, + script_path, + render_path, + exe_node_name, + start_frame, + end_frame, + responce_data=None + ): render_dir = os.path.normpath(os.path.dirname(render_path)) script_name = os.path.basename(script_path) jobname = "%s - %s" % (script_name, instance.name) @@ -192,8 +200,8 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): "Plugin": "Nuke", "Frames": "{start}-{end}".format( - start=self._frame_start, - end=self._frame_end + start=start_frame, + end=end_frame ), "Comment": self._comment, @@ -293,7 +301,13 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): self.log.info(json.dumps(payload, indent=4, sort_keys=True)) # adding expectied files to instance.data - self.expected_files(instance, render_path) + self.expected_files( + instance, + render_path, + start_frame, + end_frame + ) + self.log.debug("__ expectedFiles: `{}`".format( instance.data["expectedFiles"])) response = requests.post(self.deadline_url, json=payload, timeout=10) @@ -339,9 +353,13 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): self.log.debug("_ path: `{}`".format(path)) return path - def expected_files(self, - instance, - path): + def expected_files( + self, + instance, + path, + start_frame, + end_frame + ): """ Create expected files in instance data """ if not instance.data.get("expectedFiles"): @@ -359,7 +377,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): instance.data["expectedFiles"].append(path) return - for i in range(self._frame_start, (self._frame_end + 1)): + for i in range(start_frame, (end_frame + 1)): instance.data["expectedFiles"].append( os.path.join(dir, (file % i)).replace("\\", "/")) From bd01ee948cb93414be4e8ef5657bb65491d936df Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 17 Jun 2022 16:09:34 +0200 Subject: [PATCH 0704/1227] OP-2448 - added support for single frame playblast review Before clique.assemble returns empty collections --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 3 ++- openpype/plugins/publish/extract_review.py | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index bb1ecf279d..3e8655be7d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -111,7 +111,8 @@ class ExtractPlayblast(openpype.api.Extractor): self.log.debug("playblast path {}".format(path)) collected_files = os.listdir(stagingdir) - collections, remainder = clique.assemble(collected_files) + collections, remainder = clique.assemble(collected_files, + minimum_items=1) self.log.debug("filename {}".format(filename)) frame_collection = None diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 879125dac3..de9e3926bd 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -762,8 +762,9 @@ class ExtractReview(pyblish.api.InstancePlugin): """ start_frame = int(start_frame) end_frame = int(end_frame) - collections = clique.assemble(files)[0] - assert len(collections) == 1, "Multiple collections found." + collections = clique.assemble(files, minimum_items=1)[0] + msg = "Multiple collections {} found.".format(collections) + assert len(collections) == 1, msg col = collections[0] # do nothing if no gap is found in input range @@ -845,7 +846,7 @@ class ExtractReview(pyblish.api.InstancePlugin): dst_staging_dir = new_repre["stagingDir"] if temp_data["input_is_sequence"]: - collections = clique.assemble(repre["files"])[0] + collections = clique.assemble(repre["files"], minimum_items=1)[0] full_input_path = os.path.join( src_staging_dir, collections[0].format("{head}{padding}{tail}") From 285bfdf57e25bc4784633fb31caffb1b5962a381 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 17 Jun 2022 16:41:10 +0200 Subject: [PATCH 0705/1227] OP-2448 - revert changes with minimum_review This solution might be too dangerous and have unforeseeable consequences. --- openpype/plugins/publish/extract_review.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index de9e3926bd..b6e5fee1fe 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -762,7 +762,7 @@ class ExtractReview(pyblish.api.InstancePlugin): """ start_frame = int(start_frame) end_frame = int(end_frame) - collections = clique.assemble(files, minimum_items=1)[0] + collections = clique.assemble(files)[0] msg = "Multiple collections {} found.".format(collections) assert len(collections) == 1, msg col = collections[0] @@ -846,7 +846,7 @@ class ExtractReview(pyblish.api.InstancePlugin): dst_staging_dir = new_repre["stagingDir"] if temp_data["input_is_sequence"]: - collections = clique.assemble(repre["files"], minimum_items=1)[0] + collections = clique.assemble(repre["files"])[0] full_input_path = os.path.join( src_staging_dir, collections[0].format("{head}{padding}{tail}") From fc93785ef22f3fea1226a47a37e0a6404e9c61c3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 17 Jun 2022 16:46:14 +0200 Subject: [PATCH 0706/1227] OP-2448 - safer variant of passing collected files Later process doesn't handle well single frame file in a list, it should be always as a regular string. --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 3e8655be7d..ba939d5428 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -135,10 +135,15 @@ class ExtractPlayblast(openpype.api.Extractor): # Add camera node name to representation data camera_node_name = pm.ls(camera)[0].getTransform().name() + collected_files = list(frame_collection) + # single frame file shouldn't be in list, only as a string + if len(collected_files) == 1: + collected_files = collected_files[0] + representation = { 'name': 'png', 'ext': 'png', - 'files': list(frame_collection), + 'files': collected_files, "stagingDir": stagingdir, "frameStart": start, "frameEnd": end, From c368fb587f6bc89668ce07024154768f6de2ef7d Mon Sep 17 00:00:00 2001 From: OpenPype Date: Fri, 17 Jun 2022 15:29:09 +0000 Subject: [PATCH 0707/1227] [Automated] Bump version --- CHANGELOG.md | 33 +++++---------------------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 7 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb71071205..8f9eb04f45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [3.11.0-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.11.0-nightly.4](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...HEAD) @@ -22,11 +22,7 @@ - General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268) - Unreal: add support for skeletalMesh and staticMesh to loaders [\#3267](https://github.com/pypeclub/OpenPype/pull/3267) - Maya: reference loaders could store placeholder in referenced url [\#3264](https://github.com/pypeclub/OpenPype/pull/3264) -- Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) -- TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250) - Nuke: Change default icon path in settings [\#3247](https://github.com/pypeclub/OpenPype/pull/3247) -- Maya: publishing of animation and pointcache on a farm [\#3225](https://github.com/pypeclub/OpenPype/pull/3225) -- Maya: Look assigner UI improvements [\#3208](https://github.com/pypeclub/OpenPype/pull/3208) **🐛 Bug fixes** @@ -43,13 +39,13 @@ - Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) - Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) - General: Fix Oiio tool path resolving [\#3278](https://github.com/pypeclub/OpenPype/pull/3278) +- Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) - Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) - Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) +- Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) - Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240) - Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) - Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) -- TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) -- Flame: debugging [\#3224](https://github.com/pypeclub/OpenPype/pull/3224) **🔀 Refactored code** @@ -60,7 +56,6 @@ - Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) - Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) -- Harmony: message length in 21.1 [\#3258](https://github.com/pypeclub/OpenPype/pull/3258) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -68,46 +63,28 @@ **🚀 Enhancements** -- TVPaint: Init file for TVPaint worker also handle guideline images [\#3251](https://github.com/pypeclub/OpenPype/pull/3251) +- Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) - General: updating common vendor `scriptmenu` to 1.5.2 [\#3246](https://github.com/pypeclub/OpenPype/pull/3246) - Project Manager: Allow to paste Tasks into multiple assets at the same time [\#3226](https://github.com/pypeclub/OpenPype/pull/3226) -- Project manager: Sped up project load [\#3216](https://github.com/pypeclub/OpenPype/pull/3216) **🐛 Bug fixes** -- Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) -- Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) - nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244) - Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) - Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) - Maya: Fix support for multiple resolutions [\#3236](https://github.com/pypeclub/OpenPype/pull/3236) -- Hiero: debugging frame range and other 3.10 [\#3222](https://github.com/pypeclub/OpenPype/pull/3222) -- Project Manager: Fix persistent editors on project change [\#3218](https://github.com/pypeclub/OpenPype/pull/3218) -- Deadline: instance data overwrite fix [\#3214](https://github.com/pypeclub/OpenPype/pull/3214) -- Ftrack: Push hierarchical attributes action works [\#3210](https://github.com/pypeclub/OpenPype/pull/3210) -- Standalone Publisher: Always create new representation for thumbnail [\#3203](https://github.com/pypeclub/OpenPype/pull/3203) -- Photoshop: skip collector when automatic testing [\#3202](https://github.com/pypeclub/OpenPype/pull/3202) +- TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) **Merged pull requests:** - Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) - Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249) -- Maya: added jpg to filter for Image Plane Loader [\#3223](https://github.com/pypeclub/OpenPype/pull/3223) ## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.7...3.9.8) -**🚀 Enhancements** - -- nuke: generate publishing nodes inside render group node [\#3206](https://github.com/pypeclub/OpenPype/pull/3206) -- Loader UI: Speed issues of loader with sync server [\#3200](https://github.com/pypeclub/OpenPype/pull/3200) - -**🐛 Bug fixes** - -- Standalone Publisher: Always create new representation for thumbnail [\#3204](https://github.com/pypeclub/OpenPype/pull/3204) - ## [3.9.7](https://github.com/pypeclub/OpenPype/tree/3.9.7) (2022-05-11) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.6...3.9.7) diff --git a/openpype/version.py b/openpype/version.py index 2f4d180983..4c28b4a369 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.0-nightly.3" +__version__ = "3.11.0-nightly.4" diff --git a/pyproject.toml b/pyproject.toml index e1b5c37289..99935b3d70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.0-nightly.3" # OpenPype +version = "3.11.0-nightly.4" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 961c7b6c511765684696c6b5292e85193e24f31e Mon Sep 17 00:00:00 2001 From: OpenPype Date: Fri, 17 Jun 2022 15:39:35 +0000 Subject: [PATCH 0708/1227] [Automated] Release --- CHANGELOG.md | 10 ++++++---- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f9eb04f45..686b34177c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [3.11.0-nightly.4](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.11.0](https://github.com/pypeclub/OpenPype/tree/3.11.0) (2022-06-17) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...HEAD) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...3.11.0) ### 📖 Documentation @@ -22,6 +22,7 @@ - General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268) - Unreal: add support for skeletalMesh and staticMesh to loaders [\#3267](https://github.com/pypeclub/OpenPype/pull/3267) - Maya: reference loaders could store placeholder in referenced url [\#3264](https://github.com/pypeclub/OpenPype/pull/3264) +- TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250) - Nuke: Change default icon path in settings [\#3247](https://github.com/pypeclub/OpenPype/pull/3247) **🐛 Bug fixes** @@ -39,10 +40,12 @@ - Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) - Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) - General: Fix Oiio tool path resolving [\#3278](https://github.com/pypeclub/OpenPype/pull/3278) +- Hiero: add support for task tags [\#3277](https://github.com/pypeclub/OpenPype/pull/3277) - Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) - Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) - Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) - Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) +- nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240) - Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) - Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) @@ -56,6 +59,7 @@ - Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) - Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) +- Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -69,7 +73,6 @@ **🐛 Bug fixes** -- nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244) - Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) - Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) @@ -78,7 +81,6 @@ **Merged pull requests:** -- Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) - Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249) ## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19) diff --git a/openpype/version.py b/openpype/version.py index 4c28b4a369..41e61b0500 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.0-nightly.4" +__version__ = "3.11.0" diff --git a/pyproject.toml b/pyproject.toml index 99935b3d70..2700704530 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.0-nightly.4" # OpenPype +version = "3.11.0" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From d1b0c37c93c5a8c8dfd105d6cae8bc61e074be15 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 17 Jun 2022 18:48:08 +0300 Subject: [PATCH 0709/1227] Add resolution overrides to creator. --- openpype/hosts/maya/plugins/create/create_review.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index fbf3399f61..331f0818eb 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -15,6 +15,8 @@ class CreateReview(plugin.Creator): keepImages = False isolate = False imagePlane = True + resolutionWidth = 0 + resolutionHeight = 0 transparency = [ "preset", "simple", @@ -33,6 +35,8 @@ class CreateReview(plugin.Creator): for key, value in animation_data.items(): data[key] = value + data["resolutionWidth"] = self.resolutionWidth + data["resolutionHeight"] = self.resolutionHeight data["isolate"] = self.isolate data["keepImages"] = self.keepImages data["imagePlane"] = self.imagePlane From eb3afe63c53df0969b2717a353f8320c9d69aa0a Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 17 Jun 2022 18:48:23 +0300 Subject: [PATCH 0710/1227] Add resolution overrides to collector. --- openpype/hosts/maya/plugins/publish/collect_review.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index e9e0d74c03..ded549a849 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -70,6 +70,8 @@ class CollectReview(pyblish.api.InstancePlugin): data['handles'] = instance.data.get('handles', None) data['step'] = instance.data['step'] data['fps'] = instance.data['fps'] + data['resolutionWidth'] = instance.data['resolutionWidth'] + data['resolutionHeight'] = instance.data['resolutionHeight'] data["isolate"] = instance.data["isolate"] cmds.setAttr(str(instance) + '.active', 1) self.log.debug('data {}'.format(instance.context[i].data)) From 491e356bc654352f11feb4609f97071e4a651645 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 17 Jun 2022 18:48:39 +0300 Subject: [PATCH 0711/1227] Correct resolution overrides in extractor --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 9e1970780b..9e6363502e 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -54,15 +54,17 @@ class ExtractPlayblast(openpype.api.Extractor): capture_presets = self.capture_preset width_preset = capture_presets["Resolution"]["width"] height_preset = capture_presets["Resolution"]["height"] + instance_width = instance.data.get("resolutionWidth") + instance_height = instance.data.get("resolutionHeight") preset['camera'] = camera - if width_preset == 0: + if instance_width != 0: preset['width'] = instance.data.get("resolutionWidth") else: preset["width"] = width_preset - if height_preset == 0: - preset['width'] = instance.data.get("resolutionHeight") + if instance_height != 0: + preset['height'] = instance.data.get("resolutionHeight") else: preset['height'] = height_preset From 594707f8340ea3b9cbc9ac4df6193a1e3dc95a76 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Sat, 18 Jun 2022 00:34:25 +0300 Subject: [PATCH 0712/1227] Add comments --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 9e6363502e..32cbeed81b 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -50,14 +50,20 @@ class ExtractPlayblast(openpype.api.Extractor): ['override_viewport_options'] ) preset = lib.load_capture_preset(data=self.capture_preset) - + # Grab capture presets from the project settings capture_presets = self.capture_preset + # Set resolution variables from capture presets width_preset = capture_presets["Resolution"]["width"] height_preset = capture_presets["Resolution"]["height"] + # Set resolution variables from instance values instance_width = instance.data.get("resolutionWidth") instance_height = instance.data.get("resolutionHeight") preset['camera'] = camera + # Tests if instance resolution width is set, + # if it is a value other than zero, that value is + # used, if not then the project settings resolution is + # used if instance_width != 0: preset['width'] = instance.data.get("resolutionWidth") else: From 44a46382470e4e79b9e679e9395f94dacb556a70 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 18 Jun 2022 03:45:44 +0000 Subject: [PATCH 0713/1227] [Automated] Bump version --- CHANGELOG.md | 44 ++++++++++++++++++++++++++++++++++++++------ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 686b34177c..553102aecf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,43 @@ # Changelog +## [3.11.1-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.0...HEAD) + +**🆕 New features** + +- Flame: custom export temp folder [\#3346](https://github.com/pypeclub/OpenPype/pull/3346) +- Nuke: removing third-party plugins [\#3344](https://github.com/pypeclub/OpenPype/pull/3344) + +**🚀 Enhancements** + +- Pyblish Pype: Hiding/Close issues [\#3367](https://github.com/pypeclub/OpenPype/pull/3367) +- Ftrack: Removed requirement of pypeclub role from default settings [\#3354](https://github.com/pypeclub/OpenPype/pull/3354) +- Kitsu: Prevent crash on missing frames information [\#3352](https://github.com/pypeclub/OpenPype/pull/3352) +- Ftrack: Open browser from tray [\#3320](https://github.com/pypeclub/OpenPype/pull/3320) +- Enhancement: More control over thumbnail processing. [\#3259](https://github.com/pypeclub/OpenPype/pull/3259) + +**🐛 Bug fixes** + +- Nuke: bake streams with slate on farm [\#3368](https://github.com/pypeclub/OpenPype/pull/3368) +- Harmony: audio validator has wrong logic [\#3364](https://github.com/pypeclub/OpenPype/pull/3364) +- Nuke: Fix missing variable in extract thumbnail [\#3363](https://github.com/pypeclub/OpenPype/pull/3363) +- Nuke: Fix precollect writes [\#3361](https://github.com/pypeclub/OpenPype/pull/3361) +- AE- fix validate\_scene\_settings and renderLocal [\#3358](https://github.com/pypeclub/OpenPype/pull/3358) +- deadline: fixing misidentification of revieables [\#3356](https://github.com/pypeclub/OpenPype/pull/3356) +- General: Create only one thumbnail per instance [\#3351](https://github.com/pypeclub/OpenPype/pull/3351) +- nuke: adding extract thumbnail settings 3.10 [\#3347](https://github.com/pypeclub/OpenPype/pull/3347) +- General: Fix last version function [\#3345](https://github.com/pypeclub/OpenPype/pull/3345) +- Deadline: added OPENPYPE\_MONGO to filter [\#3336](https://github.com/pypeclub/OpenPype/pull/3336) +- Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) + +**🔀 Refactored code** + +- Webpublisher: Use client query functions [\#3333](https://github.com/pypeclub/OpenPype/pull/3333) + ## [3.11.0](https://github.com/pypeclub/OpenPype/tree/3.11.0) (2022-06-17) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...3.11.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.11.0-nightly.4...3.11.0) ### 📖 Documentation @@ -40,15 +75,12 @@ - Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) - Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) - General: Fix Oiio tool path resolving [\#3278](https://github.com/pypeclub/OpenPype/pull/3278) -- Hiero: add support for task tags [\#3277](https://github.com/pypeclub/OpenPype/pull/3277) - Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) - Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) - Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) - Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) -- nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240) - Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) -- Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) **🔀 Refactored code** @@ -59,7 +91,6 @@ - Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) - Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) -- Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -69,10 +100,10 @@ - Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) - General: updating common vendor `scriptmenu` to 1.5.2 [\#3246](https://github.com/pypeclub/OpenPype/pull/3246) -- Project Manager: Allow to paste Tasks into multiple assets at the same time [\#3226](https://github.com/pypeclub/OpenPype/pull/3226) **🐛 Bug fixes** +- nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244) - Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) - Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) @@ -81,6 +112,7 @@ **Merged pull requests:** +- Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) - Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249) ## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19) diff --git a/openpype/version.py b/openpype/version.py index 41e61b0500..8e44b02ffc 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.0" +__version__ = "3.11.1-nightly.1" diff --git a/pyproject.toml b/pyproject.toml index 2700704530..8ba829a1c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.0" # OpenPype +version = "3.11.1-nightly.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From da1fb174bca44ec40ce00ea51b034d79bcebcca9 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Mon, 20 Jun 2022 08:48:15 +0000 Subject: [PATCH 0714/1227] [Automated] Release --- CHANGELOG.md | 7 +++---- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 553102aecf..48d0d8181e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [3.11.1-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.11.1](https://github.com/pypeclub/OpenPype/tree/3.11.1) (2022-06-20) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.0...HEAD) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.0...3.11.1) **🆕 New features** @@ -26,7 +26,6 @@ - AE- fix validate\_scene\_settings and renderLocal [\#3358](https://github.com/pypeclub/OpenPype/pull/3358) - deadline: fixing misidentification of revieables [\#3356](https://github.com/pypeclub/OpenPype/pull/3356) - General: Create only one thumbnail per instance [\#3351](https://github.com/pypeclub/OpenPype/pull/3351) -- nuke: adding extract thumbnail settings 3.10 [\#3347](https://github.com/pypeclub/OpenPype/pull/3347) - General: Fix last version function [\#3345](https://github.com/pypeclub/OpenPype/pull/3345) - Deadline: added OPENPYPE\_MONGO to filter [\#3336](https://github.com/pypeclub/OpenPype/pull/3336) - Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) @@ -91,6 +90,7 @@ - Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) - Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) +- Harmony: 21.1 fix [\#3248](https://github.com/pypeclub/OpenPype/pull/3248) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -108,7 +108,6 @@ - Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) - Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) - Maya: Fix support for multiple resolutions [\#3236](https://github.com/pypeclub/OpenPype/pull/3236) -- TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) **Merged pull requests:** diff --git a/openpype/version.py b/openpype/version.py index 8e44b02ffc..7bf368108a 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.1-nightly.1" +__version__ = "3.11.1" diff --git a/pyproject.toml b/pyproject.toml index 8ba829a1c9..ae89e7d9d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.1-nightly.1" # OpenPype +version = "3.11.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 8b1576aed2b74dd5e01842d2a87bc04c77f755f6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 11:22:05 +0200 Subject: [PATCH 0715/1227] remove unused line --- openpype/pipeline/context_tools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index 3e63eeba27..4a147c230b 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -88,7 +88,6 @@ def install_host(host): avalon host-interface. """ global _is_installed - global _modules_manager _is_installed = True From c762fa92f6ab7f692c751fefdffe5f9c9571d582 Mon Sep 17 00:00:00 2001 From: macman Date: Mon, 20 Jun 2022 13:39:52 +0300 Subject: [PATCH 0716/1227] Handle excluding `model` family from frame range validator. --- openpype/hosts/maya/plugins/publish/validate_frame_range.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_frame_range.py b/openpype/hosts/maya/plugins/publish/validate_frame_range.py index 98b5b4d79b..4415815d32 100644 --- a/openpype/hosts/maya/plugins/publish/validate_frame_range.py +++ b/openpype/hosts/maya/plugins/publish/validate_frame_range.py @@ -27,6 +27,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): "yeticache"] optional = True actions = [openpype.api.RepairAction] + exclude_families = ["model"] def process(self, instance): context = instance.context @@ -56,7 +57,9 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): # compare with data on instance errors = [] - + if [ef for ef in self.exclude_families + if instance.data["family"] in ef]: + return if(inst_start != frame_start_handle): errors.append("Instance start frame [ {} ] doesn't " "match the one set on instance [ {} ]: " From 3a1d9c9fcadab29f3b2962527dedea4da49abd4a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 20 Jun 2022 13:11:01 +0200 Subject: [PATCH 0717/1227] Added far future value for null values for dates Null values were sorted as last, this keeps queued items together with last synched. --- openpype/modules/sync_server/tray/models.py | 60 +++++++++++++-------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index c49edeafb9..6d1e85c17a 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -1,6 +1,7 @@ import os import attr from bson.objectid import ObjectId +import datetime from Qt import QtCore from Qt.QtCore import Qt @@ -413,6 +414,23 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): return index return None + def _convert_date(self, date_value, current_date): + """Converts 'date_value' to string. + + Value of date_value might contain date in the future, used for nicely + sort queued items next to last downloaded. + """ + try: + converted_date = None + # ignore date in the future - for sorting only + if date_value and date_value < current_date: + converted_date = date_value.strftime("%Y%m%dT%H%M%SZ") + except (AttributeError, TypeError): + # ignore unparseable values + pass + + return converted_date + class SyncRepresentationSummaryModel(_SyncRepresentationModel): """ @@ -560,7 +578,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): remote_provider = lib.translate_provider_for_icon(self.sync_server, self.project, remote_site) - + current_date = datetime.datetime.now() for repre in result.get("paginatedResults"): files = repre.get("files", []) if isinstance(files, dict): # aggregate returns dictionary @@ -570,14 +588,10 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): if not files: continue - local_updated = remote_updated = None - if repre.get('updated_dt_local'): - local_updated = \ - repre.get('updated_dt_local').strftime("%Y%m%dT%H%M%SZ") - - if repre.get('updated_dt_remote'): - remote_updated = \ - repre.get('updated_dt_remote').strftime("%Y%m%dT%H%M%SZ") + local_updated = self._convert_date(repre.get('updated_dt_local'), + current_date) + remote_updated = self._convert_date(repre.get('updated_dt_remote'), + current_date) avg_progress_remote = lib.convert_progress( repre.get('avg_progress_remote', '0')) @@ -645,6 +659,8 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): if limit == 0: limit = SyncRepresentationSummaryModel.PAGE_SIZE + # replace null with value in the future for better sorting + dummy_max_date = datetime.datetime(2099, 1, 1) aggr = [ {"$match": self.get_match_part()}, {'$unwind': '$files'}, @@ -687,7 +703,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): {'$cond': [ {'$size': "$order_remote.last_failed_dt"}, "$order_remote.last_failed_dt", - [] + [dummy_max_date] ]} ]}}, 'updated_dt_local': {'$first': { @@ -696,7 +712,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): {'$cond': [ {'$size': "$order_local.last_failed_dt"}, "$order_local.last_failed_dt", - [] + [dummy_max_date] ]} ]}}, 'files_size': {'$ifNull': ["$files.size", 0]}, @@ -1039,6 +1055,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): self.project, remote_site) + current_date = datetime.datetime.now() for repre in result.get("paginatedResults"): # log.info("!!! repre:: {}".format(repre)) files = repre.get("files", []) @@ -1046,16 +1063,12 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): files = [files] for file in files: - local_updated = remote_updated = None - if repre.get('updated_dt_local'): - local_updated = \ - repre.get('updated_dt_local').strftime( - "%Y%m%dT%H%M%SZ") - - if repre.get('updated_dt_remote'): - remote_updated = \ - repre.get('updated_dt_remote').strftime( - "%Y%m%dT%H%M%SZ") + local_updated = self._convert_date( + repre.get('updated_dt_local'), + current_date) + remote_updated = self._convert_date( + repre.get('updated_dt_remote'), + current_date) remote_progress = lib.convert_progress( repre.get('progress_remote', '0')) @@ -1104,6 +1117,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): if limit == 0: limit = SyncRepresentationSummaryModel.PAGE_SIZE + dummy_max_date = datetime.datetime(2099, 1, 1) aggr = [ {"$match": self.get_match_part()}, {"$unwind": "$files"}, @@ -1147,7 +1161,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): '$cond': [ {'$size': "$order_remote.last_failed_dt"}, "$order_remote.last_failed_dt", - [] + [dummy_max_date] ] } ] @@ -1160,7 +1174,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): '$cond': [ {'$size': "$order_local.last_failed_dt"}, "$order_local.last_failed_dt", - [] + [dummy_max_date] ] } ] From 20bc7300b1946e5ef082b4a43c922e108a46cabb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 13:14:16 +0200 Subject: [PATCH 0718/1227] fix delivery action --- .../modules/ftrack/event_handlers_user/action_delivery.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_delivery.py b/openpype/modules/ftrack/event_handlers_user/action_delivery.py index 47f2853820..4b799b092b 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delivery.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delivery.py @@ -542,7 +542,9 @@ class Delivery(BaseAction): os.makedirs(location_path) self.log.debug("Collecting representations to process.") - version_ids = self._get_interest_version_ids(session, entities) + version_ids = self._get_interest_version_ids( + project_name, session, entities + ) repres_to_deliver = list(get_representations( project_name, representation_names=repre_names, From 665fab4e35b76b2d9cb37195fb203ff3203c1ef5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 13:15:32 +0200 Subject: [PATCH 0719/1227] fix delete asset action to filter assets right way --- .../modules/ftrack/event_handlers_user/action_delete_asset.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py index 6dae3a4ca1..03d029b0c1 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py @@ -134,7 +134,8 @@ class DeleteAssetSubset(BaseAction): ftrack_id = asset_doc["data"].get("ftrackId") if ftrack_id: found_ftrack_ids.add(ftrack_id) - selected_av_entities.append(asset_doc) + if ftrack_id in entity_mapping: + selected_av_entities.append(asset_doc) asset_name = asset_doc["name"] asset_docs_by_name[asset_name].append(asset_doc) From d49c2bac223f77b20710e029c6edb9caf1577d20 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 13:42:04 +0200 Subject: [PATCH 0720/1227] reverted poetry.lock changes --- poetry.lock | 197 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 114 insertions(+), 83 deletions(-) diff --git a/poetry.lock b/poetry.lock index 010dd57e17..f6ccf1ffc9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -161,11 +161,11 @@ toml = "*" [[package]] name = "babel" -version = "2.10.1" +version = "2.9.1" description = "Internationalization utilities" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pytz = ">=2015.7" @@ -671,14 +671,14 @@ testing = ["colorama", "docopt", "pytest (>=3.1.0)"] [[package]] name = "jeepney" -version = "0.8.0" +version = "0.7.1" description = "Low-level, pure Python DBus protocol wrapper." category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [package.extras] -test = ["pytest", "pytest-trio", "pytest-asyncio (>=0.17)", "testpath", "trio", "async-timeout"] +test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio", "async-timeout"] trio = ["trio", "async-generator"] [[package]] @@ -756,11 +756,11 @@ pymongo = "*" [[package]] name = "markupsafe" -version = "2.1.1" +version = "2.0.1" description = "Safely add untrusted strings to HTML/XML markup." category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [[package]] name = "mccabe" @@ -909,11 +909,11 @@ python-versions = "*" [[package]] name = "protobuf" -version = "3.20.1" +version = "3.19.4" description = "Protocol Buffers" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.5" [[package]] name = "py" @@ -1617,11 +1617,11 @@ python-versions = ">=3.6" [[package]] name = "typing-extensions" -version = "4.2.0" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.0.1" +description = "Backported and Experimental Type Hints for Python 3.6+" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [[package]] name = "uritemplate" @@ -1716,7 +1716,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "3.7.*" -content-hash = "f7150d3d8098c26a004db9850ed328efe79695e77e830721a3e04c5941850cc5" +content-hash = "bd8e0a03668c380c6e76c8cd6c71020692f4ea9f32de7a4f09433564faa9dad0" [metadata.files] acre = [] @@ -1843,8 +1843,8 @@ autopep8 = [ {file = "autopep8-1.5.7.tar.gz", hash = "sha256:276ced7e9e3cb22e5d7c14748384a5cf5d9002257c0ed50c0e075b68011bb6d0"}, ] babel = [ - {file = "Babel-2.10.1-py3-none-any.whl", hash = "sha256:3f349e85ad3154559ac4930c3918247d319f21910d5ce4b25d439ed8693b98d2"}, - {file = "Babel-2.10.1.tar.gz", hash = "sha256:98aeaca086133efb3e1e2aad0396987490c8425929ddbcfe0550184fdc54cd13"}, + {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, + {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] bcrypt = [ {file = "bcrypt-3.2.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e"}, @@ -2202,8 +2202,8 @@ jedi = [ {file = "jedi-0.13.3.tar.gz", hash = "sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b"}, ] jeepney = [ - {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, - {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, + {file = "jeepney-0.7.1-py3-none-any.whl", hash = "sha256:1b5a0ea5c0e7b166b2f5895b91a08c14de8915afda4407fb5022a195224958ac"}, + {file = "jeepney-0.7.1.tar.gz", hash = "sha256:fa9e232dfa0c498bd0b8a3a73b8d8a31978304dcef0515adc859d4e096f96f4f"}, ] jinja2 = [ {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, @@ -2264,46 +2264,75 @@ log4mongo = [ {file = "log4mongo-1.7.0.tar.gz", hash = "sha256:dc374617206162a0b14167fbb5feac01dbef587539a235dadba6200362984a68"}, ] markupsafe = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, + {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -2444,30 +2473,32 @@ prefixed = [ {file = "prefixed-0.3.2.tar.gz", hash = "sha256:ca48277ba5fa8346dd4b760847da930c7b84416387c39e93affef086add2c029"}, ] protobuf = [ - {file = "protobuf-3.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3cc797c9d15d7689ed507b165cd05913acb992d78b379f6014e013f9ecb20996"}, - {file = "protobuf-3.20.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:ff8d8fa42675249bb456f5db06c00de6c2f4c27a065955917b28c4f15978b9c3"}, - {file = "protobuf-3.20.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd68be2559e2a3b84f517fb029ee611546f7812b1fdd0aa2ecc9bc6ec0e4fdde"}, - {file = "protobuf-3.20.1-cp310-cp310-win32.whl", hash = "sha256:9016d01c91e8e625141d24ec1b20fed584703e527d28512aa8c8707f105a683c"}, - {file = "protobuf-3.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:32ca378605b41fd180dfe4e14d3226386d8d1b002ab31c969c366549e66a2bb7"}, - {file = "protobuf-3.20.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9be73ad47579abc26c12024239d3540e6b765182a91dbc88e23658ab71767153"}, - {file = "protobuf-3.20.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:097c5d8a9808302fb0da7e20edf0b8d4703274d140fd25c5edabddcde43e081f"}, - {file = "protobuf-3.20.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e250a42f15bf9d5b09fe1b293bdba2801cd520a9f5ea2d7fb7536d4441811d20"}, - {file = "protobuf-3.20.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cdee09140e1cd184ba9324ec1df410e7147242b94b5f8b0c64fc89e38a8ba531"}, - {file = "protobuf-3.20.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:af0ebadc74e281a517141daad9d0f2c5d93ab78e9d455113719a45a49da9db4e"}, - {file = "protobuf-3.20.1-cp37-cp37m-win32.whl", hash = "sha256:755f3aee41354ae395e104d62119cb223339a8f3276a0cd009ffabfcdd46bb0c"}, - {file = "protobuf-3.20.1-cp37-cp37m-win_amd64.whl", hash = "sha256:62f1b5c4cd6c5402b4e2d63804ba49a327e0c386c99b1675c8a0fefda23b2067"}, - {file = "protobuf-3.20.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf"}, - {file = "protobuf-3.20.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:cb29edb9eab15742d791e1025dd7b6a8f6fcb53802ad2f6e3adcb102051063ab"}, - {file = "protobuf-3.20.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:69ccfdf3657ba59569c64295b7d51325f91af586f8d5793b734260dfe2e94e2c"}, - {file = "protobuf-3.20.1-cp38-cp38-win32.whl", hash = "sha256:dd5789b2948ca702c17027c84c2accb552fc30f4622a98ab5c51fcfe8c50d3e7"}, - {file = "protobuf-3.20.1-cp38-cp38-win_amd64.whl", hash = "sha256:77053d28427a29987ca9caf7b72ccafee011257561259faba8dd308fda9a8739"}, - {file = "protobuf-3.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f50601512a3d23625d8a85b1638d914a0970f17920ff39cec63aaef80a93fb7"}, - {file = "protobuf-3.20.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:284f86a6207c897542d7e956eb243a36bb8f9564c1742b253462386e96c6b78f"}, - {file = "protobuf-3.20.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7403941f6d0992d40161aa8bb23e12575637008a5a02283a930addc0508982f9"}, - {file = "protobuf-3.20.1-cp39-cp39-win32.whl", hash = "sha256:db977c4ca738dd9ce508557d4fce0f5aebd105e158c725beec86feb1f6bc20d8"}, - {file = "protobuf-3.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:7e371f10abe57cee5021797126c93479f59fccc9693dafd6bd5633ab67808a91"}, - {file = "protobuf-3.20.1-py2.py3-none-any.whl", hash = "sha256:adfc6cf69c7f8c50fd24c793964eef18f0ac321315439d94945820612849c388"}, - {file = "protobuf-3.20.1.tar.gz", hash = "sha256:adc31566d027f45efe3f44eeb5b1f329da43891634d61c75a5944e9be6dd42c9"}, + {file = "protobuf-3.19.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f51d5a9f137f7a2cec2d326a74b6e3fc79d635d69ffe1b036d39fc7d75430d37"}, + {file = "protobuf-3.19.4-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:09297b7972da685ce269ec52af761743714996b4381c085205914c41fcab59fb"}, + {file = "protobuf-3.19.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:072fbc78d705d3edc7ccac58a62c4c8e0cec856987da7df8aca86e647be4e35c"}, + {file = "protobuf-3.19.4-cp310-cp310-win32.whl", hash = "sha256:7bb03bc2873a2842e5ebb4801f5c7ff1bfbdf426f85d0172f7644fcda0671ae0"}, + {file = "protobuf-3.19.4-cp310-cp310-win_amd64.whl", hash = "sha256:f358aa33e03b7a84e0d91270a4d4d8f5df6921abe99a377828839e8ed0c04e07"}, + {file = "protobuf-3.19.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1c91ef4110fdd2c590effb5dca8fdbdcb3bf563eece99287019c4204f53d81a4"}, + {file = "protobuf-3.19.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c438268eebb8cf039552897d78f402d734a404f1360592fef55297285f7f953f"}, + {file = "protobuf-3.19.4-cp36-cp36m-win32.whl", hash = "sha256:835a9c949dc193953c319603b2961c5c8f4327957fe23d914ca80d982665e8ee"}, + {file = "protobuf-3.19.4-cp36-cp36m-win_amd64.whl", hash = "sha256:4276cdec4447bd5015453e41bdc0c0c1234eda08420b7c9a18b8d647add51e4b"}, + {file = "protobuf-3.19.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6cbc312be5e71869d9d5ea25147cdf652a6781cf4d906497ca7690b7b9b5df13"}, + {file = "protobuf-3.19.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:54a1473077f3b616779ce31f477351a45b4fef8c9fd7892d6d87e287a38df368"}, + {file = "protobuf-3.19.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:435bb78b37fc386f9275a7035fe4fb1364484e38980d0dd91bc834a02c5ec909"}, + {file = "protobuf-3.19.4-cp37-cp37m-win32.whl", hash = "sha256:16f519de1313f1b7139ad70772e7db515b1420d208cb16c6d7858ea989fc64a9"}, + {file = "protobuf-3.19.4-cp37-cp37m-win_amd64.whl", hash = "sha256:cdc076c03381f5c1d9bb1abdcc5503d9ca8b53cf0a9d31a9f6754ec9e6c8af0f"}, + {file = "protobuf-3.19.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:69da7d39e39942bd52848438462674c463e23963a1fdaa84d88df7fbd7e749b2"}, + {file = "protobuf-3.19.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:48ed3877fa43e22bcacc852ca76d4775741f9709dd9575881a373bd3e85e54b2"}, + {file = "protobuf-3.19.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd95d1dfb9c4f4563e6093a9aa19d9c186bf98fa54da5252531cc0d3a07977e7"}, + {file = "protobuf-3.19.4-cp38-cp38-win32.whl", hash = "sha256:b38057450a0c566cbd04890a40edf916db890f2818e8682221611d78dc32ae26"}, + {file = "protobuf-3.19.4-cp38-cp38-win_amd64.whl", hash = "sha256:7ca7da9c339ca8890d66958f5462beabd611eca6c958691a8fe6eccbd1eb0c6e"}, + {file = "protobuf-3.19.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:36cecbabbda242915529b8ff364f2263cd4de7c46bbe361418b5ed859677ba58"}, + {file = "protobuf-3.19.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c1068287025f8ea025103e37d62ffd63fec8e9e636246b89c341aeda8a67c934"}, + {file = "protobuf-3.19.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96bd766831596d6014ca88d86dc8fe0fb2e428c0b02432fd9db3943202bf8c5e"}, + {file = "protobuf-3.19.4-cp39-cp39-win32.whl", hash = "sha256:84123274d982b9e248a143dadd1b9815049f4477dc783bf84efe6250eb4b836a"}, + {file = "protobuf-3.19.4-cp39-cp39-win_amd64.whl", hash = "sha256:3112b58aac3bac9c8be2b60a9daf6b558ca3f7681c130dcdd788ade7c9ffbdca"}, + {file = "protobuf-3.19.4-py2.py3-none-any.whl", hash = "sha256:8961c3a78ebfcd000920c9060a262f082f29838682b1f7201889300c1fbe0616"}, + {file = "protobuf-3.19.4.tar.gz", hash = "sha256:9df0c10adf3e83015ced42a9a7bd64e13d06c4cf45c340d2c63020ea04499d0a"}, ] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, @@ -2891,8 +2922,8 @@ typed-ast = [ {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] typing-extensions = [ - {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, - {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, + {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, + {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, ] uritemplate = [ {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"}, From b4c6cb414cbd9118c4c8d8a6527c14940f11b771 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Fri, 10 Jun 2022 18:25:40 +0200 Subject: [PATCH 0721/1227] remove unused import --- openpype/modules/shotgrid/shotgrid_module.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index dcc8187194..5644f0c35f 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -1,5 +1,4 @@ import os -import threading from openpype_interfaces import ( ITrayModule, From 151bf297c7b72788a6ff8a8f87d98c73b67d1aaf Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 13 Jun 2022 14:50:26 +0200 Subject: [PATCH 0722/1227] Remove unused code and add sugestions --- .../shotgrid/hooks/post_shotgrid_changes.py | 9 -------- openpype/modules/shotgrid/lib/settings.py | 23 ------------------- .../publish/collect_shotgrid_entities.py | 5 ++-- 3 files changed, 2 insertions(+), 35 deletions(-) delete mode 100644 openpype/modules/shotgrid/hooks/post_shotgrid_changes.py diff --git a/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py b/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py deleted file mode 100644 index e8369ad3cb..0000000000 --- a/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py +++ /dev/null @@ -1,9 +0,0 @@ -from openpype.lib import PostLaunchHook - - -class PostShotgridHook(PostLaunchHook): - order = None - - def execute(self, *args, **kwargs): - print(args, kwargs) - pass diff --git a/openpype/modules/shotgrid/lib/settings.py b/openpype/modules/shotgrid/lib/settings.py index 4a772de5b7..924099f04b 100644 --- a/openpype/modules/shotgrid/lib/settings.py +++ b/openpype/modules/shotgrid/lib/settings.py @@ -1,24 +1,11 @@ -import os - -from pymongo import MongoClient from openpype.api import get_system_settings, get_project_settings from openpype.modules.shotgrid.lib.const import MODULE_NAME -from openpype.modules.shotgrid.lib.tools import memoize -def get_project_list(): - mongo_url = os.getenv("OPENPYPE_MONGO") - client = MongoClient(mongo_url) - db = client['avalon'] - return db.list_collection_names() - - -@memoize def get_shotgrid_project_settings(project): return get_project_settings(project).get(MODULE_NAME, {}) -@memoize def get_shotgrid_settings(): return get_system_settings().get("modules", {}).get(MODULE_NAME, {}) @@ -29,13 +16,3 @@ def get_shotgrid_servers(): def get_leecher_backend_url(): return get_shotgrid_settings().get("leecher_backend_url") - - -def filter_projects_by_login(): - return bool(get_shotgrid_settings().get("filter_projects_by_login", False)) - - -def get_shotgrid_event_mongo_info(): - database_name = os.environ["OPENPYPE_DATABASE_NAME"] - collection_name = "shotgrid_events" - return database_name, collection_name diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py index a770c1eb87..9880425a41 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py @@ -1,7 +1,7 @@ import os import pyblish.api -from pymongo import MongoClient +from openpype.lib.mongo import OpenPypeMongoConnection from openpype.modules.shotgrid.lib.settings import ( get_shotgrid_project_settings, @@ -63,8 +63,7 @@ class CollectShotgridEntities(pyblish.api.ContextPlugin): def _get_shotgrid_collection(project): - mongo_url = os.getenv("OPENPYPE_MONGO") - client = MongoClient(mongo_url) + client = OpenPypeMongoConnection.get_mongo_client() return client.get_database("shotgrid_openpype").get_collection(project) From ad0aea007a7604a02430c5a55366e458802b853e Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 20 Jun 2022 13:49:21 +0200 Subject: [PATCH 0723/1227] remove unused method --- openpype/modules/shotgrid/lib/server.py | 26 - openpype/modules/shotgrid/lib/tools.py | 16 - poetry.lock | 1009 ++++++++++------------- 3 files changed, 453 insertions(+), 598 deletions(-) delete mode 100644 openpype/modules/shotgrid/lib/server.py delete mode 100644 openpype/modules/shotgrid/lib/tools.py diff --git a/openpype/modules/shotgrid/lib/server.py b/openpype/modules/shotgrid/lib/server.py deleted file mode 100644 index 50645d4089..0000000000 --- a/openpype/modules/shotgrid/lib/server.py +++ /dev/null @@ -1,26 +0,0 @@ -import traceback - -import requests - -from openpype.api import Logger -from openpype.modules.shotgrid.lib import ( - settings as settings_lib, -) - -_LOG = Logger().get_logger("ShotgridModule.server") - - -def find_linked_projects(email): - url = "".join( - [ - settings_lib.get_leecher_backend_url(), - "/user/", - email, - "/project-user-links", - ] - ) - try: - return requests.get(url).json() - except requests.exceptions.RequestException as e: - _LOG.error(e) - traceback.print_stack() diff --git a/openpype/modules/shotgrid/lib/tools.py b/openpype/modules/shotgrid/lib/tools.py deleted file mode 100644 index 6305e9ca50..0000000000 --- a/openpype/modules/shotgrid/lib/tools.py +++ /dev/null @@ -1,16 +0,0 @@ -from functools import wraps - - -def memoize(function): - memo = {} - - @wraps(function) - def wrapper(*args): - try: - return memo[args] - except KeyError: - rv = function(*args) - memo[args] = rv - return rv - - return wrapper diff --git a/poetry.lock b/poetry.lock index 04be8c78f2..9ae486d59a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -94,7 +94,7 @@ python-dateutil = ">=2.7.0" [[package]] name = "astroid" -version = "2.11.5" +version = "2.9.3" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -104,7 +104,7 @@ python-versions = ">=3.6.2" lazy-object-proxy = ">=1.4.0" typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} -wrapt = ">=1.11,<2" +wrapt = ">=1.11,<1.14" [[package]] name = "async-timeout" @@ -172,7 +172,7 @@ pytz = ">=2015.7" [[package]] name = "bcrypt" -version = "3.2.2" +version = "3.2.0" description = "Modern password hashing for your software and your servers" category = "main" optional = false @@ -180,6 +180,7 @@ python-versions = ">=3.6" [package.dependencies] cffi = ">=1.1" +six = ">=1.4.1" [package.extras] tests = ["pytest (>=3.2.1,!=3.3.0)"] @@ -200,7 +201,7 @@ wcwidth = ">=0.1.4" [[package]] name = "cachetools" -version = "5.2.0" +version = "5.0.0" description = "Extensible memoizing collections and decorators" category = "main" optional = false @@ -208,11 +209,11 @@ python-versions = "~=3.7" [[package]] name = "certifi" -version = "2022.5.18.1" +version = "2021.10.8" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false -python-versions = ">=3.6" +python-versions = "*" [[package]] name = "cffi" @@ -225,9 +226,17 @@ python-versions = "*" [package.dependencies] pycparser = "*" +[[package]] +name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + [[package]] name = "charset-normalizer" -version = "2.0.12" +version = "2.0.11" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -286,21 +295,21 @@ python-versions = "*" [[package]] name = "coverage" -version = "6.4.1" +version = "6.3.1" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} +tomli = {version = "*", optional = true, markers = "extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "cryptography" -version = "37.0.2" +version = "36.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false @@ -315,7 +324,7 @@ docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] sdist = ["setuptools_rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] name = "cx-freeze" @@ -337,34 +346,9 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "deprecated" -version = "1.2.13" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -wrapt = ">=1.10,<2" - -[package.extras] -dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] - -[[package]] -name = "dill" -version = "0.3.5.1" -description = "serialize all of python" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" - -[package.extras] -graph = ["objgraph (>=1.7.2)"] - [[package]] name = "dnspython" -version = "2.2.1" +version = "2.2.0" description = "DNS toolkit" category = "main" optional = false @@ -380,7 +364,7 @@ wmi = ["wmi (>=1.5.1,<2.0.0)"] [[package]] name = "docutils" -version = "0.17.1" +version = "0.18.1" description = "Docutils -- Python Documentation Utilities" category = "dev" optional = false @@ -388,7 +372,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "dropbox" -version = "11.31.0" +version = "11.26.0" description = "Official Dropbox API Client" category = "main" optional = false @@ -413,7 +397,7 @@ prefixed = ">=0.3.2" [[package]] name = "evdev" -version = "1.5.0" +version = "1.4.0" description = "Bindings to the Linux input handling subsystem" category = "main" optional = false @@ -467,23 +451,6 @@ category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -[[package]] -name = "gazu" -version = "0.8.28" -description = "Gazu is a client for Zou, the API to store the data of your CG production." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.dependencies] -deprecated = "1.2.13" -python-socketio = {version = "4.6.1", extras = ["client"], markers = "python_version >= \"3.5\""} -requests = ">=2.25.1,<=2.27.1" - -[package.extras] -dev = ["wheel"] -test = ["pytest-cov (==2.12.1)", "requests-mock (==1.9.3)", "pytest (==4.6.11)", "pytest (==6.1.2)", "pytest (==6.2.5)", "black (==21.12b0)", "pre-commit (==2.17.0)"] - [[package]] name = "gitdb" version = "4.0.9" @@ -497,7 +464,7 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.27" +version = "3.1.26" description = "GitPython is a python library used to interact with Git repositories" category = "dev" optional = false @@ -509,7 +476,7 @@ typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\"" [[package]] name = "google-api-core" -version = "2.8.1" +version = "2.4.0" description = "Google API client core library" category = "main" optional = false @@ -517,18 +484,18 @@ python-versions = ">=3.6" [package.dependencies] google-auth = ">=1.25.0,<3.0dev" -googleapis-common-protos = ">=1.56.2,<2.0dev" -protobuf = ">=3.15.0,<4.0.0dev" +googleapis-common-protos = ">=1.52.0,<2.0dev" +protobuf = ">=3.12.0" requests = ">=2.18.0,<3.0.0dev" [package.extras] grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio-status (>=1.33.2,<2.0dev)"] -grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] +grpcgcp = ["grpcio-gcp (>=0.2.2)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2)"] [[package]] name = "google-api-python-client" -version = "1.12.11" +version = "1.12.10" description = "Google API Client Library for Python" category = "main" optional = false @@ -544,7 +511,7 @@ uritemplate = ">=3.0.0,<4dev" [[package]] name = "google-auth" -version = "2.7.0" +version = "2.6.0" description = "Google Authentication Library" category = "main" optional = false @@ -558,7 +525,6 @@ six = ">=1.9.0" [package.extras] aiohttp = ["requests (>=2.20.0,<3.0.0dev)", "aiohttp (>=3.6.2,<4.0.0dev)"] -enterprise_cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] pyopenssl = ["pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] @@ -577,21 +543,21 @@ six = "*" [[package]] name = "googleapis-common-protos" -version = "1.56.2" +version = "1.54.0" description = "Common protobufs used in Google APIs" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -protobuf = ">=3.15.0,<4.0.0dev" +protobuf = ">=3.12.0" [package.extras] -grpc = ["grpcio (>=1.0.0,<2.0.0dev)"] +grpc = ["grpcio (>=1.0.0)"] [[package]] name = "httplib2" -version = "0.20.4" +version = "0.20.2" description = "A comprehensive HTTP client library." category = "main" optional = false @@ -602,11 +568,11 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "idna" -version = "3.3" +version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "imagesize" @@ -618,7 +584,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.11.4" +version = "4.10.1" description = "Read metadata from Python packages" category = "main" optional = false @@ -629,9 +595,9 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -671,14 +637,14 @@ testing = ["colorama", "docopt", "pytest (>=3.1.0)"] [[package]] name = "jeepney" -version = "0.8.0" +version = "0.7.1" description = "Low-level, pure Python DBus protocol wrapper." category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [package.extras] -test = ["pytest", "pytest-trio", "pytest-asyncio (>=0.17)", "testpath", "trio", "async-timeout"] +test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio", "async-timeout"] trio = ["trio", "async-generator"] [[package]] @@ -697,7 +663,7 @@ i18n = ["Babel (>=0.8)"] [[package]] name = "jinxed" -version = "1.2.0" +version = "1.1.0" description = "Jinxed Terminal Library" category = "main" optional = false @@ -756,11 +722,11 @@ pymongo = "*" [[package]] name = "markupsafe" -version = "2.1.1" +version = "2.0.1" description = "Safely add untrusted strings to HTML/XML markup." category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [[package]] name = "mccabe" @@ -811,7 +777,7 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "paramiko" -version = "2.11.0" +version = "2.10.1" description = "SSH2 protocol library" category = "main" optional = false @@ -843,7 +809,7 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pathlib2" -version = "2.3.7.post1" +version = "2.3.6" description = "Object-oriented filesystem paths" category = "main" optional = false @@ -854,27 +820,23 @@ six = "*" [[package]] name = "pillow" -version = "9.1.1" +version = "9.0.1" description = "Python Imaging Library (Fork)" category = "main" optional = false python-versions = ">=3.7" -[package.extras] -docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] - [[package]] name = "platformdirs" -version = "2.5.2" +version = "2.4.1" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] -test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] [[package]] name = "pluggy" @@ -913,7 +875,7 @@ version = "3.20.0" description = "Protocol Buffers" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.5" [[package]] name = "py" @@ -996,33 +958,29 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.12.0" +version = "2.11.2" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.5" [[package]] name = "pylint" -version = "2.13.9" +version = "2.12.2" description = "python code static checker" category = "dev" optional = false python-versions = ">=3.6.2" [package.dependencies] -astroid = ">=2.11.5,<=2.12.0-dev0" +astroid = ">=2.9.0,<2.10" colorama = {version = "*", markers = "sys_platform == \"win32\""} -dill = ">=0.2" isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.8" +mccabe = ">=0.6,<0.7" platformdirs = ">=2.2.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +toml = ">=0.9.2" typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} -[package.extras] -testutil = ["gitpython (>3)"] - [[package]] name = "pymongo" version = "3.12.3" @@ -1073,7 +1031,7 @@ six = "*" [[package]] name = "pyobjc-core" -version = "8.5" +version = "8.2" description = "Python<->ObjC Interoperability Module" category = "main" optional = false @@ -1081,39 +1039,39 @@ python-versions = ">=3.6" [[package]] name = "pyobjc-framework-applicationservices" -version = "8.5" +version = "8.2" description = "Wrappers for the framework ApplicationServices on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.5" -pyobjc-framework-Cocoa = ">=8.5" -pyobjc-framework-Quartz = ">=8.5" +pyobjc-core = ">=8.2" +pyobjc-framework-Cocoa = ">=8.2" +pyobjc-framework-Quartz = ">=8.2" [[package]] name = "pyobjc-framework-cocoa" -version = "8.5" +version = "8.2" description = "Wrappers for the Cocoa frameworks on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.5" +pyobjc-core = ">=8.2" [[package]] name = "pyobjc-framework-quartz" -version = "8.5" +version = "8.2" description = "Wrappers for the Quartz frameworks on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.5" -pyobjc-framework-Cocoa = ">=8.5" +pyobjc-core = ">=8.2" +pyobjc-framework-Cocoa = ">=8.2" [[package]] name = "pyparsing" @@ -1196,39 +1154,6 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" [package.dependencies] six = ">=1.5" -[[package]] -name = "python-engineio" -version = "3.14.2" -description = "Engine.IO server" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -six = ">=1.9.0" - -[package.extras] -asyncio_client = ["aiohttp (>=3.4)"] -client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] - -[[package]] -name = "python-socketio" -version = "4.6.1" -description = "Socket.IO server" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -python-engineio = ">=3.13.0,<4" -requests = {version = ">=2.21.0", optional = true, markers = "extra == \"client\""} -six = ">=1.9.0" -websocket-client = {version = ">=0.54.0", optional = true, markers = "extra == \"client\""} - -[package.extras] -asyncio_client = ["aiohttp (>=3.4)", "websockets (>=7.0)"] -client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] - [[package]] name = "python-xlib" version = "0.31" @@ -1250,7 +1175,7 @@ python-versions = "*" [[package]] name = "pytz" -version = "2022.1" +version = "2021.3" description = "World timezone definitions, modern and historical" category = "dev" optional = false @@ -1274,7 +1199,7 @@ python-versions = "*" [[package]] name = "qt.py" -version = "1.3.7" +version = "1.3.6" description = "Python 2 & 3 compatibility wrapper around all Qt bindings - PySide, PySide2, PyQt4 and PyQt5." category = "main" optional = false @@ -1315,21 +1240,21 @@ sphinx = ">=1.3.1" [[package]] name = "requests" -version = "2.27.1" +version = "2.25.1" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} -idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" urllib3 = ">=1.21.1,<1.27" [package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "rsa" @@ -1344,7 +1269,7 @@ pyasn1 = ">=0.1.3" [[package]] name = "secretstorage" -version = "3.3.2" +version = "3.3.1" description = "Python bindings to FreeDesktop.org Secret Service API" category = "main" optional = false @@ -1362,21 +1287,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "shotgun-api3" -version = "3.3.3" -description = "Shotgun Python API" -category = "main" -optional = false -python-versions = "*" -develop = false - -[package.source] -type = "git" -url = "https://github.com/shotgunsoftware/python-api.git" -reference = "v3.3.3" -resolved_reference = "b9f066c0edbea6e0733242e18f32f75489064840" - [[package]] name = "six" version = "1.16.0" @@ -1387,7 +1297,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "slack-sdk" -version = "3.17.0" +version = "3.13.0" description = "The Slack API Platform SDK for Python" category = "main" optional = false @@ -1395,7 +1305,7 @@ python-versions = ">=3.6.0" [package.extras] optional = ["aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "SQLAlchemy (>=1,<2)", "websockets (>=10,<11)", "websocket-client (>=1,<2)"] -testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "itsdangerous (==1.1.0)", "Jinja2 (==3.0.3)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==22.3.0)", "click (==8.0.4)", "psutil (>=5,<6)", "databases (>=0.5)", "boto3 (<=2)", "moto (>=3,<4)"] +testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==21.12b0)", "psutil (>=5,<6)", "databases (>=0.3)", "boto3 (<=2)", "moto (<2)"] [[package]] name = "smmap" @@ -1415,7 +1325,7 @@ python-versions = "*" [[package]] name = "speedcopy" -version = "2.1.4" +version = "2.1.2" description = "Replacement or alternative for python copyfile() utilizing server side copy on network shares for faster copying." category = "main" optional = false @@ -1423,19 +1333,18 @@ python-versions = "*" [[package]] name = "sphinx" -version = "5.0.1" +version = "3.5.3" description = "Python documentation generator" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.5" [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=1.3" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.14,<0.19" +docutils = ">=0.12" imagesize = "*" -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} Jinja2 = ">=2.3" packaging = "*" Pygments = ">=2.0" @@ -1443,19 +1352,19 @@ requests = ">=2.5.0" snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-htmlhelp = "*" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" +sphinxcontrib-serializinghtml = "*" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.950)", "docutils-stubs", "types-typed-ast", "types-requests"] -test = ["pytest (>=4.6)", "html5lib", "cython", "typed-ast"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"] +test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-qt-documentation" -version = "0.4" +version = "0.3" description = "Plugin for proper resolve intersphinx references for Qt elements" category = "dev" optional = false @@ -1465,22 +1374,16 @@ python-versions = ">=3.6" docutils = "*" sphinx = "*" -[package.extras] -dev = ["pre-commit"] -lint = ["black", "flake8", "pylint"] -test = ["pytest (>=3.0.0)", "pytest-cov"] - [[package]] name = "sphinx-rtd-theme" -version = "1.0.0" +version = "0.5.1" description = "Read the Docs theme for Sphinx" category = "dev" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +python-versions = "*" [package.dependencies] -docutils = "<0.18" -sphinx = ">=1.6" +sphinx = "*" [package.extras] dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"] @@ -1601,7 +1504,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" -version = "2.0.1" +version = "2.0.0" description = "A lil' TOML parser" category = "dev" optional = false @@ -1609,7 +1512,7 @@ python-versions = ">=3.7" [[package]] name = "typed-ast" -version = "1.5.4" +version = "1.5.2" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false @@ -1633,14 +1536,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "urllib3" -version = "1.26.9" +version = "1.26.8" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] +brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] @@ -1665,9 +1568,9 @@ six = "*" [[package]] name = "wrapt" -version = "1.14.1" +version = "1.13.3" description = "Module for decorators, wrappers and monkey patching." -category = "main" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" @@ -1703,15 +1606,15 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [[package]] name = "zipp" -version = "3.8.0" +version = "3.7.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] lock-version = "1.1" @@ -1819,8 +1722,8 @@ arrow = [ {file = "arrow-0.17.0.tar.gz", hash = "sha256:ff08d10cda1d36c68657d6ad20d74fbea493d980f8b2d45344e00d6ed2bf6ed4"}, ] astroid = [ - {file = "astroid-2.11.5-py3-none-any.whl", hash = "sha256:14ffbb4f6aa2cf474a0834014005487f7ecd8924996083ab411e7fa0b508ce0b"}, - {file = "astroid-2.11.5.tar.gz", hash = "sha256:f4e4ec5294c4b07ac38bab9ca5ddd3914d4bf46f9006eb5c0ae755755061044e"}, + {file = "astroid-2.9.3-py3-none-any.whl", hash = "sha256:506daabe5edffb7e696ad82483ad0228245a9742ed7d2d8c9cdb31537decf9f6"}, + {file = "astroid-2.9.3.tar.gz", hash = "sha256:1efdf4e867d4d8ba4a9f6cf9ce07cd182c4c41de77f23814feb27ca93ca9d877"}, ] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, @@ -1847,29 +1750,28 @@ babel = [ {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] bcrypt = [ - {file = "bcrypt-3.2.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40"}, - {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa"}, - {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa"}, - {file = "bcrypt-3.2.2-cp36-abi3-win32.whl", hash = "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e"}, - {file = "bcrypt-3.2.2-cp36-abi3-win_amd64.whl", hash = "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129"}, - {file = "bcrypt-3.2.2.tar.gz", hash = "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb"}, + {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b589229207630484aefe5899122fb938a5b017b0f4349f769b8c13e78d99a8fd"}, + {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a0584a92329210fcd75eb8a3250c5a941633f8bfaf2a18f81009b097732839b7"}, + {file = "bcrypt-3.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:56e5da069a76470679f312a7d3d23deb3ac4519991a0361abc11da837087b61d"}, + {file = "bcrypt-3.2.0-cp36-abi3-win32.whl", hash = "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55"}, + {file = "bcrypt-3.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34"}, + {file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"}, ] blessed = [ {file = "blessed-1.19.1-py2.py3-none-any.whl", hash = "sha256:63b8554ae2e0e7f43749b6715c734cc8f3883010a809bf16790102563e6cf25b"}, {file = "blessed-1.19.1.tar.gz", hash = "sha256:9a0d099695bf621d4680dd6c73f6ad547f6a3442fbdbe80c4b1daa1edbc492fc"}, ] cachetools = [ - {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"}, - {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"}, + {file = "cachetools-5.0.0-py3-none-any.whl", hash = "sha256:8fecd4203a38af17928be7b90689d8083603073622229ca7077b72d8e5a976e4"}, + {file = "cachetools-5.0.0.tar.gz", hash = "sha256:486471dfa8799eb7ec503a8059e263db000cdda20075ce5e48903087f79d5fd6"}, ] certifi = [ - {file = "certifi-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"}, - {file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"}, + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] cffi = [ {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, @@ -1923,9 +1825,13 @@ cffi = [ {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, ] +chardet = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] charset-normalizer = [ - {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, - {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, + {file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"}, + {file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"}, ] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, @@ -1948,71 +1854,69 @@ coolname = [ {file = "coolname-1.1.0.tar.gz", hash = "sha256:410fe6ea9999bf96f2856ef0c726d5f38782bbefb7bb1aca0e91e0dc98ed09e3"}, ] coverage = [ - {file = "coverage-6.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1d5aa2703e1dab4ae6cf416eb0095304f49d004c39e9db1d86f57924f43006b"}, - {file = "coverage-6.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ce1b258493cbf8aec43e9b50d89982346b98e9ffdfaae8ae5793bc112fb0068"}, - {file = "coverage-6.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c4e737f60c6936460c5be330d296dd5b48b3963f48634c53b3f7deb0f34ec4"}, - {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e65ef149028516c6d64461b95a8dbcfce95cfd5b9eb634320596173332ea84"}, - {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69718750eaae75efe506406c490d6fc5a6161d047206cc63ce25527e8a3adad"}, - {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e57816f8ffe46b1df8f12e1b348f06d164fd5219beba7d9433ba79608ef011cc"}, - {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:01c5615d13f3dd3aa8543afc069e5319cfa0c7d712f6e04b920431e5c564a749"}, - {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ab269400706fab15981fd4bd5080c56bd5cc07c3bccb86aab5e1d5a88dc8f4"}, - {file = "coverage-6.4.1-cp310-cp310-win32.whl", hash = "sha256:a7f3049243783df2e6cc6deafc49ea123522b59f464831476d3d1448e30d72df"}, - {file = "coverage-6.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2ddcac99b2d2aec413e36d7a429ae9ebcadf912946b13ffa88e7d4c9b712d6"}, - {file = "coverage-6.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb73e0011b8793c053bfa85e53129ba5f0250fdc0392c1591fd35d915ec75c46"}, - {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106c16dfe494de3193ec55cac9640dd039b66e196e4641fa8ac396181578b982"}, - {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f4f3df85aa39da00fd3ec4b5abeb7407e82b68c7c5ad181308b0e2526da5d4"}, - {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:961e2fb0680b4f5ad63234e0bf55dfb90d302740ae9c7ed0120677a94a1590cb"}, - {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cec3a0f75c8f1031825e19cd86ee787e87cf03e4fd2865c79c057092e69e3a3b"}, - {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:129cd05ba6f0d08a766d942a9ed4b29283aff7b2cccf5b7ce279d50796860bb3"}, - {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bf5601c33213d3cb19d17a796f8a14a9eaa5e87629a53979a5981e3e3ae166f6"}, - {file = "coverage-6.4.1-cp37-cp37m-win32.whl", hash = "sha256:269eaa2c20a13a5bf17558d4dc91a8d078c4fa1872f25303dddcbba3a813085e"}, - {file = "coverage-6.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f02cbbf8119db68455b9d763f2f8737bb7db7e43720afa07d8eb1604e5c5ae28"}, - {file = "coverage-6.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffa9297c3a453fba4717d06df579af42ab9a28022444cae7fa605af4df612d54"}, - {file = "coverage-6.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:145f296d00441ca703a659e8f3eb48ae39fb083baba2d7ce4482fb2723e050d9"}, - {file = "coverage-6.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d44996140af8b84284e5e7d398e589574b376fb4de8ccd28d82ad8e3bea13"}, - {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bd9a6fc18aab8d2e18f89b7ff91c0f34ff4d5e0ba0b33e989b3cd4194c81fd9"}, - {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384f2a3652cef289e38100f2d037956194a837221edd520a7ee5b42d00cc605"}, - {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b3e07152b4563722be523e8cd0b209e0d1a373022cfbde395ebb6575bf6790d"}, - {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1480ff858b4113db2718848d7b2d1b75bc79895a9c22e76a221b9d8d62496428"}, - {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:865d69ae811a392f4d06bde506d531f6a28a00af36f5c8649684a9e5e4a85c83"}, - {file = "coverage-6.4.1-cp38-cp38-win32.whl", hash = "sha256:664a47ce62fe4bef9e2d2c430306e1428ecea207ffd68649e3b942fa8ea83b0b"}, - {file = "coverage-6.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:26dff09fb0d82693ba9e6231248641d60ba606150d02ed45110f9ec26404ed1c"}, - {file = "coverage-6.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9c80df769f5ec05ad21ea34be7458d1dc51ff1fb4b2219e77fe24edf462d6df"}, - {file = "coverage-6.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39ee53946bf009788108b4dd2894bf1349b4e0ca18c2016ffa7d26ce46b8f10d"}, - {file = "coverage-6.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b66caa62922531059bc5ac04f836860412f7f88d38a476eda0a6f11d4724f4"}, - {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd180ed867e289964404051a958f7cccabdeed423f91a899829264bb7974d3d3"}, - {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3"}, - {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c08da0bd238f2970230c2a0d28ff0e99961598cb2e810245d7fc5afcf1254e8"}, - {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d42c549a8f41dc103a8004b9f0c433e2086add8a719da00e246e17cbe4056f72"}, - {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:309ce4a522ed5fca432af4ebe0f32b21d6d7ccbb0f5fcc99290e71feba67c264"}, - {file = "coverage-6.4.1-cp39-cp39-win32.whl", hash = "sha256:fdb6f7bd51c2d1714cea40718f6149ad9be6a2ee7d93b19e9f00934c0f2a74d9"}, - {file = "coverage-6.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:342d4aefd1c3e7f620a13f4fe563154d808b69cccef415415aece4c786665397"}, - {file = "coverage-6.4.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:4803e7ccf93230accb928f3a68f00ffa80a88213af98ed338a57ad021ef06815"}, - {file = "coverage-6.4.1.tar.gz", hash = "sha256:4321f075095a096e70aff1d002030ee612b65a205a0a0f5b815280d5dc58100c"}, + {file = "coverage-6.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525"}, + {file = "coverage-6.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c"}, + {file = "coverage-6.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145"}, + {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce"}, + {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167"}, + {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda"}, + {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27"}, + {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e"}, + {file = "coverage-6.3.1-cp310-cp310-win32.whl", hash = "sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217"}, + {file = "coverage-6.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb"}, + {file = "coverage-6.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0"}, + {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793"}, + {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd"}, + {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1"}, + {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554"}, + {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1"}, + {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8"}, + {file = "coverage-6.3.1-cp37-cp37m-win32.whl", hash = "sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0"}, + {file = "coverage-6.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687"}, + {file = "coverage-6.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320"}, + {file = "coverage-6.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8"}, + {file = "coverage-6.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734"}, + {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4"}, + {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975"}, + {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa"}, + {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b"}, + {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a"}, + {file = "coverage-6.3.1-cp38-cp38-win32.whl", hash = "sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10"}, + {file = "coverage-6.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f"}, + {file = "coverage-6.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d"}, + {file = "coverage-6.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6"}, + {file = "coverage-6.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1"}, + {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c"}, + {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba"}, + {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed"}, + {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f"}, + {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38"}, + {file = "coverage-6.3.1-cp39-cp39-win32.whl", hash = "sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2"}, + {file = "coverage-6.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa"}, + {file = "coverage-6.3.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2"}, + {file = "coverage-6.3.1.tar.gz", hash = "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8"}, ] cryptography = [ - {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181"}, - {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c"}, - {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178"}, - {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a"}, - {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15"}, - {file = "cryptography-37.0.2-cp36-abi3-win32.whl", hash = "sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0"}, - {file = "cryptography-37.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d"}, - {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9"}, - {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1"}, - {file = "cryptography-37.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023"}, - {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06"}, - {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717"}, - {file = "cryptography-37.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f"}, - {file = "cryptography-37.0.2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982"}, - {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4"}, - {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de"}, - {file = "cryptography-37.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452"}, - {file = "cryptography-37.0.2.tar.gz", hash = "sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e"}, + {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b"}, + {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31"}, + {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a"}, + {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173"}, + {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12"}, + {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3"}, + {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2"}, + {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f"}, + {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3"}, + {file = "cryptography-36.0.1-cp36-abi3-win32.whl", hash = "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca"}, + {file = "cryptography-36.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf"}, + {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9"}, + {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1"}, + {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94"}, + {file = "cryptography-36.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac"}, + {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee"}, + {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46"}, + {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903"}, + {file = "cryptography-36.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316"}, + {file = "cryptography-36.0.1.tar.gz", hash = "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638"}, ] cx-freeze = [ {file = "cx_Freeze-6.9-cp310-cp310-win32.whl", hash = "sha256:776d4fb68a4831691acbd3c374362b9b48ce2e568514a73c3d4cb14d5dcf1470"}, @@ -2042,33 +1946,25 @@ cx-logging = [ {file = "cx_Logging-3.0-cp39-cp39-win_amd64.whl", hash = "sha256:302e9c4f65a936c288a4fa59a90e7e142d9ef994aa29676731acafdcccdbb3f5"}, {file = "cx_Logging-3.0.tar.gz", hash = "sha256:ba8a7465facf7b98d8f494030fb481a2e8aeee29dc191e10383bb54ed42bdb34"}, ] -deprecated = [ - {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, - {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, -] -dill = [ - {file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"}, - {file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"}, -] dnspython = [ - {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"}, - {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"}, + {file = "dnspython-2.2.0-py3-none-any.whl", hash = "sha256:081649da27ced5e75709a1ee542136eaba9842a0fe4c03da4fb0a3d3ed1f3c44"}, + {file = "dnspython-2.2.0.tar.gz", hash = "sha256:e79351e032d0b606b98d38a4b0e6e2275b31a5b85c873e587cc11b73aca026d6"}, ] docutils = [ - {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, - {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, + {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, + {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, ] dropbox = [ - {file = "dropbox-11.31.0-py2-none-any.whl", hash = "sha256:393a99dfe30d42fd73c265b9b7d24bb21c9a961739cd097c3541e709eb2a209c"}, - {file = "dropbox-11.31.0-py3-none-any.whl", hash = "sha256:5f924102fd6464def81573320c6aa4ea9cd3368e1b1c13d838403dd4c9ffc919"}, - {file = "dropbox-11.31.0.tar.gz", hash = "sha256:f483d65b702775b9abf7b9328f702c68c6397fc01770477c6ddbfb1d858a5bcf"}, + {file = "dropbox-11.26.0-py2-none-any.whl", hash = "sha256:abd29587fa692bde0c3a48ce8efb56c24d8df92c5f402bbcdd78f3089d5ced5c"}, + {file = "dropbox-11.26.0-py3-none-any.whl", hash = "sha256:84becf043a63007ae67620946acb0fa164669ce691c7f1dbfdabb6712a258b29"}, + {file = "dropbox-11.26.0.tar.gz", hash = "sha256:dd79e3dfc216688020959462aefac54ffce7c07a45e89f4fb258348885195a73"}, ] enlighten = [ {file = "enlighten-1.10.2-py2.py3-none-any.whl", hash = "sha256:b237fe562b320bf9f1d4bb76d0c98e0daf914372a76ab87c35cd02f57aa9d8c1"}, {file = "enlighten-1.10.2.tar.gz", hash = "sha256:7a5b83cd0f4d095e59d80c648ebb5f7ffca0cd8bcf7ae6639828ee1ad000632a"}, ] evdev = [ - {file = "evdev-1.5.0.tar.gz", hash = "sha256:5b33b174f7c84576e7dd6071e438bf5ad227da95efd4356a39fe4c8355412fe6"}, + {file = "evdev-1.4.0.tar.gz", hash = "sha256:8782740eb1a86b187334c07feb5127d3faa0b236e113206dfe3ae8f77fb1aaf1"}, ] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, @@ -2142,52 +2038,49 @@ ftrack-python-api = [ future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] -gazu = [ - {file = "gazu-0.8.28-py2.py3-none-any.whl", hash = "sha256:ec4f7c2688a2b37ee8a77737e4e30565ad362428c3ade9046136a998c043e51c"}, -] gitdb = [ {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, ] gitpython = [ - {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, - {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, + {file = "GitPython-3.1.26-py3-none-any.whl", hash = "sha256:26ac35c212d1f7b16036361ca5cff3ec66e11753a0d677fb6c48fa4e1a9dd8d6"}, + {file = "GitPython-3.1.26.tar.gz", hash = "sha256:fc8868f63a2e6d268fb25f481995ba185a85a66fcad126f039323ff6635669ee"}, ] google-api-core = [ - {file = "google-api-core-2.8.1.tar.gz", hash = "sha256:958024c6aa3460b08f35741231076a4dd9a4c819a6a39d44da9627febe8b28f0"}, - {file = "google_api_core-2.8.1-py3-none-any.whl", hash = "sha256:ce1daa49644b50398093d2a9ad886501aa845e2602af70c3001b9f402a9d7359"}, + {file = "google-api-core-2.4.0.tar.gz", hash = "sha256:ba8787b7c61632cd0340f095e1c036bef9426b2594f10afb290ba311ae8cb2cb"}, + {file = "google_api_core-2.4.0-py2.py3-none-any.whl", hash = "sha256:58e2c1171a3d51778bf4e428fbb4bf79cbd05007b4b44deaa80cf73c80eebc0f"}, ] google-api-python-client = [ - {file = "google-api-python-client-1.12.11.tar.gz", hash = "sha256:1b4bd42a46321e13c0542a9e4d96fa05d73626f07b39f83a73a947d70ca706a9"}, - {file = "google_api_python_client-1.12.11-py2.py3-none-any.whl", hash = "sha256:7e0a1a265c8d3088ee1987778c72683fcb376e32bada8d7767162bd9c503fd9b"}, + {file = "google-api-python-client-1.12.10.tar.gz", hash = "sha256:1cb773647e7d97048d9d1c7fa746247fbad39fd1a3b5040f2cb2645dd7156b11"}, + {file = "google_api_python_client-1.12.10-py2.py3-none-any.whl", hash = "sha256:5a8742b9b604b34e13462cc3d6aedbbf11d3af1ef558eb95defe74a29ebc5c28"}, ] google-auth = [ - {file = "google-auth-2.7.0.tar.gz", hash = "sha256:8a954960f852d5f19e6af14dd8e75c20159609e85d8db37e4013cc8c3824a7e1"}, - {file = "google_auth-2.7.0-py2.py3-none-any.whl", hash = "sha256:df549a1433108801b11bdcc0e312eaf0d5f0500db42f0523e4d65c78722e8475"}, + {file = "google-auth-2.6.0.tar.gz", hash = "sha256:ad160fc1ea8f19e331a16a14a79f3d643d813a69534ba9611d2c80dc10439dad"}, + {file = "google_auth-2.6.0-py2.py3-none-any.whl", hash = "sha256:218ca03d7744ca0c8b6697b6083334be7df49b7bf76a69d555962fd1a7657b5f"}, ] google-auth-httplib2 = [ {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, {file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"}, ] googleapis-common-protos = [ - {file = "googleapis-common-protos-1.56.2.tar.gz", hash = "sha256:b09b56f5463070c2153753ef123f07d2e49235e89148e9b2459ec8ed2f68d7d3"}, - {file = "googleapis_common_protos-1.56.2-py2.py3-none-any.whl", hash = "sha256:023eaea9d8c1cceccd9587c6af6c20f33eeeb05d4148670f2b0322dc1511700c"}, + {file = "googleapis-common-protos-1.54.0.tar.gz", hash = "sha256:a4031d6ec6c2b1b6dc3e0be7e10a1bd72fb0b18b07ef9be7b51f2c1004ce2437"}, + {file = "googleapis_common_protos-1.54.0-py2.py3-none-any.whl", hash = "sha256:e54345a2add15dc5e1a7891c27731ff347b4c33765d79b5ed7026a6c0c7cbcae"}, ] httplib2 = [ - {file = "httplib2-0.20.4-py3-none-any.whl", hash = "sha256:8b6a905cb1c79eefd03f8669fd993c36dc341f7c558f056cb5a33b5c2f458543"}, - {file = "httplib2-0.20.4.tar.gz", hash = "sha256:58a98e45b4b1a48273073f905d2961666ecf0fbac4250ea5b47aef259eb5c585"}, + {file = "httplib2-0.20.2-py3-none-any.whl", hash = "sha256:6b937120e7d786482881b44b8eec230c1ee1c5c1d06bce8cc865f25abbbf713b"}, + {file = "httplib2-0.20.2.tar.gz", hash = "sha256:e404681d2fbcec7506bcb52c503f2b021e95bee0ef7d01e5c221468a2406d8dc"}, ] idna = [ - {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, - {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] imagesize = [ {file = "imagesize-1.3.0-py2.py3-none-any.whl", hash = "sha256:1db2f82529e53c3e929e8926a1fa9235aa82d0bd0c580359c67ec31b2fddaa8c"}, {file = "imagesize-1.3.0.tar.gz", hash = "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.11.4-py3-none-any.whl", hash = "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec"}, - {file = "importlib_metadata-4.11.4.tar.gz", hash = "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700"}, + {file = "importlib_metadata-4.10.1-py3-none-any.whl", hash = "sha256:899e2a40a8c4a1aec681feef45733de8a6c58f3f6a0dbed2eb6574b4387a77b6"}, + {file = "importlib_metadata-4.10.1.tar.gz", hash = "sha256:951f0d8a5b7260e9db5e41d429285b5f451e928479f19d80818878527d36e95e"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -2202,16 +2095,16 @@ jedi = [ {file = "jedi-0.13.3.tar.gz", hash = "sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b"}, ] jeepney = [ - {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, - {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, + {file = "jeepney-0.7.1-py3-none-any.whl", hash = "sha256:1b5a0ea5c0e7b166b2f5895b91a08c14de8915afda4407fb5022a195224958ac"}, + {file = "jeepney-0.7.1.tar.gz", hash = "sha256:fa9e232dfa0c498bd0b8a3a73b8d8a31978304dcef0515adc859d4e096f96f4f"}, ] jinja2 = [ {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, ] jinxed = [ - {file = "jinxed-1.2.0-py2.py3-none-any.whl", hash = "sha256:cfc2b2e4e3b4326954d546ba6d6b9a7a796ddcb0aef8d03161d005177eb0d48b"}, - {file = "jinxed-1.2.0.tar.gz", hash = "sha256:032acda92d5c57cd216033cbbd53de731e6ed50deb63eb4781336ca55f72cda5"}, + {file = "jinxed-1.1.0-py2.py3-none-any.whl", hash = "sha256:6a61ccf963c16aa885304f27e6e5693783676897cea0c7f223270c8b8e78baf8"}, + {file = "jinxed-1.1.0.tar.gz", hash = "sha256:d8f1731f134e9e6b04d95095845ae6c10eb15cb223a5f0cabdea87d4a279c305"}, ] jsonschema = [ {file = "jsonschema-2.6.0-py2.py3-none-any.whl", hash = "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08"}, @@ -2264,46 +2157,75 @@ log4mongo = [ {file = "log4mongo-1.7.0.tar.gz", hash = "sha256:dc374617206162a0b14167fbb5feac01dbef587539a235dadba6200362984a68"}, ] markupsafe = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, + {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -2376,60 +2298,57 @@ packaging = [ {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] paramiko = [ - {file = "paramiko-2.11.0-py2.py3-none-any.whl", hash = "sha256:655f25dc8baf763277b933dfcea101d636581df8d6b9774d1fb653426b72c270"}, - {file = "paramiko-2.11.0.tar.gz", hash = "sha256:003e6bee7c034c21fbb051bf83dc0a9ee4106204dd3c53054c71452cc4ec3938"}, + {file = "paramiko-2.10.1-py2.py3-none-any.whl", hash = "sha256:f6cbd3e1204abfdbcd40b3ecbc9d32f04027cd3080fe666245e21e7540ccfc1b"}, + {file = "paramiko-2.10.1.tar.gz", hash = "sha256:443f4da23ec24e9a9c0ea54017829c282abdda1d57110bf229360775ccd27a31"}, ] parso = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] pathlib2 = [ - {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, - {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, + {file = "pathlib2-2.3.6-py2.py3-none-any.whl", hash = "sha256:3a130b266b3a36134dcc79c17b3c7ac9634f083825ca6ea9d8f557ee6195c9c8"}, + {file = "pathlib2-2.3.6.tar.gz", hash = "sha256:7d8bcb5555003cdf4a8d2872c538faa3a0f5d20630cb360e518ca3b981795e5f"}, ] pillow = [ - {file = "Pillow-9.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:42dfefbef90eb67c10c45a73a9bc1599d4dac920f7dfcbf4ec6b80cb620757fe"}, - {file = "Pillow-9.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffde4c6fabb52891d81606411cbfaf77756e3b561b566efd270b3ed3791fde4e"}, - {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c857532c719fb30fafabd2371ce9b7031812ff3889d75273827633bca0c4602"}, - {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59789a7d06c742e9d13b883d5e3569188c16acb02eeed2510fd3bfdbc1bd1530"}, - {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d45dbe4b21a9679c3e8b3f7f4f42a45a7d3ddff8a4a16109dff0e1da30a35b2"}, - {file = "Pillow-9.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e9ed59d1b6ee837f4515b9584f3d26cf0388b742a11ecdae0d9237a94505d03a"}, - {file = "Pillow-9.1.1-cp310-cp310-win32.whl", hash = "sha256:b3fe2ff1e1715d4475d7e2c3e8dabd7c025f4410f79513b4ff2de3d51ce0fa9c"}, - {file = "Pillow-9.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5b650dbbc0969a4e226d98a0b440c2f07a850896aed9266b6fedc0f7e7834108"}, - {file = "Pillow-9.1.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:0b4d5ad2cd3a1f0d1df882d926b37dbb2ab6c823ae21d041b46910c8f8cd844b"}, - {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9370d6744d379f2de5d7fa95cdbd3a4d92f0b0ef29609b4b1687f16bc197063d"}, - {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b761727ed7d593e49671d1827044b942dd2f4caae6e51bab144d4accf8244a84"}, - {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a66fe50386162df2da701b3722781cbe90ce043e7d53c1fd6bd801bca6b48d4"}, - {file = "Pillow-9.1.1-cp37-cp37m-win32.whl", hash = "sha256:2b291cab8a888658d72b575a03e340509b6b050b62db1f5539dd5cd18fd50578"}, - {file = "Pillow-9.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1d4331aeb12f6b3791911a6da82de72257a99ad99726ed6b63f481c0184b6fb9"}, - {file = "Pillow-9.1.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8844217cdf66eabe39567118f229e275f0727e9195635a15e0e4b9227458daaf"}, - {file = "Pillow-9.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b6617221ff08fbd3b7a811950b5c3f9367f6e941b86259843eab77c8e3d2b56b"}, - {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20d514c989fa28e73a5adbddd7a171afa5824710d0ab06d4e1234195d2a2e546"}, - {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:088df396b047477dd1bbc7de6e22f58400dae2f21310d9e2ec2933b2ef7dfa4f"}, - {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53c27bd452e0f1bc4bfed07ceb235663a1df7c74df08e37fd6b03eb89454946a"}, - {file = "Pillow-9.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3f6c1716c473ebd1649663bf3b42702d0d53e27af8b64642be0dd3598c761fb1"}, - {file = "Pillow-9.1.1-cp38-cp38-win32.whl", hash = "sha256:c67db410508b9de9c4694c57ed754b65a460e4812126e87f5052ecf23a011a54"}, - {file = "Pillow-9.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:f054b020c4d7e9786ae0404278ea318768eb123403b18453e28e47cdb7a0a4bf"}, - {file = "Pillow-9.1.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c17770a62a71718a74b7548098a74cd6880be16bcfff5f937f900ead90ca8e92"}, - {file = "Pillow-9.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3f6a6034140e9e17e9abc175fc7a266a6e63652028e157750bd98e804a8ed9a"}, - {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f372d0f08eff1475ef426344efe42493f71f377ec52237bf153c5713de987251"}, - {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09e67ef6e430f90caa093528bd758b0616f8165e57ed8d8ce014ae32df6a831d"}, - {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66daa16952d5bf0c9d5389c5e9df562922a59bd16d77e2a276e575d32e38afd1"}, - {file = "Pillow-9.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d78ca526a559fb84faaaf84da2dd4addef5edb109db8b81677c0bb1aad342601"}, - {file = "Pillow-9.1.1-cp39-cp39-win32.whl", hash = "sha256:55e74faf8359ddda43fee01bffbc5bd99d96ea508d8a08c527099e84eb708f45"}, - {file = "Pillow-9.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c150dbbb4a94ea4825d1e5f2c5501af7141ea95825fadd7829f9b11c97aaf6c"}, - {file = "Pillow-9.1.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:769a7f131a2f43752455cc72f9f7a093c3ff3856bf976c5fb53a59d0ccc704f6"}, - {file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:488f3383cf5159907d48d32957ac6f9ea85ccdcc296c14eca1a4e396ecc32098"}, - {file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b525a356680022b0af53385944026d3486fc8c013638cf9900eb87c866afb4c"}, - {file = "Pillow-9.1.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6e760cf01259a1c0a50f3c845f9cad1af30577fd8b670339b1659c6d0e7a41dd"}, - {file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4165205a13b16a29e1ac57efeee6be2dfd5b5408122d59ef2145bc3239fa340"}, - {file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937a54e5694684f74dcbf6e24cc453bfc5b33940216ddd8f4cd8f0f79167f765"}, - {file = "Pillow-9.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:baf3be0b9446a4083cc0c5bb9f9c964034be5374b5bc09757be89f5d2fa247b8"}, - {file = "Pillow-9.1.1.tar.gz", hash = "sha256:7502539939b53d7565f3d11d87c78e7ec900d3c72945d4ee0e2f250d598309a0"}, + {file = "Pillow-9.0.1-1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4"}, + {file = "Pillow-9.0.1-1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976"}, + {file = "Pillow-9.0.1-1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc"}, + {file = "Pillow-9.0.1-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd"}, + {file = "Pillow-9.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f"}, + {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a"}, + {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049"}, + {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a"}, + {file = "Pillow-9.0.1-cp310-cp310-win32.whl", hash = "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e"}, + {file = "Pillow-9.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b"}, + {file = "Pillow-9.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e"}, + {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360"}, + {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b"}, + {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030"}, + {file = "Pillow-9.0.1-cp37-cp37m-win32.whl", hash = "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669"}, + {file = "Pillow-9.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092"}, + {file = "Pillow-9.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204"}, + {file = "Pillow-9.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e"}, + {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c"}, + {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5"}, + {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae"}, + {file = "Pillow-9.0.1-cp38-cp38-win32.whl", hash = "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c"}, + {file = "Pillow-9.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00"}, + {file = "Pillow-9.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838"}, + {file = "Pillow-9.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28"}, + {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c"}, + {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b"}, + {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7"}, + {file = "Pillow-9.0.1-cp39-cp39-win32.whl", hash = "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7"}, + {file = "Pillow-9.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac"}, + {file = "Pillow-9.0.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97"}, + {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56"}, + {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e"}, + {file = "Pillow-9.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70"}, + {file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"}, ] platformdirs = [ - {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, - {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, + {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, + {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, @@ -2528,12 +2447,12 @@ pyflakes = [ {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pygments = [ - {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, - {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, + {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, + {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, ] pylint = [ - {file = "pylint-2.13.9-py3-none-any.whl", hash = "sha256:705c620d388035bdd9ff8b44c5bcdd235bfb49d276d488dd2c8ff1736aa42526"}, - {file = "pylint-2.13.9.tar.gz", hash = "sha256:095567c96e19e6f57b5b907e67d265ff535e588fe26b12b5ebe1fc5645b2c731"}, + {file = "pylint-2.12.2-py3-none-any.whl", hash = "sha256:daabda3f7ed9d1c60f52d563b1b854632fd90035bcf01443e234d3dc794e3b74"}, + {file = "pylint-2.12.2.tar.gz", hash = "sha256:9d945a73640e1fec07ee34b42f5669b770c759acd536ec7b16d7e4b87a9c9ff9"}, ] pymongo = [ {file = "pymongo-3.12.3-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:c164eda0be9048f83c24b9b2656900041e069ddf72de81c17d874d0c32f6079f"}, @@ -2662,40 +2581,40 @@ pynput = [ {file = "pynput-1.7.6.tar.gz", hash = "sha256:3a5726546da54116b687785d38b1db56997ce1d28e53e8d22fc656d8b92e533c"}, ] pyobjc-core = [ - {file = "pyobjc-core-8.5.tar.gz", hash = "sha256:704c275439856c0d1287469f0d589a7d808d48b754a93d9ce5415d4eaf06d576"}, - {file = "pyobjc_core-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0c234143b48334443f5adcf26e668945a6d47bc1fa6223e80918c6c735a029d9"}, - {file = "pyobjc_core-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1486ee533f0d76f666804ce89723ada4db56bfde55e56151ba512d3f849857f8"}, - {file = "pyobjc_core-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:412de06dfa728301c04b3e46fd7453320a8ae8b862e85236e547cd797a73b490"}, - {file = "pyobjc_core-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b3e09cccb1be574a82cc9f929ae27fc4283eccc75496cb5d51534caa6bb83a3"}, - {file = "pyobjc_core-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:eeafe21f879666ab7f57efcc6b007c9f5f8733d367b7e380c925203ed83f000d"}, - {file = "pyobjc_core-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c0071686976d7ea8c14690950e504a13cb22b4ebb2bc7b5ec47c1c1c0f6eff41"}, + {file = "pyobjc-core-8.2.tar.gz", hash = "sha256:6afb8ee1dd0647cbfaaf99906eca3b43ce045b27e3d4510462d04e7e5361c89b"}, + {file = "pyobjc_core-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:311e45556c3afa8831713b89017e0204562f51f35661d76c07ffe04985f44e1d"}, + {file = "pyobjc_core-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:711f361e83382e405e4273ff085178b0cf730901ccf6801f834e7037e50278f9"}, + {file = "pyobjc_core-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bdd2e2960ec73214bcfe2d86bb4ca94f5f5119db86d129fa32d3c003b6532c50"}, + {file = "pyobjc_core-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b879f91fc614c399aafa1d08cf5e279c267510e904bad5c336c3a6064c0eb3aa"}, + {file = "pyobjc_core-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:b4ef4bdb99a330f5e15cc6273098964276fccbc432453cdba3c2963292bc066c"}, + {file = "pyobjc_core-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fd8e5be0955790ff8f9d17a0f356b6eb7eb1ce4995e0c94355c462dd52d22d6d"}, ] pyobjc-framework-applicationservices = [ - {file = "pyobjc-framework-ApplicationServices-8.5.tar.gz", hash = "sha256:fa3015ef8e3add90af3447d7fdcc7f8dd083cc2a1d58f99a569480a2df10d2b1"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:436b16ebe448a829a8312e10208eec81a2adcae1fff674dbcc3262e1bd76e0ca"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:408958d14aa7fcf46f2163754c211078bc63be1368934d86188202914dce077d"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1d6cd4ce192859a22e208da4d7177a1c3ceb1ef2f64c339fd881102b1210cadd"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0251d092adb1d2d116fd9f147ceef0e53b158a46c21245131c40b9d7b786d0db"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:9742e69fe6d4545d0e02b0ad0a7a2432bc9944569ee07d6e90ffa5ef614df9f7"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16f5677c14ea903c6aaca1dd121521825c39e816cae696d6ae32c0b287252ab2"}, + {file = "pyobjc-framework-ApplicationServices-8.2.tar.gz", hash = "sha256:f901b2ebb278b7d00033b45cf9ee9d43f651e096ff4c4defa261509238138da8"}, + {file = "pyobjc_framework_ApplicationServices-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b5be1757a8df944a58449c628ed68fc76dd746fcd1e96ebb6db0f734e94cdfc8"}, + {file = "pyobjc_framework_ApplicationServices-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:32657c4b89983a98fbe831a14884954710719e763197968a20ac07e1daa21f1e"}, + {file = "pyobjc_framework_ApplicationServices-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ce2c9701590f752a75151b2fe1e462e7a7ad67dec3119a796711ea93ea01031"}, + {file = "pyobjc_framework_ApplicationServices-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28124bc892045222305cf8953b163495bf662c401e54f48905dafb1104d9c1d1"}, + {file = "pyobjc_framework_ApplicationServices-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d5c60d66c6ed2569cb042a6c14dd5b9f4d01e182a464b893cd6002ce445b4ddf"}, + {file = "pyobjc_framework_ApplicationServices-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e1727fccc1d48922fa28a4cebf4a6d287d633f451cb03779968df60de8cb1bd0"}, ] pyobjc-framework-cocoa = [ - {file = "pyobjc-framework-Cocoa-8.5.tar.gz", hash = "sha256:569bd3a020f64b536fb2d1c085b37553e50558c9f907e08b73ffc16ae68e1861"}, - {file = "pyobjc_framework_Cocoa-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7a7c160416696bf6035dfcdf0e603aaa52858d6afcddfcc5ab41733619ac2529"}, - {file = "pyobjc_framework_Cocoa-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6ceba444282030be8596b812260e8d28b671254a51052ad778d32da6e17db847"}, - {file = "pyobjc_framework_Cocoa-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f46b2b161b8dd40c7b9e00bc69636c3e6480b2704a69aee22ee0154befbe163a"}, - {file = "pyobjc_framework_Cocoa-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b31d425aee8698cbf62b187338f5ca59427fa4dca2153a73866f7cb410713119"}, - {file = "pyobjc_framework_Cocoa-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:898359ac1f76eedec8aa156847682378a8950824421c40edb89391286e607dc4"}, - {file = "pyobjc_framework_Cocoa-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:baa2947f76b119a3360973d74d57d6dada87ac527bab9a88f31596af392f123c"}, + {file = "pyobjc-framework-Cocoa-8.2.tar.gz", hash = "sha256:f0901998e2f18415ef6d1f8a12b083f69fc93bd56b3e88040002e3c09bd8c304"}, + {file = "pyobjc_framework_Cocoa-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5af73f150e242542735e0663bb5504f88aabaec2a54c60e856cfca3ff6dd9712"}, + {file = "pyobjc_framework_Cocoa-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d1de3763ee01850c311da74de5c82c85ec199120e85ab45acaf203accc37a470"}, + {file = "pyobjc_framework_Cocoa-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:86d69bf667f99f3c43184d8830567195fff94c675fe7f60f899dd90553d9b265"}, + {file = "pyobjc_framework_Cocoa-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9dbadb22826392c48b00087359f66579f8404a4f4f77498f31f9869c54bb0fa9"}, + {file = "pyobjc_framework_Cocoa-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7adf8b57da1c1292c24375b8e74b6dd45f99a4d3c10ba925a9b38f63a97ba782"}, + {file = "pyobjc_framework_Cocoa-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc8279c8c1544087d46a7e99324f093029df89b8c527c5da2a682bad4cb3197e"}, ] pyobjc-framework-quartz = [ - {file = "pyobjc-framework-Quartz-8.5.tar.gz", hash = "sha256:d2bc5467a792ddc04814f12a1e9c2fcaf699a1c3ad3d4264cfdce6b9c7b10624"}, - {file = "pyobjc_framework_Quartz-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e9f0fb663f7872c9de94169031ac42b91ad01bd4cad49a9f1a0164be8f028426"}, - {file = "pyobjc_framework_Quartz-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:567eec91287cfe9a1b6433717192c585935de8f3daa28d82ce72fdd6c7ac00f6"}, - {file = "pyobjc_framework_Quartz-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f910ab41a712ffc7a8c3e3716a2d6f39ea4419004b26a2fd2d2f740ff5c262c"}, - {file = "pyobjc_framework_Quartz-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:29d07066781628278bf0e5278abcfc96ef6724c66c5629a0b4c214d319a82e55"}, - {file = "pyobjc_framework_Quartz-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:72abcde1a3d72be11f2c881c9b9872044c8f2de86d2047b67fe771713638b107"}, - {file = "pyobjc_framework_Quartz-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8809b9a2df2f461697bdb45b6d1b5a4f881f88f09450e3990858e64e3e26c530"}, + {file = "pyobjc-framework-Quartz-8.2.tar.gz", hash = "sha256:219d8797235bf071723f8b0f30a681de0b12875e2d04ae902a3a269f72de0b66"}, + {file = "pyobjc_framework_Quartz-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e5ab3117201a494b11bb1b80febf5dd288111a196b35731815b656ae30ee6ce3"}, + {file = "pyobjc_framework_Quartz-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4941b3039ab7bcf89cb4255c381358de2383c1ab45c9e00c3b64655f271a3b32"}, + {file = "pyobjc_framework_Quartz-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ab75a50dea84ec794641522d9f035fc6c19ec4eb37a56c9186b9943575fbc7ab"}, + {file = "pyobjc_framework_Quartz-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8cb687b20ebfc467034868b38945b573d1c50f237e379cf86c4f557c9766b759"}, + {file = "pyobjc_framework_Quartz-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:8b5c3ca1fef900fa66aafe82fff07bc352c60ea7dceed2bb9b5b1db0957b4fea"}, + {file = "pyobjc_framework_Quartz-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7bca7e649da77056348d71032def44e345043319d71b0c592197888d92e3eec1"}, ] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, @@ -2720,14 +2639,6 @@ python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] -python-engineio = [ - {file = "python-engineio-3.14.2.tar.gz", hash = "sha256:eab4553f2804c1ce97054c8b22cf0d5a9ab23128075248b97e1a5b2f29553085"}, - {file = "python_engineio-3.14.2-py2.py3-none-any.whl", hash = "sha256:5a9e6086d192463b04a1428ff1f85b6ba631bbb19d453b144ffc04f530542b84"}, -] -python-socketio = [ - {file = "python-socketio-4.6.1.tar.gz", hash = "sha256:cd1f5aa492c1eb2be77838e837a495f117e17f686029ebc03d62c09e33f4fa10"}, - {file = "python_socketio-4.6.1-py2.py3-none-any.whl", hash = "sha256:5a21da53fdbdc6bb6c8071f40e13d100e0b279ad997681c2492478e06f370523"}, -] python-xlib = [ {file = "python-xlib-0.31.tar.gz", hash = "sha256:74d83a081f532bc07f6d7afcd6416ec38403d68f68b9b9dc9e1f28fbf2d799e9"}, {file = "python_xlib-0.31-py2.py3-none-any.whl", hash = "sha256:1ec6ce0de73d9e6592ead666779a5732b384e5b8fb1f1886bd0a81cafa477759"}, @@ -2736,8 +2647,8 @@ python3-xlib = [ {file = "python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8"}, ] pytz = [ - {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, - {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, + {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, + {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, ] pywin32 = [ {file = "pywin32-301-cp35-cp35m-win32.whl", hash = "sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7"}, @@ -2756,8 +2667,8 @@ pywin32-ctypes = [ {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, ] "qt.py" = [ - {file = "Qt.py-1.3.7-py2.py3-none-any.whl", hash = "sha256:150099d1c6f64c9621a2c9d79d45102ec781c30ee30ee69fc082c6e9be7324fe"}, - {file = "Qt.py-1.3.7.tar.gz", hash = "sha256:803c7bdf4d6230f9a466be19d55934a173eabb61406d21cb91e80c2a3f773b1f"}, + {file = "Qt.py-1.3.6-py2.py3-none-any.whl", hash = "sha256:7edf6048d07a6924707506b5ba34a6e05d66dde9a3f4e3a62f9996ccab0b91c7"}, + {file = "Qt.py-1.3.6.tar.gz", hash = "sha256:0d78656a2f814602eee304521c7bf5da0cec414818b3833712c77524294c404a"}, ] qtawesome = [ {file = "QtAwesome-0.7.3-py2.py3-none-any.whl", hash = "sha256:ddf4530b4af71cec13b24b88a4cdb56ec85b1e44c43c42d0698804c7137b09b0"}, @@ -2772,29 +2683,28 @@ recommonmark = [ {file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"}, ] requests = [ - {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, - {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, ] rsa = [ {file = "rsa-4.8-py3-none-any.whl", hash = "sha256:95c5d300c4e879ee69708c428ba566c59478fd653cc3a22243eeb8ed846950bb"}, {file = "rsa-4.8.tar.gz", hash = "sha256:5c6bd9dc7a543b7fe4304a631f8a8a3b674e2bbfc49c2ae96200cdbe55df6b17"}, ] secretstorage = [ - {file = "SecretStorage-3.3.2-py3-none-any.whl", hash = "sha256:755dc845b6ad76dcbcbc07ea3da75ae54bb1ea529eb72d15f83d26499a5df319"}, - {file = "SecretStorage-3.3.2.tar.gz", hash = "sha256:0a8eb9645b320881c222e827c26f4cfcf55363e8b374a021981ef886657a912f"}, + {file = "SecretStorage-3.3.1-py3-none-any.whl", hash = "sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f"}, + {file = "SecretStorage-3.3.1.tar.gz", hash = "sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195"}, ] semver = [ {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, ] -shotgun-api3 = [] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] slack-sdk = [ - {file = "slack_sdk-3.17.0-py2.py3-none-any.whl", hash = "sha256:0816efc43d1d2db8286e8dbcbb2e86fd0f71c206c01c521c2cb054ecb40f9ced"}, - {file = "slack_sdk-3.17.0.tar.gz", hash = "sha256:860cd0e50c454b955f14321c8c5486a47cc1e0e84116acdb009107f836752feb"}, + {file = "slack_sdk-3.13.0-py2.py3-none-any.whl", hash = "sha256:54f2a5f7419f1ab932af9e3200f7f2f93db96e0f0eb8ad7d3b4214aa9f124641"}, + {file = "slack_sdk-3.13.0.tar.gz", hash = "sha256:aae6ce057e286a5e7fe7a9f256e85b886eee556def8e04b82b08f699e64d7f67"}, ] smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, @@ -2805,20 +2715,20 @@ snowballstemmer = [ {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] speedcopy = [ - {file = "speedcopy-2.1.4-py3-none-any.whl", hash = "sha256:e09eb1de67ae0e0b51d5b99a28882009d565a37a3cb3c6bae121e3a5d3cccb17"}, - {file = "speedcopy-2.1.4.tar.gz", hash = "sha256:eff007a97e49ec1934df4fa8074f4bd1cf4a3b14c5499d914988785cff0c199a"}, + {file = "speedcopy-2.1.2-py3-none-any.whl", hash = "sha256:91e271b84c00952812dbf669d360b2610fd8fa198670373e02acf2a04db89a4c"}, + {file = "speedcopy-2.1.2.tar.gz", hash = "sha256:1b2d779fadebd53a59384f7d286c40b2ef382e0d000fa53011699fcd3190d33f"}, ] sphinx = [ - {file = "Sphinx-5.0.1-py3-none-any.whl", hash = "sha256:36aa2a3c2f6d5230be94585bc5d74badd5f9ed8f3388b8eedc1726fe45b1ad30"}, - {file = "Sphinx-5.0.1.tar.gz", hash = "sha256:f4da1187785a5bc7312cc271b0e867a93946c319d106363e102936a3d9857306"}, + {file = "Sphinx-3.5.3-py3-none-any.whl", hash = "sha256:3f01732296465648da43dec8fb40dc451ba79eb3e2cc5c6d79005fd98197107d"}, + {file = "Sphinx-3.5.3.tar.gz", hash = "sha256:ce9c228456131bab09a3d7d10ae58474de562a6f79abb3dc811ae401cf8c1abc"}, ] sphinx-qt-documentation = [ - {file = "sphinx_qt_documentation-0.4-py3-none-any.whl", hash = "sha256:fa131093f75cd1bd48699cd132e18e4d46ba9eaadc070e6026867cea75ecdb7b"}, - {file = "sphinx_qt_documentation-0.4.tar.gz", hash = "sha256:f43ba17baa93e353fb94045027fb67f9d935ed158ce8662de93f08b88eec6774"}, + {file = "sphinx_qt_documentation-0.3-py3-none-any.whl", hash = "sha256:bee247cb9e4fc03fc496d07adfdb943100e1103320c3e5e820e0cfa7c790d9b6"}, + {file = "sphinx_qt_documentation-0.3.tar.gz", hash = "sha256:f09a0c9d9e989172ba3e282b92bf55613bb23ad47315ec5b0d38536b343ac6c8"}, ] sphinx-rtd-theme = [ - {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, - {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, + {file = "sphinx_rtd_theme-0.5.1-py2.py3-none-any.whl", hash = "sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113"}, + {file = "sphinx_rtd_theme-0.5.1.tar.gz", hash = "sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, @@ -2861,34 +2771,34 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"}, + {file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"}, ] typed-ast = [ - {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, - {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, - {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, - {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, - {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, - {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, - {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, - {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, - {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, - {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, + {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"}, + {file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"}, + {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"}, + {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"}, + {file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"}, + {file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"}, + {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"}, + {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"}, + {file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"}, + {file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"}, + {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"}, + {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"}, + {file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"}, + {file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"}, + {file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"}, + {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"}, + {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"}, + {file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"}, + {file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"}, + {file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"}, + {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"}, + {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"}, + {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, + {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, ] typing-extensions = [ {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, @@ -2899,8 +2809,8 @@ uritemplate = [ {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, ] urllib3 = [ - {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, - {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, + {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, + {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -2911,70 +2821,57 @@ websocket-client = [ {file = "websocket_client-0.59.0-py2.py3-none-any.whl", hash = "sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32"}, ] wrapt = [ - {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, - {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, - {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, - {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, - {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, - {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, - {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, - {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, - {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, - {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, - {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, - {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, - {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, - {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, - {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, - {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, - {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, - {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, - {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, - {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, - {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, - {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, + {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229"}, + {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80"}, + {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca"}, + {file = "wrapt-1.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44"}, + {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056"}, + {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785"}, + {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096"}, + {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33"}, + {file = "wrapt-1.13.3-cp310-cp310-win32.whl", hash = "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f"}, + {file = "wrapt-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3"}, + {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755"}, + {file = "wrapt-1.13.3-cp35-cp35m-win32.whl", hash = "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851"}, + {file = "wrapt-1.13.3-cp35-cp35m-win_amd64.whl", hash = "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13"}, + {file = "wrapt-1.13.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918"}, + {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade"}, + {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc"}, + {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf"}, + {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125"}, + {file = "wrapt-1.13.3-cp36-cp36m-win32.whl", hash = "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36"}, + {file = "wrapt-1.13.3-cp36-cp36m-win_amd64.whl", hash = "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10"}, + {file = "wrapt-1.13.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068"}, + {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709"}, + {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df"}, + {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2"}, + {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b"}, + {file = "wrapt-1.13.3-cp37-cp37m-win32.whl", hash = "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829"}, + {file = "wrapt-1.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"}, + {file = "wrapt-1.13.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9"}, + {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554"}, + {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c"}, + {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b"}, + {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce"}, + {file = "wrapt-1.13.3-cp38-cp38-win32.whl", hash = "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79"}, + {file = "wrapt-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb"}, + {file = "wrapt-1.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb"}, + {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32"}, + {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7"}, + {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e"}, + {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640"}, + {file = "wrapt-1.13.3-cp39-cp39-win32.whl", hash = "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374"}, + {file = "wrapt-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb"}, + {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, ] wsrpc-aiohttp = [ {file = "wsrpc-aiohttp-3.2.0.tar.gz", hash = "sha256:f467abc51bcdc760fc5aeb7041abdeef46eeca3928dc43dd6e7fa7a533563818"}, @@ -3055,6 +2952,6 @@ yarl = [ {file = "yarl-1.7.2.tar.gz", hash = "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd"}, ] zipp = [ - {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, - {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, + {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, + {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, ] From 50ec61f1b08ae6895e7817634aa50473626339b0 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 20 Jun 2022 13:49:59 +0200 Subject: [PATCH 0724/1227] use context to get shotgrid project id --- .../plugins/publish/collect_shotgrid_entities.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py index 9880425a41..8af47194e6 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py @@ -23,9 +23,11 @@ class CollectShotgridEntities(pyblish.api.ContextPlugin): self.log.info(avalon_project) self.log.info(avalon_asset) - sg_project = _get_shotgrid_project(avalon_project) + sg_project = _get_shotgrid_project(context) sg_task = _get_shotgrid_task( - avalon_project, avalon_asset, avalon_task_name + avalon_project, + avalon_asset, + avalon_task_name ) sg_entity = _get_shotgrid_entity(avalon_project, avalon_asset) @@ -67,9 +69,9 @@ def _get_shotgrid_collection(project): return client.get_database("shotgrid_openpype").get_collection(project) -def _get_shotgrid_project(avalon_project): - proj_settings = get_shotgrid_project_settings(avalon_project["name"]) - shotgrid_project_id = proj_settings.get("shotgrid_project_id") +def _get_shotgrid_project(context): + shotgrid_project_id = context.data["project_settings"].get( + "shotgrid_project_id") if shotgrid_project_id: return {"type": "Project", "id": shotgrid_project_id} return {} From 43c41d2793e18dd0f8b54cdd748dbcd672e8cc35 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 20 Jun 2022 14:04:24 +0200 Subject: [PATCH 0725/1227] Fix - added unc path to zifile command in Harmony Extracting too large url resulted in 'File not found' issue (side effect was that files in offending directory were skipped). UNC path seems to help. --- .../deadline/plugins/publish/submit_harmony_deadline.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py index 2cf502224f..a1ee5e0957 100644 --- a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py @@ -322,7 +322,9 @@ class HarmonySubmitDeadline( ) unzip_dir = (published_scene.parent / published_scene.stem) with _ZipFile(published_scene, "r") as zip_ref: - zip_ref.extractall(unzip_dir.as_posix()) + # UNC path (//?/) added to minimalize risk with extracting + # to large file paths + zip_ref.extractall("//?/" + str(unzip_dir.as_posix())) # find any xstage files in directory, prefer the one with the same name # as directory (plus extension) From 47d2a611e9046cb2c87654f3d5d405cb222a0c57 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 20 Jun 2022 14:14:13 +0200 Subject: [PATCH 0726/1227] update poetry lock --- poetry.lock | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/poetry.lock b/poetry.lock index 9ae486d59a..23a70ffb0b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1287,6 +1287,21 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "shotgun-api3" +version = "3.3.3" +description = "Shotgun Python API" +category = "main" +optional = false +python-versions = "*" +develop = false + +[package.source] +type = "git" +url = "https://github.com/shotgunsoftware/python-api.git" +reference = "v3.3.3" +resolved_reference = "b9f066c0edbea6e0733242e18f32f75489064840" + [[package]] name = "six" version = "1.16.0" From ad3380b3a01c8ba3d97c2e2c4dc8ba54da83403c Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 20 Jun 2022 14:17:57 +0200 Subject: [PATCH 0727/1227] remove unused import --- .../shotgrid/plugins/publish/collect_shotgrid_entities.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py index 8af47194e6..0b03ac2e5d 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py @@ -3,10 +3,6 @@ import os import pyblish.api from openpype.lib.mongo import OpenPypeMongoConnection -from openpype.modules.shotgrid.lib.settings import ( - get_shotgrid_project_settings, -) - class CollectShotgridEntities(pyblish.api.ContextPlugin): """Collect shotgrid entities according to the current context""" From 0417fb17ca38bc79e60b4c7aa70f111cee217f6f Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 20 Jun 2022 14:48:00 +0200 Subject: [PATCH 0728/1227] poetry lock rebase --- poetry.lock | 868 +++++++++++++++++++++++++++++----------------------- 1 file changed, 492 insertions(+), 376 deletions(-) diff --git a/poetry.lock b/poetry.lock index 810fa50b90..3e6620b4a2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -94,7 +94,7 @@ python-dateutil = ">=2.7.0" [[package]] name = "astroid" -version = "2.9.3" +version = "2.11.5" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -104,7 +104,7 @@ python-versions = ">=3.6.2" lazy-object-proxy = ">=1.4.0" typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} -wrapt = ">=1.11,<1.14" +wrapt = ">=1.11,<2" [[package]] name = "async-timeout" @@ -172,7 +172,7 @@ pytz = ">=2015.7" [[package]] name = "bcrypt" -version = "3.2.0" +version = "3.2.2" description = "Modern password hashing for your software and your servers" category = "main" optional = false @@ -180,7 +180,6 @@ python-versions = ">=3.6" [package.dependencies] cffi = ">=1.1" -six = ">=1.4.1" [package.extras] tests = ["pytest (>=3.2.1,!=3.3.0)"] @@ -201,7 +200,7 @@ wcwidth = ">=0.1.4" [[package]] name = "cachetools" -version = "5.0.0" +version = "5.2.0" description = "Extensible memoizing collections and decorators" category = "main" optional = false @@ -209,11 +208,11 @@ python-versions = "~=3.7" [[package]] name = "certifi" -version = "2021.10.8" +version = "2022.5.18.1" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "cffi" @@ -226,17 +225,9 @@ python-versions = "*" [package.dependencies] pycparser = "*" -[[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - [[package]] name = "charset-normalizer" -version = "2.0.11" +version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -295,21 +286,21 @@ python-versions = "*" [[package]] name = "coverage" -version = "6.3.1" +version = "6.4.1" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] -tomli = {version = "*", optional = true, markers = "extra == \"toml\""} +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "cryptography" -version = "36.0.1" +version = "37.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false @@ -324,7 +315,7 @@ docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] sdist = ["setuptools_rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] name = "cx-freeze" @@ -346,9 +337,34 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "deprecated" +version = "1.2.13" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] + +[[package]] +name = "dill" +version = "0.3.5.1" +description = "serialize all of python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + [[package]] name = "dnspython" -version = "2.2.0" +version = "2.2.1" description = "DNS toolkit" category = "main" optional = false @@ -364,7 +380,7 @@ wmi = ["wmi (>=1.5.1,<2.0.0)"] [[package]] name = "docutils" -version = "0.18.1" +version = "0.17.1" description = "Docutils -- Python Documentation Utilities" category = "dev" optional = false @@ -372,7 +388,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "dropbox" -version = "11.26.0" +version = "11.31.0" description = "Official Dropbox API Client" category = "main" optional = false @@ -397,7 +413,7 @@ prefixed = ">=0.3.2" [[package]] name = "evdev" -version = "1.4.0" +version = "1.5.0" description = "Bindings to the Linux input handling subsystem" category = "main" optional = false @@ -451,6 +467,23 @@ category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "gazu" +version = "0.8.28" +description = "Gazu is a client for Zou, the API to store the data of your CG production." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +deprecated = "1.2.13" +python-socketio = {version = "4.6.1", extras = ["client"], markers = "python_version >= \"3.5\""} +requests = ">=2.25.1,<=2.27.1" + +[package.extras] +dev = ["wheel"] +test = ["pytest-cov (==2.12.1)", "requests-mock (==1.9.3)", "pytest (==4.6.11)", "pytest (==6.1.2)", "pytest (==6.2.5)", "black (==21.12b0)", "pre-commit (==2.17.0)"] + [[package]] name = "gitdb" version = "4.0.9" @@ -464,7 +497,7 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.26" +version = "3.1.27" description = "GitPython is a python library used to interact with Git repositories" category = "dev" optional = false @@ -476,7 +509,7 @@ typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\"" [[package]] name = "google-api-core" -version = "2.4.0" +version = "2.8.1" description = "Google API client core library" category = "main" optional = false @@ -484,18 +517,18 @@ python-versions = ">=3.6" [package.dependencies] google-auth = ">=1.25.0,<3.0dev" -googleapis-common-protos = ">=1.52.0,<2.0dev" -protobuf = ">=3.12.0" +googleapis-common-protos = ">=1.56.2,<2.0dev" +protobuf = ">=3.15.0,<4.0.0dev" requests = ">=2.18.0,<3.0.0dev" [package.extras] grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio-status (>=1.33.2,<2.0dev)"] -grpcgcp = ["grpcio-gcp (>=0.2.2)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2)"] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"] [[package]] name = "google-api-python-client" -version = "1.12.10" +version = "1.12.11" description = "Google API Client Library for Python" category = "main" optional = false @@ -511,7 +544,7 @@ uritemplate = ">=3.0.0,<4dev" [[package]] name = "google-auth" -version = "2.6.0" +version = "2.7.0" description = "Google Authentication Library" category = "main" optional = false @@ -525,6 +558,7 @@ six = ">=1.9.0" [package.extras] aiohttp = ["requests (>=2.20.0,<3.0.0dev)", "aiohttp (>=3.6.2,<4.0.0dev)"] +enterprise_cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] pyopenssl = ["pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] @@ -543,21 +577,21 @@ six = "*" [[package]] name = "googleapis-common-protos" -version = "1.54.0" +version = "1.56.2" description = "Common protobufs used in Google APIs" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -protobuf = ">=3.12.0" +protobuf = ">=3.15.0,<4.0.0dev" [package.extras] -grpc = ["grpcio (>=1.0.0)"] +grpc = ["grpcio (>=1.0.0,<2.0.0dev)"] [[package]] name = "httplib2" -version = "0.20.2" +version = "0.20.4" description = "A comprehensive HTTP client library." category = "main" optional = false @@ -568,11 +602,11 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "idna" -version = "2.10" +version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" [[package]] name = "imagesize" @@ -584,7 +618,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.10.1" +version = "4.11.4" description = "Read metadata from Python packages" category = "main" optional = false @@ -595,9 +629,9 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -663,7 +697,7 @@ i18n = ["Babel (>=0.8)"] [[package]] name = "jinxed" -version = "1.1.0" +version = "1.2.0" description = "Jinxed Terminal Library" category = "main" optional = false @@ -777,7 +811,7 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "paramiko" -version = "2.10.1" +version = "2.11.0" description = "SSH2 protocol library" category = "main" optional = false @@ -809,7 +843,7 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pathlib2" -version = "2.3.6" +version = "2.3.7.post1" description = "Object-oriented filesystem paths" category = "main" optional = false @@ -820,23 +854,27 @@ six = "*" [[package]] name = "pillow" -version = "9.0.1" +version = "9.1.1" description = "Python Imaging Library (Fork)" category = "main" optional = false python-versions = ">=3.7" +[package.extras] +docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + [[package]] name = "platformdirs" -version = "2.4.1" +version = "2.5.2" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" [package.extras] -docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] +test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] [[package]] name = "pluggy" @@ -958,29 +996,33 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.11.2" +version = "2.12.0" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [[package]] name = "pylint" -version = "2.12.2" +version = "2.13.9" description = "python code static checker" category = "dev" optional = false python-versions = ">=3.6.2" [package.dependencies] -astroid = ">=2.9.0,<2.10" +astroid = ">=2.11.5,<=2.12.0-dev0" colorama = {version = "*", markers = "sys_platform == \"win32\""} +dill = ">=0.2" isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.7" +mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" -toml = ">=0.9.2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} +[package.extras] +testutil = ["gitpython (>3)"] + [[package]] name = "pymongo" version = "3.12.3" @@ -1031,7 +1073,7 @@ six = "*" [[package]] name = "pyobjc-core" -version = "8.2" +version = "8.5" description = "Python<->ObjC Interoperability Module" category = "main" optional = false @@ -1039,39 +1081,39 @@ python-versions = ">=3.6" [[package]] name = "pyobjc-framework-applicationservices" -version = "8.2" +version = "8.5" description = "Wrappers for the framework ApplicationServices on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.2" -pyobjc-framework-Cocoa = ">=8.2" -pyobjc-framework-Quartz = ">=8.2" +pyobjc-core = ">=8.5" +pyobjc-framework-Cocoa = ">=8.5" +pyobjc-framework-Quartz = ">=8.5" [[package]] name = "pyobjc-framework-cocoa" -version = "8.2" +version = "8.5" description = "Wrappers for the Cocoa frameworks on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.2" +pyobjc-core = ">=8.5" [[package]] name = "pyobjc-framework-quartz" -version = "8.2" +version = "8.5" description = "Wrappers for the Quartz frameworks on macOS" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -pyobjc-core = ">=8.2" -pyobjc-framework-Cocoa = ">=8.2" +pyobjc-core = ">=8.5" +pyobjc-framework-Cocoa = ">=8.5" [[package]] name = "pyparsing" @@ -1154,6 +1196,39 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" [package.dependencies] six = ">=1.5" +[[package]] +name = "python-engineio" +version = "3.14.2" +description = "Engine.IO server" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = ">=1.9.0" + +[package.extras] +asyncio_client = ["aiohttp (>=3.4)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] + +[[package]] +name = "python-socketio" +version = "4.6.1" +description = "Socket.IO server" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +python-engineio = ">=3.13.0,<4" +requests = {version = ">=2.21.0", optional = true, markers = "extra == \"client\""} +six = ">=1.9.0" +websocket-client = {version = ">=0.54.0", optional = true, markers = "extra == \"client\""} + +[package.extras] +asyncio_client = ["aiohttp (>=3.4)", "websockets (>=7.0)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] + [[package]] name = "python-xlib" version = "0.31" @@ -1175,7 +1250,7 @@ python-versions = "*" [[package]] name = "pytz" -version = "2021.3" +version = "2022.1" description = "World timezone definitions, modern and historical" category = "dev" optional = false @@ -1199,7 +1274,7 @@ python-versions = "*" [[package]] name = "qt.py" -version = "1.3.6" +version = "1.3.7" description = "Python 2 & 3 compatibility wrapper around all Qt bindings - PySide, PySide2, PyQt4 and PyQt5." category = "main" optional = false @@ -1240,21 +1315,21 @@ sphinx = ">=1.3.1" [[package]] name = "requests" -version = "2.25.1" +version = "2.27.1" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<5" -idna = ">=2.5,<3" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "rsa" @@ -1269,7 +1344,7 @@ pyasn1 = ">=0.1.3" [[package]] name = "secretstorage" -version = "3.3.1" +version = "3.3.2" description = "Python bindings to FreeDesktop.org Secret Service API" category = "main" optional = false @@ -1312,7 +1387,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "slack-sdk" -version = "3.13.0" +version = "3.17.0" description = "The Slack API Platform SDK for Python" category = "main" optional = false @@ -1320,7 +1395,7 @@ python-versions = ">=3.6.0" [package.extras] optional = ["aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "SQLAlchemy (>=1,<2)", "websockets (>=10,<11)", "websocket-client (>=1,<2)"] -testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==21.12b0)", "psutil (>=5,<6)", "databases (>=0.3)", "boto3 (<=2)", "moto (<2)"] +testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "itsdangerous (==1.1.0)", "Jinja2 (==3.0.3)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==22.3.0)", "click (==8.0.4)", "psutil (>=5,<6)", "databases (>=0.5)", "boto3 (<=2)", "moto (>=3,<4)"] [[package]] name = "smmap" @@ -1340,7 +1415,7 @@ python-versions = "*" [[package]] name = "speedcopy" -version = "2.1.2" +version = "2.1.4" description = "Replacement or alternative for python copyfile() utilizing server side copy on network shares for faster copying." category = "main" optional = false @@ -1348,18 +1423,19 @@ python-versions = "*" [[package]] name = "sphinx" -version = "3.5.3" +version = "5.0.1" description = "Python documentation generator" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=1.3" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.12" +docutils = ">=0.14,<0.19" imagesize = "*" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} Jinja2 = ">=2.3" packaging = "*" Pygments = ">=2.0" @@ -1367,19 +1443,19 @@ requests = ">=2.5.0" snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"] -test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.950)", "docutils-stubs", "types-typed-ast", "types-requests"] +test = ["pytest (>=4.6)", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-qt-documentation" -version = "0.3" +version = "0.4" description = "Plugin for proper resolve intersphinx references for Qt elements" category = "dev" optional = false @@ -1389,16 +1465,22 @@ python-versions = ">=3.6" docutils = "*" sphinx = "*" +[package.extras] +dev = ["pre-commit"] +lint = ["black", "flake8", "pylint"] +test = ["pytest (>=3.0.0)", "pytest-cov"] + [[package]] name = "sphinx-rtd-theme" -version = "0.5.1" +version = "1.0.0" description = "Read the Docs theme for Sphinx" category = "dev" optional = false -python-versions = "*" +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" [package.dependencies] -sphinx = "*" +docutils = "<0.18" +sphinx = ">=1.6" [package.extras] dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"] @@ -1519,7 +1601,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" -version = "2.0.0" +version = "2.0.1" description = "A lil' TOML parser" category = "dev" optional = false @@ -1527,7 +1609,7 @@ python-versions = ">=3.7" [[package]] name = "typed-ast" -version = "1.5.2" +version = "1.5.4" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false @@ -1551,14 +1633,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "urllib3" -version = "1.26.8" +version = "1.26.9" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -brotli = ["brotlipy (>=0.6.0)"] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] @@ -1583,9 +1665,9 @@ six = "*" [[package]] name = "wrapt" -version = "1.13.3" +version = "1.14.1" description = "Module for decorators, wrappers and monkey patching." -category = "dev" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" @@ -1621,15 +1703,15 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [[package]] name = "zipp" -version = "3.7.0" +version = "3.8.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "1.1" @@ -1737,8 +1819,8 @@ arrow = [ {file = "arrow-0.17.0.tar.gz", hash = "sha256:ff08d10cda1d36c68657d6ad20d74fbea493d980f8b2d45344e00d6ed2bf6ed4"}, ] astroid = [ - {file = "astroid-2.9.3-py3-none-any.whl", hash = "sha256:506daabe5edffb7e696ad82483ad0228245a9742ed7d2d8c9cdb31537decf9f6"}, - {file = "astroid-2.9.3.tar.gz", hash = "sha256:1efdf4e867d4d8ba4a9f6cf9ce07cd182c4c41de77f23814feb27ca93ca9d877"}, + {file = "astroid-2.11.5-py3-none-any.whl", hash = "sha256:14ffbb4f6aa2cf474a0834014005487f7ecd8924996083ab411e7fa0b508ce0b"}, + {file = "astroid-2.11.5.tar.gz", hash = "sha256:f4e4ec5294c4b07ac38bab9ca5ddd3914d4bf46f9006eb5c0ae755755061044e"}, ] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, @@ -1765,28 +1847,29 @@ babel = [ {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] bcrypt = [ - {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b589229207630484aefe5899122fb938a5b017b0f4349f769b8c13e78d99a8fd"}, - {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6"}, - {file = "bcrypt-3.2.0-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7"}, - {file = "bcrypt-3.2.0-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1"}, - {file = "bcrypt-3.2.0-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d"}, - {file = "bcrypt-3.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a0584a92329210fcd75eb8a3250c5a941633f8bfaf2a18f81009b097732839b7"}, - {file = "bcrypt-3.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:56e5da069a76470679f312a7d3d23deb3ac4519991a0361abc11da837087b61d"}, - {file = "bcrypt-3.2.0-cp36-abi3-win32.whl", hash = "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55"}, - {file = "bcrypt-3.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34"}, - {file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"}, + {file = "bcrypt-3.2.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40"}, + {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa"}, + {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa"}, + {file = "bcrypt-3.2.2-cp36-abi3-win32.whl", hash = "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e"}, + {file = "bcrypt-3.2.2-cp36-abi3-win_amd64.whl", hash = "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129"}, + {file = "bcrypt-3.2.2.tar.gz", hash = "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb"}, ] blessed = [ {file = "blessed-1.19.1-py2.py3-none-any.whl", hash = "sha256:63b8554ae2e0e7f43749b6715c734cc8f3883010a809bf16790102563e6cf25b"}, {file = "blessed-1.19.1.tar.gz", hash = "sha256:9a0d099695bf621d4680dd6c73f6ad547f6a3442fbdbe80c4b1daa1edbc492fc"}, ] cachetools = [ - {file = "cachetools-5.0.0-py3-none-any.whl", hash = "sha256:8fecd4203a38af17928be7b90689d8083603073622229ca7077b72d8e5a976e4"}, - {file = "cachetools-5.0.0.tar.gz", hash = "sha256:486471dfa8799eb7ec503a8059e263db000cdda20075ce5e48903087f79d5fd6"}, + {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"}, + {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"}, ] certifi = [ - {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, - {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, + {file = "certifi-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"}, + {file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"}, ] cffi = [ {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, @@ -1840,13 +1923,9 @@ cffi = [ {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, ] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, -] charset-normalizer = [ - {file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"}, - {file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"}, + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, @@ -1869,69 +1948,71 @@ coolname = [ {file = "coolname-1.1.0.tar.gz", hash = "sha256:410fe6ea9999bf96f2856ef0c726d5f38782bbefb7bb1aca0e91e0dc98ed09e3"}, ] coverage = [ - {file = "coverage-6.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525"}, - {file = "coverage-6.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e"}, - {file = "coverage-6.3.1-cp310-cp310-win32.whl", hash = "sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217"}, - {file = "coverage-6.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb"}, - {file = "coverage-6.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8"}, - {file = "coverage-6.3.1-cp37-cp37m-win32.whl", hash = "sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0"}, - {file = "coverage-6.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687"}, - {file = "coverage-6.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320"}, - {file = "coverage-6.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a"}, - {file = "coverage-6.3.1-cp38-cp38-win32.whl", hash = "sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10"}, - {file = "coverage-6.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f"}, - {file = "coverage-6.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d"}, - {file = "coverage-6.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38"}, - {file = "coverage-6.3.1-cp39-cp39-win32.whl", hash = "sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2"}, - {file = "coverage-6.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa"}, - {file = "coverage-6.3.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2"}, - {file = "coverage-6.3.1.tar.gz", hash = "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8"}, + {file = "coverage-6.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1d5aa2703e1dab4ae6cf416eb0095304f49d004c39e9db1d86f57924f43006b"}, + {file = "coverage-6.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ce1b258493cbf8aec43e9b50d89982346b98e9ffdfaae8ae5793bc112fb0068"}, + {file = "coverage-6.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c4e737f60c6936460c5be330d296dd5b48b3963f48634c53b3f7deb0f34ec4"}, + {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e65ef149028516c6d64461b95a8dbcfce95cfd5b9eb634320596173332ea84"}, + {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69718750eaae75efe506406c490d6fc5a6161d047206cc63ce25527e8a3adad"}, + {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e57816f8ffe46b1df8f12e1b348f06d164fd5219beba7d9433ba79608ef011cc"}, + {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:01c5615d13f3dd3aa8543afc069e5319cfa0c7d712f6e04b920431e5c564a749"}, + {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ab269400706fab15981fd4bd5080c56bd5cc07c3bccb86aab5e1d5a88dc8f4"}, + {file = "coverage-6.4.1-cp310-cp310-win32.whl", hash = "sha256:a7f3049243783df2e6cc6deafc49ea123522b59f464831476d3d1448e30d72df"}, + {file = "coverage-6.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2ddcac99b2d2aec413e36d7a429ae9ebcadf912946b13ffa88e7d4c9b712d6"}, + {file = "coverage-6.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb73e0011b8793c053bfa85e53129ba5f0250fdc0392c1591fd35d915ec75c46"}, + {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106c16dfe494de3193ec55cac9640dd039b66e196e4641fa8ac396181578b982"}, + {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f4f3df85aa39da00fd3ec4b5abeb7407e82b68c7c5ad181308b0e2526da5d4"}, + {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:961e2fb0680b4f5ad63234e0bf55dfb90d302740ae9c7ed0120677a94a1590cb"}, + {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cec3a0f75c8f1031825e19cd86ee787e87cf03e4fd2865c79c057092e69e3a3b"}, + {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:129cd05ba6f0d08a766d942a9ed4b29283aff7b2cccf5b7ce279d50796860bb3"}, + {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bf5601c33213d3cb19d17a796f8a14a9eaa5e87629a53979a5981e3e3ae166f6"}, + {file = "coverage-6.4.1-cp37-cp37m-win32.whl", hash = "sha256:269eaa2c20a13a5bf17558d4dc91a8d078c4fa1872f25303dddcbba3a813085e"}, + {file = "coverage-6.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f02cbbf8119db68455b9d763f2f8737bb7db7e43720afa07d8eb1604e5c5ae28"}, + {file = "coverage-6.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffa9297c3a453fba4717d06df579af42ab9a28022444cae7fa605af4df612d54"}, + {file = "coverage-6.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:145f296d00441ca703a659e8f3eb48ae39fb083baba2d7ce4482fb2723e050d9"}, + {file = "coverage-6.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d44996140af8b84284e5e7d398e589574b376fb4de8ccd28d82ad8e3bea13"}, + {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bd9a6fc18aab8d2e18f89b7ff91c0f34ff4d5e0ba0b33e989b3cd4194c81fd9"}, + {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384f2a3652cef289e38100f2d037956194a837221edd520a7ee5b42d00cc605"}, + {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b3e07152b4563722be523e8cd0b209e0d1a373022cfbde395ebb6575bf6790d"}, + {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1480ff858b4113db2718848d7b2d1b75bc79895a9c22e76a221b9d8d62496428"}, + {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:865d69ae811a392f4d06bde506d531f6a28a00af36f5c8649684a9e5e4a85c83"}, + {file = "coverage-6.4.1-cp38-cp38-win32.whl", hash = "sha256:664a47ce62fe4bef9e2d2c430306e1428ecea207ffd68649e3b942fa8ea83b0b"}, + {file = "coverage-6.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:26dff09fb0d82693ba9e6231248641d60ba606150d02ed45110f9ec26404ed1c"}, + {file = "coverage-6.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9c80df769f5ec05ad21ea34be7458d1dc51ff1fb4b2219e77fe24edf462d6df"}, + {file = "coverage-6.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39ee53946bf009788108b4dd2894bf1349b4e0ca18c2016ffa7d26ce46b8f10d"}, + {file = "coverage-6.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b66caa62922531059bc5ac04f836860412f7f88d38a476eda0a6f11d4724f4"}, + {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd180ed867e289964404051a958f7cccabdeed423f91a899829264bb7974d3d3"}, + {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3"}, + {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c08da0bd238f2970230c2a0d28ff0e99961598cb2e810245d7fc5afcf1254e8"}, + {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d42c549a8f41dc103a8004b9f0c433e2086add8a719da00e246e17cbe4056f72"}, + {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:309ce4a522ed5fca432af4ebe0f32b21d6d7ccbb0f5fcc99290e71feba67c264"}, + {file = "coverage-6.4.1-cp39-cp39-win32.whl", hash = "sha256:fdb6f7bd51c2d1714cea40718f6149ad9be6a2ee7d93b19e9f00934c0f2a74d9"}, + {file = "coverage-6.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:342d4aefd1c3e7f620a13f4fe563154d808b69cccef415415aece4c786665397"}, + {file = "coverage-6.4.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:4803e7ccf93230accb928f3a68f00ffa80a88213af98ed338a57ad021ef06815"}, + {file = "coverage-6.4.1.tar.gz", hash = "sha256:4321f075095a096e70aff1d002030ee612b65a205a0a0f5b815280d5dc58100c"}, ] cryptography = [ - {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b"}, - {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2"}, - {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f"}, - {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3"}, - {file = "cryptography-36.0.1-cp36-abi3-win32.whl", hash = "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca"}, - {file = "cryptography-36.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf"}, - {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9"}, - {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1"}, - {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316"}, - {file = "cryptography-36.0.1.tar.gz", hash = "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638"}, + {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181"}, + {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c"}, + {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178"}, + {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a"}, + {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15"}, + {file = "cryptography-37.0.2-cp36-abi3-win32.whl", hash = "sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0"}, + {file = "cryptography-37.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d"}, + {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9"}, + {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1"}, + {file = "cryptography-37.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023"}, + {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06"}, + {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717"}, + {file = "cryptography-37.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f"}, + {file = "cryptography-37.0.2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982"}, + {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4"}, + {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de"}, + {file = "cryptography-37.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452"}, + {file = "cryptography-37.0.2.tar.gz", hash = "sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e"}, ] cx-freeze = [ {file = "cx_Freeze-6.9-cp310-cp310-win32.whl", hash = "sha256:776d4fb68a4831691acbd3c374362b9b48ce2e568514a73c3d4cb14d5dcf1470"}, @@ -1961,25 +2042,33 @@ cx-logging = [ {file = "cx_Logging-3.0-cp39-cp39-win_amd64.whl", hash = "sha256:302e9c4f65a936c288a4fa59a90e7e142d9ef994aa29676731acafdcccdbb3f5"}, {file = "cx_Logging-3.0.tar.gz", hash = "sha256:ba8a7465facf7b98d8f494030fb481a2e8aeee29dc191e10383bb54ed42bdb34"}, ] +deprecated = [ + {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, + {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, +] +dill = [ + {file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"}, + {file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"}, +] dnspython = [ - {file = "dnspython-2.2.0-py3-none-any.whl", hash = "sha256:081649da27ced5e75709a1ee542136eaba9842a0fe4c03da4fb0a3d3ed1f3c44"}, - {file = "dnspython-2.2.0.tar.gz", hash = "sha256:e79351e032d0b606b98d38a4b0e6e2275b31a5b85c873e587cc11b73aca026d6"}, + {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"}, + {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"}, ] docutils = [ - {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, - {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, + {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, + {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] dropbox = [ - {file = "dropbox-11.26.0-py2-none-any.whl", hash = "sha256:abd29587fa692bde0c3a48ce8efb56c24d8df92c5f402bbcdd78f3089d5ced5c"}, - {file = "dropbox-11.26.0-py3-none-any.whl", hash = "sha256:84becf043a63007ae67620946acb0fa164669ce691c7f1dbfdabb6712a258b29"}, - {file = "dropbox-11.26.0.tar.gz", hash = "sha256:dd79e3dfc216688020959462aefac54ffce7c07a45e89f4fb258348885195a73"}, + {file = "dropbox-11.31.0-py2-none-any.whl", hash = "sha256:393a99dfe30d42fd73c265b9b7d24bb21c9a961739cd097c3541e709eb2a209c"}, + {file = "dropbox-11.31.0-py3-none-any.whl", hash = "sha256:5f924102fd6464def81573320c6aa4ea9cd3368e1b1c13d838403dd4c9ffc919"}, + {file = "dropbox-11.31.0.tar.gz", hash = "sha256:f483d65b702775b9abf7b9328f702c68c6397fc01770477c6ddbfb1d858a5bcf"}, ] enlighten = [ {file = "enlighten-1.10.2-py2.py3-none-any.whl", hash = "sha256:b237fe562b320bf9f1d4bb76d0c98e0daf914372a76ab87c35cd02f57aa9d8c1"}, {file = "enlighten-1.10.2.tar.gz", hash = "sha256:7a5b83cd0f4d095e59d80c648ebb5f7ffca0cd8bcf7ae6639828ee1ad000632a"}, ] evdev = [ - {file = "evdev-1.4.0.tar.gz", hash = "sha256:8782740eb1a86b187334c07feb5127d3faa0b236e113206dfe3ae8f77fb1aaf1"}, + {file = "evdev-1.5.0.tar.gz", hash = "sha256:5b33b174f7c84576e7dd6071e438bf5ad227da95efd4356a39fe4c8355412fe6"}, ] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, @@ -2053,49 +2142,52 @@ ftrack-python-api = [ future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] +gazu = [ + {file = "gazu-0.8.28-py2.py3-none-any.whl", hash = "sha256:ec4f7c2688a2b37ee8a77737e4e30565ad362428c3ade9046136a998c043e51c"}, +] gitdb = [ {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, ] gitpython = [ - {file = "GitPython-3.1.26-py3-none-any.whl", hash = "sha256:26ac35c212d1f7b16036361ca5cff3ec66e11753a0d677fb6c48fa4e1a9dd8d6"}, - {file = "GitPython-3.1.26.tar.gz", hash = "sha256:fc8868f63a2e6d268fb25f481995ba185a85a66fcad126f039323ff6635669ee"}, + {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, + {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, ] google-api-core = [ - {file = "google-api-core-2.4.0.tar.gz", hash = "sha256:ba8787b7c61632cd0340f095e1c036bef9426b2594f10afb290ba311ae8cb2cb"}, - {file = "google_api_core-2.4.0-py2.py3-none-any.whl", hash = "sha256:58e2c1171a3d51778bf4e428fbb4bf79cbd05007b4b44deaa80cf73c80eebc0f"}, + {file = "google-api-core-2.8.1.tar.gz", hash = "sha256:958024c6aa3460b08f35741231076a4dd9a4c819a6a39d44da9627febe8b28f0"}, + {file = "google_api_core-2.8.1-py3-none-any.whl", hash = "sha256:ce1daa49644b50398093d2a9ad886501aa845e2602af70c3001b9f402a9d7359"}, ] google-api-python-client = [ - {file = "google-api-python-client-1.12.10.tar.gz", hash = "sha256:1cb773647e7d97048d9d1c7fa746247fbad39fd1a3b5040f2cb2645dd7156b11"}, - {file = "google_api_python_client-1.12.10-py2.py3-none-any.whl", hash = "sha256:5a8742b9b604b34e13462cc3d6aedbbf11d3af1ef558eb95defe74a29ebc5c28"}, + {file = "google-api-python-client-1.12.11.tar.gz", hash = "sha256:1b4bd42a46321e13c0542a9e4d96fa05d73626f07b39f83a73a947d70ca706a9"}, + {file = "google_api_python_client-1.12.11-py2.py3-none-any.whl", hash = "sha256:7e0a1a265c8d3088ee1987778c72683fcb376e32bada8d7767162bd9c503fd9b"}, ] google-auth = [ - {file = "google-auth-2.6.0.tar.gz", hash = "sha256:ad160fc1ea8f19e331a16a14a79f3d643d813a69534ba9611d2c80dc10439dad"}, - {file = "google_auth-2.6.0-py2.py3-none-any.whl", hash = "sha256:218ca03d7744ca0c8b6697b6083334be7df49b7bf76a69d555962fd1a7657b5f"}, + {file = "google-auth-2.7.0.tar.gz", hash = "sha256:8a954960f852d5f19e6af14dd8e75c20159609e85d8db37e4013cc8c3824a7e1"}, + {file = "google_auth-2.7.0-py2.py3-none-any.whl", hash = "sha256:df549a1433108801b11bdcc0e312eaf0d5f0500db42f0523e4d65c78722e8475"}, ] google-auth-httplib2 = [ {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, {file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"}, ] googleapis-common-protos = [ - {file = "googleapis-common-protos-1.54.0.tar.gz", hash = "sha256:a4031d6ec6c2b1b6dc3e0be7e10a1bd72fb0b18b07ef9be7b51f2c1004ce2437"}, - {file = "googleapis_common_protos-1.54.0-py2.py3-none-any.whl", hash = "sha256:e54345a2add15dc5e1a7891c27731ff347b4c33765d79b5ed7026a6c0c7cbcae"}, + {file = "googleapis-common-protos-1.56.2.tar.gz", hash = "sha256:b09b56f5463070c2153753ef123f07d2e49235e89148e9b2459ec8ed2f68d7d3"}, + {file = "googleapis_common_protos-1.56.2-py2.py3-none-any.whl", hash = "sha256:023eaea9d8c1cceccd9587c6af6c20f33eeeb05d4148670f2b0322dc1511700c"}, ] httplib2 = [ - {file = "httplib2-0.20.2-py3-none-any.whl", hash = "sha256:6b937120e7d786482881b44b8eec230c1ee1c5c1d06bce8cc865f25abbbf713b"}, - {file = "httplib2-0.20.2.tar.gz", hash = "sha256:e404681d2fbcec7506bcb52c503f2b021e95bee0ef7d01e5c221468a2406d8dc"}, + {file = "httplib2-0.20.4-py3-none-any.whl", hash = "sha256:8b6a905cb1c79eefd03f8669fd993c36dc341f7c558f056cb5a33b5c2f458543"}, + {file = "httplib2-0.20.4.tar.gz", hash = "sha256:58a98e45b4b1a48273073f905d2961666ecf0fbac4250ea5b47aef259eb5c585"}, ] idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] imagesize = [ {file = "imagesize-1.3.0-py2.py3-none-any.whl", hash = "sha256:1db2f82529e53c3e929e8926a1fa9235aa82d0bd0c580359c67ec31b2fddaa8c"}, {file = "imagesize-1.3.0.tar.gz", hash = "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.10.1-py3-none-any.whl", hash = "sha256:899e2a40a8c4a1aec681feef45733de8a6c58f3f6a0dbed2eb6574b4387a77b6"}, - {file = "importlib_metadata-4.10.1.tar.gz", hash = "sha256:951f0d8a5b7260e9db5e41d429285b5f451e928479f19d80818878527d36e95e"}, + {file = "importlib_metadata-4.11.4-py3-none-any.whl", hash = "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec"}, + {file = "importlib_metadata-4.11.4.tar.gz", hash = "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -2118,8 +2210,8 @@ jinja2 = [ {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, ] jinxed = [ - {file = "jinxed-1.1.0-py2.py3-none-any.whl", hash = "sha256:6a61ccf963c16aa885304f27e6e5693783676897cea0c7f223270c8b8e78baf8"}, - {file = "jinxed-1.1.0.tar.gz", hash = "sha256:d8f1731f134e9e6b04d95095845ae6c10eb15cb223a5f0cabdea87d4a279c305"}, + {file = "jinxed-1.2.0-py2.py3-none-any.whl", hash = "sha256:cfc2b2e4e3b4326954d546ba6d6b9a7a796ddcb0aef8d03161d005177eb0d48b"}, + {file = "jinxed-1.2.0.tar.gz", hash = "sha256:032acda92d5c57cd216033cbbd53de731e6ed50deb63eb4781336ca55f72cda5"}, ] jsonschema = [ {file = "jsonschema-2.6.0-py2.py3-none-any.whl", hash = "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08"}, @@ -2313,57 +2405,60 @@ packaging = [ {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] paramiko = [ - {file = "paramiko-2.10.1-py2.py3-none-any.whl", hash = "sha256:f6cbd3e1204abfdbcd40b3ecbc9d32f04027cd3080fe666245e21e7540ccfc1b"}, - {file = "paramiko-2.10.1.tar.gz", hash = "sha256:443f4da23ec24e9a9c0ea54017829c282abdda1d57110bf229360775ccd27a31"}, + {file = "paramiko-2.11.0-py2.py3-none-any.whl", hash = "sha256:655f25dc8baf763277b933dfcea101d636581df8d6b9774d1fb653426b72c270"}, + {file = "paramiko-2.11.0.tar.gz", hash = "sha256:003e6bee7c034c21fbb051bf83dc0a9ee4106204dd3c53054c71452cc4ec3938"}, ] parso = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] pathlib2 = [ - {file = "pathlib2-2.3.6-py2.py3-none-any.whl", hash = "sha256:3a130b266b3a36134dcc79c17b3c7ac9634f083825ca6ea9d8f557ee6195c9c8"}, - {file = "pathlib2-2.3.6.tar.gz", hash = "sha256:7d8bcb5555003cdf4a8d2872c538faa3a0f5d20630cb360e518ca3b981795e5f"}, + {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, + {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, ] pillow = [ - {file = "Pillow-9.0.1-1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4"}, - {file = "Pillow-9.0.1-1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976"}, - {file = "Pillow-9.0.1-1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc"}, - {file = "Pillow-9.0.1-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd"}, - {file = "Pillow-9.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f"}, - {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a"}, - {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049"}, - {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a"}, - {file = "Pillow-9.0.1-cp310-cp310-win32.whl", hash = "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e"}, - {file = "Pillow-9.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b"}, - {file = "Pillow-9.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e"}, - {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360"}, - {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b"}, - {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030"}, - {file = "Pillow-9.0.1-cp37-cp37m-win32.whl", hash = "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669"}, - {file = "Pillow-9.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092"}, - {file = "Pillow-9.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204"}, - {file = "Pillow-9.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e"}, - {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c"}, - {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5"}, - {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae"}, - {file = "Pillow-9.0.1-cp38-cp38-win32.whl", hash = "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c"}, - {file = "Pillow-9.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00"}, - {file = "Pillow-9.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838"}, - {file = "Pillow-9.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28"}, - {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c"}, - {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b"}, - {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7"}, - {file = "Pillow-9.0.1-cp39-cp39-win32.whl", hash = "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7"}, - {file = "Pillow-9.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac"}, - {file = "Pillow-9.0.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97"}, - {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56"}, - {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e"}, - {file = "Pillow-9.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70"}, - {file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"}, + {file = "Pillow-9.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:42dfefbef90eb67c10c45a73a9bc1599d4dac920f7dfcbf4ec6b80cb620757fe"}, + {file = "Pillow-9.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffde4c6fabb52891d81606411cbfaf77756e3b561b566efd270b3ed3791fde4e"}, + {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c857532c719fb30fafabd2371ce9b7031812ff3889d75273827633bca0c4602"}, + {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59789a7d06c742e9d13b883d5e3569188c16acb02eeed2510fd3bfdbc1bd1530"}, + {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d45dbe4b21a9679c3e8b3f7f4f42a45a7d3ddff8a4a16109dff0e1da30a35b2"}, + {file = "Pillow-9.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e9ed59d1b6ee837f4515b9584f3d26cf0388b742a11ecdae0d9237a94505d03a"}, + {file = "Pillow-9.1.1-cp310-cp310-win32.whl", hash = "sha256:b3fe2ff1e1715d4475d7e2c3e8dabd7c025f4410f79513b4ff2de3d51ce0fa9c"}, + {file = "Pillow-9.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5b650dbbc0969a4e226d98a0b440c2f07a850896aed9266b6fedc0f7e7834108"}, + {file = "Pillow-9.1.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:0b4d5ad2cd3a1f0d1df882d926b37dbb2ab6c823ae21d041b46910c8f8cd844b"}, + {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9370d6744d379f2de5d7fa95cdbd3a4d92f0b0ef29609b4b1687f16bc197063d"}, + {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b761727ed7d593e49671d1827044b942dd2f4caae6e51bab144d4accf8244a84"}, + {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a66fe50386162df2da701b3722781cbe90ce043e7d53c1fd6bd801bca6b48d4"}, + {file = "Pillow-9.1.1-cp37-cp37m-win32.whl", hash = "sha256:2b291cab8a888658d72b575a03e340509b6b050b62db1f5539dd5cd18fd50578"}, + {file = "Pillow-9.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1d4331aeb12f6b3791911a6da82de72257a99ad99726ed6b63f481c0184b6fb9"}, + {file = "Pillow-9.1.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8844217cdf66eabe39567118f229e275f0727e9195635a15e0e4b9227458daaf"}, + {file = "Pillow-9.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b6617221ff08fbd3b7a811950b5c3f9367f6e941b86259843eab77c8e3d2b56b"}, + {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20d514c989fa28e73a5adbddd7a171afa5824710d0ab06d4e1234195d2a2e546"}, + {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:088df396b047477dd1bbc7de6e22f58400dae2f21310d9e2ec2933b2ef7dfa4f"}, + {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53c27bd452e0f1bc4bfed07ceb235663a1df7c74df08e37fd6b03eb89454946a"}, + {file = "Pillow-9.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3f6c1716c473ebd1649663bf3b42702d0d53e27af8b64642be0dd3598c761fb1"}, + {file = "Pillow-9.1.1-cp38-cp38-win32.whl", hash = "sha256:c67db410508b9de9c4694c57ed754b65a460e4812126e87f5052ecf23a011a54"}, + {file = "Pillow-9.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:f054b020c4d7e9786ae0404278ea318768eb123403b18453e28e47cdb7a0a4bf"}, + {file = "Pillow-9.1.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c17770a62a71718a74b7548098a74cd6880be16bcfff5f937f900ead90ca8e92"}, + {file = "Pillow-9.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3f6a6034140e9e17e9abc175fc7a266a6e63652028e157750bd98e804a8ed9a"}, + {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f372d0f08eff1475ef426344efe42493f71f377ec52237bf153c5713de987251"}, + {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09e67ef6e430f90caa093528bd758b0616f8165e57ed8d8ce014ae32df6a831d"}, + {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66daa16952d5bf0c9d5389c5e9df562922a59bd16d77e2a276e575d32e38afd1"}, + {file = "Pillow-9.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d78ca526a559fb84faaaf84da2dd4addef5edb109db8b81677c0bb1aad342601"}, + {file = "Pillow-9.1.1-cp39-cp39-win32.whl", hash = "sha256:55e74faf8359ddda43fee01bffbc5bd99d96ea508d8a08c527099e84eb708f45"}, + {file = "Pillow-9.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c150dbbb4a94ea4825d1e5f2c5501af7141ea95825fadd7829f9b11c97aaf6c"}, + {file = "Pillow-9.1.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:769a7f131a2f43752455cc72f9f7a093c3ff3856bf976c5fb53a59d0ccc704f6"}, + {file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:488f3383cf5159907d48d32957ac6f9ea85ccdcc296c14eca1a4e396ecc32098"}, + {file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b525a356680022b0af53385944026d3486fc8c013638cf9900eb87c866afb4c"}, + {file = "Pillow-9.1.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6e760cf01259a1c0a50f3c845f9cad1af30577fd8b670339b1659c6d0e7a41dd"}, + {file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4165205a13b16a29e1ac57efeee6be2dfd5b5408122d59ef2145bc3239fa340"}, + {file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937a54e5694684f74dcbf6e24cc453bfc5b33940216ddd8f4cd8f0f79167f765"}, + {file = "Pillow-9.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:baf3be0b9446a4083cc0c5bb9f9c964034be5374b5bc09757be89f5d2fa247b8"}, + {file = "Pillow-9.1.1.tar.gz", hash = "sha256:7502539939b53d7565f3d11d87c78e7ec900d3c72945d4ee0e2f250d598309a0"}, ] platformdirs = [ - {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, - {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, + {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, + {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, @@ -2464,12 +2559,12 @@ pyflakes = [ {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pygments = [ - {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, - {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, + {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, + {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, ] pylint = [ - {file = "pylint-2.12.2-py3-none-any.whl", hash = "sha256:daabda3f7ed9d1c60f52d563b1b854632fd90035bcf01443e234d3dc794e3b74"}, - {file = "pylint-2.12.2.tar.gz", hash = "sha256:9d945a73640e1fec07ee34b42f5669b770c759acd536ec7b16d7e4b87a9c9ff9"}, + {file = "pylint-2.13.9-py3-none-any.whl", hash = "sha256:705c620d388035bdd9ff8b44c5bcdd235bfb49d276d488dd2c8ff1736aa42526"}, + {file = "pylint-2.13.9.tar.gz", hash = "sha256:095567c96e19e6f57b5b907e67d265ff535e588fe26b12b5ebe1fc5645b2c731"}, ] pymongo = [ {file = "pymongo-3.12.3-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:c164eda0be9048f83c24b9b2656900041e069ddf72de81c17d874d0c32f6079f"}, @@ -2598,40 +2693,40 @@ pynput = [ {file = "pynput-1.7.6.tar.gz", hash = "sha256:3a5726546da54116b687785d38b1db56997ce1d28e53e8d22fc656d8b92e533c"}, ] pyobjc-core = [ - {file = "pyobjc-core-8.2.tar.gz", hash = "sha256:6afb8ee1dd0647cbfaaf99906eca3b43ce045b27e3d4510462d04e7e5361c89b"}, - {file = "pyobjc_core-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:311e45556c3afa8831713b89017e0204562f51f35661d76c07ffe04985f44e1d"}, - {file = "pyobjc_core-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:711f361e83382e405e4273ff085178b0cf730901ccf6801f834e7037e50278f9"}, - {file = "pyobjc_core-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bdd2e2960ec73214bcfe2d86bb4ca94f5f5119db86d129fa32d3c003b6532c50"}, - {file = "pyobjc_core-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b879f91fc614c399aafa1d08cf5e279c267510e904bad5c336c3a6064c0eb3aa"}, - {file = "pyobjc_core-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:b4ef4bdb99a330f5e15cc6273098964276fccbc432453cdba3c2963292bc066c"}, - {file = "pyobjc_core-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fd8e5be0955790ff8f9d17a0f356b6eb7eb1ce4995e0c94355c462dd52d22d6d"}, + {file = "pyobjc-core-8.5.tar.gz", hash = "sha256:704c275439856c0d1287469f0d589a7d808d48b754a93d9ce5415d4eaf06d576"}, + {file = "pyobjc_core-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0c234143b48334443f5adcf26e668945a6d47bc1fa6223e80918c6c735a029d9"}, + {file = "pyobjc_core-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1486ee533f0d76f666804ce89723ada4db56bfde55e56151ba512d3f849857f8"}, + {file = "pyobjc_core-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:412de06dfa728301c04b3e46fd7453320a8ae8b862e85236e547cd797a73b490"}, + {file = "pyobjc_core-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b3e09cccb1be574a82cc9f929ae27fc4283eccc75496cb5d51534caa6bb83a3"}, + {file = "pyobjc_core-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:eeafe21f879666ab7f57efcc6b007c9f5f8733d367b7e380c925203ed83f000d"}, + {file = "pyobjc_core-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c0071686976d7ea8c14690950e504a13cb22b4ebb2bc7b5ec47c1c1c0f6eff41"}, ] pyobjc-framework-applicationservices = [ - {file = "pyobjc-framework-ApplicationServices-8.2.tar.gz", hash = "sha256:f901b2ebb278b7d00033b45cf9ee9d43f651e096ff4c4defa261509238138da8"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b5be1757a8df944a58449c628ed68fc76dd746fcd1e96ebb6db0f734e94cdfc8"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:32657c4b89983a98fbe831a14884954710719e763197968a20ac07e1daa21f1e"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ce2c9701590f752a75151b2fe1e462e7a7ad67dec3119a796711ea93ea01031"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28124bc892045222305cf8953b163495bf662c401e54f48905dafb1104d9c1d1"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d5c60d66c6ed2569cb042a6c14dd5b9f4d01e182a464b893cd6002ce445b4ddf"}, - {file = "pyobjc_framework_ApplicationServices-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e1727fccc1d48922fa28a4cebf4a6d287d633f451cb03779968df60de8cb1bd0"}, + {file = "pyobjc-framework-ApplicationServices-8.5.tar.gz", hash = "sha256:fa3015ef8e3add90af3447d7fdcc7f8dd083cc2a1d58f99a569480a2df10d2b1"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:436b16ebe448a829a8312e10208eec81a2adcae1fff674dbcc3262e1bd76e0ca"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:408958d14aa7fcf46f2163754c211078bc63be1368934d86188202914dce077d"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1d6cd4ce192859a22e208da4d7177a1c3ceb1ef2f64c339fd881102b1210cadd"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0251d092adb1d2d116fd9f147ceef0e53b158a46c21245131c40b9d7b786d0db"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:9742e69fe6d4545d0e02b0ad0a7a2432bc9944569ee07d6e90ffa5ef614df9f7"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16f5677c14ea903c6aaca1dd121521825c39e816cae696d6ae32c0b287252ab2"}, ] pyobjc-framework-cocoa = [ - {file = "pyobjc-framework-Cocoa-8.2.tar.gz", hash = "sha256:f0901998e2f18415ef6d1f8a12b083f69fc93bd56b3e88040002e3c09bd8c304"}, - {file = "pyobjc_framework_Cocoa-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5af73f150e242542735e0663bb5504f88aabaec2a54c60e856cfca3ff6dd9712"}, - {file = "pyobjc_framework_Cocoa-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d1de3763ee01850c311da74de5c82c85ec199120e85ab45acaf203accc37a470"}, - {file = "pyobjc_framework_Cocoa-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:86d69bf667f99f3c43184d8830567195fff94c675fe7f60f899dd90553d9b265"}, - {file = "pyobjc_framework_Cocoa-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9dbadb22826392c48b00087359f66579f8404a4f4f77498f31f9869c54bb0fa9"}, - {file = "pyobjc_framework_Cocoa-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7adf8b57da1c1292c24375b8e74b6dd45f99a4d3c10ba925a9b38f63a97ba782"}, - {file = "pyobjc_framework_Cocoa-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc8279c8c1544087d46a7e99324f093029df89b8c527c5da2a682bad4cb3197e"}, + {file = "pyobjc-framework-Cocoa-8.5.tar.gz", hash = "sha256:569bd3a020f64b536fb2d1c085b37553e50558c9f907e08b73ffc16ae68e1861"}, + {file = "pyobjc_framework_Cocoa-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7a7c160416696bf6035dfcdf0e603aaa52858d6afcddfcc5ab41733619ac2529"}, + {file = "pyobjc_framework_Cocoa-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6ceba444282030be8596b812260e8d28b671254a51052ad778d32da6e17db847"}, + {file = "pyobjc_framework_Cocoa-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f46b2b161b8dd40c7b9e00bc69636c3e6480b2704a69aee22ee0154befbe163a"}, + {file = "pyobjc_framework_Cocoa-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b31d425aee8698cbf62b187338f5ca59427fa4dca2153a73866f7cb410713119"}, + {file = "pyobjc_framework_Cocoa-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:898359ac1f76eedec8aa156847682378a8950824421c40edb89391286e607dc4"}, + {file = "pyobjc_framework_Cocoa-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:baa2947f76b119a3360973d74d57d6dada87ac527bab9a88f31596af392f123c"}, ] pyobjc-framework-quartz = [ - {file = "pyobjc-framework-Quartz-8.2.tar.gz", hash = "sha256:219d8797235bf071723f8b0f30a681de0b12875e2d04ae902a3a269f72de0b66"}, - {file = "pyobjc_framework_Quartz-8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e5ab3117201a494b11bb1b80febf5dd288111a196b35731815b656ae30ee6ce3"}, - {file = "pyobjc_framework_Quartz-8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4941b3039ab7bcf89cb4255c381358de2383c1ab45c9e00c3b64655f271a3b32"}, - {file = "pyobjc_framework_Quartz-8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ab75a50dea84ec794641522d9f035fc6c19ec4eb37a56c9186b9943575fbc7ab"}, - {file = "pyobjc_framework_Quartz-8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8cb687b20ebfc467034868b38945b573d1c50f237e379cf86c4f557c9766b759"}, - {file = "pyobjc_framework_Quartz-8.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:8b5c3ca1fef900fa66aafe82fff07bc352c60ea7dceed2bb9b5b1db0957b4fea"}, - {file = "pyobjc_framework_Quartz-8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7bca7e649da77056348d71032def44e345043319d71b0c592197888d92e3eec1"}, + {file = "pyobjc-framework-Quartz-8.5.tar.gz", hash = "sha256:d2bc5467a792ddc04814f12a1e9c2fcaf699a1c3ad3d4264cfdce6b9c7b10624"}, + {file = "pyobjc_framework_Quartz-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e9f0fb663f7872c9de94169031ac42b91ad01bd4cad49a9f1a0164be8f028426"}, + {file = "pyobjc_framework_Quartz-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:567eec91287cfe9a1b6433717192c585935de8f3daa28d82ce72fdd6c7ac00f6"}, + {file = "pyobjc_framework_Quartz-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f910ab41a712ffc7a8c3e3716a2d6f39ea4419004b26a2fd2d2f740ff5c262c"}, + {file = "pyobjc_framework_Quartz-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:29d07066781628278bf0e5278abcfc96ef6724c66c5629a0b4c214d319a82e55"}, + {file = "pyobjc_framework_Quartz-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:72abcde1a3d72be11f2c881c9b9872044c8f2de86d2047b67fe771713638b107"}, + {file = "pyobjc_framework_Quartz-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8809b9a2df2f461697bdb45b6d1b5a4f881f88f09450e3990858e64e3e26c530"}, ] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, @@ -2656,6 +2751,14 @@ python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] +python-engineio = [ + {file = "python-engineio-3.14.2.tar.gz", hash = "sha256:eab4553f2804c1ce97054c8b22cf0d5a9ab23128075248b97e1a5b2f29553085"}, + {file = "python_engineio-3.14.2-py2.py3-none-any.whl", hash = "sha256:5a9e6086d192463b04a1428ff1f85b6ba631bbb19d453b144ffc04f530542b84"}, +] +python-socketio = [ + {file = "python-socketio-4.6.1.tar.gz", hash = "sha256:cd1f5aa492c1eb2be77838e837a495f117e17f686029ebc03d62c09e33f4fa10"}, + {file = "python_socketio-4.6.1-py2.py3-none-any.whl", hash = "sha256:5a21da53fdbdc6bb6c8071f40e13d100e0b279ad997681c2492478e06f370523"}, +] python-xlib = [ {file = "python-xlib-0.31.tar.gz", hash = "sha256:74d83a081f532bc07f6d7afcd6416ec38403d68f68b9b9dc9e1f28fbf2d799e9"}, {file = "python_xlib-0.31-py2.py3-none-any.whl", hash = "sha256:1ec6ce0de73d9e6592ead666779a5732b384e5b8fb1f1886bd0a81cafa477759"}, @@ -2664,8 +2767,8 @@ python3-xlib = [ {file = "python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8"}, ] pytz = [ - {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, - {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, + {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, + {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, ] pywin32 = [ {file = "pywin32-301-cp35-cp35m-win32.whl", hash = "sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7"}, @@ -2684,8 +2787,8 @@ pywin32-ctypes = [ {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, ] "qt.py" = [ - {file = "Qt.py-1.3.6-py2.py3-none-any.whl", hash = "sha256:7edf6048d07a6924707506b5ba34a6e05d66dde9a3f4e3a62f9996ccab0b91c7"}, - {file = "Qt.py-1.3.6.tar.gz", hash = "sha256:0d78656a2f814602eee304521c7bf5da0cec414818b3833712c77524294c404a"}, + {file = "Qt.py-1.3.7-py2.py3-none-any.whl", hash = "sha256:150099d1c6f64c9621a2c9d79d45102ec781c30ee30ee69fc082c6e9be7324fe"}, + {file = "Qt.py-1.3.7.tar.gz", hash = "sha256:803c7bdf4d6230f9a466be19d55934a173eabb61406d21cb91e80c2a3f773b1f"}, ] qtawesome = [ {file = "QtAwesome-0.7.3-py2.py3-none-any.whl", hash = "sha256:ddf4530b4af71cec13b24b88a4cdb56ec85b1e44c43c42d0698804c7137b09b0"}, @@ -2700,16 +2803,16 @@ recommonmark = [ {file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"}, ] requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] rsa = [ {file = "rsa-4.8-py3-none-any.whl", hash = "sha256:95c5d300c4e879ee69708c428ba566c59478fd653cc3a22243eeb8ed846950bb"}, {file = "rsa-4.8.tar.gz", hash = "sha256:5c6bd9dc7a543b7fe4304a631f8a8a3b674e2bbfc49c2ae96200cdbe55df6b17"}, ] secretstorage = [ - {file = "SecretStorage-3.3.1-py3-none-any.whl", hash = "sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f"}, - {file = "SecretStorage-3.3.1.tar.gz", hash = "sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195"}, + {file = "SecretStorage-3.3.2-py3-none-any.whl", hash = "sha256:755dc845b6ad76dcbcbc07ea3da75ae54bb1ea529eb72d15f83d26499a5df319"}, + {file = "SecretStorage-3.3.2.tar.gz", hash = "sha256:0a8eb9645b320881c222e827c26f4cfcf55363e8b374a021981ef886657a912f"}, ] semver = [ {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, @@ -2720,8 +2823,8 @@ six = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] slack-sdk = [ - {file = "slack_sdk-3.13.0-py2.py3-none-any.whl", hash = "sha256:54f2a5f7419f1ab932af9e3200f7f2f93db96e0f0eb8ad7d3b4214aa9f124641"}, - {file = "slack_sdk-3.13.0.tar.gz", hash = "sha256:aae6ce057e286a5e7fe7a9f256e85b886eee556def8e04b82b08f699e64d7f67"}, + {file = "slack_sdk-3.17.0-py2.py3-none-any.whl", hash = "sha256:0816efc43d1d2db8286e8dbcbb2e86fd0f71c206c01c521c2cb054ecb40f9ced"}, + {file = "slack_sdk-3.17.0.tar.gz", hash = "sha256:860cd0e50c454b955f14321c8c5486a47cc1e0e84116acdb009107f836752feb"}, ] smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, @@ -2732,20 +2835,20 @@ snowballstemmer = [ {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] speedcopy = [ - {file = "speedcopy-2.1.2-py3-none-any.whl", hash = "sha256:91e271b84c00952812dbf669d360b2610fd8fa198670373e02acf2a04db89a4c"}, - {file = "speedcopy-2.1.2.tar.gz", hash = "sha256:1b2d779fadebd53a59384f7d286c40b2ef382e0d000fa53011699fcd3190d33f"}, + {file = "speedcopy-2.1.4-py3-none-any.whl", hash = "sha256:e09eb1de67ae0e0b51d5b99a28882009d565a37a3cb3c6bae121e3a5d3cccb17"}, + {file = "speedcopy-2.1.4.tar.gz", hash = "sha256:eff007a97e49ec1934df4fa8074f4bd1cf4a3b14c5499d914988785cff0c199a"}, ] sphinx = [ - {file = "Sphinx-3.5.3-py3-none-any.whl", hash = "sha256:3f01732296465648da43dec8fb40dc451ba79eb3e2cc5c6d79005fd98197107d"}, - {file = "Sphinx-3.5.3.tar.gz", hash = "sha256:ce9c228456131bab09a3d7d10ae58474de562a6f79abb3dc811ae401cf8c1abc"}, + {file = "Sphinx-5.0.1-py3-none-any.whl", hash = "sha256:36aa2a3c2f6d5230be94585bc5d74badd5f9ed8f3388b8eedc1726fe45b1ad30"}, + {file = "Sphinx-5.0.1.tar.gz", hash = "sha256:f4da1187785a5bc7312cc271b0e867a93946c319d106363e102936a3d9857306"}, ] sphinx-qt-documentation = [ - {file = "sphinx_qt_documentation-0.3-py3-none-any.whl", hash = "sha256:bee247cb9e4fc03fc496d07adfdb943100e1103320c3e5e820e0cfa7c790d9b6"}, - {file = "sphinx_qt_documentation-0.3.tar.gz", hash = "sha256:f09a0c9d9e989172ba3e282b92bf55613bb23ad47315ec5b0d38536b343ac6c8"}, + {file = "sphinx_qt_documentation-0.4-py3-none-any.whl", hash = "sha256:fa131093f75cd1bd48699cd132e18e4d46ba9eaadc070e6026867cea75ecdb7b"}, + {file = "sphinx_qt_documentation-0.4.tar.gz", hash = "sha256:f43ba17baa93e353fb94045027fb67f9d935ed158ce8662de93f08b88eec6774"}, ] sphinx-rtd-theme = [ - {file = "sphinx_rtd_theme-0.5.1-py2.py3-none-any.whl", hash = "sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113"}, - {file = "sphinx_rtd_theme-0.5.1.tar.gz", hash = "sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5"}, + {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, + {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, @@ -2788,34 +2891,34 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ - {file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"}, - {file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"}, + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] typed-ast = [ - {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"}, - {file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"}, - {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"}, - {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"}, - {file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"}, - {file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"}, - {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"}, - {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"}, - {file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"}, - {file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"}, - {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"}, - {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"}, - {file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"}, - {file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"}, - {file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"}, - {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"}, - {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"}, - {file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"}, - {file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"}, - {file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"}, - {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"}, - {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"}, - {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, - {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] typing-extensions = [ {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, @@ -2826,8 +2929,8 @@ uritemplate = [ {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, ] urllib3 = [ - {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, - {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, + {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, + {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -2838,57 +2941,70 @@ websocket-client = [ {file = "websocket_client-0.59.0-py2.py3-none-any.whl", hash = "sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32"}, ] wrapt = [ - {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca"}, - {file = "wrapt-1.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44"}, - {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056"}, - {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785"}, - {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096"}, - {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33"}, - {file = "wrapt-1.13.3-cp310-cp310-win32.whl", hash = "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f"}, - {file = "wrapt-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755"}, - {file = "wrapt-1.13.3-cp35-cp35m-win32.whl", hash = "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851"}, - {file = "wrapt-1.13.3-cp35-cp35m-win_amd64.whl", hash = "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13"}, - {file = "wrapt-1.13.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918"}, - {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade"}, - {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc"}, - {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf"}, - {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125"}, - {file = "wrapt-1.13.3-cp36-cp36m-win32.whl", hash = "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36"}, - {file = "wrapt-1.13.3-cp36-cp36m-win_amd64.whl", hash = "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10"}, - {file = "wrapt-1.13.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068"}, - {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709"}, - {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df"}, - {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2"}, - {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b"}, - {file = "wrapt-1.13.3-cp37-cp37m-win32.whl", hash = "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829"}, - {file = "wrapt-1.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"}, - {file = "wrapt-1.13.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9"}, - {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554"}, - {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c"}, - {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b"}, - {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce"}, - {file = "wrapt-1.13.3-cp38-cp38-win32.whl", hash = "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79"}, - {file = "wrapt-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb"}, - {file = "wrapt-1.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb"}, - {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32"}, - {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7"}, - {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e"}, - {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640"}, - {file = "wrapt-1.13.3-cp39-cp39-win32.whl", hash = "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374"}, - {file = "wrapt-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb"}, - {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, + {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, + {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, + {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, + {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, + {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, + {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, + {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, + {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, + {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, + {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, + {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, + {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, + {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, + {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, + {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, + {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, ] wsrpc-aiohttp = [ {file = "wsrpc-aiohttp-3.2.0.tar.gz", hash = "sha256:f467abc51bcdc760fc5aeb7041abdeef46eeca3928dc43dd6e7fa7a533563818"}, @@ -2969,6 +3085,6 @@ yarl = [ {file = "yarl-1.7.2.tar.gz", hash = "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd"}, ] zipp = [ - {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, - {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, + {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, + {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, ] From e5fbdd1d43ee2a51a0bfec38d3859c5b8b20a080 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 15:11:26 +0200 Subject: [PATCH 0729/1227] added shotgun-api3 source files to poetry lock --- poetry.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/poetry.lock b/poetry.lock index 3e6620b4a2..f6ccf1ffc9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2818,6 +2818,7 @@ semver = [ {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, ] +shotgun-api3 = [] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, From 8ac3ed77d55def1ea674009cf630783059ea7823 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 20 Jun 2022 15:19:26 +0200 Subject: [PATCH 0730/1227] Nuke: fix none existing rendered files error addressing the comment https://github.com/pypeclub/OpenPype/pull/3245#pullrequestreview-1004589515 --- .../plugins/publish/extract_slate_frame.py | 49 +++++++++++++++---- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index 87cb333ae1..e0c4bdb953 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -74,6 +74,30 @@ class ExtractSlateFrame(openpype.api.Extractor): self.log.info( "StagingDir `{0}`...".format(instance.data["stagingDir"])) + def _check_frames_exists(self, instance): + # rendering path from group write node + fpath = instance.data["path"] + + # instance frame range with handles + first = instance.data["frameStartHandle"] + last = instance.data["frameEndHandle"] + + padding = fpath.count('#') + + test_path_template = fpath + if padding: + repl_string = "#" * padding + test_path_template = fpath.replace( + repl_string, "%0{}d".format(padding)) + + for frame in range(first, last + 1): + test_file = test_path_template % frame + if not os.path.exists(test_file): + self.log.debug("__ test_file: `{}`".format(test_file)) + return None + + return True + def render_slate( self, instance, @@ -128,16 +152,21 @@ class ExtractSlateFrame(openpype.api.Extractor): self.log.debug("__ first_frame: {}".format(first_frame)) self.log.debug("__ slate_first_frame: {}".format(slate_first_frame)) - # Read node - r_node = nuke.createNode("Read") - r_node["file"].setValue(fpath) - r_node["first"].setValue(first_frame) - r_node["origfirst"].setValue(first_frame) - r_node["last"].setValue(last_frame) - r_node["origlast"].setValue(last_frame) - r_node["colorspace"].setValue(instance.data["colorspace"]) - previous_node = r_node - temporary_nodes = [previous_node] + # fallback if files does not exists + if self._check_frames_exists(instance): + # Read node + r_node = nuke.createNode("Read") + r_node["file"].setValue(fpath) + r_node["first"].setValue(first_frame) + r_node["origfirst"].setValue(first_frame) + r_node["last"].setValue(last_frame) + r_node["origlast"].setValue(last_frame) + r_node["colorspace"].setValue(instance.data["colorspace"]) + previous_node = r_node + temporary_nodes = [previous_node] + else: + previous_node = slate_node.dependencies().pop() + temporary_nodes = [] # only create colorspace baking if toggled on if bake_viewer_process: From 497e4c0cca40ee8e39b03ec00c08a594ffc76dbd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 18:31:29 +0200 Subject: [PATCH 0731/1227] use query functions in aftereffects --- .../aftereffects/plugins/create/workfile_creator.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/workfile_creator.py b/openpype/hosts/aftereffects/plugins/create/workfile_creator.py index 88e55e21b5..badb3675fd 100644 --- a/openpype/hosts/aftereffects/plugins/create/workfile_creator.py +++ b/openpype/hosts/aftereffects/plugins/create/workfile_creator.py @@ -1,4 +1,5 @@ import openpype.hosts.aftereffects.api as api +from openpype.client import get_asset_by_name from openpype.pipeline import ( AutoCreator, CreatedInstance, @@ -41,10 +42,7 @@ class AEWorkfileCreator(AutoCreator): host_name = legacy_io.Session["AVALON_APP"] if existing_instance is None: - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( variant, task_name, asset_doc, project_name, host_name ) @@ -69,10 +67,7 @@ class AEWorkfileCreator(AutoCreator): existing_instance["asset"] != asset_name or existing_instance["task"] != task_name ): - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( variant, task_name, asset_doc, project_name, host_name ) From b9a67fb80117e980462222f733df5f64968b16b0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 18:43:14 +0200 Subject: [PATCH 0732/1227] pass asset document to 'get_asset_settings' --- openpype/hosts/aftereffects/api/pipeline.py | 4 ++-- .../plugins/publish/validate_scene_settings.py | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/aftereffects/api/pipeline.py b/openpype/hosts/aftereffects/api/pipeline.py index a428a1470d..0bc47665b0 100644 --- a/openpype/hosts/aftereffects/api/pipeline.py +++ b/openpype/hosts/aftereffects/api/pipeline.py @@ -65,14 +65,14 @@ def on_pyblish_instance_toggled(instance, old_value, new_value): instance[0].Visible = new_value -def get_asset_settings(): +def get_asset_settings(asset_doc): """Get settings on current asset from database. Returns: dict: Scene data. """ - asset_data = lib.get_asset()["data"] + asset_data = asset_doc["data"] fps = asset_data.get("fps") frame_start = asset_data.get("frameStart") frame_end = asset_data.get("frameEnd") diff --git a/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py b/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py index 6fe63fc41e..78f98d7445 100644 --- a/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py +++ b/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py @@ -1,5 +1,9 @@ # -*- coding: utf-8 -*- -"""Validate scene settings.""" +"""Validate scene settings. +Requires: + instance -> assetEntity + instance -> anatomyData +""" import os import re @@ -67,7 +71,8 @@ class ValidateSceneSettings(OptionalPyblishPluginMixin, if not self.is_active(instance.data): return - expected_settings = get_asset_settings() + asset_doc = instance.data["assetEntity"] + expected_settings = get_asset_settings(asset_doc) self.log.info("config from DB::{}".format(expected_settings)) task_name = instance.data["anatomyData"]["task"]["name"] From d39481042dd7fde38a57acf466da8a23fe877ad3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 18:50:26 +0200 Subject: [PATCH 0733/1227] use query functions in photoshop creator --- .../photoshop/plugins/create/workfile_creator.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/create/workfile_creator.py b/openpype/hosts/photoshop/plugins/create/workfile_creator.py index 875a9b8a94..43302329f1 100644 --- a/openpype/hosts/photoshop/plugins/create/workfile_creator.py +++ b/openpype/hosts/photoshop/plugins/create/workfile_creator.py @@ -1,4 +1,5 @@ import openpype.hosts.photoshop.api as api +from openpype.client import get_asset_by_name from openpype.pipeline import ( AutoCreator, CreatedInstance, @@ -40,10 +41,7 @@ class PSWorkfileCreator(AutoCreator): task_name = legacy_io.Session["AVALON_TASK"] host_name = legacy_io.Session["AVALON_APP"] if existing_instance is None: - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( variant, task_name, asset_doc, project_name, host_name ) @@ -67,10 +65,7 @@ class PSWorkfileCreator(AutoCreator): existing_instance["asset"] != asset_name or existing_instance["task"] != task_name ): - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( variant, task_name, asset_doc, project_name, host_name ) From d3afa478b71974fff455669a394eaff3bca3468e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 19:01:47 +0200 Subject: [PATCH 0734/1227] use query functions in celaction --- .../plugins/publish/collect_audio.py | 47 +++++++------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/celaction/plugins/publish/collect_audio.py b/openpype/hosts/celaction/plugins/publish/collect_audio.py index 8acda5fc7c..c6e3bf2c03 100644 --- a/openpype/hosts/celaction/plugins/publish/collect_audio.py +++ b/openpype/hosts/celaction/plugins/publish/collect_audio.py @@ -4,6 +4,11 @@ from pprint import pformat import pyblish.api +from openpype.client import ( + get_subsets, + get_last_versions, + get_representations +) from openpype.pipeline import legacy_io @@ -60,10 +65,10 @@ class AppendCelactionAudio(pyblish.api.ContextPlugin): """ # Query all subsets for asset - subset_docs = legacy_io.find({ - "type": "subset", - "parent": asset_doc["_id"] - }) + project_name = legacy_io.active_project() + subset_docs = get_subsets( + project_name, asset_ids=[asset_doc["_id"]], fields=["_id"] + ) # Collect all subset ids subset_ids = [ subset_doc["_id"] @@ -76,37 +81,19 @@ class AppendCelactionAudio(pyblish.api.ContextPlugin): "Try this for start `r'.*'`: asset: `{}`" ).format(asset_doc["name"]) - # Last version aggregation - pipeline = [ - # Find all versions of those subsets - {"$match": { - "type": "version", - "parent": {"$in": subset_ids} - }}, - # Sorting versions all together - {"$sort": {"name": 1}}, - # Group them by "parent", but only take the last - {"$group": { - "_id": "$parent", - "_version_id": {"$last": "$_id"}, - "name": {"$last": "$name"} - }} - ] - last_versions_by_subset_id = dict() - for doc in legacy_io.aggregate(pipeline): - doc["parent"] = doc["_id"] - doc["_id"] = doc.pop("_version_id") - last_versions_by_subset_id[doc["parent"]] = doc + last_versions_by_subset_id = get_last_versions( + project_name, subset_ids, fields=["_id", "parent"] + ) version_docs_by_id = {} for version_doc in last_versions_by_subset_id.values(): version_docs_by_id[version_doc["_id"]] = version_doc - repre_docs = legacy_io.find({ - "type": "representation", - "parent": {"$in": list(version_docs_by_id.keys())}, - "name": {"$in": representations} - }) + repre_docs = get_representations( + project_name, + version_ids=version_docs_by_id.keys(), + representation_names=representations + ) repre_docs_by_version_id = collections.defaultdict(list) for repre_doc in repre_docs: version_id = repre_doc["parent"] From a93b978f354b6ed34034f0f4caaa98b8c637468e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 20 Jun 2022 21:48:08 +0200 Subject: [PATCH 0735/1227] flame: fixing thumbnail duplication issue --- .../hosts/flame/plugins/publish/extract_subset_resources.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 5e0a5e344d..dd672ec375 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -86,7 +86,11 @@ class ExtractSubsetResources(openpype.api.Extractor): # add default preset type for thumbnail and reviewable video # update them with settings and override in case the same # are found in there - export_presets = deepcopy(self.default_presets) + _preset_keys = [k.split('_')[0] for k in self.export_presets_mapping] + export_presets = { + k: v for k, v in deepcopy(self.default_presets) + if k not in _preset_keys + } export_presets.update(self.export_presets_mapping) # loop all preset names and From 70d9b6fcb73c7ac3abb74d2218fcf2e53e845427 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 20 Jun 2022 22:00:41 +0200 Subject: [PATCH 0736/1227] flame: fixing dict iter with items --- .../hosts/flame/plugins/publish/extract_subset_resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index dd672ec375..1b6900e405 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -88,7 +88,7 @@ class ExtractSubsetResources(openpype.api.Extractor): # are found in there _preset_keys = [k.split('_')[0] for k in self.export_presets_mapping] export_presets = { - k: v for k, v in deepcopy(self.default_presets) + k: v for k, v in deepcopy(self.default_presets).items() if k not in _preset_keys } export_presets.update(self.export_presets_mapping) From 250f73656ac382e7d9bb441326bbd6f55118e0eb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 20 Jun 2022 22:04:36 +0200 Subject: [PATCH 0737/1227] Flame: fixing NoneType in abs --- .../flame/plugins/publish/collect_timeline_instances.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py index aa19b78bf1..b8489de758 100644 --- a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py @@ -1,4 +1,5 @@ import re +from types import NoneType import pyblish import openpype.hosts.flame.api as opfapi from openpype.hosts.flame.otio import flame_export @@ -75,6 +76,12 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): marker_data["handleEnd"] ) + # make sure there is not NoneType rather 0 + if isinstance(head, NoneType): + head = 0 + if isinstance(tail, NoneType): + tail = 0 + # make sure value is absolute if head != 0: head = abs(head) From a29a9af927c339acac56711351a3b995846e4111 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 20 Jun 2022 22:11:15 +0200 Subject: [PATCH 0738/1227] flame: unique name swapped with repre name unique name could be more than `thumbnail` --- .../flame/plugins/publish/extract_subset_resources.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 1b6900e405..3ae8779398 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -228,7 +228,11 @@ class ExtractSubsetResources(openpype.api.Extractor): # make sure only first segment is used if underscore in name # HACK: `ftrackreview_withLUT` will result only in `ftrackreview` - repr_name = unique_name.split("_")[0] + if ( + "thumbnail" in unique_name + or "ftrackreview" in unique_name + ): + repr_name = unique_name.split("_")[0] # create representation data representation_data = { @@ -267,7 +271,7 @@ class ExtractSubsetResources(openpype.api.Extractor): if os.path.splitext(f)[-1] == ".mov" ] # then try if thumbnail is not in unique name - or unique_name == "thumbnail" + or repr_name == "thumbnail" ): representation_data["files"] = files.pop() else: From 035b202b58fc3c8a2e0f381ce4856a4f551e18a4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 20 Jun 2022 22:19:05 +0200 Subject: [PATCH 0739/1227] Flame: fixing repr_name missing --- openpype/hosts/flame/plugins/publish/extract_subset_resources.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 3ae8779398..d34f5d5854 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -226,6 +226,7 @@ class ExtractSubsetResources(openpype.api.Extractor): opfapi.export_clip( export_dir_path, exporting_clip, preset_path, **export_kwargs) + repr_name = unique_name # make sure only first segment is used if underscore in name # HACK: `ftrackreview_withLUT` will result only in `ftrackreview` if ( From 144ef08260f58fcbc3eff56f80073db46432c055 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Tue, 21 Jun 2022 08:29:04 +0200 Subject: [PATCH 0740/1227] change default settings --- openpype/settings/defaults/system_settings/modules.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index d55691d7a2..9d8910689a 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -132,8 +132,7 @@ } }, "shotgrid": { - "enabled": true, - "filter_projects_by_login": true, + "enabled": false, "leecher_manager_url": "http://127.0.0.1:3000", "leecher_backend_url": "http://127.0.0.1:8090", "shotgrid_settings": {} From fa8d37d9b68ebb1c1ac85ec6bd8c00fd348e4f80 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 10:55:07 +0200 Subject: [PATCH 0741/1227] use query functions in harmony --- openpype/hosts/harmony/api/README.md | 3 ++- openpype/hosts/harmony/api/pipeline.py | 18 ++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/harmony/api/README.md b/openpype/hosts/harmony/api/README.md index dd45eb14dd..b39f900886 100644 --- a/openpype/hosts/harmony/api/README.md +++ b/openpype/hosts/harmony/api/README.md @@ -610,7 +610,8 @@ class ImageSequenceLoader(load.LoaderPlugin): def update(self, container, representation): node = container.pop("node") - version = legacy_io.find_one({"_id": representation["parent"]}) + project_name = legacy_io.active_project() + version = get_version_by_id(project_name, representation["parent"]) files = [] for f in version["data"]["files"]: files.append( diff --git a/openpype/hosts/harmony/api/pipeline.py b/openpype/hosts/harmony/api/pipeline.py index b953d0e984..86b5753f7e 100644 --- a/openpype/hosts/harmony/api/pipeline.py +++ b/openpype/hosts/harmony/api/pipeline.py @@ -2,10 +2,10 @@ import os from pathlib import Path import logging -from bson.objectid import ObjectId import pyblish.api from openpype import lib +from openpype.client import get_representation_by_id from openpype.lib import register_event_callback from openpype.pipeline import ( legacy_io, @@ -104,22 +104,20 @@ def check_inventory(): If it does it will colorize outdated nodes and display warning message in Harmony. """ - if not lib.any_outdated(): - return + project_name = legacy_io.active_project() outdated_containers = [] for container in ls(): - representation = container['representation'] - representation_doc = legacy_io.find_one( - { - "_id": ObjectId(representation), - "type": "representation" - }, - projection={"parent": True} + representation_id = container['representation'] + representation_doc = get_representation_by_id( + project_name, representation_id, fields=["parent"] ) if representation_doc and not lib.is_latest(representation_doc): outdated_containers.append(container) + if not outdated_containers: + return + # Colour nodes. outdated_nodes = [] for container in outdated_containers: From 6de95ba8064a9ec06757dc8d8fc2b0e15e12498b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 11:04:02 +0200 Subject: [PATCH 0742/1227] resolve is using query functions --- .../hosts/resolve/plugins/load/load_clip.py | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/resolve/plugins/load/load_clip.py b/openpype/hosts/resolve/plugins/load/load_clip.py index cf88b14e81..86dd6850e7 100644 --- a/openpype/hosts/resolve/plugins/load/load_clip.py +++ b/openpype/hosts/resolve/plugins/load/load_clip.py @@ -1,6 +1,10 @@ from copy import deepcopy from importlib import reload +from openpype.client import ( + get_version_by_id, + get_last_version_by_subset_id, +) from openpype.hosts import resolve from openpype.pipeline import ( get_representation_path, @@ -96,10 +100,8 @@ class LoadClip(resolve.TimelineItemLoader): namespace = container['namespace'] timeline_item_data = resolve.get_pype_timeline_item_by_name(namespace) timeline_item = timeline_item_data["clip"]["item"] - version = legacy_io.find_one({ - "type": "version", - "_id": representation["parent"] - }) + project_name = legacy_io.active_project() + version = get_version_by_id(project_name, representation["parent"]) version_data = version.get("data", {}) version_name = version.get("name", None) colorspace = version_data.get("colorspace", None) @@ -138,19 +140,22 @@ class LoadClip(resolve.TimelineItemLoader): @classmethod def set_item_color(cls, timeline_item, version): - # define version name version_name = version.get("name", None) # get all versions in list - versions = legacy_io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') - - max_version = max(versions) + project_name = legacy_io.active_project() + last_version_doc = get_last_version_by_subset_id( + project_name, + version["parent"], + fields=["name"] + ) + if last_version_doc: + last_version = last_version_doc["name"] + else: + last_version = None # set clip colour - if version_name == max_version: + if version_name == last_version: timeline_item.SetClipColor(cls.clip_color_last) else: timeline_item.SetClipColor(cls.clip_color) From 2fd9063de4b92818cc26151243da2e5bdcd4af05 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 11:20:09 +0200 Subject: [PATCH 0743/1227] use query functions in fusion --- openpype/hosts/fusion/api/lib.py | 53 +++++++++---------- .../hosts/fusion/utility_scripts/switch_ui.py | 13 +++-- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index 29f3a3a3eb..001eb636ee 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -3,9 +3,16 @@ import sys import re import contextlib -from bson.objectid import ObjectId from Qt import QtGui +from openpype.client import ( + get_asset_by_name, + get_subset_by_name, + get_last_version_by_subset_id, + get_representation_by_id, + get_representation_by_name, + get_representation_parents, +) from openpype.pipeline import ( switch_container, legacy_io, @@ -93,13 +100,16 @@ def switch_item(container, raise ValueError("Must have at least one change provided to switch.") # Collect any of current asset, subset and representation if not provided - # so we can use the original name from those. + # so we can use the original name from those. + project_name = legacy_io.active_project() if any(not x for x in [asset_name, subset_name, representation_name]): - _id = ObjectId(container["representation"]) - representation = legacy_io.find_one({ - "type": "representation", "_id": _id - }) - version, subset, asset, project = legacy_io.parenthood(representation) + repre_id = container["representation"] + representation = get_representation_by_id(project_name, repre_id) + repre_parent_docs = get_representation_parents(representation) + if repre_parent_docs: + version, subset, asset, _ = repre_parent_docs + else: + version = subset = asset = None if asset_name is None: asset_name = asset["name"] @@ -111,39 +121,26 @@ def switch_item(container, representation_name = representation["name"] # Find the new one - asset = legacy_io.find_one({ - "name": asset_name, - "type": "asset" - }) + asset = get_asset_by_name(project_name, asset_name, fields=["_id"]) assert asset, ("Could not find asset in the database with the name " "'%s'" % asset_name) - subset = legacy_io.find_one({ - "name": subset_name, - "type": "subset", - "parent": asset["_id"] - }) + subset = get_subset_by_name( + project_name, subset_name, asset["_id"], fields=["_id"] + ) assert subset, ("Could not find subset in the database with the name " "'%s'" % subset_name) - version = legacy_io.find_one( - { - "type": "version", - "parent": subset["_id"] - }, - sort=[('name', -1)] + version = get_last_version_by_subset_id( + project_name, subset["_id"], fields=["_id"] ) - assert version, "Could not find a version for {}.{}".format( asset_name, subset_name ) - representation = legacy_io.find_one({ - "name": representation_name, - "type": "representation", - "parent": version["_id"]} + representation = get_representation_by_name( + project_name, representation_name, version["_id"] ) - assert representation, ("Could not find representation in the database " "with the name '%s'" % representation_name) diff --git a/openpype/hosts/fusion/utility_scripts/switch_ui.py b/openpype/hosts/fusion/utility_scripts/switch_ui.py index 70eb3d0a19..01d55db647 100644 --- a/openpype/hosts/fusion/utility_scripts/switch_ui.py +++ b/openpype/hosts/fusion/utility_scripts/switch_ui.py @@ -7,6 +7,7 @@ from Qt import QtWidgets, QtCore import qtawesome as qta +from openpype.client import get_assets from openpype import style from openpype.pipeline import ( install_host, @@ -142,7 +143,7 @@ class App(QtWidgets.QWidget): # Clear any existing items self._assets.clear() - asset_names = [a["name"] for a in self.collect_assets()] + asset_names = self.collect_asset_names() completer = QtWidgets.QCompleter(asset_names) self._assets.setCompleter(completer) @@ -165,8 +166,14 @@ class App(QtWidgets.QWidget): items = glob.glob("{}/*.comp".format(directory)) return items - def collect_assets(self): - return list(legacy_io.find({"type": "asset"}, {"name": True})) + def collect_asset_names(self): + project_name = legacy_io.active_project() + asset_docs = get_assets(project_name, fields=["name"]) + asset_names = { + asset_doc["name"] + for asset_doc in asset_docs + } + return list(asset_names) def populate_comp_box(self, files): """Ensure we display the filename only but the path is stored as well From 645b89ced5419d6f74ff5c5782981a72d0e70df1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 11:38:18 +0200 Subject: [PATCH 0744/1227] use query functions in remaining places --- .../hosts/fusion/plugins/load/load_sequence.py | 7 +++---- .../hosts/fusion/scripts/fusion_switch_shot.py | 16 +++++++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/fusion/plugins/load/load_sequence.py b/openpype/hosts/fusion/plugins/load/load_sequence.py index b860abd88b..9baa652b60 100644 --- a/openpype/hosts/fusion/plugins/load/load_sequence.py +++ b/openpype/hosts/fusion/plugins/load/load_sequence.py @@ -1,6 +1,7 @@ import os import contextlib +from openpype.client import get_version_by_id from openpype.pipeline import ( load, legacy_io, @@ -211,10 +212,8 @@ class FusionLoadSequence(load.LoaderPlugin): path = self._get_first_image(root) # Get start frame from version data - version = legacy_io.find_one({ - "type": "version", - "_id": representation["parent"] - }) + project_name = legacy_io.active_project() + version = get_version_by_id(project_name, representation["parent"]) start = version["data"].get("frameStart") if start is None: self.log.warning("Missing start frame for updated version" diff --git a/openpype/hosts/fusion/scripts/fusion_switch_shot.py b/openpype/hosts/fusion/scripts/fusion_switch_shot.py index 704f420796..52a157c56e 100644 --- a/openpype/hosts/fusion/scripts/fusion_switch_shot.py +++ b/openpype/hosts/fusion/scripts/fusion_switch_shot.py @@ -4,6 +4,11 @@ import sys import logging # Pipeline imports +from openpype.client import ( + get_project, + get_asset_by_name, + get_versions, +) from openpype.pipeline import ( legacy_io, install_host, @@ -164,9 +169,9 @@ def update_frame_range(comp, representations): """ - version_ids = [r["parent"] for r in representations] - versions = legacy_io.find({"type": "version", "_id": {"$in": version_ids}}) - versions = list(versions) + project_name = legacy_io.active_project() + version_ids = {r["parent"] for r in representations} + versions = list(get_versions(project_name, version_ids)) versions = [v for v in versions if v["data"].get("frameStart", None) is not None] @@ -203,11 +208,12 @@ def switch(asset_name, filepath=None, new=True): # Assert asset name exists # It is better to do this here then to wait till switch_shot does it - asset = legacy_io.find_one({"type": "asset", "name": asset_name}) + project_name = legacy_io.active_project() + asset = get_asset_by_name(project_name, asset_name) assert asset, "Could not find '%s' in the database" % asset_name # Get current project - self._project = legacy_io.find_one({"type": "project"}) + self._project = get_project(project_name) # Go to comp if not filepath: From 2c7c10a404400903089ab561e85661d2f84fb0bd Mon Sep 17 00:00:00 2001 From: murphy Date: Tue, 21 Jun 2022 12:03:31 +0200 Subject: [PATCH 0745/1227] vray device aspect ratio fix Vray has different attribute name for device aspect ratio from other renderers. (.aspectRatio instead of .deviceAspectRatio) Testing - open/create a maya scene with vray renderer set - run OpenPype -> Set Resolution resolution should be set without an error message about nonexistent vray attribute --- openpype/hosts/maya/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index bce03a648b..d5763160bc 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2141,7 +2141,7 @@ def set_scene_resolution(width, height, pixelAspect): cmds.setAttr("%s.height" % control_node, height) deviceAspectRatio = ((float(width) / float(height)) * float(pixelAspect)) - cmds.setAttr("%s.deviceAspectRatio" % control_node, deviceAspectRatio) + cmds.setAttr("%s.aspectRatio" % control_node, deviceAspectRatio) cmds.setAttr("%s.pixelAspect" % control_node, pixelAspect) From 861fbee46c36762f50bffbe9aa00a9a60af30cfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 21 Jun 2022 14:14:39 +0200 Subject: [PATCH 0746/1227] :ambulance: limit attribute name only to vray --- openpype/hosts/maya/api/lib.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index d5763160bc..3e239d5361 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2126,9 +2126,11 @@ def set_scene_resolution(width, height, pixelAspect): control_node = "defaultResolution" current_renderer = cmds.getAttr("defaultRenderGlobals.currentRenderer") + aspect_ration_attr = "deviceAspectRatio" # Give VRay a helping hand as it is slightly different from the rest if current_renderer == "vray": + aspect_ration_attr = "aspectRatio" vray_node = "vraySettings" if cmds.objExists(vray_node): control_node = vray_node @@ -2141,7 +2143,8 @@ def set_scene_resolution(width, height, pixelAspect): cmds.setAttr("%s.height" % control_node, height) deviceAspectRatio = ((float(width) / float(height)) * float(pixelAspect)) - cmds.setAttr("%s.aspectRatio" % control_node, deviceAspectRatio) + cmds.setAttr( + "{}.{}".format(control_node, aspect_ration_attr), deviceAspectRatio) cmds.setAttr("%s.pixelAspect" % control_node, pixelAspect) From 94af9cdcc949ee4b84cb2cedc4d35cec9e47af11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 21 Jun 2022 14:16:00 +0200 Subject: [PATCH 0747/1227] :pencil2: fix typo in variable name --- openpype/hosts/maya/api/lib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 3e239d5361..92a3efcd1f 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2126,11 +2126,11 @@ def set_scene_resolution(width, height, pixelAspect): control_node = "defaultResolution" current_renderer = cmds.getAttr("defaultRenderGlobals.currentRenderer") - aspect_ration_attr = "deviceAspectRatio" + aspect_ratio_attr = "deviceAspectRatio" # Give VRay a helping hand as it is slightly different from the rest if current_renderer == "vray": - aspect_ration_attr = "aspectRatio" + aspect_ratio_attr = "aspectRatio" vray_node = "vraySettings" if cmds.objExists(vray_node): control_node = vray_node @@ -2144,7 +2144,7 @@ def set_scene_resolution(width, height, pixelAspect): deviceAspectRatio = ((float(width) / float(height)) * float(pixelAspect)) cmds.setAttr( - "{}.{}".format(control_node, aspect_ration_attr), deviceAspectRatio) + "{}.{}".format(control_node, aspect_ratio_attr), deviceAspectRatio) cmds.setAttr("%s.pixelAspect" % control_node, pixelAspect) From 783f07e616a8a9749f2f37e44870a37c99719317 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 14:35:06 +0200 Subject: [PATCH 0748/1227] make sure exit code is set to not None --- openpype/hosts/tvpaint/api/communication_server.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/tvpaint/api/communication_server.py b/openpype/hosts/tvpaint/api/communication_server.py index 65cb9aa2f3..6ac3e6324c 100644 --- a/openpype/hosts/tvpaint/api/communication_server.py +++ b/openpype/hosts/tvpaint/api/communication_server.py @@ -707,6 +707,9 @@ class BaseCommunicator: if exit_code is not None: self.exit_code = exit_code + if self.exit_code is None: + self.exit_code = 0 + def stop(self): """Stop communication and currently running python process.""" log.info("Stopping communication") From 28d7e2ab517f0a311b689bb18582dcd737308ed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 21 Jun 2022 14:50:59 +0200 Subject: [PATCH 0749/1227] :recycle: clean up some superfluous output --- .../hosts/maya/plugins/publish/validate_camera_contents.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_camera_contents.py b/openpype/hosts/maya/plugins/publish/validate_camera_contents.py index eb93245f93..87712a4cea 100644 --- a/openpype/hosts/maya/plugins/publish/validate_camera_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_camera_contents.py @@ -51,18 +51,17 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): raise RuntimeError("No cameras found in empty instance.") if not cls.validate_shapes: - cls.log.info("not validating shapes in the content") + cls.log.info("Not validating shapes in the content.") for member in members: parents = cmds.ls(member, long=True)[0].split("|")[1:-1] - cls.log.info(parents) parents_long_named = [ "|".join(parents[:i]) for i in range(1, 1 + len(parents)) ] - cls.log.info(parents_long_named) if cameras[0] in parents_long_named: cls.log.error( - "{} is parented under camera {}".format(member, cameras[0])) + "{} is parented under camera {}".format( + member, cameras[0])) invalid.extend(member) return invalid From 9136af76fadf21af6ce6b93292809174f95f70a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 21 Jun 2022 15:06:00 +0200 Subject: [PATCH 0750/1227] :recycle: simplify prefix reduction --- .../hosts/maya/plugins/publish/validate_rendersettings.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 5875e65e47..1dab3274a0 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -218,11 +218,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): default_prefix = re.sub( cls.R_AOV_TOKEN, "", default_prefix) # remove aov token from prefix to pass validation - # first resolve it and then remove if dangling - default_prefix = default_prefix.replace( - "{aov_separator}", instance.data.get("aovSeparator", "_")) - default_prefix = default_prefix.rstrip( - instance.data.get("aovSeparator", "_")) + default_prefix = default_prefix.split("{aov_separator}")[0] elif not re.search(cls.R_AOV_TOKEN, prefix): invalid = True cls.log.error("Wrong image prefix [ {} ] - " From 569d7c98fc7eaed14bb20ea86c5ea78c1d2941d2 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 21 Jun 2022 16:06:53 +0300 Subject: [PATCH 0751/1227] Add hardware fog keys to Schema --- .../schemas/schema_maya_capture.json | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index d6b81c8687..a5e1cb45d9 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -224,6 +224,42 @@ "key": "twoSidedLighting", "label": "Two Sided Lighting" }, + { + "type": "splitter" + }, + { + "type": "boolean", + "key": "hwFogEnable", + "label": "Enable Hardware Fog" + }, + { + "type": "number", + "key": "hwFogStart", + "label": "Hardware Fog Start" + }, + { + "type": "number", + "key": "hwFogEnd", + "label": "Hardware Fog End" + }, + { + "type": "number", + "key": "hwFogAlpha", + "label": "Hardware Fog Alpha" + }, + { + "type": "number", + "key": "hwFogFalloff", + "label": "Hardware Fog Falloff" + }, + { + "type": "number", + "key": "hwFogDensity", + "label": "Hardware Fog Density" + }, + { + "type": "splitter" + }, { "type": "boolean", "key": "ssaoEnable", From cb6e093ee33be1aa9af97dca37e07cad7387b4af Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 21 Jun 2022 16:25:11 +0300 Subject: [PATCH 0752/1227] Add SSAO options to schema --- .../schemas/schema_maya_capture.json | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index a5e1cb45d9..919c847a9b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -227,6 +227,34 @@ { "type": "splitter" }, + { + "type": "boolean", + "key": "ssaoEnable", + "label": "Screen Space Ambient Occlusion" + }, + { + "type": "number", + "key": "ssaoAmount", + "label": "SSAO Amount" + }, + { + "type": "number", + "key": "ssaoFilterRadius", + "label": "SSAO Filter Radius" + }, + { + "type": "number", + "key": "ssaoRadius", + "label": "SSAO Radius" + }, + { + "type": "number", + "key": "ssaoSamples", + "label": "SSAO Samples" + }, + { + "type": "splitter" + }, { "type": "boolean", "key": "hwFogEnable", @@ -262,8 +290,18 @@ }, { "type": "boolean", - "key": "ssaoEnable", - "label": "Screen Space Ambient Occlusion" + "key": "motionBlurEnable", + "label": "Enable Motion Blur" + }, + { + "type": "number", + "key": "motionBlurShutterOpenFraction", + "label": "Shutter Open Fraction" + }, + { + "type": "number", + "key": "hwFogFalloff", + "label": "Hardware Fog Falloff" }, { "type": "splitter" From d08316d19ce5c097e3740cffd1163cd85f51cad5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 15:25:23 +0200 Subject: [PATCH 0753/1227] generate uuids is using query functions and before that tries to look for asset entity --- openpype/hosts/maya/api/action.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/api/action.py b/openpype/hosts/maya/api/action.py index ca1006b6aa..90605734e7 100644 --- a/openpype/hosts/maya/api/action.py +++ b/openpype/hosts/maya/api/action.py @@ -3,6 +3,7 @@ from __future__ import absolute_import import pyblish.api +from openpype.client import get_asset_by_name from openpype.pipeline import legacy_io from openpype.api import get_errored_instances_from_context @@ -74,12 +75,21 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): from . import lib - asset = instance.data['asset'] - asset_id = legacy_io.find_one( - {"name": asset, "type": "asset"}, - projection={"_id": True} - )['_id'] - for node, _id in lib.generate_ids(nodes, asset_id=asset_id): + # Expecting this is called on validators in which case 'assetEntity' + # should be always available, but kept a way to query it by name. + asset_doc = instance.data.get("assetEntity") + if not asset_doc: + asset_name = instance.data["asset"] + project_name = legacy_io.active_project() + self.log.info(( + "Asset is not stored on instance." + " Querying by name \"{}\" from project \"{}\"" + ).format(asset_name, project_name)) + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["_id"] + ) + + for node, _id in lib.generate_ids(nodes, asset_id=asset_doc["_id"]): lib.set_id(node, _id, overwrite=True) From c9cfcd2b8025e64d02b5b49c99707ce07eaac9ea Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 15:26:02 +0200 Subject: [PATCH 0754/1227] use query functions in commands and lib --- openpype/hosts/maya/api/commands.py | 9 +- openpype/hosts/maya/api/lib.py | 132 +++++++++++++++------------- 2 files changed, 79 insertions(+), 62 deletions(-) diff --git a/openpype/hosts/maya/api/commands.py b/openpype/hosts/maya/api/commands.py index dd616b6dd6..355edf3ae4 100644 --- a/openpype/hosts/maya/api/commands.py +++ b/openpype/hosts/maya/api/commands.py @@ -2,6 +2,7 @@ """OpenPype script commands to be used directly in Maya.""" from maya import cmds +from openpype.client import get_asset_by_name, get_project from openpype.pipeline import legacy_io @@ -79,8 +80,9 @@ def reset_frame_range(): cmds.currentUnit(time=fps) # Set frame start/end + project_name = legacy_io.active_project() asset_name = legacy_io.Session["AVALON_ASSET"] - asset = legacy_io.find_one({"name": asset_name, "type": "asset"}) + asset = get_asset_by_name(project_name, asset_name) frame_start = asset["data"].get("frameStart") frame_end = asset["data"].get("frameEnd") @@ -145,8 +147,9 @@ def reset_resolution(): resolution_height = 1080 # Get resolution from asset + project_name = legacy_io.active_project() asset_name = legacy_io.Session["AVALON_ASSET"] - asset_doc = legacy_io.find_one({"name": asset_name, "type": "asset"}) + asset_doc = get_asset_by_name(project_name, asset_name) resolution = _resolution_from_document(asset_doc) # Try get resolution from project if resolution is None: @@ -155,7 +158,7 @@ def reset_resolution(): "Asset \"{}\" does not have set resolution." " Trying to get resolution from project" ).format(asset_name)) - project_doc = legacy_io.find_one({"type": "project"}) + project_doc = get_project(project_name) resolution = _resolution_from_document(project_doc) if resolution is None: diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index bce03a648b..96d98aa6ae 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -12,11 +12,18 @@ import contextlib from collections import OrderedDict, defaultdict from math import ceil from six import string_types -import bson from maya import cmds, mel import maya.api.OpenMaya as om +from openpype.client import ( + get_project, + get_asset_by_name, + get_subsets, + get_subset_by_name, + get_last_versions, + get_representation_by_name +) from openpype import lib from openpype.api import get_anatomy_settings from openpype.pipeline import ( @@ -1387,15 +1394,11 @@ def generate_ids(nodes, asset_id=None): if asset_id is None: # Get the asset ID from the database for the asset of current context - asset_data = legacy_io.find_one( - { - "type": "asset", - "name": legacy_io.Session["AVALON_ASSET"] - }, - projection={"_id": True} - ) - assert asset_data, "No current asset found in Session" - asset_id = asset_data['_id'] + project_name = legacy_io.active_project() + asset_name = legacy_io.Session["AVALON_ASSET"] + asset_doc = get_asset_by_name(project_name, asset_name, fields=["_id"]) + assert asset_doc, "No current asset found in Session" + asset_id = asset_doc['_id'] node_ids = [] for node in nodes: @@ -1548,13 +1551,13 @@ def list_looks(asset_id): # # get all subsets with look leading in # the name associated with the asset - subset = legacy_io.find({ - "parent": bson.ObjectId(asset_id), - "type": "subset", - "name": {"$regex": "look*"} - }) - - return list(subset) + project_name = legacy_io.active_project() + subset_docs = get_subsets(project_name, asset_ids=[asset_id]) + return [ + subset_doc + for subset_doc in subset_docs + if subset_doc["name"].startswith("look") + ] def assign_look_by_version(nodes, version_id): @@ -1570,18 +1573,15 @@ def assign_look_by_version(nodes, version_id): None """ - # Get representations of shader file and relationships - look_representation = legacy_io.find_one({ - "type": "representation", - "parent": version_id, - "name": "ma" - }) + project_name = legacy_io.active_project() - json_representation = legacy_io.find_one({ - "type": "representation", - "parent": version_id, - "name": "json" - }) + # Get representations of shader file and relationships + look_representation = get_representation_by_name( + project_name, "ma", version_id + ) + json_representation = get_representation_by_name( + project_name, "json", version_id + ) # See if representation is already loaded, if so reuse it. host = registered_host() @@ -1639,42 +1639,54 @@ def assign_look(nodes, subset="lookDefault"): parts = pype_id.split(":", 1) grouped[parts[0]].append(node) + project_name = legacy_io.active_project() + subset_docs = get_subsets( + project_name, subset_names=[subset], asset_ids=grouped.keys() + ) + subset_docs_by_asset_id = { + str(subset_doc["parent"]): subset_doc + for subset_doc in subset_docs + } + subset_ids = { + subset_doc["_id"] + for subset_doc in subset_docs_by_asset_id.values() + } + last_version_docs = get_last_versions( + project_name, + subset_ids=subset_ids, + fields=["_id", "name", "data.families"] + ) + last_version_docs_by_subset_id = { + last_version_doc["parent"]: last_version_doc + for last_version_doc in last_version_docs + } + for asset_id, asset_nodes in grouped.items(): # create objectId for database - try: - asset_id = bson.ObjectId(asset_id) - except bson.errors.InvalidId: - log.warning("Asset ID is not compatible with bson") - continue - subset_data = legacy_io.find_one({ - "type": "subset", - "name": subset, - "parent": asset_id - }) - - if not subset_data: + subset_doc = subset_docs_by_asset_id.get(asset_id) + if not subset_doc: log.warning("No subset '{}' found for {}".format(subset, asset_id)) continue - # get last version - # with backwards compatibility - version = legacy_io.find_one( - { - "parent": subset_data['_id'], - "type": "version", - "data.families": {"$in": ["look"]} - }, - sort=[("name", -1)], - projection={ - "_id": True, - "name": True - } - ) + last_version = last_version_docs_by_subset_id.get(subset_doc["_id"]) + if not last_version: + log.warning(( + "Not found last version for subset '{}' on asset with id {}" + ).format(subset, asset_id)) + continue - log.debug("Assigning look '{}' ".format(subset, - version["name"])) + families = last_version.get("data", {}).get("families") or [] + if "look" not in families: + log.warning(( + "Last version for subset '{}' on asset with id {}" + " does not have look family" + ).format(subset, asset_id)) + continue - assign_look_by_version(asset_nodes, version['_id']) + log.debug("Assigning look '{}' ".format( + subset, last_version["name"])) + + assign_look_by_version(asset_nodes, last_version["_id"]) def apply_shaders(relationships, shadernodes, nodes): @@ -2155,7 +2167,8 @@ def reset_scene_resolution(): None """ - project_doc = legacy_io.find_one({"type": "project"}) + project_name = legacy_io.active_project() + project_doc = get_project(project_name) project_data = project_doc["data"] asset_data = lib.get_asset()["data"] @@ -2188,7 +2201,8 @@ def set_context_settings(): """ # Todo (Wijnand): apply renderer and resolution of project - project_doc = legacy_io.find_one({"type": "project"}) + project_name = legacy_io.active_project() + project_doc = get_project(project_name) project_data = project_doc["data"] asset_data = lib.get_asset()["data"] From 8cd440e99f69a3dfe031bf48fbba69d6b10f7727 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 15:29:34 +0200 Subject: [PATCH 0755/1227] use query functions in setdress --- openpype/hosts/maya/api/setdress.py | 66 ++++++++++++++++------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/maya/api/setdress.py b/openpype/hosts/maya/api/setdress.py index f8d3ed79b8..bea8f154b1 100644 --- a/openpype/hosts/maya/api/setdress.py +++ b/openpype/hosts/maya/api/setdress.py @@ -6,10 +6,16 @@ import contextlib import copy import six -from bson.objectid import ObjectId from maya import cmds +from openpype.client import ( + get_version_by_name, + get_last_version_by_subset_id, + get_representation_by_id, + get_representation_by_name, + get_representation_parents, +) from openpype.pipeline import ( schema, legacy_io, @@ -283,36 +289,35 @@ def update_package_version(container, version): """ # Versioning (from `core.maya.pipeline`) - current_representation = legacy_io.find_one({ - "_id": ObjectId(container["representation"]) - }) + project_name = legacy_io.active_project() + current_representation = get_representation_by_id( + project_name, container["representation"] + ) assert current_representation is not None, "This is a bug" - version_, subset, asset, project = legacy_io.parenthood( - current_representation + repre_parents = get_representation_parents( + project_name, current_representation ) + version_doc = subset_doc = asset_doc = project_doc = None + if repre_parents: + version_doc, subset_doc, asset_doc, project_doc = repre_parents if version == -1: - new_version = legacy_io.find_one({ - "type": "version", - "parent": subset["_id"] - }, sort=[("name", -1)]) + new_version = get_last_version_by_subset_id( + project_name, subset_doc["_id"] + ) else: - new_version = legacy_io.find_one({ - "type": "version", - "parent": subset["_id"], - "name": version, - }) + new_version = get_version_by_name( + project_name, version, subset_doc["_id"] + ) assert new_version is not None, "This is a bug" # Get the new representation (new file) - new_representation = legacy_io.find_one({ - "type": "representation", - "parent": new_version["_id"], - "name": current_representation["name"] - }) + new_representation = get_representation_by_name( + project_name, current_representation["name"], new_version["_id"] + ) update_package(container, new_representation) @@ -330,10 +335,10 @@ def update_package(set_container, representation): """ # Load the original package data - current_representation = legacy_io.find_one({ - "_id": ObjectId(set_container['representation']), - "type": "representation" - }) + project_name = legacy_io.active_project() + current_representation = get_representation_by_id( + project_name, set_container["representation"] + ) current_file = get_representation_path(current_representation) assert current_file.endswith(".json") @@ -380,6 +385,7 @@ def update_scene(set_container, containers, current_data, new_data, new_file): from openpype.hosts.maya.lib import DEFAULT_MATRIX, get_container_transforms set_namespace = set_container['namespace'] + project_name = legacy_io.active_project() # Update the setdress hierarchy alembic set_root = get_container_transforms(set_container, root=True) @@ -481,12 +487,12 @@ def update_scene(set_container, containers, current_data, new_data, new_file): # Check whether the conversion can be done by the Loader. # They *must* use the same asset, subset and Loader for # `update_container` to make sense. - old = legacy_io.find_one({ - "_id": ObjectId(representation_current) - }) - new = legacy_io.find_one({ - "_id": ObjectId(representation_new) - }) + old = get_representation_by_id( + project_name, representation_current + ) + new = get_representation_by_id( + project_name, representation_new + ) is_valid = compare_representations(old=old, new=new) if not is_valid: log.error("Skipping: %s. See log for details.", From d4d2f8fe3e9a23ed39cbaa27802f8cb48d09c65f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 15:30:30 +0200 Subject: [PATCH 0756/1227] added comment to look queries --- openpype/hosts/maya/api/lib.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 96d98aa6ae..6863edf3e0 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1551,6 +1551,8 @@ def list_looks(asset_id): # # get all subsets with look leading in # the name associated with the asset + # TODO this should probably look for family 'look' instead of checking + # subset name that can not start with family project_name = legacy_io.active_project() subset_docs = get_subsets(project_name, asset_ids=[asset_id]) return [ From a78381265e31f5bb27ef10006b1e4c0c7f869a7e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 15:31:14 +0200 Subject: [PATCH 0757/1227] use query functions in maya plugins --- .../plugins/inventory/import_modelrender.py | 53 ++++++++++++------- .../hosts/maya/plugins/load/load_audio.py | 18 +++++-- .../maya/plugins/load/load_image_plane.py | 18 +++++-- openpype/hosts/maya/plugins/load/load_look.py | 10 ++-- .../hosts/maya/plugins/load/load_vrayproxy.py | 11 ++-- .../maya/plugins/publish/collect_review.py | 15 ++++-- .../publish/validate_node_ids_in_database.py | 9 +++- .../publish/validate_node_ids_related.py | 11 +--- .../publish/validate_renderlayer_aovs.py | 23 ++++---- 9 files changed, 101 insertions(+), 67 deletions(-) diff --git a/openpype/hosts/maya/plugins/inventory/import_modelrender.py b/openpype/hosts/maya/plugins/inventory/import_modelrender.py index a5367f16e5..8a7390bc8d 100644 --- a/openpype/hosts/maya/plugins/inventory/import_modelrender.py +++ b/openpype/hosts/maya/plugins/inventory/import_modelrender.py @@ -1,6 +1,10 @@ +import re import json -from bson.objectid import ObjectId +from openpype.client import ( + get_representation_by_id, + get_representations +) from openpype.pipeline import ( InventoryAction, get_representation_context, @@ -31,6 +35,7 @@ class ImportModelRender(InventoryAction): def process(self, containers): from maya import cmds + project_name = legacy_io.active_project() for container in containers: con_name = container["objectName"] nodes = [] @@ -40,9 +45,9 @@ class ImportModelRender(InventoryAction): else: nodes.append(n) - repr_doc = legacy_io.find_one({ - "_id": ObjectId(container["representation"]), - }) + repr_doc = get_representation_by_id( + project_name, container["representation"], fields=["parent"] + ) version_id = repr_doc["parent"] print("Importing render sets for model %r" % con_name) @@ -63,26 +68,38 @@ class ImportModelRender(InventoryAction): from maya import cmds + project_name = legacy_io.active_project() + repre_docs = get_representations( + project_name, version_ids=[version_id], fields=["_id", "name"] + ) # Get representations of shader file and relationships - look_repr = legacy_io.find_one({ - "type": "representation", - "parent": version_id, - "name": {"$regex": self.scene_type_regex}, - }) - if not look_repr: + json_repre = None + look_repres = [] + scene_type_regex = re.compile(self.scene_type_regex) + for repre_doc in repre_docs: + repre_name = repre_doc["name"] + if repre_name == self.look_data_type: + json_repre = repre_doc + continue + + if scene_type_regex.fullmatch(repre_name): + look_repres.append(repre_doc) + + # QUESTION should we care if there is more then one look + # representation? (since it's based on regex match) + look_repre = None + if look_repres: + look_repre = look_repres[0] + + # QUESTION shouldn't be json representation validated too? + if not look_repre: print("No model render sets for this model version..") return - json_repr = legacy_io.find_one({ - "type": "representation", - "parent": version_id, - "name": self.look_data_type, - }) - - context = get_representation_context(look_repr["_id"]) + context = get_representation_context(look_repre["_id"]) maya_file = self.filepath_from_context(context) - context = get_representation_context(json_repr["_id"]) + context = get_representation_context(json_repre["_id"]) json_file = self.filepath_from_context(context) # Import the look file diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py index ce814e1299..6f60cb5726 100644 --- a/openpype/hosts/maya/plugins/load/load_audio.py +++ b/openpype/hosts/maya/plugins/load/load_audio.py @@ -1,5 +1,10 @@ from maya import cmds, mel +from openpype.client import ( + get_asset_by_id, + get_subset_by_id, + get_version_by_id, +) from openpype.pipeline import ( legacy_io, load, @@ -65,9 +70,16 @@ class AudioLoader(load.LoaderPlugin): ) # Set frame range. - version = legacy_io.find_one({"_id": representation["parent"]}) - subset = legacy_io.find_one({"_id": version["parent"]}) - asset = legacy_io.find_one({"_id": subset["parent"]}) + project_name = legacy_io.active_project() + version = get_version_by_id( + project_name, representation["parent"], fields=["parent"] + ) + subset = get_subset_by_id( + project_name, version["parent"], fields=["parent"] + ) + asset = get_asset_by_id( + project_name, subset["parent"], fields=["parent"] + ) audio_node.sourceStart.set(1 - asset["data"]["frameStart"]) audio_node.sourceEnd.set(asset["data"]["frameEnd"]) diff --git a/openpype/hosts/maya/plugins/load/load_image_plane.py b/openpype/hosts/maya/plugins/load/load_image_plane.py index 5e44917f28..b267921bdc 100644 --- a/openpype/hosts/maya/plugins/load/load_image_plane.py +++ b/openpype/hosts/maya/plugins/load/load_image_plane.py @@ -1,5 +1,10 @@ from Qt import QtWidgets, QtCore +from openpype.client import ( + get_asset_by_id, + get_subset_by_id, + get_version_by_id, +) from openpype.pipeline import ( legacy_io, load, @@ -216,9 +221,16 @@ class ImagePlaneLoader(load.LoaderPlugin): ) # Set frame range. - version = legacy_io.find_one({"_id": representation["parent"]}) - subset = legacy_io.find_one({"_id": version["parent"]}) - asset = legacy_io.find_one({"_id": subset["parent"]}) + project_name = legacy_io.active_project() + version = get_version_by_id( + project_name, representation["parent"], fields=["parent"] + ) + subset = get_subset_by_id( + project_name, version["parent"], fields=["parent"] + ) + asset = get_asset_by_id( + project_name, subset["parent"], fields=["parent"] + ) start_frame = asset["data"]["frameStart"] end_frame = asset["data"]["frameEnd"] image_plane_shape.frameOffset.set(1 - start_frame) diff --git a/openpype/hosts/maya/plugins/load/load_look.py b/openpype/hosts/maya/plugins/load/load_look.py index ae3a683241..7392adc4dd 100644 --- a/openpype/hosts/maya/plugins/load/load_look.py +++ b/openpype/hosts/maya/plugins/load/load_look.py @@ -5,6 +5,7 @@ from collections import defaultdict from Qt import QtWidgets +from openpype.client import get_representation_by_name from openpype.pipeline import ( legacy_io, get_representation_path, @@ -75,11 +76,10 @@ class LookLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): shader_nodes = cmds.ls(members, type='shadingEngine') nodes = set(self._get_nodes_with_shader(shader_nodes)) - json_representation = legacy_io.find_one({ - "type": "representation", - "parent": representation['parent'], - "name": "json" - }) + project_name = legacy_io.active_project() + json_representation = get_representation_by_name( + project_name, "json", representation["parent"] + ) # Load relationships shader_relation = get_representation_path(json_representation) diff --git a/openpype/hosts/maya/plugins/load/load_vrayproxy.py b/openpype/hosts/maya/plugins/load/load_vrayproxy.py index 22d56139f6..e3d6166d3a 100644 --- a/openpype/hosts/maya/plugins/load/load_vrayproxy.py +++ b/openpype/hosts/maya/plugins/load/load_vrayproxy.py @@ -7,10 +7,9 @@ loader will use them instead of native vray vrmesh format. """ import os -from bson.objectid import ObjectId - import maya.cmds as cmds +from openpype.client import get_representation_by_name from openpype.api import get_project_settings from openpype.pipeline import ( legacy_io, @@ -185,12 +184,8 @@ class VRayProxyLoader(load.LoaderPlugin): """ self.log.debug( "Looking for abc in published representations of this version.") - abc_rep = legacy_io.find_one({ - "type": "representation", - "parent": ObjectId(version_id), - "name": "abc" - }) - + project_name = legacy_io.active_project() + abc_rep = get_representation_by_name(project_name, "abc", version_id) if abc_rep: self.log.debug("Found, we'll link alembic to vray proxy.") file_name = get_representation_path(abc_rep) diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index e9e0d74c03..15b89ad53c 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -3,6 +3,7 @@ import pymel.core as pm import pyblish.api +from openpype.client import get_subset_by_name from openpype.pipeline import legacy_io @@ -78,11 +79,15 @@ class CollectReview(pyblish.api.InstancePlugin): self.log.debug('isntance data {}'.format(instance.data)) else: legacy_subset_name = task + 'Review' - asset_doc_id = instance.context.data['assetEntity']["_id"] - subsets = legacy_io.find({"type": "subset", - "name": legacy_subset_name, - "parent": asset_doc_id}).distinct("_id") - if len(list(subsets)) > 0: + asset_doc = instance.context.data['assetEntity'] + project_name = legacy_io.active_project() + subset_doc = get_subset_by_name( + project_name, + legacy_subset_name, + asset_doc["_id"], + fields=["_id"] + ) + if subset_doc: self.log.debug("Existing subsets found, keep legacy name.") instance.data['subset'] = legacy_subset_name diff --git a/openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py b/openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py index 068d6b38a1..632b531668 100644 --- a/openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py @@ -1,6 +1,7 @@ import pyblish.api import openpype.api +from openpype.client import get_assets from openpype.pipeline import legacy_io import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib @@ -42,8 +43,12 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): nodes=instance[:]) # check ids against database ids - db_asset_ids = legacy_io.find({"type": "asset"}).distinct("_id") - db_asset_ids = set(str(i) for i in db_asset_ids) + project_name = legacy_io.active_project() + asset_docs = get_assets(project_name, fields=["_id"]) + db_asset_ids = { + str(asset_doc["_id"]) + for asset_doc in asset_docs + } # Get all asset IDs for node in id_required_nodes: diff --git a/openpype/hosts/maya/plugins/publish/validate_node_ids_related.py b/openpype/hosts/maya/plugins/publish/validate_node_ids_related.py index 38407e4176..c8bac6e569 100644 --- a/openpype/hosts/maya/plugins/publish/validate_node_ids_related.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_ids_related.py @@ -1,7 +1,6 @@ import pyblish.api import openpype.api -from openpype.pipeline import legacy_io import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib @@ -36,15 +35,7 @@ class ValidateNodeIDsRelated(pyblish.api.InstancePlugin): """Return the member nodes that are invalid""" invalid = list() - asset = instance.data['asset'] - asset_data = legacy_io.find_one( - { - "name": asset, - "type": "asset" - }, - projection={"_id": True} - ) - asset_id = str(asset_data['_id']) + asset_id = str(instance.data['assetEntity']["_id"]) # We do want to check the referenced nodes as we it might be # part of the end product diff --git a/openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py b/openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py index e65150eb0f..6b6fb03eec 100644 --- a/openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py +++ b/openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py @@ -1,8 +1,8 @@ import pyblish.api +from openpype.client import get_subset_by_name import openpype.hosts.maya.api.action from openpype.pipeline import legacy_io -import openpype.api class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin): @@ -33,26 +33,23 @@ class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin): raise RuntimeError("Found unregistered subsets: {}".format(invalid)) def get_invalid(self, instance): - invalid = [] - asset_name = instance.data["asset"] + project_name = legacy_io.active_project() + asset_doc = instance.data["assetEntity"] render_passses = instance.data.get("renderPasses", []) for render_pass in render_passses: - is_valid = self.validate_subset_registered(asset_name, render_pass) + is_valid = self.validate_subset_registered( + project_name, asset_doc, render_pass + ) if not is_valid: invalid.append(render_pass) return invalid - def validate_subset_registered(self, asset_name, subset_name): + def validate_subset_registered(self, project_name, asset_doc, subset_name): """Check if subset is registered in the database under the asset""" - asset = legacy_io.find_one({"type": "asset", "name": asset_name}) - is_valid = legacy_io.find_one({ - "type": "subset", - "name": subset_name, - "parent": asset["_id"] - }) - - return is_valid + return get_subset_by_name( + project_name, subset_name, asset_doc["_id"], fields=["_id"] + ) From 240c7113601141dffe08233e3be5a9b9f5944447 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 15:53:32 +0200 Subject: [PATCH 0758/1227] remove unused import --- openpype/hosts/maya/api/lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 6863edf3e0..edca1f45b8 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -20,7 +20,6 @@ from openpype.client import ( get_project, get_asset_by_name, get_subsets, - get_subset_by_name, get_last_versions, get_representation_by_name ) From 697916c3cdba015b68f0376902422606ac27f9fe Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 21 Jun 2022 17:03:10 +0300 Subject: [PATCH 0759/1227] Append enum for fog falloff. --- .../schemas/schema_maya_capture.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 919c847a9b..caf001f7e1 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -260,6 +260,16 @@ "key": "hwFogEnable", "label": "Enable Hardware Fog" }, + { + "type": "enum", + "key": "hwFogFalloff", + "label": "Hardware Fog Falloff", + "enum_items": [ + { "0": "Linear"}, + { "1": "Exponential"}, + { "2": "Exponential Squared"} + ] + }, { "type": "number", "key": "hwFogStart", @@ -275,11 +285,6 @@ "key": "hwFogAlpha", "label": "Hardware Fog Alpha" }, - { - "type": "number", - "key": "hwFogFalloff", - "label": "Hardware Fog Falloff" - }, { "type": "number", "key": "hwFogDensity", @@ -298,11 +303,6 @@ "key": "motionBlurShutterOpenFraction", "label": "Shutter Open Fraction" }, - { - "type": "number", - "key": "hwFogFalloff", - "label": "Hardware Fog Falloff" - }, { "type": "splitter" }, From ed0ba7e3ab306f1674def5f7cc69666cb96e8dc6 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 21 Jun 2022 17:39:46 +0300 Subject: [PATCH 0760/1227] Append Antialiasing and MotionBlur Sampling --- .../schemas/schema_maya_capture.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index caf001f7e1..fa5be19cda 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -201,6 +201,14 @@ "label": "Texture Clamp Resolution", "decimal": 0 }, + { + "type": "splitter" + }, + { + "type": "boolean", + "key": "lineAAEnable", + "label": "Smooth Wireframe" + }, { "type": "number", "key": "multiSample", @@ -209,6 +217,9 @@ "minimum": 0, "maximum": 32 }, + { + "type": "splitter" + }, { "type": "boolean", "key": "shadows", @@ -303,6 +314,11 @@ "key": "motionBlurShutterOpenFraction", "label": "Shutter Open Fraction" }, + { + "type": "number", + "key": "motionBlurSampleCount", + "label": "Sample Count" + }, { "type": "splitter" }, From de6bd72c2ed1ef40b3cecfa4a7caed9cf93937ea Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 21 Jun 2022 18:01:49 +0300 Subject: [PATCH 0761/1227] Appen defaults. --- .../settings/defaults/project_settings/maya.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 69fbf5bfdf..6780921001 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -491,11 +491,25 @@ "override_viewport_options": true, "displayLights": "default", "textureMaxResolution": 1024, + "lineAAEnable": true, "multiSample": 4, "shadows": true, "textures": true, "twoSidedLighting": true, "ssaoEnable": true, + "ssaoAmount": 0, + "ssaoFilterRadius": 0, + "ssaoRadius": 0, + "ssaoSamples": 0, + "hwFogEnable": true, + "hwFogFalloff": "0", + "hwFogStart": 0, + "hwFogEnd": 0, + "hwFogAlpha": 0, + "hwFogDensity": 0, + "motionBlurEnable": true, + "motionBlurShutterOpenFraction": 0, + "motionBlurSampleCount": 0, "cameras": false, "clipGhosts": false, "controlVertices": false, From 269c752bfc9ff109b8a75e57b3f78349d7bf1b75 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 21 Jun 2022 18:01:49 +0300 Subject: [PATCH 0762/1227] Append settings defaults. --- .../settings/defaults/project_settings/maya.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 69fbf5bfdf..6780921001 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -491,11 +491,25 @@ "override_viewport_options": true, "displayLights": "default", "textureMaxResolution": 1024, + "lineAAEnable": true, "multiSample": 4, "shadows": true, "textures": true, "twoSidedLighting": true, "ssaoEnable": true, + "ssaoAmount": 0, + "ssaoFilterRadius": 0, + "ssaoRadius": 0, + "ssaoSamples": 0, + "hwFogEnable": true, + "hwFogFalloff": "0", + "hwFogStart": 0, + "hwFogEnd": 0, + "hwFogAlpha": 0, + "hwFogDensity": 0, + "motionBlurEnable": true, + "motionBlurShutterOpenFraction": 0, + "motionBlurSampleCount": 0, "cameras": false, "clipGhosts": false, "controlVertices": false, From ecda9a6099f6efe52d8ef3121d894c76277098a4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 21 Jun 2022 18:18:36 +0200 Subject: [PATCH 0763/1227] remove unused functions --- openpype/hosts/nuke/api/__init__.py | 6 -- openpype/hosts/nuke/api/command.py | 114 ---------------------------- openpype/hosts/nuke/api/lib.py | 23 ------ 3 files changed, 143 deletions(-) diff --git a/openpype/hosts/nuke/api/__init__.py b/openpype/hosts/nuke/api/__init__.py index b571c4098c..9e3ef1a397 100644 --- a/openpype/hosts/nuke/api/__init__.py +++ b/openpype/hosts/nuke/api/__init__.py @@ -8,9 +8,6 @@ from .workio import ( ) from .command import ( - reset_frame_range, - get_handles, - reset_resolution, viewer_update_and_undo_stop ) @@ -42,9 +39,6 @@ __all__ = ( "current_file", "work_root", - "reset_frame_range", - "get_handles", - "reset_resolution", "viewer_update_and_undo_stop", "OpenPypeCreator", diff --git a/openpype/hosts/nuke/api/command.py b/openpype/hosts/nuke/api/command.py index c756c48a12..2f772469d8 100644 --- a/openpype/hosts/nuke/api/command.py +++ b/openpype/hosts/nuke/api/command.py @@ -1,124 +1,10 @@ import logging import contextlib import nuke -from bson.objectid import ObjectId - -from openpype.pipeline import legacy_io log = logging.getLogger(__name__) -def reset_frame_range(): - """ Set frame range to current asset - Also it will set a Viewer range with - displayed handles - """ - - fps = float(legacy_io.Session.get("AVALON_FPS", 25)) - - nuke.root()["fps"].setValue(fps) - name = legacy_io.Session["AVALON_ASSET"] - asset = legacy_io.find_one({"name": name, "type": "asset"}) - asset_data = asset["data"] - - handles = get_handles(asset) - - frame_start = int(asset_data.get( - "frameStart", - asset_data.get("edit_in"))) - - frame_end = int(asset_data.get( - "frameEnd", - asset_data.get("edit_out"))) - - if not all([frame_start, frame_end]): - missing = ", ".join(["frame_start", "frame_end"]) - msg = "'{}' are not set for asset '{}'!".format(missing, name) - log.warning(msg) - nuke.message(msg) - return - - frame_start -= handles - frame_end += handles - - nuke.root()["first_frame"].setValue(frame_start) - nuke.root()["last_frame"].setValue(frame_end) - - # setting active viewers - vv = nuke.activeViewer().node() - vv["frame_range_lock"].setValue(True) - vv["frame_range"].setValue("{0}-{1}".format( - int(asset_data["frameStart"]), - int(asset_data["frameEnd"])) - ) - - -def get_handles(asset): - """ Gets handles data - - Arguments: - asset (dict): avalon asset entity - - Returns: - handles (int) - """ - data = asset["data"] - if "handles" in data and data["handles"] is not None: - return int(data["handles"]) - - parent_asset = None - if "visualParent" in data: - vp = data["visualParent"] - if vp is not None: - parent_asset = legacy_io.find_one({"_id": ObjectId(vp)}) - - if parent_asset is None: - parent_asset = legacy_io.find_one({"_id": ObjectId(asset["parent"])}) - - if parent_asset is not None: - return get_handles(parent_asset) - else: - return 0 - - -def reset_resolution(): - """Set resolution to project resolution.""" - project = legacy_io.find_one({"type": "project"}) - p_data = project["data"] - - width = p_data.get("resolution_width", - p_data.get("resolutionWidth")) - height = p_data.get("resolution_height", - p_data.get("resolutionHeight")) - - if not all([width, height]): - missing = ", ".join(["width", "height"]) - msg = "No resolution information `{0}` found for '{1}'.".format( - missing, - project["name"]) - log.warning(msg) - nuke.message(msg) - return - - current_width = nuke.root()["format"].value().width() - current_height = nuke.root()["format"].value().height() - - if width != current_width or height != current_height: - - fmt = None - for f in nuke.formats(): - if f.width() == width and f.height() == height: - fmt = f.name() - - if not fmt: - nuke.addFormat( - "{0} {1} {2}".format(int(width), int(height), project["name"]) - ) - fmt = project["name"] - - nuke.root()["format"].setValue(fmt) - - @contextlib.contextmanager def viewer_update_and_undo_stop(): """Lock viewer from updating and stop recording undo steps""" diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 505eb19419..7e44aaa7c5 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2151,29 +2151,6 @@ class WorkfileSettings(object): set_context_favorites(favorite_items) -def get_hierarchical_attr(entity, attr, default=None): - attr_parts = attr.split('.') - value = entity - for part in attr_parts: - value = value.get(part) - if not value: - break - - if value or entity["type"].lower() == "project": - return value - - parent_id = entity["parent"] - if ( - entity["type"].lower() == "asset" - and entity.get("data", {}).get("visualParent") - ): - parent_id = entity["data"]["visualParent"] - - parent = legacy_io.find_one({"_id": parent_id}) - - return get_hierarchical_attr(parent, attr) - - def get_write_node_template_attr(node): ''' Gets all defined data from presets From 45d228ce9fdc0791c79b079782613136f2ff7bc0 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 22 Jun 2022 01:07:45 +0200 Subject: [PATCH 0764/1227] :bug: fix handling of extra geometry --- .../plugins/publish/extract_camera_alembic.py | 6 ++--- .../publish/extract_camera_mayaScene.py | 24 +++++++++++-------- .../publish/validate_camera_contents.py | 13 +--------- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py index 4110ad474d..893aa63b01 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py @@ -61,10 +61,10 @@ class ExtractCameraAlembic(openpype.api.Extractor): if bake_to_worldspace: job_str += ' -worldSpace' - for member in member_shapes: - self.log.info(f"processing {member}") + for member in members: transform = cmds.listRelatives( - member, parent=True, fullPath=True)[0] + member, parent=True, fullPath=True) + transform = transform[0] if transform else member job_str += ' -root {0}'.format(transform) job_str += ' -file "{0}"'.format(path) diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py index 1cb30e65ea..569efbe335 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py @@ -172,18 +172,22 @@ class ExtractCameraMayaScene(openpype.api.Extractor): dag=True, shapes=True, long=True) + attrs = {"backgroundColorR": 0.0, + "backgroundColorG": 0.0, + "backgroundColorB": 0.0, + "overscan": 1.0} + # Fix PLN-178: Don't allow background color to be non-black - for cam in cmds.ls( + for cam, (attr, value) in itertools.product(cmds.ls( baked_camera_shapes, type="camera", dag=True, - shapes=True, long=True): - attrs = {"backgroundColorR": 0.0, - "backgroundColorG": 0.0, - "backgroundColorB": 0.0, - "overscan": 1.0} - for attr, value in attrs.items(): - plug = "{0}.{1}".format(cam, attr) - unlock(plug) - cmds.setAttr(plug, value) + shapes=True, long=True), attrs.items()): + # the above call still pull in shapes that are not + # cameras, so we filter them out here + if cmds.nodeType(cam) != "camera": + continue + plug = "{0}.{1}".format(cam, attr) + unlock(plug) + cmds.setAttr(plug, value) self.log.info("Performing extraction..") cmds.select(cmds.ls(members, dag=True, diff --git a/openpype/hosts/maya/plugins/publish/validate_camera_contents.py b/openpype/hosts/maya/plugins/publish/validate_camera_contents.py index eb93245f93..e209487ae4 100644 --- a/openpype/hosts/maya/plugins/publish/validate_camera_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_camera_contents.py @@ -52,20 +52,9 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): if not cls.validate_shapes: cls.log.info("not validating shapes in the content") - - for member in members: - parents = cmds.ls(member, long=True)[0].split("|")[1:-1] - cls.log.info(parents) - parents_long_named = [ - "|".join(parents[:i]) for i in range(1, 1 + len(parents)) - ] - cls.log.info(parents_long_named) - if cameras[0] in parents_long_named: - cls.log.error( - "{} is parented under camera {}".format(member, cameras[0])) - invalid.extend(member) return invalid + # non-camera shapes valid_shapes = cmds.ls(shapes, type=('camera', 'locator'), long=True) shapes = set(shapes) - set(valid_shapes) From d2bfcb80cb1597c35f6bbce1e7a4c51a909533b4 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 22 Jun 2022 03:53:06 +0000 Subject: [PATCH 0765/1227] [Automated] Bump version --- CHANGELOG.md | 46 ++++++++++++++++++++++++++++++++++++--------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48d0d8181e..f0a9a9651d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,42 @@ # Changelog +## [3.11.2-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.1...HEAD) + +### 📖 Documentation + +- General: Add ability to change user value for templates [\#3366](https://github.com/pypeclub/OpenPype/pull/3366) +- Feature/multiverse [\#3350](https://github.com/pypeclub/OpenPype/pull/3350) + +**🚀 Enhancements** + +- Hosts: More options for in-host callbacks [\#3357](https://github.com/pypeclub/OpenPype/pull/3357) +- TVPaint: Extractor use mark in/out range to render [\#3308](https://github.com/pypeclub/OpenPype/pull/3308) +- Maya: Allow more data to be published along camera 🎥 [\#3304](https://github.com/pypeclub/OpenPype/pull/3304) + +**🐛 Bug fixes** + +- TVPaint: Make sure exit code is set to not None [\#3382](https://github.com/pypeclub/OpenPype/pull/3382) +- Maya: vray device aspect ratio fix [\#3381](https://github.com/pypeclub/OpenPype/pull/3381) +- Harmony: added unc path to zifile command in Harmony [\#3372](https://github.com/pypeclub/OpenPype/pull/3372) + +**🔀 Refactored code** + +- Harmony: Use client query functions [\#3378](https://github.com/pypeclub/OpenPype/pull/3378) +- Photoshop: Use client query functions [\#3375](https://github.com/pypeclub/OpenPype/pull/3375) +- AfterEffects: Use client query functions [\#3374](https://github.com/pypeclub/OpenPype/pull/3374) +- TVPaint: Use client query functions [\#3340](https://github.com/pypeclub/OpenPype/pull/3340) +- Ftrack: Use client query functions [\#3339](https://github.com/pypeclub/OpenPype/pull/3339) +- Standalone Publisher: Use client query functions [\#3330](https://github.com/pypeclub/OpenPype/pull/3330) + +**Merged pull requests:** + +- Maya - added support for single frame playblast review [\#3369](https://github.com/pypeclub/OpenPype/pull/3369) + ## [3.11.1](https://github.com/pypeclub/OpenPype/tree/3.11.1) (2022-06-20) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.0...3.11.1) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.11.1-nightly.1...3.11.1) **🆕 New features** @@ -26,9 +60,9 @@ - AE- fix validate\_scene\_settings and renderLocal [\#3358](https://github.com/pypeclub/OpenPype/pull/3358) - deadline: fixing misidentification of revieables [\#3356](https://github.com/pypeclub/OpenPype/pull/3356) - General: Create only one thumbnail per instance [\#3351](https://github.com/pypeclub/OpenPype/pull/3351) +- nuke: adding extract thumbnail settings 3.10 [\#3347](https://github.com/pypeclub/OpenPype/pull/3347) - General: Fix last version function [\#3345](https://github.com/pypeclub/OpenPype/pull/3345) - Deadline: added OPENPYPE\_MONGO to filter [\#3336](https://github.com/pypeclub/OpenPype/pull/3336) -- Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) **🔀 Refactored code** @@ -61,10 +95,10 @@ **🐛 Bug fixes** -- General: Handle empty source key on instance [\#3342](https://github.com/pypeclub/OpenPype/pull/3342) - Houdini: Fix Houdini VDB manage update wrong file attribute name [\#3322](https://github.com/pypeclub/OpenPype/pull/3322) - Nuke: anatomy compatibility issue hacks [\#3321](https://github.com/pypeclub/OpenPype/pull/3321) - hiero: otio p3 compatibility issue - metadata on effect use update 3.11 [\#3314](https://github.com/pypeclub/OpenPype/pull/3314) +- Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) - General: Vendorized modules for Python 2 and update poetry lock [\#3305](https://github.com/pypeclub/OpenPype/pull/3305) - Fix - added local targets to install host [\#3303](https://github.com/pypeclub/OpenPype/pull/3303) - Settings: Add missing default settings for nuke gizmo [\#3301](https://github.com/pypeclub/OpenPype/pull/3301) @@ -78,8 +112,6 @@ - Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) - Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) - Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) -- Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240) -- Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) **🔀 Refactored code** @@ -90,7 +122,6 @@ - Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) - Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) -- Harmony: 21.1 fix [\#3248](https://github.com/pypeclub/OpenPype/pull/3248) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -105,9 +136,6 @@ - nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244) -- Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) -- Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) -- Maya: Fix support for multiple resolutions [\#3236](https://github.com/pypeclub/OpenPype/pull/3236) **Merged pull requests:** diff --git a/openpype/version.py b/openpype/version.py index 7bf368108a..79e3b445f9 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.1" +__version__ = "3.11.2-nightly.1" diff --git a/pyproject.toml b/pyproject.toml index ae89e7d9d8..4b297fe042 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.1" # OpenPype +version = "3.11.2-nightly.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 759cea424c30b06960fc7a247055b96c58ab0eea Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 22 Jun 2022 10:45:14 +0300 Subject: [PATCH 0766/1227] Start separating "lineAAEnable" --- openpype/vendor/python/common/capture.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/openpype/vendor/python/common/capture.py b/openpype/vendor/python/common/capture.py index 6b4c40a6e8..ae3a0d5cc1 100644 --- a/openpype/vendor/python/common/capture.py +++ b/openpype/vendor/python/common/capture.py @@ -361,7 +361,6 @@ Viewport2Options = { "floatingPointRTFormat": 1, "gammaCorrectionEnable": False, "gammaValue": 2.2, - "lineAAEnable": False, "maxHardwareLights": 8, "motionBlurEnable": False, "motionBlurSampleCount": 8, @@ -383,6 +382,10 @@ Viewport2Options = { "vertexAnimationCache": 0 } +Viewport2OAAoption = { + "lineAAenable": False, +} + def apply_view(panel, **options): """Apply options to panel""" @@ -496,6 +499,13 @@ def parse_view(panel): except ValueError: continue + for key in Viewport2OAAoption.keys(): + attr = "hardwareRenderingGlobals.{0}".format(key) + try: + viewport2_options[key] = cmds.getAttr(attr) + except ValueError: + continue + return { "camera": camera, "display_options": display_options, From 277682c03dcdbed093905188def33545f8a9b24a Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 22 Jun 2022 10:56:49 +0300 Subject: [PATCH 0767/1227] Revert "Start separating "lineAAEnable"" This reverts commit 759cea424c30b06960fc7a247055b96c58ab0eea. --- openpype/vendor/python/common/capture.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/openpype/vendor/python/common/capture.py b/openpype/vendor/python/common/capture.py index ae3a0d5cc1..6b4c40a6e8 100644 --- a/openpype/vendor/python/common/capture.py +++ b/openpype/vendor/python/common/capture.py @@ -361,6 +361,7 @@ Viewport2Options = { "floatingPointRTFormat": 1, "gammaCorrectionEnable": False, "gammaValue": 2.2, + "lineAAEnable": False, "maxHardwareLights": 8, "motionBlurEnable": False, "motionBlurSampleCount": 8, @@ -382,10 +383,6 @@ Viewport2Options = { "vertexAnimationCache": 0 } -Viewport2OAAoption = { - "lineAAenable": False, -} - def apply_view(panel, **options): """Apply options to panel""" @@ -499,13 +496,6 @@ def parse_view(panel): except ValueError: continue - for key in Viewport2OAAoption.keys(): - attr = "hardwareRenderingGlobals.{0}".format(key) - try: - viewport2_options[key] = cmds.getAttr(attr) - except ValueError: - continue - return { "camera": camera, "display_options": display_options, From f39a95422dd4c694743c458e285315c83e5121ef Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 22 Jun 2022 11:06:44 +0200 Subject: [PATCH 0768/1227] Update integrations section on the web --- website/src/pages/index.js | 45 +++++++++++++++++-------------- website/static/img/app_hibob.png | Bin 0 -> 15943 bytes 2 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 website/static/img/app_hibob.png diff --git a/website/src/pages/index.js b/website/src/pages/index.js index 115102ed04..0886706015 100644 --- a/website/src/pages/index.js +++ b/website/src/pages/index.js @@ -361,7 +361,7 @@ function Home() { - DaVinci Resolve (Beta) + Resolve (Beta) @@ -374,6 +374,16 @@ function Home() { Ftrack + + + Shotgrid (Beta) + + + + + Kitsu (Beta) + + Clockify @@ -384,12 +394,7 @@ function Home() { Deadline - - - Muster - - - + Royal Render @@ -399,30 +404,30 @@ function Home() { Slack -

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 z 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

- -

In development by us or OpenPype community.

- - +

Planned or in development by us and OpenPype community.

+ + diff --git a/website/static/img/app_hibob.png b/website/static/img/app_hibob.png new file mode 100644 index 0000000000000000000000000000000000000000..91dd8d3f6bae8ae50f7a74e0d3d2d8fba8b66b50 GIT binary patch literal 15943 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 Date: Wed, 22 Jun 2022 13:49:54 +0200 Subject: [PATCH 0769/1227] Revert "Shotgrid: Add production beta of shotgrid integration" --- .gitignore | 3 - .../plugins/publish/submit_maya_deadline.py | 1 - openpype/modules/shotgrid/README.md | 19 -- openpype/modules/shotgrid/__init__.py | 5 - openpype/modules/shotgrid/lib/__init__.py | 0 openpype/modules/shotgrid/lib/const.py | 1 - openpype/modules/shotgrid/lib/credentials.py | 125 ----------- openpype/modules/shotgrid/lib/record.py | 20 -- openpype/modules/shotgrid/lib/settings.py | 18 -- .../publish/collect_shotgrid_entities.py | 100 --------- .../publish/collect_shotgrid_session.py | 123 ----------- .../publish/integrate_shotgrid_publish.py | 77 ------- .../publish/integrate_shotgrid_version.py | 92 -------- .../plugins/publish/validate_shotgrid_user.py | 38 ---- openpype/modules/shotgrid/server/README.md | 5 - openpype/modules/shotgrid/shotgrid_module.py | 58 ----- .../tests/shotgrid/lib/test_credentials.py | 34 --- .../shotgrid/tray/credential_dialog.py | 201 ------------------ .../modules/shotgrid/tray/shotgrid_tray.py | 75 ------- openpype/resources/app_icons/shotgrid.png | Bin 45744 -> 0 bytes .../defaults/project_settings/shotgrid.json | 22 -- .../defaults/system_settings/modules.json | 8 +- openpype/settings/entities/__init__.py | 2 - openpype/settings/entities/enum_entity.py | 114 ++++------ .../schemas/projects_schema/schema_main.json | 4 - .../schema_project_shotgrid.json | 98 --------- .../schemas/schema_representation_tags.json | 3 - .../schemas/system_schema/schema_modules.json | 54 ----- poetry.lock | 16 -- pyproject.toml | 1 - 30 files changed, 38 insertions(+), 1279 deletions(-) delete mode 100644 openpype/modules/shotgrid/README.md delete mode 100644 openpype/modules/shotgrid/__init__.py delete mode 100644 openpype/modules/shotgrid/lib/__init__.py delete mode 100644 openpype/modules/shotgrid/lib/const.py delete mode 100644 openpype/modules/shotgrid/lib/credentials.py delete mode 100644 openpype/modules/shotgrid/lib/record.py delete mode 100644 openpype/modules/shotgrid/lib/settings.py delete mode 100644 openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py delete mode 100644 openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py delete mode 100644 openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py delete mode 100644 openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py delete mode 100644 openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py delete mode 100644 openpype/modules/shotgrid/server/README.md delete mode 100644 openpype/modules/shotgrid/shotgrid_module.py delete mode 100644 openpype/modules/shotgrid/tests/shotgrid/lib/test_credentials.py delete mode 100644 openpype/modules/shotgrid/tray/credential_dialog.py delete mode 100644 openpype/modules/shotgrid/tray/shotgrid_tray.py delete mode 100644 openpype/resources/app_icons/shotgrid.png delete mode 100644 openpype/settings/defaults/project_settings/shotgrid.json delete mode 100644 openpype/settings/entities/schemas/projects_schema/schema_project_shotgrid.json diff --git a/.gitignore b/.gitignore index e18c94a1f4..28cfb4b1e9 100644 --- a/.gitignore +++ b/.gitignore @@ -102,6 +102,3 @@ website/.docusaurus .poetry/ .python-version -.editorconfig -.pre-commit-config.yaml -mypy.ini diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index dff80e62b9..9964e3c646 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -519,7 +519,6 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): "FTRACK_API_KEY", "FTRACK_API_USER", "FTRACK_SERVER", - "OPENPYPE_SG_USER", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/shotgrid/README.md b/openpype/modules/shotgrid/README.md deleted file mode 100644 index cbee0e9bf4..0000000000 --- a/openpype/modules/shotgrid/README.md +++ /dev/null @@ -1,19 +0,0 @@ -## Shotgrid Module - -### Pre-requisites - -Install and launch a [shotgrid leecher](https://github.com/Ellipsanime/shotgrid-leecher) server - -### Quickstart - -The goal of this tutorial is to synchronize an already existing shotgrid project with OpenPype. - -- Activate the shotgrid module in the **system settings** and inform the shotgrid leecher server API url - -- Create a new OpenPype project with the **project manager** - -- Inform the shotgrid authentication infos (url, script name, api key) and the shotgrid project ID related to this OpenPype project in the **project settings** - -- Use the batch interface (Tray > shotgrid > Launch batch), select your project and click "batch" - -- You can now access your shotgrid entities within the **avalon launcher** and publish informations to shotgrid with **pyblish** diff --git a/openpype/modules/shotgrid/__init__.py b/openpype/modules/shotgrid/__init__.py deleted file mode 100644 index f1337a9492..0000000000 --- a/openpype/modules/shotgrid/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .shotgrid_module import ( - ShotgridModule, -) - -__all__ = ("ShotgridModule",) diff --git a/openpype/modules/shotgrid/lib/__init__.py b/openpype/modules/shotgrid/lib/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openpype/modules/shotgrid/lib/const.py b/openpype/modules/shotgrid/lib/const.py deleted file mode 100644 index 2a34800fac..0000000000 --- a/openpype/modules/shotgrid/lib/const.py +++ /dev/null @@ -1 +0,0 @@ -MODULE_NAME = "shotgrid" diff --git a/openpype/modules/shotgrid/lib/credentials.py b/openpype/modules/shotgrid/lib/credentials.py deleted file mode 100644 index 337c4f6ecb..0000000000 --- a/openpype/modules/shotgrid/lib/credentials.py +++ /dev/null @@ -1,125 +0,0 @@ - -from urllib.parse import urlparse - -import shotgun_api3 -from shotgun_api3.shotgun import AuthenticationFault - -from openpype.lib import OpenPypeSecureRegistry, OpenPypeSettingsRegistry -from openpype.modules.shotgrid.lib.record import Credentials - - -def _get_shotgrid_secure_key(hostname, key): - """Secure item key for entered hostname.""" - return f"shotgrid/{hostname}/{key}" - - -def _get_secure_value_and_registry( - hostname, - name, -): - key = _get_shotgrid_secure_key(hostname, name) - registry = OpenPypeSecureRegistry(key) - return registry.get_item(name, None), registry - - -def get_shotgrid_hostname(shotgrid_url): - - if not shotgrid_url: - raise Exception("Shotgrid url cannot be a null") - valid_shotgrid_url = ( - f"//{shotgrid_url}" if "//" not in shotgrid_url else shotgrid_url - ) - return urlparse(valid_shotgrid_url).hostname - - -# Credentials storing function (using keyring) - - -def get_credentials(shotgrid_url): - hostname = get_shotgrid_hostname(shotgrid_url) - if not hostname: - return None - login_value, _ = _get_secure_value_and_registry( - hostname, - Credentials.login_key_prefix(), - ) - password_value, _ = _get_secure_value_and_registry( - hostname, - Credentials.password_key_prefix(), - ) - return Credentials(login_value, password_value) - - -def save_credentials(login, password, shotgrid_url): - hostname = get_shotgrid_hostname(shotgrid_url) - _, login_registry = _get_secure_value_and_registry( - hostname, - Credentials.login_key_prefix(), - ) - _, password_registry = _get_secure_value_and_registry( - hostname, - Credentials.password_key_prefix(), - ) - clear_credentials(shotgrid_url) - login_registry.set_item(Credentials.login_key_prefix(), login) - password_registry.set_item(Credentials.password_key_prefix(), password) - - -def clear_credentials(shotgrid_url): - hostname = get_shotgrid_hostname(shotgrid_url) - login_value, login_registry = _get_secure_value_and_registry( - hostname, - Credentials.login_key_prefix(), - ) - password_value, password_registry = _get_secure_value_and_registry( - hostname, - Credentials.password_key_prefix(), - ) - - if login_value is not None: - login_registry.delete_item(Credentials.login_key_prefix()) - - if password_value is not None: - password_registry.delete_item(Credentials.password_key_prefix()) - - -# Login storing function (using json) - - -def get_local_login(): - reg = OpenPypeSettingsRegistry() - try: - return str(reg.get_item("shotgrid_login")) - except Exception: - return None - - -def save_local_login(login): - reg = OpenPypeSettingsRegistry() - reg.set_item("shotgrid_login", login) - - -def clear_local_login(): - reg = OpenPypeSettingsRegistry() - reg.delete_item("shotgrid_login") - - -def check_credentials( - login, - password, - shotgrid_url, -): - - if not shotgrid_url or not login or not password: - return False - try: - session = shotgun_api3.Shotgun( - shotgrid_url, - login=login, - password=password, - ) - session.preferences_read() - session.close() - except AuthenticationFault: - return False - return True diff --git a/openpype/modules/shotgrid/lib/record.py b/openpype/modules/shotgrid/lib/record.py deleted file mode 100644 index f62f4855d5..0000000000 --- a/openpype/modules/shotgrid/lib/record.py +++ /dev/null @@ -1,20 +0,0 @@ - -class Credentials: - login = None - password = None - - def __init__(self, login, password) -> None: - super().__init__() - self.login = login - self.password = password - - def is_empty(self): - return not (self.login and self.password) - - @staticmethod - def login_key_prefix(): - return "login" - - @staticmethod - def password_key_prefix(): - return "password" diff --git a/openpype/modules/shotgrid/lib/settings.py b/openpype/modules/shotgrid/lib/settings.py deleted file mode 100644 index 924099f04b..0000000000 --- a/openpype/modules/shotgrid/lib/settings.py +++ /dev/null @@ -1,18 +0,0 @@ -from openpype.api import get_system_settings, get_project_settings -from openpype.modules.shotgrid.lib.const import MODULE_NAME - - -def get_shotgrid_project_settings(project): - return get_project_settings(project).get(MODULE_NAME, {}) - - -def get_shotgrid_settings(): - return get_system_settings().get("modules", {}).get(MODULE_NAME, {}) - - -def get_shotgrid_servers(): - return get_shotgrid_settings().get("shotgrid_settings", {}) - - -def get_leecher_backend_url(): - return get_shotgrid_settings().get("leecher_backend_url") diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py deleted file mode 100644 index 0b03ac2e5d..0000000000 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py +++ /dev/null @@ -1,100 +0,0 @@ -import os - -import pyblish.api -from openpype.lib.mongo import OpenPypeMongoConnection - - -class CollectShotgridEntities(pyblish.api.ContextPlugin): - """Collect shotgrid entities according to the current context""" - - order = pyblish.api.CollectorOrder + 0.499 - label = "Shotgrid entities" - - def process(self, context): - - avalon_project = context.data.get("projectEntity") - avalon_asset = context.data.get("assetEntity") - avalon_task_name = os.getenv("AVALON_TASK") - - self.log.info(avalon_project) - self.log.info(avalon_asset) - - sg_project = _get_shotgrid_project(context) - sg_task = _get_shotgrid_task( - avalon_project, - avalon_asset, - avalon_task_name - ) - sg_entity = _get_shotgrid_entity(avalon_project, avalon_asset) - - if sg_project: - context.data["shotgridProject"] = sg_project - self.log.info( - "Collected correspondig shotgrid project : {}".format( - sg_project - ) - ) - - if sg_task: - context.data["shotgridTask"] = sg_task - self.log.info( - "Collected correspondig shotgrid task : {}".format(sg_task) - ) - - if sg_entity: - context.data["shotgridEntity"] = sg_entity - self.log.info( - "Collected correspondig shotgrid entity : {}".format(sg_entity) - ) - - def _find_existing_version(self, code, context): - - filters = [ - ["project", "is", context.data.get("shotgridProject")], - ["sg_task", "is", context.data.get("shotgridTask")], - ["entity", "is", context.data.get("shotgridEntity")], - ["code", "is", code], - ] - - sg = context.data.get("shotgridSession") - return sg.find_one("Version", filters, []) - - -def _get_shotgrid_collection(project): - client = OpenPypeMongoConnection.get_mongo_client() - return client.get_database("shotgrid_openpype").get_collection(project) - - -def _get_shotgrid_project(context): - shotgrid_project_id = context.data["project_settings"].get( - "shotgrid_project_id") - if shotgrid_project_id: - return {"type": "Project", "id": shotgrid_project_id} - return {} - - -def _get_shotgrid_task(avalon_project, avalon_asset, avalon_task): - sg_col = _get_shotgrid_collection(avalon_project["name"]) - shotgrid_task_hierarchy_row = sg_col.find_one( - { - "type": "Task", - "_id": {"$regex": "^" + avalon_task + "_[0-9]*"}, - "parent": {"$regex": ".*," + avalon_asset["name"] + ","}, - } - ) - if shotgrid_task_hierarchy_row: - return {"type": "Task", "id": shotgrid_task_hierarchy_row["src_id"]} - return {} - - -def _get_shotgrid_entity(avalon_project, avalon_asset): - sg_col = _get_shotgrid_collection(avalon_project["name"]) - shotgrid_entity_hierarchy_row = sg_col.find_one( - {"_id": avalon_asset["name"]} - ) - if shotgrid_entity_hierarchy_row: - return { - "type": shotgrid_entity_hierarchy_row["type"], - "id": shotgrid_entity_hierarchy_row["src_id"], - } - return {} diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py deleted file mode 100644 index 9d5d2271bf..0000000000 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_session.py +++ /dev/null @@ -1,123 +0,0 @@ -import os - -import pyblish.api -import shotgun_api3 -from shotgun_api3.shotgun import AuthenticationFault - -from openpype.lib import OpenPypeSettingsRegistry -from openpype.modules.shotgrid.lib.settings import ( - get_shotgrid_servers, - get_shotgrid_project_settings, -) - - -class CollectShotgridSession(pyblish.api.ContextPlugin): - """Collect shotgrid session using user credentials""" - - order = pyblish.api.CollectorOrder - label = "Shotgrid user session" - - def process(self, context): - - certificate_path = os.getenv("SHOTGUN_API_CACERTS") - if certificate_path is None or not os.path.exists(certificate_path): - self.log.info( - "SHOTGUN_API_CACERTS does not contains a valid \ - path: {}".format( - certificate_path - ) - ) - certificate_path = get_shotgrid_certificate() - self.log.info("Get Certificate from shotgrid_api") - - if not os.path.exists(certificate_path): - self.log.error( - "Could not find certificate in shotgun_api3: \ - {}".format( - certificate_path - ) - ) - return - - set_shotgrid_certificate(certificate_path) - self.log.info("Set Certificate: {}".format(certificate_path)) - - avalon_project = os.getenv("AVALON_PROJECT") - - shotgrid_settings = get_shotgrid_project_settings(avalon_project) - self.log.info("shotgrid settings: {}".format(shotgrid_settings)) - shotgrid_servers_settings = get_shotgrid_servers() - self.log.info( - "shotgrid_servers_settings: {}".format(shotgrid_servers_settings) - ) - - shotgrid_server = shotgrid_settings.get("shotgrid_server", "") - if not shotgrid_server: - self.log.error( - "No Shotgrid server found, please choose a credential" - "in script name and script key in OpenPype settings" - ) - - shotgrid_server_setting = shotgrid_servers_settings.get( - shotgrid_server, {} - ) - shotgrid_url = shotgrid_server_setting.get("shotgrid_url", "") - - shotgrid_script_name = shotgrid_server_setting.get( - "shotgrid_script_name", "" - ) - shotgrid_script_key = shotgrid_server_setting.get( - "shotgrid_script_key", "" - ) - if not shotgrid_script_name and not shotgrid_script_key: - self.log.error( - "No Shotgrid api credential found, please enter " - "script name and script key in OpenPype settings" - ) - - login = get_login() or os.getenv("OPENPYPE_SG_USER") - - if not login: - self.log.error( - "No Shotgrid login found, please " - "login to shotgrid withing openpype Tray" - ) - - session = shotgun_api3.Shotgun( - base_url=shotgrid_url, - script_name=shotgrid_script_name, - api_key=shotgrid_script_key, - sudo_as_login=login, - ) - - try: - session.preferences_read() - except AuthenticationFault: - raise ValueError( - "Could not connect to shotgrid {} with user {}".format( - shotgrid_url, login - ) - ) - - self.log.info( - "Logged to shotgrid {} with user {}".format(shotgrid_url, login) - ) - context.data["shotgridSession"] = session - context.data["shotgridUser"] = login - - -def get_shotgrid_certificate(): - shotgun_api_path = os.path.dirname(shotgun_api3.__file__) - return os.path.join(shotgun_api_path, "lib", "certifi", "cacert.pem") - - -def set_shotgrid_certificate(certificate): - os.environ["SHOTGUN_API_CACERTS"] = certificate - - -def get_login(): - reg = OpenPypeSettingsRegistry() - try: - return str(reg.get_item("shotgrid_login")) - except Exception: - return None diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py deleted file mode 100644 index cfd2d10fd9..0000000000 --- a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py +++ /dev/null @@ -1,77 +0,0 @@ -import os -import pyblish.api - - -class IntegrateShotgridPublish(pyblish.api.InstancePlugin): - """ - Create published Files from representations and add it to version. If - representation is tagged add shotgrid review, it will add it in - path to movie for a movie file or path to frame for an image sequence. - """ - - order = pyblish.api.IntegratorOrder + 0.499 - label = "Shotgrid Published Files" - - def process(self, instance): - - context = instance.context - - self.sg = context.data.get("shotgridSession") - - shotgrid_version = instance.data.get("shotgridVersion") - - for representation in instance.data.get("representations", []): - - local_path = representation.get("published_path") - code = os.path.basename(local_path) - - if representation.get("tags", []): - continue - - published_file = self._find_existing_publish( - code, context, shotgrid_version - ) - - published_file_data = { - "project": context.data.get("shotgridProject"), - "code": code, - "entity": context.data.get("shotgridEntity"), - "task": context.data.get("shotgridTask"), - "version": shotgrid_version, - "path": {"local_path": local_path}, - } - if not published_file: - published_file = self._create_published(published_file_data) - self.log.info( - "Create Shotgrid PublishedFile: {}".format(published_file) - ) - else: - self.sg.update( - published_file["type"], - published_file["id"], - published_file_data, - ) - self.log.info( - "Update Shotgrid PublishedFile: {}".format(published_file) - ) - - if instance.data["family"] == "image": - self.sg.upload_thumbnail( - published_file["type"], published_file["id"], local_path - ) - instance.data["shotgridPublishedFile"] = published_file - - def _find_existing_publish(self, code, context, shotgrid_version): - - filters = [ - ["project", "is", context.data.get("shotgridProject")], - ["task", "is", context.data.get("shotgridTask")], - ["entity", "is", context.data.get("shotgridEntity")], - ["version", "is", shotgrid_version], - ["code", "is", code], - ] - return self.sg.find_one("PublishedFile", filters, []) - - def _create_published(self, published_file_data): - - return self.sg.create("PublishedFile", published_file_data) diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py deleted file mode 100644 index a1b7140e22..0000000000 --- a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py +++ /dev/null @@ -1,92 +0,0 @@ -import os -import pyblish.api - - -class IntegrateShotgridVersion(pyblish.api.InstancePlugin): - """Integrate Shotgrid Version""" - - order = pyblish.api.IntegratorOrder + 0.497 - label = "Shotgrid Version" - - sg = None - - def process(self, instance): - - context = instance.context - self.sg = context.data.get("shotgridSession") - - # TODO: Use path template solver to build version code from settings - anatomy = instance.data.get("anatomyData", {}) - code = "_".join( - [ - anatomy["project"]["code"], - anatomy["parent"], - anatomy["asset"], - anatomy["task"]["name"], - "v{:03}".format(int(anatomy["version"])), - ] - ) - - version = self._find_existing_version(code, context) - - if not version: - version = self._create_version(code, context) - self.log.info("Create Shotgrid version: {}".format(version)) - else: - self.log.info("Use existing Shotgrid version: {}".format(version)) - - data_to_update = {} - status = context.data.get("intent", {}).get("value") - if status: - data_to_update["sg_status_list"] = status - - for representation in instance.data.get("representations", []): - local_path = representation.get("published_path") - code = os.path.basename(local_path) - - if "shotgridreview" in representation.get("tags", []): - - if representation["ext"] in ["mov", "avi"]: - self.log.info( - "Upload review: {} for version shotgrid {}".format( - local_path, version.get("id") - ) - ) - self.sg.upload( - "Version", - version.get("id"), - local_path, - field_name="sg_uploaded_movie", - ) - - data_to_update["sg_path_to_movie"] = local_path - - elif representation["ext"] in ["jpg", "png", "exr", "tga"]: - path_to_frame = local_path.replace("0000", "#") - data_to_update["sg_path_to_frames"] = path_to_frame - - self.log.info("Update Shotgrid version with {}".format(data_to_update)) - self.sg.update("Version", version["id"], data_to_update) - - instance.data["shotgridVersion"] = version - - def _find_existing_version(self, code, context): - - filters = [ - ["project", "is", context.data.get("shotgridProject")], - ["sg_task", "is", context.data.get("shotgridTask")], - ["entity", "is", context.data.get("shotgridEntity")], - ["code", "is", code], - ] - return self.sg.find_one("Version", filters, []) - - def _create_version(self, code, context): - - version_data = { - "project": context.data.get("shotgridProject"), - "sg_task": context.data.get("shotgridTask"), - "entity": context.data.get("shotgridEntity"), - "code": code, - } - - return self.sg.create("Version", version_data) diff --git a/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py b/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py deleted file mode 100644 index c14c980e2a..0000000000 --- a/openpype/modules/shotgrid/plugins/publish/validate_shotgrid_user.py +++ /dev/null @@ -1,38 +0,0 @@ -import pyblish.api -import openpype.api - - -class ValidateShotgridUser(pyblish.api.ContextPlugin): - """ - Check if user is valid and have access to the project. - """ - - label = "Validate Shotgrid User" - order = openpype.api.ValidateContentsOrder - - def process(self, context): - sg = context.data.get("shotgridSession") - - login = context.data.get("shotgridUser") - self.log.info("Login shotgrid set in OpenPype is {}".format(login)) - project = context.data.get("shotgridProject") - self.log.info("Current shotgun project is {}".format(project)) - - if not (login and sg and project): - raise KeyError() - - user = sg.find_one("HumanUser", [["login", "is", login]], ["projects"]) - - self.log.info(user) - self.log.info(login) - user_projects_id = [p["id"] for p in user.get("projects", [])] - if not project.get("id") in user_projects_id: - raise PermissionError( - "Login {} don't have access to the project {}".format( - login, project - ) - ) - - self.log.info( - "Login {} have access to the project {}".format(login, project) - ) diff --git a/openpype/modules/shotgrid/server/README.md b/openpype/modules/shotgrid/server/README.md deleted file mode 100644 index 15e056ff3e..0000000000 --- a/openpype/modules/shotgrid/server/README.md +++ /dev/null @@ -1,5 +0,0 @@ - -### Shotgrid server - -Please refer to the external project that covers Openpype/Shotgrid communication: - - https://github.com/Ellipsanime/shotgrid-leecher diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py deleted file mode 100644 index 5644f0c35f..0000000000 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ /dev/null @@ -1,58 +0,0 @@ -import os - -from openpype_interfaces import ( - ITrayModule, - IPluginPaths, - ILaunchHookPaths, -) - -from openpype.modules import OpenPypeModule - -SHOTGRID_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) - - -class ShotgridModule( - OpenPypeModule, ITrayModule, IPluginPaths, ILaunchHookPaths -): - leecher_manager_url = None - name = "shotgrid" - enabled = False - project_id = None - tray_wrapper = None - - def initialize(self, modules_settings): - shotgrid_settings = modules_settings.get(self.name, dict()) - self.enabled = shotgrid_settings.get("enabled", False) - self.leecher_manager_url = shotgrid_settings.get( - "leecher_manager_url", "" - ) - - def connect_with_modules(self, enabled_modules): - pass - - def get_global_environments(self): - return {"PROJECT_ID": self.project_id} - - def get_plugin_paths(self): - return { - "publish": [ - os.path.join(SHOTGRID_MODULE_DIR, "plugins", "publish") - ] - } - - def get_launch_hook_paths(self): - return os.path.join(SHOTGRID_MODULE_DIR, "hooks") - - def tray_init(self): - from .tray.shotgrid_tray import ShotgridTrayWrapper - - self.tray_wrapper = ShotgridTrayWrapper(self) - - def tray_start(self): - return self.tray_wrapper.validate() - - def tray_exit(self, *args, **kwargs): - return self.tray_wrapper - - def tray_menu(self, tray_menu): - return self.tray_wrapper.tray_menu(tray_menu) diff --git a/openpype/modules/shotgrid/tests/shotgrid/lib/test_credentials.py b/openpype/modules/shotgrid/tests/shotgrid/lib/test_credentials.py deleted file mode 100644 index 1f78cf77c9..0000000000 --- a/openpype/modules/shotgrid/tests/shotgrid/lib/test_credentials.py +++ /dev/null @@ -1,34 +0,0 @@ -import pytest -from assertpy import assert_that - -import openpype.modules.shotgrid.lib.credentials as sut - - -def test_missing_shotgrid_url(): - with pytest.raises(Exception) as ex: - # arrange - url = "" - # act - sut.get_shotgrid_hostname(url) - # assert - assert_that(ex).is_equal_to("Shotgrid url cannot be a null") - - -def test_full_shotgrid_url(): - # arrange - url = "https://shotgrid.com/myinstance" - # act - actual = sut.get_shotgrid_hostname(url) - # assert - assert_that(actual).is_not_empty() - assert_that(actual).is_equal_to("shotgrid.com") - - -def test_incomplete_shotgrid_url(): - # arrange - url = "shotgrid.com/myinstance" - # act - actual = sut.get_shotgrid_hostname(url) - # assert - assert_that(actual).is_not_empty() - assert_that(actual).is_equal_to("shotgrid.com") diff --git a/openpype/modules/shotgrid/tray/credential_dialog.py b/openpype/modules/shotgrid/tray/credential_dialog.py deleted file mode 100644 index 9d841d98be..0000000000 --- a/openpype/modules/shotgrid/tray/credential_dialog.py +++ /dev/null @@ -1,201 +0,0 @@ -import os -from Qt import QtCore, QtWidgets, QtGui - -from openpype import style -from openpype import resources -from openpype.modules.shotgrid.lib import settings, credentials - - -class CredentialsDialog(QtWidgets.QDialog): - SIZE_W = 450 - SIZE_H = 200 - - _module = None - _is_logged = False - url_label = None - login_label = None - password_label = None - url_input = None - login_input = None - password_input = None - input_layout = None - login_button = None - buttons_layout = None - main_widget = None - - login_changed = QtCore.Signal() - - def __init__(self, module, parent=None): - super(CredentialsDialog, self).__init__(parent) - - self._module = module - self._is_logged = False - - self.setWindowTitle("OpenPype - Shotgrid Login") - - icon = QtGui.QIcon(resources.get_openpype_icon_filepath()) - self.setWindowIcon(icon) - - self.setWindowFlags( - QtCore.Qt.WindowCloseButtonHint - | QtCore.Qt.WindowMinimizeButtonHint - ) - self.setMinimumSize(QtCore.QSize(self.SIZE_W, self.SIZE_H)) - self.setMaximumSize(QtCore.QSize(self.SIZE_W + 100, self.SIZE_H + 100)) - self.setStyleSheet(style.load_stylesheet()) - - self.ui_init() - - def ui_init(self): - self.url_label = QtWidgets.QLabel("Shotgrid server:") - self.login_label = QtWidgets.QLabel("Login:") - self.password_label = QtWidgets.QLabel("Password:") - - self.url_input = QtWidgets.QComboBox() - # self.url_input.setReadOnly(True) - - self.login_input = QtWidgets.QLineEdit() - self.login_input.setPlaceholderText("login") - - self.password_input = QtWidgets.QLineEdit() - self.password_input.setPlaceholderText("password") - self.password_input.setEchoMode(QtWidgets.QLineEdit.Password) - - self.error_label = QtWidgets.QLabel("") - self.error_label.setStyleSheet("color: red;") - self.error_label.setWordWrap(True) - self.error_label.hide() - - self.input_layout = QtWidgets.QFormLayout() - self.input_layout.setContentsMargins(10, 15, 10, 5) - - self.input_layout.addRow(self.url_label, self.url_input) - self.input_layout.addRow(self.login_label, self.login_input) - self.input_layout.addRow(self.password_label, self.password_input) - self.input_layout.addRow(self.error_label) - - self.login_button = QtWidgets.QPushButton("Login") - self.login_button.setToolTip("Log in shotgrid instance") - self.login_button.clicked.connect(self._on_shotgrid_login_clicked) - - self.logout_button = QtWidgets.QPushButton("Logout") - self.logout_button.setToolTip("Log out shotgrid instance") - self.logout_button.clicked.connect(self._on_shotgrid_logout_clicked) - - self.buttons_layout = QtWidgets.QHBoxLayout() - self.buttons_layout.addWidget(self.logout_button) - self.buttons_layout.addWidget(self.login_button) - - self.main_widget = QtWidgets.QVBoxLayout(self) - self.main_widget.addLayout(self.input_layout) - self.main_widget.addLayout(self.buttons_layout) - self.setLayout(self.main_widget) - - def show(self, *args, **kwargs): - super(CredentialsDialog, self).show(*args, **kwargs) - self._fill_shotgrid_url() - self._fill_shotgrid_login() - - def _fill_shotgrid_url(self): - servers = settings.get_shotgrid_servers() - - if servers: - for _, v in servers.items(): - self.url_input.addItem("{}".format(v.get('shotgrid_url'))) - self._valid_input(self.url_input) - self.login_button.show() - self.logout_button.show() - enabled = True - else: - self.set_error("Ask your admin to add shotgrid server in settings") - self._invalid_input(self.url_input) - self.login_button.hide() - self.logout_button.hide() - enabled = False - - self.login_input.setEnabled(enabled) - self.password_input.setEnabled(enabled) - - def _fill_shotgrid_login(self): - login = credentials.get_local_login() - - if login: - self.login_input.setText(login) - - def _clear_shotgrid_login(self): - self.login_input.setText("") - self.password_input.setText("") - - def _on_shotgrid_login_clicked(self): - login = self.login_input.text().strip() - password = self.password_input.text().strip() - missing = [] - - if login == "": - missing.append("login") - self._invalid_input(self.login_input) - - if password == "": - missing.append("password") - self._invalid_input(self.password_input) - - url = self.url_input.currentText() - if url == "": - missing.append("url") - self._invalid_input(self.url_input) - - if len(missing) > 0: - self.set_error("You didn't enter {}".format(" and ".join(missing))) - return - - # if credentials.check_credentials( - # login=login, - # password=password, - # shotgrid_url=url, - # ): - credentials.save_local_login( - login=login - ) - os.environ['OPENPYPE_SG_USER'] = login - self._on_login() - - self.set_error("CANT LOGIN") - - def _on_shotgrid_logout_clicked(self): - credentials.clear_local_login() - del os.environ['OPENPYPE_SG_USER'] - self._clear_shotgrid_login() - self._on_logout() - - def set_error(self, msg): - self.error_label.setText(msg) - self.error_label.show() - - def _on_login(self): - self._is_logged = True - self.login_changed.emit() - self._close_widget() - - def _on_logout(self): - self._is_logged = False - self.login_changed.emit() - - def _close_widget(self): - self.hide() - - def _valid_input(self, input_widget): - input_widget.setStyleSheet("") - - def _invalid_input(self, input_widget): - input_widget.setStyleSheet("border: 1px solid red;") - - def login_with_credentials( - self, url, login, password - ): - verification = credentials.check_credentials(url, login, password) - if verification: - credentials.save_credentials(login, password, False) - self._module.set_credentials_to_env(login, password) - self.set_credentials(login, password) - self.login_changed.emit() - return verification diff --git a/openpype/modules/shotgrid/tray/shotgrid_tray.py b/openpype/modules/shotgrid/tray/shotgrid_tray.py deleted file mode 100644 index 4038d77b03..0000000000 --- a/openpype/modules/shotgrid/tray/shotgrid_tray.py +++ /dev/null @@ -1,75 +0,0 @@ -import os -import webbrowser - -from Qt import QtWidgets - -from openpype.modules.shotgrid.lib import credentials -from openpype.modules.shotgrid.tray.credential_dialog import ( - CredentialsDialog, -) - - -class ShotgridTrayWrapper: - module = None - credentials_dialog = None - logged_user_label = None - - def __init__(self, module): - self.module = module - self.credentials_dialog = CredentialsDialog(module) - self.credentials_dialog.login_changed.connect(self.set_login_label) - self.logged_user_label = QtWidgets.QAction("") - self.logged_user_label.setDisabled(True) - self.set_login_label() - - def show_batch_dialog(self): - if self.module.leecher_manager_url: - webbrowser.open(self.module.leecher_manager_url) - - def show_connect_dialog(self): - self.show_credential_dialog() - - def show_credential_dialog(self): - self.credentials_dialog.show() - self.credentials_dialog.activateWindow() - self.credentials_dialog.raise_() - - def set_login_label(self): - login = credentials.get_local_login() - if login: - self.logged_user_label.setText("{}".format(login)) - else: - self.logged_user_label.setText( - "No User logged in {0}".format(login) - ) - - def tray_menu(self, tray_menu): - # Add login to user menu - menu = QtWidgets.QMenu("Shotgrid", tray_menu) - show_connect_action = QtWidgets.QAction("Connect to Shotgrid", menu) - show_connect_action.triggered.connect(self.show_connect_dialog) - menu.addAction(self.logged_user_label) - menu.addSeparator() - menu.addAction(show_connect_action) - tray_menu.addMenu(menu) - - # Add manager to Admin menu - for m in tray_menu.findChildren(QtWidgets.QMenu): - if m.title() == "Admin": - shotgrid_manager_action = QtWidgets.QAction( - "Shotgrid manager", menu - ) - shotgrid_manager_action.triggered.connect( - self.show_batch_dialog - ) - m.addAction(shotgrid_manager_action) - - def validate(self): - login = credentials.get_local_login() - - if not login: - self.show_credential_dialog() - else: - os.environ["OPENPYPE_SG_USER"] = login - - return True diff --git a/openpype/resources/app_icons/shotgrid.png b/openpype/resources/app_icons/shotgrid.png deleted file mode 100644 index 6d0cc047f9ed86e0db45ea557404ab7edb2f5bf2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45744 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 N8$~sR3OTc&{{b(fTU!7C diff --git a/openpype/settings/defaults/project_settings/shotgrid.json b/openpype/settings/defaults/project_settings/shotgrid.json deleted file mode 100644 index 83b6f69074..0000000000 --- a/openpype/settings/defaults/project_settings/shotgrid.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "shotgrid_project_id": 0, - "shotgrid_server": "", - "event": { - "enabled": false - }, - "fields": { - "asset": { - "type": "sg_asset_type" - }, - "sequence": { - "episode_link": "episode" - }, - "shot": { - "episode_link": "sg_episode", - "sequence_link": "sg_sequence" - }, - "task": { - "step": "step" - } - } -} diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 9d8910689a..8cd4114cb0 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -131,12 +131,6 @@ } } }, - "shotgrid": { - "enabled": false, - "leecher_manager_url": "http://127.0.0.1:3000", - "leecher_backend_url": "http://127.0.0.1:8090", - "shotgrid_settings": {} - }, "kitsu": { "enabled": false, "server": "" @@ -209,4 +203,4 @@ "linux": "" } } -} +} \ No newline at end of file diff --git a/openpype/settings/entities/__init__.py b/openpype/settings/entities/__init__.py index b2cb2204f4..a173e2454f 100644 --- a/openpype/settings/entities/__init__.py +++ b/openpype/settings/entities/__init__.py @@ -107,7 +107,6 @@ from .enum_entity import ( TaskTypeEnumEntity, DeadlineUrlEnumEntity, AnatomyTemplatesEnumEntity, - ShotgridUrlEnumEntity ) from .list_entity import ListEntity @@ -172,7 +171,6 @@ __all__ = ( "ToolsEnumEntity", "TaskTypeEnumEntity", "DeadlineUrlEnumEntity", - "ShotgridUrlEnumEntity", "AnatomyTemplatesEnumEntity", "ListEntity", diff --git a/openpype/settings/entities/enum_entity.py b/openpype/settings/entities/enum_entity.py index 3b3dd47e61..92a397afba 100644 --- a/openpype/settings/entities/enum_entity.py +++ b/openpype/settings/entities/enum_entity.py @@ -1,7 +1,10 @@ import copy from .input_entities import InputEntity from .exceptions import EntitySchemaError -from .lib import NOT_SET, STRING_TYPE +from .lib import ( + NOT_SET, + STRING_TYPE +) class BaseEnumEntity(InputEntity): @@ -23,7 +26,7 @@ class BaseEnumEntity(InputEntity): for item in self.enum_items: key = tuple(item.keys())[0] if key in enum_keys: - reason = 'Key "{}" is more than once in enum items.'.format( + reason = "Key \"{}\" is more than once in enum items.".format( key ) raise EntitySchemaError(self, reason) @@ -31,7 +34,7 @@ class BaseEnumEntity(InputEntity): enum_keys.add(key) if not isinstance(key, STRING_TYPE): - reason = 'Key "{}" has invalid type {}, expected {}.'.format( + reason = "Key \"{}\" has invalid type {}, expected {}.".format( key, type(key), STRING_TYPE ) raise EntitySchemaError(self, reason) @@ -56,7 +59,7 @@ class BaseEnumEntity(InputEntity): for item in check_values: if item not in self.valid_keys: raise ValueError( - '{} Invalid value "{}". Expected one of: {}'.format( + "{} Invalid value \"{}\". Expected one of: {}".format( self.path, item, self.valid_keys ) ) @@ -81,7 +84,7 @@ class EnumEntity(BaseEnumEntity): self.valid_keys = set(all_keys) if self.multiselection: - self.valid_value_types = (list,) + self.valid_value_types = (list, ) value_on_not_set = [] if enum_default: if not isinstance(enum_default, list): @@ -106,7 +109,7 @@ class EnumEntity(BaseEnumEntity): self.value_on_not_set = key break - self.valid_value_types = (STRING_TYPE,) + self.valid_value_types = (STRING_TYPE, ) # GUI attribute self.placeholder = self.schema_data.get("placeholder") @@ -149,7 +152,6 @@ class HostsEnumEntity(BaseEnumEntity): Host name is not the same as application name. Host name defines implementation instead of application name. """ - schema_types = ["hosts-enum"] all_host_names = [ "aftereffects", @@ -167,7 +169,7 @@ class HostsEnumEntity(BaseEnumEntity): "tvpaint", "unreal", "standalonepublisher", - "webpublisher", + "webpublisher" ] def _item_initialization(self): @@ -208,7 +210,7 @@ class HostsEnumEntity(BaseEnumEntity): self.valid_keys = valid_keys if self.multiselection: - self.valid_value_types = (list,) + self.valid_value_types = (list, ) self.value_on_not_set = [] else: for key in valid_keys: @@ -216,7 +218,7 @@ class HostsEnumEntity(BaseEnumEntity): self.value_on_not_set = key break - self.valid_value_types = (STRING_TYPE,) + self.valid_value_types = (STRING_TYPE, ) # GUI attribute self.placeholder = self.schema_data.get("placeholder") @@ -224,10 +226,14 @@ class HostsEnumEntity(BaseEnumEntity): def schema_validations(self): if self.hosts_filter: enum_len = len(self.enum_items) - if enum_len == 0 or (enum_len == 1 and self.use_empty_value): - joined_filters = ", ".join( - ['"{}"'.format(item) for item in self.hosts_filter] - ) + if ( + enum_len == 0 + or (enum_len == 1 and self.use_empty_value) + ): + joined_filters = ", ".join([ + '"{}"'.format(item) + for item in self.hosts_filter + ]) reason = ( "All host names were removed after applying" " host filters. {}" @@ -240,25 +246,24 @@ class HostsEnumEntity(BaseEnumEntity): invalid_filters.add(item) if invalid_filters: - joined_filters = ", ".join( - ['"{}"'.format(item) for item in self.hosts_filter] - ) - expected_hosts = ", ".join( - ['"{}"'.format(item) for item in self.all_host_names] - ) - self.log.warning( - ( - "Host filters containt invalid host names:" - ' "{}" Expected values are {}' - ).format(joined_filters, expected_hosts) - ) + joined_filters = ", ".join([ + '"{}"'.format(item) + for item in self.hosts_filter + ]) + expected_hosts = ", ".join([ + '"{}"'.format(item) + for item in self.all_host_names + ]) + self.log.warning(( + "Host filters containt invalid host names:" + " \"{}\" Expected values are {}" + ).format(joined_filters, expected_hosts)) super(HostsEnumEntity, self).schema_validations() class AppsEnumEntity(BaseEnumEntity): """Enum of applications for project anatomy attributes.""" - schema_types = ["apps-enum"] def _item_initialization(self): @@ -266,7 +271,7 @@ class AppsEnumEntity(BaseEnumEntity): self.value_on_not_set = [] self.enum_items = [] self.valid_keys = set() - self.valid_value_types = (list,) + self.valid_value_types = (list, ) self.placeholder = None def _get_enum_values(self): @@ -347,7 +352,7 @@ class ToolsEnumEntity(BaseEnumEntity): self.value_on_not_set = [] self.enum_items = [] self.valid_keys = set() - self.valid_value_types = (list,) + self.valid_value_types = (list, ) self.placeholder = None def _get_enum_values(self): @@ -404,10 +409,10 @@ class TaskTypeEnumEntity(BaseEnumEntity): def _item_initialization(self): self.multiselection = self.schema_data.get("multiselection", True) if self.multiselection: - self.valid_value_types = (list,) + self.valid_value_types = (list, ) self.value_on_not_set = [] else: - self.valid_value_types = (STRING_TYPE,) + self.valid_value_types = (STRING_TYPE, ) self.value_on_not_set = "" self.enum_items = [] @@ -502,8 +507,7 @@ class DeadlineUrlEnumEntity(BaseEnumEntity): enum_items_list = [] for server_name, url_entity in deadline_urls_entity.items(): enum_items_list.append( - {server_name: "{}: {}".format(server_name, url_entity.value)} - ) + {server_name: "{}: {}".format(server_name, url_entity.value)}) valid_keys.add(server_name) return enum_items_list, valid_keys @@ -526,50 +530,6 @@ class DeadlineUrlEnumEntity(BaseEnumEntity): self._current_value = tuple(self.valid_keys)[0] -class ShotgridUrlEnumEntity(BaseEnumEntity): - schema_types = ["shotgrid_url-enum"] - - def _item_initialization(self): - self.multiselection = False - - self.enum_items = [] - self.valid_keys = set() - - self.valid_value_types = (STRING_TYPE,) - self.value_on_not_set = "" - - # GUI attribute - self.placeholder = self.schema_data.get("placeholder") - - def _get_enum_values(self): - shotgrid_settings = self.get_entity_from_path( - "system_settings/modules/shotgrid/shotgrid_settings" - ) - - valid_keys = set() - enum_items_list = [] - for server_name, settings in shotgrid_settings.items(): - enum_items_list.append( - { - server_name: "{}: {}".format( - server_name, settings["shotgrid_url"].value - ) - } - ) - valid_keys.add(server_name) - return enum_items_list, valid_keys - - def set_override_state(self, *args, **kwargs): - super(ShotgridUrlEnumEntity, self).set_override_state(*args, **kwargs) - - self.enum_items, self.valid_keys = self._get_enum_values() - if not self.valid_keys: - self._current_value = "" - - elif self._current_value not in self.valid_keys: - self._current_value = tuple(self.valid_keys)[0] - - class AnatomyTemplatesEnumEntity(BaseEnumEntity): schema_types = ["anatomy-templates-enum"] diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json index 80b1baad1b..6c07209de3 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_main.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json @@ -62,10 +62,6 @@ "type": "schema", "name": "schema_project_ftrack" }, - { - "type": "schema", - "name": "schema_project_shotgrid" - }, { "type": "schema", "name": "schema_project_kitsu" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_shotgrid.json b/openpype/settings/entities/schemas/projects_schema/schema_project_shotgrid.json deleted file mode 100644 index 4faeca89f3..0000000000 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_shotgrid.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "type": "dict", - "key": "shotgrid", - "label": "Shotgrid", - "collapsible": true, - "is_file": true, - "children": [ - { - "type": "number", - "key": "shotgrid_project_id", - "label": "Shotgrid project id" - }, - { - "type": "shotgrid_url-enum", - "key": "shotgrid_server", - "label": "Shotgrid Server" - }, - { - "type": "dict", - "key": "event", - "label": "Event Handler", - "collapsible": true, - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - } - ] - }, - { - "type": "dict", - "key": "fields", - "label": "Fields Template", - "collapsible": true, - "children": [ - { - "type": "dict", - "key": "asset", - "label": "Asset", - "collapsible": true, - "children": [ - { - "type": "text", - "key": "type", - "label": "Asset Type" - } - ] - }, - { - "type": "dict", - "key": "sequence", - "label": "Sequence", - "collapsible": true, - "children": [ - { - "type": "text", - "key": "episode_link", - "label": "Episode link" - } - ] - }, - { - "type": "dict", - "key": "shot", - "label": "Shot", - "collapsible": true, - "children": [ - { - "type": "text", - "key": "episode_link", - "label": "Episode link" - }, - { - "type": "text", - "key": "sequence_link", - "label": "Sequence link" - } - ] - }, - { - "type": "dict", - "key": "task", - "label": "Task", - "collapsible": true, - "children": [ - { - "type": "text", - "key": "step", - "label": "Step link" - } - ] - } - ] - } - ] -} diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json index a4b28f47bc..484fbf9d07 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json @@ -13,9 +13,6 @@ { "ftrackreview": "Add review to Ftrack" }, - { - "shotgridreview": "Add review to Shotgrid" - }, { "delete": "Delete output" }, diff --git a/openpype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json index 952b38040c..d22b9016a7 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_modules.json +++ b/openpype/settings/entities/schemas/system_schema/schema_modules.json @@ -48,60 +48,6 @@ "type": "schema", "name": "schema_kitsu" }, - { - "type": "dict", - "key": "shotgrid", - "label": "Shotgrid", - "collapsible": true, - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "text", - "key": "leecher_manager_url", - "label": "Shotgrid Leecher Manager URL" - }, - { - "type": "text", - "key": "leecher_backend_url", - "label": "Shotgrid Leecher Backend URL" - }, - { - "type": "boolean", - "key": "filter_projects_by_login", - "label": "Filter projects by SG login" - }, - { - "type": "dict-modifiable", - "key": "shotgrid_settings", - "label": "Shotgrid Servers", - "object_type": { - "type": "dict", - "children": [ - { - "key": "shotgrid_url", - "label": "Server URL", - "type": "text" - }, - { - "key": "shotgrid_script_name", - "label": "Script Name", - "type": "text" - }, - { - "key": "shotgrid_script_key", - "label": "Script api key", - "type": "text" - } - ] - } - } - ] - }, { "type": "dict", "key": "timers_manager", diff --git a/poetry.lock b/poetry.lock index f6ccf1ffc9..47509f334e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1362,21 +1362,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "shotgun-api3" -version = "3.3.3" -description = "Shotgun Python API" -category = "main" -optional = false -python-versions = "*" -develop = false - -[package.source] -type = "git" -url = "https://github.com/shotgunsoftware/python-api.git" -reference = "v3.3.3" -resolved_reference = "b9f066c0edbea6e0733242e18f32f75489064840" - [[package]] name = "six" version = "1.16.0" @@ -2818,7 +2803,6 @@ semver = [ {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, ] -shotgun-api3 = [] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, diff --git a/pyproject.toml b/pyproject.toml index c68de91623..4b297fe042 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,6 @@ clique = "1.6.*" Click = "^7" dnspython = "^2.1.0" ftrack-python-api = "2.0.*" -shotgun_api3 = {git = "https://github.com/shotgunsoftware/python-api.git", rev = "v3.3.3"} gazu = "^0.8.28" google-api-python-client = "^1.12.8" # sync server google support (should be separate?) jsonschema = "^2.6.0" From 6d4f05e3da277f11fc7614c340f63024f1997036 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Jun 2022 14:12:05 +0200 Subject: [PATCH 0770/1227] added ability to pass asset name to 'get_last_version_by_subset_name' --- openpype/client/entities.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 4b4a3729fe..9864fee469 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -789,14 +789,19 @@ def get_last_version_by_subset_id(project_name, subset_id, fields=None): def get_last_version_by_subset_name( - project_name, subset_name, asset_id, fields=None + project_name, subset_name, asset_id=None, asset_name=None, fields=None ): - """Last version for passed subset name under asset id. + """Last version for passed subset name under asset id/name. + + It is required to pass 'asset_id' or 'asset_name'. Asset id is recommended + if is available. Args: project_name (str): Name of project where to look for queried entities. subset_name (str): Name of subset. - asset_id (str|ObjectId): Asset id which is parnt of passed subset name. + asset_id (str|ObjectId): Asset id which is parent of passed + subset name. + asset_name (str): Asset name which is parent of passed subset name. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -805,6 +810,14 @@ def get_last_version_by_subset_name( Dict: Version document which can be reduced to specified 'fields'. """ + if not asset_id and not asset_name: + return None + + if not asset_id: + asset_doc = get_asset_by_name(project_name, asset_name, fields=["_id"]) + if not asset_doc: + return None + asset_id = asset_doc["_id"] subset_doc = get_subset_by_name( project_name, subset_name, asset_id, fields=["_id"] ) From f43042eb169ad9214ad01511a3c4dee5fe469083 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 22 Jun 2022 15:12:40 +0300 Subject: [PATCH 0771/1227] Change hwFogEnable into fogging flag. --- .../projects_schema/schemas/schema_maya_capture.json | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index fa5be19cda..217aa947fc 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -268,7 +268,7 @@ }, { "type": "boolean", - "key": "hwFogEnable", + "key": "fogging", "label": "Enable Hardware Fog" }, { @@ -304,21 +304,11 @@ { "type": "splitter" }, - { - "type": "boolean", - "key": "motionBlurEnable", - "label": "Enable Motion Blur" - }, { "type": "number", "key": "motionBlurShutterOpenFraction", "label": "Shutter Open Fraction" }, - { - "type": "number", - "key": "motionBlurSampleCount", - "label": "Sample Count" - }, { "type": "splitter" }, From 8d849b7e08139b4e1a13db79f41cf35f43ef7893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 22 Jun 2022 14:13:32 +0200 Subject: [PATCH 0772/1227] :recycle: remove unnecessary check --- .../hosts/maya/plugins/publish/extract_camera_mayaScene.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py index ac696ebdef..8d6c4b5f3c 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py @@ -181,16 +181,11 @@ class ExtractCameraMayaScene(openpype.api.Extractor): # Fix PLN-178: Don't allow background color to be non-black for cam, (attr, value) in itertools.product(cmds.ls( baked_camera_shapes, type="camera", dag=True, - shapes=True, long=True), attrs.items()): - # the above call still pull in shapes that are not - # cameras, so we filter them out here - if cmds.nodeType(cam) != "camera": - continue + long=True), attrs.items()): plug = "{0}.{1}".format(cam, attr) unlock(plug) cmds.setAttr(plug, value) - self.log.info("Performing extraction..") cmds.select(cmds.ls(members, dag=True, shapes=True, long=True), noExpand=True) From 4e59ae973e055953cbf77f0f9c8932120e0d19b2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Jun 2022 14:19:15 +0200 Subject: [PATCH 0773/1227] use query functions in loaders --- .../hosts/nuke/plugins/load/load_backdrop.py | 35 ++++++++------- .../nuke/plugins/load/load_camera_abc.py | 38 ++++++++-------- openpype/hosts/nuke/plugins/load/load_clip.py | 37 +++++++-------- .../hosts/nuke/plugins/load/load_effects.py | 35 ++++++++------- .../nuke/plugins/load/load_effects_ip.py | 33 +++++++------- .../hosts/nuke/plugins/load/load_gizmo.py | 34 +++++++------- .../hosts/nuke/plugins/load/load_gizmo_ip.py | 34 +++++++------- .../hosts/nuke/plugins/load/load_image.py | 34 +++++++------- .../hosts/nuke/plugins/load/load_model.py | 38 ++++++++-------- .../nuke/plugins/load/load_script_precomp.py | 45 +++++++++---------- 10 files changed, 183 insertions(+), 180 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_backdrop.py b/openpype/hosts/nuke/plugins/load/load_backdrop.py index 143fdf1f30..164ab6f9f4 100644 --- a/openpype/hosts/nuke/plugins/load/load_backdrop.py +++ b/openpype/hosts/nuke/plugins/load/load_backdrop.py @@ -1,6 +1,10 @@ import nuke import nukescripts +from openpype.client import ( + get_version_by_id, + get_last_version_by_subset_id, +) from openpype.pipeline import ( legacy_io, load, @@ -188,18 +192,17 @@ class LoadBackdropNodes(load.LoaderPlugin): # get main variables # Get version from io - version = legacy_io.find_one({ - "type": "version", - "_id": representation["parent"] - }) + project_name = legacy_io.active_project() + version_doc = get_version_by_id(project_name, representation["parent"]) + # get corresponding node GN = nuke.toNode(container['objectName']) file = get_representation_path(representation).replace("\\", "/") - context = representation["context"] + name = container['name'] - version_data = version.get("data", {}) - vname = version.get("name", None) + version_data = version_doc.get("data", {}) + vname = version_doc.get("name", None) first = version_data.get("frameStart", None) last = version_data.get("frameEnd", None) namespace = container['namespace'] @@ -237,20 +240,18 @@ class LoadBackdropNodes(load.LoaderPlugin): GN["name"].setValue(object_name) # get all versions in list - versions = legacy_io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') - - max_version = max(versions) + last_version_doc = get_last_version_by_subset_id( + project_name, version_doc["parent"], fields=["_id"] + ) # change color of node - if version.get("name") not in [max_version]: - GN["tile_color"].setValue(int("0xd88467ff", 16)) + if version_doc["_id"] == last_version_doc["_id"]: + color_value = self.node_color else: - GN["tile_color"].setValue(int(self.node_color, 16)) + color_value = "0xd88467ff" + GN["tile_color"].setValue(int(color_value, 16)) - self.log.info("updated to version: {}".format(version.get("name"))) + self.log.info("updated to version: {}".format(version_doc.get("name"))) return update_container(GN, data_imprint) diff --git a/openpype/hosts/nuke/plugins/load/load_camera_abc.py b/openpype/hosts/nuke/plugins/load/load_camera_abc.py index 964ca5ec90..f5dfc8c0ab 100644 --- a/openpype/hosts/nuke/plugins/load/load_camera_abc.py +++ b/openpype/hosts/nuke/plugins/load/load_camera_abc.py @@ -1,5 +1,9 @@ import nuke +from openpype.client import ( + get_version_by_id, + get_last_version_by_subset_id +) from openpype.pipeline import ( legacy_io, load, @@ -102,17 +106,16 @@ class AlembicCameraLoader(load.LoaderPlugin): None """ # Get version from io - version = legacy_io.find_one({ - "type": "version", - "_id": representation["parent"] - }) + project_name = legacy_io.active_project() + version_doc = get_version_by_id(project_name, representation["parent"]) + object_name = container['objectName'] # get corresponding node camera_node = nuke.toNode(object_name) # get main variables - version_data = version.get("data", {}) - vname = version.get("name", None) + version_data = version_doc.get("data", {}) + vname = version_doc.get("name", None) first = version_data.get("frameStart", None) last = version_data.get("frameEnd", None) fps = version_data.get("fps") or nuke.root()["fps"].getValue() @@ -165,28 +168,27 @@ class AlembicCameraLoader(load.LoaderPlugin): d.setInput(index, camera_node) # color node by correct color by actual version - self.node_version_color(version, camera_node) + self.node_version_color(version_doc, camera_node) - self.log.info("updated to version: {}".format(version.get("name"))) + self.log.info("updated to version: {}".format(version_doc.get("name"))) return update_container(camera_node, data_imprint) - def node_version_color(self, version, node): + def node_version_color(self, version_doc, node): """ Coloring a node by correct color by actual version """ # get all versions in list - versions = legacy_io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') - - max_version = max(versions) + project_name = legacy_io.active_project() + last_version_doc = get_last_version_by_subset_id( + project_name, version_doc["parent"], fields=["_id"] + ) # change color of node - if version.get("name") not in [max_version]: - node["tile_color"].setValue(int("0xd88467ff", 16)) + if version_doc["_id"] == last_version_doc["_id"]: + color_value = self.node_color else: - node["tile_color"].setValue(int(self.node_color, 16)) + color_value = "0xd88467ff" + node["tile_color"].setValue(int(color_value, 16)) def switch(self, container, representation): self.update(container, representation) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index 681561e303..d177e6ba76 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -1,6 +1,10 @@ import nuke import qargparse +from openpype.client import ( + get_version_by_id, + get_last_version_by_subset_id, +) from openpype.pipeline import ( legacy_io, get_representation_path, @@ -196,11 +200,10 @@ class LoadClip(plugin.NukeLoader): start_at_workfile = bool("start at" in read_node['frame_mode'].value()) - version = legacy_io.find_one({ - "type": "version", - "_id": representation["parent"] - }) - version_data = version.get("data", {}) + project_name = legacy_io.active_project() + version_doc = get_version_by_id(project_name, representation["parent"]) + + version_data = version_doc.get("data", {}) repre_id = representation["_id"] repre_cont = representation["context"] @@ -251,7 +254,7 @@ class LoadClip(plugin.NukeLoader): "representation": str(representation["_id"]), "frameStart": str(first), "frameEnd": str(last), - "version": str(version.get("name")), + "version": str(version_doc.get("name")), "db_colorspace": colorspace, "source": version_data.get("source"), "handleStart": str(self.handle_start), @@ -264,26 +267,24 @@ class LoadClip(plugin.NukeLoader): if used_colorspace: updated_dict["used_colorspace"] = used_colorspace + last_version_doc = get_last_version_by_subset_id( + project_name, version_doc["parent"], fields=["_id"] + ) # change color of read_node - # get all versions in list - versions = legacy_io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') - - max_version = max(versions) - - if version.get("name") not in [max_version]: - read_node["tile_color"].setValue(int("0xd84f20ff", 16)) + if version_doc["_id"] == last_version_doc["_id"]: + color_value = "0x4ecd25ff" else: - read_node["tile_color"].setValue(int("0x4ecd25ff", 16)) + color_value = "0xd84f20ff" + read_node["tile_color"].setValue(int(color_value, 16)) # Update the imprinted representation update_container( read_node, updated_dict ) - self.log.info("updated to version: {}".format(version.get("name"))) + self.log.info( + "updated to version: {}".format(version_doc.get("name")) + ) if version_data.get("retime", None): self._make_retimes(read_node, version_data) diff --git a/openpype/hosts/nuke/plugins/load/load_effects.py b/openpype/hosts/nuke/plugins/load/load_effects.py index 6a30330ed0..d164e0604c 100644 --- a/openpype/hosts/nuke/plugins/load/load_effects.py +++ b/openpype/hosts/nuke/plugins/load/load_effects.py @@ -3,6 +3,10 @@ from collections import OrderedDict import nuke import six +from openpype.client import ( + get_version_by_id, + get_last_version_by_subset_id, +) from openpype.pipeline import ( legacy_io, load, @@ -148,17 +152,16 @@ class LoadEffects(load.LoaderPlugin): """ # get main variables # Get version from io - version = legacy_io.find_one({ - "type": "version", - "_id": representation["parent"] - }) + project_name = legacy_io.active_project() + version_doc = get_version_by_id(project_name, representation["parent"]) + # get corresponding node GN = nuke.toNode(container['objectName']) file = get_representation_path(representation).replace("\\", "/") name = container['name'] - version_data = version.get("data", {}) - vname = version.get("name", None) + version_data = version_doc.get("data", {}) + vname = version_doc.get("name", None) first = version_data.get("frameStart", None) last = version_data.get("frameEnd", None) workfile_first_frame = int(nuke.root()["first_frame"].getValue()) @@ -243,21 +246,19 @@ class LoadEffects(load.LoaderPlugin): # try to find parent read node self.connect_read_node(GN, namespace, json_f["assignTo"]) - # get all versions in list - versions = legacy_io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') - - max_version = max(versions) + last_version_doc = get_last_version_by_subset_id( + project_name, version_doc["parent"], fields=["_id"] + ) # change color of node - if version.get("name") not in [max_version]: - GN["tile_color"].setValue(int("0xd84f20ff", 16)) + if version_doc["_id"] == last_version_doc["_id"]: + color_value = "0x3469ffff" else: - GN["tile_color"].setValue(int("0x3469ffff", 16)) + color_value = "0xd84f20ff" - self.log.info("updated to version: {}".format(version.get("name"))) + GN["tile_color"].setValue(int(color_value, 16)) + + self.log.info("updated to version: {}".format(version_doc.get("name"))) def connect_read_node(self, group_node, asset, subset): """ diff --git a/openpype/hosts/nuke/plugins/load/load_effects_ip.py b/openpype/hosts/nuke/plugins/load/load_effects_ip.py index eaf151b3b8..44565c139d 100644 --- a/openpype/hosts/nuke/plugins/load/load_effects_ip.py +++ b/openpype/hosts/nuke/plugins/load/load_effects_ip.py @@ -3,6 +3,10 @@ from collections import OrderedDict import six import nuke +from openpype.client import ( + get_version_by_id, + get_last_version_by_subset_id, +) from openpype.pipeline import ( legacy_io, load, @@ -153,17 +157,16 @@ class LoadEffectsInputProcess(load.LoaderPlugin): # get main variables # Get version from io - version = legacy_io.find_one({ - "type": "version", - "_id": representation["parent"] - }) + project_name = legacy_io.active_project() + version_doc = get_version_by_id(project_name, representation["parent"]) + # get corresponding node GN = nuke.toNode(container['objectName']) file = get_representation_path(representation).replace("\\", "/") name = container['name'] - version_data = version.get("data", {}) - vname = version.get("name", None) + version_data = version_doc.get("data", {}) + vname = version_doc.get("name", None) first = version_data.get("frameStart", None) last = version_data.get("frameEnd", None) workfile_first_frame = int(nuke.root()["first_frame"].getValue()) @@ -251,20 +254,18 @@ class LoadEffectsInputProcess(load.LoaderPlugin): # return # get all versions in list - versions = legacy_io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') - - max_version = max(versions) + last_version_doc = get_last_version_by_subset_id( + project_name, version_doc["parent"], fields=["_id"] + ) # change color of node - if version.get("name") not in [max_version]: - GN["tile_color"].setValue(int("0xd84f20ff", 16)) + if version_doc["_id"] == last_version_doc["_id"]: + color_value = "0x3469ffff" else: - GN["tile_color"].setValue(int("0x3469ffff", 16)) + color_value = "0xd84f20ff" + GN["tile_color"].setValue(int(color_value, 16)) - self.log.info("updated to version: {}".format(version.get("name"))) + self.log.info("updated to version: {}".format(version_doc.get("name"))) def connect_active_viewer(self, group_node): """ diff --git a/openpype/hosts/nuke/plugins/load/load_gizmo.py b/openpype/hosts/nuke/plugins/load/load_gizmo.py index 4ea9d64d7d..9a18eeef5c 100644 --- a/openpype/hosts/nuke/plugins/load/load_gizmo.py +++ b/openpype/hosts/nuke/plugins/load/load_gizmo.py @@ -1,5 +1,9 @@ import nuke +from openpype.client import ( + get_version_by_id, + get_last_version_by_subset_id, +) from openpype.pipeline import ( legacy_io, load, @@ -101,17 +105,16 @@ class LoadGizmo(load.LoaderPlugin): # get main variables # Get version from io - version = legacy_io.find_one({ - "type": "version", - "_id": representation["parent"] - }) + project_name = legacy_io.active_project() + version_doc = get_version_by_id(project_name, representation["parent"]) + # get corresponding node GN = nuke.toNode(container['objectName']) file = get_representation_path(representation).replace("\\", "/") name = container['name'] - version_data = version.get("data", {}) - vname = version.get("name", None) + version_data = version_doc.get("data", {}) + vname = version_doc.get("name", None) first = version_data.get("frameStart", None) last = version_data.get("frameEnd", None) namespace = container['namespace'] @@ -148,21 +151,18 @@ class LoadGizmo(load.LoaderPlugin): GN.setXYpos(xpos, ypos) GN["name"].setValue(object_name) - # get all versions in list - versions = legacy_io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') - - max_version = max(versions) + last_version_doc = get_last_version_by_subset_id( + project_name, version_doc["parent"], fields=["_id"] + ) # change color of node - if version.get("name") not in [max_version]: - GN["tile_color"].setValue(int("0xd88467ff", 16)) + if version_doc["_id"] == last_version_doc["_id"]: + color_value = self.node_color else: - GN["tile_color"].setValue(int(self.node_color, 16)) + color_value = "0xd88467ff" + GN["tile_color"].setValue(int(color_value, 16)) - self.log.info("updated to version: {}".format(version.get("name"))) + self.log.info("updated to version: {}".format(version_doc.get("name"))) return update_container(GN, data_imprint) diff --git a/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py b/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py index 38dd70935e..2890dbfd2c 100644 --- a/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py +++ b/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py @@ -1,6 +1,10 @@ import nuke import six +from openpype.client import ( + get_version_by_id, + get_last_version_by_subset_id, +) from openpype.pipeline import ( legacy_io, load, @@ -108,17 +112,16 @@ class LoadGizmoInputProcess(load.LoaderPlugin): # get main variables # Get version from io - version = legacy_io.find_one({ - "type": "version", - "_id": representation["parent"] - }) + project_name = legacy_io.active_project() + version_doc = get_version_by_id(project_name, representation["parent"]) + # get corresponding node GN = nuke.toNode(container['objectName']) file = get_representation_path(representation).replace("\\", "/") name = container['name'] - version_data = version.get("data", {}) - vname = version.get("name", None) + version_data = version_doc.get("data", {}) + vname = version_doc.get("name", None) first = version_data.get("frameStart", None) last = version_data.get("frameEnd", None) namespace = container['namespace'] @@ -155,21 +158,18 @@ class LoadGizmoInputProcess(load.LoaderPlugin): GN.setXYpos(xpos, ypos) GN["name"].setValue(object_name) - # get all versions in list - versions = legacy_io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') - - max_version = max(versions) + last_version_doc = get_last_version_by_subset_id( + project_name, version_doc["parent"], fields=["_id"] + ) # change color of node - if version.get("name") not in [max_version]: - GN["tile_color"].setValue(int("0xd88467ff", 16)) + if version_doc["_id"] == last_version_doc["_id"]: + color_value = self.node_color else: - GN["tile_color"].setValue(int(self.node_color, 16)) + color_value = "0xd88467ff" + GN["tile_color"].setValue(int(color_value, 16)) - self.log.info("updated to version: {}".format(version.get("name"))) + self.log.info("updated to version: {}".format(version_doc.get("name"))) return update_container(GN, data_imprint) diff --git a/openpype/hosts/nuke/plugins/load/load_image.py b/openpype/hosts/nuke/plugins/load/load_image.py index 6df286a4f7..3e81ef999b 100644 --- a/openpype/hosts/nuke/plugins/load/load_image.py +++ b/openpype/hosts/nuke/plugins/load/load_image.py @@ -2,6 +2,10 @@ import nuke import qargparse +from openpype.client import ( + get_version_by_id, + get_last_version_by_subset_id, +) from openpype.pipeline import ( legacy_io, load, @@ -186,20 +190,13 @@ class LoadImage(load.LoaderPlugin): format(frame_number, "0{}".format(padding))) # Get start frame from version data - version = legacy_io.find_one({ - "type": "version", - "_id": representation["parent"] - }) + project_name = legacy_io.active_project() + version_doc = get_version_by_id(project_name, representation["parent"]) + last_version_doc = get_last_version_by_subset_id( + project_name, version_doc["parent"], fields=["_id"] + ) - # get all versions in list - versions = legacy_io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') - - max_version = max(versions) - - version_data = version.get("data", {}) + version_data = version_doc.get("data", {}) last = first = int(frame_number) @@ -215,7 +212,7 @@ class LoadImage(load.LoaderPlugin): "representation": str(representation["_id"]), "frameStart": str(first), "frameEnd": str(last), - "version": str(version.get("name")), + "version": str(version_doc.get("name")), "colorspace": version_data.get("colorspace"), "source": version_data.get("source"), "fps": str(version_data.get("fps")), @@ -223,17 +220,18 @@ class LoadImage(load.LoaderPlugin): }) # change color of node - if version.get("name") not in [max_version]: - node["tile_color"].setValue(int("0xd84f20ff", 16)) + if version_doc["_id"] == last_version_doc["_id"]: + color_value = "0x4ecd25ff" else: - node["tile_color"].setValue(int("0x4ecd25ff", 16)) + color_value = "0xd84f20ff" + node["tile_color"].setValue(int(color_value, 16)) # Update the imprinted representation update_container( node, updated_dict ) - self.log.info("updated to version: {}".format(version.get("name"))) + self.log.info("updated to version: {}".format(version_doc.get("name"))) def remove(self, container): node = nuke.toNode(container['objectName']) diff --git a/openpype/hosts/nuke/plugins/load/load_model.py b/openpype/hosts/nuke/plugins/load/load_model.py index 2f54595cb0..c317b15450 100644 --- a/openpype/hosts/nuke/plugins/load/load_model.py +++ b/openpype/hosts/nuke/plugins/load/load_model.py @@ -1,5 +1,9 @@ import nuke +from openpype.client import ( + get_version_by_id, + get_last_version_by_subset_id, +) from openpype.pipeline import ( legacy_io, load, @@ -100,17 +104,15 @@ class AlembicModelLoader(load.LoaderPlugin): None """ # Get version from io - version = legacy_io.find_one({ - "type": "version", - "_id": representation["parent"] - }) + project_name = legacy_io.active_project() + version_doc = get_version_by_id(project_name, representation["parent"]) object_name = container['objectName'] # get corresponding node model_node = nuke.toNode(object_name) # get main variables - version_data = version.get("data", {}) - vname = version.get("name", None) + version_data = version_doc.get("data", {}) + vname = version_doc.get("name", None) first = version_data.get("frameStart", None) last = version_data.get("frameEnd", None) fps = version_data.get("fps") or nuke.root()["fps"].getValue() @@ -163,28 +165,26 @@ class AlembicModelLoader(load.LoaderPlugin): d.setInput(index, model_node) # color node by correct color by actual version - self.node_version_color(version, model_node) + self.node_version_color(version_doc, model_node) - self.log.info("updated to version: {}".format(version.get("name"))) + self.log.info("updated to version: {}".format(version_doc.get("name"))) return update_container(model_node, data_imprint) def node_version_color(self, version, node): - """ Coloring a node by correct color by actual version - """ - # get all versions in list - versions = legacy_io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') + """ Coloring a node by correct color by actual version""" - max_version = max(versions) + project_name = legacy_io.active_project() + last_version_doc = get_last_version_by_subset_id( + project_name, version["parent"], fields=["_id"] + ) # change color of node - if version.get("name") not in [max_version]: - node["tile_color"].setValue(int("0xd88467ff", 16)) + if version["_id"] == last_version_doc["_id"]: + color_value = self.node_color else: - node["tile_color"].setValue(int(self.node_color, 16)) + color_value = "0xd88467ff" + node["tile_color"].setValue(int(color_value, 16)) def switch(self, container, representation): self.update(container, representation) diff --git a/openpype/hosts/nuke/plugins/load/load_script_precomp.py b/openpype/hosts/nuke/plugins/load/load_script_precomp.py index bd351ad785..21e384b538 100644 --- a/openpype/hosts/nuke/plugins/load/load_script_precomp.py +++ b/openpype/hosts/nuke/plugins/load/load_script_precomp.py @@ -1,5 +1,9 @@ import nuke +from openpype.client import ( + get_version_by_id, + get_last_version_by_subset_id, +) from openpype.pipeline import ( legacy_io, load, @@ -116,29 +120,23 @@ class LinkAsGroup(load.LoaderPlugin): root = get_representation_path(representation).replace("\\", "/") # Get start frame from version data - version = legacy_io.find_one({ - "type": "version", - "_id": representation["parent"] - }) - - # get all versions in list - versions = legacy_io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') - - max_version = max(versions) + project_name = legacy_io.active_project() + version_doc = get_version_by_id(project_name, representation["parent"]) + last_version_doc = get_last_version_by_subset_id( + project_name, version_doc["parent"], fields=["_id"] + ) updated_dict = {} + version_data = version_doc["data"] updated_dict.update({ "representation": str(representation["_id"]), - "frameEnd": version["data"].get("frameEnd"), - "version": version.get("name"), - "colorspace": version["data"].get("colorspace"), - "source": version["data"].get("source"), - "handles": version["data"].get("handles"), - "fps": version["data"].get("fps"), - "author": version["data"].get("author") + "frameEnd": version_data.get("frameEnd"), + "version": version_doc.get("name"), + "colorspace": version_data.get("colorspace"), + "source": version_data.get("source"), + "handles": version_data.get("handles"), + "fps": version_data.get("fps"), + "author": version_data.get("author") }) # Update the imprinted representation @@ -150,12 +148,13 @@ class LinkAsGroup(load.LoaderPlugin): node["file"].setValue(root) # change color of node - if version.get("name") not in [max_version]: - node["tile_color"].setValue(int("0xd84f20ff", 16)) + if version_doc["_id"] == last_version_doc["_id"]: + color_value = "0xff0ff0ff" else: - node["tile_color"].setValue(int("0xff0ff0ff", 16)) + color_value = "0xd84f20ff" + node["tile_color"].setValue(int(color_value, 16)) - self.log.info("updated to version: {}".format(version.get("name"))) + self.log.info("updated to version: {}".format(version_doc.get("name"))) def remove(self, container): node = nuke.toNode(container['objectName']) From f919c24989739e40e46028b3dfdd82642e38507c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Jun 2022 14:20:05 +0200 Subject: [PATCH 0774/1227] remove unnecessary query of asset document --- .../hosts/nuke/plugins/publish/precollect_instances.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_instances.py b/openpype/hosts/nuke/plugins/publish/precollect_instances.py index 8bf7280cea..4b3b70fa12 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_instances.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_instances.py @@ -1,7 +1,6 @@ import nuke import pyblish.api -from openpype.pipeline import legacy_io from openpype.hosts.nuke.api.lib import ( add_publish_knob, get_avalon_knob_data @@ -20,12 +19,6 @@ class PreCollectNukeInstances(pyblish.api.ContextPlugin): sync_workfile_version_on_families = [] def process(self, context): - asset_data = legacy_io.find_one({ - "type": "asset", - "name": legacy_io.Session["AVALON_ASSET"] - }) - - self.log.debug("asset_data: {}".format(asset_data["data"])) instances = [] root = nuke.root() From 2ce3200fa5579fc18963f0e25023156179c3996d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Jun 2022 14:21:22 +0200 Subject: [PATCH 0775/1227] use query functions in validate script plugin --- .../nuke/plugins/publish/validate_script.py | 56 +++++++++++++++---- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/validate_script.py b/openpype/hosts/nuke/plugins/publish/validate_script.py index 10c9e93f8b..9bda0da85e 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_script.py +++ b/openpype/hosts/nuke/plugins/publish/validate_script.py @@ -1,5 +1,6 @@ import pyblish.api +from openpype.client import get_project, get_asset_by_id from openpype import lib from openpype.pipeline import legacy_io @@ -19,6 +20,7 @@ class ValidateScript(pyblish.api.InstancePlugin): asset_name = ctx_data["asset"] asset = lib.get_asset(asset_name) asset_data = asset["data"] + project_name = legacy_io.active_project() # These attributes will be checked attributes = [ @@ -48,12 +50,19 @@ class ValidateScript(pyblish.api.InstancePlugin): asset_attributes[attr] = asset_data[attr] elif attr in hierarchical_attributes: - # Try to find fps on parent - parent = asset['parent'] - if asset_data['visualParent'] is not None: - parent = asset_data['visualParent'] + # TODO this should be probably removed + # Hierarchical attributes is not a thing since Pype 2? - value = self.check_parent_hierarchical(parent, attr) + # Try to find attribute on parent + parent_id = asset['parent'] + parent_type = "project" + if asset_data['visualParent'] is not None: + parent_type = "asset" + parent_id = asset_data['visualParent'] + + value = self.check_parent_hierarchical( + project_name, parent_type, parent_id, attr + ) if value is None: missing_attributes.append(attr) else: @@ -113,12 +122,35 @@ class ValidateScript(pyblish.api.InstancePlugin): message = msg.format(", ".join(not_matching)) raise ValueError(message) - def check_parent_hierarchical(self, entityId, attr): - if entityId is None: + def check_parent_hierarchical( + self, project_name, parent_type, parent_id, attr + ): + if parent_id is None: return None - entity = legacy_io.find_one({"_id": entityId}) - if attr in entity['data']: + + doc = None + if parent_type == "project": + doc = get_project(project_name) + elif parent_type == "asset": + doc = get_asset_by_id(project_name, parent_id) + + if not doc: + return None + + doc_data = doc["data"] + if attr in doc_data: self.log.info(attr) - return entity['data'][attr] - else: - return self.check_parent_hierarchical(entity['parent'], attr) + return doc_data[attr] + + if parent_type == "project": + return None + + parent_id = doc_data.get("visualParent") + new_parent_type = "asset" + if parent_id is None: + parent_id = doc["parent"] + new_parent_type = "project" + + return self.check_parent_hierarchical( + project_name, new_parent_type, parent_id, attr + ) From 796f32bccdfd171dc8f768227bcdff4d41c53e8d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Jun 2022 14:23:35 +0200 Subject: [PATCH 0776/1227] use query functions in publish plugins --- .../nuke/plugins/publish/collect_reads.py | 12 ++++----- .../nuke/plugins/publish/precollect_writes.py | 26 ++++++++++++++----- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/collect_reads.py b/openpype/hosts/nuke/plugins/publish/collect_reads.py index 4d6944f523..b79d9646d5 100644 --- a/openpype/hosts/nuke/plugins/publish/collect_reads.py +++ b/openpype/hosts/nuke/plugins/publish/collect_reads.py @@ -3,6 +3,7 @@ import re import nuke import pyblish.api +from openpype.client import get_asset_by_name from openpype.pipeline import legacy_io @@ -16,12 +17,11 @@ class CollectNukeReads(pyblish.api.InstancePlugin): families = ["source"] def process(self, instance): - asset_data = legacy_io.find_one({ - "type": "asset", - "name": legacy_io.Session["AVALON_ASSET"] - }) + project_name = legacy_io.active_project() + asset_name = legacy_io.Session["AVALON_ASSET"] + asset_doc = get_asset_by_name(project_name, asset_name) - self.log.debug("asset_data: {}".format(asset_data["data"])) + self.log.debug("asset_doc: {}".format(asset_doc["data"])) self.log.debug("checking instance: {}".format(instance)) @@ -127,7 +127,7 @@ class CollectNukeReads(pyblish.api.InstancePlugin): "frameStart": first_frame, "frameEnd": last_frame, "colorspace": colorspace, - "handles": int(asset_data["data"].get("handles", 0)), + "handles": int(asset_doc["data"].get("handles", 0)), "step": 1, "fps": int(nuke.root()['fps'].value()) }) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py index 7e50679ed5..a7c07975e2 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -4,7 +4,10 @@ from pprint import pformat import nuke import pyblish.api -import openpype.api as pype +from openpype.client import ( + get_last_version_by_subset_name, + get_representations, +) from openpype.pipeline import ( legacy_io, get_representation_path, @@ -180,17 +183,26 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): if not instance.data["review"]: instance.data["useSequenceForReview"] = False + project_name = legacy_io.active_project() + asset_name = instance.data["asset"] # * Add audio to instance if exists. # Find latest versions document - version_doc = pype.get_latest_version( - instance.data["asset"], "audioMain" + last_version_doc = get_last_version_by_subset_name( + project_name, "audioMain", asset_name=asset_name, fields=["_id"] ) + repre_doc = None - if version_doc: + if last_version_doc: # Try to find it's representation (Expected there is only one) - repre_doc = legacy_io.find_one( - {"type": "representation", "parent": version_doc["_id"]} - ) + repre_docs = list(get_representations( + project_name, version_ids=[last_version_doc["_id"]] + )) + if not repre_docs: + self.log.warning( + "Version document does not contain any representations" + ) + else: + repre_doc = repre_docs[0] # Add audio to instance if representation was found if repre_doc: From 20bee203523e2979df32f010a305024d94a4310e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Jun 2022 14:25:23 +0200 Subject: [PATCH 0777/1227] use query functions in nuke lib --- openpype/hosts/nuke/api/lib.py | 135 ++++++++++++++++++++++----------- 1 file changed, 89 insertions(+), 46 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 7e44aaa7c5..57a81f7909 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -7,10 +7,16 @@ import contextlib from collections import OrderedDict import clique -from bson.objectid import ObjectId import nuke +from openpype.client import ( + get_project, + get_asset_by_name, + get_versions, + get_last_versions, + get_representations, +) from openpype.api import ( Logger, Anatomy, @@ -734,47 +740,84 @@ def check_inventory_versions(): from .pipeline import parse_container # get all Loader nodes by avalon attribute metadata - for each in nuke.allNodes(): - container = parse_container(each) + node_with_repre_id = [] + repre_ids = set() + # Find all containers and collect it's node and representation ids + for node in nuke.allNodes(): + container = parse_container(node) if container: node = nuke.toNode(container["objectName"]) avalon_knob_data = read_avalon_data(node) + repre_id = avalon_knob_data["representation"] - # get representation from io - representation = legacy_io.find_one({ - "type": "representation", - "_id": ObjectId(avalon_knob_data["representation"]) - }) + repre_ids.add(repre_id) + node_with_repre_id.append((node, repre_id)) - # Failsafe for not finding the representation. - if not representation: - log.warning( - "Could not find the representation on " - "node \"{}\"".format(node.name()) - ) - continue + # Skip if nothing was found + if not repre_ids: + return - # Get start frame from version data - version = legacy_io.find_one({ - "type": "version", - "_id": representation["parent"] - }) + project_name = legacy_io.active_project() + # Find representations based on found containers + repre_docs = get_representations( + project_name, + repre_ids=repre_ids, + fields=["_id", "parent"] + ) + # Store representations by id and collect version ids + repre_docs_by_id = {} + version_ids = set() + for repre_doc in repre_docs: + # Use stringed representation id to match value in containers + repre_id = str(repre_doc["_id"]) + repre_docs_by_id[repre_id] = repre_doc + version_ids.add(repre_doc["parent"]) - # get all versions in list - versions = legacy_io.find({ - "type": "version", - "parent": version["parent"] - }).distinct("name") + version_docs = get_versions( + project_name, version_ids, fields=["_id", "name", "parent"] + ) + # Store versions by id and collect subset ids + version_docs_by_id = {} + subset_ids = set() + for version_doc in version_docs: + version_docs_by_id[version_doc["_id"]] = version_doc + subset_ids.add(version_doc["parent"]) - max_version = max(versions) + # Query last versions based on subset ids + last_versions_by_subset_id = get_last_versions( + project_name, subset_ids=subset_ids, fields=["_id", "parent"] + ) - # check the available version and do match - # change color of node if not max version - if version.get("name") not in [max_version]: - node["tile_color"].setValue(int("0xd84f20ff", 16)) - else: - node["tile_color"].setValue(int("0x4ecd25ff", 16)) + # Loop through collected container nodes and their representation ids + for item in node_with_repre_id: + # Some python versions of nuke can't unfold tuple in for loop + node, repre_id = item + repre_doc = repre_docs_by_id.get(repre_id) + # Failsafe for not finding the representation. + if not repre_doc: + log.warning(( + "Could not find the representation on node \"{}\"" + ).format(node.name())) + continue + + version_id = repre_doc["parent"] + version_doc = version_docs_by_id.get(version_id) + if not version_doc: + log.warning(( + "Could not find the version on node \"{}\"" + ).format(node.name())) + continue + + # Get last version based on subset id + subset_id = version_doc["parent"] + last_version = last_versions_by_subset_id[subset_id] + # Check if last version is same as current version + if last_version["_id"] == version_doc["_id"]: + color_value = "0x4ecd25ff" + else: + color_value = "0xd84f20ff" + node["tile_color"].setValue(int(color_value, 16)) def writes_version_sync(): @@ -899,11 +942,9 @@ def format_anatomy(data): file = script_name() data["version"] = get_version_from_path(file) - project_doc = legacy_io.find_one({"type": "project"}) - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": data["avalon"]["asset"] - }) + project_name = anatomy.project_name + project_doc = get_project(project_name) + asset_doc = get_asset_by_name(project_name, data["avalon"]["asset"]) task_name = os.environ["AVALON_TASK"] host_name = os.environ["AVALON_APP"] context_data = get_workdir_data( @@ -1692,12 +1733,13 @@ class WorkfileSettings(object): """ - def __init__(self, - root_node=None, - nodes=None, - **kwargs): - Context._project_doc = kwargs.get( - "project") or legacy_io.find_one({"type": "project"}) + def __init__(self, root_node=None, nodes=None, **kwargs): + project_doc = kwargs.get("project") + if project_doc is None: + project_name = legacy_io.active_project() + project_doc = get_project(project_name) + + Context._project_doc = project_doc self._asset = ( kwargs.get("asset_name") or legacy_io.Session["AVALON_ASSET"] @@ -2047,9 +2089,10 @@ class WorkfileSettings(object): def reset_resolution(self): """Set resolution to project resolution.""" log.info("Resetting resolution") - project = legacy_io.find_one({"type": "project"}) - asset = legacy_io.Session["AVALON_ASSET"] - asset = legacy_io.find_one({"name": asset, "type": "asset"}) + project_name = legacy_io.active_project() + project = get_project(project_name) + asset_name = legacy_io.Session["AVALON_ASSET"] + asset = get_asset_by_name(project_name, asset_name) asset_data = asset.get('data', {}) data = { From da89c2d06bf04376e85ddd9594740067e99009e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Wed, 22 Jun 2022 14:53:50 +0200 Subject: [PATCH 0778/1227] :pencil2: fixing typo --- openpype/hosts/maya/plugins/publish/collect_look.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index e8ada57f8f..ec583bcce7 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -40,7 +40,7 @@ FILE_NODES = { "aiImage": "filename", - "RedshiftNormalMap": "text0", + "RedshiftNormalMap": "tex0", "PxrBump": "filename", "PxrNormalMap": "filename", From 38522154e78c22cd0037dbbe91c2ea1f820b057c Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 22 Jun 2022 16:45:51 +0300 Subject: [PATCH 0779/1227] Remove flags --- .../defaults/project_settings/maya.json | 9 +------ .../schemas/schema_maya_capture.json | 25 ------------------- 2 files changed, 1 insertion(+), 33 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 0f40651c35..f9201286ab 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -496,25 +496,18 @@ "override_viewport_options": true, "displayLights": "default", "textureMaxResolution": 1024, - "lineAAEnable": true, "multiSample": 4, "shadows": true, "textures": true, "twoSidedLighting": true, "ssaoEnable": true, - "ssaoAmount": 0, "ssaoFilterRadius": 0, - "ssaoRadius": 0, "ssaoSamples": 0, - "hwFogEnable": true, + "fogging": true, "hwFogFalloff": "0", "hwFogStart": 0, "hwFogEnd": 0, "hwFogAlpha": 0, - "hwFogDensity": 0, - "motionBlurEnable": true, - "motionBlurShutterOpenFraction": 0, - "motionBlurSampleCount": 0, "cameras": false, "clipGhosts": false, "controlVertices": false, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 217aa947fc..c7842e5031 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -204,11 +204,6 @@ { "type": "splitter" }, - { - "type": "boolean", - "key": "lineAAEnable", - "label": "Smooth Wireframe" - }, { "type": "number", "key": "multiSample", @@ -243,21 +238,11 @@ "key": "ssaoEnable", "label": "Screen Space Ambient Occlusion" }, - { - "type": "number", - "key": "ssaoAmount", - "label": "SSAO Amount" - }, { "type": "number", "key": "ssaoFilterRadius", "label": "SSAO Filter Radius" }, - { - "type": "number", - "key": "ssaoRadius", - "label": "SSAO Radius" - }, { "type": "number", "key": "ssaoSamples", @@ -296,19 +281,9 @@ "key": "hwFogAlpha", "label": "Hardware Fog Alpha" }, - { - "type": "number", - "key": "hwFogDensity", - "label": "Hardware Fog Density" - }, { "type": "splitter" }, - { - "type": "number", - "key": "motionBlurShutterOpenFraction", - "label": "Shutter Open Fraction" - }, { "type": "splitter" }, From 1ed58ef89cd17b3485b38292e3774dfacba6c36c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Jun 2022 16:16:37 +0200 Subject: [PATCH 0780/1227] use query functions in hiero --- openpype/hosts/hiero/api/lib.py | 80 +++++++++++++------ openpype/hosts/hiero/api/tags.py | 9 ++- .../hosts/hiero/plugins/load/load_clip.py | 37 ++++----- .../collect_assetbuilds.py | 4 +- 4 files changed, 84 insertions(+), 46 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 06dfd2f2ee..482ddafa4d 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -14,6 +14,12 @@ import hiero from Qt import QtWidgets from bson.objectid import ObjectId +from openpype.client import ( + get_project, + get_versions, + get_last_versions, + get_representations, +) from openpype.pipeline import legacy_io from openpype.api import (Logger, Anatomy, get_anatomy_settings) from . import tags @@ -477,7 +483,7 @@ def sync_avalon_data_to_workfile(): project.setProjectRoot(active_project_root) # get project data from avalon db - project_doc = legacy_io.find_one({"type": "project"}) + project_doc = get_project(project_name) project_data = project_doc["data"] log.debug("project_data: {}".format(project_data)) @@ -1065,35 +1071,63 @@ def check_inventory_versions(track_items=None): clip_color_last = "green" clip_color = "red" - # get all track items from current timeline + item_with_repre_id = [] + repre_ids = set() + # Find all containers and collect it's node and representation ids for track_item in track_item: container = parse_container(track_item) if container: - # get representation from io - representation = legacy_io.find_one({ - "type": "representation", - "_id": ObjectId(container["representation"]) - }) + repre_id = container["representation"] + repre_ids.add(repre_id) + item_with_repre_id.append((track_item, repre_id)) - # Get start frame from version data - version = legacy_io.find_one({ - "type": "version", - "_id": representation["parent"] - }) + # Skip if nothing was found + if not repre_ids: + return - # get all versions in list - versions = legacy_io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') + project_name = legacy_io.active_project() + # Find representations based on found containers + repre_docs = get_representations( + project_name, + repre_ids=repre_ids, + fields=["_id", "parent"] + ) + # Store representations by id and collect version ids + repre_docs_by_id = {} + version_ids = set() + for repre_doc in repre_docs: + # Use stringed representation id to match value in containers + repre_id = str(repre_doc["_id"]) + repre_docs_by_id[repre_id] = repre_doc + version_ids.add(repre_doc["parent"]) - max_version = max(versions) + version_docs = get_versions( + project_name, version_ids, fields=["_id", "name", "parent"] + ) + # Store versions by id and collect subset ids + version_docs_by_id = {} + subset_ids = set() + for version_doc in version_docs: + version_docs_by_id[version_doc["_id"]] = version_doc + subset_ids.add(version_doc["parent"]) - # set clip colour - if version.get("name") == max_version: - track_item.source().binItem().setColor(clip_color_last) - else: - track_item.source().binItem().setColor(clip_color) + # Query last versions based on subset ids + last_versions_by_subset_id = get_last_versions( + project_name, subset_ids=subset_ids, fields=["_id", "parent"] + ) + + for item in item_with_repre_id: + # Some python versions of nuke can't unfold tuple in for loop + track_item, repre_id = item + + repre_doc = repre_docs_by_id[repre_id] + version_doc = version_docs_by_id[repre_doc["parent"]] + last_version_doc = last_versions_by_subset_id[version_doc["parent"]] + # Check if last version is same as current version + if version_doc["_id"] == last_version_doc["_id"]: + track_item.source().binItem().setColor(clip_color_last) + else: + track_item.source().binItem().setColor(clip_color) def selection_changed_timeline(event): diff --git a/openpype/hosts/hiero/api/tags.py b/openpype/hosts/hiero/api/tags.py index 8c6ff2a77b..10df96fa53 100644 --- a/openpype/hosts/hiero/api/tags.py +++ b/openpype/hosts/hiero/api/tags.py @@ -2,6 +2,7 @@ import re import os import hiero +from openpype.client import get_project, get_assets from openpype.api import Logger from openpype.pipeline import legacy_io @@ -141,7 +142,9 @@ def add_tags_to_workfile(): nks_pres_tags = tag_data() # Get project task types. - tasks = legacy_io.find_one({"type": "project"})["config"]["tasks"] + project_name = legacy_io.active_project() + project_doc = get_project(project_name) + tasks = project_doc["config"]["tasks"] nks_pres_tags["[Tasks]"] = {} log.debug("__ tasks: {}".format(tasks)) for task_type in tasks.keys(): @@ -159,7 +162,9 @@ def add_tags_to_workfile(): # asset builds and shots. if int(os.getenv("TAG_ASSETBUILD_STARTUP", 0)) == 1: nks_pres_tags["[AssetBuilds]"] = {} - for asset in legacy_io.find({"type": "asset"}): + for asset in get_assets( + project_name, fields=["name", "data.entityType"] + ): if asset["data"]["entityType"] == "AssetBuild": nks_pres_tags["[AssetBuilds]"][asset["name"]] = { "editable": "1", diff --git a/openpype/hosts/hiero/plugins/load/load_clip.py b/openpype/hosts/hiero/plugins/load/load_clip.py index a3365253b3..2a7d1af41e 100644 --- a/openpype/hosts/hiero/plugins/load/load_clip.py +++ b/openpype/hosts/hiero/plugins/load/load_clip.py @@ -1,3 +1,7 @@ +from openpype.client import ( + get_version_by_id, + get_last_version_by_subset_id +) from openpype.pipeline import ( legacy_io, get_representation_path, @@ -103,12 +107,12 @@ class LoadClip(phiero.SequenceLoader): namespace = container['namespace'] track_item = phiero.get_track_items( track_item_name=namespace).pop() - version = legacy_io.find_one({ - "type": "version", - "_id": representation["parent"] - }) - version_data = version.get("data", {}) - version_name = version.get("name", None) + + project_name = legacy_io.active_project() + version_doc = get_version_by_id(project_name, representation["parent"]) + + version_data = version_doc.get("data", {}) + version_name = version_doc.get("name", None) colorspace = version_data.get("colorspace", None) object_name = "{}_{}".format(name, namespace) file = get_representation_path(representation).replace("\\", "/") @@ -143,7 +147,7 @@ class LoadClip(phiero.SequenceLoader): }) # update color of clip regarding the version order - self.set_item_color(track_item, version) + self.set_item_color(track_item, version_doc) return phiero.update_container(track_item, data_imprint) @@ -166,21 +170,14 @@ class LoadClip(phiero.SequenceLoader): cls.sequence = cls.track.parent() @classmethod - def set_item_color(cls, track_item, version): - + def set_item_color(cls, track_item, version_doc): + project_name = legacy_io.active_project() + last_version_doc = get_last_version_by_subset_id( + project_name, version_doc["parent"], fields=["_id"] + ) clip = track_item.source() - # define version name - version_name = version.get("name", None) - # get all versions in list - versions = legacy_io.find({ - "type": "version", - "parent": version["parent"] - }).distinct('name') - - max_version = max(versions) - # set clip colour - if version_name == max_version: + if version_doc["_id"] == last_version_doc["_id"]: clip.binItem().setColor(cls.clip_color_last) else: clip.binItem().setColor(cls.clip_color) diff --git a/openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py b/openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py index 10baf25803..5f96533052 100644 --- a/openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py +++ b/openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py @@ -1,4 +1,5 @@ from pyblish import api +from openpype.client import get_assets from openpype.pipeline import legacy_io @@ -17,8 +18,9 @@ class CollectAssetBuilds(api.ContextPlugin): hosts = ["hiero"] def process(self, context): + project_name = legacy_io.active_project() asset_builds = {} - for asset in legacy_io.find({"type": "asset"}): + for asset in get_assets(project_name): if asset["data"]["entityType"] == "AssetBuild": self.log.debug("Found \"{}\" in database.".format(asset)) asset_builds[asset["name"]] = asset From 52179d4015e981e4c0cdd56c979e230e61ecc7d4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Jun 2022 16:17:07 +0200 Subject: [PATCH 0781/1227] remove unused import --- openpype/hosts/hiero/api/lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 482ddafa4d..8c8c31bc4c 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -12,7 +12,6 @@ import shutil import hiero from Qt import QtWidgets -from bson.objectid import ObjectId from openpype.client import ( get_project, From 137ef3e22bbe9495e69a4379c3f85f367f3b4a7d Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 22 Jun 2022 17:35:51 +0300 Subject: [PATCH 0782/1227] Remove keys that fail. --- .../defaults/project_settings/maya.json | 6 --- .../schemas/schema_maya_capture.json | 38 ------------------- 2 files changed, 44 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index f9201286ab..8494989556 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -501,13 +501,7 @@ "textures": true, "twoSidedLighting": true, "ssaoEnable": true, - "ssaoFilterRadius": 0, - "ssaoSamples": 0, "fogging": true, - "hwFogFalloff": "0", - "hwFogStart": 0, - "hwFogEnd": 0, - "hwFogAlpha": 0, "cameras": false, "clipGhosts": false, "controlVertices": false, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index c7842e5031..ace9fc22da 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -238,16 +238,6 @@ "key": "ssaoEnable", "label": "Screen Space Ambient Occlusion" }, - { - "type": "number", - "key": "ssaoFilterRadius", - "label": "SSAO Filter Radius" - }, - { - "type": "number", - "key": "ssaoSamples", - "label": "SSAO Samples" - }, { "type": "splitter" }, @@ -256,34 +246,6 @@ "key": "fogging", "label": "Enable Hardware Fog" }, - { - "type": "enum", - "key": "hwFogFalloff", - "label": "Hardware Fog Falloff", - "enum_items": [ - { "0": "Linear"}, - { "1": "Exponential"}, - { "2": "Exponential Squared"} - ] - }, - { - "type": "number", - "key": "hwFogStart", - "label": "Hardware Fog Start" - }, - { - "type": "number", - "key": "hwFogEnd", - "label": "Hardware Fog End" - }, - { - "type": "number", - "key": "hwFogAlpha", - "label": "Hardware Fog Alpha" - }, - { - "type": "splitter" - }, { "type": "splitter" }, From bcecebf9ff016d5fd8b8480769783a71e8cb6d1a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Jun 2022 16:58:11 +0200 Subject: [PATCH 0783/1227] husdoutputprocessors is using project anatomy and query functions --- .../avalon_uri_processor.py | 31 +++++-------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py b/openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py index 01a29472e7..202287f1c3 100644 --- a/openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py +++ b/openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py @@ -4,19 +4,9 @@ import husdoutputprocessors.base as base import colorbleed.usdlib as usdlib -from openpype.pipeline import ( - legacy_io, - registered_root, -) - - -def _get_project_publish_template(): - """Return publish template from database for current project""" - project = legacy_io.find_one( - {"type": "project"}, - projection={"config.template.publish": True} - ) - return project["config"]["template"]["publish"] +from openpype.client import get_asset_by_name +from openpype.api import Anatomy +from openpype.pipeline import legacy_io class AvalonURIOutputProcessor(base.OutputProcessorBase): @@ -35,7 +25,6 @@ class AvalonURIOutputProcessor(base.OutputProcessorBase): ever created in a Houdini session. Therefore be very careful about what data gets put in this object. """ - self._template = None self._use_publish_paths = False self._cache = dict() @@ -60,14 +49,11 @@ class AvalonURIOutputProcessor(base.OutputProcessorBase): return self._parameters def beginSave(self, config_node, t): - self._template = _get_project_publish_template() - parm = self._parms["use_publish_paths"] self._use_publish_paths = config_node.parm(parm).evalAtTime(t) self._cache.clear() def endSave(self): - self._template = None self._use_publish_paths = None self._cache.clear() @@ -138,22 +124,19 @@ class AvalonURIOutputProcessor(base.OutputProcessorBase): """ PROJECT = legacy_io.Session["AVALON_PROJECT"] - asset_doc = legacy_io.find_one({ - "name": asset, - "type": "asset" - }) + anatomy = Anatomy(PROJECT) + asset_doc = get_asset_by_name(PROJECT, asset) if not asset_doc: raise RuntimeError("Invalid asset name: '%s'" % asset) - root = registered_root() - path = self._template.format(**{ - "root": root, + formatted_anatomy = anatomy.format({ "project": PROJECT, "asset": asset_doc["name"], "subset": subset, "representation": ext, "version": 0 # stub version zero }) + path = formatted_anatomy["publish"]["path"] # Remove the version folder subset_folder = os.path.dirname(os.path.dirname(path)) From fe77fe64adfcd2bf2c9eb77d6c5181c0fb20dd5f Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 22 Jun 2022 17:10:36 +0200 Subject: [PATCH 0784/1227] add new studios to main page --- website/src/pages/index.js | 27 ++++++++++++++++++- website/static/img/Logo_On_White-HR.png | Bin 0 -> 77588 bytes website/static/img/NoGhost_Logo_black.svg | 31 ++++++++++++++++++++++ website/static/img/agora_studio.png | Bin 0 -> 133985 bytes website/static/img/igg-logo.png | Bin 80331 -> 96336 bytes website/static/img/methodmadness.png | Bin 0 -> 8650 bytes website/static/img/noghost.png | Bin 0 -> 22435 bytes website/static/img/staticvfx.png | Bin 0 -> 12912 bytes 8 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 website/static/img/Logo_On_White-HR.png create mode 100644 website/static/img/NoGhost_Logo_black.svg create mode 100644 website/static/img/agora_studio.png create mode 100644 website/static/img/methodmadness.png create mode 100644 website/static/img/noghost.png create mode 100644 website/static/img/staticvfx.png diff --git a/website/src/pages/index.js b/website/src/pages/index.js index 0886706015..ae7119e928 100644 --- a/website/src/pages/index.js +++ b/website/src/pages/index.js @@ -153,7 +153,32 @@ const studios = [ title: "IGG Canada", image: "/img/igg-logo.png", infoLink: "https://www.igg.com/", - } + }, + { + title: "Agora Studio", + image: "/img/agora_studio.png", + infoLink: "https://agora.studio/", + }, + { + title: "Lucan Visuals", + image: "/img/lucan_Logo_On_White-HR.png", + infoLink: "https://www.lucan.tv/", + }, + { + title: "No Ghost", + image: "/img/noghost.png", + infoLink: "https://www.noghost.co.uk/", + }, + { + title: "Static VFX", + image: "/img/staticvfx.png", + infoLink: "http://www.staticvfx.com/", + }, + { + title: "Method n Madness", + image: "/img/methodmadness.png", + infoLink: "https://www.methodnmadness.com/", +} ]; function Service({imageUrl, title, description}) { diff --git a/website/static/img/Logo_On_White-HR.png b/website/static/img/Logo_On_White-HR.png new file mode 100644 index 0000000000000000000000000000000000000000..c86030e1e78092c7ecd1de2cd129198c99680ee4 GIT binary patch literal 77588 zcmeFZWmlVFw>4UVOQD4pw-$FRR*E|W4J{s^Sdk#1xEE=0cXxMpx8TLS#WfTtZYMn5 z`wzTljD5~{$NoURkleX!tu?PX=Sq;Ovg~s#Qmkjso;{bBlTv^73={b58NdVs?b$P= zhPELE#6M8X#g)XLJ*$eqzB597_Dt-Vyp*_x3)0~VW-7U?>z~K+q`2C8)`fY1v2l&V zk%C0O&-tREa=ng`ipyUjh6$h3DU(^{)nCqp zMe8Xo_nt-WDv{#UY|8_Xw6Dhb`hZa z+-jJOqPAmzkZ*_wB@ml@iR*mgGQT~}aer`UI>($e?D&FhFjy6(c6L~zNFZ0i;mwR< z9n&j2JoYr?(4xOOkb8ELP{iC-Yy819+2tB-->@$;yp0a&$iU@y0E2%UsO9|o ziVUET29Jwro4oMt8gKMWW(&<4bVJEm_H`YEgS_KJgFZOp2MnemgraZ$lgS3bT$LucLsb+5eIc5IS57?*y97_NNe zI6(Pn)=GHg===G6MbEmxH=B;ecE*eZhG2uBYGDqDedcMlR8l%1hf-_1fozx~v6Z5( zJl(g7Gt_?1>HgmF>%-#PoxU!`5omGuR{iECKQ0HF z*8z@tI`YRCZJ{;?ThjY+{U-Y0B^(`P`r8J#B1z?sS8HI{f=++F-)nSc-OK178_~{> z-se9}@bOr|r~9!{ApF%;*X84iRFM-s=d?TJldOAKu4xk+It#9b#X=5X`_1tK2ac9B zGhYG-|DoUN?WyrT6&{P#Ft(>wYJJ6yn5jFS_N%okc@|BrSIVqexfW7IqJ-@^8?Vt&!s`xO2X62V)y}rgS_lmuu7-W#(rmtyasyi~<3?0Q z=v{`<(Q`7##yM)LPv1um?ngx3NIa9r;*jGkCQ)V?II2SpYV?Nn9lqV4L38{Cz`wkz zNu4<1lQ>3(6f=*VV`@im4cWa`Y`TR1Src#a?6Fout4Z3%6YCd@oRu6M9 zEb!$_WxC-#mv5mH%=mfW9d)?}ETCIcQkl)}yw0qKxDns)wOuCn5BZN6F8C8;AmO-> zrpUPnA2t6WU*^o})WOSf@!~(Hd~3iM8nrJarufqZIO>wlqQVboLZixocok=I?6zK`h5= zpGS8bBB)G-VwUVONi&<=>raxAN+Qalop)3y+}Ah6^He|V=D1u*$DMvX1A3ak2?@Hs zf=kmn==XDR&j38{8V{{*@$t?(M1S?T41YK6q_?PBL1s)rmZSZO=>DyS zaqf0Wsfl_WhFK8|A$ZCm8MlQn=SjvT(pRZ$_Bg>+HSF*RpVah;y&xa&l{Dq;=!>C& z+L-Sjx8hg42ilGLw&!Hq{96C4hnyxDxM>e7+Cg>(Ip~PS&vb3l{J^R3=EAe1gn}Re zw5`Po{gi|jdi$+hU{`OHPf*&B;zQ16y+C~!89)1y`_fT+#ZGM_CC3RG8;?S}O$zjE zA_L}mJ*Ko3yg$9@Dpzr|NxfxyW^B6r{u_LL zfH~yEmq?cDHjP*k2%H*GbKNa&1iynuTDoI!#J;uPB3IzP)Y#wBh;EJXc1q;R&4LSD zJjd0byi6?=V%WcMlt=V8W6q+)>sOdk-cB1 z3ZA#^VP%gfc0d#bMJgH65a@$J?vX1JhL%jb;mKtazoEOD4W+38B4;hs7&$A?pWgzV z+Z)MZZo{@2m)utRA(CDOgo__J+d_5LYcKILG=~|7hrCYm7#BChc)vl3g}$y13A2T| z$;wsi(8Dc=jCvUigbWB|+Js|%SQsUr#oVtasZ}D*qT0gBHSo}<$pcPeBg|8CUJ*vD zkeiYOTT;6&XmtE&o>8Jkwb9yj(&ndSP^8?D;oy3P1?z zW}jPL3kY|Ki^#U&*R|fkt?z-Bs!^j#AVuc#EV8RVnb-JY%&TLqaU885oo{{Xtme`M zo&QW!m}pdeQK~FRBT}4jUNTfOaj=>b!GwqARyTRyz!q1MR*+*W!aNA!_lO1tfabJxaw7t3^~6U4P0z=;}o_^O$Kvm6ga3%H^yT9G?s-8?Qhj6 zGvDLf3@)cbv+OK(TvNU29`C)DyzPE>6*!%yx1)?}m#${}Eb>VkmtvQCX_72Pz$ zP1uHV+hv1SLsgbX>b)?lH?De*KX;?Z7{zRS(DG;s|4z;9dEDod2)l={;-VKBNswqCpRe#_fbjUF*C`8&Ym`dH zLZZ(Cai0&{-d-FzJL6XS!P0fv*UTr}Qs#z^v6O4*L~zyX|4K=$CtAGoN{>{P#R83W zf<=A?3iB8$!&ix>*vPaQs^*9mXw({Eby3n#Gq*Z8pYy<8GG1uqw-~`58D*_|FkUXy2g@_FN4$7^jE9E@bX-$xRpli+7t<@UjEL?u z@VCnw_q+QlqFzww@1F7s(%w71!$GG42dChz; zbin;gc?s+DOBb3li>tz0^mI3_<-+K6foax1t8k4wUoIL+TlpE9&8I^zgc)sP{601~Dyv zM9NyipjiSNUycrRDO@j$HA-f5oQ2gsP%@cgmTG2{>bG2sH2Hn^(C@0&6wW`rh)yZG z$gwV2Vi<91n_i&^xfLj6Kriac7EX`&$t7HT(#H5@=f>tw`g0SBM@MdhiwCs^{wjUq zCigwrm?Jlf@%!%XagR(QU5+EPp((}t6?-&`;j-_7MK+@%5gP&qv|k1;7T5|H~ zX-ztEn!uHimYB#lX{QP)Ty?+O_4&n0@P`4?#vR|U54M|`S2umb8!qr|)&_NN84bvH zi{_hc8bZ*BYO7#-kw-!d8(Brq^P3e7dLYS$lXV;}&=#uEy*mXb4ivt?->7`Qu0<7~yW3$Hj?5m+CzM9;b!UH?Kzc-c zY5rr$eZ(}^W0+n1DenIEg}rJ;CYOJc=h#qN=25LE#V0ZuWpNCa#@v|$3t5e^flcz_ z1O~PS@e{WpUF92(sMNo%*O_iw-~TUw8ULS5Assb#C;H_pR+U$^L1vbSGIh!w6{F;s zTa^La?DgVb1UDP?1a6^UuC*;q-aO>YOxUuVYt}9F3*DBPHn}1pSJ1FkJk4M=+ArlNw1d>&`0a!+iJ_RoCHQw=m8AJ@x(tp;=0qd3oKP`FMpi ze)ssC2j(7Qw-mLq?=^qT4`oKp@aB^DOMe_C9P+f5SsBE=$8w{3xftwOd9=+*_k*nr zY5cAgtwy)|EI5H;xUeI05h<5rCN$7$8Do&xHp=F=4i&O1-c0we=i_eYSn{;BV8t7n{DUG&|bPH#wLW-Wq=xT3E2$Iqx|za(U!TB(;&k zMB!#qO=9#?KvU?Z$P5?($lTseCRHxqHZjmIKTcH1Ym@4W0Obk-Im*-L~bP^WuIqF=^kn zhZHe7>IP5OR=u)2dHR?JNaoL=0&ca&viD1R#@uu7X6p(jaX!#YawYI@UZU_q*QSNe zQOiP(?lBuLEEnblp#e7*-n#iM?|49aaD!^e&ITWx-1a^993-;&yZiw$OR<5K8d$Hj z)TPM8y^G7iy<*~z67Qo@5?X4_!bj8x>;PgQ+uvU#fFwdrHy@2LFvvDF2_8!v!(S!( zcM;uXH#n8% zyxI$pkEQ|jTI;y9^4n5G1vWWb#$zvnKYupZn@*VSK*Z^yLvNUm5mM8_aan%xJI_wO z*F@(o1d&KEBU?K3c*`RjK2ecE|5SH^t2y6O?9rXFr|K(yRB4kXMqDwTQ=<_;#cItY zG)@dOEvOiJ<@*IC_D1#H^GO9_03t=`bBkemRH?i6P~IRj2nJn8>XDzHX1b10eE3_E z>1YCRcNQ~sQ>Ej_?BFb^(ZI>CzQB5GfZ~QRsOgbM86vBrVZ{d)`Wa~bszoeMUHgsg z4{wrpFGOvBLgTK9P$AgE`+Z;K?v+y{vTJ$X%X*IPS5%(9 z^9G>(IuiS<2y;nO%%vIs9t$VS4`Rh|(sPC9eFMO*>xfnF0No-7O8aXSX42 z6D+BZL8~EVvD5D#s63B`0&HCvYEq$Etu|E@k`urw{I;94@X4$w-LjafnTES6!g0=g z$rue(e~xiN{ld>@Sver4-`K7pOCDqkg~F#wls0QV6kyTsM;Xw1O-c!Cm-SGnZpAr7 z4c)DHj!r+AakCRRawT$S$x&d?>WO)EKZt{vtmi9#(}En;R2l)N{j=PY--ePx4@kqb zNtPd3zv!KY^sdDz(!cVXjoCUfPaaiCK3odV6UyZF)NfH{n+TCUw<0h|SdPVgUmnrGbiWwh~&4JQe}7^OC9x zBrRXeo3uPW9%8-}GeB~_P(@>{IMAh*W&z^;RhQaedEG=m|J(@2^tXv-J9l0Re)tBxEQ}wuUMEwv^Wer(7>{+ZRf4S*w$swS?Xw7Y9`1 zVs4Zp}$DZ6F0?GC}r%>#Xz((fiN$4B#G zuVAER@}N_-D|z^yf@xE_+Yk!Tn z3-&-Ox-{9rBKv2wmnXR0?M|UTl5Cn2W^WsfY*|#DhrR*7`L+3Bce>i8 z_QVCSEeSQO=inlLz0TginS+|CA@#aQGemp~DEQoxX86EF=4^!zE}4Jp-xX^yi8GfF zKs2(@*ImuJxnRrh#BrF^cej!n6WdUP^lem7GiCJb=BU6cA?5UR$tE7wa{KeN>JE7- znaAL)Z=!~eDidg*d-RKzBbKbBX7o1cR9;aS`n)JV=UhtXR6A7`-=HKP>@GjFqR|f3 z`E_Yo6DG^Hif-*{!>mGfGo!)2;z{5P9sxRDqD6#ZHVQc{3pE6pY?k=jwzzUe?w4s> z@nKo)9ViDvi$b>KM~CK9rnMd(DDajZlZD21WnoQP{bnuJ&LKP^4dct&4+6%VD;J4- zSVefgHojxN%3en2nAZU_8DA@EuI(9YQjBZa>TP(8ZTDZ&6~%`^BG2r+1lN|Wd9pu^ z^3f3G+~w*?GTG%p!OP<%b&KhpucftoTaJ_FcMx#y2*3X0+Pu^tu?1+U9+Kivru0`g z*Z0z5q&SV9t?vYB~Oy@M$)a=9aWP@c_ZQexgled}S zCRjivrIiZD-9OLOq&N&#JT;WTp3<)%lQOnQF}4qFy1P2bAdWI#(u`f5=;rU@Yd_vo z+jqQiTqi!S7#X98Ul61*prTv(tRY$dKd6TOkIAn8`$hjV^7jA1IxnHbLXckKA?W>I zSim@i2q2)}Lb})w}JBlg{rIR^FnQrywNXnX#?*k0mo+KE#N3zbNqe=vQ^3|S`P?H7rpB*fPG(MKq!=Z4js3o#EvjZ8g8ay!ugC%oKHKF&<; zYeZ#lIJ$ns>KjITW%@A{prg{a;IX*mbop4jL#*kAlfNEappU}+c7mi=V3DNZt!bHo zhYE1Jptn?x^M)GOE{_mT%%w8KopjzUXfCLcr8D^z{g#Opd*=HRCH#4|%sgMVQY@LV zX|~HD%fOGtyd)$*b6LkbL*5b`j6sn{LgX1$>4ax~MeI0~Z+*nFInq6BXoTqtO8N*ud8zcu;gY`vESu+Z&3Z zaPPk(evl$6BJ0~c-6=dpB_K!Fd?}+JQ%(AGpcp;sGO|NSnRk5P=8E2?`2*Qhq~g3F z@OA!dzzW*=cZN+Qjx=&^VW z$W&cuk?bEHdYyp>V&j4O1>VlEY=wf3m+A-UAcYhtoO}B2>)ya>{%9QNA zuAYQUZY$T6u>?e^Q`?uMMh5J>3Mnu*&5Vlsa1XCnYXmCCrXTUM!)}S@5kh6looyfC z3IHd+nb#%s89)+sHbEfP(d2h!M>8gjiGrzofaRBO1YUy89PNpBF%Rd&_I$w zj0pLP!WWyP^^bo~*#&UN_0t2a?EyZbP!2vVeHEucT0UoNRhbnZyqTKygYZy5UvH3I@eE-F;UGIW?aOgvp4@k{p@It`06 zaE4!s8TyKSTAN;PC%gZqT={FLS$)r!&|e(G2y>+F%5e>&9`SfFV*-?F`c5%sE8$hq zHJY-Ey8F4nntVORu|N<{-39*O{y|$pz?`Wu7+W0|38k7zmHkyaGe1-Kp@Fhs#nOtk z*My4Ssaea^q+3}ABrwiY#xhaW|Ddkj+rMlw7NZfF8H~K@jB1wOs;h?YYmN0~r^tw* zoc!GAvh(O+$C-Mo|C1HptYdvbf$#W%`fz`L&zNA)`GBCY8WUkgic}5lfF9GU2a%x@ zSrQ??BE~ukY+rv&|A+0O>F>+pstEqj67)`eO`$3+} z{yDSEIca*)qhLJ)hsa7SQ{W#&UvyqKkL%*`@NA6+sbiQZi=nAgVK7)YO3xz~!5dfV z$69`Qs8Eq_;bY*zSK|i;P)(>`->Pii{CuYh|(Zrq2ugLmRbb)3< zSL{Ic9L#m2E?9JXh`2dvApj$8;&RLik$IPxWz}H!&SFxF*>C*EaUjWX%FXi!UGtDk z05{LIkK@5?8ewgEt_N5V%AdJqk$Yp z=W-wV}iy_(B z`|v_W&wg&mkQq*y>4~Dwu4=_MF6SDY%P(bUR(}!jM%trnw(O$710rUW1Ayin^vJLP z#Swv_J`t4b{RI(bYAQ+L-9(f|)N>l`l9{-NWvr8P*9g9n^q+{8R^a!PeEG%h4k>d2& zv(o5kI+{2WPg!JKQPdf!MSZkCGLQ7EJK_Ku^OG} zv>IuzcnElsyK;UMprL_I8mE{6pD+hiel=*le$my-n8}X@Sw0x~t~YYl@vVN#CS!~L zMC~5l#3F7reRK<9;L(`7)J@pl!pZ-++_D*nAhNGsBr+3*t^IP-I)mEGv>TafQHA^9 zxD5d3Sa+1u+Ojm7JNDcl6%k#rN5WDb2%CVI6rimBL3tt{DtONCwIAtFqI>~rH&}Sm z=)4ZJRt-82SU*3K#+M7uTaL5qC&TFwU$)X=@?ESKu;2EQU(O*O^U&tqLx+IG&u}n- zY>UO0nzi%JZ=3OT6<`t|>Vob(ArcB92J3K%H@MP=A?W3gv(e+H;NeC3$VB)!kKjZ~ zdfF%x)yI^lT!%&Ck_@g8d`R1+Diwf)a9+D&+(h7hq(Ed}Q%1?`czhUe@`XWoNt7Q4TfVOq!V>U`eSrX6AkI z=uJw`pniGzV;tvxU6wK$i0?0ei#n3jx5+t={x$vy>r>$W(G&RgZP7mT?mg!>)iOx; zbf=Tiw;KyGejB}dJMH)BT!{*@qS2^;PZ)y`3-;Q#FTVYuHQdnTfKHC{yJ30BWerK} znMwv%y8RF%MlNn^Sn+a^s|nH@HDr!ji4V-Iiz=Zk5dTL0Law^gbL_*0RLse+v}l;# zRbAN<6)%bArvr6_*r>E)vq3HLB^)RJ8&>tb9Dw_|0kMT7pTsjH8UPeLSsN5w-n?Io zOuwInD}-8HGgA_q1LvklJM?ZyD(4xy30>Rd95eYMBo0XKidAZ(>sWOSnT3owR~LaK z!lgwfK6FYE@QrgfRt21VjKgf8ol#4Jlya|(S3jnGD$?NFrE!qY-X&w<0eMO$2zkfjbiE4Zt*HP%L)h>PGOE~ayRf;X_kfT#>J^bl$EW)ou#pfkkxKwaAERIAwA5Y^ zBf>s#9xFX5MXHdeYAw5Hh(-!hhR2b}O@V@O@;`8?73zw?aXSibT_XoTAkZatwPz$` zIH0=&bwMO-Dfyvx0lH|Qlmo(dNM?;okMC^^S^)d*Ti?@M$PhtHaPqUy^XsGmWs=`* zwQ3og3l^{ac1eeA6zjcK8cc`dQ^|DmR9(yn-3k!pgbv#k7gois*6J|fHhWFPd5<*W zjpAh8We5xR8vObS{?RT6jS<07an9tc8+RBNvy<%)G3_|!Z$wsT;UW%}oM)WGmeGG) zab=P=V|N+cnQrQlH}iZXfW=7$I+~v*(nCHldtxA+RYo0y&X%4TbD~!Hx00m_-q+rB z+AC&HPh5DyYhTHsr^nDHqPYs-oRESi6#t@h)H8>;stPV?{OwVmCK(tx(=(+6$S$k2QdbeZ&2y!$VovC4chD^;a;C#*2Qg0WmF&NxDDXAsjGsNT=n@Yoq7wa;5D8DXp%X zEf+0PqVML-g(<$ZPL>;VJcRZ*Q#DqZ_n{G%e{emQp98xec_Dw))|a05T$cB`%Z)3^ zFDN+P|3GzKG3hdJddcsqU#I3k>L>&v6bFCL>0h-K0tw3BJ1c@&ZtIMfpaiZv8gROn zlyk+LK_#@)jCkKWDw)anC7I>hFFGCD4rkTZ9fcPa6H8GQO%f~LmyzXmI_kDgjla`r zFcBIoFi~xb!NO>;k&ALx_bURMQnbAPl;1l$-Pl-W-?@ViV*M`GzScWDx~+_Xnh@R` zGAuIqL6Nlr_K>S@Kl@LsZ-c*5wu9)`v%*&aGiE#>XPDfxbpYLlD5^v<1 zwOyhI@=2 z*5>2B>48;rZ>HPzm%pxaJ=>m6?Xk=r?WE8 zx8P!6TSZ)6=(Y+qgm@Aa7Qq(wt=cX*apY9y_C!+n$?glma-`LHFXJAClQ6G4>iv0G zaj#$YI|Rm^RD)OV9(oj;8et~CT?i5CdZ$%eZN=i(92a^WWdQ;=lQ@)9T-7X41Z zg^JF;;xX=ZNNUX?Qk~h6Xu=^5AqPUzo{@4n-8MH-HG3OhdB8}7Rg~e2y)>bCZy{oX zfREMas{J)$b=qrhMPDLV%H^LPFa9hyg<$2c^tK_W?S+*&HieV|LQF>U!{0L9lVgoh zZiOa=KDWd3SpoQ3K3y6Bq0x&-1bbYwx%lqZI%3Mo!-DFai{tJ`_A)*j@agy=Z;2Zc zDcEe^d{=&NZr}Qx8RQ=qw5|(8z|?!xWyR1zbokx_U360*qFCOJ-V|#v%Xtwl~oIIg>^1zd+Ezwt2N{IR@-?S9xh>!l>v0{io$ z$k?$A7Iko{t4PHSwmUZ$>ksTGY z`^as6(b#6LU;^%wOp<5LYL&;Yk^iUp}8M zb0)VRT`iFHj>4P-sNI^A$Mo(}n#M17ZYh20E(Ku}vRT@w6%WYRnlI2nNdGo+@@+w%F?Nr7On8^M6cnLQPRx zu`9eleJghEZ(`OMlsidkFQn^tjY_zmBZA&W*MtO^B8Yay^RnmJb~rx+|6Y14dW*i1 zqo8aMrbf49Ip)~mddTVn>f^r27#CKsNjhuGdak3_85;o1u#F;#Y0yn&&eEpuG=x>- zzDdmioSNOzxz>2f7~ zJhhEW?v?)O<~n>f0xK^HDULZ0S7`A%y7tkPm+vBD#`1i^XT210Z~nId_-viM8X0gF zl~-RgK31`okMix6Xl+_d`}-(WFkE+D_F2}PuoAfD72&4b{MgV>zF-w{U?K|fW+qTR zjvC4U9fnTGYAASq+1meT+jOyLb$L4`LTtiTU{SfhLDm7Xb<@VWfP~W4)0&j4oNo}v>sn)SsY{&FH_vX=%ZPcs{QT*@pF9>kt+L(Jxf ztq4R^(AIz$;Uq>WOI&_l4lGTGw)SRL&eF5~LKKmWO3FB~b6d8J9SNDD?v6kj- zwpeDvGXhYXzE<#mZ9XBUMsS}dqAkacfi}rx@583;Go+&teFy7og`yRN%Ec_^XdUch zi{#42lJsSD%e`lpPxZ9Ri=XN+2f2W*1^`8a^a;CLA{r@-n*}{<78Z-1Bk=%$z$E)z z!vESFPSy#}Pq9D+lJd0uc#JQ?dtNS0=B|otWV%BXNC}Kpos~YVV`ZW%_78rZS%0f^ zY96?I_COSXC`|I{TifWlC6Ok~iJ>T71xXB-SHEgCB2-`fm!9qW2i0Wa?zuCx_g>|J z;;{&Pr8Lzo;oB<0?QGZ1ffay^t$Bij7>+(cmKifn4dIHHMU(1wdAkuQ$axoV+TOhA zx$==Y?o7fq6b)g_?Cg!jV-0@$w9C{!KCZq+K|et|3}WHRGQyJ{*we&vVnQ2f4^crq z!S33 zc9nVqz&&LD{q_hlT=kIff#kSoV^7f=;>1w2Ot-;MrpF~L?0I#iL;2>HQl>hihb}uo z!%tm{7KI~7-KByQti13Cnr{>e6n>(&HGNhbhGy_SRBBKr9|d7pjHNyQuU@K7)u(=+ z^8HlL4P}MrD?Wf(EOLao_DuuFQ=4WTpB$h@*|1vGB?qVgsb=S-LUN=x_q3K!Ln(b7 zX(iwl@42`L9|9Tzfu{K-@~{3A?Y>tiu&vVtc&9V`SC;3AwD_i-0`_=^{Pu0zt z_#9;JhO*hrN46<~b}TO`;}NMM$q_Bt;M1a_LKt$q3+^{Y$;L;VZ)S@{@qa7f2RIe~ zyLv|TYg=c|obw)-SL%4Hbb23>M7Ze9Z4;&Q%ZYVCvnJ%k68(SpnEN{E zcErhRj_bX8$B5W2abWR(B_v-2*5eW0!nN7*+ht2Mk<8><(twhkqMOs`R{v$OyUt6< zJC+z;WEwoLe(0+}-FjK6-|6|dZAMgosouP9{Fzz*UY_q?g!i%|R55QhWS#OalT_jqLKJ?fxa>O_He}@<^xos7a71xB-Kte)X4ha}lUUeJ@ z+@RdP<)MmN&ik2`xiunwRy+O0*T*UKZ29sUblL=}pF42YdV&8Y7w8jI6zj;i_wR_k z$m^V*)+ln^wPSpk8a21FD0j^i?BL+ou@y7XG^@2T^#5C`?mltznzTf z9YC*1`eNM5A{jc;4o1-NPVakS3t z45x*Lt~nQxh2v@|X~6$-3hT?r137)B81;yH+SI5XRp!fv8MAu`ji#YXk7b=(iN^IN z+>#%%_|8ev;liC;`+Nev6(vkLtTJm^XK8XYshW+NAKZstxA;D$A_`L}0R2%+f*EKaVim%*R^9SLeXJu$RA$mN*(KmzSf6pE+2V|^37;7g&gddSi? z?do^O&rU}SA$i)@WN?-J*c+*`1s96~{fD=lz|J<+YQ30k^B!29Ya4LUfWx`4)jQdN zmLFwvWOb#wf-2fm*j`k=;PQ~b@H4DkgK))#O?wx3%xgi4MfQinecZD)4^5VYK+fG-B9-3iU`Y3#Lv5CN1%76bDA+MxerJTVE>Xg;Pm%`s^>ufflE zOfyztG+$l47_=fixeVe|_kB|-&n!)EZl zWTy;L)Ai8n)(6O8(q?aOuLkrcSnl7c{qd1n-f!&0!=^heH_3uel!QeGCv2E@M)!S; z1~j6w4J=8F&^1nXduQ(!w7~p1uMoQx*R4!P_iych*tDkV%umjL_$KUYn^&8KAMGrIvvTv?ok3&;A0OZWr1v^#ilLU!Bc zL_M4vBUd`JEDev|bR?4S?A>M;Z~o7Tkj;P9LhJ`Xp4bp}JXa4JA5SNHE16wVKK7|1 z8f9Cw)cFQf-oncuYOfc++lwLM{-p5Csw70VkllZI&&v8RgT`xRsNLUG}8 zYBJ8qysKAZzI*y#H!k7~&@%*6{S2?aI7$Y%P*9r3pj9^9=nos-WDpXqee=g_ z!1u|PKSpr(?KxL7*C6^|GDuK>z>6R1ooT7jkBwFavAdX7Awfm{j>NzJmD3jiodHB( z7m20Gity|*wZ*}$X#%U7urt|(cnHFS)7ZJXWFt!68vlzI!57VsU%JiE&uP2Y1C_Py zjhupxL!nuJY}?jNNB|@+TuOz+>xwEZJ|H(mXR9F5=X-opBmb8bSuvKMeyw6DdFM2h z8KL&ITUdGY28b=9F5@NeZ5C?4g`xMg8MqQYWu);4LI_OYov@(}&4ESI1E%SfN{{lc_K{YXJIaIc*Krp*FEXcHl zgQ$s#!KtZe{{o~^SB%~4&W4O8R<}#k>Pd31p{oqnvC)mcyr38vS&s08+3j&T`zo# z5On***M(zzDx~8(^dqyD?R%}52Cd5+FYTkoZeH6lc0P-YR}qjGEB4wgIxNUNfA-oKR{P+Q{%7-!j-oVxPbU}cGhCEH-$*_-T z!@Nm<-qh^8vHaAuky$lJAxy>OSnD@yY?8;g2*t#(SoR-6VFD})p1Ll5{urLT^weTm-zWV6!$cZk_!9aeSq>j} zRay-LjlCUOik$Pn(OHu%uQ?O6T7MI%fi;*5Uy=1jD=AIsdvAK6bS_lL+h+dRnWcXO7OUFu9#Ge@07V| zY-}4QwMR#z*%^pHz>z#?R39OH_*{6uDE(jreI4hCq<#3Tkv}N7yW0K3{|nb6p;#ik ztj|(vm;eZG2%-%J?O|y7hZ4s^>8$mt)sTcF_H=dC^T1*(y)cBaiJO<#v7_^~8WKPf zC06}rx2_;H;yIILm*xH3EHGL(GivrlVxof8cr?+!Ef7K3bi?g9$Z?^S-YO6h4at2& zD*ntD^{EMi^k}yV72DVi|3X^{ac4|GbcqYnC6mv~X0 z91aM}!dC#y9!{ojwWMY+?dlVm#$V!N)TZ zS7Dh6y-fOx`bBqyMylk?Y_u`|l#kl5G5!l=UZHOG{3<(<6-Pnjx$izoqGT~k^AQ6^ z6BZ5!vt?|8x|>{SJNA!pH4^C`*$E%t!3DmErU2jv_O2r4Gvj*h7yeH01ImFXH=XcY z&4e3b4yDI$0sn5e)E-FJW~?CEyVYbFVBVQ6&!u+fmMls5$nBvUf_sQ z{noHA%`5z~RrzFOTwC}D&>Lz|UN5f*ghYnA>BcOYV8!!cuzdcn5N*2i;%kCQ>PS7`r{4z{ z&ffj_|FHL#Z&7_;+^`OVQo_(6E!`;HC<4+cozmT%qaY$F-2+N@r-XEOH`0wDISf1p z{NDF-J^#V;;`fFZb8+pn&)RE$)>@wx2S)CK+#;qUsU7%X~%qh;BVT>u>!84F|p#e@MwRHH!iZyfuvY>B*u;MZr zytXe|l(jyymByHv=BF{8-v#+QMZpjhhNwWSh{vh}1DkWBiV1sxO|LjnWAe^GQp4$e zO`Ydr)o%JjgaD{q~a$75;zgIyKeF+BCB1h;q^8UiWrXriSEuELh@0OD40hy;<8v7@b~peQ$C3zIG_oP=7Y*)I;4RI>qH%ed4&vp zO-h(Vf5jKl9b-NA27oUAXH4x0i;Z{emn@wC-aNZ!G))veuK!7li%hHM5W;w1rv8k;Fx2MibzvPnR*pCJ^ZIM#j?W}@1_l)GfD zDx?)I!Ot&Ps`LE}v0$Pf$;syC3=g{>jI#&6GFCQ?#gdPtu$?L;RX+K;6XR4{7 zc<=f{UmH_qVV!yAlW#3YfYO7t2FHmdl<`NR?I-CcsOV%hTP-Wh8Aa6A~T z%uQXUVX%1D=Y{6K#E#GN70< zUS%l0v!B(=?}?~hc^)rHC(~nn5_MOptY1Cr6ESq?$V-aU?{i;@f}EbN%x?~-t3-IK zKDnjhU~lcf69j=!-qKOJ!z&Jd2fy-IrXUWGc&6vKV>-3S2=<^#N z|Ej}WSN$Hrm||gy!B>P)d#+@8c%j38T4;4@)8q;%cGsBm&`q#Vt zbICkj1xkgj; z4SQypTxG+OVs|q{G_vLXbk#ONdl-;;V1rtGxt%e3yRwLA)MZ$*@y{BS(qxNWTDz{o zBx|nL*gH^%Gap{xH=#F8RSx()uJ7NKs`|lvE+rGFjB0WfjjT*O9zhJ9vsv4bCbmxO zP1Ghp=XhDI8Mp-)#Ddno@|&GZ9!SQ?6_#n|2PS)S!TS>2he#%XzDjRFTMtZB)esm_5t(4iFN;mln0|Cu^@PzKFLWOE0{!k zW|T}_;^p0SEz^sSuQOT#1gE0*bhO>|=5DboyJ8>ZUY~!fI-pEX!~**YX@!Z^2Ix*_ zty<)cG!0aAYwJ7bC)Sm!F);%jOrPSf*ieGk=0TWyu$lAQZRGv^4@R>9ZBf+Nm6k9g z#Yioxc-x$cdVQ>1qgOGgdFwxERec~EU)lN`8lFAY>#cLpGzET3IfdHuv>D;VTNP7; z&SLs__U~ic^LljjhIs>joR)yNW*t&mL#5L{B?Kl@&_ zAtBO)%x7Fs8@h(sc3t$<)DcG%r2{1KBLrKs#ScLod?eyv`~ftSPdNQN+`n2_zrL&I zt|xbFxm;L*SA_pP+}24dzD52`QAeGc7@g;NZ;-17t6oeP?THSw*vYnXi;j9OTe(E1 z_OOcjDb^CDIV^%*k7Oy@^lD))?d)34gH@@L;_-Y}q2GsG8w?Qt8R4sVPXi2A^Y79r z`jM8v&b)K6buR3F zXM4+M?b@tGMzV(-jmER;IKdkJRtUd}@iX&LzO>iEDEwftruQ5{AG6A8^5QSz74~%l z7vvsY76&(jZ3a#6i=Nb-MPqu!nC*(2 z*1wXVO^sjH-=BzrYV-=fIjY|+RXT$10N*DzfxTvdQ|Z!NsWoRf(lRx1*X2FX3zNGr zO6@u=*MIF88{%l7j|&)D)G{JP8R~*dT%|`Hqly($HM&hQenNGzb)W@1(SA-rF*VUHRE1aaEK$* z+evoi>a``uvpMa9_`JD(C&81>dvfA~#uUF()4*fHkcs5OgtLl3$oPx>X`^Kak>;5Hul{(ec=yGwK zZL?;gaF)t$e2#dou9li`)1RzjLHKetg%Dyor;l9&l;bxfN@2i(>=T5_zTY5vr zD(4m8qQ9;Dr5pLrtoU^<+W}lK3<(H_u@X_R@8pP~+w87YDayGD$Q-`WNnm#kP1};b zO*xx3X6sFp+6P8_kUy?RIX7Ok{+UHj{Yr~6BXyXAv098KPSJ1F;%1Iav$w@Q=hf@z z=S&V4&4T{Mct5&K(`yb>;oJh3(#Ruu;Tr=t&!&$zcs;Kr}ZJ)l~=*#TnazRQF?~CVIIT5 z93@6LvREC{7DK&==5UJEHQbleC%+ocm+eYwTDdJf(?nSuScwY_H+l5`6GfpbGv{DLAFXy&1c3gl(60m3Z62Uk!eemITd zC!N(7@q1P`9gEGeXMQ$EP$_nr%NSFzkzPsd;^$P{P1c?P()*4sUu2t<*x%I)wS_{( zc|6ZyF|gNuy{fBU&@305O$s!9l->@GVt_Ky>Wndy9^Jmn3sX;-^;*lhRAK4<}Fl3q?sN^~BWnn9!&DE1s| z0=kSlg;9+~$w@i^eg-v}d@>4+R>qW#F2~*G_X#rTA?nF;X7kc{sWA=*YSd%(C1Cm}uj|Pg;ABEU&>G328>2Dbs3;>aue?TwGzyq^~d z&b)D>n)4{qqO6ws=LurptN>@h0oQZCD?k3&+Wl$$Ta5e3up2x?&|0#V^FB)?Fjvpe zKoDG&m6~Ebc)j;I_CvMe;|tdf=a`Yu`SbD75!@W;1iD(vS>)WMwyf~aVQ^C7396(c z(EMQ_X7|in^x_#^g6dJ2XtIqYWy_-fcHD9s>>!BK){r~rG{cr!M++JTWWW8Vd|oWO8ui(-1=36Vuus%BQ=RY z3rz;-&^vi;or0u_yar3EwL$CJVeGE~^D@CDZ>4AGkEbtEsA+0|#-lF+H16?Qj*n=y^*+e_BsxQQrgy=77OeZ<=!t2`Z?|8CF0jbii*{RZJ+p*dB zRsx5tZi|gcfpaKv)DR`Z3Nvg!J0fHogU1PtDdEm0(UGV=FgGK@S|BZ=aV45) zIdv<%|1J5h)+qw6xP5c+I_~%@IcFsT*&~59XeBj7DrSS1b#)i*Mt-WHDSb zk5ZjVt?>aS5iXS5zcZMhaF>&Nwo@cgI1PkYR`*HZPy(rIu05DW_lwD#mjHP|Y4(`k z^&)?fsz<_1L(tjTV0x~;%-hq7g82-!zHwDnHH9L?RDFdoysMP<%G({uhQX%ph-&N5 zKcSM2V!TPHIMVdhYVoV2NG8?P&*$vtt~9WM&ncBGhQ)36PSwZnBqf($Y0yG-|@Nq zs$E>OXo8pkxXvcz1Vuh|7Ay~<=(ByIODao z<&^aFP0-N~#|r#^wCsP$%6}=T|A^rK{q{dW{8t(BKSBIY5dQ;;|DogmOv3+=@PA17 z|1%_v;(Hud_XU;cA;Sy&`68X6k}uS1SQd)0&QQI!je~mK#?pdin9`u+3saHNL6E~ryFJ1>PKAoow)&U7CCE}RhWvK&7Ph*bhPK?N_5{D%!Os8BcXXf(Kz?}l zhh93vwn*U%1D_hE)Ux9G1H<)ah&tNwXBv3xYFi)9;t22iv*q+NATU*9<(@ zay&v>3_@x{7T27`XQwt#qbkdqqx%c7hJb498FJ=7R2&5XVZ?J4wm*+1jjwvOi`B`v zpVsu}h%Kz?d5-Rv4)H8jx?OF(2I^KD>Wb&@ro~6!eKqmD5hMtC75;x!nE)t+E@B?o z5kHFQomqBow;c9=W;*Duj1;thx~?uOswZ+YkPriVdZ<>vK;kyNXFyg*#f<&#OY2;< zwZD<3 zbl|UxE3ClpOz7N2WAbX~qKRAfVn;RiZ!STL|lgs6hxkO$&cNpp?lNfx$=s{fUerx53IH z0;~+Y2L}Xth!M;|d3t-;Di_b9iz7gG*|vei=t7+4@-)8zbG5~HQE@n7w1W!#nF-?O zwk$FMj@T8t5%K)3oFYW4LjC1lIY_dPxmORwYLY6a0>mmAOtpwXk*I=>>VO>EHUvW`&I%sq*V-gtm|!c zqk6JG7>&x%umF-P9sMBmfYwn^xb}f`u$j~#yKan0ucg${#Z3t)jmFz`y3m_`sPXVm zSgQka+we&w7VjtHi*(vFUUKiqV(@Q5l+b7CBz`ser2aXtXbihzkN#~`EW;4A8V&0v zeIB)IG8M41y5M?r=xz~Epp9_A8sw%LafcSgAe@&!oinb)_x4&$HSMZOw}nNCUCw%K z)$~fcCz575>rbc*CNk|N#qd5XO7s#sSUtBXj-;YhVcj7!oW0<%GvR;aHC0&czCH%c z0LN27*gA!2N#ohpMlwk&MtZ;3sjet}owj5Im)N_PuuD*VWvC7SCNsnIJF)N80Qwu< z2AF~?R2288>G;W?-QOCxM#x&{(evg?!u)OfS!!<3E4PWgl|YFR#lt1d%>CTgz8eH6 zdmMg^3-|>k+n^^oU;UnJC_X5Z3=2eG?yjACaLPxMdF?Mw0rv%_c5ZPn;S$-N^^s2y~nEcNzv7`KDn!d(g=aXbzV?zcXAxc`*2n_#A2!hi3Qb?Ai|nMW=fii1>Jz zHt9$DQF;((^JN7Bo0RcTiF^C7$*(MsDWU6KpT^4E9&S95i=xwH4vsp*bG6lBlLcgx zG+3nIFTu*fX{*xa(A-fth2Ic_-d{l! z2vLKWCj9pSywaDzoa{P@PZE^2PiFMzYR7oLJVfjWqy?w&wiil`Pbo;L!N@gl!r6w2 zX%6`7`F=moyp_NUY25{`UGb6<_&@Z(2N^+6_uA$NTgtvdk~di(1h7(f^GaKw?^1k7 z4^;JCq4g_6XCQ z;GnJC@CFIx-IiPL=|l9&d2-cQpAF*N)@KJpw2o$OH$`oa4nhRo5C7ioT*{pjyv&)ul+T%1vD-5*wdXVT;TUvpnaB{k6-J=@^ zs681l^l`XM_mddl?<>9ml&jA_;{Cga4Sk1f50#<1BY8RD^}hQq3es}3(xfjMhZ%m4 z)iYe{SXYeC>{5uoQ6}D1LV~t~R=uxTfo~M?BaWR7P%*SyTPHF)&!iq!l2oG9ZRU)l znbo4!yk>|^CD-oo!()2CRsa@|-nuJh5_=ooK1fm{85^O7QlxEoJB3)*afLia`O5&{ zniSvQy(s~%_XcX<>wN3B4Z-jYvV*+uXf984-S%f*s!!Z(q?NW}sr#3yxi4ez+Sf4{ zyc|Wv(5EcoHFdB$-uJ=)-phyNKT}bU8p!>n1oM*$)<7{5^aoDeNguP~7bdFSx6}@* zPKfMLnsL}$9-x1J+&lkP^D9n>n5O;i-(z<;*|#j}aBQ68ewRRn8Wq(0I`9&c`Aj)|7;OUCL_ApM6nY1BtXNsVmY|&=#x7>^V){# zp8LW{D(QP6s6+nl^w)&yuuv92Mm^?=hY!n8zJWSy6`PxDo$RBi#;9O|ER+2la%O$Z zF*Ac$Lc}h8kH1I9I@P{ydNrL}Gm4MG37QT}(e`n2 zK~@ua2dwpJVv6tq78uIK+^JTsZ2T)bXqgV)LAho{$F1xHoMO7c5d=K~v&d5R%=4$L zF)`)|#dK$oI=vM~5_)==%qOuW;NVUO2w)GFE$mCH_pt9m5ZQQ#UH z;5weAcME6G2yV@%vBzcMnL_GP#Bvv}rVl0$_Ks@WpMZP;9G;Yt%Y4wI);zQcdMg-u zwYT2O7t4>fL~e}Ms8tGHX;78)byk?ZUkGpnv%j1|K++u zvi!Nn9IiBZdU)iJR(B=weFx=i3odE*v0YAsB z&flru?CKedR*(i`$i=O{W%~3W2n=~j>$|m)>!GH#zspR$m!)!p@_LhzCFeEg`eeUd z?!5O_J;Ei|mS)@9_F`B(cUEo7{p82jTaFeRFH*`k=}SF;jqf(LU;ITu^!u3veto}- zf@UH;h2oxhbG9>8gnTD;e6yuj!Fb_`9{PW+p~&l|e4CAI>FcGWdUdaP|H0gIYbC3f zxjQ>MN1xA<{Z<`dmbAW(d^`=r(nd!8WkWuykvynnGFIKb)>Ww<%ILz!iBdoKNGbuOU4xR3*rV$M)W1 ziNiYCj%OMFgy?7!=SlAp|A6k{z97PfDE9hH5a#{X&=D$Gd_K^55$~yZ-e_8)U<+;l z0mf*V?sdj8ph?%POyvLU6gKo8xJG54SxYXu%xP{^aiF*i=TA+TmQUh7>D3=2%a9pc z&kc^#*}n1Sbe2zGe6|P1D_bs#@huCmyrA8XubvaX#(4(;VWV>uRFk|=P_#k@bnJa= z44vsekI@F|fV%7+oYro2jfJH0R3R_xBF&ClvEIXt=bCV4Riy>X?P*?D7n7o<{GxEx zVB5+}lea}~@0a^|ZySHHSrJOJ2Q2X44AZ6OqmZ78{aCMRw_6Lf!oJriM2XJ>D?}c2 zSu6z1GrbpymT?UZ)*qC}_6t_#*9b=0?1E7EEA)m2io$RnVhZ+nY{{*06ExXNH)mq$)4% z#%s)G6Z&3N_6v?FgVET^Ms8&;lN8bQgZ@%OI5_ZsNk@VH0mN6aI^a{EU!(WFd8$YE zDGpX8XE(+u)pZ>C=U8`5yR*v+RV+=4kQZi&)|n5dk1aYnRB%0Mf?Uj_FgvWi4YrM_ERfHv{AcDVdG%fh#=H_C# zu*=Jh%lIEAYe@V1*;igoZk!+Sfr63S211~^AH3>X5?kzwCjFQ%l#tl_-((}5!G zf#FNH$dy<_Tq*vuI@ZOKT8751C;kym2&w6%9g|W~uaqrtA5)ZRuO|SnNv6e$dROQ~ ziG9nOSPy`cfd^U*+A>C0eV1I-Q27Nd#vX>V2-N$(asPF~CC{d7*~2BB`)O9Ov?^^4 z)dg8VYYU0ykm)`w`dgwSM6IL`_~^dJsm??E_T2p{|6rkIUMRux{3;l- z1K(?!x6jE|e_v0LN*^ztqHbTA7>_3tls{J-=H_LCdpyPAdy_G0u>k*)EAGn|ujXfy zhe|r@Ij8^U(VuUF!gNYpP+#5!59d**czaUd(*5Scs`%&rmZJ-?or4>%f?nq*?bNF` zq111t855;mZ#$}>Tu(rwd;%}N`5|lOos>#PWyCw z@2kK?b?E6SxGw$9G}VB|NC-Qh&^;JpB*=_r@$Jo{1}#@YqZLz|$=5REl)7^lGcvS@ z1HnGI?(_=xqb2*LKMP18VH6YTb53EdJ@1%ulR@eTjOS*kOD^lDMk1K*T)qb!@M&JC z5z4;3`{w7-Qjt)=bu38SlArqLflz|dSgt~$E>&L@#<x7tzO*4<0Jw>x_MEw zh=4q^Ki7EjRgX&dyyA)nD7?3Ki`4#(^5Veupy|5jb}H;Wx#xe+TI3(NJ(D-o1g&;4 z!aq;e`i0U0&(hqIt@-yXLy-BTl_*Eqkk=stA`LJh_J0?Az$AyZJa@(o+;D4Z&4y?= zOH{R>a?yrMLQ+8Ow8!MY!1T55VL@BaY%bf5PFzM8GX4yNK>V)p<0jj|V>AF3DQimp z!QwZt-C%TNcdC6NEw?D=^v~0=s>GN$5@r`d-nZ8=&jvAz#@)s;qbORV+X12MC}eeBlAOkEINNzJ&-f!6Ia>&6uBk zb3l}XqvZUss*)r$95+Uk*jX=*>*~FGOz0IxuPqpSNGKjoPoFFD+ru+UTEO=w;?shA zd-ZfOH}^XSUkO@=UI~Nr$a|+1kSkNcI2G~3Vvu05c$Zs&2)T%~PPM|b&&EgPagP8p z12FebPY#Xi|FB!@xgq{U=(&%5wwQ853(cT#ha3RSuTmLckfrf=FGti}r8k(swRIB1 zkA84A@boAbejj#dTIXe1lRvk%J9)u@1JXJXHO}KK&KlNZid<}T_xH~?ep26}oaVDg z2_GkfCW70nL!C)*W7xkk(${wY>;zN9DbO8ufA)}Yu^;N*DqOv!aql#bZ(7x?nIfuf zBl-qaK|d%VexfY=L|hEPJ7&&W?#M-1#_XhO3bJQ0rewS}nrOYZD(Hd4vqy70{GL|% zp=t|X@8iWpIi!D*w1Ya-_ZoC-ucFe!@H!iK%CCDu0T)t>K6LFuJoyIhWSwR;nA);n z{M1p`_`Bm0<@M-Os3tN%CPa9n+7)^BGyi`AvagI$rlJtb-?h4qo%qud$thJRvHFJA zu6w@5j3@Q%9={?>X%aZ*PmG!QMW&6%&r5XI&=oQsfsBe9>D^{pHEuxj ziuLKfxD99&-K5vIJ^Pn^n1~`O=38K4u|(H5{}!flkmtF$*5ph}PhTSY?|@drO)y-| zjN`gXxFYXe%{025HktVi*BhvB?TwUvc_%GIq=&x{1#NX~#h_q%FZ{(iQ@0Nw&5b=~ zfq(FpD0aTfhpRw6$gIGsg}q%X_Cp&!@7KN0y)-Z|KA^@oZ;E} z)$`rT$WwmHjpCpcO$$Z*7mCuGOu)%&=>K|%mBd!TV?>h#(Pi#p|R$j zN;ZjSe{1wl0hAG)x1JZ8vjyxeZ)DvEoNcs$Dl-G(+YAF8a5%?tQwxe`-^>5c38B$K zrffn^TwiM{Zg#zSEfX)+3+8!WI2@Us1$fT=8ga78H=PA@7v+w*F$gn?c2tQKJJ7gu zvG<~_aUe-s>qW!$+XHdz9J=hssBf=8E@IVtpC6PS!uMvy_vm(ykn|5%a&TM`j;a3W zBk4e~A9SPVk+r4=YAJra$I zh#&2}mYGX%O={Z+Sk{qrN8--{SO_mlvKr>ISKCM zqD1febObgKkx@t~KM3OyloYHWj3Do zUCUe~A0p(N?Vcj3G771|N|<&vB%#+-`eencxTb-Gj1<hxe3RAlH(w;vvY`2SijnIN@6l=qu5g1p ze%g^{2czJ<&UpFq>bvI2PoOsszAfa0YCYr29a-1ZF;KBS?QNO+3>t?fSDWJ=bcFEN zw`?>bT^4aTZfxJyfPEqaXe$qA;eX-KHh7HX>f1~-8hffJk&eO%6$cYYYs^!FrrHeX zQ(i6hT(4M-jiHE>TF^JPE1101DYdPpF2jNDsGnM{Uh4Xl0Qb}>;@O_Ogfrpb#`<33 z5+I_p%^duH*E~TiUgF|&UbNb6zHUze70_|*a5aGRy_bs&-uu(3{^q3rVgtW1p)O6~ zBwv5dQapnYnp8jaS%2h|88ZE&dP3KX*17}NC&;x$|1-ofaWW5aQnv3`el5FhS?il*}CR9*#@;!`Q|pVI+u2rqB(KfSw`K&%4borRiZ&=q|; zErLGE?G?qyf|t2Hx7q3h*^6L55ktzL}#M63ppe+L2#s^5Fb z|8@p=xWJc_jA|wnJEIgmeh8JF!P<)Q%{!D%S4jb`MO5f!Mg#N-l9+BS>;OR&)kGd{ zl$?d^(rb`N!7qXH=yX>w`A6yA@62%Jo?z)jI&%4jIqovpdWiR&jTobUztcImf@SY! z_XP_NZjZbnLYh83Rpwyo zl^+l#p!E@Q8^-5PNc##JU`hQ9{Zbe<`Wwx#tH>v5{pkRvU^EP`oh6u}!}yl`;DBeu zF`0}=Ilj1%_3SuwAF!^NDn>*wE*%4FJ{#5_{%_6Spk*ze1Yt1BUmZ$43xm0%#HKFv z(3f}GrM4R5Y{gmjy7F+5S~5=Rtl{r%tg@5~E-ToptwR-<WdMSi{R|e5sS*zss(t zjx5jvf93yqffBf^b0%4BC13L*wTRY}HjDuiifd8$#^`d_`Y;Xi)6mg<7xf#tQWM1q7UyTD4isDk?&Qf8%k^+1vt%cMhw|kpIo{3$sYctb!PPbIl z&LVBkV;#B8OEs6^F_rGMmrRqkU<*op-=9O^Dj(W?_|JdOSr)m|-cc_>o(=~qR zHTAMPKPX=x8#Fe`M3g6{>L>pNb3L97s)r2KONy)nrOvpW4RsC{i{Ov z%rJ-0A*r9pP3f#w`|#^0KiGf>W?2*RN#=((;>~NPI5U74*_eND>`;I)6^1~ngXFuM zoC;Wm62j(sMNuC6a?-|6C|);Wz?g;Dd-vOLFrrSmu*q?TA4sM&nEizC0z~8b&tuM0 zIrAbSK;~Z^(@mrw7L-(2>DnvtUg*IuHU!;=m1#aZKv=0xXf%52&c4@5MOx)2i_{s6a)lma4ep{+M`_wO7< zW7La#_n9gxXNS9e`@+N5O+cDT|B}4)2^^(^VWd~UpIlZSAQVDB^loT+26DSDc^4hD z&(&BuouIbw>a>6{(d7n*ZgrxwU_v)So278Ox<8wVr!n)kz&rTR-yacxu(e&W1MfDX zX<^|dOGT%@x1FLP(2wZANTYfJS=7}Ut>sACgw7Kb>s=xHa^LouB|CQ!f&0J?xNb@H zo49Sk+RBSnE8C=4qFC0uzKCOEgEipX@V7j_-J8QF^ig#5SS6Kf7kEoqM2P|-%68u4 zT=2++j~C<~MHe~RIFt@7&>*CzZ2VK_Mm9Q}>Yg2dZ{Y~D^lJY)zdoJudo1h5K|(*N z)upiB&6M@Cvx_EB4ln6?;@ZBWSK1HUs%lc&Zs1)q7C^b3s`^%jR`2MIi&U=uMm0>a zJ_dcl>xs7T+cMx56y)w1^FFX<(ZZd4G{*`MD$1qR7E%C>8D}#12~Nft=>ahB2gNZl zSbfwriG_qj2EHiR_tzn#2Ou_by*r})H zB{zKf7}QT-AvbZ$9csbYax9n&*+Z+T{M64w-T{4F3_*JQscfYC7&&J%r^HIF&>q$O zm-ohTCBNSj_B*^pjh7#k; zQ7B*2H~GQViJo+?AY3AOC=whqRWLb@UEj6tP$ZcfGa962HivkZ?jdoZ zlL1_UqUD#JBK6kfEt3y$l9%;9H7=iA7_RpT^LfE&yI8%;hy%m<`3 zcbj=x5j{!)r&JtsLHeiB~_(O5syYEBB7JPUNLpx&lg)A{K8ej$JNBk1*?)jT5P^ zif-oadNHqcGW~LsmL4R5ZNc^Y?E5;?@CIOcy0F;qETnLH3|77}m(;QSjQ4Q76)tWL z=HNT4ER1~drsdwc$*Pcoz+b7Nvd!cqd~zzd(!%+g#&UUt1>v_up%^eL5uvW1>u)m{ zK(lY)zwcMZM^$T^OQJ8%3;Kk*&cd;1)6C2==-@fDh+^;xkO8e=W*?Q9AtQ9ZC_AFe z-vhL^*VQ8MbLotKu(W_79Gx9V zz=&iWOE4H_0Qd7rUoj_Od^>=6?2w;6C+vKGc07y`raVBq@ZOxNt3gofWR~bO8W%c) z+$Qjr;;XRK)fs?%ub0QXtyxjm=^Z4C8|YfLUnIx_h%!5r=Y{f?0kX8cL~5k1f(8Oy zPhH4;8o{i^J7Mda5Nf5!)WJ--F-x>d*6R#DfQ|UkDXpHzpG{!fM_mR? zmJ5=h^_BBvQjtyv_INUZpYjMQ4$_L&CBT)?1fMS?o)DX{rEz&K0lb*I4+yJB-JiX= z$gY1I$i$p{({zp@gc2d|R?qbt56CL`67*EuEpB~j!`=>URja(3rIEiaXa5Ce{J$j# zKy#CPWogH^-UF?HS_9(uBJFZ?{fC?bhi7Cku?yUx%L;*1<@3PyNFQ1;Lt-E`u9)B$ zD=tG>X8HX#7AH0{5C;f&wMB{h8ma@c8S^87Jzsxe#Cr_tCV0mjr%38O>`?Q0zP-W6 zmhZ3Z*>k$-D?(si1P$lQb;lyW$xPN#?fzIE-l_~TS5UAMvWm&2C9t4B@Mm`Plwq!G z9niHF|0NscHv}x}^n@V&5tPPLZ0j{_9Rd?;()QQZ)yjrNGQ+Tk=*aGpGz+n;<%RjD z7S<&w>7Z8%CY5oja{066~aWMAKua)IMyne(umh9gW(ts*{Gsqw*lJXrjauJs|IDfmStt zL^0TQR3xCwtI={gdraZV6xTxRNARi+IEK5ui0*H~D78|er~8$|%~B?o(!pX}KrK=X zMFbsj2!;Y>KhLugPGM}#`c@ZfH}g833tsglmCy=+Z)buNt5e(yy(fCN+&!#oio>`F zTPGWsr7&K;!8LYe5(kT@ztyq(xdVT>Mb8Ur$8+f2<2VbRcQ^iie)o(QsR@^_`QEJx zTca>KVz}oeJ1a}fK;qc%M0sQFBF8P8w0)->-4eVs<_>)dCXkuzCPAFeoG^XYs8pmt zUPJ#lXTmmIhSb(Bt26u{S?xAoTohi9D+6D2ItVlstRQq28{>q=YV#VPYw-VeNT-O6 zg2%WJVmAD_TG!1pPS4duR>xgn>d(*`XAp0N1BjOb5JjDSp@?U6@LP13igi{*B-4j) zU>*g9Tg`7LaeLdSx_(z1@Mx|OFSGOQw76Aun}04P$1Bi9^De< z&y6(29|FY7Z84TOix*N$TbU?-6dR{^fC89wbU4-mk$1$)&FQ?!bZoQK!S2MAR4>9A z08I~@s^d+uw}Ie7J6?N2Ss$9DzW2#8Syyuj?R^anphC=}PArc>!sxmlt3kEr3w*<$ z(GR}qOf&Qgw4~8nvX@Usgg2EZxXrCIJN)S+T#mNKLWJm-PH7`TxSlJ2oYtvSaQl_{ z>&oh2#X?Q^`TH8dkj9Geu6+H1J}#f*&5|%###vDpASf>MPS_tB!Tr`c0*LP!0uQKl zfEuB>*ylC@!wtUG4>c(lH@hV+g`WA|6V3$l=5Pn(F_~-J_CZ79CiB@qWdDR!JWSLU zBsRHrxP)DC`B@c4U?n> z?;nNB-j{0OZ?^#%6?vGA;nU2KY$qaz?t?bX)#Uw2c@!OW? zlU>w@3%^H4K}N@ijjQghi(zd6U2`0K)py%#!tC*_U32SFp>gFlr+6qWxFnukgehVz z;5LCOtp+n6otEDrs&asMJB92k=#=E3fBr0oz5H_+2K6dFcYe7!HJl0r7|hqirT*n( z{(A;2-<+`DG20K)+QB}V&9(*ISL*!bw^?n!$J-(igb|D#*(sru z9J}-F{64?)Nb>GVKt`X}=Z_jXIYEwSa*B!uI0WLNqC@*8+$g{nJ}FHBPDe1fjUtWt zNA|XfKe4Ty?>n2djxNp^8o@%W%xm%wEPAKA%}?>mNp0l26220RD?5zG2d>Q*DLjyA6x+Z)fE%6*2kB1VP*}@?` zP7*IP@;LiGHQP5{zyMN4Hz_yM*yJ@yGZC44{i~_CNu&E(n5t9|d5p$)I&Jucf7M9( z+)qzu))N+IMfL^Hqv7bFa~Z<+B?#M9Bjd%o%ZLWlK{b^3j61_H8yltN>GzgKE6Xzc z3M)XNPpjTP#m&&XF7j=vfnxq-;UwSD7Y`k7HnQEF zBFyGd04F*>9aUeD6P{Bf7AsCr%zFoZsKpSySjKc;f_Lf{Y_nyM`4L`EG!Xuzs;ak@ z+=mEEnNk4-A<#EPxSA+((wJ{o0%g3Gp)Of5d4Q)N;VM8Qo^f;kXol$|0Tzm>Z4W1a zsPPPj5hAQToUb}*WT(2dyEO&QE6X?ON*mDwwN(9GFN$BJ~N<{-E3L7%X<+oZ8*Qao^M(p#kxUo3A8V#aTVXF z)EBBPjsg;hAo!CC1kGvt!YKulTZCjUe)1zsfTy>=oG!2BKCK0yv$hcpcyv&sv=~vQ zrxsCaa)UMPh7ZB+&kRA;BKt|xUP~apul5b4S6W7PJ6zC0o$iLsT(pAXWmZ1BZAGi# zXC;oI1Ki9q=~T+|ke0=4(WHKsk=BVM62toRcW`csQnNOwEzYHgf7LESa6di*=w%cgxUFK z4w;kIH&H~kXLJj<2TZ%ev1P+<(SKen9$jhP{rIZM13h*Y(yB?GQ1yeBEluQ{XymuG z10ukby#$P6>-|0wu!<_*yG-=X9bqO^+`yUMU}Rff$eScGo*iepG=Eg+?(R-+_}EVN zJOs#xoG*UMs`$+{_Y~8Q7h}%Pq4OEF9mMXbzbUYtJKlyC4NO7_(#(Jt zX&*ebgK;Z=(W3G;GZT6L<>;~WlDwf0qT%U+t?_G~z}NHUZvF~23JM~5l#CRdgATdH zqWphP(`n7gPXqky2PzJa670_ei#(dp1J57^i+~#^r9ioNbJH_fw9c*HYMC=>pqESjz~)v9_aS?XzF_-xv&t2MoM zpVb;hfp2mSn#L5{h(A=}yc>Zv=iLF6ugrPxom30K z_bZPafe&2zistmSVf4ci?W%A2b5hse0B&)UGbaURWtu2~h^E(=O81<=;!7& zzGCVU7+~e6mb5p=naB!nXU|O&m5Hb9xTnXHuyjN=kHqa_EyU@}2&g(gv@_ZJIehzu{5my)~pyx^p4&5Mll7 z6EQ&bGr&e+xSB3u(ZmQbB!)NXHGmb_rhAkRb;;XI-CPS;=*D1UGn=?xYcMo%D8aChD|IZf!|W0>SdtsJyktq`d_A^*$!ZrdK1 zbG$emIEo=dYk9It#ew&|pU9rTv0t87PX$Ft8i((5t7d~6#~u^;5Bro8d=6tP?BzrB zHVL;`T`{OK1vKI+HV7JD`dLeLt0Q$y#tFSetp!HAlQIRXS}ksLKWg@*wT%?vSpfOz zG>mwf*16D<@BJ(tV? z>Oj-}h+Y%B83-Kf>k?3I@*rNWPc!wnpMA$Qhg_CiM5f;C_$ z;`Pdl!lQe?eVVa9TY zDE{IrJ-Zn>He-2){yhH9X_D(|m`m0yh6`brXmz|uGuWI!8;3i_(x`D+b)~H&1`vAVCHPMvTPwR@ZRurbFexH@zl)ufTnF_RYEP%GfJPx&#JT_@Nu$R zvVJXHzuj(FBsjWigy8SGZmzKq&VTNVGF&=IRj>=d$NUb%nd z+n@mnY{|V~Y`+*>ectCcggIS5qk*Iv+SBnj)btyktv<9h?=GV>z=|RGvm$N_=@U~l zZ}-y74E(b3<0qS99!R)npmAiAaJr3S)J=-O$5RHObW|Z2>Xh>ASe&`!uVxgaN^8R> zcdgsP`ED8F``YE&4p!OhVIj0I%~O;kW`Z|%%Fo_;+b-N4#V0O_Mzmdp_cl^2%l~i~ zir7-yRosl!sPhHlW@@F+jozI*r&m-bC+w4ukFf2YO6n)Nqp=2ELDGQ{d= z1Thq}8|lnX5}#x@WIvD9UP3KZ8{0m7+vxb=ggzmm=?6*&ql1&zobc}fIiS7<$OcNt zlK{ODY!DGw#3|rcaXg1Q_^~(L7blL=(0tPcx8?d?4AYin1VKms zfnrSMMZWLe{F;x6gQHJ?c=u1Hi%+V8AW*MMW?riY{!X~vTq4K36Z%6@?PEVsXI;%g z_Y|8ls#ffB;*9Y0Q2gfqVehTts!Z1hP+5W!Dh&!sr!+{{5(TBZyQRBxDJ31!sR&AU zHz?iRh?KN+!+96X{P%44`JJnCIX4XREf?SOKHcv;iqD?g)^FFbJrbneuZj$og0Fv% zpLQ1e`#RhfBv4l~Y&Wi-IgyiaP)UE|YfLUVSJI4EMO+WOaJS{z_YaU4X|9tN?#C{w zp_<72|LMIEM+22O7jfdHfKMns(j}hxZ@9ZEoUTiB@B-3=8zKW5Lpar0i<+%8qy2qM zss_-^xpn#y2{`#F?n5#ao-j`iUFhmc0O5B0*OZLaKzhJldCefyz ziIL9J7&cOL2;u`2AN5d9nGdgYvbo(%U9x36#6b7aB9driy(^`~WA!&MdX34Ykmpig zn7fK!t>9FDDOB$1l{y%<{zjOwpdS_-0t#7s^RnjD!UA-7l|SC1P3r4MUzPv#BX~&; z>hKBjq?q6>XbOYuGqhEgxSAwS+{EHo3{4#d`WBw&FT2l`H!6mp+7S7XGdq z`@o%?1Cp0xwx~1m;!Wc^xQ>{ql1-j?_`Rs zyXV9}KZ3|5=Dx3(N->CGNPHZO+?l2&N!+z-`Et1rc%7znU$7tz?yu`gHbw=;yW137 z8c*QscRIFoQ1g?4vm}T=98L~lI0D7v89F%Er76P>^rPQvO__2&2H{bQ%@`fX$>LOk zda<#!X}l84<8pYzVkjv(_cvM$Bh+(8$}B!=5KWb7zzSRYzZk1YGM&bvjV{2>fCEwnQ4IMX;v3!r@*w^dHf1Wp~`V&iBIRTb6 zm@J2cNfUd$@p}FE)FEmqJ3DD47~BdySLb%rvy>~Pc!3oMIwql%s(U{?zhfI0f*!=n z#1?`VVT9%d=@V1zJyp_e3N8r>^pP`Pu;HC(+Xu7?bIoAJhIqU}2Z9*@B{|P?)xoS>7&;VKHt);d% z$qt-Y*!^vs{b9R=P(hB`{;P}>0<+-~^so=>VegV9Ns1*-NlA1>frhpk-t#zF zirq$AZ}(*%7$WRc(Vj3}dq5!t%Jb2A`6}xs62qS4nbXy=R;?eTAKAtOhZR%viR$j# z>jH!#>M@CVwD)aOOC3{ae>FJU0mLn_=L(taOL@LPB=QB;FRgm~-Y(&bKRd{^oLCx)e8Q8bC1{#6SS_~B%@a0LrqQ!D3Dif|(xiqTu) z75iKz9e7RM+W1Fb0Y~WJ2@2>%?Nc%UZafyUnUn`zZN$MD9ravBv!7W3UNsxdbg~I8 z?U^!Jf1O6fnFp?Ay62|YBsT{!jG1s3Ht&1Rw1TZUynsqy+F(6>aeMaB9U3qDM$J$O zr+GipyDy58$59`=OFs%_pgsZZyU#xs}L<{MXeql(mm;#w8u_Re9+75{CdKi6eI3S zW@27_H^G`>tegus68Tsgm zGo{Dt8Zqyf{dcB$D`1Y)G%z@Iw<>6zl z`L}3{){>qly_Td;6D%dSdG<>*Mdwrke1f7E^Ny;nE;xMR!^7y5CYQP$_45wZSrqY% z@TMQi3mEoNp5GIoEol#RQG<)PVFSl)*o(&{GH*YkxPZ zzfvblS>sLKIM^}3W_?5)WO}Hiz`;#{Eud1v?O&LKT9u`CwW^5J9|x}4o_{yy_A+U< zJ;O-HbWy76Ey@S&G&lA2En>p4@Pcy~C!;A72WX+Pd4>uCtI>+3F1EY=N~2^~=`9kAGbcx%{97RU(TlSF#BIA} z$z@kmF&c}{Nzh@~ZEc<;pt&ty0~oK)o>CyZB#;@i_8O~3K+xG8DuI^6$)e88ttUs< zU0w^~(jtgkPcgP;D>mWhuGSydGzmSSzxSGdyWd-(#HEP^z0l?>WK-(!s&FgDyrBjlW!^Kl^`J5LQI7cov+fIHgVp;S$FeHE?6{M3 zSA0&~ic`aF)7Z@-FwC>7k)!|S}6CV8nN)+m1yNsgV3O3 zT*@12f&_nmm-m{#k*dOA?1%FHbky}(U0TUj^t?Xo(I2M+zo$2f(u4UJ>KjK&O59qG zjxMSN=jM0Iu5h2O;WT7t&>_caY1Vn>vJT|&4qt5~^E!pToDuK(=Jhg$GAw|$%ijHb zo@I&67HIe}{hTTmS+)h%@+h*@dbC`@)o%_S=udwXBQ<|UxVo?~1Om!D!S)~bGU+=~Y}#6d_+B?Ci4F>0 zI}D_Q(TkCggZHKiY|2BQcZ*l6CMuZXD{tH425g>vQa9&OI*DJMm^fNdtNek89GnZ< zIwJTFP^qChl#rF-^`okcAs69egEMZ?7|Gnv7WHkJspxw(vwPDkf>)Z1Gabrm`@5*- ziAj71ep0*H6zjEI!B~7Geqt?XrB=R=WQku7?i@L7GZVa!vb`BUfcI(xHyB-!1qI{b zBbelfr26FQId+Swq%`tvr72ryc|xJgnijoYZDh8O0yfQ})E+!GC}GR~-HohkvEuUn%%k3jURXf2H7GDfm|k{*{7%rQlyF_*V-4m4g32 zrQnn{{E#i~|0N}}gwK_ubOyVvs?;-RPj-({(ja;9xhO%sU)#si_Y_@H4BkS#l0#O$ z8U><*`w7p)o5$$gpVUx2>Z&iwv}t7tLZ&}UoWYR7YNx1+6O z2DkY&@;?(vAD%b*WU(fFM;Ha`JF2s7F}K+U*HGYH*i6M!M(}aCcGxhZL;!<)(HCh1*)PIv)=`Guzm1$||#80hn4<(}C|mjMcJVZHX$~TVy6( zLorD>mpQJ^?-#};Kao0qVe6pcu^y!Rb_9PkW3|l*R21tTW_7`r1*FsUe;v7`UpHvr zs)jfzr6>(nNOLAK)z}5Wr=or}c<;4STBU+~$i#PM28`o=crxe89QaaXxziJh3vz%u z_IvePvrP-|ZH#_o{a>Z(pRU_W#8hiG@Y>MFNl1g%LcR~!pX31)!I5C1Lu=gYM{7vg zq*-P{k04RT;D{K!cNjU2nf;!F$Mvx8KLK{dxgN}3FQ~iRF&T9ci8J3TWhPzOxBSXW zCG3zIhcTiUaH*}bWM=W&)-BS$oEoiTn=`wNmr4QbSPHRL|JKmg+RBKGoyGVp;_2xS z?Q;m20V}kz-OWmNT#=-t$ta*SP+gI1GBP!FJbdACpZ+2I?T_WthB{{ZI+Te3wP>mvCz$lFImU~k4nGMK*)GhR+8sIm*Yo}BAv zLcq>ErKEiTKg&N!uY(JpFyc44VyLKP-ZUscez$O7`4x2Go8V%yGo(XRqSr`uQHlg> z>XXkW=BLgwM2mSPqnuLYvXZQ@6d(0fCjePchj%4W_YreB+x((F7dx=RgxS|eX+F*+ z0;9r2Ofq=Si{k-+7x?X-l(k!4JD?&y)x14tbhQu!ZxbKD{Tgh!`t+|!%m09PPa4d^ zTA3;2Y+FeTFSp!sF27p#;G_ft9_Z3B1X@O}j0w==l!?iX$O?_QEsePgEmS(WC%MWw z+m;~F&Zx$)O9`^+X-Lg4hwVuvVoHm&A_M_~^sow>6(o#bF#b2H;N=UjcR-+irdJDJ>8|VWM>71$`B1YNuZoMS{ufu| z{dZx%VTTDA%Sd4RB-(=lAzVDk6ZeeB^H%s=i-0{TBKYHCCYKvRTAFrGE#JL z%o@e;v25pq(QAMU(7QY3*uYg=@71e9$!`T;om8`R5PZ#MPj!5=q64{8;Q#X7NEsb4(II?3w;^+GCs<-jvxYIrPik{}XXTEHMW`-oC+oQk`eO}}4pxn|_V!Rdz! z@Z!ZU{HMAUQTPF$25Dp9+;p=@FlZRpoTruuCy+%k#D|jHpG)uK8ShQOrIK+Ns-u(~ zoKtriEZg|h@%JtOKfwvm6VU`&WD*9)%2i7fkUd+X=+dfQgQgRZh-(pmjh4#^R*w}S z4qz3LP8>uvhL52(M*H>9;trU5H1>-Rst1cOWE2qSBybP9I}-mu4_KZFUyP#vsk)}d zw2y&vcK-rYHGUpWeZ0@liGra5YxJ{r3!9tr+N;0quauUpbT!|tW}%0)`2?lU`Zx#%1R}xGF%Ks_(a{cp1j>$gT^c2mV3nhIif3U|S@qn){JY;d z97;KYd~Z?3sGw<|A&F*XJ@YMw6)bB*C!1=Hl4m$l*E89_o|Du{d+dWM~h>2oc6dCibyf zHR!;%O67A{%IxUy$E~9}!6sp@hJdDq57uqd1&A4w`;MLhWneq^M|rP<%sNmqx+K}DjI1ukqYTNC)ryRS?di>n@wAroz3MA zK^2Am&=aKQ#Vod|&0b-DDrrfDhH^vO-Hl!4JRvYAPS$@hmD0_mfpwXgst}U^)=Pw& zjFp>hfT?}S(~R84dNXV)NTd{X zYUxCoW!+H68Bf7Z;yi;RMKT>|yY60NcVa;&J#snPN#@Jzi$%q33*Wdb?Kh`+5JBe- zMRKyJX}!HB#d(bYAE>k7*`aIMV=nrw3UtSP-B0r{(EdmlGdCCkM`bXyl7E1=`Ke3T6A7#I zDe?n@K zp<9O-6Xv4F<5wbPfdi(QLv)4Kd#!C+yeuY5R}l*=$*upgf$II`*J3de5x_{qebov&UumK$cntcsW1AZVU#anrmx9R$0>fEfU^GJPpgDfOew<%v!)Ag(}LTsxUZi2U5kqoOW9KF zpmCMr_q!2dJ_axn@>pIfJx=`Sv3=inno%(B20o<6ppr};?tP$zJImA{xXiZ%e|;_B zwoY{w_dl2ojFG~Z$MQ?GR~{!AnF&brI}bp4#~vX-#KCIHUGEdemEFR%DOl^`qFs#n zk6YmDxlkR@wH=*Uvd)CkU?!#UoXvlkq@VxpjNJe~HMbA`_5La)T8vpH!J4xKclo@- z-->>N@TnAEoZn<+;a?;N;ZpGrpBgOAPUN(z_o@^+%sx>+@?vs_I&BS{@q--=j4<%_ z$=n$iZ71AH;I(irbV_jme_8Q|$4bMB{JaA+@9Rk{!}eCyuav0bkqjaL;0NKfvxM52 zj(Qv_DUV^qvEn2}2OvHgK;OjBEph#U0TMA7vuw6ffl?wEqjP7|Ag!V7q@Wb`Zp4C2 zY&%j*u~V5^p%G7h7JqCGU!WaIi{a^iu6X=pH%@=6)aDIj#%_?G^MyFH4SuM?&(LGd zi_b^(XXhu@n1fNVeB%h+|dPl zCfdMKZn=3IzM1D+(wAw_9VD)^u(&Agi|%~IoS$o8R|-9P?9ewdFa_uX-cgCzY$}gF z2RD!K&2-XpY*gFbmb4wS7`;j?zYAVt16PY@Nqx|I#H78BYs z2+aq3lu?u;<0bke9Gsq{y43B8Nk8e^yQoiHTT98e67+@EA$^uv8zM~6;Qai7yJWVg zUl80(Oh@7?ml`XS<@a{|l5sr7UlfvUy(Mf+_`0)3u11g~csi}0ttpYm3 z?G~+Zk1(bEe`7*_xIk>r>ZlcYM&uq_OEKc0zUx!}{sI7*rU$Fpu3eYzj9U(L(rK|q zX&(;>)vf$QMsR%s&_kc`V2(%5lh4?=p)$5d1>Aau{8GgGZC3j|To~CO7xaYRgVkDi z4c-eg|A4MP6&1mvN*4YQA)VoZr|M!B_FbC=o~yu zPN^UK!VR#$`$EL=Ud-a-%Q}L6;9r3^Dk2Ulnna6W{zc5c5e&#GioD1cU-03s|E%D9 z&GBam5NX;BakZRCjx`p;tOi2z3ge+=(FIj$wdz{QEWhVb94{hg)22ghz7Fisr(56v56TR!X{^!u#pY`dYj zrpArS^s%O0ijVg0h*~*7ySiCJf)Ai z-9}G<12PFSEzB1Uzn6Hne&JAb^IK~KcKF-MDZ|__l@g<#>UJdQM!*oaAiFD3JnBY? z1QDu6(oxM>iKNegoHsK9NgRI_aQr`#7!Nfz{^BsQq&%C;XJ^|j7bMBUIx{#UrXIU# zmSt8Wo)O|kvCQR0AJ_$nf6^trwJv?-#=$`QnJ1Tz%Q=O<63U23@9v0%o`M^$ngM1{ z#jL`wmbTuX!$E06rq{0Lb2v(NnqkQ6xtA%dhEmknJ^$pLQ&*g^a;}Q(1DhnDYu0A2 zhl*Wlt#wYt3o;cZO1qtk(=Ax_z@P{6&v4oF+|3ykp}n;)yrXuaWo25Eg(GYuD;L)2TW$_6Mb#_WVE$G{^V=fLZp&WT3TO9@|=6_Pqd3p zr<^}cq|)-t2dEH462Jzx0-k~U)USEvKR?{?+c}LH_asg})meR?TtoSFWg)iebnrn8 zU8w+F?GWko$qLm;-xcTSP)t`{DWBYB@h}R}UUxCqxs}O5ljqol++$nPyNDRbU%YYB zJiGV0Pv(O|zh*P)bf4JX!&!7VU{l-pPHk<>&k_Ub{&9>xvor>Sd>BI|Hv zwggdHN-U=Q6E?v5+_2ElQ2TstyN2ofV7;u40ZBKrynSh7wbSsQ-Z??Hd+BIn5;#^r z5QI;}UaCB_jy+wl?$`D2jkG!WI$dtL_kjSdNy@gZZjLCTh>#`*wMhp90%5^>Q)X8X ztCuCNmqeDONb-aD_GOVwQ>OV_IC?M;5c-_wIws0X;C7Y_gOQZX8wx?Bi4$uii6Z+G z#sdtlNdebcwKfk4OG!rs{!?kaRJmX~hoPj2y^Eho&E6&F2yHHW?;q&)Of(?*26wHX z=Ch3vrz>>wXqwCrk2dP_S*JoA_f;%SWMOmSWTsl8*(?m<<0@)M~u4z^ouzBU;I-)0jGf` z%4AlLgCSHUQuHP)kajs8+cM^^WCt~AQZ+9bW z9T)327WLeF46orr?27z*xtM5e|NS3g0+m2gV~roGi`d=PPtckfik$dNR78KVCeT*Z zf-YhirgRjCOyUgtg7zcyK;6=!x2{=!DaW9^{G{pVnO4y0D7pP!SM_xMlLd<=IPpl{ zz{>L7f|Azp5N8-SzrSdtDQOfGp-42`X%C>krd$eUxZQb8#+x;WIOpRPuZYF?rSmWV zl!ChVqs(juiL?I*@Xc=&FG;Lcc=`Hd=oNi;VuWbF&>FILnalYL%7X`%Z&-aG9=E^G zwO0}|Z+oAFOb%-27zNlS<;kxqh}$%B$LnR8iVbl_TH`F|dMStvUqW3;pkcMy5lAb#Spj9#(?}>d(bhu)Sj__YlfE%M?V!c%5NSG67OD!77}wp^ZL0pMsr7bxnYNSf^0djW z@4AIp57bvoE;LQrBK2}pH%9MYJ3ET_Z%lgYWdW?&Yfpr~-mW^>qBR@MRUMm{SnZKg zZ4*Zrk@0CR5dC_|vOvtsz8WlbO?y*DonVvw;A&0OFP_u|{=;m;?ufrIV2jAsZ$*1m zqGpVdF7+pq^q5Fmi-D_&=wtD zstnc0PWSftRg$;Y#&KJ}owzgHs*qrCBG4xBEM#{TJnWsQiUpb5-RB^#K$c;~4YeUb z{Zk8(Q5V{;4ywLLQKf`^A;Wh^zCEhuA!qaCc#nepL0 zTEEkI=lRa+9$Il?P3z=z!_4fS1&cPg2&E-J#BHnI88~)MWly{so{Tv{(B5_ukKHD6 z(oHLrQ%(3eI-`3&xAc&JKc-c!jTH_GtP)wJ^MhAZ37b=CMFnRoCG9SF`2Pj%PKU$g zJ>)ZC#^2#c^d(|5{6Kh0(DKh#!+k}#FmZtGeiW(qb?;*8K(&)c8TIp>RLK{Nm0i_U z9-(&IC0)+fyr%+1%_l4m`Jmj9i^*m>?eL~1%I-jNSK!Cb9SA*7uM>-3dd0+>-eJWm?AVUkI zq1!YzLdo@I&i;GWoZ&$rRTgFI@hOpH{em*pN~oU`>|dNBpUmwtRZ>$r?Y_JP`;BU@ zQL$y@f^MVYOrC3DAjfsl4yidwrCKZVYIF(f78+q6a1>SFCKkVBi7u_}zz25ab+$LH zn{t=AyxHXB)~B zs}lJPfj4+CV?@BIYTV0<39N$P+fWar-Exl*7E@J^bY$sm&mH@0lU5tJ>m2>EZ2SDg zm?n?7z+j5PVu4uxvMBmDy1_Ms12css=qGi<7;~Puf)Eb}6N=d0o5FopR1xu$Cg07a1Kq(Rm7oKsdzX zA1}W)%&*g+Zf=l+Vuz;JzkML}ZW?iDk)F=hWcW zm1QVHgI7w{Xqr)*xhnJmAH-CdQa3zB$7cmw&-EElsv^nZJkcnHa5b8%{;D%fd|(5g z`PyftgEkEtd>{Xx@ZSgZp5=sNp8G>?!H=ubbQXCd{8rUJ&Q)#AN*g3|NF8tVB8eWv zh4%`*l}%Zgeq+rq=fEi_bZ<_6^K*g1>ATU<@A5N(y=NzF30~dbxnJ47fCD}*4-GUD zj4Rpv0=R?KE#D9YfW}-7)UAF^A&9U4Kt>96c!`}cp<%DfEE$m!4GEk0jDqzHzJsHq zEACda(G5F&6Z-L0Vp*rkZMvuEHB}l#sayrF=E~-fMgEVNdPbxP!8l9ao#_Vmyy?FHS6FFk0>6qd_@Vr6sKU6<#1}TP7bCLv~mub#} zyFr>hExNw?2amBtEv5Rk5h1}~Lb$qUan#>pX?@m8SCR!xliWJqo3qlaio*32xp0q& z!P8#N*xf|0?vu9J-lIb2>+exWLhCmUVl+zX^4OzXbO&vzwGlDmk^nlQ_VmpD#0D01 zq3(?QoZPNt&o@ee9-p#|2DOLQy@^I_8*s-9NCyQHN4~mOP^}jiMP!pQ^&9=3H+=P7 zo0FIZdv~9>wr7du+<#vFD5!jKoRI&He#AY|YbYe1>ljV29W7d%t;mnZY69*e1-waV zkS2=#2Ss36$cwgB8J*5U^%mUZsP2p;T{vtym#`cUkoCfG>a;{tlfrNbA=U!}2UlU^E=o z@(QUT$lnx!Mym{^cYzzA8&dy;Y@fz}qwgdMq6&nQ)un*Q;a`(P@vcFIeL=GAi5LHD zzjENsN1ax7D&v*4-_XZ^ayXD6mnD7b>`<83`k9>@?^Rew+~*er*MC+X>$T{-g7R$d z^fbs3q{yLd01H-xlLp57Tg2r4`DQpE6545$QQVqTbX*pv43ycvf_O87yjamJ9XHD1 zcPW%H>O|RP`!@?T1-oBNTi$vm8$V5)xS^E(VrRv5dB6^`tj`^trDf5vA}9HhmTZ`s<@L7%#u zSvIHqMvOT6XJDSwBO&-J5t=ES#1D!ZtRw^>v8FGscjP zB5v26aU=LJ_T`GKo<pErwmiu2-t1w;A8p-E|TKJYed5KuG81iXFVp70Hq+%OH49kI6f5I{kLaMSt%4eQ?kNlVCBfi}eMD40}OU z>jO@4XZvBYS1Jc#6U=R*=HLa5Dv;d-Rtn)F>Nnz7>A5RGJo==Py79)tSBvITsH*T^j!KCd7gJ1iX>+m98yA{;w!N4sIDwFj^y~A}S{2=sUy6r7Fll0O1)MGhUGDmc6Yrj|s*GN!65&+|&cG?fF zhS=t;(r&8bshtYFp3i>;g7uV9C&!-;_4NV2)dKAjK`eyz-%%g{RR=cDS|YCadaZBP z`iHK6ZCP{A#Jw9}R8ST-yB*{VJj`<=6C7Djv8v)OqD(m1dhVRgT)NR4CiC_f=~dTo zJFXNF4(-1NN6-W5kYzS_*!tA)2~(KAUaEBn);`~!iHnExr?Rqx-vkb#oFIK^4CuDT zcgy~N=oW{_!^1NumV?%eBOj+fti4r!gHB@oeaHN>LKOnlbPLz zJ3&9a_udzNn}fSEdZ$7vKT+f#Uly$sx9A4GSAl=019d-I%&F5{p3Pu%A&0t#X)26? zqyYVH%w7M{@@K~N+=qDd!na^tD8NhoGC+a9jT)m#Vq-w-c^LSd0h}0W84KfUYQvs3a|IORC zAVZkUf$?Nd_oJqEJc^nn|0dTsa;Z@XwOi}ib8{?IGR!>3?l`t}#3~t!w{IAKam?D! zpIp(i1ylph^FKV-pAgJk2uI@v;$4h1`a#SCK%$Y6$3&@4R=E+vYS(G>#-X>o;C{ZM zn$Rs6GaNOU10;BVqGpW-IPh{_Mp-564~2ZHN1Zx??{mJQ(Ge29F$v=Hd?FYJbyT#e@&-`fuXqKrr>V=qo@p2WV# zoN=)5ye$6MG1&FRZ127D@k5mN6}N7|;@|+4AW%N}6QIneg*F$bv39)ms0XDdV!rCd zk8aRcgwejT$JY7o#ZkLC+Nuf#jto{z`Rw!xmlNW!#z+D!4yuq z`Gr1Tvk3+d`1bGI#OiGf}=x44XGmO6S4N*b{ru3N^9lgjt zue_=4oLk?*QwP_a@%N9fIo>dHRj-rOx-~mh4n=WqoUy2cK$%LR_v8;lj)?Ai3Te;9R zpd>_)ljUyI^kJ5rcB(k~#Y0C<+{Y^yO{GdTN`aw!{Nk%tBl&H=_7{m4E{lkg1Kz`( zwm6lv8ih%aV%xh64#2kR!NW!UXEdc$iJl0!7#VIn=F>2+{^a5^Je6vDlXom@$7)A} z-!JLKFGvGzu-R_@nLK9u)ZxlfjNuh)w8uhqSoiSG@1RiLLtNRO0(cNIx+X0f(vViG+tz3dW3HRjINPqMNIQx_vcb<4D@}GtornWw=(C->;a_4Zfq~9Z_MFjQzCI!fy zc$_~I5^=9}y8P~Y7U85~OWdL?5j9`4OX8&(4<5O}-ZvqV)D8^_q!HpkVO9i6(u(CL zCUNY0mAG_DA8wl#GxluodR+=W%_GVYUm@Kd7F@p27hKfEA_9kDHi0o#n7wLp2rH5s zRV^-&$ZeCxfMjlHGL%T>nV6YPQ3o8pxs8BmO5J$(V_8Wi^`9IqME4_FQT)w2Gp^-%$kl3NO-+rz1CK}2Il*dkfoaG) zuZ^=lm}!}G4LA*`Or45n5QcJJR^E9(A9fH-uH->D#8IqoEnm;=W|O;1_6SiAUL)wf z8@K;IeEb^fb`n@Fek{V;f>W^|e#`ApNGL*$gAc|~IrOn_&_+hruj}+IYkv1> zx&%+Lr~!JXnb0DY+Poh; zA#%?SqD(lxZ%L!I*F8X=0>wlPqo?P5G{E1m1mHCD3%vCI101L%;%9JiQdB6}?0fbW zSF)e97y>tp?1rn7GaW5QW80|3DK;*aH*6tXu^(EQsc9`f*ROU#l7Ave&2&}uLy$Ui z=*}73nUuu{|CBYh>Ste5yKE66R4W@GD`0#g(_5ZuYG&5;l2l9YCCPiZr*1Rc@#t>_ z^OwE>AxmjO%^8{Jocxc%mIrR`eHrwBd4@y~TiI>bs!At&zG4(Aw7mVOY|@KC9W^v7 zI@_G4AOpt;-eyAG3$ndb>`S|JPt1O}B+3vXR4J~{q2AtloRN2xEg6RL>-$$15L1f- zbzz9o9)p7!0FPTD2w5TjlQnZju;@hHPjZ_nyhGbsB)23Fel)pV@@c}cJ!tT{j36nPvL z-waq&kUj7UXVsq28X{{2ea2btm(h#nS$+2e2A@*8guPAukn?@=6ykb)As z5T)r*E3owCCTl5265CO$_-Olf#J&2!=U+ZJC|Ow*$bV-P8rkoRrmIM_UqeLc^m$rX zh)CWSF|Z=0@{{xdfF&&u@?CG|W3#ti8jhq#Y2XY+-fFyI-*i;PW-2v<&8{#>E+CPw zgF(QJWA`6A!3a5LY2)9KGwqqLq@i! z%{RM{cHb`cO6V`#59U)ANo8BeGv)^8!C*t6O*qwn7ZsvMZ?lN!S+2I{Y4ycSWQu&Y zUK_4PdJdBA%uk_BICmj8ocIqra180DU>>kZ8}Y=pXm-7koKF<}DLq743MZK~-!+0% z3^_t_Kwj&++CFZp0L78^(+okp*70Uc`IF4#zGUl{FL2;;UxwuB>N<+A*)|&EX;yY7 zCUZtho3z$B5ijUuZ~qzl17{W)##10OLhO$b{}f?pGGghh+?SN}p*-2U!qh5cf4L0X zkggbpL~fNsr4FU6I;;5HGPQL#0M1@y^g+r5-rc0W!0!%j`MU3sKcBCJUOg59$`j*t zB(%wfG0~~4aKB&mCm!paQsr+Am@aFJ)n}s5x zk@+KGZepUtm=>F>2y`s~o+E%r^PP@sFO^wby?LpgNRt75Gn89(mZXq4+dL=U!|Q&AZ|A+?u9PJZ8#)5Anme;X4w7n+l!3{;Zh7W z@WSR+J=Z`3=V$2;>Ns*Lf}_t#`aTY<463ZFz|-R2G}64%>@P4M)jT&!tfpMf8fE0y zkKx;CPoJ`l@18Opqva~g(D#aGY zRa8JtL5!&-&^P|z` zQ_)@G9MJl_EKmB1s*#}ZLKqYo5lHBWiFkvI&;-$2Ydy@jEAirshXvB5<>f>~K|Ze` zuyDA0TZ};Z_(nGWV}{pA(-=my!#vYRzf4z$qv^^LKKpqsH!^6OzK&HJ7es+Gh^1Sx z@fp1n7PJka&jPe@42#!#oz-_`2nerKyrltXs*A0`@l*I5W)Q*n42rLuR^()cIvPE%pF*Ucx*2L z2k%ylN+nU@y0>|1Szi)%>8R1z_w#a27^kN5rJz}c7>I0}*6FXRmIYU^%%N)lC5>O9 z0}gW&X^E`6UGm|G6gu5*yFKwed5-JN-fEv8A!0E#62E>ghzJ1xZ*ZVlNQZB0zI_b- z{te=4;G>#JWu%7&#~Caf+1%ZB<_T7(Cn*Z`Gu_Yfqe0sZSDuVNrEFjF@bdDTiKJ`4 z;o$1fj4D3ylonixjAU|wi{b)muFY5_&>@#<>ZwFd9&B%+Dr8H``l* zTFr!KE{h8>{st!Cop(H@KYcp_%oU{_$$Q@QK(YaO_(fVyj@e7vx?&=aS9#LXt3jXe z*mkbPokE_(%Gi@~h?<4%r)7)P>acHJMx-`FH^T!qX8JUrp?ypsS?4RJZr9 zV<_M$5mZD}%hjlAQjhTIQ3ng8#1c_Th)CUYEwel7=4U=)9L9to7oZNzUy-It;9vd; zCRVs$=^}(lP#?LQQ2$X7JQJJa$B$q960tnp8#;)%82YS&f?+x z8?57?`ZnO-Ng`i#IG!Ozq21MOHF+*%#jWhUi!si|S3lV9z&yaz=X?eQHRtQ5tHz{v!=qFwXB}U-)-|R%i!HzUJa9fK+0CVvm7EYUn<+fs6Mz5z+y@2aAFH4I zJhG?6_to(619d)enmfE#35~v38?^bw%ML8PXD~AJ8<>75?{cQ1(=0m;ak-897>Z3# zd{FU3%t(gZl}}>vUqu>roHrc?Qb&B-Q|ue8teTGu%iHPijR{lz2ta)5C2tueV=PQD zo#1Ek;mE>8>hOrSSCV}*t{0A847C0Iee;OlTK%RgTG|{-#J*g^G`>c(@|Tjmz0(uV z+(tX}_;50QyvGHM2)a+0TuyI{>Ma-v5{>D@7xI>huQ9tHGSU;sd;hF1QVBj3tew9L zdx?^(QuK+w;=xHg#YO8&N`dbN{BmFJ4IUgt$@v zUqp76DU@X|IXy%vh?q&&$Z(QL*A8!-gEOv-6Yv~ItM6hq-#VXoK3OtB{^B74sjmc- ztz|fl{8O1K?=mVL8HN)=UcAG{K^Bg+Csi7jCK-t(pj{FE7e;VYiVZq8cKc;2_M28N zM|?6ZK%p2PUI^DuZ)@RglN;5tF8TI{i*pPl&6FqjXd zcJ+MEDt!22D*LxQ%$qT`AX|rx${Ee?uz@)+SO%fIaGQEQ6P zz!}03VfAvW=tyItba|E%LiOw&199aMPu?Z&Iv#qkuZBrs1f&5Kg!%ZH&|h9RaQwSl zYMADak|C|E-L6edg~|vgG3z#NGb9~!Wy{l%i#yWRTgl1EDbu3|cTuR~6xM8~yoGn? z7yPNpunBLv6fp{+ps(iLRRWH6$ukh*AfO)i$zD|v=tMn(w^4o*gLc_(3y<;3XUMm8 zE;Ol7Dz_N~@6<0p3hQ%OaDl4}d!*gT+%kO3mAivf`h_Nr zU|R;Xq%3q4@}WTPXIW8@_N3W$jV#$Snvj68(3ElU6~z zn2ycS{o@?fD0>8M9ykOf%vpC^)xX<<)>#=EoWNa+UhOvizFT`kOCRS`?~W5m-qaps zB0y5#rGj;~LAjKL5acqr5Er?5Pp9v{diE0TxBDoi{o=uIHL2nkYz8{nA(R`|-+FS|;qnIQ&% zG7MaVc#CY363NY+vV-qmzeyy4XHVc5VN-hfw>elq?oQ;~PO(uc8pmf|(;&fk2s*S+ z4+>&GRks$6QhLUZdcf1IDb9CqVKMZp(_hMF($q2xAV*2e(%5Ou_If%;))c{`4>*DF z+Lvry3_CfcTwgf?-Siu|-OhQx>Tyay0NhbT&Vx2FczhM@6HqWYP( zTDX^Nl7Uvkv;sI+4#Q5$e`dhvF2oJ_p~EaumkRS!5@E7##_vVm-pFzGYl5)2ByX14 z6@zl`66y+k^&7U8sRHTzv&Wi;)rRPn4O-lU>SP!q2%Cbj7c2G;-;q-R(Iqj5@LTrj z1Kw4jTffi^3ULO{65vUCyXk;yM;|yAhd61Ky3$d`F`tQ)(Cg>pwU3Drw9{rR1z!fP zO1;uIN672W(=X7ag>+zrXKh;zsa=q%s@@IHqt%`>tzf*DMwxKqp6K8sxS$s}na?=J zo(np~;+}&hvIv~tsa?{7UueC97m=}^fd-TpY0NUG36`(7b4s@gUR}XEjCMdJ@~MEn zDAkP~q)=;ASzCU2TOz!++i@ozg+te&8S&1@Xayzxl4yq6;A$dO*=<5{41L(0u2X36 zP>zymwYlGAj8$*LSG6bojBq-EUTn~(L>SFt(b1ifiy4sR&G+S1>J*D)SSj3~m?8sv zEh#)^q8KC4K(IMP1zKQV02jg_m`(L2Fu`y`=n}soJrtE9f1W<93w-VrlV?r&ELb3$ zY8O2~940FU%OulY_joB_f1;iiGWZaJiKn9Zw1bo5AeKBys7`21BxE|dV(-ZTs<-F0 zbN`dFe=zAJlVxq`CLhp<|2uW+((dtnxQ>N&px``N@rO&cyXE{`ejG7)sv`$TB^ekL z@>g&Gg6oUh7daX2^YewR_g2pT+8?i$7O5qc~%+s(OTAKkJb&EH*kJ8+*+XBvjW9gs5{d63YWHH(Drd8$Qvt2 zL+g*ZR32N0O46=NtP0ojcEgkLGVwt9MspWk*OY-CcnO6DK8`aqWm+%@jz9a|2wlpRiJ+lAW(Z>TWn+{z&AtNH!5)ZMQsmXr=l zpBxdC3j_84_Kx+)byQu2%_Fnq$Wn|2CL=p)E5&+-%n=bg-?b=pT|*56pMM+)UMl(}$560;&Po zD93#+S5?|&58lAr5(A3hjl;^;$!w)kOrXzr{^1ufQ*aP(fF%Ty!Ry{SxVC(e)}U~z zopI1={SteYW^pR8VXcsNv3~0u)WZXp1jtL|QjkPh|k3 zt_HwQu`$F6?N-;5I8@!~Hc%Px$@t?o7{p6&LV1tsjGB!YvnNbCbxZg9#?|6gq}mRvrE z%On43U&L`9a4CFw?=Z@>4S7v1IMpk`Tmc`eyr65mf$?K0NLcNB!bSrPYd=L@}+sU*aZ!_NWwW) zhM7iqmjW5gF|b>o(HWVwp0(&OUe_F3ZCXY2TiT-F=P<`u&>j@_Q44e`_1e**4WX?! zaU#6sN=B!~%+U<@yOTy3kzWYr*4Iwv^mk-|kwrM&fEgXa;0SOz>(aZQK%Z@e0~Q=P zoiu9IbGc@(K;DsV_Trr1X1<9<3dQUjGi2auz2L1ZXJl9U*#R`~!4miolO|!A_fue& z%H`!?gN)Y-mJXexWMk^!#xvSXgH;w(zJf^$tvUsPJr^U_R4c{|5%CsEtTFgKg zbl=T=gw4VSo9^*V4Z3KB=8+`z*?3ud>P+>xxnp7u8HKa7i7BRAZbenLnxn2*QsI4m z^4rE~nH3$P?9*|AVtDb$g>=7Y$wmnm`dtoAz%b1l9Jvv$CTj^E_Wl69X3ni(z&Ii%W0-t!;*}xr`=N9n% z8v10=pl|nbQG-usi)N|x1%{My>IuOpfX}Cbsox4v(#S~U3c@J)^Cai0nvNFMQuC(i z_}L6#$({37xCQ4J2s^b)=xF7OL7Ve1jM@yr9;|iuajI+S$arPZBMi-6Zdq2E0FiS{ zGxoFdhQnMS=ha`ORN(67K(eNLL_xi?3U)A zWZLZQcx22$fM~}D->feAiZ1}(@Dn-t3{oX*);@4t?{-X;2HHmIf;=D!a(?@pl-_q? zaTiQCY=!yx#o2+B4$bqjv3{q5e~#uopbLFCw}4~+b0aWy9IT^|Ht}re>@S|1jK4X$ z6+~(qYuKwN9tf+7>=(4l^hEoB?%=C!HMx6u9HCdJF zIhO9nP_$THbxOl!pIn>gB=DL#S|?Kp zcttMgW(W^KmUP75cO%4(JV*9dh#V%qTE22cAXP#7fwpVceXX(EzwUZ<)XECPP*P)b z$8X#$vGo&yiz=XTDq|q zZlRJlQ`T_&x!6&B?81C4HpUKiX+@ep8!bM6rkT!dB7mN9_~3z-Ef}@xJvFdqo=kA+ z5iImTfCx>714M?7>2}vhS06IGjJKq3-UlFhHhUShCekJ^BkMkky4&jpiG{=8{oBDG zQx87V7J~K30g|6)OPB(tY{i^bEu`{!f^;@SuQ^F#h-W@y1WdtZiHtN;E!|;-nPtK} z;!d4`!WD{#*|mx9qcRlC=J#S%17}E~D7cAnEIs zZ2qH4Uyr>+3I|$hH>zB})BNP7&3zb~BYcw7ImPI*R-8QA6NN9MjE#g#^G!HBdVFW39&ZgHRNA2RBXv!3p`6jMvSi2R+_v4i7qvF^ zjOhmQ2TOsqPnrpo6K7Q_!60oRgtO*8%cz?5-PI&RY{}82cD_*yenOBxmqNCE5s6)V zdx>8hzbk@wlgWa$lbE)c5H4m7az~JMb3-Yf9J~!ln7^1Tb0DZK1F2?^K8W5V#Mj^b zCGp@w5KS897lH~GNWD;;$k*G zN9RVnDquO3YEKZ=mONHI=FcK?hCX>AZq5@7qj< zq{vvnT~t927bz|#$dY~AFuMhs)tI8Wd_Eg+*U+9Ft-XK;jHoOyll?h7WCzYE@g~po z9RtG~@77UI9EYPUtyC&8(Xt#nduHyXi&DIxH1Eo$9Iq1>V==w)C6IppG>iP#_I#(- zi)m-QJPLU~?e`0QKJZ72Or>Wf@#6;awCY(mK?X`rFb9hF#PK%alOI8vU!7o!wGEwx zZME6zyLn&Hs;}cI^`8oshyIKz?NE*_MXlAxju;oVYJS*a_j8<1k^im~10B?#d3v%0 zzS}XRVjF99jqZiY3y{vZVjUU2SY^2H^c6$2nWytCH){v1Mq3aU7cUBvHLnO_K6D5z${uSsWQs7l=9!vw-4yC{~0Cx8Fe4 zDq8!1K)vUhmhz`?nL;MNih9Oc9UZ&C z6~@7Cwk-`)hJZagJ-1&n4SrwYwM=*(?)=qBB^$fFYCML;t?YS|XkV+!&}zHywfa*C zW4_XjlR?%H?_F*t2}$$*0IkB1ZI=B?`-hh=Fh0;OJ$Rv-a%nJZd3)AQ2SMn2lX^E+IyZfr^8y3J`d>)fvZ} zO=QH*$|_(X7gRS}33^?8Cx#>vZiZO-@)G2>pv{QD*+;^G+~41C`C2?a)PYlYwTVKH z!GiV+PQ1dGGJxRFww?xdC`7g2erbqCC<|oQt3`FTWRB&e-N&))SOe^T=epW{wi!O; z7J1BUtVP#m8)LuO2kW%{ABT{~4t=r+uOZ;+AND(#M7y678E6&2YbX8NGRKZ@aEE#3 zQAqU`x;0cea&F);DFSS_0*xMiWgj&c49|gVSFc>OR|*!pome{LIq_eYnDD-5@KQ48 znbpGPMrYSAr}X}w_R-~NmtP0vbJDguKklBC zQxEv4gQ1ee&AoDrPlG$Hv#4>c0+gtavmeYkwD>VvIOn!1V zBo@UEa+yf~oKXMRxKwb}{q4y^TC=b~Q0=7*Jtj`iy0^hn?K#f;ZtM2A=kKh<@qG7I zyH;8nC-Nj#5*s9dPqmE?rl=Mf@={Vyix0Ey=lH!>AKozo%`lZv}CRSvyQp@KcAiBJ}xBcsIx`}SyZFbwK z*10=TFMX|D+;l$jfS#*AHELn-AQ6>Q58!k^&lP?Qxvc#NZVf%Gp+ME4=^O^EpF>=j zeO{OETkw~To|T*o&peLORVqex5#G--$%=Rp)pg_NjpC&y^5Qxso(~~zp>+p_^y&Fw z*vJ)va!ZkmWv>OLpX=~E}%d##^Z11}#yjaz#hdl}kc!Y%Z)`!Mihi@cE!!E--%wRkrEccwfFS&Z2)kp#lBH40RfW$a^m?H4DZcW1F>^7az9yT=I05`;m^pBw9f%Fiy_x0WJ7Oq%C; zU}8bS?&F;9(h93d3B3Zt8ek>Xu#UA*M_ZLCWpl0Mh!A!uv45rR6j6xG_yB!6IV@3D zV6nIIGg>#tk4^^01}W~Y)=LIpa!e3;jgTePh+aG2$A;@qOtC4vk(J814&O!XA2K~n zyQ*9;Xn%|wE>-GE;}#8s63mJKFCVOkdhL?Cb-E>Yji~n}UODAm8P<#UV^bGOT-(ZU z+pjZ2-K4z>Qy0ZD?z5LwkrO7Lxw_Uy^wfYL|3-{c+`f`V9BVXV=Tp7q1ksDWqpO!p zsr;PfxiiuwYg$$!np~RAnFBS^N#XWvV(5un;1`~pJ2u7lU zl&PN3c*Ht?l11ycmz8v|!jyQhl2Pcdwo9UiOnSD@0M6T+B}X`kdKPyoJl#JX!Y`Hg zpJbQ_@)@+<*2#i%&Zo2JGmM7WGt4q*f<5CISiv*9cf5_S@p*vrq@4f+(a+}3qT%ac z7nP>ny5Fo8fXbZ}&$SE+FbX(}0{*@y^58gi!=BqaEXuR-pDPPrx<((%LJ||;4;Rli zrW+o=i5Da-N=l}WA*VqayYF=WHt$2TVLt7n*-m!=hx>Kk$<7+V=mL2WTau*X*e?~H zjRX3EFW>5iw~*O@Vm~J2?}upttV1awsdC;$kdc=A!q{9@hWig&bI0CKw#fbT>W<{o zzkED6=;HWc=qrahiUu|uhR8M2u;>5^CBHQDg2M<-6q{UQ1ADJodw$&Y99Q1HAF^>jH@a~t)u+P zgp1>bciE|H9Kvzz#JcFR3zr`PclvZF=e02aE3=EbBAQ7Ys|Xi?2h$wmCQe+U1UBicu%dSJWS zk1uWwaF(!l0R}ar&pZ{a-yYk80U-2E*b3aa7zp(sdvjy(O|JNQIq;<=0P6jBIsJPq z>~zir+@F=>pbmP}k8P#&2Ko2hfR&@?5#Z~Bg4*I)`asvoBI5X==62e%bYS#N zM42KaM$blWAlsq4wSP?J^?gm>c7kX`= zkwj@0V|ky5i5L3&K(%>b)4Nsr=cdas>nb@XA3^(dP_?mtg%K#Hvg_X2n36CLLshDz z@2D6-3m3Wi#34G=n*fR8?cHlR*IY&vPSG<0r5J9=QTCnJM~W!3KwnJw7zeNQC|aLG zPV*f(mqd?MQvWo;6H{iwHYuB<#gdp{841h+Wx<)F`yL8>|0F1blUQj;%uR?pV4gqp zT!kO}U7di}JbF35kbApqv17N&?^jg95{Z0NfbOxv1(okM(X3K@Jcbl(6os4z3!&U) zC6xvE>C(g5Fx%>>syLwwIvVHGuJl=0i~zs*_fpmq?!r*Rq2i? zUz5>~#6_?vFn*2Mdo^?aHg=H&6u62PPR1Q6oYQgc5QjG{V7ooULg;c&#z=n4$o2jf zISSSg87r(=Q#L%eM{466p{7fE6`$}c;9Aw7?z59q%sE#G&!i78@p&>hzJG5ZkrJ*)yiNoDA%hF7_xO|Gg)GOioTVsU%4nrwHi1I~uvUoY8dZYVeGVg%tCA zaUt91wmVD7XnsHAYyHeP*bfDPy1x_}cIDrGkuqd5r(YIzLwprME~)HHN(k`ykLBK> zQia;@)b;5X@3jNUAy=Re_^{i(W{b~@kNI*MMD~(KK!pClx2b(6?T$fsw)H*{1ww1S z{^Pn6 z398uV_!uw;^j<(Hi|G<`ucNrq@Gx$lEMDRji~1vQt#)B5djD=QL1doO)!mvF15x7m z@!iDPM8;8=x*S&V#EciKB$PRa~w^3a#7InS%ok)pakoHZw5Uz`Fd=4`q$uEO9{Ps1d z2lF)2nxK9M=hfX(t-+Va3|MYlGt^f)lNIUbHVUWNh5R&SGe1CF9HN=9d8pfNjr-n3 zy9iWDXXZlA(M$GY%xTO4jW;0t-Tbd3=#I)X76R_;eg=z{gCa)i_@YGRc<>x~{*&CX z3m4pKBOA`x`u-!2HxA-$*;-1R)x4>!;6v=W_q$=A2Yu$L$fnc3J3b4CFX3!=A5phY z`@9I1?DNLqYHr_I=#{Di!+s3EJj^r!U7i@U=mCv|aTVG@KX1+lciDpAYOszzOx+%< zL@J8k(RQ`_%yD`9)9~k;uOOgY*h<$xx7k0oAloUG?J+Vb4zY6^vkWO%ilB`Br$ZY6 zZ^Jodi3V}P6db!O@3xtp+ge|5ego*AF1juBGiQf`0P%Z$o_oI^vO&MAWVWt*6-mr6 z)b1qJ4CS#&)ss-5A8=$G6Fl{6nD7wf-3SF!Q9f*__xWsM236tF+;=hD zXZbJ&u)R+qF7LW1CN%TvbPXf93w7${F5rh*91pP{@|w<7Ca3u`zn}1MdNtwEi|>$) z9nwgFgYl3}&1~{;mpP=ElYg34G4%Gz7)NvLzlt->#vsd;H?xb#S<|HX1Cw3T5IsA+ zeichml9!P@ym<=0%0saZqBvAcKeLj!q9tq6sgoCcR zS&hc}o77fVXYk)es8EGZ-^_v^zT@mb0h|;nZD{IVFeq52%Ad3oJLs#wkxWb%`aX^H z2dg?OR`G^%yN^-B=Es?ujBP?v`$5r3n7S)6^es}Ldi^*sSry?fJy&ZDh;vDh=)v*8hc~Xpk(e zuJTgiPOQ~kUFc1s&GV-Ue)EbH2}-U#DLKnE+y)7=f2uBGI!wlz%LS4uzwV5oDa04u zu^PAV?vM14Pn4TBsuh|;yl0R{D3uk@i~Ekx;@O3<)2Z4d9}V{w0~9q@vBcmWuQCT zLLJk7{qRAi3(}~pTO5~n(Oe1Jj%Nn(^vS^WW$^P{_LyGlUY+!#dSUC9ayoMx>~Dgb z+B{ZYbRBh$LN`MdgD_jR1oE1S|BjpINfht=-tq2};Z1r-J4p;n0qIfSS()G+$7Obm zKF!H$|5@5J53ayffwR^|a4^!b_dnnser))ZfGdUEuaq#WoPHzwYSv}6f+s--w5gPPkYMY_l|q(J(b zsKUQHb_AOsr~j77YZ(knzP5qj6@RuxN(`ETF@!}I#bDarHs0bng_4S1{23`c(-?C8 z%yZk@FXZAEYQp~+R_W*-9sY4~P5{GVlgN&2URUK17dqJylpd<#Id-=pyOg-dw+;oJ$yNaf{bSB0FRv5-$4Y(Wa3^a>f^=lHFyuNT*h-s{I+Q) zlM%VzakcPq6loQUu$W@am9kj2Gw2)3b8SyGvWRZd{C8I<`)+;D<;DKM)|b?M!%)Cy zxSg|Sl*j!+8C2TE#{aSQ1yiO9S+ zJ>2o^eWcDRn*dQBz|f|TC!G~S?|tLE6hDG!b6Y&jY^^{cv6dRSU2ifv)IJ%$S_p!V zl=#4QD^P^T4zNax7@8^d)E5 z**k9n`!43a0j#Co*J~yH?uPGc4d8_x@h}b2V&2Whe|jG#s&FRv)!9F#e6XDwxrLoH z=Yr?J>qG3ay1Nx*S)DU&42q#YR)o6DKpHtZowq#N9^(gm`t`5JqMhA}269t+e3BZW zf0jkCFp`B&4EGzl@!*_9Lbnk{75hY=RN)nw#Erxv{_6UaqOGa>h6K4D)V~hCU~2Hv zdqvVf`>M+vPR!^b&Hn%gO|@{Ft{3ysDZx7HVJ)Qj+VDbp`U?MHpt0qb{oKR~N=H+1gVWN%@swrdFyP zQ@dx>Nvg)cLHE|`-j32}{Bg|WVO4z%z5F--3bG`@<-fZEP*VRiS4wKz5Wh?4`|-Go z&zyF=W0iseviLR=a(?CIYHx^2Ug z<{eB?Frm>cX-kiE#wo8C%+W0MTJTJ;*m0*&iY#mn4|w@gVF7=N$%1~!3Z^7OENmFVjun-Dfd4(7v4{7Y&dcD-_g=Q@N3~^$6Tu8J6MgA>n>E(N^?Bdy7zv1M*P+;o0&bOVbQ2iUq_CFkgR||hI z*1}lwOnIQq*cD&tldy@z3S?CSsCV0lXt7w;$&p}wnVCUk9S}D!7N)yW+!Q%~QTNrD zIErs}=Ngoi#5lcj!bdbphk+EGP3{ur7`}hJWZ$W1k{xlI{WdCoSsJpi1RQ^Iz#@qwSiy7T3ubLCDWsZ`TV zeyg-`;8FJgluYFRtxf9Fy$zrsL>K;wZRkKdTjLGel|P<6wkvPpU3%)(L|y*0Yy3Bn z62^|n!Vf}Lc4+hrX+A2z*FqX?`B+uwgYUb0=hVxdy4`p3vW}r`#a@>C`MjEmnJzej?mmO~)|#YKfhv_w*g!yDc2 zqTgI04a?lw~I?|5f{zyTyo@lfJn` zel`U?SZ5ZZ32cGzC)iYDgzILao>4;!CUYJXobn`fYDJy$%3ri-KkmsEniwSQiCP$l zQxXJ!EkL)VU!GYSBu~-f#I!fZO|r_-M#c>$e8PqrVW~73lrhPU6^gd2!#oaWipI4Y z{vJfcp!4|wB4;Inw;{Ch;D}7YLg#My^WU0PUd0oRs}s;X_!3lE2}~zC%GX>F9k64= zx@%t?no@pe8#A&GYdOxG5(m>{61bdPQ+@}sKDrkB(rUvIW&|JOjjuP&$vYj)8Pg=7|nI4TsNSZ+k4XYU&@= zfO#^bK^5N5rcRIKJYKvYR`bxS{F|c#4u%^P(f9Y3|5YYQ!ag=)dY^@V^ed#folq28 zZ@Dg#6y{aMzH$G&@5b#v#ztLnHXb;UuIX*n^cICa*xfBE^)bc1EPP4mu`c%O2+Kxw zdzZ-Px0dXy>ikb|bh=b;1#9@%yr~}NTBRy5JMWAzD07^z@F9p<3xpHlWM7#rbveIV zAM!-@18;TOK@xC^noV54so5m^QHP>UeEj~l@iLCi8Vi+5r_cMl1SP+F^?Ux$yaWUu=Dzs%Iys6Eysm{$oMLQ15beb>D@^$Ggujj{E*2tE~ zOqcSzH%uJ-K2O(d&u|FDP^jSCe8~O0+;6weVJJ5%A}=cLTVU3lyRvapw}#8t=NIa@ z3`Xkdb+^3+xz;-_3Ui<<{#pG8IjQ{zr3PizPo7Wq{VxwZSir!KJ&QnoNUz!bR|Eut zfE5-3H=+`Z3oLLsiuo*z=cDL|`c?4KQns*KakY9||2!Gd(Uk}(s&+AREzz}3DzcC9 z8`$n%!sYG*lMOXr@+ozG#>JPd-^~x5-wL}&H?-aGL-Bl=wZG?MIr@>RD^jyx@RZM`g4Z(eaix&)&$p&>P3ndV0xgo&P?{!^_!fmC40Ti|h$$`Jltf-ogwrlR07`q-M4^J3uAM@P$UZY|BdLA*rwJM89c8@$?t*o<-mX95Z zp|B-d_nl0-NQ`Y&Oj^5dkFung#UKIf4ktnUGU#7_ + + + + + + + + + + + + diff --git a/website/static/img/agora_studio.png b/website/static/img/agora_studio.png new file mode 100644 index 0000000000000000000000000000000000000000..48b07b877504267eb302aa7ebf9e0b49540ed686 GIT binary patch literal 133985 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�Hb3N@C8UbRsXio1UqFP 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%0A%VsM|U#I>^O_ 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 literal 80331 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 z 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 CA{hw) diff --git a/website/static/img/methodmadness.png b/website/static/img/methodmadness.png new file mode 100644 index 0000000000000000000000000000000000000000..9dd0681d4a029b11c8db74154934333db42e1851 GIT binary patch literal 8650 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 literal 0 HcmV?d00001 diff --git a/website/static/img/noghost.png b/website/static/img/noghost.png new file mode 100644 index 0000000000000000000000000000000000000000..febaedcae8212926ae8d7eb3705c8304531f8375 GIT binary patch literal 22435 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 D(4BF@ literal 0 HcmV?d00001 diff --git a/website/static/img/staticvfx.png b/website/static/img/staticvfx.png new file mode 100644 index 0000000000000000000000000000000000000000..41efd7f12005db1c67e168d1447eaa0596603e1b GIT binary patch literal 12912 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 Date: Wed, 22 Jun 2022 17:11:34 +0200 Subject: [PATCH 0785/1227] fix lucan logo name --- ...o_On_White-HR.png => lucan_Logo_On_White-HR.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename website/static/img/{Logo_On_White-HR.png => lucan_Logo_On_White-HR.png} (100%) diff --git a/website/static/img/Logo_On_White-HR.png b/website/static/img/lucan_Logo_On_White-HR.png similarity index 100% rename from website/static/img/Logo_On_White-HR.png rename to website/static/img/lucan_Logo_On_White-HR.png From a4b09cccc21c1ade1d170fc23cd112e8efc9f4e4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Jun 2022 17:39:30 +0200 Subject: [PATCH 0786/1227] use query functions in houdini --- openpype/hosts/houdini/api/lib.py | 35 ++++++++-------- openpype/hosts/houdini/api/usd.py | 7 ++-- .../houdini/plugins/create/create_hda.py | 24 +++++------ .../plugins/publish/collect_usd_bootstrap.py | 22 +++++----- .../plugins/publish/extract_usd_layered.py | 41 ++++++++++--------- .../validate_usd_shade_model_exists.py | 23 ++++------- 6 files changed, 73 insertions(+), 79 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 96ca019f8f..dd8a5ba473 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -4,6 +4,7 @@ from contextlib import contextmanager import six +from openpype.client import get_asset_by_name from openpype.api import get_asset from openpype.pipeline import legacy_io @@ -74,16 +75,13 @@ def generate_ids(nodes, asset_id=None): """ if asset_id is None: + project_name = legacy_io.active_project() + asset_name = legacy_io.Session["AVALON_ASSET"] # Get the asset ID from the database for the asset of current context - asset_data = legacy_io.find_one( - { - "type": "asset", - "name": legacy_io.Session["AVALON_ASSET"] - }, - projection={"_id": True} - ) - assert asset_data, "No current asset found in Session" - asset_id = asset_data['_id'] + asset_doc = get_asset_by_name(project_name, asset_name, fields=["_id"]) + + assert asset_doc, "No current asset found in Session" + asset_id = asset_doc['_id'] node_ids = [] for node in nodes: @@ -430,26 +428,29 @@ def maintained_selection(): def reset_framerange(): """Set frame range to current asset""" + project_name = legacy_io.active_project() asset_name = legacy_io.Session["AVALON_ASSET"] - asset = legacy_io.find_one({"name": asset_name, "type": "asset"}) + # Get the asset ID from the database for the asset of current context + asset_doc = get_asset_by_name(project_name, asset_name) + asset_data = asset_doc["data"] - frame_start = asset["data"].get("frameStart") - frame_end = asset["data"].get("frameEnd") + frame_start = asset_data.get("frameStart") + frame_end = asset_data.get("frameEnd") # Backwards compatibility if frame_start is None or frame_end is None: - frame_start = asset["data"].get("edit_in") - frame_end = asset["data"].get("edit_out") + frame_start = asset_data.get("edit_in") + frame_end = asset_data.get("edit_out") if frame_start is None or frame_end is None: log.warning("No edit information found for %s" % asset_name) return - handles = asset["data"].get("handles") or 0 - handle_start = asset["data"].get("handleStart") + handles = asset_data.get("handles") or 0 + handle_start = asset_data.get("handleStart") if handle_start is None: handle_start = handles - handle_end = asset["data"].get("handleEnd") + handle_end = asset_data.get("handleEnd") if handle_end is None: handle_end = handles diff --git a/openpype/hosts/houdini/api/usd.py b/openpype/hosts/houdini/api/usd.py index e9991e38ec..4f4a3d8e6f 100644 --- a/openpype/hosts/houdini/api/usd.py +++ b/openpype/hosts/houdini/api/usd.py @@ -6,6 +6,7 @@ import logging from Qt import QtWidgets, QtCore, QtGui from openpype import style +from openpype.client import get_asset_by_name from openpype.pipeline import legacy_io from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget @@ -46,10 +47,8 @@ class SelectAssetDialog(QtWidgets.QWidget): select_id = None name = self._parm.eval() if name: - db_asset = legacy_io.find_one( - {"name": name, "type": "asset"}, - {"_id": True} - ) + project_name = legacy_io.active_project() + db_asset = get_asset_by_name(project_name, name, fields=["_id"]) if db_asset: select_id = db_asset["_id"] diff --git a/openpype/hosts/houdini/plugins/create/create_hda.py b/openpype/hosts/houdini/plugins/create/create_hda.py index 5fc78c7539..d15d5bcd29 100644 --- a/openpype/hosts/houdini/plugins/create/create_hda.py +++ b/openpype/hosts/houdini/plugins/create/create_hda.py @@ -1,6 +1,10 @@ # -*- coding: utf-8 -*- import hou +from openpye.client import ( + get_asset_by_name, + get_subsets, +) from openpype.pipeline import legacy_io from openpype.hosts.houdini.api import lib from openpype.hosts.houdini.api import plugin @@ -23,20 +27,16 @@ class CreateHDA(plugin.Creator): # type: (str) -> bool """Check if existing subset name versions already exists.""" # Get all subsets of the current asset - asset_id = legacy_io.find_one( - {"name": self.data["asset"], "type": "asset"}, - projection={"_id": True} - )['_id'] - subset_docs = legacy_io.find( - { - "type": "subset", - "parent": asset_id - }, - {"name": 1} + project_name = legacy_io.active_project() + asset_doc = get_asset_by_name( + project_name, self.data["asset"], fields=["_id"] + ) + subset_docs = get_subsets( + project_name, asset_ids=[asset_doc["_id"]], fields=["name"] ) - existing_subset_names = set(subset_docs.distinct("name")) existing_subset_names_low = { - _name.lower() for _name in existing_subset_names + subset_doc["name"].lower() + for subset_doc in subset_docs } return subset_name.lower() in existing_subset_names_low diff --git a/openpype/hosts/houdini/plugins/publish/collect_usd_bootstrap.py b/openpype/hosts/houdini/plugins/publish/collect_usd_bootstrap.py index 3f0d10e0ba..cf8d61cda3 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_usd_bootstrap.py +++ b/openpype/hosts/houdini/plugins/publish/collect_usd_bootstrap.py @@ -1,5 +1,6 @@ import pyblish.api +from openyppe.client import get_subset_by_name, get_asset_by_name from openpype.pipeline import legacy_io import openpype.lib.usdlib as usdlib @@ -50,10 +51,8 @@ class CollectUsdBootstrap(pyblish.api.InstancePlugin): self.log.debug("Add bootstrap for: %s" % bootstrap) - asset = legacy_io.find_one({ - "name": instance.data["asset"], - "type": "asset" - }) + project_name = legacy_io.active_project() + asset = get_asset_by_name(project_name, instance.data["asset"]) assert asset, "Asset must exist: %s" % asset # Check which are not about to be created and don't exist yet @@ -70,7 +69,7 @@ class CollectUsdBootstrap(pyblish.api.InstancePlugin): self.log.debug("Checking required bootstrap: %s" % required) for subset in required: - if self._subset_exists(instance, subset, asset): + if self._subset_exists(project_name, instance, subset, asset): continue self.log.debug( @@ -93,7 +92,7 @@ class CollectUsdBootstrap(pyblish.api.InstancePlugin): for key in ["asset"]: new.data[key] = instance.data[key] - def _subset_exists(self, instance, subset, asset): + def _subset_exists(self, project_name, instance, subset, asset): """Return whether subset exists in current context or in database.""" # Allow it to be created during this publish session context = instance.context @@ -106,9 +105,8 @@ class CollectUsdBootstrap(pyblish.api.InstancePlugin): # Or, if they already exist in the database we can # skip them too. - return bool( - legacy_io.find_one( - {"name": subset, "type": "subset", "parent": asset["_id"]}, - {"_id": True} - ) - ) + if get_subset_by_name( + project_name, subset, asset["_id"], fields=["_id"] + ): + return True + return False diff --git a/openpype/hosts/houdini/plugins/publish/extract_usd_layered.py b/openpype/hosts/houdini/plugins/publish/extract_usd_layered.py index bfcd93c1cb..80919c023b 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_usd_layered.py +++ b/openpype/hosts/houdini/plugins/publish/extract_usd_layered.py @@ -7,6 +7,12 @@ from collections import deque import pyblish.api import openpype.api +from openpype.client import ( + get_asset_by_name, + get_subset_by_name, + get_last_version_by_subset_id, + get_representation_by_name, +) from openpype.pipeline import ( get_representation_path, legacy_io, @@ -244,11 +250,14 @@ class ExtractUSDLayered(openpype.api.Extractor): # Set up the dependency for publish if they have new content # compared to previous publishes + project_name = legacy_io.active_project() for dependency in active_dependencies: dependency_fname = dependency.data["usdFilename"] filepath = os.path.join(staging_dir, dependency_fname) - similar = self._compare_with_latest_publish(dependency, filepath) + similar = self._compare_with_latest_publish( + project_name, dependency, filepath + ) if similar: # Deactivate this dependency self.log.debug( @@ -268,7 +277,7 @@ class ExtractUSDLayered(openpype.api.Extractor): instance.data["files"] = [] instance.data["files"].append(fname) - def _compare_with_latest_publish(self, dependency, new_file): + def _compare_with_latest_publish(self, project_name, dependency, new_file): import filecmp _, ext = os.path.splitext(new_file) @@ -276,35 +285,29 @@ class ExtractUSDLayered(openpype.api.Extractor): # Compare this dependency with the latest published version # to detect whether we should make this into a new publish # version. If not, skip it. - asset = legacy_io.find_one( - {"name": dependency.data["asset"], "type": "asset"} + asset = get_asset_by_name( + project_name, dependency.data["asset"], fields=["_id"] ) - subset = legacy_io.find_one( - { - "name": dependency.data["subset"], - "type": "subset", - "parent": asset["_id"], - } + subset = get_subset_by_name( + project_name, + dependency.data["subset"], + asset["_id"], + fields=["_id"] ) if not subset: # Subset doesn't exist yet. Definitely new file self.log.debug("No existing subset..") return False - version = legacy_io.find_one( - {"type": "version", "parent": subset["_id"], }, - sort=[("name", -1)] + version = get_last_version_by_subset_id( + project_name, subset["_id"], fields=["_id"] ) if not version: self.log.debug("No existing version..") return False - representation = legacy_io.find_one( - { - "name": ext.lstrip("."), - "type": "representation", - "parent": version["_id"], - } + representation = get_representation_by_name( + project_name, ext.lstrip("."), version["_id"] ) if not representation: self.log.debug("No existing representation..") diff --git a/openpype/hosts/houdini/plugins/publish/validate_usd_shade_model_exists.py b/openpype/hosts/houdini/plugins/publish/validate_usd_shade_model_exists.py index 44719ae488..b979b87d84 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_usd_shade_model_exists.py +++ b/openpype/hosts/houdini/plugins/publish/validate_usd_shade_model_exists.py @@ -2,6 +2,7 @@ import re import pyblish.api +from openpype.client import get_subset_by_name import openpype.api from openpype.pipeline import legacy_io @@ -15,31 +16,23 @@ class ValidateUSDShadeModelExists(pyblish.api.InstancePlugin): label = "USD Shade model exists" def process(self, instance): - - asset = instance.data["asset"] + project_name = legacy_io.active_project() + asset_name = instance.data["asset"] subset = instance.data["subset"] # Assume shading variation starts after a dot separator shade_subset = subset.split(".", 1)[0] model_subset = re.sub("^usdShade", "usdModel", shade_subset) - asset_doc = legacy_io.find_one( - {"name": asset, "type": "asset"}, - {"_id": True} - ) + asset_doc = instance.data.get("assetEntity") if not asset_doc: - raise RuntimeError("Asset does not exist: %s" % asset) + raise RuntimeError("Asset document is not filled on instance.") - subset_doc = legacy_io.find_one( - { - "name": model_subset, - "type": "subset", - "parent": asset_doc["_id"], - }, - {"_id": True} + subset_doc = get_subset_by_name( + project_name, model_subset, asset_doc["_id"], fields=["_id"] ) if not subset_doc: raise RuntimeError( "USD Model subset not found: " - "%s (%s)" % (model_subset, asset) + "%s (%s)" % (model_subset, asset_name) ) From 796348d4afc38a29eac73c97484d516eb5ba774b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Jun 2022 18:05:37 +0200 Subject: [PATCH 0787/1227] renamed 'HostImplementation' to 'HostBase' and `MayaHostImplementation' to 'MayaHost' --- openpype/host/__init__.py | 4 ++-- openpype/host/host.py | 6 +++--- openpype/hosts/maya/api/__init__.py | 4 ++-- openpype/hosts/maya/api/pipeline.py | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/openpype/host/__init__.py b/openpype/host/__init__.py index fcc9372a99..84a2fa930a 100644 --- a/openpype/host/__init__.py +++ b/openpype/host/__init__.py @@ -1,12 +1,12 @@ from .host import ( - HostImplementation, + HostBase, IWorkfileHost, ILoadHost, INewPublisher, ) __all__ = ( - "HostImplementation", + "HostBase", "IWorkfileHost", "ILoadHost", "INewPublisher", diff --git a/openpype/host/host.py b/openpype/host/host.py index 302c181598..2a59daf473 100644 --- a/openpype/host/host.py +++ b/openpype/host/host.py @@ -16,7 +16,7 @@ class MissingMethodsError(ValueError): @six.add_metaclass(ABCMeta) -class HostImplementation(object): +class HostBase(object): """Base of host implementation class. Host is pipeline implementation of DCC application. This class should help @@ -175,7 +175,7 @@ class ILoadHost: QUESTIONS - Is list container dependency of host or load plugins? - - Should this be directly in HostImplementation? + - Should this be directly in HostBase? - how to find out if referencing is available? - do we need to know that? """ @@ -369,7 +369,7 @@ class INewPublisher: but also some global data. At this moment are data related only to context publish plugins but that can extend in future. - HostImplementation does not have to inherit from this interface just have + HostBase does not have to inherit from this interface just have to imlement mentioned all listed methods. """ diff --git a/openpype/hosts/maya/api/__init__.py b/openpype/hosts/maya/api/__init__.py index 6c28c59580..a6c5f50e1a 100644 --- a/openpype/hosts/maya/api/__init__.py +++ b/openpype/hosts/maya/api/__init__.py @@ -9,7 +9,7 @@ from .pipeline import ( ls, containerise, - MayaHostImplementation, + MayaHost, ) from .plugin import ( Creator, @@ -44,7 +44,7 @@ __all__ = [ "ls", "containerise", - "MayaHostImplementation", + "MayaHost", "Creator", "Loader", diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index f68bed9338..bfb9b289e0 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -9,7 +9,7 @@ import maya.api.OpenMaya as om import pyblish.api from openpype.settings import get_project_settings -from openpype.host import HostImplementation, IWorkfileHost, ILoadHost +from openpype.host import HostBase, IWorkfileHost, ILoadHost import openpype.hosts.maya from openpype.tools.utils import host_tools from openpype.lib import ( @@ -51,11 +51,11 @@ INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") AVALON_CONTAINERS = ":AVALON_CONTAINERS" -class MayaHostImplementation(HostImplementation, IWorkfileHost, ILoadHost): +class MayaHost(HostBase, IWorkfileHost, ILoadHost): name = "maya" def __init__(self): - super(MayaHostImplementation, self).__init__() + super(MayaHost, self).__init__() self._op_events = {} def install(self): From 5b037244fe4e43c7a738e9fe2ace5c6f1883e72e Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 23 Jun 2022 11:36:22 +0300 Subject: [PATCH 0788/1227] Append capture schema. --- .../schemas/schema_maya_capture.json | 71 ++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index ace9fc22da..f8dba0be4b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -238,14 +238,83 @@ "key": "ssaoEnable", "label": "Screen Space Ambient Occlusion" }, + { + "type": "number", + "key": "ssaoAmount", + "label": "SSAO Amount" + }, + { + "type": "number", + "key": "ssaoRadius", + "label": "SSAO Radius" + }, + { + "type": "number", + "key": "ssaoFilterRadius", + "label": "SSAO Filter Radius" + }, + { + "type": "number", + "key": "ssaoSamples", + "label": "SSAO Samples", + "decimal": 0, + "minimum": 8, + "maximum": 32 + }, { "type": "splitter" }, { "type": "boolean", - "key": "fogging", + "key": "hwFogEnable", "label": "Enable Hardware Fog" }, + { + "type": "enum", + "key": "hwFogFalloff", + "label": "Hardware Falloff", + "enum_items": [ + { "0": "Linear"}, + { "1": "Exponential"}, + { "2": "Exponential Squared"} + ] + }, + { + "type": "number", + "key": "hwFogStart", + "label": "Fog Start" + }, + { + "type": "number", + "key": "hwFogEnd", + "label": "Fog End" + }, + { + "type": "number", + "key": "hwFogAlpha", + "label": "Enable Fog Alpha" + }, + { + "type": "splitter" + }, + { + "type": "boolean", + "key": "motionBlurEnable", + "label": "Enable Motion Blur" + }, + { + "type": "number", + "key": "motionBlurSampleCount", + "label": "Motion Blur Sample Count", + "decimal": 0, + "minimum": 8, + "maximum": 32 + }, + { + "type": "number", + "key": "motionBlurShutterOpenFraction", + "label": "Shutter Open Fraction" + }, { "type": "splitter" }, From 4a521ec081e22b226655f49a41a38dbe31ef8f49 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 23 Jun 2022 11:39:23 +0300 Subject: [PATCH 0789/1227] Adjust --- .../settings/defaults/project_settings/maya.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 8494989556..437adbc1f0 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -501,7 +501,18 @@ "textures": true, "twoSidedLighting": true, "ssaoEnable": true, - "fogging": true, + "ssaoAmount": 0, + "ssaoRadius": 0, + "ssaoFilterRadius": 0, + "ssaoSamples": 8, + "hwFogEnable": true, + "hwFogFalloff": "0", + "hwFogStart": 0, + "hwFogEnd": 0, + "hwFogAlpha": 0, + "motionBlurEnable": true, + "motionBlurSampleCount": 8, + "motionBlurShutterOpenFraction": 0, "cameras": false, "clipGhosts": false, "controlVertices": false, From ea6b098bb2f86d027815e8e685ff5932a85fcc73 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 23 Jun 2022 11:40:01 +0300 Subject: [PATCH 0790/1227] Adjust `load_capture_preset()` to work with additional settings. --- openpype/hosts/maya/api/lib.py | 62 +++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index de9a9da911..bd403ad340 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2525,8 +2525,19 @@ def load_capture_preset(data=None): if key == 'ssaoEnable': if preset[id][key] is True: temp_options2['ssaoEnable'] = True + temp_options2['ssaoSamples'] = preset[id][key] else: temp_options2['ssaoEnable'] = False + temp_options2['ssaoSamples'] = preset[id][key] + + if key == 'ssaoAmount': + temp_options2['ssaoAmount'] = preset[id][key] + + if key == 'ssaoRadius': + temp_options2['ssaoRadius'] = preset[id][key] + + if key == 'ssaoFilterRadius': + temp_options2['ssaoFilterRadius'] = preset[id][key] if key == 'alphaCut': temp_options2['transparencyAlgorithm'] = 5 @@ -2535,6 +2546,42 @@ def load_capture_preset(data=None): if key == 'headsUpDisplay': temp_options['headsUpDisplay'] = True + if key == 'hwFogEnable': + if preset[id][key] is True: + temp_options2['hwFogEnable'] = True + else: + temp_options2['hwFogEnable'] = False + + if key == 'hwFogStart': + temp_options2['hwFogStart'] = preset[id][key] + + if key == 'hwFogEnd': + temp_options2['hwFogEnd'] = preset[id][key] + + if key == 'hwFogAlpha': + temp_options2['hwFogAlpha'] = preset[id][key] + + if key == 'hwFogFalloff': + temp_options2['hwFogFalloff'] = preset[id][key] + + if key == 'motionBlurEnable': + if preset[id][key] is True: + temp_options2['motionBlurEnable'] = True + else: + temp_options2['motionBlurEnable'] = False + + if key == 'motionBlurSampleCount': + temp_options2['motionBlurSampleCount'] = preset[id][key] + + if key == 'motionBlurShutterOpenFraction': + temp_options2['motionBlurShutterOpenFraction'] = preset[id][key] + + if key == 'lineAAEnable': + if preset[id][key] is True: + temp_options2['lineAAEnable'] = True + else: + temp_options2['lineAAEnable'] = False + else: temp_options[str(key)] = preset[id][key] @@ -2544,7 +2591,20 @@ def load_capture_preset(data=None): 'gpuCacheDisplayFilter', 'multiSample', 'ssaoEnable', - 'textureMaxResolution' + 'ssaoSamples', + 'ssaoAmount', + 'ssaoFilterRadius', + 'ssaoRadius', + 'hwFogEnable', + 'hwFogStart', + 'hwFogEnd', + 'hwFogAlpha', + 'hwFogFalloff', + 'textureMaxResolution', + 'motionBlurEnable', + 'motionBlurSampleCount', + 'motionBlurShutterOpenFraction', + 'lineAAEnable', ]: temp_options.pop(key, None) From 20cf87c07d08a058eff3684eeb8e0df0fae1fb1d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Jun 2022 11:03:08 +0200 Subject: [PATCH 0791/1227] forgotten changes of MayaHost imports --- openpype/hosts/maya/startup/userSetup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index daaf305612..10e68c2ddb 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -1,10 +1,10 @@ import os from openpype.api import get_project_settings from openpype.pipeline import install_host -from openpype.hosts.maya.api import MayaHostImplementation +from openpype.hosts.maya.api import MayaHost from maya import cmds -host = MayaHostImplementation() +host = MayaHost() install_host(host) From f5e3f56981660fd6131cf80d802cb6ed382a6915 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 23 Jun 2022 12:31:50 +0300 Subject: [PATCH 0792/1227] Append AA flag as schema key. --- .../schemas/schema_maya_capture.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index f8dba0be4b..2c5aed8a67 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -233,6 +233,14 @@ { "type": "splitter" }, + { + "type": "boolean", + "key": "lineAAEnable", + "label": "Enable Anti-Aliasing" + }, + { + "type": "splitter" + }, { "type": "boolean", "key": "ssaoEnable", @@ -313,7 +321,10 @@ { "type": "number", "key": "motionBlurShutterOpenFraction", - "label": "Shutter Open Fraction" + "label": "Shutter Open Fraction", + "decimal": 3, + "minimum": 0.01, + "maximum": 32 }, { "type": "splitter" From f497956c5fc31657f9798137c303b359004046c9 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 23 Jun 2022 12:31:58 +0300 Subject: [PATCH 0793/1227] Adjust schema defaults. --- openpype/settings/defaults/project_settings/maya.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 437adbc1f0..48a34068db 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -500,6 +500,7 @@ "shadows": true, "textures": true, "twoSidedLighting": true, + "lineAAEnable": true, "ssaoEnable": true, "ssaoAmount": 0, "ssaoRadius": 0, @@ -512,7 +513,7 @@ "hwFogAlpha": 0, "motionBlurEnable": true, "motionBlurSampleCount": 8, - "motionBlurShutterOpenFraction": 0, + "motionBlurShutterOpenFraction": 0.01, "cameras": false, "clipGhosts": false, "controlVertices": false, From 98b912f0b36634c345faaf0e617385460a402af1 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 23 Jun 2022 12:32:15 +0300 Subject: [PATCH 0794/1227] Change key check placement. --- openpype/hosts/maya/api/lib.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index bd403ad340..12cbac2a32 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2525,10 +2525,11 @@ def load_capture_preset(data=None): if key == 'ssaoEnable': if preset[id][key] is True: temp_options2['ssaoEnable'] = True - temp_options2['ssaoSamples'] = preset[id][key] else: temp_options2['ssaoEnable'] = False - temp_options2['ssaoSamples'] = preset[id][key] + + if key == 'ssaoSamples': + temp_options2['ssaoSamples'] = preset[id][key] if key == 'ssaoAmount': temp_options2['ssaoAmount'] = preset[id][key] @@ -2549,20 +2550,16 @@ def load_capture_preset(data=None): if key == 'hwFogEnable': if preset[id][key] is True: temp_options2['hwFogEnable'] = True + temp_options2['hwFogStart'] = preset[id][key] + temp_options2['hwFogEnd'] = preset[id][key] + temp_options2['hwFogAlpha'] = preset[id][key] + temp_options2['hwFogFalloff'] = preset[id][key] else: temp_options2['hwFogEnable'] = False - - if key == 'hwFogStart': - temp_options2['hwFogStart'] = preset[id][key] - - if key == 'hwFogEnd': - temp_options2['hwFogEnd'] = preset[id][key] - - if key == 'hwFogAlpha': - temp_options2['hwFogAlpha'] = preset[id][key] - - if key == 'hwFogFalloff': - temp_options2['hwFogFalloff'] = preset[id][key] + temp_options2['hwFogStart'] = preset[id][key] + temp_options2['hwFogEnd'] = preset[id][key] + temp_options2['hwFogAlpha'] = preset[id][key] + temp_options2['hwFogFalloff'] = preset[id][key] if key == 'motionBlurEnable': if preset[id][key] is True: From ae897ed2901de70da53ff16bfc5a6d3fa1bc1432 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 23 Jun 2022 13:07:38 +0300 Subject: [PATCH 0795/1227] Append Fog Color Key to schema. --- .../schemas/projects_schema/schemas/schema_maya_capture.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 2c5aed8a67..08207824b1 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -302,6 +302,11 @@ "key": "hwFogAlpha", "label": "Enable Fog Alpha" }, + { + "type": "color", + "key": "hwFogColor", + "label": "Fog Color" + }, { "type": "splitter" }, From 4c1bba042d69bfbc807f2aea81a004fe27593163 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 23 Jun 2022 13:08:13 +0300 Subject: [PATCH 0796/1227] Append Fog Color to functionk, fix loop bug. --- openpype/hosts/maya/api/lib.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 12cbac2a32..4b76757e97 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2550,16 +2550,24 @@ def load_capture_preset(data=None): if key == 'hwFogEnable': if preset[id][key] is True: temp_options2['hwFogEnable'] = True - temp_options2['hwFogStart'] = preset[id][key] - temp_options2['hwFogEnd'] = preset[id][key] - temp_options2['hwFogAlpha'] = preset[id][key] - temp_options2['hwFogFalloff'] = preset[id][key] + else: temp_options2['hwFogEnable'] = False - temp_options2['hwFogStart'] = preset[id][key] - temp_options2['hwFogEnd'] = preset[id][key] - temp_options2['hwFogAlpha'] = preset[id][key] - temp_options2['hwFogFalloff'] = preset[id][key] + + if key == 'hwFogStart': + temp_options2['hwFogStart'] = preset[id][key] + + if key == 'hwFogEnd': + temp_options2['hwFogEnd'] = preset[id][key] + + if key == 'hwFogAlpha': + temp_options2['hwFogAlpha'] = preset[id][key] + + if key == 'hwFogFalloff': + temp_options2['hwFogFalloff'] = int(preset[id][key]) + + if key == 'hwFogColor': + temp_options2['hwFogColor'] = preset[id][key] if key == 'motionBlurEnable': if preset[id][key] is True: @@ -2597,6 +2605,7 @@ def load_capture_preset(data=None): 'hwFogEnd', 'hwFogAlpha', 'hwFogFalloff', + 'hwFogColor', 'textureMaxResolution', 'motionBlurEnable', 'motionBlurSampleCount', From bb339e0bbd9828a650f213d8839fecf7ca0f3ff7 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 23 Jun 2022 13:08:26 +0300 Subject: [PATCH 0797/1227] Append schema default for color. --- openpype/settings/defaults/project_settings/maya.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 48a34068db..874e23400e 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -511,6 +511,12 @@ "hwFogStart": 0, "hwFogEnd": 0, "hwFogAlpha": 0, + "hwFogColor": [ + 158, + 53, + 53, + 255 + ], "motionBlurEnable": true, "motionBlurSampleCount": 8, "motionBlurShutterOpenFraction": 0.01, From 25b7f98022e7ac3a9e85352713130ba1121a05bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 23 Jun 2022 12:59:01 +0200 Subject: [PATCH 0798/1227] Renaming: Kitsu Plural func sync_all_projects --- openpype/modules/kitsu/kitsu_module.py | 4 ++-- openpype/modules/kitsu/utils/update_op_with_zou.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index 8e7ab6f78c..d19d14dda7 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -129,8 +129,8 @@ def sync_service(login, password): login (str): Kitsu user login password (str): Kitsu user password """ - from .utils.update_op_with_zou import sync_all_project + from .utils.update_op_with_zou import sync_all_projects from .utils.sync_service import start_listeners - sync_all_project(login, password) + sync_all_projects(login, password) start_listeners(login, password) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 08e50d959b..cd98c0d204 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -250,7 +250,7 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: ) -def sync_all_project(login: str, password: str): +def sync_all_projects(login: str, password: str): """Update all OP projects in DB with Zou data. Args: From 5325b6eb508d695ea29be31c3ff73347ada7346b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 23 Jun 2022 13:57:57 +0200 Subject: [PATCH 0799/1227] :package: update OIIO for linux --- pyproject.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4b297fe042..5bf4f18db8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,6 +114,8 @@ build-backend = "poetry.core.masonry.api" # https://pip.pypa.io/en/stable/cli/pip_install/#requirement-specifiers version = "==5.15.2" +# TODO: we will need to handle different linux flavours here and +# also different macos versions too. [openpype.thirdparty.ffmpeg.windows] url = "https://distribute.openpype.io/thirdparty/ffmpeg-4.4-windows.zip" hash = "dd51ba29d64ee238e7c4c3c7301b19754c3f0ee2e2a729c20a0e2789e72db925" @@ -131,8 +133,8 @@ url = "https://distribute.openpype.io/thirdparty/oiio_tools-2.3.10-windows.zip" hash = "b9950f5d2fa3720b52b8be55bacf5f56d33f9e029d38ee86534995f3d8d253d2" [openpype.thirdparty.oiio.linux] -url = "https://distribute.openpype.io/thirdparty/oiio_tools-2.2.12-linux.tgz" -hash = "de63a8bf7f6c45ff59ecafeba13123f710c2cbc1783ec9e0b938e980d4f5c37f" +url = "https://distribute.openpype.io/thirdparty/oiio_tools-2.2.20-linux-centos7" +hash = "BE1ABF8A50E9DA5913298447421AF0A17829D83ED6252AE1D40DA7FA36A78787" [openpype.thirdparty.oiio.darwin] url = "https://distribute.openpype.io/thirdparty/oiio-2.2.0-darwin.tgz" From 68ee05bd36312f7bb5f476125dee26bf7241f239 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Jun 2022 14:00:18 +0200 Subject: [PATCH 0800/1227] filter representations before integration starts --- openpype/plugins/publish/integrate_new.py | 62 +++++++++++++++-------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 2471105250..918ca4ba94 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -145,9 +145,43 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if instance.data.get("farm"): return + # Prepare repsentations that should be integrated + repres = instance.data.get("representations") + # Raise error if instance don't have any representations + if not repres: + raise ValueError( + "Instance {} has no files to transfer".format( + instance.data["family"] + ) + ) + + # Validate type of stored representations + if not isinstance(repres, (list, tuple)): + raise TypeError( + "Instance 'files' must be a list, got: {0} {1}".format( + str(type(repres)), str(repres) + ) + ) + + # Filter representations + filtered_repres = [] + for repre in repres: + if "delete" in repre.get("tags", []): + continue + filtered_repres.append(repre) + + # Skip instance if there are not representations to integrate + # all representations should not be integrated + if not filtered_repres: + self.log.warning(( + "Skipping, there are no representations" + " to integrate for instance {}" + ).format(instance.data["family"])) + return + self.integrated_file_sizes = {} try: - self.register(instance) + self.register(instance, filtered_repres) self.log.info("Integrated Asset in to the database ...") self.log.info("instance.data: {}".format(instance.data)) self.handle_destination_files(self.integrated_file_sizes, @@ -158,7 +192,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self.handle_destination_files(self.integrated_file_sizes, 'remove') six.reraise(*sys.exc_info()) - def register(self, instance): + def register(self, instance, repres): # Required environment variables anatomy_data = instance.data["anatomyData"] @@ -236,18 +270,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "Establishing staging directory @ {0}".format(stagingdir) ) - # Ensure at least one file is set up for transfer in staging dir. - repres = instance.data.get("representations") - repres = instance.data.get("representations") - msg = "Instance {} has no files to transfer".format( - instance.data["family"]) - assert repres, msg - assert isinstance(repres, (list, tuple)), ( - "Instance 'files' must be a list, got: {0} {1}".format( - str(type(repres)), str(repres) - ) - ) - subset = self.get_subset(asset_entity, instance) instance.data["subsetEntity"] = subset @@ -270,7 +292,10 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self.log.debug("Creating version ...") - new_repre_names_low = [_repre["name"].lower() for _repre in repres] + new_repre_names_low = [ + _repre["name"].lower() + for _repre in repres + ] existing_version = legacy_io.find_one({ 'type': 'version', @@ -373,18 +398,13 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if profile: template_name = profile["template_name"] - - published_representations = {} - for idx, repre in enumerate(instance.data["representations"]): + for idx, repre in enumerate(repres): # reset transfers for next representation # instance.data['transfers'] is used as a global variable # in current codebase instance.data['transfers'] = list(orig_transfers) - if "delete" in repre.get("tags", []): - continue - published_files = [] # create template data for Anatomy From 7cb38127658e2ba43780e0a537350f407229f5d1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Jun 2022 14:09:47 +0200 Subject: [PATCH 0801/1227] moved reset of tranfers to the end of repre iteration --- openpype/plugins/publish/integrate_new.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 918ca4ba94..4c14c17dae 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -400,11 +400,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): published_representations = {} for idx, repre in enumerate(repres): - # reset transfers for next representation - # instance.data['transfers'] is used as a global variable - # in current codebase - instance.data['transfers'] = list(orig_transfers) - published_files = [] # create template data for Anatomy @@ -682,6 +677,10 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "published_files": published_files } self.log.debug("__ representations: {}".format(representations)) + # reset transfers for next representation + # instance.data['transfers'] is used as a global variable + # in current codebase + instance.data['transfers'] = list(orig_transfers) # Remove old representations if there are any (before insertion of new) if existing_repres: From 0a20a69e0e37a656d2004abf17259029c1750d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 23 Jun 2022 14:46:06 +0200 Subject: [PATCH 0802/1227] :bug: fix extension --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5bf4f18db8..3a5acb8490 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,8 +133,8 @@ url = "https://distribute.openpype.io/thirdparty/oiio_tools-2.3.10-windows.zip" hash = "b9950f5d2fa3720b52b8be55bacf5f56d33f9e029d38ee86534995f3d8d253d2" [openpype.thirdparty.oiio.linux] -url = "https://distribute.openpype.io/thirdparty/oiio_tools-2.2.20-linux-centos7" -hash = "BE1ABF8A50E9DA5913298447421AF0A17829D83ED6252AE1D40DA7FA36A78787" +url = "https://distribute.openpype.io/thirdparty/oiio_tools-2.2.20-linux-centos7.tgz" +hash = "be1abf8a50e9da5913298447421af0a17829d83ed6252ae1d40da7fa36a78787" [openpype.thirdparty.oiio.darwin] url = "https://distribute.openpype.io/thirdparty/oiio-2.2.0-darwin.tgz" From ecd2686ad19af5ef9a47d197c2f439be6bc143bd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Jun 2022 14:47:16 +0200 Subject: [PATCH 0803/1227] added some docstrings --- openpype/host/host.py | 95 ++++++++++++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 33 deletions(-) diff --git a/openpype/host/host.py b/openpype/host/host.py index 2a59daf473..ab75ec5bc3 100644 --- a/openpype/host/host.py +++ b/openpype/host/host.py @@ -5,6 +5,13 @@ import six class MissingMethodsError(ValueError): + """Exception when host miss some required methods for specific workflow. + + Args: + host (HostBase): Host implementation where are missing methods. + missing_methods (list[str]): List of missing methods. + """ + def __init__(self, host, missing_methods): joined_missing = ", ".join( ['"{}"'.format(item) for item in missing_methods] @@ -53,15 +60,15 @@ class HostBase(object): install_host(host) ``` - # TODOs - - move content of 'install_host' as method of this class - - register host object - - install legacy_io - - install global plugin paths - - store registered plugin paths to this object - - handle current context (project, asset, task) - - this must be done in many separated steps - - have it's object of host tools instead of using globals + Todo: + - move content of 'install_host' as method of this class + - register host object + - install legacy_io + - install global plugin paths + - store registered plugin paths to this object + - handle current context (project, asset, task) + - this must be done in many separated steps + - have it's object of host tools instead of using globals This implementation will probably change over time when more functionality and responsibility will be added. @@ -75,7 +82,7 @@ class HostBase(object): Register DCC callbacks, host specific plugin paths, targets etc. (Part of what 'install' did in 'avalon' concept.) - NOTE: + Note: At this moment global "installation" must happen before host installation. Because of this current limitation it is recommended to implement 'install' method which is triggered after global @@ -127,10 +134,10 @@ class HostBase(object): Should return current context title if possible. - NOTE: This method is used only for UI purposes so it is possible to - return some logical title for contextless cases. - - Is not meant for "Context menu" label. + Note: + This method is used only for UI purposes so it is possible to + return some logical title for contextless cases. + Is not meant for "Context menu" label. Returns: str: Context title. @@ -159,6 +166,9 @@ class HostBase(object): This is DCC specific. Some may not allow to implement this ability that is reason why default implementation is empty context manager. + + Yields: + None: Yield when is ready to restore selected at the end. """ try: @@ -173,11 +183,11 @@ class ILoadHost: The load plugins can do referencing even without implementation of methods here, but switch and removement of containers would not be possible. - QUESTIONS - - Is list container dependency of host or load plugins? - - Should this be directly in HostBase? - - how to find out if referencing is available? - - do we need to know that? + Questions: + - Is list container dependency of host or load plugins? + - Should this be directly in HostBase? + - how to find out if referencing is available? + - do we need to know that? """ @staticmethod @@ -188,6 +198,9 @@ class ILoadHost: loading. Checks only existence of methods. Args: + HostBase: Object of host where to look for required methods. + + Returns: list[str]: Missing method implementations for loading workflow. """ @@ -202,6 +215,9 @@ class ILoadHost: def validate_load_methods(host): """Validate implemented methods of host for load workflow. + Args: + HostBase: Object of host to validate. + Raises: MissingMethodsError: If there are missing methods on host implementation. @@ -216,7 +232,7 @@ class ILoadHost: This can be implemented in hosts where referencing can be used. - TODO: + Todo: Rename function to something more self explanatory. Suggestion: 'get_referenced_containers' @@ -242,6 +258,9 @@ class IWorkfileHost: Method is used for validation of implemented functions related to workfiles. Checks only existence of methods. + Args: + HostBase: Object of host where to look for required methods. + Returns: list[str]: Missing method implementations for workfiles workflow. """ @@ -264,6 +283,9 @@ class IWorkfileHost: def validate_workfile_methods(host): """Validate implemented methods of host for workfiles workflow. + Args: + HostBase: Object of host to validate. + Raises: MissingMethodsError: If there are missing methods on host implementation. @@ -276,9 +298,10 @@ class IWorkfileHost: def file_extensions(self): """Extensions that can be used as save. - QUESTION: This could potentially use 'HostDefinition'. + Questions: + This could potentially use 'HostDefinition'. - TODO: + Todo: Rename to 'get_workfile_extensions'. """ @@ -288,7 +311,7 @@ class IWorkfileHost: def save_file(self, dst_path=None): """Save currently opened scene. - TODO: + Todo: Rename to 'save_current_workfile'. Args: @@ -302,7 +325,7 @@ class IWorkfileHost: def open_file(self, filepath): """Open passed filepath in the host. - TODO: + Todo: Rename to 'open_workfile'. Args: @@ -315,7 +338,7 @@ class IWorkfileHost: def current_file(self): """Retreive path to current opened file. - TODO: + Todo: Rename to 'get_current_workfile'. Returns: @@ -342,16 +365,16 @@ class IWorkfileHost: def work_root(self, session): """Modify workdir per host. - WARNING: - We must handle this modification with more sofisticated way because - this can't be called out of DCC so opening of last workfile - (calculated before DCC is launched) is complicated. Also breaking - defined work template is not a good idea. - Only place where it's really used and can make sense is Maya. There - workspace.mel can modify subfolders where to look for maya files. - Default implementation keeps workdir untouched. + Warnings: + We must handle this modification with more sofisticated way because + this can't be called out of DCC so opening of last workfile + (calculated before DCC is launched) is complicated. Also breaking + defined work template is not a good idea. + Only place where it's really used and can make sense is Maya. There + workspace.mel can modify subfolders where to look for maya files. + Args: session (dict): Session context data. @@ -381,6 +404,9 @@ class INewPublisher: new publish creation. Checks only existence of methods. Args: + HostBase: Object of host where to look for required methods. + + Returns: list[str]: Missing method implementations for new publsher workflow. """ @@ -399,6 +425,9 @@ class INewPublisher: def validate_publish_methods(host): """Validate implemented methods of host for create-publish workflow. + Args: + HostBase: Object of host to validate. + Raises: MissingMethodsError: If there are missing methods on host implementation. From 4a8dfc3db9988b61572aa307fe01b8fe1e011e8e Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Thu, 16 Jun 2022 14:15:26 +0200 Subject: [PATCH 0804/1227] blender install pyside2 for all platforms --- .../hosts/blender/hooks/pre_pyside_install.py | 112 ++++++++++++------ 1 file changed, 75 insertions(+), 37 deletions(-) diff --git a/openpype/hosts/blender/hooks/pre_pyside_install.py b/openpype/hosts/blender/hooks/pre_pyside_install.py index a37f8f0379..aaa545b2cf 100644 --- a/openpype/hosts/blender/hooks/pre_pyside_install.py +++ b/openpype/hosts/blender/hooks/pre_pyside_install.py @@ -1,6 +1,7 @@ import os import re import subprocess +from platform import system from openpype.lib import PreLaunchHook @@ -13,12 +14,9 @@ class InstallPySideToBlender(PreLaunchHook): For pipeline implementation is required to have Qt binding installed in blender's python packages. - - Prelaunch hook can work only on Windows right now. """ app_groups = ["blender"] - platforms = ["windows"] def execute(self): # Prelaunch hook is not crucial @@ -34,25 +32,28 @@ class InstallPySideToBlender(PreLaunchHook): # Get blender's python directory version_regex = re.compile(r"^[2-3]\.[0-9]+$") + platform = system().lower() executable = self.launch_context.executable.executable_path - if os.path.basename(executable).lower() != "blender.exe": + expected_executable = "blender" + if platform == "windows": + expected_executable += ".exe" + + if os.path.basename(executable).lower() != expected_executable: self.log.info(( - "Executable does not lead to blender.exe file. Can't determine" - " blender's python to check/install PySide2." + f"Executable does not lead to {expected_executable} file." + "Can't determine blender's python to check/install PySide2." )) return - executable_dir = os.path.dirname(executable) + versions_dir = os.path.dirname(executable) + if platform == "darwin": + versions_dir = os.path.join( + os.path.dirname(versions_dir), "Resources" + ) version_subfolders = [] - for name in os.listdir(executable_dir): - fullpath = os.path.join(name, executable_dir) - if not os.path.isdir(fullpath): - continue - - if not version_regex.match(name): - continue - - version_subfolders.append(name) + for dir_entry in os.scandir(versions_dir): + if dir_entry.is_dir() and version_regex.match(dir_entry.name): + version_subfolders.append(dir_entry.name) if not version_subfolders: self.log.info( @@ -72,16 +73,21 @@ class InstallPySideToBlender(PreLaunchHook): version_subfolder = version_subfolders[0] - pythond_dir = os.path.join( - os.path.dirname(executable), - version_subfolder, - "python" - ) + python_dir = os.path.join(versions_dir, version_subfolder, "python") + python_lib = os.path.join(python_dir, "lib") + python_version = "python" + + if platform != "windows": + for dir_entry in os.scandir(python_lib): + if dir_entry.is_dir() and dir_entry.name.startswith("python"): + python_lib = dir_entry.path + python_version = dir_entry.name + break # Change PYTHONPATH to contain blender's packages as first python_paths = [ - os.path.join(pythond_dir, "lib"), - os.path.join(pythond_dir, "lib", "site-packages"), + python_lib, + os.path.join(python_lib, "site-packages"), ] python_path = self.launch_context.env.get("PYTHONPATH") or "" for path in python_path.split(os.pathsep): @@ -91,7 +97,12 @@ class InstallPySideToBlender(PreLaunchHook): self.launch_context.env["PYTHONPATH"] = os.pathsep.join(python_paths) # Get blender's python executable - python_executable = os.path.join(pythond_dir, "bin", "python.exe") + python_bin = os.path.join(python_dir, "bin") + if platform == "windows": + python_executable = os.path.join(python_bin, "python.exe") + else: + python_executable = os.path.join(python_bin, python_version) + if not os.path.exists(python_executable): self.log.warning( "Couldn't find python executable for blender. {}".format( @@ -106,7 +117,15 @@ class InstallPySideToBlender(PreLaunchHook): return # Install PySide2 in blender's python - self.install_pyside_windows(python_executable) + if platform == "windows": + result = self.install_pyside_windows(python_executable) + else: + result = self.install_pyside(python_executable) + + if result: + self.log.info("Successfully installed PySide2 module to blender.") + else: + self.log.warning("Failed to install PySide2 module to blender.") def install_pyside_windows(self, python_executable): """Install PySide2 python module to blender's python. @@ -144,21 +163,38 @@ class InstallPySideToBlender(PreLaunchHook): lpDirectory=os.path.dirname(python_executable) ) process_handle = process_info["hProcess"] - obj = win32event.WaitForSingleObject( - process_handle, win32event.INFINITE - ) + win32event.WaitForSingleObject(process_handle, win32event.INFINITE) returncode = win32process.GetExitCodeProcess(process_handle) - if returncode == 0: - self.log.info( - "Successfully installed PySide2 module to blender." - ) - return + return returncode == 0 except pywintypes.error: pass - self.log.warning("Failed to install PySide2 module to blender.") + @staticmethod + def install_pyside(python_executable): + """Install PySide2 python module to blender's python.""" + try: + # Parameters + # - use "-m pip" as module pip to install PySide2 and argument + # "--ignore-installed" is to force install module to blender's + # site-packages and make sure it is binary compatible + args = [ + python_executable, + "-m", + "pip", + "install", + "--ignore-installed", + "PySide2", + ] + process = subprocess.Popen( + args, stdout=subprocess.PIPE, universal_newlines=True + ) + stdout, _ = process.communicate() + return process.returncode == 0 + except subprocess.SubprocessError: + pass - def is_pyside_installed(self, python_executable): + @staticmethod + def is_pyside_installed(python_executable): """Check if PySide2 module is in blender's pip list. Check that PySide2 is installed directly in blender's site-packages. @@ -167,9 +203,11 @@ class InstallPySideToBlender(PreLaunchHook): """ # Get pip list from blender's python executable args = [python_executable, "-m", "pip", "list"] - process = subprocess.Popen(args, stdout=subprocess.PIPE) + process = subprocess.Popen( + args, stdout=subprocess.PIPE, universal_newlines=True + ) stdout, _ = process.communicate() - lines = stdout.decode().split("\r\n") + lines = stdout.split(os.linesep) # Second line contain dashes that define maximum length of module name. # Second column of dashes define maximum length of module version. package_dashes, *_ = lines[1].split(" ") From b42505b430fa77608c722d91fce2f50ff38ba5e6 Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Tue, 21 Jun 2022 15:19:34 +0200 Subject: [PATCH 0805/1227] fix pre_pyside_install for all platform --- openpype/hosts/blender/hooks/pre_pyside_install.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/blender/hooks/pre_pyside_install.py b/openpype/hosts/blender/hooks/pre_pyside_install.py index aaa545b2cf..d2c07e860d 100644 --- a/openpype/hosts/blender/hooks/pre_pyside_install.py +++ b/openpype/hosts/blender/hooks/pre_pyside_install.py @@ -203,11 +203,9 @@ class InstallPySideToBlender(PreLaunchHook): """ # Get pip list from blender's python executable args = [python_executable, "-m", "pip", "list"] - process = subprocess.Popen( - args, stdout=subprocess.PIPE, universal_newlines=True - ) + process = subprocess.Popen(args, stdout=subprocess.PIPE) stdout, _ = process.communicate() - lines = stdout.split(os.linesep) + lines = stdout.decode().split(os.linesep) # Second line contain dashes that define maximum length of module name. # Second column of dashes define maximum length of module version. package_dashes, *_ = lines[1].split(" ") From 1d6fff34746c34e76775d59118c1748a75ff1031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 23 Jun 2022 15:11:18 +0200 Subject: [PATCH 0806/1227] :memo: update requirements for oiio on centos 7 --- website/docs/dev_requirements.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/docs/dev_requirements.md b/website/docs/dev_requirements.md index a10aea7865..eb4b132297 100644 --- a/website/docs/dev_requirements.md +++ b/website/docs/dev_requirements.md @@ -87,9 +87,11 @@ This can also be hosted on the cloud in fully distributed deployments. - [**Avalon**](https://github.com/getavalon) - [**Pyblish**](https://github.com/pyblish) - [**OpenTimelineIO**](https://github.com/PixarAnimationStudios/OpenTimelineIO) -- [**OpenImageIO**](https://github.com/OpenImageIO/oiio) +- [**OpenImageIO**](https://github.com/OpenImageIO/oiio) [^centos7] - [**FFmpeg**](https://github.com/FFmpeg/FFmpeg) +[^centos7]: On Centos 7 you need to install additional libraries to support OIIO there - mainly boost +and libraw (`sudo yum install boost-1.53.0` and `sudo yum install LibRaw`) ### Python modules we use and their licenses From e36c80fd09adea0322bc03c3c4da1764cc620d62 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Jun 2022 15:41:41 +0200 Subject: [PATCH 0807/1227] added type hints --- openpype/host/host.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/openpype/host/host.py b/openpype/host/host.py index ab75ec5bc3..676f0e6771 100644 --- a/openpype/host/host.py +++ b/openpype/host/host.py @@ -3,6 +3,9 @@ import contextlib from abc import ABCMeta, abstractproperty, abstractmethod import six +# NOTE can't import 'typing' because of issues in Maya 2020 +# - shiboken crashes on 'typing' module import + class MissingMethodsError(ValueError): """Exception when host miss some required methods for specific workflow. @@ -77,6 +80,7 @@ class HostBase(object): _log = None def __init__(self): + # type: () -> None """Initialization of host. Register DCC callbacks, host specific plugin paths, targets etc. @@ -93,17 +97,20 @@ class HostBase(object): @property def log(self): + # type: () -> logging.Logger if self._log is None: self._log = logging.getLogger(self.__class__.__name__) return self._log @abstractproperty def name(self): + # type: () -> str """Host implementation name.""" pass def get_current_context(self): + # type: () -> Mapping[str, Union[str, None]] """Get current context information. This method should be used to get current context of host. Usage of @@ -130,6 +137,7 @@ class HostBase(object): } def get_context_title(self): + # type: () -> Union[str, None] """Context title shown for UI purposes. Should return current context title if possible. @@ -162,6 +170,7 @@ class HostBase(object): @contextlib.contextmanager def maintained_selection(self): + # type: () -> None """Some functionlity will happen but selection should stay same. This is DCC specific. Some may not allow to implement this ability @@ -192,6 +201,7 @@ class ILoadHost: @staticmethod def get_missing_load_methods(host): + # type: (HostBase) -> List[str] """Look for missing methods on host implementation. Method is used for validation of implemented functions related to @@ -213,6 +223,7 @@ class ILoadHost: @staticmethod def validate_load_methods(host): + # type: (HostBase) -> None """Validate implemented methods of host for load workflow. Args: @@ -228,6 +239,7 @@ class ILoadHost: @abstractmethod def ls(self): + # type: (HostBase) -> List[Mapping[str, Any]] """Retreive referenced containers from scene. This can be implemented in hosts where referencing can be used. @@ -253,6 +265,7 @@ class IWorkfileHost: @staticmethod def get_missing_workfile_methods(host): + # type: (HostBase) -> List[str] """Look for missing methods on host implementation. Method is used for validation of implemented functions related to @@ -281,6 +294,7 @@ class IWorkfileHost: @staticmethod def validate_workfile_methods(host): + # type: (HostBase) -> None """Validate implemented methods of host for workfiles workflow. Args: @@ -296,6 +310,7 @@ class IWorkfileHost: @abstractmethod def file_extensions(self): + # type: () -> List[str] """Extensions that can be used as save. Questions: @@ -309,6 +324,7 @@ class IWorkfileHost: @abstractmethod def save_file(self, dst_path=None): + # type: (Optional[str]) -> None """Save currently opened scene. Todo: @@ -323,6 +339,7 @@ class IWorkfileHost: @abstractmethod def open_file(self, filepath): + # type: (str) -> None """Open passed filepath in the host. Todo: @@ -336,6 +353,7 @@ class IWorkfileHost: @abstractmethod def current_file(self): + # type: () -> Union[str, None] """Retreive path to current opened file. Todo: @@ -349,6 +367,7 @@ class IWorkfileHost: return None def has_unsaved_changes(self): + # type: () -> Union[bool, None] """Currently opened scene is saved. Not all hosts can know if current scene is saved because the API of @@ -363,6 +382,7 @@ class IWorkfileHost: return None def work_root(self, session): + # type: (Mapping[str, str]) -> str """Modify workdir per host. Default implementation keeps workdir untouched. @@ -398,6 +418,7 @@ class INewPublisher: @staticmethod def get_missing_publish_methods(host): + # type: (HostBase) -> List[str] """Look for missing methods on host implementation. Method is used for validation of implemented functions related to @@ -423,6 +444,7 @@ class INewPublisher: @staticmethod def validate_publish_methods(host): + # type: (HostBase) -> None """Validate implemented methods of host for create-publish workflow. Args: @@ -438,6 +460,7 @@ class INewPublisher: @abstractmethod def get_context_data(self): + # type: () -> Mapping[str, Any] """Get global data related to creation-publishing from workfile. These data are not related to any created instance but to whole @@ -455,6 +478,7 @@ class INewPublisher: @abstractmethod def update_context_data(self, data, changes): + # type: (Mapping[str, Any], Mapping[str, Any]) -> None """Store global context data to workfile. Called when some values in context data has changed. From 07e67a14b76047129fa0dcf49ec1be452c6af79b Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Thu, 23 Jun 2022 16:05:46 +0200 Subject: [PATCH 0808/1227] more exceptions for pre pisyde install --- openpype/hosts/blender/hooks/pre_pyside_install.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/blender/hooks/pre_pyside_install.py b/openpype/hosts/blender/hooks/pre_pyside_install.py index d2c07e860d..9e1046453b 100644 --- a/openpype/hosts/blender/hooks/pre_pyside_install.py +++ b/openpype/hosts/blender/hooks/pre_pyside_install.py @@ -169,8 +169,7 @@ class InstallPySideToBlender(PreLaunchHook): except pywintypes.error: pass - @staticmethod - def install_pyside(python_executable): + def install_pyside(self, python_executable): """Install PySide2 python module to blender's python.""" try: # Parameters @@ -188,13 +187,16 @@ class InstallPySideToBlender(PreLaunchHook): process = subprocess.Popen( args, stdout=subprocess.PIPE, universal_newlines=True ) - stdout, _ = process.communicate() + process.communicate() return process.returncode == 0 + except PermissionError: + self.log.warning("Permission denied with command: \"{}\".".format(" ".join(args))) + except OSError as error: + self.log.warning("OS error has occurred with command: \"{error}\".") except subprocess.SubprocessError: pass - @staticmethod - def is_pyside_installed(python_executable): + def is_pyside_installed(self, python_executable): """Check if PySide2 module is in blender's pip list. Check that PySide2 is installed directly in blender's site-packages. From 262d179e15e1236f09fd86352a697eaff23a899d Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Thu, 23 Jun 2022 16:09:50 +0200 Subject: [PATCH 0809/1227] fix last commit draft code --- openpype/hosts/blender/hooks/pre_pyside_install.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/hooks/pre_pyside_install.py b/openpype/hosts/blender/hooks/pre_pyside_install.py index 9e1046453b..d0f2b3d417 100644 --- a/openpype/hosts/blender/hooks/pre_pyside_install.py +++ b/openpype/hosts/blender/hooks/pre_pyside_install.py @@ -190,9 +190,12 @@ class InstallPySideToBlender(PreLaunchHook): process.communicate() return process.returncode == 0 except PermissionError: - self.log.warning("Permission denied with command: \"{}\".".format(" ".join(args))) + self.log.warning( + "Permission denied with command:" + "\"{}\".".format(" ".join(args)) + ) except OSError as error: - self.log.warning("OS error has occurred with command: \"{error}\".") + self.log.warning(f"OS error has occurred: \"{error}\".") except subprocess.SubprocessError: pass From bf592641e870556c474da160b1e107a3377388a4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Jun 2022 17:13:29 +0200 Subject: [PATCH 0810/1227] added new names of methods and old variants are marked as deprecated --- openpype/host/host.py | 142 ++++++++++++++++++---------- openpype/hosts/maya/api/pipeline.py | 12 +-- 2 files changed, 100 insertions(+), 54 deletions(-) diff --git a/openpype/host/host.py b/openpype/host/host.py index 676f0e6771..1755c19216 100644 --- a/openpype/host/host.py +++ b/openpype/host/host.py @@ -105,7 +105,7 @@ class HostBase(object): @abstractproperty def name(self): # type: () -> str - """Host implementation name.""" + """Host name.""" pass @@ -201,19 +201,22 @@ class ILoadHost: @staticmethod def get_missing_load_methods(host): - # type: (HostBase) -> List[str] - """Look for missing methods on host implementation. + # type: (Union[ModuleType, HostBase]) -> List[str] + """Look for missing methods on "old type" host implementation. Method is used for validation of implemented functions related to loading. Checks only existence of methods. Args: - HostBase: Object of host where to look for required methods. + Union[ModuleType, HostBase]: Object of host where to look for required methods. Returns: list[str]: Missing method implementations for loading workflow. """ + if isinstance(host, ILoadHost): + return [] + required = ["ls"] missing = [] for name in required: @@ -223,11 +226,11 @@ class ILoadHost: @staticmethod def validate_load_methods(host): - # type: (HostBase) -> None - """Validate implemented methods of host for load workflow. + # type: (Union[ModuleType, HostBase]) -> None + """Validate implemented methods of "old type" host for load workflow. Args: - HostBase: Object of host to validate. + Union[ModuleType, HostBase]: Object of host to validate. Raises: MissingMethodsError: If there are missing methods on host @@ -238,8 +241,8 @@ class ILoadHost: raise MissingMethodsError(host, missing) @abstractmethod - def ls(self): - # type: (HostBase) -> List[Mapping[str, Any]] + def get_referenced_containers(self): + # type: () -> List[Mapping[str, Any]] """Retreive referenced containers from scene. This can be implemented in hosts where referencing can be used. @@ -251,33 +254,42 @@ class ILoadHost: Returns: list[dict]: Information about loaded containers. """ - return [] + + pass + + # --- Deprecated method names --- + def ls(self): + """Deprecated variant of 'get_referenced_containers'. + + Todo: + Remove when all usages are replaced. + """ + + return self.get_referenced_containers() @six.add_metaclass(ABCMeta) class IWorkfileHost: - """Implementation requirements to be able use workfile utils and tool. - - This interface just provides what is needed to implement in host - implementation to support workfiles workflow, but does not have necessarily - to inherit from this interface. - """ + """Implementation requirements to be able use workfile utils and tool.""" @staticmethod def get_missing_workfile_methods(host): - # type: (HostBase) -> List[str] - """Look for missing methods on host implementation. + # type: (Union[ModuleType, HostBase]) -> List[str] + """Look for missing methods on "old type" host implementation. Method is used for validation of implemented functions related to workfiles. Checks only existence of methods. Args: - HostBase: Object of host where to look for required methods. + Union[ModuleType, HostBase]: Object of host where to look for required methods. Returns: list[str]: Missing method implementations for workfiles workflow. """ + if isinstance(host, IWorkfileHost): + return [] + required = [ "open_file", "save_file", @@ -294,42 +306,37 @@ class IWorkfileHost: @staticmethod def validate_workfile_methods(host): - # type: (HostBase) -> None - """Validate implemented methods of host for workfiles workflow. + # type: (Union[ModuleType, HostBase]) -> None + """Validate methods of "old type" host for workfiles workflow. Args: - HostBase: Object of host to validate. + Union[ModuleType, HostBase]: Object of host to validate. Raises: MissingMethodsError: If there are missing methods on host implementation. """ + missing = IWorkfileHost.get_missing_workfile_methods(host) if missing: raise MissingMethodsError(host, missing) @abstractmethod - def file_extensions(self): + def get_workfile_extensions(self): # type: () -> List[str] """Extensions that can be used as save. Questions: This could potentially use 'HostDefinition'. - - Todo: - Rename to 'get_workfile_extensions'. """ return [] @abstractmethod - def save_file(self, dst_path=None): + def save_current_workfile(self, dst_path=None): # type: (Optional[str]) -> None """Save currently opened scene. - Todo: - Rename to 'save_current_workfile'. - Args: dst_path (str): Where the current scene should be saved. Or use current path if 'None' is passed. @@ -338,13 +345,10 @@ class IWorkfileHost: pass @abstractmethod - def open_file(self, filepath): + def open_workfile(self, filepath): # type: (str) -> None """Open passed filepath in the host. - Todo: - Rename to 'open_workfile'. - Args: filepath (str): Path to workfile. """ @@ -352,13 +356,10 @@ class IWorkfileHost: pass @abstractmethod - def current_file(self): + def get_current_workfile(self): # type: () -> Union[str, None] """Retreive path to current opened file. - Todo: - Rename to 'get_current_workfile'. - Returns: str: Path to file which is currently opened. None: If nothing is opened. @@ -366,7 +367,7 @@ class IWorkfileHost: return None - def has_unsaved_changes(self): + def workfile_has_unsaved_changes(self): # type: () -> Union[bool, None] """Currently opened scene is saved. @@ -404,6 +405,51 @@ class IWorkfileHost: return session["AVALON_WORKDIR"] + # --- Deprecated method names --- + def file_extensions(self): + """Deprecated variant of 'get_workfile_extensions'. + + Todo: + Remove when all usages are replaced. + """ + return self.get_workfile_extensions() + + def save_file(self, dst_path=None): + """Deprecated variant of 'save_current_workfile'. + + Todo: + Remove when all usages are replaced. + """ + + self.save_current_workfile() + + def open_file(self, filepath): + """Deprecated variant of 'open_workfile'. + + Todo: + Remove when all usages are replaced. + """ + + return self.open_workfile(filepath) + + def current_file(self): + """Deprecated variant of 'get_current_workfile'. + + Todo: + Remove when all usages are replaced. + """ + + return self.get_current_workfile() + + def has_unsaved_changes(self): + """Deprecated variant of 'workfile_has_unsaved_changes'. + + Todo: + Remove when all usages are replaced. + """ + + return self.workfile_has_unsaved_changes() + class INewPublisher: """Functions related to new creation system in new publisher. @@ -411,27 +457,27 @@ class INewPublisher: New publisher is not storing information only about each created instance but also some global data. At this moment are data related only to context publish plugins but that can extend in future. - - HostBase does not have to inherit from this interface just have - to imlement mentioned all listed methods. """ @staticmethod def get_missing_publish_methods(host): - # type: (HostBase) -> List[str] - """Look for missing methods on host implementation. + # type: (Union[ModuleType, HostBase]) -> List[str] + """Look for missing methods on "old type" host implementation. Method is used for validation of implemented functions related to new publish creation. Checks only existence of methods. Args: - HostBase: Object of host where to look for required methods. + Union[ModuleType, HostBase]: Host module where to look for required methods. Returns: list[str]: Missing method implementations for new publsher workflow. """ + if isinstance(host, INewPublisher): + return [] + required = [ "get_context_data", "update_context_data", @@ -444,11 +490,11 @@ class INewPublisher: @staticmethod def validate_publish_methods(host): - # type: (HostBase) -> None - """Validate implemented methods of host for create-publish workflow. + # type: (Union[ModuleType, HostBase]) -> None + """Validate implemented methods of "old type" host. Args: - HostBase: Object of host to validate. + Union[ModuleType, HostBase]: Host module to validate. Raises: MissingMethodsError: If there are missing methods on host diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index bfb9b289e0..b8c6042e4f 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -97,25 +97,25 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): register_event_callback("taskChanged", on_task_changed) register_event_callback("workfile.save.before", before_workfile_save) - def open_file(self, filepath): + def open_workfile(self, filepath): return open_file(filepath) - def save_file(self, filepath=None): + def save_current_workfile(self, filepath=None): return save_file(filepath) def work_root(self, session): return work_root(session) - def current_file(self): + def get_current_workfile(self): return current_file() - def has_unsaved_changes(self): + def workfile_has_unsaved_changes(self): return has_unsaved_changes() - def file_extensions(self): + def get_workfile_extensions(self): return file_extensions() - def ls(self): + def get_referenced_containers(self): return ls() @contextlib.contextmanager From 779f4230bcaa6f499d1e293baf6cd6d964a15644 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Jun 2022 17:16:55 +0200 Subject: [PATCH 0811/1227] use new method names based on inheritance in workfiles tool and sceneinventory --- openpype/tools/sceneinventory/model.py | 6 +++- openpype/tools/workfiles/files_widget.py | 44 +++++++++++++++++++----- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 0bb9c4a658..2894932e96 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -6,6 +6,7 @@ from collections import defaultdict from Qt import QtCore, QtGui import qtawesome +from openpype.host import ILoadHost from openpype.client import ( get_asset_by_id, get_subset_by_id, @@ -193,7 +194,10 @@ class InventoryModel(TreeModel): host = registered_host() if not items: # for debugging or testing, injecting items from outside - items = host.ls() + if isinstance(host, ILoadHost): + items = host.get_referenced_containers() + else: + items = host.ls() self.clear() diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index a7e54471dc..8669a28f6c 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -6,6 +6,7 @@ import copy import Qt from Qt import QtWidgets, QtCore +from openpype.host import IWorkfileHost from openpype.client import get_asset_by_id from openpype.tools.utils import PlaceholderLineEdit from openpype.tools.utils.delegates import PrettyTimeDelegate @@ -125,7 +126,7 @@ class FilesWidget(QtWidgets.QWidget): filter_layout.addWidget(published_checkbox, 0) # Create the Files models - extensions = set(self.host.file_extensions()) + extensions = set(self.self._get_host_extensions()) views_widget = QtWidgets.QWidget(self) # --- Workarea view --- @@ -452,7 +453,12 @@ class FilesWidget(QtWidgets.QWidget): def open_file(self, filepath): host = self.host - if host.has_unsaved_changes(): + if isinstance(host, IWorkfileHost): + has_unsaved_changes = host.workfile_has_unsaved_changes() + else: + has_unsaved_changes = host.has_unsaved_changes() + + if has_unsaved_changes: result = self.save_changes_prompt() if result is None: # Cancel operation @@ -460,7 +466,10 @@ class FilesWidget(QtWidgets.QWidget): # Save first if has changes if result: - current_file = host.current_file() + if isinstance(host, IWorkfileHost): + current_file = host.get_current_workfile() + else: + current_file = host.current_file() if not current_file: # If the user requested to save the current scene # we can't actually automatically do so if the current @@ -471,7 +480,10 @@ class FilesWidget(QtWidgets.QWidget): return # Save current scene, continue to open file - host.save_file(current_file) + if isinstance(host, IWorkfileHost): + host.save_current_workfile(current_file) + else: + host.save_file(current_file) event_data_before = self._get_event_context_data() event_data_before["filepath"] = filepath @@ -482,7 +494,10 @@ class FilesWidget(QtWidgets.QWidget): source="workfiles.tool" ) self._enter_session() - host.open_file(filepath) + if isinstance(host, IWorkfileHost): + host.open_workfile(filepath) + else: + host.open_file(filepath) emit_event( "workfile.open.after", event_data_after, @@ -524,7 +539,7 @@ class FilesWidget(QtWidgets.QWidget): filepath = self._get_selected_filepath() extensions = [os.path.splitext(filepath)[1]] else: - extensions = self.host.file_extensions() + extensions = self._get_host_extensions() window = SaveAsDialog( parent=self, @@ -572,9 +587,14 @@ class FilesWidget(QtWidgets.QWidget): self.open_file(path) + def _get_host_extensions(self): + if isinstance(self.host, IWorkfileHost): + return self.host.get_workfile_extensions() + return self.host.file_extensions() + def on_browse_pressed(self): ext_filter = "Work File (*{0})".format( - " *".join(self.host.file_extensions()) + " *".join(self._get_host_extensions()) ) kwargs = { "caption": "Work Files", @@ -632,10 +652,16 @@ class FilesWidget(QtWidgets.QWidget): self._enter_session() if not self.published_enabled: - self.host.save_file(filepath) + if isinstance(self.host, IWorkfileHost): + self.host.save_current_workfile(filepath) + else: + self.host.save_file(filepath) else: shutil.copy(src_path, filepath) - self.host.open_file(filepath) + if isinstance(self.host, IWorkfileHost): + self.host.open_workfile(filepath) + else: + self.host.open_file(filepath) # Create extra folders create_workdir_extra_folders( From 3ab8b75ada8c5bb56359903928b78ff281ba5fad Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Jun 2022 17:18:25 +0200 Subject: [PATCH 0812/1227] fix double accessed self --- openpype/tools/workfiles/files_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 8669a28f6c..c019518d8e 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -126,7 +126,7 @@ class FilesWidget(QtWidgets.QWidget): filter_layout.addWidget(published_checkbox, 0) # Create the Files models - extensions = set(self.self._get_host_extensions()) + extensions = set(self._get_host_extensions()) views_widget = QtWidgets.QWidget(self) # --- Workarea view --- From 18859001b137ec61c281a5b7750f456f19abb27f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 23 Jun 2022 17:23:34 +0200 Subject: [PATCH 0813/1227] :bug: keep baked camera out of hierarchy --- .../plugins/publish/extract_camera_alembic.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py index a8e13e645f..054aadcbee 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py @@ -30,7 +30,7 @@ class ExtractCameraAlembic(openpype.api.Extractor): # get cameras members = instance.data['setMembers'] - cameras = cmds.ls(members, leaf=True, shapes=True, long=True, + cameras = cmds.ls(members, leaf=True, long=True, dag=True, type="camera") # validate required settings @@ -62,7 +62,21 @@ class ExtractCameraAlembic(openpype.api.Extractor): if bake_to_worldspace: job_str += ' -worldSpace' + # if baked, drop the camera hierarchy to maintain + # clean output and backwards compatibility + camera_root = cmds.listRelatives( + camera, parent=True, fullPath=True)[0] + job_str += ' -root {0}'.format(camera_root) + for member in members: + member_content = cmds.listRelatives( + member, ad=True, fullPath=True) or [] + # skip hierarchy if it contains only camera + # `member_content` will contain camera + its parents + if camera in member_content \ + and len(member_content) == len(camera.split("|")) - 2: # noqa + continue + transform = cmds.listRelatives( member, parent=True, fullPath=True) transform = transform[0] if transform else member From f2ef34c1a2b5dc531c476f5661a79bd8dfeb7d15 Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Thu, 23 Jun 2022 19:18:28 +0300 Subject: [PATCH 0814/1227] Update openpype/hosts/maya/api/lib.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplify key test. Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- openpype/hosts/maya/api/lib.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 4b76757e97..b9f23a2c0e 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2548,11 +2548,7 @@ def load_capture_preset(data=None): temp_options['headsUpDisplay'] = True if key == 'hwFogEnable': - if preset[id][key] is True: - temp_options2['hwFogEnable'] = True - - else: - temp_options2['hwFogEnable'] = False + temp_options2['hwFogEnable'] = preset[id][key] or False if key == 'hwFogStart': temp_options2['hwFogStart'] = preset[id][key] From 3af840f8881432634f776a904655be32536de000 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 24 Jun 2022 04:00:33 +0300 Subject: [PATCH 0815/1227] Add Color Options --- openpype/hosts/maya/api/lib.py | 14 ++++++++--- .../defaults/project_settings/maya.json | 9 +++---- .../schemas/schema_maya_capture.json | 25 ++++++++++++++++--- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 4b76757e97..c6585f5a8f 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2566,8 +2566,14 @@ def load_capture_preset(data=None): if key == 'hwFogFalloff': temp_options2['hwFogFalloff'] = int(preset[id][key]) - if key == 'hwFogColor': - temp_options2['hwFogColor'] = preset[id][key] + if key == 'hwFogColorR': + temp_options2['hwFogColorR'] = preset[id][key] + + if key == 'hwFogColorG': + temp_options2['hwFogColorG'] = preset[id][key] + + if key == 'hwFogColorB': + temp_options2['hwFogColorB'] = preset[id][key] if key == 'motionBlurEnable': if preset[id][key] is True: @@ -2605,7 +2611,9 @@ def load_capture_preset(data=None): 'hwFogEnd', 'hwFogAlpha', 'hwFogFalloff', - 'hwFogColor', + 'hwFogColorR', + 'hwFogColorG', + 'hwFogColorB', 'textureMaxResolution', 'motionBlurEnable', 'motionBlurSampleCount', diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 874e23400e..7057160a40 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -511,12 +511,9 @@ "hwFogStart": 0, "hwFogEnd": 0, "hwFogAlpha": 0, - "hwFogColor": [ - 158, - 53, - 53, - 255 - ], + "hwFogColorR": 0, + "hwFogColorG": 0, + "hwFogColorB": 0, "motionBlurEnable": true, "motionBlurSampleCount": 8, "motionBlurShutterOpenFraction": 0.01, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 08207824b1..42685623ce 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -303,9 +303,28 @@ "label": "Enable Fog Alpha" }, { - "type": "color", - "key": "hwFogColor", - "label": "Fog Color" + "type": "number", + "key": "hwFogColorR", + "label": "Fog Color R", + "decimal": 2, + "minimum": 0, + "maximum": 1 + }, + { + "type": "number", + "key": "hwFogColorG", + "label": "Fog Color G", + "decimal": 2, + "minimum": 0, + "maximum": 1 + }, + { + "type": "number", + "key": "hwFogColorB", + "label": "Fog Color B", + "decimal": 2, + "minimum": 0, + "maximum": 1 }, { "type": "splitter" From 0c1688eaa5d64de6935b8fbcbe43ea8074d5b443 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 24 Jun 2022 11:06:57 +0300 Subject: [PATCH 0816/1227] Fix comparison statements. --- .../maya/plugins/publish/extract_playblast.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 32cbeed81b..4ba8bd5976 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -56,23 +56,23 @@ class ExtractPlayblast(openpype.api.Extractor): width_preset = capture_presets["Resolution"]["width"] height_preset = capture_presets["Resolution"]["height"] # Set resolution variables from instance values - instance_width = instance.data.get("resolutionWidth") - instance_height = instance.data.get("resolutionHeight") + instance_width = instance.context.data.get("resolutionWidth") + instance_height = instance.context.data.get("resolutionHeight") preset['camera'] = camera # Tests if instance resolution width is set, # if it is a value other than zero, that value is # used, if not then the project settings resolution is # used - if instance_width != 0: - preset['width'] = instance.data.get("resolutionWidth") - else: + if width_preset != 0: preset["width"] = width_preset - - if instance_height != 0: - preset['height'] = instance.data.get("resolutionHeight") else: + preset['width'] = instance_width + + if height_preset != 0: preset['height'] = height_preset + else: + preset['height'] = instance_height preset['start_frame'] = start preset['end_frame'] = end From c6d473f6b0354ebc7beffd0641eef3d8ea6a79b1 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 24 Jun 2022 11:08:11 +0300 Subject: [PATCH 0817/1227] Fix comments. --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 4ba8bd5976..8378b5c22a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -55,14 +55,14 @@ class ExtractPlayblast(openpype.api.Extractor): # Set resolution variables from capture presets width_preset = capture_presets["Resolution"]["width"] height_preset = capture_presets["Resolution"]["height"] - # Set resolution variables from instance values + # Set resolution variables from asset values instance_width = instance.context.data.get("resolutionWidth") instance_height = instance.context.data.get("resolutionHeight") preset['camera'] = camera - # Tests if instance resolution width is set, + # Tests if project resolution is set, # if it is a value other than zero, that value is - # used, if not then the project settings resolution is + # used, if not then the asset resolution is # used if width_preset != 0: preset["width"] = width_preset From d0f1f897fcfb67623d21dd87a25e969ab03180a7 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 24 Jun 2022 11:18:20 +0300 Subject: [PATCH 0818/1227] Append SSAO Radius minimum. --- .../schemas/projects_schema/schemas/schema_maya_capture.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 42685623ce..d9d565943f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -259,7 +259,10 @@ { "type": "number", "key": "ssaoFilterRadius", - "label": "SSAO Filter Radius" + "label": "SSAO Filter Radius", + "decimal": 0, + "minimum": 1, + "maximum": 32 }, { "type": "number", From 54b8056238250d8699b2c59bfafd13947a88ff4d Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 24 Jun 2022 11:19:25 +0300 Subject: [PATCH 0819/1227] Adjust defaults. --- openpype/settings/defaults/project_settings/maya.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 7057160a40..293648385b 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -504,7 +504,7 @@ "ssaoEnable": true, "ssaoAmount": 0, "ssaoRadius": 0, - "ssaoFilterRadius": 0, + "ssaoFilterRadius": 1, "ssaoSamples": 8, "hwFogEnable": true, "hwFogFalloff": "0", From 59fee838079b1cbc887d36b1021360235cb09f72 Mon Sep 17 00:00:00 2001 From: Petr Dvorak Date: Fri, 24 Jun 2022 11:35:39 +0200 Subject: [PATCH 0820/1227] Changes in schema settings for AE and Harmony --- .../system_settings/applications.json | 18 ++------- .../host_settings/schema_aftereffects.json | 38 +++++++------------ .../host_settings/schema_harmony.json | 38 +++++++------------ 3 files changed, 31 insertions(+), 63 deletions(-) diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 6c90a99661..b70b59b95b 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -986,8 +986,6 @@ }, "variants": { "21": { - "enabled": true, - "variant_label": "21", "executables": { "windows": [ "c:\\Program Files (x86)\\Toon Boom Animation\\Toon Boom Harmony 21 Premium\\win64\\bin\\HarmonyPremium.exe" @@ -1005,8 +1003,6 @@ "environment": {} }, "20": { - "enabled": true, - "variant_label": "20", "executables": { "windows": [ "c:\\Program Files (x86)\\Toon Boom Animation\\Toon Boom Harmony 20 Premium\\win64\\bin\\HarmonyPremium.exe" @@ -1024,8 +1020,6 @@ "environment": {} }, "17": { - "enabled": true, - "variant_label": "17", "executables": { "windows": [ "c:\\Program Files (x86)\\Toon Boom Animation\\Toon Boom Harmony 17 Premium\\win64\\bin\\HarmonyPremium.exe" @@ -1155,11 +1149,9 @@ }, "variants": { "2020": { - "enabled": true, - "variant_label": "2020", "executables": { "windows": [ - "" + "C:\\Program Files\\Adobe\\Adobe After Effects 2020\\Support Files\\Photoshop.exe" ], "darwin": [], "linux": [] @@ -1172,11 +1164,9 @@ "environment": {} }, "2021": { - "enabled": true, - "variant_label": "2021", "executables": { "windows": [ - "C:\\Program Files\\Adobe\\Adobe After Effects 2021\\Support Files\\AfterFX.exe" + "C:\\Program Files\\Adobe\\Adobe After Effects 2021\\Support Files\\Photoshop.exe" ], "darwin": [], "linux": [] @@ -1189,11 +1179,9 @@ "environment": {} }, "2022": { - "enabled": true, - "variant_label": "2022", "executables": { "windows": [ - "C:\\Program Files\\Adobe\\Adobe After Effects 2022\\Support Files\\AfterFX.exe" + "C:\\Program Files\\Adobe\\Adobe After Effects 2022\\Support Files\\Photoshop.exe" ], "darwin": [], "linux": [] diff --git a/openpype/settings/entities/schemas/system_schema/host_settings/schema_aftereffects.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_aftereffects.json index 334c9aa235..b92a2edf85 100644 --- a/openpype/settings/entities/schemas/system_schema/host_settings/schema_aftereffects.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_aftereffects.json @@ -20,31 +20,21 @@ "type": "raw-json" }, { - "type": "dict", + "type": "dict-modifiable", "key": "variants", - "children": [ - { - "type": "schema_template", - "name": "template_host_variant", - "template_data": [ - { - "app_variant_label": "2020", - "app_variant": "2020", - "variant_skip_paths": ["use_python_2"] - }, - { - "app_variant_label": "2021", - "app_variant": "2021", - "variant_skip_paths": ["use_python_2"] - }, - { - "app_variant_label": "2022", - "app_variant": "2022", - "variant_skip_paths": ["use_python_2"] - } - ] - } - ] + "collapsible_key": true, + "use_label_wrap": false, + "object_type": { + "type": "dict", + "collapsible": true, + "children": [ + { + "type": "schema_template", + "name": "template_host_variant_items", + "skip_paths": ["use_python_2"] + } + ] + } } ] } diff --git a/openpype/settings/entities/schemas/system_schema/host_settings/schema_harmony.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_harmony.json index 69ce7735e8..d5d041d0c2 100644 --- a/openpype/settings/entities/schemas/system_schema/host_settings/schema_harmony.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_harmony.json @@ -20,31 +20,21 @@ "type": "raw-json" }, { - "type": "dict", + "type": "dict-modifiable", "key": "variants", - "children": [ - { - "type": "schema_template", - "name": "template_host_variant", - "template_data": [ - { - "app_variant_label": "21", - "app_variant": "21", - "variant_skip_paths": ["use_python_2"] - }, - { - "app_variant_label": "20", - "app_variant": "20", - "variant_skip_paths": ["use_python_2"] - }, - { - "app_variant_label": "17", - "app_variant": "17", - "variant_skip_paths": ["use_python_2"] - } - ] - } - ] + "collapsible_key": true, + "use_label_wrap": false, + "object_type": { + "type": "dict", + "collapsible": true, + "children": [ + { + "type": "schema_template", + "name": "template_host_variant_items", + "skip_paths": ["use_python_2"] + } + ] + } } ] } From 96fefa32c039fee52bc2120ab3c8c23bb63d8de6 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 24 Jun 2022 12:50:20 +0300 Subject: [PATCH 0821/1227] Add Fog Density --- .../projects_schema/schemas/schema_maya_capture.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index d9d565943f..6e5eb43dd0 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -290,6 +290,11 @@ { "2": "Exponential Squared"} ] }, + { + "type": "number", + "key": "hwFogDensity", + "label": "Fog Density" + }, { "type": "number", "key": "hwFogStart", @@ -303,7 +308,7 @@ { "type": "number", "key": "hwFogAlpha", - "label": "Enable Fog Alpha" + "label": "Fog Alpha" }, { "type": "number", From 66cce36da5646553e6ffde4ec278433c47aea202 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 24 Jun 2022 12:50:30 +0300 Subject: [PATCH 0822/1227] Adjust defaults to Maya Values --- .../defaults/project_settings/maya.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 293648385b..bd8d1fecf5 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -502,21 +502,21 @@ "twoSidedLighting": true, "lineAAEnable": true, "ssaoEnable": true, - "ssaoAmount": 0, - "ssaoRadius": 0, - "ssaoFilterRadius": 1, - "ssaoSamples": 8, + "ssaoAmount": 1, + "ssaoRadius": 16, + "ssaoFilterRadius": 16, + "ssaoSamples": 16, "hwFogEnable": true, "hwFogFalloff": "0", "hwFogStart": 0, - "hwFogEnd": 0, + "hwFogEnd": 100, "hwFogAlpha": 0, - "hwFogColorR": 0, - "hwFogColorG": 0, - "hwFogColorB": 0, + "hwFogColorR": 1.0, + "hwFogColorG": 1.0, + "hwFogColorB": 1.0, "motionBlurEnable": true, "motionBlurSampleCount": 8, - "motionBlurShutterOpenFraction": 0.01, + "motionBlurShutterOpenFraction": 0.2, "cameras": false, "clipGhosts": false, "controlVertices": false, From 5b9168b421ed91870beb11bccc2dc9bf71136370 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 24 Jun 2022 12:51:54 +0300 Subject: [PATCH 0823/1227] Append density default --- openpype/settings/defaults/project_settings/maya.json | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index bd8d1fecf5..bf6c18bb95 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -508,6 +508,7 @@ "ssaoSamples": 16, "hwFogEnable": true, "hwFogFalloff": "0", + "hwFogDensity": 0, "hwFogStart": 0, "hwFogEnd": 100, "hwFogAlpha": 0, From 76cf10a61186d38a4dd1f4c3bc33edce7d605e06 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 24 Jun 2022 13:08:47 +0300 Subject: [PATCH 0824/1227] Add missing hwFogDensity key. --- openpype/hosts/maya/api/lib.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 166f348319..924dc03729 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2537,6 +2537,9 @@ def load_capture_preset(data=None): if key == 'ssaoRadius': temp_options2['ssaoRadius'] = preset[id][key] + if key == 'hwFogDensity': + temp_options2['hwFogDensity'] = preset[id][key] + if key == 'ssaoFilterRadius': temp_options2['ssaoFilterRadius'] = preset[id][key] @@ -2610,6 +2613,7 @@ def load_capture_preset(data=None): 'hwFogColorR', 'hwFogColorG', 'hwFogColorB', + 'hwFogDensity', 'textureMaxResolution', 'motionBlurEnable', 'motionBlurSampleCount', From 3968e1607e4c08e31c16d731c8e3d7fdafcd01d6 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 24 Jun 2022 14:10:29 +0300 Subject: [PATCH 0825/1227] Change logic into correct setting. --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 8378b5c22a..ff15546033 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -65,14 +65,14 @@ class ExtractPlayblast(openpype.api.Extractor): # used, if not then the asset resolution is # used if width_preset != 0: - preset["width"] = width_preset + preset["width"] = instance_width else: - preset['width'] = instance_width + preset['width'] = width_preset if height_preset != 0: - preset['height'] = height_preset - else: preset['height'] = instance_height + else: + preset['height'] = height_preset preset['start_frame'] = start preset['end_frame'] = end From 0486b99a3438f8b70635e06087a0bbc9f9676b1d Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 24 Jun 2022 14:14:41 +0300 Subject: [PATCH 0826/1227] Change hwFogDensity into float key. --- .../schemas/projects_schema/schemas/schema_maya_capture.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 6e5eb43dd0..a87918878d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -293,7 +293,10 @@ { "type": "number", "key": "hwFogDensity", - "label": "Fog Density" + "label": "Fog Density", + "decimal": 2, + "minimum": 0, + "maximum": 1 }, { "type": "number", From 07b708bcdd3baa9004f6566d15ce8a378b47a528 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 24 Jun 2022 13:26:24 +0200 Subject: [PATCH 0827/1227] added plate to supported families --- openpype/hosts/fusion/plugins/load/load_sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/plugins/load/load_sequence.py b/openpype/hosts/fusion/plugins/load/load_sequence.py index b860abd88b..3e28f3e411 100644 --- a/openpype/hosts/fusion/plugins/load/load_sequence.py +++ b/openpype/hosts/fusion/plugins/load/load_sequence.py @@ -123,7 +123,7 @@ def loader_shift(loader, frame, relative=True): class FusionLoadSequence(load.LoaderPlugin): """Load image sequence into Fusion""" - families = ["imagesequence", "review", "render"] + families = ["imagesequence", "review", "render", "plate"] representations = ["*"] label = "Load sequence" From 9778d2747b8a09754778d50cf1d1af6af643dd14 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 24 Jun 2022 14:30:32 +0300 Subject: [PATCH 0828/1227] Revert "Change logic into correct setting." This reverts commit 3968e1607e4c08e31c16d731c8e3d7fdafcd01d6. --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index ff15546033..8378b5c22a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -65,14 +65,14 @@ class ExtractPlayblast(openpype.api.Extractor): # used, if not then the asset resolution is # used if width_preset != 0: - preset["width"] = instance_width + preset["width"] = width_preset else: - preset['width'] = width_preset + preset['width'] = instance_width if height_preset != 0: - preset['height'] = instance_height - else: preset['height'] = height_preset + else: + preset['height'] = instance_height preset['start_frame'] = start preset['end_frame'] = end From a50f73ab7f495d901fdecfa5b4ca1b3f6589160d Mon Sep 17 00:00:00 2001 From: 64qam Date: Fri, 24 Jun 2022 13:43:08 +0200 Subject: [PATCH 0829/1227] Apply suggestions from code review Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../settings/defaults/system_settings/applications.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index b70b59b95b..30b0a5cbe3 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -1151,7 +1151,7 @@ "2020": { "executables": { "windows": [ - "C:\\Program Files\\Adobe\\Adobe After Effects 2020\\Support Files\\Photoshop.exe" + "C:\\Program Files\\Adobe\\Adobe After Effects 2020\\Support Files\\AfterFX.exe" ], "darwin": [], "linux": [] @@ -1166,7 +1166,7 @@ "2021": { "executables": { "windows": [ - "C:\\Program Files\\Adobe\\Adobe After Effects 2021\\Support Files\\Photoshop.exe" + "C:\\Program Files\\Adobe\\Adobe After Effects 2021\\Support Files\\AfterFX.exe" ], "darwin": [], "linux": [] @@ -1181,7 +1181,7 @@ "2022": { "executables": { "windows": [ - "C:\\Program Files\\Adobe\\Adobe After Effects 2022\\Support Files\\Photoshop.exe" + "C:\\Program Files\\Adobe\\Adobe After Effects 2022\\Support Files\\AfterFX.exe" ], "darwin": [], "linux": [] From 87a71842cacc409007e789fcb4dbf889869ed83d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 24 Jun 2022 14:50:29 +0200 Subject: [PATCH 0830/1227] use nuke api to get expected output files instead of guessing based on files in output directory --- .../plugins/publish/extract_render_local.py | 27 ++++++++++++------- .../nuke/plugins/publish/precollect_writes.py | 25 +++++++++++++---- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_render_local.py b/openpype/hosts/nuke/plugins/publish/extract_render_local.py index 057bca11ac..1595fe03fb 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_render_local.py +++ b/openpype/hosts/nuke/plugins/publish/extract_render_local.py @@ -42,12 +42,22 @@ class NukeRenderLocal(openpype.api.Extractor): self.log.info("Start frame: {}".format(first_frame)) self.log.info("End frame: {}".format(last_frame)) - # write node url might contain nuke's ctl expressin - # as [python ...]/path... - path = node["file"].evaluate() + node_file = node["file"] + # Collecte expected filepaths for each frame + # - for cases that output is still image is first created set of + # paths which is then sorted and converted to list + expected_paths = list(sorted({ + node_file.evaluate(frame) + for frame in range(first_frame, last_frame + 1) + })) + # Extract only filenames for representation + filenames = [ + os.path.basename(filepath) + for filepath in expected_paths + ] # Ensure output directory exists. - out_dir = os.path.dirname(path) + out_dir = os.path.dirname(expected_paths[0]) if not os.path.exists(out_dir): os.makedirs(out_dir) @@ -67,12 +77,11 @@ class NukeRenderLocal(openpype.api.Extractor): if "representations" not in instance.data: instance.data["representations"] = [] - collected_frames = os.listdir(out_dir) - if len(collected_frames) == 1: + if len(filenames) == 1: repre = { 'name': ext, 'ext': ext, - 'files': collected_frames.pop(), + 'files': filenames[0], "stagingDir": out_dir } else: @@ -81,7 +90,7 @@ class NukeRenderLocal(openpype.api.Extractor): 'ext': ext, 'frameStart': "%0{}d".format( len(str(last_frame))) % first_frame, - 'files': collected_frames, + 'files': filenames, "stagingDir": out_dir } instance.data["representations"].append(repre) @@ -105,7 +114,7 @@ class NukeRenderLocal(openpype.api.Extractor): families.remove('still.local') instance.data["families"] = families - collections, remainder = clique.assemble(collected_frames) + collections, remainder = clique.assemble(filenames) self.log.info('collections: {}'.format(str(collections))) if collections: diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py index a7c07975e2..a267652f11 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -56,9 +56,21 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): first_frame = int(node["first"].getValue()) last_frame = int(node["last"].getValue()) - # get path - path = nuke.filename(node) - output_dir = os.path.dirname(path) + # Prepare expected output paths by evaluating each frame of write node + # - paths are first collected to set to avoid duplicated paths, then + # sorted and converted to list + node_file = node["file"] + expected_paths = list(sorted({ + node_file.evaluate(frame) + for frame in range(first_frame, last_frame + 1) + })) + expected_filenames = [ + os.path.basename(filepath) + for filepath in expected_paths + ] + + output_dir = os.path.dirname(expected_paths[0]) + self.log.debug('output dir: {}'.format(output_dir)) # create label @@ -83,8 +95,11 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): } try: - collected_frames = [f for f in os.listdir(output_dir) - if ext in f] + collected_frames = [ + filename + for filename in os.listdir(output_dir) + if filename in expected_filenames + ] if collected_frames: collected_frames_len = len(collected_frames) frame_start_str = "%0{}d".format( From 4c6677e3ce80ef55e08a13099f3918314f891abe Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 24 Jun 2022 15:01:16 +0200 Subject: [PATCH 0831/1227] fix missing path variable --- openpype/hosts/nuke/plugins/publish/precollect_writes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py index a267652f11..049958bd07 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -68,8 +68,8 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): os.path.basename(filepath) for filepath in expected_paths ] - - output_dir = os.path.dirname(expected_paths[0]) + path = nuke.filename(node) + output_dir = os.path.dirname(path) self.log.debug('output dir: {}'.format(output_dir)) From ca38d5d484f217ecaaf71888bf67c44707850d4c Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 24 Jun 2022 15:19:03 +0200 Subject: [PATCH 0832/1227] fix typo --- openpype/hosts/houdini/plugins/create/create_hda.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_hda.py b/openpype/hosts/houdini/plugins/create/create_hda.py index d15d5bcd29..b98da8b8bb 100644 --- a/openpype/hosts/houdini/plugins/create/create_hda.py +++ b/openpype/hosts/houdini/plugins/create/create_hda.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import hou -from openpye.client import ( +from openpype.client import ( get_asset_by_name, get_subsets, ) From ae0427bbad4db877472799b207cf1db206eadd76 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 24 Jun 2022 16:49:37 +0200 Subject: [PATCH 0833/1227] :bug: fix loading and updating vbd/bgeo sequences --- .../hosts/houdini/plugins/load/load_bgeo.py | 6 ++--- .../hosts/houdini/plugins/load/load_vdb.py | 27 +++++++------------ 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/houdini/plugins/load/load_bgeo.py b/openpype/hosts/houdini/plugins/load/load_bgeo.py index a463d51383..1c0cb81bee 100644 --- a/openpype/hosts/houdini/plugins/load/load_bgeo.py +++ b/openpype/hosts/houdini/plugins/load/load_bgeo.py @@ -70,7 +70,6 @@ class BgeoLoader(load.LoaderPlugin): # The path is either a single file or sequence in a folder. if not is_sequence: filename = path - print("single") else: filename = re.sub(r"(.*)\.(\d+)\.(bgeo.*)", "\\1.$F4.\\3", path) @@ -94,9 +93,10 @@ class BgeoLoader(load.LoaderPlugin): # Update the file path file_path = get_representation_path(representation) - file_path = self.format_path(file_path) + is_sequence = bool(representation["context"].get("frame")) + file_path = self.format_path(file_path, is_sequence) - file_node.setParms({"fileName": file_path}) + file_node.setParms({"file": file_path}) # Update attribute node.setParms({"representation": str(representation["_id"])}) diff --git a/openpype/hosts/houdini/plugins/load/load_vdb.py b/openpype/hosts/houdini/plugins/load/load_vdb.py index 9455b76b89..efbac334ab 100644 --- a/openpype/hosts/houdini/plugins/load/load_vdb.py +++ b/openpype/hosts/houdini/plugins/load/load_vdb.py @@ -31,6 +31,7 @@ class VdbLoader(load.LoaderPlugin): # Create a new geo node container = obj.createNode("geo", node_name=node_name) + is_sequence = bool(context["representation"]["context"].get("frame")) # Remove the file node, it only loads static meshes # Houdini 17 has removed the file node from the geo node @@ -40,7 +41,7 @@ class VdbLoader(load.LoaderPlugin): # Explicitly create a file node file_node = container.createNode("file", node_name=node_name) - file_node.setParms({"file": self.format_path(self.fname)}) + file_node.setParms({"file": self.format_path(self.fname, is_sequence)}) # Set display on last node file_node.setDisplayFlag(True) @@ -57,30 +58,19 @@ class VdbLoader(load.LoaderPlugin): suffix="", ) - def format_path(self, path): + @staticmethod + def format_path(path, is_sequence): """Format file path correctly for single vdb or vdb sequence.""" if not os.path.exists(path): raise RuntimeError("Path does not exist: %s" % path) # The path is either a single file or sequence in a folder. - is_single_file = os.path.isfile(path) - if is_single_file: + if not is_sequence: filename = path else: - # The path points to the publish .vdb sequence folder so we - # find the first file in there that ends with .vdb - files = sorted(os.listdir(path)) - first = next((x for x in files if x.endswith(".vdb")), None) - if first is None: - raise RuntimeError( - "Couldn't find first .vdb file of " - "sequence in: %s" % path - ) + filename = re.sub(r"(.*)\.(\d+)\.vdb$", "\\1.$F4.vdb", path) - # Set .vdb to $F.vdb - first = re.sub(r"\.(\d+)\.vdb$", ".$F.vdb", first) - - filename = os.path.join(path, first) + filename = os.path.join(path, filename) filename = os.path.normpath(filename) filename = filename.replace("\\", "/") @@ -100,7 +90,8 @@ class VdbLoader(load.LoaderPlugin): # Update the file path file_path = get_representation_path(representation) - file_path = self.format_path(file_path) + is_sequence = bool(representation["context"].get("frame")) + file_path = self.format_path(file_path, is_sequence) file_node.setParms({"file": file_path}) From a080eb00ece263ddb645f7f562934fd8f5364073 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 24 Jun 2022 17:05:45 +0200 Subject: [PATCH 0834/1227] 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 0b07036fa832adb4e408b3797060bd9d7026ede3 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 24 Jun 2022 17:34:14 +0200 Subject: [PATCH 0835/1227] :recycle: use sets for member comparsion --- .../plugins/publish/extract_camera_alembic.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py index 054aadcbee..b744bfd0fe 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py @@ -69,14 +69,19 @@ class ExtractCameraAlembic(openpype.api.Extractor): job_str += ' -root {0}'.format(camera_root) for member in members: - member_content = cmds.listRelatives( - member, ad=True, fullPath=True) or [] - # skip hierarchy if it contains only camera - # `member_content` will contain camera + its parents - if camera in member_content \ - and len(member_content) == len(camera.split("|")) - 2: # noqa - continue - + descendants = cmds.listRelatives(member, + allDescendents=True, + fullPath=True) or [] + shapes = cmds.ls(descendants, shapes=True, + noIntermediate=True, long=True) + cameras = cmds.ls(shapes, type="camera", long=True) + if cameras: + if not set(shapes) - set(cameras): + continue + self.log.warning(( + "Camera hierarchy contains additional geometry. " + "Extraction will fail.") + ) transform = cmds.listRelatives( member, parent=True, fullPath=True) transform = transform[0] if transform else member From 8412fca0b1d7786417623770f3bb866a732154be Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 24 Jun 2022 17:41:44 +0200 Subject: [PATCH 0836/1227] :recycle: refactor format function --- openpype/hosts/houdini/plugins/load/load_bgeo.py | 9 +++++---- openpype/hosts/houdini/plugins/load/load_vdb.py | 10 +++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/houdini/plugins/load/load_bgeo.py b/openpype/hosts/houdini/plugins/load/load_bgeo.py index 1c0cb81bee..b298d423bc 100644 --- a/openpype/hosts/houdini/plugins/load/load_bgeo.py +++ b/openpype/hosts/houdini/plugins/load/load_bgeo.py @@ -44,7 +44,8 @@ class BgeoLoader(load.LoaderPlugin): # Explicitly create a file node file_node = container.createNode("file", node_name=node_name) - file_node.setParms({"file": self.format_path(self.fname, is_sequence)}) + file_node.setParms( + {"file": self.format_path(self.fname, context["representation"])}) # Set display on last node file_node.setDisplayFlag(True) @@ -62,11 +63,12 @@ class BgeoLoader(load.LoaderPlugin): ) @staticmethod - def format_path(path, is_sequence): + def format_path(path, representation): """Format file path correctly for single bgeo or bgeo sequence.""" if not os.path.exists(path): raise RuntimeError("Path does not exist: %s" % path) + is_sequence = bool(representation["context"].get("frame")) # The path is either a single file or sequence in a folder. if not is_sequence: filename = path @@ -93,8 +95,7 @@ class BgeoLoader(load.LoaderPlugin): # Update the file path file_path = get_representation_path(representation) - is_sequence = bool(representation["context"].get("frame")) - file_path = self.format_path(file_path, is_sequence) + file_path = self.format_path(file_path, representation) file_node.setParms({"file": file_path}) diff --git a/openpype/hosts/houdini/plugins/load/load_vdb.py b/openpype/hosts/houdini/plugins/load/load_vdb.py index efbac334ab..c558a7a0e7 100644 --- a/openpype/hosts/houdini/plugins/load/load_vdb.py +++ b/openpype/hosts/houdini/plugins/load/load_vdb.py @@ -31,7 +31,6 @@ class VdbLoader(load.LoaderPlugin): # Create a new geo node container = obj.createNode("geo", node_name=node_name) - is_sequence = bool(context["representation"]["context"].get("frame")) # Remove the file node, it only loads static meshes # Houdini 17 has removed the file node from the geo node @@ -41,7 +40,8 @@ class VdbLoader(load.LoaderPlugin): # Explicitly create a file node file_node = container.createNode("file", node_name=node_name) - file_node.setParms({"file": self.format_path(self.fname, is_sequence)}) + file_node.setParms( + {"file": self.format_path(self.fname, context["representation"])}) # Set display on last node file_node.setDisplayFlag(True) @@ -59,11 +59,12 @@ class VdbLoader(load.LoaderPlugin): ) @staticmethod - def format_path(path, is_sequence): + def format_path(path, representation): """Format file path correctly for single vdb or vdb sequence.""" if not os.path.exists(path): raise RuntimeError("Path does not exist: %s" % path) + is_sequence = bool(representation["context"].get("frame")) # The path is either a single file or sequence in a folder. if not is_sequence: filename = path @@ -90,8 +91,7 @@ class VdbLoader(load.LoaderPlugin): # Update the file path file_path = get_representation_path(representation) - is_sequence = bool(representation["context"].get("frame")) - file_path = self.format_path(file_path, is_sequence) + file_path = self.format_path(file_path, representation) file_node.setParms({"file": file_path}) From 5ec1f8c1ccf094bf11d7f9e9bd722d4606e0d127 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 24 Jun 2022 17:47:28 +0200 Subject: [PATCH 0837/1227] 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 fcefcc74348f7f7be89c3d34caa4ca88819dbd1e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 24 Jun 2022 17:52:29 +0200 Subject: [PATCH 0838/1227] removed type hints --- openpype/host/host.py | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/openpype/host/host.py b/openpype/host/host.py index 1755c19216..94eeeb986f 100644 --- a/openpype/host/host.py +++ b/openpype/host/host.py @@ -80,7 +80,6 @@ class HostBase(object): _log = None def __init__(self): - # type: () -> None """Initialization of host. Register DCC callbacks, host specific plugin paths, targets etc. @@ -97,20 +96,17 @@ class HostBase(object): @property def log(self): - # type: () -> logging.Logger if self._log is None: self._log = logging.getLogger(self.__class__.__name__) return self._log @abstractproperty def name(self): - # type: () -> str """Host name.""" pass def get_current_context(self): - # type: () -> Mapping[str, Union[str, None]] """Get current context information. This method should be used to get current context of host. Usage of @@ -137,7 +133,6 @@ class HostBase(object): } def get_context_title(self): - # type: () -> Union[str, None] """Context title shown for UI purposes. Should return current context title if possible. @@ -170,7 +165,6 @@ class HostBase(object): @contextlib.contextmanager def maintained_selection(self): - # type: () -> None """Some functionlity will happen but selection should stay same. This is DCC specific. Some may not allow to implement this ability @@ -201,14 +195,14 @@ class ILoadHost: @staticmethod def get_missing_load_methods(host): - # type: (Union[ModuleType, HostBase]) -> List[str] """Look for missing methods on "old type" host implementation. Method is used for validation of implemented functions related to loading. Checks only existence of methods. Args: - Union[ModuleType, HostBase]: Object of host where to look for required methods. + Union[ModuleType, HostBase]: Object of host where to look for + required methods. Returns: list[str]: Missing method implementations for loading workflow. @@ -226,7 +220,6 @@ class ILoadHost: @staticmethod def validate_load_methods(host): - # type: (Union[ModuleType, HostBase]) -> None """Validate implemented methods of "old type" host for load workflow. Args: @@ -242,7 +235,6 @@ class ILoadHost: @abstractmethod def get_referenced_containers(self): - # type: () -> List[Mapping[str, Any]] """Retreive referenced containers from scene. This can be implemented in hosts where referencing can be used. @@ -274,14 +266,14 @@ class IWorkfileHost: @staticmethod def get_missing_workfile_methods(host): - # type: (Union[ModuleType, HostBase]) -> List[str] """Look for missing methods on "old type" host implementation. Method is used for validation of implemented functions related to workfiles. Checks only existence of methods. Args: - Union[ModuleType, HostBase]: Object of host where to look for required methods. + Union[ModuleType, HostBase]: Object of host where to look for + required methods. Returns: list[str]: Missing method implementations for workfiles workflow. @@ -306,7 +298,6 @@ class IWorkfileHost: @staticmethod def validate_workfile_methods(host): - # type: (Union[ModuleType, HostBase]) -> None """Validate methods of "old type" host for workfiles workflow. Args: @@ -323,7 +314,6 @@ class IWorkfileHost: @abstractmethod def get_workfile_extensions(self): - # type: () -> List[str] """Extensions that can be used as save. Questions: @@ -334,7 +324,6 @@ class IWorkfileHost: @abstractmethod def save_current_workfile(self, dst_path=None): - # type: (Optional[str]) -> None """Save currently opened scene. Args: @@ -346,7 +335,6 @@ class IWorkfileHost: @abstractmethod def open_workfile(self, filepath): - # type: (str) -> None """Open passed filepath in the host. Args: @@ -357,7 +345,6 @@ class IWorkfileHost: @abstractmethod def get_current_workfile(self): - # type: () -> Union[str, None] """Retreive path to current opened file. Returns: @@ -368,7 +355,6 @@ class IWorkfileHost: return None def workfile_has_unsaved_changes(self): - # type: () -> Union[bool, None] """Currently opened scene is saved. Not all hosts can know if current scene is saved because the API of @@ -383,7 +369,6 @@ class IWorkfileHost: return None def work_root(self, session): - # type: (Mapping[str, str]) -> str """Modify workdir per host. Default implementation keeps workdir untouched. @@ -461,14 +446,14 @@ class INewPublisher: @staticmethod def get_missing_publish_methods(host): - # type: (Union[ModuleType, HostBase]) -> List[str] """Look for missing methods on "old type" host implementation. Method is used for validation of implemented functions related to new publish creation. Checks only existence of methods. Args: - Union[ModuleType, HostBase]: Host module where to look for required methods. + Union[ModuleType, HostBase]: Host module where to look for + required methods. Returns: list[str]: Missing method implementations for new publsher @@ -490,7 +475,6 @@ class INewPublisher: @staticmethod def validate_publish_methods(host): - # type: (Union[ModuleType, HostBase]) -> None """Validate implemented methods of "old type" host. Args: @@ -506,7 +490,6 @@ class INewPublisher: @abstractmethod def get_context_data(self): - # type: () -> Mapping[str, Any] """Get global data related to creation-publishing from workfile. These data are not related to any created instance but to whole @@ -524,7 +507,6 @@ class INewPublisher: @abstractmethod def update_context_data(self, data, changes): - # type: (Mapping[str, Any], Mapping[str, Any]) -> None """Store global context data to workfile. Called when some values in context data has changed. From b4d141f1a5e7ebc04e055b0d376f4433699112cf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 24 Jun 2022 17:53:19 +0200 Subject: [PATCH 0839/1227] initial commit of docstrings --- website/docs/dev_host_implementation.md | 74 +++++++++++++++++++++++++ website/sidebars.js | 1 + 2 files changed, 75 insertions(+) create mode 100644 website/docs/dev_host_implementation.md diff --git a/website/docs/dev_host_implementation.md b/website/docs/dev_host_implementation.md new file mode 100644 index 0000000000..e3a2fff5e2 --- /dev/null +++ b/website/docs/dev_host_implementation.md @@ -0,0 +1,74 @@ +--- +id: dev_host_implementation +title: Host implementation +sidebar_label: Host implementation +toc_max_heading_level: 4 +--- + +Host is an integration of DCC but in most of cases have logic that need to be handled before DCC is launched. Then based on abilities (or purpose) of DCC the integration can support different pipeline workflows. + +## Pipeline workflows +Workflows available in OpenPype are Workfiles, Load and Create-Publish. Each of them may require some functionality available in integration (e.g. call host API to achieve certain functionality). We'll go through them later. + +## How to implement and manage host +At this moment there is not fully unified way how host should be implemented but we're working on it. Host should have a "public face" code that can be used outside of DCC and in-DCC integration code. The main reason is that in-DCC code can have specific dependencies for python modules not available out of it's process. Hosts are located in `openpype/hosts/{host name}` folder. Current code (at many places) expect that the host name has equivalent folder there. So each subfolder should be named with the name of host it represents. + +### Recommended folder structure +``` +openpype/hosts/{host name} +│ +│ # Content of DCC integration - with in-DCC imports +├─ api +│ ├─ __init__.py +│ └─ [DCC integration files] +│ +│ # Plugins related to host - dynamically imported (can contain in-DCC imports) +├─ plugins +│ ├─ create +│ │ └─ [create plugin files] +│ ├─ load +│ │ └─ [load plugin files] +│ └─ publish +│ └─ [publish plugin files] +│ +│ # Launch hooks - used to modify how application is launched +├─ hooks +│ └─ [some pre/post launch hooks] +| +│ # Code initializing host integration in-DCC (DCC specific - example from Maya) +├─ startup +│ └─ userSetup.py +│ +│ # Public interface +├─ __init__.py +└─ [other public code] +``` + +### Launch Hooks +Launch hooks are not directly connected to host implementation, but they can be used to modify launch of process which may be crutial for the implementation. Launch hook are plugins called when DCC is launched. They are processed in sequence before and after launch. Pre launch hooks can change how process of DCC is launched, e.g. change subprocess flags, modify environments or modify launch arguments. If prelaunch hook crashes the application is not launched at all. Postlaunch hooks are triggered after launch of subprocess. They can be used to change statuses in your project tracker, start timer, etc. Crashed postlaunch hooks have no effect on rest of postlaunch hooks or launched process. They can be filtered by platform, host and application and order is defined by integer value. Hooks inside host are automatically loaded (one reason why folder name should match host name) or can be defined from modules. Hooks execution share same launch context where can be stored data used across multiple hooks (please be very specific in stored keys e.g. 'project' vs. 'project_name'). For more detailed information look into `openpype/lib/applications.py`. + +### Public interface +Public face is at this moment related to launching of the DCC. At this moment there there is only option to modify environment variables before launch by implementing function `add_implementation_envs` (must be available in `openpype/hosts/{host name}/__init__.py`). The function is called after pre launch hooks, as last step before subprocess launch, to be able set environment variables crutial for proper integration. It is also good place for functions that are used in prelaunch hooks and in-DCC integration. Future plans are to be able get workfiles extensions from here. Right now workfiles extensions are hardcoded in `openpype/pipeline/constants.py` under `HOST_WORKFILE_EXTENSIONS`, we would like to handle hosts as addons similar to OpenPype modules, and more improvements which are now hardcoded. + +### Integration +We've prepared base class `HostBase` in `openpype/host/host.py` to define minimum requirements and provide some default implementations. The minimum requirement for a host is `name` attribute, this host would not be able to do much but is valid. To extend functionality we've prepared interfaces that helps to identify what is host capable of and if is possible to use certain tools with it. For those cases we defined interfaces for each workflow. `IWorkfileHost` interface add requirement to implement workfiles related methods which makes host usable in combination with Workfiles tool. `ILoadHost` interface add requirements to be able load, update, switch or remove referenced representations which should add support to use Loader and Scene Inventory tools. `INewPublisher` interface is required to be able use host with new OpenPype publish workflow. This is what must or can be implemented to allow certain functionality. `HostBase` will have more responsibility which will be taken from global variables, this process won't happen at once but will be slow to keep backwards compatibility for some time. + +#### Example +```python +from openpype.host import HostBase, IWorkfileHost, ILoadHost + + +class MayaHost(HostBase, IWorkfileHost, ILoadHost): + def open_workfile(self, filepath): + ... + + def save_current_workfile(self, filepath=None): + ... + + def get_current_workfile(self): + ... + ... +``` + +### Install integration +We have host class, now where and how to initialize it. diff --git a/website/sidebars.js b/website/sidebars.js index d4fec9fba2..0e578bd085 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -155,6 +155,7 @@ module.exports = { type: "category", label: "Hosts integrations", items: [ + "dev_host_implementation", "dev_publishing" ] } From b4817f70a6b2058aff705ebc5dcae67ff2965c15 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 24 Jun 2022 19:11:21 +0200 Subject: [PATCH 0840/1227] show what is allowed to drop in the files widget --- openpype/style/style.css | 3 + .../widgets/attribute_defs/files_widget.py | 125 ++++++++++++++++-- 2 files changed, 117 insertions(+), 11 deletions(-) diff --git a/openpype/style/style.css b/openpype/style/style.css index d76d833be1..72d12a9230 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -1418,3 +1418,6 @@ InViewButton, InViewButton:disabled { InViewButton:hover { background: rgba(255, 255, 255, 37); } +SupportLabel { + color: {color:font-disabled}; +} diff --git a/openpype/widgets/attribute_defs/files_widget.py b/openpype/widgets/attribute_defs/files_widget.py index 23cf8342b1..24e3f4bb25 100644 --- a/openpype/widgets/attribute_defs/files_widget.py +++ b/openpype/widgets/attribute_defs/files_widget.py @@ -26,26 +26,122 @@ IS_SEQUENCE_ROLE = QtCore.Qt.UserRole + 7 EXT_ROLE = QtCore.Qt.UserRole + 8 +class SupportLabel(QtWidgets.QLabel): + pass + + class DropEmpty(QtWidgets.QWidget): - _drop_enabled_text = "Drag & Drop\n(drop files here)" + _empty_extensions = "Any file" - def __init__(self, parent): + def __init__(self, single_item, allow_sequences, parent): super(DropEmpty, self).__init__(parent) - label_widget = QtWidgets.QLabel(self._drop_enabled_text, self) - label_widget.setAlignment(QtCore.Qt.AlignCenter) - label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + drop_label_widget = QtWidgets.QLabel("Drag & Drop files here", self) - layout = QtWidgets.QHBoxLayout(self) + detail_widget = QtWidgets.QWidget(self) + items_label_widget = SupportLabel(detail_widget) + extensions_label_widget = SupportLabel(detail_widget) + extensions_label_widget.setWordWrap(True) + + detail_layout = QtWidgets.QVBoxLayout(detail_widget) + detail_layout.setContentsMargins(0, 0, 0, 0) + detail_layout.addStretch(1) + detail_layout.addWidget( + items_label_widget, 0, alignment=QtCore.Qt.AlignCenter + ) + detail_layout.addWidget( + extensions_label_widget, 0, alignment=QtCore.Qt.AlignCenter + ) + + layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addSpacing(10) layout.addWidget( - label_widget, - alignment=QtCore.Qt.AlignCenter + drop_label_widget, 0, alignment=QtCore.Qt.AlignCenter ) + layout.addWidget(detail_widget, 1) layout.addSpacing(10) - self._label_widget = label_widget + for widget in ( + detail_widget, + drop_label_widget, + items_label_widget, + extensions_label_widget, + ): + if isinstance(widget, QtWidgets.QLabel): + widget.setAlignment(QtCore.Qt.AlignCenter) + widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + self._single_item = single_item + self._allow_sequences = allow_sequences + self._allowed_extensions = set() + self._allow_folders = None + + self._drop_label_widget = drop_label_widget + self._items_label_widget = items_label_widget + self._extensions_label_widget = extensions_label_widget + + self.set_allow_folders(False) + + def set_extensions(self, extensions): + if extensions: + extensions = { + ext.replace(".", "") + for ext in extensions + } + if extensions == self._allowed_extensions: + return + self._allowed_extensions = extensions + + self._update_items_label() + + def set_allow_folders(self, allowed): + if self._allow_folders == allowed: + return + + self._allow_folders = allowed + self._update_items_label() + + def _update_items_label(self): + extensions_label = "" + if self._allowed_extensions: + extensions_label = ", ".join(sorted(self._allowed_extensions)) + + allowed_items = [] + if self._allow_folders: + allowed_items.append("folder") + + if extensions_label: + allowed_items.append("file") + if self._allow_sequences: + allowed_items.append("sequence") + + num_label = "Single" + if not self._single_item: + num_label = "Multiple" + allowed_items = [item + "s" for item in allowed_items] + + if not allowed_items: + allowed_items_label = "" + elif len(allowed_items) == 1: + allowed_items_label = allowed_items[0] + elif len(allowed_items) == 2: + allowed_items_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) + + if allowed_items_label: + items_label = "{} {}".format(num_label, allowed_items_label) + if extensions_label: + items_label += " of" + else: + items_label = "It is not allowed to add anything here!" + + self._items_label_widget.setText(items_label) + self._extensions_label_widget.setText(extensions_label) def paintEvent(self, event): super(DropEmpty, self).paintEvent(event) @@ -188,7 +284,12 @@ class FilesProxyModel(QtCore.QSortFilterProxyModel): def set_allowed_extensions(self, extensions=None): if extensions is not None: - extensions = set(extensions) + _extensions = set() + for ext in set(extensions): + if not ext.startswith("."): + ext = ".{}".format(ext) + _extensions.add(ext.lower()) + extensions = _extensions if self._allowed_extensions != extensions: self._allowed_extensions = extensions @@ -444,7 +545,7 @@ class FilesWidget(QtWidgets.QFrame): super(FilesWidget, self).__init__(parent) self.setAcceptDrops(True) - empty_widget = DropEmpty(self) + empty_widget = DropEmpty(single_item, allow_sequences, self) files_model = FilesModel(single_item, allow_sequences) files_proxy_model = FilesProxyModel() @@ -519,6 +620,8 @@ class FilesWidget(QtWidgets.QFrame): def set_filters(self, folders_allowed, exts_filter): self._files_proxy_model.set_allow_folders(folders_allowed) self._files_proxy_model.set_allowed_extensions(exts_filter) + self._empty_widget.set_extensions(exts_filter) + self._empty_widget.set_allow_folders(folders_allowed) def _on_rows_inserted(self, parent_index, start_row, end_row): for row in range(start_row, end_row + 1): From 462807c2726b0cff2e351db8edc759114fb52cc3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 24 Jun 2022 19:19:25 +0200 Subject: [PATCH 0841/1227] removed unnecessary widgets --- .../widgets/attribute_defs/files_widget.py | 59 ++++++++----------- 1 file changed, 23 insertions(+), 36 deletions(-) diff --git a/openpype/widgets/attribute_defs/files_widget.py b/openpype/widgets/attribute_defs/files_widget.py index 24e3f4bb25..af5a1d130b 100644 --- a/openpype/widgets/attribute_defs/files_widget.py +++ b/openpype/widgets/attribute_defs/files_widget.py @@ -38,20 +38,8 @@ class DropEmpty(QtWidgets.QWidget): drop_label_widget = QtWidgets.QLabel("Drag & Drop files here", self) - detail_widget = QtWidgets.QWidget(self) - items_label_widget = SupportLabel(detail_widget) - extensions_label_widget = SupportLabel(detail_widget) - extensions_label_widget.setWordWrap(True) - - detail_layout = QtWidgets.QVBoxLayout(detail_widget) - detail_layout.setContentsMargins(0, 0, 0, 0) - detail_layout.addStretch(1) - detail_layout.addWidget( - items_label_widget, 0, alignment=QtCore.Qt.AlignCenter - ) - detail_layout.addWidget( - extensions_label_widget, 0, alignment=QtCore.Qt.AlignCenter - ) + items_label_widget = SupportLabel(self) + items_label_widget.setWordWrap(True) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -59,17 +47,17 @@ class DropEmpty(QtWidgets.QWidget): layout.addWidget( drop_label_widget, 0, alignment=QtCore.Qt.AlignCenter ) - layout.addWidget(detail_widget, 1) + layout.addStretch(1) + layout.addWidget( + items_label_widget, 0, alignment=QtCore.Qt.AlignCenter + ) layout.addSpacing(10) for widget in ( - detail_widget, drop_label_widget, items_label_widget, - extensions_label_widget, ): - if isinstance(widget, QtWidgets.QLabel): - widget.setAlignment(QtCore.Qt.AlignCenter) + widget.setAlignment(QtCore.Qt.AlignCenter) widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) self._single_item = single_item @@ -79,7 +67,6 @@ class DropEmpty(QtWidgets.QWidget): self._drop_label_widget = drop_label_widget self._items_label_widget = items_label_widget - self._extensions_label_widget = extensions_label_widget self.set_allow_folders(False) @@ -103,27 +90,29 @@ class DropEmpty(QtWidgets.QWidget): self._update_items_label() def _update_items_label(self): - extensions_label = "" - if self._allowed_extensions: - extensions_label = ", ".join(sorted(self._allowed_extensions)) - allowed_items = [] if self._allow_folders: allowed_items.append("folder") - if extensions_label: + if self._allowed_extensions: allowed_items.append("file") if self._allow_sequences: allowed_items.append("sequence") - num_label = "Single" if not self._single_item: - num_label = "Multiple" allowed_items = [item + "s" for item in allowed_items] if not allowed_items: - allowed_items_label = "" - elif len(allowed_items) == 1: + self._items_label_widget.setText( + "It is not allowed to add anything here!" + ) + return + + items_label = "Multiple " + if self._single_item: + items_label = "Single " + + if len(allowed_items) == 1: allowed_items_label = allowed_items[0] elif len(allowed_items) == 2: allowed_items_label = " or ".join(allowed_items) @@ -133,15 +122,13 @@ class DropEmpty(QtWidgets.QWidget): allowed_items.append(new_last_item) allowed_items_label = ", ".join(allowed_items) - if allowed_items_label: - items_label = "{} {}".format(num_label, allowed_items_label) - if extensions_label: - items_label += " of" - else: - items_label = "It is not allowed to add anything here!" + items_label += allowed_items_label + if self._allowed_extensions: + items_label += " of\n{}".format( + ", ".join(sorted(self._allowed_extensions)) + ) self._items_label_widget.setText(items_label) - self._extensions_label_widget.setText(extensions_label) def paintEvent(self, event): super(DropEmpty, self).paintEvent(event) From 3e6856b9ba01856309088df81c7dd14e1e72bf98 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 25 Jun 2022 03:51:29 +0000 Subject: [PATCH 0842/1227] [Automated] Bump version --- CHANGELOG.md | 38 ++++++++++++++------------------------ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0a9a9651d..aa720137a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,29 +1,39 @@ # Changelog -## [3.11.2-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.12.0-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.1...HEAD) ### 📖 Documentation +- Linux: update OIIO package [\#3401](https://github.com/pypeclub/OpenPype/pull/3401) - General: Add ability to change user value for templates [\#3366](https://github.com/pypeclub/OpenPype/pull/3366) - Feature/multiverse [\#3350](https://github.com/pypeclub/OpenPype/pull/3350) **🚀 Enhancements** - Hosts: More options for in-host callbacks [\#3357](https://github.com/pypeclub/OpenPype/pull/3357) -- TVPaint: Extractor use mark in/out range to render [\#3308](https://github.com/pypeclub/OpenPype/pull/3308) - Maya: Allow more data to be published along camera 🎥 [\#3304](https://github.com/pypeclub/OpenPype/pull/3304) **🐛 Bug fixes** +- Nuke: Collect representation files based on Write [\#3407](https://github.com/pypeclub/OpenPype/pull/3407) +- General: Filter representations before integration start [\#3398](https://github.com/pypeclub/OpenPype/pull/3398) +- Maya: look collector typo [\#3392](https://github.com/pypeclub/OpenPype/pull/3392) - TVPaint: Make sure exit code is set to not None [\#3382](https://github.com/pypeclub/OpenPype/pull/3382) - Maya: vray device aspect ratio fix [\#3381](https://github.com/pypeclub/OpenPype/pull/3381) - Harmony: added unc path to zifile command in Harmony [\#3372](https://github.com/pypeclub/OpenPype/pull/3372) +- Standalone: settings improvements [\#3355](https://github.com/pypeclub/OpenPype/pull/3355) +- Nuke: Load full model hierarchy by default [\#3328](https://github.com/pypeclub/OpenPype/pull/3328) **🔀 Refactored code** +- Kitsu: renaming to plural func sync\_all\_projects [\#3397](https://github.com/pypeclub/OpenPype/pull/3397) +- Hiero: Use client query functions [\#3393](https://github.com/pypeclub/OpenPype/pull/3393) +- Nuke: Use client query functions [\#3391](https://github.com/pypeclub/OpenPype/pull/3391) +- Maya: Use client query functions [\#3385](https://github.com/pypeclub/OpenPype/pull/3385) - Harmony: Use client query functions [\#3378](https://github.com/pypeclub/OpenPype/pull/3378) +- Celaction: Use client query functions [\#3376](https://github.com/pypeclub/OpenPype/pull/3376) - Photoshop: Use client query functions [\#3375](https://github.com/pypeclub/OpenPype/pull/3375) - AfterEffects: Use client query functions [\#3374](https://github.com/pypeclub/OpenPype/pull/3374) - TVPaint: Use client query functions [\#3340](https://github.com/pypeclub/OpenPype/pull/3340) @@ -49,7 +59,6 @@ - Ftrack: Removed requirement of pypeclub role from default settings [\#3354](https://github.com/pypeclub/OpenPype/pull/3354) - Kitsu: Prevent crash on missing frames information [\#3352](https://github.com/pypeclub/OpenPype/pull/3352) - Ftrack: Open browser from tray [\#3320](https://github.com/pypeclub/OpenPype/pull/3320) -- Enhancement: More control over thumbnail processing. [\#3259](https://github.com/pypeclub/OpenPype/pull/3259) **🐛 Bug fixes** @@ -63,6 +72,7 @@ - nuke: adding extract thumbnail settings 3.10 [\#3347](https://github.com/pypeclub/OpenPype/pull/3347) - General: Fix last version function [\#3345](https://github.com/pypeclub/OpenPype/pull/3345) - Deadline: added OPENPYPE\_MONGO to filter [\#3336](https://github.com/pypeclub/OpenPype/pull/3336) +- Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) **🔀 Refactored code** @@ -90,15 +100,13 @@ - General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268) - Unreal: add support for skeletalMesh and staticMesh to loaders [\#3267](https://github.com/pypeclub/OpenPype/pull/3267) - Maya: reference loaders could store placeholder in referenced url [\#3264](https://github.com/pypeclub/OpenPype/pull/3264) -- TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250) -- Nuke: Change default icon path in settings [\#3247](https://github.com/pypeclub/OpenPype/pull/3247) **🐛 Bug fixes** +- General: Handle empty source key on instance [\#3342](https://github.com/pypeclub/OpenPype/pull/3342) - Houdini: Fix Houdini VDB manage update wrong file attribute name [\#3322](https://github.com/pypeclub/OpenPype/pull/3322) - Nuke: anatomy compatibility issue hacks [\#3321](https://github.com/pypeclub/OpenPype/pull/3321) - hiero: otio p3 compatibility issue - metadata on effect use update 3.11 [\#3314](https://github.com/pypeclub/OpenPype/pull/3314) -- Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) - General: Vendorized modules for Python 2 and update poetry lock [\#3305](https://github.com/pypeclub/OpenPype/pull/3305) - Fix - added local targets to install host [\#3303](https://github.com/pypeclub/OpenPype/pull/3303) - Settings: Add missing default settings for nuke gizmo [\#3301](https://github.com/pypeclub/OpenPype/pull/3301) @@ -109,9 +117,6 @@ - Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) - General: Fix Oiio tool path resolving [\#3278](https://github.com/pypeclub/OpenPype/pull/3278) - Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) -- Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) -- Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) -- Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) **🔀 Refactored code** @@ -127,21 +132,6 @@ [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.10.0-nightly.6...3.10.0) -**🚀 Enhancements** - -- Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) -- General: updating common vendor `scriptmenu` to 1.5.2 [\#3246](https://github.com/pypeclub/OpenPype/pull/3246) - -**🐛 Bug fixes** - -- nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) -- Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244) - -**Merged pull requests:** - -- Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) -- Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249) - ## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.7...3.9.8) diff --git a/openpype/version.py b/openpype/version.py index 79e3b445f9..a30bca9f0f 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.2-nightly.1" +__version__ = "3.12.0-nightly.1" diff --git a/pyproject.toml b/pyproject.toml index 3a5acb8490..47e6453551 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.2-nightly.1" # OpenPype +version = "3.12.0-nightly.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From f25c662c37ced86fde5c22764665c55b9268edea Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Sat, 25 Jun 2022 11:21:25 +0300 Subject: [PATCH 0843/1227] Replace `hwFogEnable` with `fogging` flag. --- openpype/hosts/maya/api/lib.py | 5 ++--- openpype/settings/defaults/project_settings/maya.json | 2 +- .../schemas/projects_schema/schemas/schema_maya_capture.json | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 924dc03729..cd41ba3ffd 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2550,8 +2550,8 @@ def load_capture_preset(data=None): if key == 'headsUpDisplay': temp_options['headsUpDisplay'] = True - if key == 'hwFogEnable': - temp_options2['hwFogEnable'] = preset[id][key] or False + if key == 'fogging': + temp_options['fogging'] = preset[id][key] or False if key == 'hwFogStart': temp_options2['hwFogStart'] = preset[id][key] @@ -2605,7 +2605,6 @@ def load_capture_preset(data=None): 'ssaoAmount', 'ssaoFilterRadius', 'ssaoRadius', - 'hwFogEnable', 'hwFogStart', 'hwFogEnd', 'hwFogAlpha', diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index bf6c18bb95..77bc8118df 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -506,7 +506,7 @@ "ssaoRadius": 16, "ssaoFilterRadius": 16, "ssaoSamples": 16, - "hwFogEnable": true, + "fogging": true, "hwFogFalloff": "0", "hwFogDensity": 0, "hwFogStart": 0, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index a87918878d..2323fbaba0 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -277,7 +277,7 @@ }, { "type": "boolean", - "key": "hwFogEnable", + "key": "fogging", "label": "Enable Hardware Fog" }, { From 070e1fbe7eeae73372439d4a479960d6f5f11701 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Sat, 25 Jun 2022 17:40:02 +0200 Subject: [PATCH 0844/1227] change default project_folder_structure --- openpype/settings/defaults/project_settings/global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 9c0c6f6958..68a7b4966d 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -333,7 +333,7 @@ ] } }, - "project_folder_structure": "{\"__project_root__\": {\"prod\": {}, \"resources\": {\"footage\": {\"plates\": {}, \"offline\": {}}, \"audio\": {}, \"art_dept\": {}}, \"editorial\": {}, \"assets[ftrack.Library]\": {\"characters[ftrack]\": {}, \"locations[ftrack]\": {}}, \"shots[ftrack.Sequence]\": {\"scripts\": {}, \"editorial[ftrack.Folder]\": {}}}}", + "project_folder_structure": "{\"__project_root__\": {\"prod\": {}, \"resources\": {\"footage\": {\"plates\": {}, \"offline\": {}}, \"audio\": {}, \"art_dept\": {}}, \"editorial\": {}, \"assets\": {\"characters\": {}, \"locations\": {}}, \"shots\": {}}}", "sync_server": { "enabled": false, "config": { From 46bfbd28506be577532cb68f319a3051e63957ee Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Mon, 27 Jun 2022 06:13:35 +0300 Subject: [PATCH 0845/1227] Make appropriate feature fixes. --- .../maya/plugins/create/create_review.py | 8 +++--- .../maya/plugins/publish/collect_review.py | 4 +-- .../maya/plugins/publish/extract_playblast.py | 26 ++++++++++++------- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index 331f0818eb..83b7f34d82 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -15,8 +15,8 @@ class CreateReview(plugin.Creator): keepImages = False isolate = False imagePlane = True - resolutionWidth = 0 - resolutionHeight = 0 + attrWidth = 0 + attrHeight = 0 transparency = [ "preset", "simple", @@ -35,8 +35,8 @@ class CreateReview(plugin.Creator): for key, value in animation_data.items(): data[key] = value - data["resolutionWidth"] = self.resolutionWidth - data["resolutionHeight"] = self.resolutionHeight + data["attrWidth"] = self.attrWidth + data["attrHeight"] = self.attrHeight data["isolate"] = self.isolate data["keepImages"] = self.keepImages data["imagePlane"] = self.imagePlane diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index 0769747205..83c4760535 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -71,8 +71,8 @@ class CollectReview(pyblish.api.InstancePlugin): data['handles'] = instance.data.get('handles', None) data['step'] = instance.data['step'] data['fps'] = instance.data['fps'] - data['resolutionWidth'] = instance.data['resolutionWidth'] - data['resolutionHeight'] = instance.data['resolutionHeight'] + data['attrWidth'] = instance.data['attrWidth'] + data['attrHeight'] = instance.data['attrHeight'] data["isolate"] = instance.data["isolate"] cmds.setAttr(str(instance) + '.active', 1) self.log.debug('data {}'.format(instance.context[i].data)) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 7cd829dafb..6f7dd5e16a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -1,6 +1,7 @@ import os import glob import contextlib + import clique import capture @@ -56,23 +57,30 @@ class ExtractPlayblast(openpype.api.Extractor): width_preset = capture_presets["Resolution"]["width"] height_preset = capture_presets["Resolution"]["height"] # Set resolution variables from asset values - instance_width = instance.context.data.get("resolutionWidth") - instance_height = instance.context.data.get("resolutionHeight") + asset_width = instance.data.get("resolutionWidth") + asset_height = instance.data.get("resolutionHeight") + review_instance_width = instance.data.get("attrWidth") + review_instance_height = instance.data.get("attrHeight") preset['camera'] = camera # Tests if project resolution is set, # if it is a value other than zero, that value is # used, if not then the asset resolution is # used - if width_preset != 0: - preset["width"] = width_preset - else: - preset['width'] = instance_width - if height_preset != 0: + if review_instance_width != 0: + preset['width'] = review_instance_width + elif width_preset == 0: + preset['width'] = asset_width + elif width_preset != 0: + preset['width'] = width_preset + + if review_instance_height != 0: + preset['height'] = review_instance_height + elif height_preset == 0: + preset['height'] = asset_height + elif height_preset != 0: preset['height'] = height_preset - else: - preset['height'] = instance_height preset['start_frame'] = start preset['end_frame'] = end From 37c98c045e7047a6e35ee807ba476a916c04a84b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 27 Jun 2022 10:14:43 +0200 Subject: [PATCH 0846/1227] added spacing --- openpype/widgets/attribute_defs/files_widget.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/widgets/attribute_defs/files_widget.py b/openpype/widgets/attribute_defs/files_widget.py index af5a1d130b..3135da6691 100644 --- a/openpype/widgets/attribute_defs/files_widget.py +++ b/openpype/widgets/attribute_defs/files_widget.py @@ -47,6 +47,7 @@ class DropEmpty(QtWidgets.QWidget): layout.addWidget( drop_label_widget, 0, alignment=QtCore.Qt.AlignCenter ) + layout.addSpacing(10) layout.addStretch(1) layout.addWidget( items_label_widget, 0, alignment=QtCore.Qt.AlignCenter From 798734fdd8906fc1f2c9fe67e131bc8f8b85ff25 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 27 Jun 2022 10:37:33 +0200 Subject: [PATCH 0847/1227] fix keyword argument --- openpype/hosts/nuke/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 4b69747275..45a1e72703 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -777,7 +777,7 @@ def check_inventory_versions(): # Find representations based on found containers repre_docs = get_representations( project_name, - repre_ids=repre_ids, + representation_ids=repre_ids, fields=["_id", "parent"] ) # Store representations by id and collect version ids From b74e144dbb6d9fda8ac701d957d7c9fa98005139 Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Mon, 27 Jun 2022 12:15:13 +0300 Subject: [PATCH 0848/1227] Update openpype/settings/defaults/project_settings/maya.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- openpype/settings/defaults/project_settings/maya.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 77bc8118df..2f1dca6978 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -506,7 +506,7 @@ "ssaoRadius": 16, "ssaoFilterRadius": 16, "ssaoSamples": 16, - "fogging": true, + "fogging": false, "hwFogFalloff": "0", "hwFogDensity": 0, "hwFogStart": 0, From 800a2f90d2baaf59ad662a42a920012fac827931 Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Mon, 27 Jun 2022 12:15:21 +0300 Subject: [PATCH 0849/1227] Update openpype/settings/defaults/project_settings/maya.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- openpype/settings/defaults/project_settings/maya.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 2f1dca6978..117b260853 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -515,7 +515,7 @@ "hwFogColorR": 1.0, "hwFogColorG": 1.0, "hwFogColorB": 1.0, - "motionBlurEnable": true, + "motionBlurEnable": false, "motionBlurSampleCount": 8, "motionBlurShutterOpenFraction": 0.2, "cameras": false, From bd07eaf262ddd017eb9fc80541df60b06e048a50 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Mon, 27 Jun 2022 09:46:14 +0000 Subject: [PATCH 0850/1227] [Automated] Bump version --- CHANGELOG.md | 5 +++-- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa720137a3..d118bbff54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [3.12.0-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.12.0-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.1...HEAD) @@ -8,7 +8,7 @@ - Linux: update OIIO package [\#3401](https://github.com/pypeclub/OpenPype/pull/3401) - General: Add ability to change user value for templates [\#3366](https://github.com/pypeclub/OpenPype/pull/3366) -- Feature/multiverse [\#3350](https://github.com/pypeclub/OpenPype/pull/3350) +- Multiverse: expose some settings to GUI [\#3350](https://github.com/pypeclub/OpenPype/pull/3350) **🚀 Enhancements** @@ -17,6 +17,7 @@ **🐛 Bug fixes** +- Nuke: Fix keyword argument in query function [\#3414](https://github.com/pypeclub/OpenPype/pull/3414) - Nuke: Collect representation files based on Write [\#3407](https://github.com/pypeclub/OpenPype/pull/3407) - General: Filter representations before integration start [\#3398](https://github.com/pypeclub/OpenPype/pull/3398) - Maya: look collector typo [\#3392](https://github.com/pypeclub/OpenPype/pull/3392) diff --git a/openpype/version.py b/openpype/version.py index a30bca9f0f..02f928d83c 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.12.0-nightly.1" +__version__ = "3.12.0-nightly.2" diff --git a/pyproject.toml b/pyproject.toml index 47e6453551..a159559763 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.12.0-nightly.1" # OpenPype +version = "3.12.0-nightly.2" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From c3ffa95eceb7023c9c57853ffe5eb86e95896cbe Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Mon, 27 Jun 2022 13:16:35 +0300 Subject: [PATCH 0851/1227] Fix pyenv typo --- website/docs/dev_build.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/dev_build.md b/website/docs/dev_build.md index c797326ce6..4e80f6e19d 100644 --- a/website/docs/dev_build.md +++ b/website/docs/dev_build.md @@ -214,7 +214,7 @@ $ brew install cmake 3) Install [pyenv](https://github.com/pyenv/pyenv): ```shell $ brew install pyenv -$ echo 'eval "$(pypenv init -)"' >> ~/.zshrc +$ echo 'eval "$(pyenv init -)"' >> ~/.zshrc $ pyenv init $ exec "$SHELL" $ PATH=$(pyenv root)/shims:$PATH From 6af7f906e5b3cde5d76aac36676e2a3ee3f18ac6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 27 Jun 2022 13:19:15 +0200 Subject: [PATCH 0852/1227] added host installation and usage of tools in host --- website/docs/dev_host_implementation.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/website/docs/dev_host_implementation.md b/website/docs/dev_host_implementation.md index e3a2fff5e2..3702483ad1 100644 --- a/website/docs/dev_host_implementation.md +++ b/website/docs/dev_host_implementation.md @@ -14,7 +14,7 @@ Workflows available in OpenPype are Workfiles, Load and Create-Publish. Each of At this moment there is not fully unified way how host should be implemented but we're working on it. Host should have a "public face" code that can be used outside of DCC and in-DCC integration code. The main reason is that in-DCC code can have specific dependencies for python modules not available out of it's process. Hosts are located in `openpype/hosts/{host name}` folder. Current code (at many places) expect that the host name has equivalent folder there. So each subfolder should be named with the name of host it represents. ### Recommended folder structure -``` +```python openpype/hosts/{host name} │ │ # Content of DCC integration - with in-DCC imports @@ -51,7 +51,7 @@ Launch hooks are not directly connected to host implementation, but they can be Public face is at this moment related to launching of the DCC. At this moment there there is only option to modify environment variables before launch by implementing function `add_implementation_envs` (must be available in `openpype/hosts/{host name}/__init__.py`). The function is called after pre launch hooks, as last step before subprocess launch, to be able set environment variables crutial for proper integration. It is also good place for functions that are used in prelaunch hooks and in-DCC integration. Future plans are to be able get workfiles extensions from here. Right now workfiles extensions are hardcoded in `openpype/pipeline/constants.py` under `HOST_WORKFILE_EXTENSIONS`, we would like to handle hosts as addons similar to OpenPype modules, and more improvements which are now hardcoded. ### Integration -We've prepared base class `HostBase` in `openpype/host/host.py` to define minimum requirements and provide some default implementations. The minimum requirement for a host is `name` attribute, this host would not be able to do much but is valid. To extend functionality we've prepared interfaces that helps to identify what is host capable of and if is possible to use certain tools with it. For those cases we defined interfaces for each workflow. `IWorkfileHost` interface add requirement to implement workfiles related methods which makes host usable in combination with Workfiles tool. `ILoadHost` interface add requirements to be able load, update, switch or remove referenced representations which should add support to use Loader and Scene Inventory tools. `INewPublisher` interface is required to be able use host with new OpenPype publish workflow. This is what must or can be implemented to allow certain functionality. `HostBase` will have more responsibility which will be taken from global variables, this process won't happen at once but will be slow to keep backwards compatibility for some time. +We've prepared base class `HostBase` in `openpype/host/host.py` to define minimum requirements and provide some default method implementations. The minimum requirement for a host is `name` attribute, this host would not be able to do much but is valid. To extend functionality we've prepared interfaces that helps to identify what is host capable of and if is possible to use certain tools with it. For those cases we defined interfaces for each workflow. `IWorkfileHost` interface add requirement to implement workfiles related methods which makes host usable in combination with Workfiles tool. `ILoadHost` interface add requirements to be able load, update, switch or remove referenced representations which should add support to use Loader and Scene Inventory tools. `INewPublisher` interface is required to be able use host with new OpenPype publish workflow. This is what must or can be implemented to allow certain functionality. `HostBase` will have more responsibility which will be taken from global variables in future. This process won't happen at once, but will be slow to keep backwards compatibility for some time. #### Example ```python @@ -71,4 +71,19 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): ``` ### Install integration -We have host class, now where and how to initialize it. +We have prepared a host class, now where and how to initialize it's object? This part is DCC specific. In DCCs like Maya with embedded python and Qt we use advantage of being able to initialize object of the class directly in DCC process on start, the same happens in Nuke, Hiero and Houdini. In DCCs like Photoshop or Harmony there is launched OpenPype (python) process next to it which handles host initialization and communication with the DCC process (e.g. using sockects). Created object of host must be installed and registered to global scope of OpenPype. Which means that at this moment one process can handle only one host at a time. + +#### Install example (Maya startup file) +```python +from openpype.pipeline import install_host +from openpype.hosts.maya.api import MayaHost + + +host = MayaHost() +install_host(host) +``` + +Function `install_host` cares about installing global plugins, callbacks and register host. Host registration means that the object is kept in memory and is accessible using `get_registered_host()`. + +### Using UI tools +Most of functionality in DCCs is provided to artists by using UI tools. We're trying to keep UIs consistent so we use same set of tools in each host, all or most of them are Qt based. There is a `HostToolsHelper` in `openpype/tools/utils/host_tools.py` which unify showing of default tools, they can be showed almost at any point. Some of them are validating if host is capable of using them (Workfiles, Loader and Scene Inventory) which is related to [pipeline workflows](#pipeline-workflows). `HostToolsHelper` provides API to show tools but host integration must care about giving artists ability to show them. Most of DCCs have some extendable menu bar where is possible to add custom actions, which is preferred approach how to give ability to show the tools. From 96218779a2f602be9f4196f2c33ca3178839153b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 27 Jun 2022 14:06:58 +0200 Subject: [PATCH 0853/1227] renamed 'save_current_workfile' -> 'save_workfile' --- openpype/host/host.py | 6 +++--- openpype/hosts/maya/api/pipeline.py | 2 +- openpype/tools/workfiles/files_widget.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/host/host.py b/openpype/host/host.py index 94eeeb986f..b7e31d0854 100644 --- a/openpype/host/host.py +++ b/openpype/host/host.py @@ -323,7 +323,7 @@ class IWorkfileHost: return [] @abstractmethod - def save_current_workfile(self, dst_path=None): + def save_workfile(self, dst_path=None): """Save currently opened scene. Args: @@ -400,13 +400,13 @@ class IWorkfileHost: return self.get_workfile_extensions() def save_file(self, dst_path=None): - """Deprecated variant of 'save_current_workfile'. + """Deprecated variant of 'save_workfile'. Todo: Remove when all usages are replaced. """ - self.save_current_workfile() + self.save_workfile() def open_file(self, filepath): """Deprecated variant of 'open_workfile'. diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index b8c6042e4f..bad77f00c9 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -100,7 +100,7 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): def open_workfile(self, filepath): return open_file(filepath) - def save_current_workfile(self, filepath=None): + def save_workfile(self, filepath=None): return save_file(filepath) def work_root(self, session): diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index c019518d8e..48ab0fc66e 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -481,7 +481,7 @@ class FilesWidget(QtWidgets.QWidget): # Save current scene, continue to open file if isinstance(host, IWorkfileHost): - host.save_current_workfile(current_file) + host.save_workfile(current_file) else: host.save_file(current_file) @@ -653,7 +653,7 @@ class FilesWidget(QtWidgets.QWidget): if not self.published_enabled: if isinstance(self.host, IWorkfileHost): - self.host.save_current_workfile(filepath) + self.host.save_workfile(filepath) else: self.host.save_file(filepath) else: From 8aa5770c0cb8e72ad95d16169080284b025f4510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Mon, 27 Jun 2022 14:09:23 +0200 Subject: [PATCH 0854/1227] :bug: fix resurfacing of avalon import --- openpype/lib/path_tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index a016aa5c25..795866756a 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -11,7 +11,7 @@ from openpype.settings import get_project_settings from .anatomy import Anatomy from .profiles_filtering import filter_profiles -import avalon.api +from openpype.pipeline import AvalonMongoDB log = logging.getLogger(__name__) @@ -204,7 +204,7 @@ def concatenate_splitted_paths(split_paths, anatomy): def get_format_data(anatomy): - dbcon = avalon.api.AvalonMongoDB() + dbcon = AvalonMongoDB() dbcon.Session["AVALON_PROJECT"] = anatomy.project_name project_doc = dbcon.find_one({"type": "project"}) project_code = project_doc["data"]["code"] From 918ee531838c283cd4ac8312215ae7351ba8fadf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 27 Jun 2022 14:10:35 +0200 Subject: [PATCH 0855/1227] renamed 'get_referenced_containers' -> 'get_containers' --- openpype/host/host.py | 8 ++++---- openpype/hosts/maya/api/pipeline.py | 2 +- openpype/tools/sceneinventory/model.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/host/host.py b/openpype/host/host.py index b7e31d0854..48907e7ec7 100644 --- a/openpype/host/host.py +++ b/openpype/host/host.py @@ -234,14 +234,14 @@ class ILoadHost: raise MissingMethodsError(host, missing) @abstractmethod - def get_referenced_containers(self): + def get_containers(self): """Retreive referenced containers from scene. This can be implemented in hosts where referencing can be used. Todo: Rename function to something more self explanatory. - Suggestion: 'get_referenced_containers' + Suggestion: 'get_containers' Returns: list[dict]: Information about loaded containers. @@ -251,13 +251,13 @@ class ILoadHost: # --- Deprecated method names --- def ls(self): - """Deprecated variant of 'get_referenced_containers'. + """Deprecated variant of 'get_containers'. Todo: Remove when all usages are replaced. """ - return self.get_referenced_containers() + return self.get_containers() @six.add_metaclass(ABCMeta) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index bad77f00c9..d08e8d1926 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -115,7 +115,7 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): def get_workfile_extensions(self): return file_extensions() - def get_referenced_containers(self): + def get_containers(self): return ls() @contextlib.contextmanager diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 2894932e96..63fbe04c5c 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -195,7 +195,7 @@ class InventoryModel(TreeModel): host = registered_host() if not items: # for debugging or testing, injecting items from outside if isinstance(host, ILoadHost): - items = host.get_referenced_containers() + items = host.get_containers() else: items = host.ls() From aa9183b2c20ff07485b3987e8fa84504833e13c1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 27 Jun 2022 14:23:42 +0200 Subject: [PATCH 0856/1227] use client query function 'get_project' --- openpype/lib/path_tools.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index 795866756a..caad20f4d6 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -6,13 +6,12 @@ import logging import six import platform +from openpype.client import get_project from openpype.settings import get_project_settings from .anatomy import Anatomy from .profiles_filtering import filter_profiles -from openpype.pipeline import AvalonMongoDB - log = logging.getLogger(__name__) @@ -204,9 +203,7 @@ def concatenate_splitted_paths(split_paths, anatomy): def get_format_data(anatomy): - dbcon = AvalonMongoDB() - dbcon.Session["AVALON_PROJECT"] = anatomy.project_name - project_doc = dbcon.find_one({"type": "project"}) + project_doc = get_project(anatomy.project_name, fields=["data.code"]) project_code = project_doc["data"]["code"] return { From 1da6eef8d3501644cfb9751e45082aba85899e3b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 27 Jun 2022 14:54:20 +0200 Subject: [PATCH 0857/1227] fix subset name change on change of creator plugin --- openpype/tools/publisher/widgets/create_dialog.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 53bbef8b75..3a68835dc7 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -977,7 +977,12 @@ class CreateDialog(QtWidgets.QDialog): elif variant: self.variant_hints_menu.addAction(variant) - self.variant_input.setText(default_variant or "Main") + variant_text = default_variant or "Main" + # Make sure subset name is updated to new plugin + if variant_text == self.variant_input.text(): + self._on_variant_change() + else: + self.variant_input.setText(variant_text) def _on_variant_widget_resize(self): self.variant_hints_btn.setFixedHeight(self.variant_input.height()) From edff8ed4005122c90e2f823385327184149647ae Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 27 Jun 2022 18:25:07 +0200 Subject: [PATCH 0858/1227] use query functions in load camera --- .../hosts/unreal/plugins/load/load_camera.py | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index e93be486b0..a61d5642c0 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -6,7 +6,7 @@ import unreal from unreal import EditorAssetLibrary from unreal import EditorLevelLibrary from unreal import EditorLevelUtils - +from openpype.client import get_assets, get_asset_by_name from openpype.pipeline import ( AVALON_CONTAINER_ID, legacy_io, @@ -24,14 +24,6 @@ class CameraLoader(plugin.Loader): icon = "cube" color = "orange" - def _get_data(self, asset_name): - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) - - return asset_doc.get("data") - def _set_sequence_hierarchy( self, seq_i, seq_j, min_frame_j, max_frame_j ): @@ -177,6 +169,19 @@ class CameraLoader(plugin.Loader): EditorLevelLibrary.save_all_dirty_levels() EditorLevelLibrary.load_level(level) + project_name = legacy_io.active_project() + # TODO refactor + # - variables does not match their meaning + # - why scene is stored to sequences? + # - asset documents vs. elements + # - cleanup variable names in whole function + # - e.g. 'asset', 'asset_name', 'asset_data', 'asset_doc' + # - this loop should be a method + # - really inefficient queries of asset documents + # - it looks like the loader cares about much more then should? + # - existing asset in scene is considered as "with correct values" + # - variable 'elements' is modified during it's loop? + # - separate into more methods (spaghetti) # Get all the sequences in the hierarchy. It will create them, if # they don't exist. sequences = [] @@ -201,26 +206,22 @@ class CameraLoader(plugin.Loader): factory=unreal.LevelSequenceFactoryNew() ) - asset_data = legacy_io.find_one({ - "type": "asset", - "name": h.split('/')[-1] - }) - - id = asset_data.get('_id') + asset_data = get_asset_by_name(project_name, h.split('/')[-1]) start_frames = [] end_frames = [] - elements = list( - legacy_io.find({"type": "asset", "data.visualParent": id})) + elements = list(get_assets( + project_name, parent_ids=[asset_data["_id"]] + )) + for e in elements: start_frames.append(e.get('data').get('clipIn')) end_frames.append(e.get('data').get('clipOut')) - elements.extend(legacy_io.find({ - "type": "asset", - "data.visualParent": e.get('_id') - })) + elements.extend(get_assets( + project_name, parent_ids=[e["_id"]] + )) min_frame = min(start_frames) max_frame = max(end_frames) @@ -256,7 +257,7 @@ class CameraLoader(plugin.Loader): sequences[i], sequences[i + 1], frame_ranges[i + 1][0], frame_ranges[i + 1][1]) - data = self._get_data(asset) + data = get_asset_by_name(project_name, asset)["data"] cam_seq.set_display_rate( unreal.FrameRate(data.get("fps"), 1.0)) cam_seq.set_playback_start(0) From 447d9eab5c178287ddc011c1ee7d40b1a60d21f8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 27 Jun 2022 18:40:35 +0200 Subject: [PATCH 0859/1227] use query functions in unreal --- .../hosts/unreal/plugins/load/load_camera.py | 16 +++++-- .../hosts/unreal/plugins/load/load_layout.py | 42 +++++++++---------- .../unreal/plugins/publish/extract_layout.py | 16 ++++--- 3 files changed, 39 insertions(+), 35 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index a61d5642c0..15adf8a5d5 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -171,6 +171,8 @@ class CameraLoader(plugin.Loader): project_name = legacy_io.active_project() # TODO refactor + # - Creationg of hierarchy should be a function in unreal integration + # - it's used in multiple loaders but must not be loader's logic # - variables does not match their meaning # - why scene is stored to sequences? # - asset documents vs. elements @@ -206,13 +208,19 @@ class CameraLoader(plugin.Loader): factory=unreal.LevelSequenceFactoryNew() ) - asset_data = get_asset_by_name(project_name, h.split('/')[-1]) + asset_data = get_asset_by_name( + project_name, + h.split('/')[-1], + fields=["_id", "data.fps"] + ) start_frames = [] end_frames = [] elements = list(get_assets( - project_name, parent_ids=[asset_data["_id"]] + project_name, + parent_ids=[asset_data["_id"]], + fields=["_id", "data.clipIn", "data.clipOut"] )) for e in elements: @@ -220,7 +228,9 @@ class CameraLoader(plugin.Loader): end_frames.append(e.get('data').get('clipOut')) elements.extend(get_assets( - project_name, parent_ids=[e["_id"]] + project_name, + parent_ids=[e["_id"]], + fields=["_id", "data.clipIn", "data.clipOut"] )) min_frame = min(start_frames) diff --git a/openpype/hosts/unreal/plugins/load/load_layout.py b/openpype/hosts/unreal/plugins/load/load_layout.py index c65cd25ac8..3f16a68ead 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout.py +++ b/openpype/hosts/unreal/plugins/load/load_layout.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- """Loader for layouts.""" -import os import json from pathlib import Path @@ -12,6 +11,7 @@ from unreal import AssetToolsHelpers from unreal import FBXImportType from unreal import MathLibrary as umath +from openpype.client import get_asset_by_name, get_assets from openpype.pipeline import ( discover_loader_plugins, loaders_from_representation, @@ -88,15 +88,6 @@ class LayoutLoader(plugin.Loader): return None - @staticmethod - def _get_data(asset_name): - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) - - return asset_doc.get("data") - @staticmethod def _set_sequence_hierarchy( seq_i, seq_j, max_frame_i, min_frame_j, max_frame_j, map_paths @@ -364,26 +355,30 @@ class LayoutLoader(plugin.Loader): factory=unreal.LevelSequenceFactoryNew() ) - asset_data = legacy_io.find_one({ - "type": "asset", - "name": h_dir.split('/')[-1] - }) - - id = asset_data.get('_id') + project_name = legacy_io.active_project() + asset_data = get_asset_by_name( + project_name, + h_dir.split('/')[-1], + fields=["_id", "data.fps"] + ) start_frames = [] end_frames = [] - elements = list( - legacy_io.find({"type": "asset", "data.visualParent": id})) + elements = list(get_assets( + project_name, + parent_ids=[asset_data["_id"]], + fields=["_id", "data.clipIn", "data.clipOut"] + )) for e in elements: start_frames.append(e.get('data').get('clipIn')) end_frames.append(e.get('data').get('clipOut')) - elements.extend(legacy_io.find({ - "type": "asset", - "data.visualParent": e.get('_id') - })) + elements.extend(get_assets( + project_name, + parent_ids=[e["_id"]], + fields=["_id", "data.clipIn", "data.clipOut"] + )) min_frame = min(start_frames) max_frame = max(end_frames) @@ -659,7 +654,8 @@ class LayoutLoader(plugin.Loader): frame_ranges[i + 1][0], frame_ranges[i + 1][1], [level]) - data = self._get_data(asset) + project_name = legacy_io.active_project() + data = get_asset_by_name(project_name, asset)["data"] shot.set_display_rate( unreal.FrameRate(data.get("fps"), 1.0)) shot.set_playback_start(0) diff --git a/openpype/hosts/unreal/plugins/publish/extract_layout.py b/openpype/hosts/unreal/plugins/publish/extract_layout.py index 87e6693a97..8924df36a7 100644 --- a/openpype/hosts/unreal/plugins/publish/extract_layout.py +++ b/openpype/hosts/unreal/plugins/publish/extract_layout.py @@ -9,6 +9,7 @@ import unreal from unreal import EditorLevelLibrary as ell from unreal import EditorAssetLibrary as eal +from openpype.client import get_representation_by_name import openpype.api from openpype.pipeline import legacy_io @@ -34,6 +35,7 @@ class ExtractLayout(openpype.api.Extractor): "Wrong level loaded" json_data = [] + project_name = legacy_io.active_project() for member in instance[:]: actor = ell.get_actor_reference(member) @@ -57,17 +59,13 @@ class ExtractLayout(openpype.api.Extractor): self.log.error("AssetContainer not found.") return - parent = eal.get_metadata_tag(asset_container, "parent") + parent_id = eal.get_metadata_tag(asset_container, "parent") family = eal.get_metadata_tag(asset_container, "family") - self.log.info("Parent: {}".format(parent)) - blend = legacy_io.find_one( - { - "type": "representation", - "parent": ObjectId(parent), - "name": "blend" - }, - projection={"_id": True}) + self.log.info("Parent: {}".format(parent_id)) + blend = get_representation_by_name( + project_name, "blend", parent_id, fields=["_id"] + ) blend_id = blend["_id"] json_element = {} From e7883fbbcca294a7b3a78e2da82445110b0545a8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 27 Jun 2022 18:42:02 +0200 Subject: [PATCH 0860/1227] removed unused import --- openpype/hosts/unreal/plugins/create/create_render.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/unreal/plugins/create/create_render.py b/openpype/hosts/unreal/plugins/create/create_render.py index a3e125a94e..950799cc10 100644 --- a/openpype/hosts/unreal/plugins/create/create_render.py +++ b/openpype/hosts/unreal/plugins/create/create_render.py @@ -1,6 +1,5 @@ import unreal -from openpype.pipeline import legacy_io from openpype.hosts.unreal.api import pipeline from openpype.hosts.unreal.api.plugin import Creator From 12eb1cc53b0e3b6023bf03596719671496edd909 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 27 Jun 2022 18:51:10 +0200 Subject: [PATCH 0861/1227] modified comments --- openpype/hosts/unreal/plugins/load/load_camera.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index 15adf8a5d5..ca6b0ce736 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -173,17 +173,15 @@ class CameraLoader(plugin.Loader): # TODO refactor # - Creationg of hierarchy should be a function in unreal integration # - it's used in multiple loaders but must not be loader's logic + # - hard to say what is purpose of the loop # - variables does not match their meaning # - why scene is stored to sequences? # - asset documents vs. elements # - cleanup variable names in whole function # - e.g. 'asset', 'asset_name', 'asset_data', 'asset_doc' - # - this loop should be a method # - really inefficient queries of asset documents - # - it looks like the loader cares about much more then should? # - existing asset in scene is considered as "with correct values" - # - variable 'elements' is modified during it's loop? - # - separate into more methods (spaghetti) + # - variable 'elements' is modified during it's loop # Get all the sequences in the hierarchy. It will create them, if # they don't exist. sequences = [] From 409bf1b6e51c9ea6ab1fe6be1aca58dda8aa0bb4 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 28 Jun 2022 05:24:42 +0300 Subject: [PATCH 0862/1227] Rename `attr` into `instance` refactor. --- openpype/hosts/maya/plugins/create/create_review.py | 4 ++-- openpype/hosts/maya/plugins/publish/collect_review.py | 4 ++-- openpype/hosts/maya/plugins/publish/extract_playblast.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index 83b7f34d82..44dc8a3158 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -35,8 +35,8 @@ class CreateReview(plugin.Creator): for key, value in animation_data.items(): data[key] = value - data["attrWidth"] = self.attrWidth - data["attrHeight"] = self.attrHeight + data["instanceHeight"] = self.attrWidth + data["instanceWidth"] = self.attrHeight data["isolate"] = self.isolate data["keepImages"] = self.keepImages data["imagePlane"] = self.imagePlane diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index 83c4760535..fec1fbfa11 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -71,8 +71,8 @@ class CollectReview(pyblish.api.InstancePlugin): data['handles'] = instance.data.get('handles', None) data['step'] = instance.data['step'] data['fps'] = instance.data['fps'] - data['attrWidth'] = instance.data['attrWidth'] - data['attrHeight'] = instance.data['attrHeight'] + data['instanceHeight'] = instance.data['instanceHeight'] + data['instanceHeight'] = instance.data['instanceHeight'] data["isolate"] = instance.data["isolate"] cmds.setAttr(str(instance) + '.active', 1) self.log.debug('data {}'.format(instance.context[i].data)) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 6f7dd5e16a..7f9875f564 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -59,8 +59,8 @@ class ExtractPlayblast(openpype.api.Extractor): # Set resolution variables from asset values asset_width = instance.data.get("resolutionWidth") asset_height = instance.data.get("resolutionHeight") - review_instance_width = instance.data.get("attrWidth") - review_instance_height = instance.data.get("attrHeight") + review_instance_width = instance.data.get("instanceWidth") + review_instance_height = instance.data.get("instanceHeight") preset['camera'] = camera # Tests if project resolution is set, From aca0c2a52b652f013922d71d27b55314c361ebe6 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 28 Jun 2022 05:25:54 +0300 Subject: [PATCH 0863/1227] Fix thumbnail extractor to match playblast resolution. --- .../maya/plugins/publish/extract_thumbnail.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index c2cefc56f1..119ad10496 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -60,7 +60,32 @@ class ExtractThumbnail(openpype.api.Extractor): "overscan": 1.0, "depthOfField": cmds.getAttr("{0}.depthOfField".format(camera)), } + capture_presets = capture_preset + # Set resolution variables from capture presets + width_preset = capture_presets["Resolution"]["width"] + height_preset = capture_presets["Resolution"]["height"] + # Set resolution variables from asset values + asset_width = instance.data.get("resolutionWidth") + asset_height = instance.data.get("resolutionHeight") + review_instance_width = instance.data.get("instanceWidth") + review_instance_height = instance.data.get("instanceHeight") + # Tests if project resolution is set, + # if it is a value other than zero, that value is + # used, if not then the asset resolution is + # used + if review_instance_width != 0: + preset['width'] = review_instance_width + elif width_preset == 0: + preset['width'] = asset_width + elif width_preset != 0: + preset['width'] = width_preset + if review_instance_height != 0: + preset['height'] = review_instance_height + elif height_preset == 0: + preset['height'] = asset_height + elif height_preset != 0: + preset['height'] = height_preset stagingDir = self.staging_dir(instance) filename = "{0}".format(instance.name) path = os.path.join(stagingDir, filename) From dd5cef560a44a513ae3395fd84f6c02c1adffff8 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 28 Jun 2022 05:56:36 +0300 Subject: [PATCH 0864/1227] Adjust setting position --- .../schemas/schema_maya_capture.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 2323fbaba0..0a63315622 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -204,14 +204,6 @@ { "type": "splitter" }, - { - "type": "number", - "key": "multiSample", - "label": "Anti Aliasing Samples", - "decimal": 0, - "minimum": 0, - "maximum": 32 - }, { "type": "splitter" }, @@ -238,6 +230,14 @@ "key": "lineAAEnable", "label": "Enable Anti-Aliasing" }, + { + "type": "number", + "key": "multiSample", + "label": "Anti Aliasing Samples", + "decimal": 0, + "minimum": 0, + "maximum": 32 + }, { "type": "splitter" }, From e9f67f8747cad536c591884701fc441f177d3574 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 28 Jun 2022 05:56:51 +0300 Subject: [PATCH 0865/1227] Adjust some defaults. --- openpype/settings/defaults/project_settings/maya.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 117b260853..bb7719dc30 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -496,19 +496,19 @@ "override_viewport_options": true, "displayLights": "default", "textureMaxResolution": 1024, - "multiSample": 4, "shadows": true, "textures": true, "twoSidedLighting": true, "lineAAEnable": true, - "ssaoEnable": true, + "multiSample": 8, + "ssaoEnable": false, "ssaoAmount": 1, "ssaoRadius": 16, "ssaoFilterRadius": 16, "ssaoSamples": 16, "fogging": false, "hwFogFalloff": "0", - "hwFogDensity": 0, + "hwFogDensity": 0.0, "hwFogStart": 0, "hwFogEnd": 100, "hwFogAlpha": 0, From 3b8b479712af6e95545539921f0439de547c460b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 28 Jun 2022 09:07:28 +0200 Subject: [PATCH 0866/1227] Skip extraction when no visible nodes found in frame range --- openpype/hosts/maya/plugins/publish/extract_animation.py | 7 +++++++ openpype/hosts/maya/plugins/publish/extract_pointcache.py | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py index b04e2bf0c4..4b650b4b26 100644 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_animation.py @@ -86,6 +86,13 @@ class ExtractAnimation(openpype.api.Extractor): nodes = get_visible_in_frame_range(nodes, start=start, end=end) + if not nodes: + self.log.warning( + "No visible nodes found in frame range {}-{}. " + "Skipping extraction because `visibleOnly` is enabled on " + "the instance.".format(start, end) + ) + return with suspended_refresh(): with maintained_selection(): diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index f582a106d9..768ee6da3d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -89,6 +89,13 @@ class ExtractAlembic(openpype.api.Extractor): nodes = get_visible_in_frame_range(nodes, start=start, end=end) + if not nodes: + self.log.warning( + "No visible nodes found in frame range {}-{}. " + "Skipping extraction because `visibleOnly` is enabled on " + "the instance.".format(start, end) + ) + return with suspended_refresh(): with maintained_selection(): From 0c674fcc61a636ebfc5e888e0f0f782dffa366f1 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 28 Jun 2022 09:15:09 +0200 Subject: [PATCH 0867/1227] expand spacing of the drop zone --- openpype/widgets/attribute_defs/files_widget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/widgets/attribute_defs/files_widget.py b/openpype/widgets/attribute_defs/files_widget.py index 3135da6691..698a91a1a5 100644 --- a/openpype/widgets/attribute_defs/files_widget.py +++ b/openpype/widgets/attribute_defs/files_widget.py @@ -43,11 +43,11 @@ class DropEmpty(QtWidgets.QWidget): layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) - layout.addSpacing(10) + layout.addSpacing(20) layout.addWidget( drop_label_widget, 0, alignment=QtCore.Qt.AlignCenter ) - layout.addSpacing(10) + layout.addSpacing(30) layout.addStretch(1) layout.addWidget( items_label_widget, 0, alignment=QtCore.Qt.AlignCenter From 46bfa3122f1f6cfbc9f70b8ad65b68974c072dd6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 28 Jun 2022 10:33:51 +0200 Subject: [PATCH 0868/1227] fix typo in typo --- openpype/lib/editorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/editorial.py b/openpype/lib/editorial.py index 2730ba1f3c..18028c5f06 100644 --- a/openpype/lib/editorial.py +++ b/openpype/lib/editorial.py @@ -65,7 +65,7 @@ def range_from_frames(*args, **kwargs): @editorial_deprecated -def frames_to_seconds(*args, **kwargs): +def frames_to_secons(*args, **kwargs): from openpype.pipeline.editorial import frames_to_seconds return frames_to_seconds(*args, **kwargs) From 843d92484df1c19b53d95bac49ea44aeb8e2a784 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 28 Jun 2022 10:42:48 +0200 Subject: [PATCH 0869/1227] added special deprecation warning error --- openpype/lib/editorial.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/openpype/lib/editorial.py b/openpype/lib/editorial.py index 18028c5f06..49220b4f15 100644 --- a/openpype/lib/editorial.py +++ b/openpype/lib/editorial.py @@ -1,7 +1,16 @@ +"""Code related to editorial utility functions was moved +to 'openpype.pipeline.editorial' please change your imports as soon as +possible. File will be probably removed in OpenPype 3.14.* +""" + import warnings import functools +class EditorialDeprecatedWarning(DeprecationWarning): + pass + + def editorial_deprecated(func): """Mark functions as deprecated. @@ -10,12 +19,13 @@ def editorial_deprecated(func): @functools.wraps(func) def new_func(*args, **kwargs): + warnings.simplefilter("always", EditorialDeprecatedWarning) warnings.warn( ( "Call to deprecated function '{}'." " Function was moved to 'openpype.pipeline.editorial'." ).format(func.__name__), - category=DeprecationWarning, + category=EditorialDeprecatedWarning, stacklevel=2 ) return func(*args, **kwargs) From f846ef45d473ba4849f4bb0ed73fc7f9fcb1a546 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 28 Jun 2022 10:52:12 +0200 Subject: [PATCH 0870/1227] removed signature validation from host registration --- openpype/pipeline/context_tools.py | 65 ------------------------------ 1 file changed, 65 deletions(-) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index 4a147c230b..f1b7b565c3 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -1,11 +1,9 @@ """Core pipeline functionality""" import os -import sys import json import types import logging -import inspect import platform import pyblish.api @@ -235,73 +233,10 @@ def register_host(host): required, or browse the source code. """ - signatures = { - "ls": [] - } - _validate_signature(host, signatures) _registered_host["_"] = host -def _validate_signature(module, signatures): - # Required signatures for each member - - missing = list() - invalid = list() - success = True - - for member in signatures: - if not hasattr(module, member): - missing.append(member) - success = False - - else: - attr = getattr(module, member) - if sys.version_info.major >= 3: - signature = inspect.getfullargspec(attr)[0] - else: - signature = inspect.getargspec(attr)[0] - required_signature = signatures[member] - - assert isinstance(signature, list) - assert isinstance(required_signature, list) - - if not all(member in signature - for member in required_signature): - invalid.append({ - "member": member, - "signature": ", ".join(signature), - "required": ", ".join(required_signature) - }) - success = False - - if not success: - report = list() - - if missing: - report.append( - "Incomplete interface for module: '%s'\n" - "Missing: %s" % (module, ", ".join( - "'%s'" % member for member in missing)) - ) - - if invalid: - report.append( - "'%s': One or more members were found, but didn't " - "have the right argument signature." % module.__name__ - ) - - for member in invalid: - report.append( - " Found: {member}({signature})".format(**member) - ) - report.append( - " Expected: {member}({required})".format(**member) - ) - - raise ValueError("\n".join(report)) - - def registered_host(): """Return currently registered host""" return _registered_host["_"] From 4450d3e7374ad1e158e8890c8ad0a16bb9845857 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 28 Jun 2022 12:44:24 +0200 Subject: [PATCH 0871/1227] add parent_ids to possible query filters --- openpype/client/entities.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 9864fee469..28cd994254 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -146,6 +146,7 @@ def _get_assets( project_name, asset_ids=None, asset_names=None, + parent_ids=None, standard=True, archived=False, fields=None @@ -161,6 +162,7 @@ def _get_assets( project_name (str): Name of project where to look for queried entities. asset_ids (list[str|ObjectId]): Asset ids that should be found. asset_names (list[str]): Name assets that should be found. + parent_ids (list[str|ObjectId]): Parent asset ids. standard (bool): Query standart assets (type 'asset'). archived (bool): Query archived assets (type 'archived_asset'). fields (list[str]): Fields that should be returned. All fields are @@ -196,6 +198,12 @@ def _get_assets( return [] query_filter["name"] = {"$in": list(asset_names)} + if parent_ids is not None: + parent_ids = _convert_ids(parent_ids) + if not parent_ids: + return [] + query_filter["data.visualParent"] = {"$in": parent_ids} + conn = _get_project_connection(project_name) return conn.find(query_filter, _prepare_fields(fields)) @@ -205,6 +213,7 @@ def get_assets( project_name, asset_ids=None, asset_names=None, + parent_ids=None, archived=False, fields=None ): @@ -219,6 +228,7 @@ def get_assets( project_name (str): Name of project where to look for queried entities. asset_ids (list[str|ObjectId]): Asset ids that should be found. asset_names (list[str]): Name assets that should be found. + parent_ids (list[str|ObjectId]): Parent asset ids. archived (bool): Add also archived assets. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -229,7 +239,13 @@ def get_assets( """ return _get_assets( - project_name, asset_ids, asset_names, True, archived, fields + project_name, + asset_ids, + asset_names, + parent_ids, + True, + archived, + fields ) @@ -237,6 +253,7 @@ def get_archived_assets( project_name, asset_ids=None, asset_names=None, + parent_ids=None, fields=None ): """Archived assets for specified project by passed filters. @@ -250,6 +267,7 @@ def get_archived_assets( project_name (str): Name of project where to look for queried entities. asset_ids (list[str|ObjectId]): Asset ids that should be found. asset_names (list[str]): Name assets that should be found. + parent_ids (list[str|ObjectId]): Parent asset ids. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -259,7 +277,7 @@ def get_archived_assets( """ return _get_assets( - project_name, asset_ids, asset_names, False, True, fields + project_name, asset_ids, asset_names, parent_ids, False, True, fields ) From 35e5f7a3d256c799c18765a3d1e712e60a28ca9b Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 28 Jun 2022 14:02:25 +0300 Subject: [PATCH 0872/1227] Expose excluded families in publisher schema. --- .../defaults/project_settings/maya.json | 3 +- .../schemas/schema_maya_publish.json | 35 +++++++++++++++---- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index cdd3a62d00..89aa7a66e6 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -204,7 +204,8 @@ "ValidateFrameRange": { "enabled": true, "optional": true, - "active": true + "active": true, + "exclude_families": [] }, "ValidateShaderName": { "enabled": false, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 41b681d893..84182973a1 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -62,13 +62,36 @@ } ] }, - { - "type": "schema_template", - "name": "template_publish_plugin", - "template_data": [ + { + "type": "dict", + "collapsible": true, + "key": "ValidateFrameRange", + "label": "Validate Frame Range", + "checkbox_key": "enabled", + "children": [ { - "key": "ValidateFrameRange", - "label": "Validate Frame Range" + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + }, + { + "type": "splitter" + }, + { + "key": "exclude_families", + "label": "Families", + "type": "list", + "object_type": "text" } ] }, From c7b5a6ac84cd8ec3ecbce11add48f7982458c770 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 28 Jun 2022 14:02:39 +0300 Subject: [PATCH 0873/1227] Add additional exclude families. --- openpype/hosts/maya/plugins/publish/validate_frame_range.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_frame_range.py b/openpype/hosts/maya/plugins/publish/validate_frame_range.py index 4415815d32..d07a8db957 100644 --- a/openpype/hosts/maya/plugins/publish/validate_frame_range.py +++ b/openpype/hosts/maya/plugins/publish/validate_frame_range.py @@ -27,7 +27,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): "yeticache"] optional = True actions = [openpype.api.RepairAction] - exclude_families = ["model"] + exclude_families = ["model", "rig", "staticMesh"] def process(self, instance): context = instance.context From 809153b1449da7b6614a296ce19e95a446882f95 Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Tue, 28 Jun 2022 14:42:11 +0300 Subject: [PATCH 0874/1227] Update openpype/settings/defaults/project_settings/maya.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move default families. Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- openpype/settings/defaults/project_settings/maya.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 89aa7a66e6..9bebd92cb9 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -205,7 +205,7 @@ "enabled": true, "optional": true, "active": true, - "exclude_families": [] + "exclude_families": ["model", "rig", "staticMesh"] }, "ValidateShaderName": { "enabled": false, From 027cdf4f3a0dfc10b96555bf9920c3e9e3287cbe Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 28 Jun 2022 14:49:31 +0300 Subject: [PATCH 0875/1227] Remove unnecessary defaults. --- openpype/hosts/maya/plugins/publish/validate_frame_range.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_frame_range.py b/openpype/hosts/maya/plugins/publish/validate_frame_range.py index d07a8db957..c51766379e 100644 --- a/openpype/hosts/maya/plugins/publish/validate_frame_range.py +++ b/openpype/hosts/maya/plugins/publish/validate_frame_range.py @@ -27,7 +27,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): "yeticache"] optional = True actions = [openpype.api.RepairAction] - exclude_families = ["model", "rig", "staticMesh"] + exclude_families = [] def process(self, instance): context = instance.context From 449824ff479723b54ef2a5547cef1399a88c9ba6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 28 Jun 2022 14:05:07 +0200 Subject: [PATCH 0876/1227] added aiohttps middlewares dependency --- poetry.lock | 17 +++++++++++++++++ pyproject.toml | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 47509f334e..7221e191ff 100644 --- a/poetry.lock +++ b/poetry.lock @@ -46,6 +46,19 @@ python-versions = ">=3.5" [package.dependencies] aiohttp = ">=3,<4" +[[package]] +name = "aiohttp-middlewares" +version = "2.0.0" +description = "Collection of useful middlewares for aiohttp applications." +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +aiohttp = ">=3.8.1,<4.0.0" +async-timeout = ">=4.0.2,<5.0.0" +yarl = ">=1.5.1,<2.0.0" + [[package]] name = "aiosignal" version = "1.2.0" @@ -1783,6 +1796,10 @@ aiohttp-json-rpc = [ {file = "aiohttp-json-rpc-0.13.3.tar.gz", hash = "sha256:6237a104478c22c6ef96c7227a01d6832597b414e4b79a52d85593356a169e99"}, {file = "aiohttp_json_rpc-0.13.3-py3-none-any.whl", hash = "sha256:4fbd197aced61bd2df7ae3237ead7d3e08833c2ccf48b8581e1828c95ebee680"}, ] +aiohttp-middlewares = [ + {file = "aiohttp-middlewares-2.0.0.tar.gz", hash = "sha256:e08ba04dc0e8fe379aa5e9444a68485c275677ee1e18c55cbb855de0c3629502"}, + {file = "aiohttp_middlewares-2.0.0-py3-none-any.whl", hash = "sha256:29cf1513176b4013844711975ff520e26a8a5d8f9fefbbddb5e91224a86b043e"}, +] aiosignal = [ {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, diff --git a/pyproject.toml b/pyproject.toml index a159559763..09dfdf45cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,6 +68,7 @@ slack-sdk = "^3.6.0" requests = "^2.25.1" pysftp = "^0.2.9" dropbox = "^11.20.0" +aiohttp-middlewares = "^2.0.0" [tool.poetry.dev-dependencies] @@ -154,4 +155,4 @@ exclude = [ ignore = ["website", "docs", ".git"] reportMissingImports = true -reportMissingTypeStubs = false \ No newline at end of file +reportMissingTypeStubs = false From 81cb958470371a635e7bc038a5907e32b394f073 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 28 Jun 2022 14:08:53 +0200 Subject: [PATCH 0877/1227] added cors middleware --- openpype/modules/webserver/cors_middleware.py | 284 ++++++++++++++++++ openpype/modules/webserver/server.py | 11 +- 2 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 openpype/modules/webserver/cors_middleware.py diff --git a/openpype/modules/webserver/cors_middleware.py b/openpype/modules/webserver/cors_middleware.py new file mode 100644 index 0000000000..0c47f9194e --- /dev/null +++ b/openpype/modules/webserver/cors_middleware.py @@ -0,0 +1,284 @@ +r""" +=============== +CORS Middleware +=============== +.. versionadded:: 0.2.0 +Dealing with CORS headers for aiohttp applications. +**IMPORTANT:** There is a `aiohttp-cors +`_ library, which handles CORS +headers by attaching additional handlers to aiohttp application for +OPTIONS (preflight) requests. In same time this CORS middleware mimics the +logic of `django-cors-headers `_, +where all handling done in the middleware without any additional handlers. This +approach allows aiohttp application to respond with CORS headers for OPTIONS or +wildcard handlers, which is not possible with ``aiohttp-cors`` due to +https://github.com/aio-libs/aiohttp-cors/issues/241 issue. +For detailed information about CORS (Cross Origin Resource Sharing) please +visit: +- `Wikipedia `_ +- Or `MDN `_ +Configuration +============= +**IMPORTANT:** By default, CORS middleware do not allow any origins to access +content from your aiohttp appliction. Which means, you need carefully check +possible options and provide custom values for your needs. +Usage +===== +.. code-block:: python + import re + from aiohttp import web + from aiohttp_middlewares import cors_middleware + from aiohttp_middlewares.cors import DEFAULT_ALLOW_HEADERS + # Unsecure configuration to allow all CORS requests + app = web.Application( + middlewares=[cors_middleware(allow_all=True)] + ) + # Allow CORS requests from URL http://localhost:3000 + app = web.Application( + middlewares=[ + cors_middleware(origins=["http://localhost:3000"]) + ] + ) + # Allow CORS requests from all localhost urls + app = web.Application( + middlewares=[ + cors_middleware( + origins=[re.compile(r"^https?\:\/\/localhost")] + ) + ] + ) + # Allow CORS requests from https://frontend.myapp.com as well + # as allow credentials + CORS_ALLOW_ORIGINS = ["https://frontend.myapp.com"] + app = web.Application( + middlewares=[ + cors_middleware( + origins=CORS_ALLOW_ORIGINS, + allow_credentials=True, + ) + ] + ) + # Allow CORS requests only for API urls + app = web.Application( + middelwares=[ + cors_middleware( + origins=CORS_ALLOW_ORIGINS, + urls=[re.compile(r"^\/api")], + ) + ] + ) + # Allow CORS requests for POST & PATCH methods, and for all + # default headers and `X-Client-UID` + app = web.Application( + middlewares=[ + cors_middleware( + origings=CORS_ALLOW_ORIGINS, + allow_methods=("POST", "PATCH"), + allow_headers=DEFAULT_ALLOW_HEADERS + + ("X-Client-UID",), + ) + ] + ) +""" + +import logging +import re +from typing import Pattern, Tuple + +from aiohttp import web + +from aiohttp_middlewares.annotations import ( + Handler, + Middleware, + StrCollection, + UrlCollection, +) +from aiohttp_middlewares.utils import match_path + + +ACCESS_CONTROL = "Access-Control" +ACCESS_CONTROL_ALLOW = f"{ACCESS_CONTROL}-Allow" +ACCESS_CONTROL_ALLOW_CREDENTIALS = f"{ACCESS_CONTROL_ALLOW}-Credentials" +ACCESS_CONTROL_ALLOW_HEADERS = f"{ACCESS_CONTROL_ALLOW}-Headers" +ACCESS_CONTROL_ALLOW_METHODS = f"{ACCESS_CONTROL_ALLOW}-Methods" +ACCESS_CONTROL_ALLOW_ORIGIN = f"{ACCESS_CONTROL_ALLOW}-Origin" +ACCESS_CONTROL_EXPOSE_HEADERS = f"{ACCESS_CONTROL}-Expose-Headers" +ACCESS_CONTROL_MAX_AGE = f"{ACCESS_CONTROL}-Max-Age" +ACCESS_CONTROL_REQUEST_METHOD = f"{ACCESS_CONTROL}-Request-Method" + +DEFAULT_ALLOW_HEADERS = ( + "accept", + "accept-encoding", + "authorization", + "content-type", + "dnt", + "origin", + "user-agent", + "x-csrftoken", + "x-requested-with", +) +DEFAULT_ALLOW_METHODS = ("DELETE", "GET", "OPTIONS", "PATCH", "POST", "PUT") +DEFAULT_URLS: Tuple[Pattern[str]] = (re.compile(r".*"),) + +logger = logging.getLogger(__name__) + + +def cors_middleware( + *, + allow_all: bool = False, + origins: UrlCollection = None, + urls: UrlCollection = None, + expose_headers: StrCollection = None, + allow_headers: StrCollection = DEFAULT_ALLOW_HEADERS, + allow_methods: StrCollection = DEFAULT_ALLOW_METHODS, + allow_credentials: bool = False, + max_age: int = None, +) -> Middleware: + """Middleware to provide CORS headers for aiohttp applications. + :param allow_all: + When enabled, allow any Origin to access content from your aiohttp web + application. **Please be careful with enabling this option as it may + result in security issues for your application.** By default: ``False`` + :param origins: + Allow content access for given list of origins. Support supplying + strings for exact origin match or regex instances. By default: ``None`` + :param urls: + Allow contect access for given list of URLs in aiohttp application. + By default: *apply CORS headers for all URLs* + :param expose_headers: + List of headers to be exposed with every CORS request. By default: + ``None`` + :param allow_headers: + List of allowed headers. By default: + .. code-block:: python + ( + "accept", + "accept-encoding", + "authorization", + "content-type", + "dnt", + "origin", + "user-agent", + "x-csrftoken", + "x-requested-with", + ) + :param allow_methods: + List of allowed methods. By default: + .. code-block:: python + ("DELETE", "GET", "OPTIONS", "PATCH", "POST", "PUT") + :param allow_credentials: + When enabled apply allow credentials header in response, which results + in sharing cookies on shared resources. **Please be careful with + allowing credentials for CORS requests.** By default: ``False`` + :param max_age: Access control max age in seconds. By default: ``None`` + """ + check_urls: UrlCollection = DEFAULT_URLS if urls is None else urls + + @web.middleware + async def middleware( + request: web.Request, handler: Handler + ) -> web.StreamResponse: + # Initial vars + request_method = request.method + request_path = request.rel_url.path + + # Is this an OPTIONS request + is_options_request = request_method == "OPTIONS" + + # Is this a preflight request + is_preflight_request = ( + is_options_request + and ACCESS_CONTROL_REQUEST_METHOD in request.headers + ) + + # Log extra data + log_extra = { + "is_preflight_request": is_preflight_request, + "method": request_method.lower(), + "path": request_path, + } + + # Check whether CORS should be enabled for given URL or not. By default + # CORS enabled for all URLs + if not match_items(check_urls, request_path): + logger.debug( + "Request should not be processed via CORS middleware", + extra=log_extra, + ) + return await handler(request) + + # If this is a preflight request - generate empty response + if is_preflight_request: + response = web.StreamResponse() + # Otherwise - call actual handler + else: + response = await handler(request) + + # Now check origin heaer + origin = request.headers.get("Origin") + # Empty origin - do nothing + if not origin: + logger.debug( + "Request does not have Origin header. CORS headers not " + "available for given requests", + extra=log_extra, + ) + return response + + # Set allow credentials header if necessary + if allow_credentials: + response.headers[ACCESS_CONTROL_ALLOW_CREDENTIALS] = "true" + + # Check whether current origin satisfies CORS policy + if not allow_all and not (origins and match_items(origins, origin)): + logger.debug( + "CORS headers not allowed for given Origin", extra=log_extra + ) + return response + + # Now start supplying CORS headers + # First one is Access-Control-Allow-Origin + if allow_all and not allow_credentials: + cors_origin = "*" + else: + cors_origin = origin + response.headers[ACCESS_CONTROL_ALLOW_ORIGIN] = cors_origin + + # Then Access-Control-Expose-Headers + if expose_headers: + response.headers[ACCESS_CONTROL_EXPOSE_HEADERS] = ", ".join( + expose_headers + ) + + # Now, if this is an options request, respond with extra Allow headers + if is_options_request: + response.headers[ACCESS_CONTROL_ALLOW_HEADERS] = ", ".join( + allow_headers + ) + response.headers[ACCESS_CONTROL_ALLOW_METHODS] = ", ".join( + allow_methods + ) + if max_age is not None: + response.headers[ACCESS_CONTROL_MAX_AGE] = str(max_age) + + # If this is preflight request - do not allow other middlewares to + # process this request + if is_preflight_request: + logger.debug( + "Provide CORS headers with empty response for preflight " + "request", + extra=log_extra, + ) + raise web.HTTPOk(text="", headers=response.headers) + + # Otherwise return normal response + logger.debug("Provide CORS headers for request", extra=log_extra) + return response + + return middleware + + + +def match_items(items: UrlCollection, value: str) -> bool: + """Go through all items and try to match item with given value.""" + return any(match_path(item, value) for item in items) diff --git a/openpype/modules/webserver/server.py b/openpype/modules/webserver/server.py index 83a29e074e..82b681f406 100644 --- a/openpype/modules/webserver/server.py +++ b/openpype/modules/webserver/server.py @@ -1,15 +1,18 @@ +import re import threading import asyncio from aiohttp import web from openpype.lib import PypeLogger +from .cors_middleware import cors_middleware log = PypeLogger.get_logger("WebServer") class WebServerManager: """Manger that care about web server thread.""" + def __init__(self, port=None, host=None): self.port = port or 8079 self.host = host or "localhost" @@ -18,7 +21,13 @@ class WebServerManager: self.handlers = {} self.on_stop_callbacks = [] - self.app = web.Application() + self.app = web.Application( + middlewares=[ + cors_middleware( + origins=[re.compile(r"^https?\:\/\/localhost")] + ) + ] + ) # add route with multiple methods for single "external app" From 4133adf5ed469f9e97a7c288d279f05cbba43ce1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 28 Jun 2022 14:10:37 +0200 Subject: [PATCH 0878/1227] removed redundant line --- openpype/modules/webserver/cors_middleware.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/webserver/cors_middleware.py b/openpype/modules/webserver/cors_middleware.py index 0c47f9194e..f1cd7b04b3 100644 --- a/openpype/modules/webserver/cors_middleware.py +++ b/openpype/modules/webserver/cors_middleware.py @@ -278,7 +278,6 @@ def cors_middleware( return middleware - def match_items(items: UrlCollection, value: str) -> bool: """Go through all items and try to match item with given value.""" return any(match_path(item, value) for item in items) From b6312221b643f1c30a7e2dee67429564c8686ff1 Mon Sep 17 00:00:00 2001 From: kaa Date: Tue, 28 Jun 2022 14:36:45 +0200 Subject: [PATCH 0879/1227] Check for python with enabled pymalloc Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/blender/hooks/pre_pyside_install.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/blender/hooks/pre_pyside_install.py b/openpype/hosts/blender/hooks/pre_pyside_install.py index d0f2b3d417..e5f66d2a26 100644 --- a/openpype/hosts/blender/hooks/pre_pyside_install.py +++ b/openpype/hosts/blender/hooks/pre_pyside_install.py @@ -102,6 +102,9 @@ class InstallPySideToBlender(PreLaunchHook): python_executable = os.path.join(python_bin, "python.exe") else: python_executable = os.path.join(python_bin, python_version) + # Check for python with enabled 'pymalloc' + if not os.path.exists(python_executable): + python_executable += "m" if not os.path.exists(python_executable): self.log.warning( From db6a061733d5f15fbea57ac44850271abb097b41 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Tue, 28 Jun 2022 13:45:54 +0000 Subject: [PATCH 0880/1227] [Automated] Bump version --- CHANGELOG.md | 28 ++++++++++++++++------------ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d118bbff54..f4535a516c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,35 +1,45 @@ # Changelog -## [3.12.0-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.12.0-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.1...HEAD) ### 📖 Documentation +- Fix typo in documentation: pyenv on mac [\#3417](https://github.com/pypeclub/OpenPype/pull/3417) - Linux: update OIIO package [\#3401](https://github.com/pypeclub/OpenPype/pull/3401) -- General: Add ability to change user value for templates [\#3366](https://github.com/pypeclub/OpenPype/pull/3366) -- Multiverse: expose some settings to GUI [\#3350](https://github.com/pypeclub/OpenPype/pull/3350) **🚀 Enhancements** +- Webserver: Added CORS middleware [\#3422](https://github.com/pypeclub/OpenPype/pull/3422) +- Attribute Defs UI: Files widget show what is allowed to drop in [\#3411](https://github.com/pypeclub/OpenPype/pull/3411) +- General: Add ability to change user value for templates [\#3366](https://github.com/pypeclub/OpenPype/pull/3366) - Hosts: More options for in-host callbacks [\#3357](https://github.com/pypeclub/OpenPype/pull/3357) +- Multiverse: expose some settings to GUI [\#3350](https://github.com/pypeclub/OpenPype/pull/3350) - Maya: Allow more data to be published along camera 🎥 [\#3304](https://github.com/pypeclub/OpenPype/pull/3304) **🐛 Bug fixes** +- NewPublisher: Fix subset name change on change of creator plugin [\#3420](https://github.com/pypeclub/OpenPype/pull/3420) +- Bug: fix invalid avalon import [\#3418](https://github.com/pypeclub/OpenPype/pull/3418) - Nuke: Fix keyword argument in query function [\#3414](https://github.com/pypeclub/OpenPype/pull/3414) +- Houdini: fix loading and updating vbd/bgeo sequences [\#3408](https://github.com/pypeclub/OpenPype/pull/3408) - Nuke: Collect representation files based on Write [\#3407](https://github.com/pypeclub/OpenPype/pull/3407) - General: Filter representations before integration start [\#3398](https://github.com/pypeclub/OpenPype/pull/3398) - Maya: look collector typo [\#3392](https://github.com/pypeclub/OpenPype/pull/3392) - TVPaint: Make sure exit code is set to not None [\#3382](https://github.com/pypeclub/OpenPype/pull/3382) - Maya: vray device aspect ratio fix [\#3381](https://github.com/pypeclub/OpenPype/pull/3381) +- Flame: bunch of publishing issues [\#3377](https://github.com/pypeclub/OpenPype/pull/3377) - Harmony: added unc path to zifile command in Harmony [\#3372](https://github.com/pypeclub/OpenPype/pull/3372) - Standalone: settings improvements [\#3355](https://github.com/pypeclub/OpenPype/pull/3355) - Nuke: Load full model hierarchy by default [\#3328](https://github.com/pypeclub/OpenPype/pull/3328) **🔀 Refactored code** +- Unreal: Use client query functions [\#3421](https://github.com/pypeclub/OpenPype/pull/3421) +- General: Move editorial lib to pipeline [\#3419](https://github.com/pypeclub/OpenPype/pull/3419) - Kitsu: renaming to plural func sync\_all\_projects [\#3397](https://github.com/pypeclub/OpenPype/pull/3397) +- Houdini: Use client query functions [\#3395](https://github.com/pypeclub/OpenPype/pull/3395) - Hiero: Use client query functions [\#3393](https://github.com/pypeclub/OpenPype/pull/3393) - Nuke: Use client query functions [\#3391](https://github.com/pypeclub/OpenPype/pull/3391) - Maya: Use client query functions [\#3385](https://github.com/pypeclub/OpenPype/pull/3385) @@ -43,6 +53,7 @@ **Merged pull requests:** +- Sync Queue: Added far future value for null values for dates [\#3371](https://github.com/pypeclub/OpenPype/pull/3371) - Maya - added support for single frame playblast review [\#3369](https://github.com/pypeclub/OpenPype/pull/3369) ## [3.11.1](https://github.com/pypeclub/OpenPype/tree/3.11.1) (2022-06-20) @@ -73,7 +84,6 @@ - nuke: adding extract thumbnail settings 3.10 [\#3347](https://github.com/pypeclub/OpenPype/pull/3347) - General: Fix last version function [\#3345](https://github.com/pypeclub/OpenPype/pull/3345) - Deadline: added OPENPYPE\_MONGO to filter [\#3336](https://github.com/pypeclub/OpenPype/pull/3336) -- Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) **🔀 Refactored code** @@ -97,17 +107,15 @@ - Ftrack: Delivery action can work on ReviewSessions [\#3307](https://github.com/pypeclub/OpenPype/pull/3307) - Maya: Look assigner UI improvements [\#3298](https://github.com/pypeclub/OpenPype/pull/3298) - Ftrack: Action to transfer values of hierarchical attributes [\#3284](https://github.com/pypeclub/OpenPype/pull/3284) -- Maya: better handling of legacy review subsets names [\#3269](https://github.com/pypeclub/OpenPype/pull/3269) -- General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268) -- Unreal: add support for skeletalMesh and staticMesh to loaders [\#3267](https://github.com/pypeclub/OpenPype/pull/3267) -- Maya: reference loaders could store placeholder in referenced url [\#3264](https://github.com/pypeclub/OpenPype/pull/3264) **🐛 Bug fixes** - General: Handle empty source key on instance [\#3342](https://github.com/pypeclub/OpenPype/pull/3342) +- General: Handle empty source key on instance [\#3341](https://github.com/pypeclub/OpenPype/pull/3341) - Houdini: Fix Houdini VDB manage update wrong file attribute name [\#3322](https://github.com/pypeclub/OpenPype/pull/3322) - Nuke: anatomy compatibility issue hacks [\#3321](https://github.com/pypeclub/OpenPype/pull/3321) - hiero: otio p3 compatibility issue - metadata on effect use update 3.11 [\#3314](https://github.com/pypeclub/OpenPype/pull/3314) +- Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) - General: Vendorized modules for Python 2 and update poetry lock [\#3305](https://github.com/pypeclub/OpenPype/pull/3305) - Fix - added local targets to install host [\#3303](https://github.com/pypeclub/OpenPype/pull/3303) - Settings: Add missing default settings for nuke gizmo [\#3301](https://github.com/pypeclub/OpenPype/pull/3301) @@ -115,9 +123,6 @@ - Maya: point cache publish handles Maya instances [\#3297](https://github.com/pypeclub/OpenPype/pull/3297) - Global: extract review slate issues [\#3286](https://github.com/pypeclub/OpenPype/pull/3286) - Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) -- Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) -- General: Fix Oiio tool path resolving [\#3278](https://github.com/pypeclub/OpenPype/pull/3278) -- Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) **🔀 Refactored code** @@ -127,7 +132,6 @@ **Merged pull requests:** - Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) -- Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) diff --git a/openpype/version.py b/openpype/version.py index 02f928d83c..54808bfa81 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.12.0-nightly.2" +__version__ = "3.12.0-nightly.3" diff --git a/pyproject.toml b/pyproject.toml index 09dfdf45cd..df5630270d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.12.0-nightly.2" # OpenPype +version = "3.12.0-nightly.3" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From f92e0f8fa3c2a07aa9b5396be2fba4f50973535e Mon Sep 17 00:00:00 2001 From: OpenPype Date: Tue, 28 Jun 2022 13:55:09 +0000 Subject: [PATCH 0881/1227] [Automated] Release --- CHANGELOG.md | 9 ++++----- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4535a516c..003ca72a24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [3.12.0-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.12.0](https://github.com/pypeclub/OpenPype/tree/3.12.0) (2022-06-28) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.1...HEAD) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.1...3.12.0) ### 📖 Documentation @@ -29,7 +29,6 @@ - Maya: look collector typo [\#3392](https://github.com/pypeclub/OpenPype/pull/3392) - TVPaint: Make sure exit code is set to not None [\#3382](https://github.com/pypeclub/OpenPype/pull/3382) - Maya: vray device aspect ratio fix [\#3381](https://github.com/pypeclub/OpenPype/pull/3381) -- Flame: bunch of publishing issues [\#3377](https://github.com/pypeclub/OpenPype/pull/3377) - Harmony: added unc path to zifile command in Harmony [\#3372](https://github.com/pypeclub/OpenPype/pull/3372) - Standalone: settings improvements [\#3355](https://github.com/pypeclub/OpenPype/pull/3355) - Nuke: Load full model hierarchy by default [\#3328](https://github.com/pypeclub/OpenPype/pull/3328) @@ -74,6 +73,7 @@ **🐛 Bug fixes** +- Flame: bunch of publishing issues [\#3377](https://github.com/pypeclub/OpenPype/pull/3377) - Nuke: bake streams with slate on farm [\#3368](https://github.com/pypeclub/OpenPype/pull/3368) - Harmony: audio validator has wrong logic [\#3364](https://github.com/pypeclub/OpenPype/pull/3364) - Nuke: Fix missing variable in extract thumbnail [\#3363](https://github.com/pypeclub/OpenPype/pull/3363) @@ -84,6 +84,7 @@ - nuke: adding extract thumbnail settings 3.10 [\#3347](https://github.com/pypeclub/OpenPype/pull/3347) - General: Fix last version function [\#3345](https://github.com/pypeclub/OpenPype/pull/3345) - Deadline: added OPENPYPE\_MONGO to filter [\#3336](https://github.com/pypeclub/OpenPype/pull/3336) +- Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) **🔀 Refactored code** @@ -111,11 +112,9 @@ **🐛 Bug fixes** - General: Handle empty source key on instance [\#3342](https://github.com/pypeclub/OpenPype/pull/3342) -- General: Handle empty source key on instance [\#3341](https://github.com/pypeclub/OpenPype/pull/3341) - Houdini: Fix Houdini VDB manage update wrong file attribute name [\#3322](https://github.com/pypeclub/OpenPype/pull/3322) - Nuke: anatomy compatibility issue hacks [\#3321](https://github.com/pypeclub/OpenPype/pull/3321) - hiero: otio p3 compatibility issue - metadata on effect use update 3.11 [\#3314](https://github.com/pypeclub/OpenPype/pull/3314) -- Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) - General: Vendorized modules for Python 2 and update poetry lock [\#3305](https://github.com/pypeclub/OpenPype/pull/3305) - Fix - added local targets to install host [\#3303](https://github.com/pypeclub/OpenPype/pull/3303) - Settings: Add missing default settings for nuke gizmo [\#3301](https://github.com/pypeclub/OpenPype/pull/3301) diff --git a/openpype/version.py b/openpype/version.py index 54808bfa81..65e439ef33 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.12.0-nightly.3" +__version__ = "3.12.0" diff --git a/pyproject.toml b/pyproject.toml index df5630270d..bd5d3ad89d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.12.0-nightly.3" # OpenPype +version = "3.12.0" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 05e709ec065e7b7fe422e49225f5c2866028162b Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 28 Jun 2022 16:41:13 +0200 Subject: [PATCH 0882/1227] create default action in custom menu redirecting to openpype hiero doc --- openpype/settings/defaults/project_settings/hiero.json | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index cec4bca8fd..3b0127c24a 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -19,14 +19,10 @@ "definition": [ { "type": "action", - "command": "import openpype.hosts.hiero.api.commands as hiero; hiero.edit_shader_definitions()", "sourcetype": "python", - "title": "Edit shader name definitions", - "tooltip": "Edit shader name definitions used in validation and renaming.", - "tags": [ - "pipeline", - "shader" - ] + "title": "OpenPype Docs", + "command": "import webbrowser;webbrowser.open(url='https://openpype.io/docs/artist_hosts_hiero')", + "tooltip": "Open the OpenPype Hiero user doc page" } ] }, From c69482df0a8a4b67952a908c6f4431524c039413 Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Tue, 28 Jun 2022 17:06:13 +0200 Subject: [PATCH 0883/1227] Blender bugfix on open set fps --- openpype/hosts/blender/api/pipeline.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 93d81145bc..ea405b028e 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -93,7 +93,7 @@ def set_start_end_frames(): # Default scene settings frameStart = scene.frame_start frameEnd = scene.frame_end - fps = scene.render.fps + fps = scene.render.fps / scene.render.fps_base resolution_x = scene.render.resolution_x resolution_y = scene.render.resolution_y @@ -116,7 +116,8 @@ def set_start_end_frames(): scene.frame_start = frameStart scene.frame_end = frameEnd - scene.render.fps = fps + scene.render.fps = round(fps) + scene.render.fps_base = round(fps) / fps scene.render.resolution_x = resolution_x scene.render.resolution_y = resolution_y From e9edb1cb68bc65c161a5dfa1e4b4eeda322a0b23 Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Tue, 28 Jun 2022 18:20:57 +0300 Subject: [PATCH 0884/1227] Adjust dimensions to grab correct values from asset. Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 7f9875f564..8d1c62d64c 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -57,8 +57,9 @@ class ExtractPlayblast(openpype.api.Extractor): width_preset = capture_presets["Resolution"]["width"] height_preset = capture_presets["Resolution"]["height"] # Set resolution variables from asset values - asset_width = instance.data.get("resolutionWidth") - asset_height = instance.data.get("resolutionHeight") + asset_data = instance.data["assetEntity"]["data"] + asset_width = asset_data.get("width") + asset_height = asset_data.get("height") review_instance_width = instance.data.get("instanceWidth") review_instance_height = instance.data.get("instanceHeight") preset['camera'] = camera From 397a3f7fc4ebfe02ca033f775208301432e9db38 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 28 Jun 2022 17:21:09 +0200 Subject: [PATCH 0885/1227] add docs for hiero custom menu --- website/docs/admin_hosts_hiero.md | 9 +++++++++ website/docs/assets/hiero-admin_scriptsmenu.png | Bin 0 -> 23368 bytes website/sidebars.js | 1 + 3 files changed, 10 insertions(+) create mode 100644 website/docs/admin_hosts_hiero.md create mode 100644 website/docs/assets/hiero-admin_scriptsmenu.png diff --git a/website/docs/admin_hosts_hiero.md b/website/docs/admin_hosts_hiero.md new file mode 100644 index 0000000000..b75d8dee7d --- /dev/null +++ b/website/docs/admin_hosts_hiero.md @@ -0,0 +1,9 @@ +--- +id: admin_hosts_hiero +title: Hiero +sidebar_label: Hiero +--- + +## Custom Menu +You can add your custom tools menu into Hiero by extending definitions in **Hiero -> Scripts Menu Definition**. +![Custom menu definition](assets/hiero-admin_scriptsmenu.png) diff --git a/website/docs/assets/hiero-admin_scriptsmenu.png b/website/docs/assets/hiero-admin_scriptsmenu.png new file mode 100644 index 0000000000000000000000000000000000000000..6de136a434e19f78fe871be147a8e5580f3af552 GIT binary patch literal 23368 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 J3MGrM{{f4=o! Date: Tue, 28 Jun 2022 17:26:54 +0200 Subject: [PATCH 0886/1227] refactor imports --- openpype/hosts/hiero/api/launchforhiero.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/hiero/api/launchforhiero.py b/openpype/hosts/hiero/api/launchforhiero.py index 5c230eb9fe..5f7dbe23c9 100644 --- a/openpype/hosts/hiero/api/launchforhiero.py +++ b/openpype/hosts/hiero/api/launchforhiero.py @@ -1,7 +1,7 @@ import logging -from openpype.vendor.python.common.scriptsmenu import scriptsmenu -from openpype.vendor.python.common.scriptsmenu.vendor.Qt import QtWidgets +from scriptsmenu import scriptsmenu +from Qt import QtWidgets log = logging.getLogger(__name__) From be2f9a06aa611f1abb94c416416f5c5ad48c0ae6 Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Tue, 28 Jun 2022 18:42:44 +0300 Subject: [PATCH 0887/1227] Simplify testing of cases for resolution Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../maya/plugins/publish/extract_playblast.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 8d1c62d64c..0852053b8b 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -69,18 +69,14 @@ class ExtractPlayblast(openpype.api.Extractor): # used, if not then the asset resolution is # used - if review_instance_width != 0: + if review_instance_width and review_instance_height: preset['width'] = review_instance_width - elif width_preset == 0: - preset['width'] = asset_width - elif width_preset != 0: - preset['width'] = width_preset - - if review_instance_height != 0: preset['height'] = review_instance_height - elif height_preset == 0: + elif asset_width and asset_height: + preset['width'] = asset_width preset['height'] = asset_height - elif height_preset != 0: + elif width_preset and height_preset: + preset['width'] = width_preset preset['height'] = height_preset preset['start_frame'] = start From c67308d03223656465525647c7254e65d61922f6 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 28 Jun 2022 18:48:59 +0300 Subject: [PATCH 0888/1227] Variable rename. --- openpype/hosts/maya/plugins/create/create_review.py | 4 ++-- openpype/hosts/maya/plugins/publish/collect_review.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index 44dc8a3158..bafc62e82c 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -35,8 +35,8 @@ class CreateReview(plugin.Creator): for key, value in animation_data.items(): data[key] = value - data["instanceHeight"] = self.attrWidth - data["instanceWidth"] = self.attrHeight + data["Width"] = self.attrWidth + data["Height"] = self.attrHeight data["isolate"] = self.isolate data["keepImages"] = self.keepImages data["imagePlane"] = self.imagePlane diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index fec1fbfa11..b0b5ef37e8 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -71,8 +71,8 @@ class CollectReview(pyblish.api.InstancePlugin): data['handles'] = instance.data.get('handles', None) data['step'] = instance.data['step'] data['fps'] = instance.data['fps'] - data['instanceHeight'] = instance.data['instanceHeight'] - data['instanceHeight'] = instance.data['instanceHeight'] + data['Width'] = instance.data['Width'] + data['Height'] = instance.data['Height'] data["isolate"] = instance.data["isolate"] cmds.setAttr(str(instance) + '.active', 1) self.log.debug('data {}'.format(instance.context[i].data)) From bc18f8f755a771c27fcc9de452ba514d8039d555 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 28 Jun 2022 19:04:10 +0300 Subject: [PATCH 0889/1227] Simplify logic in thumbnail extractor. --- .../maya/plugins/publish/extract_thumbnail.py | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 119ad10496..bc15327aa7 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -65,26 +65,23 @@ class ExtractThumbnail(openpype.api.Extractor): width_preset = capture_presets["Resolution"]["width"] height_preset = capture_presets["Resolution"]["height"] # Set resolution variables from asset values - asset_width = instance.data.get("resolutionWidth") - asset_height = instance.data.get("resolutionHeight") - review_instance_width = instance.data.get("instanceWidth") - review_instance_height = instance.data.get("instanceHeight") + asset_data = instance.data["assetEntity"]["data"] + asset_width = asset_data.get("width") + asset_height = asset_data.get("height") + review_instance_width = instance.data.get("Width") + review_instance_height = instance.data.get("Height") # Tests if project resolution is set, # if it is a value other than zero, that value is # used, if not then the asset resolution is # used - if review_instance_width != 0: + if review_instance_width and review_instance_height: preset['width'] = review_instance_width - elif width_preset == 0: - preset['width'] = asset_width - elif width_preset != 0: - preset['width'] = width_preset - - if review_instance_height != 0: preset['height'] = review_instance_height - elif height_preset == 0: + elif asset_width and asset_height: + preset['width'] = asset_width preset['height'] = asset_height - elif height_preset != 0: + elif width_preset and height_preset: + preset['width'] = width_preset preset['height'] = height_preset stagingDir = self.staging_dir(instance) filename = "{0}".format(instance.name) From 4514ed8e18abf6dae25310d4f8acb86d3870b5f3 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 28 Jun 2022 19:04:26 +0300 Subject: [PATCH 0890/1227] Change naming in interface. --- openpype/hosts/maya/plugins/create/create_review.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index bafc62e82c..9f6721762b 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -15,8 +15,8 @@ class CreateReview(plugin.Creator): keepImages = False isolate = False imagePlane = True - attrWidth = 0 - attrHeight = 0 + Width = 0 + Height = 0 transparency = [ "preset", "simple", @@ -35,8 +35,8 @@ class CreateReview(plugin.Creator): for key, value in animation_data.items(): data[key] = value - data["Width"] = self.attrWidth - data["Height"] = self.attrHeight + data["Width"] = self.Width + data["Height"] = self.Height data["isolate"] = self.isolate data["keepImages"] = self.keepImages data["imagePlane"] = self.imagePlane From 92bf597224cba29ff532ff1b63015f1346639e28 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 28 Jun 2022 19:15:36 +0300 Subject: [PATCH 0891/1227] Fix instance variable name. --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 0852053b8b..fbf4035505 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -60,8 +60,8 @@ class ExtractPlayblast(openpype.api.Extractor): asset_data = instance.data["assetEntity"]["data"] asset_width = asset_data.get("width") asset_height = asset_data.get("height") - review_instance_width = instance.data.get("instanceWidth") - review_instance_height = instance.data.get("instanceHeight") + review_instance_width = instance.data.get("Width") + review_instance_height = instance.data.get("Height") preset['camera'] = camera # Tests if project resolution is set, From a310f499740b0989762663e25b45bad09d766225 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 28 Jun 2022 18:43:11 +0200 Subject: [PATCH 0892/1227] store write node to instance data --- openpype/hosts/nuke/plugins/publish/precollect_writes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py index 049958bd07..a97f34b370 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -35,6 +35,7 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): if node is None: return + instance.data["writeNode"] = node self.log.debug("checking instance: {}".format(instance)) # Determine defined file type From 7c48b61206ef38e593c84a4c4291941eb0923f1f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 28 Jun 2022 18:43:30 +0200 Subject: [PATCH 0893/1227] add file to representation files when slate is rendered --- .../plugins/publish/extract_slate_frame.py | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index e0c4bdb953..6d930d358d 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -4,6 +4,7 @@ import nuke import copy import pyblish.api +import six import openpype from openpype.hosts.nuke.api import ( @@ -12,7 +13,6 @@ from openpype.hosts.nuke.api import ( get_view_process_node ) - class ExtractSlateFrame(openpype.api.Extractor): """Extracts movie and thumbnail with baked in luts @@ -236,6 +236,48 @@ class ExtractSlateFrame(openpype.api.Extractor): int(slate_first_frame) ) + # Add file to representation files + # - get write node + write_node = instance.data["writeNode"] + # - evaluate filepaths for first frame and slate frame + first_filename = os.path.basename( + write_node["file"].evaluate(first_frame)) + slate_filename = os.path.basename( + write_node["file"].evaluate(slate_first_frame)) + + # Find matching representation based on first filename + matching_repre = None + is_sequence = None + for repre in instance.data["representations"]: + files = repre["files"] + if ( + not isinstance(files, six.string_types) + and first_filename in files + ): + matching_repre = repre + is_sequence = True + break + + elif files == first_filename: + matching_repre = repre + is_sequence = False + break + + if not matching_repre: + self.log.info(( + "Matching reresentaion was not found." + " Representation files were not filled with slate." + )) + return + + # Add frame to matching representation files + if not is_sequence: + matching_repre["files"] = [first_filename, slate_filename] + elif slate_filename not in matching_repre["files"]: + matching_repre["files"].insert(0, slate_filename) + + self.log.warning("Added slate frame to representation files") + def add_comment_slate_node(self, instance, node): comment = instance.context.data.get("comment") From ee273892e871b57607a8f8ad1792d3c5ef5ad95b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 28 Jun 2022 21:10:11 +0200 Subject: [PATCH 0894/1227] Add alembic visible only validator --- openpype/hosts/maya/api/lib.py | 4 +- .../plugins/publish/validate_visible_only.py | 53 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 openpype/hosts/maya/plugins/publish/validate_visible_only.py diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index b5bdca456e..ab9c06280f 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3241,8 +3241,8 @@ def get_visible_in_frame_range(nodes, start, end): Args: nodes (list): List of node names to consider. - start (int): Start frame. - end (int): End frame. + start (int, float): Start frame. + end (int, float): End frame. Returns: list: List of node names. These will be long full path names so diff --git a/openpype/hosts/maya/plugins/publish/validate_visible_only.py b/openpype/hosts/maya/plugins/publish/validate_visible_only.py new file mode 100644 index 0000000000..9094a421c6 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_visible_only.py @@ -0,0 +1,53 @@ +import pyblish.api + +import openpype.api +from openpype.hosts.maya.api.lib import get_visible_in_frame_range +import openpype.hosts.maya.api.action + + +class ValidateAlembicVisibleOnly(pyblish.api.InstancePlugin): + """Validates at least a single node is visible in frame range. + + This validation only validates if the `visibleOnly` flag is enabled + on the instance - otherwise the validation is skipped. + + """ + order = openpype.api.ValidateContentsOrder + 0.05 + label = "Alembic Visible Only" + hosts = ["maya"] + families = ["pointcache", "animation"] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] + + def process(self, instance): + + if not instance.data.get("visibleOnly", False): + self.log.debug("Visible only is disabled. Validation skipped..") + return + + invalid = self.get_invalid(instance) + if invalid: + start, end = self.get_frame_range(instance) + raise RuntimeError("No visible nodes found in " + "frame range {}-{}.".format(start, end)) + + @classmethod + def get_invalid(cls, instance): + + if instance.data["family"] == "animation": + # Special behavior to use the nodes in out_SET + nodes = instance.data["out_hierarchy"] + else: + nodes = instance[:] + + start, end = cls.get_frame_range(instance) + visible = get_visible_in_frame_range(nodes, start, end) + + if not visible: + # Return the nodes we have considered so the user can identify + # them with the select invalid action + return nodes + + @staticmethod + def get_frame_range(instance): + data = instance.data + return data["frameStartHandle"], data["frameEndHandle"] From 96150eba27fc64e519b7c1756e6b0f36f886746a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 28 Jun 2022 21:10:45 +0200 Subject: [PATCH 0895/1227] Remove no visible nodes warning in extractors since validator catches those cases --- openpype/hosts/maya/plugins/publish/extract_animation.py | 7 ------- openpype/hosts/maya/plugins/publish/extract_pointcache.py | 7 ------- 2 files changed, 14 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py index 4b650b4b26..b04e2bf0c4 100644 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_animation.py @@ -86,13 +86,6 @@ class ExtractAnimation(openpype.api.Extractor): nodes = get_visible_in_frame_range(nodes, start=start, end=end) - if not nodes: - self.log.warning( - "No visible nodes found in frame range {}-{}. " - "Skipping extraction because `visibleOnly` is enabled on " - "the instance.".format(start, end) - ) - return with suspended_refresh(): with maintained_selection(): diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 768ee6da3d..f582a106d9 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -89,13 +89,6 @@ class ExtractAlembic(openpype.api.Extractor): nodes = get_visible_in_frame_range(nodes, start=start, end=end) - if not nodes: - self.log.warning( - "No visible nodes found in frame range {}-{}. " - "Skipping extraction because `visibleOnly` is enabled on " - "the instance.".format(start, end) - ) - return with suspended_refresh(): with maintained_selection(): From a29d1d493243915db96d6d09a40471c3177b9558 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 28 Jun 2022 21:25:17 +0200 Subject: [PATCH 0896/1227] Use iterator to speed up visible only validation for most cases --- openpype/hosts/maya/api/lib.py | 15 +++++++-------- .../maya/plugins/publish/extract_animation.py | 8 ++++---- .../maya/plugins/publish/extract_pointcache.py | 8 ++++---- .../maya/plugins/publish/validate_visible_only.py | 6 ++---- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index ab9c06280f..637f9e951b 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3224,8 +3224,8 @@ def maintained_time(): cmds.currentTime(ct, edit=True) -def get_visible_in_frame_range(nodes, start, end): - """Return nodes that are visible in start-end frame range. +def iter_visible_in_frame_range(nodes, start, end): + """Yield nodes that are visible in start-end frame range. - Ignores intermediateObjects completely. - Considers animated visibility attributes + upstream visibilities. @@ -3261,7 +3261,7 @@ def get_visible_in_frame_range(nodes, start, end): # Consider only non-intermediate dag nodes and use the "long" names. nodes = cmds.ls(nodes, long=True, noIntermediate=True, type="dagNode") if not nodes: - return [] + return with maintained_time(): # Go to first frame of the range if the current time is outside @@ -3275,7 +3275,8 @@ def get_visible_in_frame_range(nodes, start, end): if len(visible) == len(nodes) or start == end: # All are visible on frame one, so they are at least visible once # inside the frame range. - return visible + for node in visible: + yield node # For the invisible ones check whether its visibility and/or # any of its parents visibility attributes are animated. If so, it might @@ -3358,7 +3359,7 @@ def get_visible_in_frame_range(nodes, start, end): node_dependencies[node] = dependencies if not node_dependencies: - return list(visible) + return # Now we only have to check the visibilities for nodes that have animated # visibility dependencies upstream. The fastest way to check these @@ -3409,7 +3410,7 @@ def get_visible_in_frame_range(nodes, start, end): else: # All dependencies are visible. - visible.add(node) + yield node # Remove node with dependencies for next frame iterations # because it was visible at least once. node_dependencies.pop(node) @@ -3417,5 +3418,3 @@ def get_visible_in_frame_range(nodes, start, end): # If no more nodes to process break the frame iterations.. if not node_dependencies: break - - return list(visible) diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py index b04e2bf0c4..b0beb5968e 100644 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_animation.py @@ -7,7 +7,7 @@ from openpype.hosts.maya.api.lib import ( extract_alembic, suspended_refresh, maintained_selection, - get_visible_in_frame_range + iter_visible_in_frame_range ) @@ -83,9 +83,9 @@ class ExtractAnimation(openpype.api.Extractor): # flag does not filter out those that are only hidden on some # frames as it counts "animated" or "connected" visibilities as # if it's always visible. - nodes = get_visible_in_frame_range(nodes, - start=start, - end=end) + nodes = list(iter_visible_in_frame_range(nodes, + start=start, + end=end)) with suspended_refresh(): with maintained_selection(): diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index f582a106d9..7aa3aaee2a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -7,7 +7,7 @@ from openpype.hosts.maya.api.lib import ( extract_alembic, suspended_refresh, maintained_selection, - get_visible_in_frame_range + iter_visible_in_frame_range ) @@ -86,9 +86,9 @@ class ExtractAlembic(openpype.api.Extractor): # flag does not filter out those that are only hidden on some # frames as it counts "animated" or "connected" visibilities as # if it's always visible. - nodes = get_visible_in_frame_range(nodes, - start=start, - end=end) + nodes = list(iter_visible_in_frame_range(nodes, + start=start, + end=end)) with suspended_refresh(): with maintained_selection(): diff --git a/openpype/hosts/maya/plugins/publish/validate_visible_only.py b/openpype/hosts/maya/plugins/publish/validate_visible_only.py index 9094a421c6..2a10171113 100644 --- a/openpype/hosts/maya/plugins/publish/validate_visible_only.py +++ b/openpype/hosts/maya/plugins/publish/validate_visible_only.py @@ -1,7 +1,7 @@ import pyblish.api import openpype.api -from openpype.hosts.maya.api.lib import get_visible_in_frame_range +from openpype.hosts.maya.api.lib import iter_visible_in_frame_range import openpype.hosts.maya.api.action @@ -40,9 +40,7 @@ class ValidateAlembicVisibleOnly(pyblish.api.InstancePlugin): nodes = instance[:] start, end = cls.get_frame_range(instance) - visible = get_visible_in_frame_range(nodes, start, end) - - if not visible: + if not any(iter_visible_in_frame_range(nodes, start, end)): # Return the nodes we have considered so the user can identify # them with the select invalid action return nodes From a99cc8f4971c52b55104eade2cac6c679db76d0f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 28 Jun 2022 21:30:22 +0200 Subject: [PATCH 0897/1227] Fix iterator --- openpype/hosts/maya/api/lib.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 637f9e951b..43f738bfc7 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3272,11 +3272,12 @@ def iter_visible_in_frame_range(nodes, start, end): cmds.currentTime(start) visible = cmds.ls(nodes, long=True, visible=True) + for node in visible: + yield node if len(visible) == len(nodes) or start == end: # All are visible on frame one, so they are at least visible once # inside the frame range. - for node in visible: - yield node + return # For the invisible ones check whether its visibility and/or # any of its parents visibility attributes are animated. If so, it might From 778f485a1813b267ee47c5147b45e66011507d1f Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 29 Jun 2022 04:03:54 +0000 Subject: [PATCH 0898/1227] [Automated] Bump version --- CHANGELOG.md | 18 +++++++++++------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 003ca72a24..438a563391 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ # Changelog +## [3.12.1-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.0...HEAD) + +**🚀 Enhancements** + +- Blender: pre pyside install for all platforms [\#3400](https://github.com/pypeclub/OpenPype/pull/3400) + ## [3.12.0](https://github.com/pypeclub/OpenPype/tree/3.12.0) (2022-06-28) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.1...3.12.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.12.0-nightly.3...3.12.0) ### 📖 Documentation @@ -29,6 +37,7 @@ - Maya: look collector typo [\#3392](https://github.com/pypeclub/OpenPype/pull/3392) - TVPaint: Make sure exit code is set to not None [\#3382](https://github.com/pypeclub/OpenPype/pull/3382) - Maya: vray device aspect ratio fix [\#3381](https://github.com/pypeclub/OpenPype/pull/3381) +- Flame: bunch of publishing issues [\#3377](https://github.com/pypeclub/OpenPype/pull/3377) - Harmony: added unc path to zifile command in Harmony [\#3372](https://github.com/pypeclub/OpenPype/pull/3372) - Standalone: settings improvements [\#3355](https://github.com/pypeclub/OpenPype/pull/3355) - Nuke: Load full model hierarchy by default [\#3328](https://github.com/pypeclub/OpenPype/pull/3328) @@ -48,6 +57,7 @@ - AfterEffects: Use client query functions [\#3374](https://github.com/pypeclub/OpenPype/pull/3374) - TVPaint: Use client query functions [\#3340](https://github.com/pypeclub/OpenPype/pull/3340) - Ftrack: Use client query functions [\#3339](https://github.com/pypeclub/OpenPype/pull/3339) +- Webpublisher: Use client query functions [\#3333](https://github.com/pypeclub/OpenPype/pull/3333) - Standalone Publisher: Use client query functions [\#3330](https://github.com/pypeclub/OpenPype/pull/3330) **Merged pull requests:** @@ -73,7 +83,6 @@ **🐛 Bug fixes** -- Flame: bunch of publishing issues [\#3377](https://github.com/pypeclub/OpenPype/pull/3377) - Nuke: bake streams with slate on farm [\#3368](https://github.com/pypeclub/OpenPype/pull/3368) - Harmony: audio validator has wrong logic [\#3364](https://github.com/pypeclub/OpenPype/pull/3364) - Nuke: Fix missing variable in extract thumbnail [\#3363](https://github.com/pypeclub/OpenPype/pull/3363) @@ -86,10 +95,6 @@ - Deadline: added OPENPYPE\_MONGO to filter [\#3336](https://github.com/pypeclub/OpenPype/pull/3336) - Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) -**🔀 Refactored code** - -- Webpublisher: Use client query functions [\#3333](https://github.com/pypeclub/OpenPype/pull/3333) - ## [3.11.0](https://github.com/pypeclub/OpenPype/tree/3.11.0) (2022-06-17) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.11.0-nightly.4...3.11.0) @@ -121,7 +126,6 @@ - Maya: Fix swaped width and height in reviews [\#3300](https://github.com/pypeclub/OpenPype/pull/3300) - Maya: point cache publish handles Maya instances [\#3297](https://github.com/pypeclub/OpenPype/pull/3297) - Global: extract review slate issues [\#3286](https://github.com/pypeclub/OpenPype/pull/3286) -- Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) **🔀 Refactored code** diff --git a/openpype/version.py b/openpype/version.py index 65e439ef33..633b0b4f33 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.12.0" +__version__ = "3.12.1-nightly.1" diff --git a/pyproject.toml b/pyproject.toml index bd5d3ad89d..26a7b4bf1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.12.0" # OpenPype +version = "3.12.1-nightly.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 0e8892e2819237cc557e7195e87acef3ffefea5c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 29 Jun 2022 11:33:24 +0200 Subject: [PATCH 0899/1227] Re-use `maintained_time` from lib --- .../hosts/maya/plugins/publish/extract_playblast.py | 13 +------------ .../hosts/maya/plugins/publish/extract_thumbnail.py | 12 +----------- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index ba939d5428..246b2cc0f1 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -1,6 +1,4 @@ import os -import glob -import contextlib import clique import capture @@ -90,7 +88,7 @@ class ExtractPlayblast(openpype.api.Extractor): else: preset["viewport_options"] = {"imagePlane": image_plane} - with maintained_time(): + with lib.maintained_time(): filename = preset.get("filename", "%TEMP%") # Force viewer to False in call to capture because we have our own @@ -153,12 +151,3 @@ class ExtractPlayblast(openpype.api.Extractor): 'camera_name': camera_node_name } instance.data["representations"].append(representation) - - -@contextlib.contextmanager -def maintained_time(): - ct = cmds.currentTime(query=True) - try: - yield - finally: - cmds.currentTime(ct, edit=True) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index c2cefc56f1..915c52109d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -1,5 +1,4 @@ import os -import contextlib import glob import capture @@ -81,7 +80,7 @@ class ExtractThumbnail(openpype.api.Extractor): if preset.pop("isolate_view", False) and instance.data.get("isolate"): preset["isolate"] = instance.data["setMembers"] - with maintained_time(): + with lib.maintained_time(): filename = preset.get("filename", "%TEMP%") # Force viewer to False in call to capture because we have our own @@ -152,12 +151,3 @@ class ExtractThumbnail(openpype.api.Extractor): filepath = max(files, key=os.path.getmtime) return filepath - - -@contextlib.contextmanager -def maintained_time(): - ct = cmds.currentTime(query=True) - try: - yield - finally: - cmds.currentTime(ct, edit=True) From eebfb02b916e94d955d0715a4beb5a3cf642f4d2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 29 Jun 2022 11:33:41 +0200 Subject: [PATCH 0900/1227] Remove unused variable `filename` --- openpype/hosts/maya/plugins/publish/extract_thumbnail.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 915c52109d..37661c3c77 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -81,8 +81,6 @@ class ExtractThumbnail(openpype.api.Extractor): preset["isolate"] = instance.data["setMembers"] with lib.maintained_time(): - filename = preset.get("filename", "%TEMP%") - # Force viewer to False in call to capture because we have our own # viewer opening call to allow a signal to trigger between # playblast and viewer From 275f5f8bf939734614fecd12b14f79b94d8a071c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 29 Jun 2022 11:34:11 +0200 Subject: [PATCH 0901/1227] Remove redundant initialization of `capture_preset` --- openpype/hosts/maya/plugins/publish/extract_thumbnail.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 37661c3c77..20a226796a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -27,7 +27,6 @@ class ExtractThumbnail(openpype.api.Extractor): camera = instance.data['review_camera'] - capture_preset = "" capture_preset = ( instance.context.data["project_settings"]['maya']['publish']['ExtractPlayblast']['capture_preset'] ) From 6779d0a88afc917c9da5f865ba32fc4c68907e1d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 29 Jun 2022 13:17:33 +0200 Subject: [PATCH 0902/1227] Draft implementation for VDB to Arnold loader --- .../maya/plugins/load/load_vdb_to_arnold.py | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 openpype/hosts/maya/plugins/load/load_vdb_to_arnold.py diff --git a/openpype/hosts/maya/plugins/load/load_vdb_to_arnold.py b/openpype/hosts/maya/plugins/load/load_vdb_to_arnold.py new file mode 100644 index 0000000000..89166c2dc8 --- /dev/null +++ b/openpype/hosts/maya/plugins/load/load_vdb_to_arnold.py @@ -0,0 +1,137 @@ +import os + +from openpype.api import get_project_settings +from openpype.pipeline import load + +# TODO aiVolume doesn't automatically set velocity fps correctly, set manual? + + +class LoadVDBtoArnold(load.LoaderPlugin): + """Load OpenVDB for Arnold in aiVolume""" + + families = ["vdbcache"] + representations = ["vdb"] + + label = "Load VDB to Arnold" + icon = "cloud" + color = "orange" + + def load(self, context, name, namespace, data): + + from maya import cmds + from openpype.hosts.maya.api.pipeline import containerise + from openpype.hosts.maya.api.lib import unique_namespace + + try: + family = context["representation"]["context"]["family"] + except ValueError: + family = "vdbcache" + + # Check if the plugin for arnold is available on the pc + try: + cmds.loadPlugin("mtoa", quiet=True) + except Exception as exc: + self.log.error("Encountered exception:\n%s" % exc) + return + + asset = context['asset'] + asset_name = asset["name"] + namespace = namespace or unique_namespace( + asset_name + "_", + prefix="_" if asset_name[0].isdigit() else "", + suffix="_", + ) + + # Root group + label = "{}:{}".format(namespace, name) + root = cmds.group(name=label, empty=True) + + settings = get_project_settings(os.environ['AVALON_PROJECT']) + colors = settings['maya']['load']['colors'] + + c = colors.get(family) + if c is not None: + cmds.setAttr(root + ".useOutlinerColor", 1) + cmds.setAttr(root + ".outlinerColor", + (float(c[0]) / 255), + (float(c[1]) / 255), + (float(c[2]) / 255) + ) + + # Create VRayVolumeGrid + grid_node = cmds.createNode("aiVolume", + name="{}Shape".format(root), + parent=root) + + self._set_path(grid_node, + path=self.fname, + representation=context["representation"]) + + # Lock the shape node so the user can't delete the transform/shape + # as if it was referenced + cmds.lockNode(grid_node, lock=True) + + nodes = [root, grid_node] + self[:] = nodes + + return containerise( + name=name, + namespace=namespace, + nodes=nodes, + context=context, + loader=self.__class__.__name__) + + def update(self, container, representation): + + from maya import cmds + + path = api.get_representation_path(representation) + + # Find VRayVolumeGrid + members = cmds.sets(container['objectName'], query=True) + grid_nodes = cmds.ls(members, type="aiVolume", long=True) + assert len(grid_nodes) == 1, "This is a bug" + + # Update the VRayVolumeGrid + self._set_path(grid_nodes[0], path=path, representation=representation) + + # Update container representation + cmds.setAttr(container["objectName"] + ".representation", + str(representation["_id"]), + type="string") + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + + from maya import cmds + + # Get all members of the avalon container, ensure they are unlocked + # and delete everything + members = cmds.sets(container['objectName'], query=True) + cmds.lockNode(members, lock=False) + cmds.delete([container['objectName']] + members) + + # Clean up the namespace + try: + cmds.namespace(removeNamespace=container['namespace'], + deleteNamespaceContent=True) + except RuntimeError: + pass + + @staticmethod + def _set_path(grid_node, + path, + representation): + """Apply the settings for the VDB path to the aiVolume node""" + from maya import cmds + + if not os.path.exists(path): + raise RuntimeError("Path does not exist: %s" % path) + + is_sequence = bool(representation["context"].get("frame")) + cmds.setAttr(grid_node + ".useFrameExtension", is_sequence) + + # Set file path + cmds.setAttr(grid_node + ".filename", path, type="string") From 251a54581c7c910bac425b24af06052dae46ea25 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 29 Jun 2022 13:23:11 +0200 Subject: [PATCH 0903/1227] Fix missing import --- openpype/hosts/maya/plugins/load/load_vdb_to_arnold.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_vdb_to_arnold.py b/openpype/hosts/maya/plugins/load/load_vdb_to_arnold.py index 89166c2dc8..d458c5abda 100644 --- a/openpype/hosts/maya/plugins/load/load_vdb_to_arnold.py +++ b/openpype/hosts/maya/plugins/load/load_vdb_to_arnold.py @@ -1,8 +1,10 @@ import os from openpype.api import get_project_settings -from openpype.pipeline import load - +from openpype.pipeline import ( + load, + get_representation_path +) # TODO aiVolume doesn't automatically set velocity fps correctly, set manual? @@ -85,7 +87,7 @@ class LoadVDBtoArnold(load.LoaderPlugin): from maya import cmds - path = api.get_representation_path(representation) + path = get_representation_path(representation) # Find VRayVolumeGrid members = cmds.sets(container['objectName'], query=True) From 866bbf0487b5339b7f0f4bafef548b04aca39f67 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 29 Jun 2022 14:04:30 +0200 Subject: [PATCH 0904/1227] anatomy was moved to openpype pipeline --- openpype/lib/anatomy.py | 1263 +--------------------------------- openpype/pipeline/anatomy.py | 1260 +++++++++++++++++++++++++++++++++ 2 files changed, 1263 insertions(+), 1260 deletions(-) create mode 100644 openpype/pipeline/anatomy.py diff --git a/openpype/lib/anatomy.py b/openpype/lib/anatomy.py index 3fbc05ee88..b62b207ade 100644 --- a/openpype/lib/anatomy.py +++ b/openpype/lib/anatomy.py @@ -1,1260 +1,3 @@ -import os -import re -import copy -import platform -import collections -import numbers - -from openpype.settings.lib import ( - get_default_anatomy_settings, - get_anatomy_settings -) -from .path_templates import ( - TemplateUnsolved, - TemplateResult, - TemplatesDict, - FormatObject, -) -from .log import PypeLogger - -log = PypeLogger().get_logger(__name__) - -try: - StringType = basestring -except NameError: - StringType = str - - -class ProjectNotSet(Exception): - """Exception raised when is created Anatomy without project name.""" - - -class RootCombinationError(Exception): - """This exception is raised when templates has combined root types.""" - - def __init__(self, roots): - joined_roots = ", ".join( - ["\"{}\"".format(_root) for _root in roots] - ) - # TODO better error message - msg = ( - "Combination of root with and" - " without root name in AnatomyTemplates. {}" - ).format(joined_roots) - - super(RootCombinationError, self).__init__(msg) - - -class Anatomy: - """Anatomy module helps to keep project settings. - - Wraps key project specifications, AnatomyTemplates and Roots. - - Args: - project_name (str): Project name to look on overrides. - """ - - root_key_regex = re.compile(r"{(root?[^}]+)}") - root_name_regex = re.compile(r"root\[([^]]+)\]") - - def __init__(self, project_name=None, site_name=None): - if not project_name: - project_name = os.environ.get("AVALON_PROJECT") - - if not project_name: - raise ProjectNotSet(( - "Implementation bug: Project name is not set. Anatomy requires" - " to load data for specific project." - )) - - self.project_name = project_name - - self._data = self._prepare_anatomy_data( - get_anatomy_settings(project_name, site_name) - ) - self._site_name = site_name - self._templates_obj = AnatomyTemplates(self) - self._roots_obj = Roots(self) - - # Anatomy used as dictionary - # - implemented only getters returning copy - def __getitem__(self, key): - return copy.deepcopy(self._data[key]) - - def get(self, key, default=None): - return copy.deepcopy(self._data).get(key, default) - - def keys(self): - return copy.deepcopy(self._data).keys() - - def values(self): - return copy.deepcopy(self._data).values() - - def items(self): - return copy.deepcopy(self._data).items() - - @staticmethod - def default_data(): - """Default project anatomy data. - - Always return fresh loaded data. May be used as data for new project. - - Not used inside Anatomy itself. - """ - return get_default_anatomy_settings(clear_metadata=False) - - @staticmethod - def _prepare_anatomy_data(anatomy_data): - """Prepare anatomy data for further processing. - - Method added to replace `{task}` with `{task[name]}` in templates. - """ - templates_data = anatomy_data.get("templates") - if templates_data: - # Replace `{task}` with `{task[name]}` in templates - value_queue = collections.deque() - value_queue.append(templates_data) - while value_queue: - item = value_queue.popleft() - if not isinstance(item, dict): - continue - - for key in tuple(item.keys()): - value = item[key] - if isinstance(value, dict): - value_queue.append(value) - - elif isinstance(value, StringType): - item[key] = value.replace("{task}", "{task[name]}") - return anatomy_data - - def reset(self): - """Reset values of cached data in templates and roots objects.""" - self._data = self._prepare_anatomy_data( - get_anatomy_settings(self.project_name, self._site_name) - ) - self.templates_obj.reset() - self.roots_obj.reset() - - @property - def templates(self): - """Wrap property `templates` of Anatomy's AnatomyTemplates instance.""" - return self._templates_obj.templates - - @property - def templates_obj(self): - """Return `AnatomyTemplates` object of current Anatomy instance.""" - return self._templates_obj - - def format(self, *args, **kwargs): - """Wrap `format` method of Anatomy's `templates_obj`.""" - return self._templates_obj.format(*args, **kwargs) - - def format_all(self, *args, **kwargs): - """Wrap `format_all` method of Anatomy's `templates_obj`.""" - return self._templates_obj.format_all(*args, **kwargs) - - @property - def roots(self): - """Wrap `roots` property of Anatomy's `roots_obj`.""" - return self._roots_obj.roots - - @property - def roots_obj(self): - """Return `Roots` object of current Anatomy instance.""" - return self._roots_obj - - def root_environments(self): - """Return OPENPYPE_ROOT_* environments for current project in dict.""" - return self._roots_obj.root_environments() - - def root_environmets_fill_data(self, template=None): - """Environment variable values in dictionary for rootless path. - - Args: - template (str): Template for environment variable key fill. - By default is set to `"${}"`. - """ - return self.roots_obj.root_environmets_fill_data(template) - - def find_root_template_from_path(self, *args, **kwargs): - """Wrapper for Roots `find_root_template_from_path`.""" - return self.roots_obj.find_root_template_from_path(*args, **kwargs) - - def path_remapper(self, *args, **kwargs): - """Wrapper for Roots `path_remapper`.""" - return self.roots_obj.path_remapper(*args, **kwargs) - - def all_root_paths(self): - """Wrapper for Roots `all_root_paths`.""" - return self.roots_obj.all_root_paths() - - def set_root_environments(self): - """Set OPENPYPE_ROOT_* environments for current project.""" - self._roots_obj.set_root_environments() - - def root_names(self): - """Return root names for current project.""" - return self.root_names_from_templates(self.templates) - - def _root_keys_from_templates(self, data): - """Extract root key from templates in data. - - Args: - data (dict): Data that may contain templates as string. - - Return: - set: Set of all root names from templates as strings. - - Output example: `{"root[work]", "root[publish]"}` - """ - - output = set() - if isinstance(data, dict): - for value in data.values(): - for root in self._root_keys_from_templates(value): - output.add(root) - - elif isinstance(data, str): - for group in re.findall(self.root_key_regex, data): - output.add(group) - - return output - - def root_value_for_template(self, template): - """Returns value of root key from template.""" - root_templates = [] - for group in re.findall(self.root_key_regex, template): - root_templates.append("{" + group + "}") - - if not root_templates: - return None - - return root_templates[0].format(**{"root": self.roots}) - - def root_names_from_templates(self, templates): - """Extract root names form anatomy templates. - - Returns None if values in templates contain only "{root}". - Empty list is returned if there is no "root" in templates. - Else returns all root names from templates in list. - - RootCombinationError is raised when templates contain both root types, - basic "{root}" and with root name specification "{root[work]}". - - Args: - templates (dict): Anatomy templates where roots are not filled. - - Return: - list/None: List of all root names from templates as strings when - multiroot setup is used, otherwise None is returned. - """ - roots = list(self._root_keys_from_templates(templates)) - # Return empty list if no roots found in templates - if not roots: - return roots - - # Raise exception when root keys have roots with and without root name. - # Invalid output example: ["root", "root[project]", "root[render]"] - if len(roots) > 1 and "root" in roots: - raise RootCombinationError(roots) - - # Return None if "root" without root name in templates - if len(roots) == 1 and roots[0] == "root": - return None - - names = set() - for root in roots: - for group in re.findall(self.root_name_regex, root): - names.add(group) - return list(names) - - def fill_root(self, template_path): - """Fill template path where is only "root" key unfilled. - - Args: - template_path (str): Path with "root" key in. - Example path: "{root}/projects/MyProject/Shot01/Lighting/..." - - Return: - str: formatted path - """ - # NOTE does not care if there are different keys than "root" - return template_path.format(**{"root": self.roots}) - - @classmethod - def fill_root_with_path(cls, rootless_path, root_path): - """Fill path without filled "root" key with passed path. - - This is helper to fill root with different directory path than anatomy - has defined no matter if is single or multiroot. - - Output path is same as input path if `rootless_path` does not contain - unfilled root key. - - Args: - rootless_path (str): Path without filled "root" key. Example: - "{root[work]}/MyProject/..." - root_path (str): What should replace root key in `rootless_path`. - - Returns: - str: Path with filled root. - """ - output = str(rootless_path) - for group in re.findall(cls.root_key_regex, rootless_path): - replacement = "{" + group + "}" - output = output.replace(replacement, root_path) - - return output - - def replace_root_with_env_key(self, filepath, template=None): - """Replace root of path with environment key. - - # Example: - ## Project with roots: - ``` - { - "nas": { - "windows": P:/projects", - ... - } - ... - } - ``` - - ## Entered filepath - "P:/projects/project/asset/task/animation_v001.ma" - - ## Entered template - "<{}>" - - ## Output - "/project/asset/task/animation_v001.ma" - - Args: - filepath (str): Full file path where root should be replaced. - template (str): Optional template for environment key. Must - have one index format key. - Default value if not entered: "${}" - - Returns: - str: Path where root is replaced with environment root key. - - Raise: - ValueError: When project's roots were not found in entered path. - """ - success, rootless_path = self.find_root_template_from_path(filepath) - if not success: - raise ValueError( - "{}: Project's roots were not found in path: {}".format( - self.project_name, filepath - ) - ) - - data = self.root_environmets_fill_data(template) - return rootless_path.format(**data) - - -class AnatomyTemplateUnsolved(TemplateUnsolved): - """Exception for unsolved template when strict is set to True.""" - - msg = "Anatomy template \"{0}\" is unsolved.{1}{2}" - - -class AnatomyTemplateResult(TemplateResult): - rootless = None - - def __new__(cls, result, rootless_path): - new_obj = super(AnatomyTemplateResult, cls).__new__( - cls, - str(result), - result.template, - result.solved, - result.used_values, - result.missing_keys, - result.invalid_types - ) - new_obj.rootless = rootless_path - return new_obj - - def validate(self): - if not self.solved: - raise AnatomyTemplateUnsolved( - self.template, - self.missing_keys, - self.invalid_types - ) - - -class AnatomyTemplates(TemplatesDict): - inner_key_pattern = re.compile(r"(\{@.*?[^{}0]*\})") - inner_key_name_pattern = re.compile(r"\{@(.*?[^{}0]*)\}") - - def __init__(self, anatomy): - super(AnatomyTemplates, self).__init__() - self.anatomy = anatomy - self.loaded_project = None - - def __getitem__(self, key): - return self.templates[key] - - def get(self, key, default=None): - return self.templates.get(key, default) - - def reset(self): - self._raw_templates = None - self._templates = None - self._objected_templates = None - - @property - def project_name(self): - return self.anatomy.project_name - - @property - def roots(self): - return self.anatomy.roots - - @property - def templates(self): - self._validate_discovery() - return self._templates - - @property - def objected_templates(self): - self._validate_discovery() - return self._objected_templates - - def _validate_discovery(self): - if self.project_name != self.loaded_project: - self.reset() - - if self._templates is None: - self._discover() - self.loaded_project = self.project_name - - def _format_value(self, value, data): - if isinstance(value, RootItem): - return self._solve_dict(value, data) - - result = super(AnatomyTemplates, self)._format_value(value, data) - if isinstance(result, TemplateResult): - rootless_path = self._rootless_path(result, data) - result = AnatomyTemplateResult(result, rootless_path) - return result - - def set_templates(self, templates): - if not templates: - self.reset() - return - - self._raw_templates = copy.deepcopy(templates) - templates = copy.deepcopy(templates) - v_queue = collections.deque() - v_queue.append(templates) - while v_queue: - item = v_queue.popleft() - if not isinstance(item, dict): - continue - - for key in tuple(item.keys()): - value = item[key] - if isinstance(value, dict): - v_queue.append(value) - - elif ( - isinstance(value, StringType) - and "{task}" in value - ): - item[key] = value.replace("{task}", "{task[name]}") - - solved_templates = self.solve_template_inner_links(templates) - self._templates = solved_templates - self._objected_templates = self.create_ojected_templates( - solved_templates - ) - - def default_templates(self): - """Return default templates data with solved inner keys.""" - return self.solve_template_inner_links( - self.anatomy["templates"] - ) - - def _discover(self): - """ Loads anatomy templates from yaml. - Default templates are loaded if project is not set or project does - not have set it's own. - TODO: create templates if not exist. - - Returns: - TemplatesResultDict: Contain templates data for current project of - default templates. - """ - - if self.project_name is None: - # QUESTION create project specific if not found? - raise AssertionError(( - "Project \"{0}\" does not have his own templates." - " Trying to use default." - ).format(self.project_name)) - - self.set_templates(self.anatomy["templates"]) - - @classmethod - def replace_inner_keys(cls, matches, value, key_values, key): - """Replacement of inner keys in template values.""" - for match in matches: - anatomy_sub_keys = ( - cls.inner_key_name_pattern.findall(match) - ) - if key in anatomy_sub_keys: - raise ValueError(( - "Unsolvable recursion in inner keys, " - "key: \"{}\" is in his own value." - " Can't determine source, please check Anatomy templates." - ).format(key)) - - for anatomy_sub_key in anatomy_sub_keys: - replace_value = key_values.get(anatomy_sub_key) - if replace_value is None: - raise KeyError(( - "Anatomy templates can't be filled." - " Anatomy key `{0}` has" - " invalid inner key `{1}`." - ).format(key, anatomy_sub_key)) - - valid = isinstance(replace_value, (numbers.Number, StringType)) - if not valid: - raise ValueError(( - "Anatomy templates can't be filled." - " Anatomy key `{0}` has" - " invalid inner key `{1}`" - " with value `{2}`." - ).format(key, anatomy_sub_key, str(replace_value))) - - value = value.replace(match, str(replace_value)) - - return value - - @classmethod - def prepare_inner_keys(cls, key_values): - """Check values of inner keys. - - Check if inner key exist in template group and has valid value. - It is also required to avoid infinite loop with unsolvable recursion - when first inner key's value refers to second inner key's value where - first is used. - """ - keys_to_solve = set(key_values.keys()) - while True: - found = False - for key in tuple(keys_to_solve): - value = key_values[key] - - if isinstance(value, StringType): - matches = cls.inner_key_pattern.findall(value) - if not matches: - keys_to_solve.remove(key) - continue - - found = True - key_values[key] = cls.replace_inner_keys( - matches, value, key_values, key - ) - continue - - elif not isinstance(value, dict): - keys_to_solve.remove(key) - continue - - subdict_found = False - for _key, _value in tuple(value.items()): - matches = cls.inner_key_pattern.findall(_value) - if not matches: - continue - - subdict_found = True - found = True - key_values[key][_key] = cls.replace_inner_keys( - matches, _value, key_values, - "{}.{}".format(key, _key) - ) - - if not subdict_found: - keys_to_solve.remove(key) - - if not found: - break - - return key_values - - @classmethod - def solve_template_inner_links(cls, templates): - """Solve templates inner keys identified by "{@*}". - - Process is split into 2 parts. - First is collecting all global keys (keys in top hierarchy where value - is not dictionary). All global keys are set for all group keys (keys - in top hierarchy where value is dictionary). Value of a key is not - overridden in group if already contain value for the key. - - In second part all keys with "at" symbol in value are replaced with - value of the key afterward "at" symbol from the group. - - Args: - templates (dict): Raw templates data. - - Example: - templates:: - key_1: "value_1", - key_2: "{@key_1}/{filling_key}" - - group_1: - key_3: "value_3/{@key_2}" - - group_2: - key_2": "value_2" - key_4": "value_4/{@key_2}" - - output:: - key_1: "value_1" - key_2: "value_1/{filling_key}" - - group_1: { - key_1: "value_1" - key_2: "value_1/{filling_key}" - key_3: "value_3/value_1/{filling_key}" - - group_2: { - key_1: "value_1" - key_2: "value_2" - key_4: "value_3/value_2" - """ - default_key_values = templates.pop("defaults", {}) - for key, value in tuple(templates.items()): - if isinstance(value, dict): - continue - default_key_values[key] = templates.pop(key) - - # Pop "others" key before before expected keys are processed - other_templates = templates.pop("others") or {} - - keys_by_subkey = {} - for sub_key, sub_value in templates.items(): - key_values = {} - key_values.update(default_key_values) - key_values.update(sub_value) - keys_by_subkey[sub_key] = cls.prepare_inner_keys(key_values) - - for sub_key, sub_value in other_templates.items(): - if sub_key in keys_by_subkey: - log.warning(( - "Key \"{}\" is duplicated in others. Skipping." - ).format(sub_key)) - continue - - key_values = {} - key_values.update(default_key_values) - key_values.update(sub_value) - keys_by_subkey[sub_key] = cls.prepare_inner_keys(key_values) - - default_keys_by_subkeys = cls.prepare_inner_keys(default_key_values) - - for key, value in default_keys_by_subkeys.items(): - keys_by_subkey[key] = value - - return keys_by_subkey - - def _dict_to_subkeys_list(self, subdict, pre_keys=None): - if pre_keys is None: - pre_keys = [] - output = [] - for key in subdict: - value = subdict[key] - result = list(pre_keys) - result.append(key) - if isinstance(value, dict): - for item in self._dict_to_subkeys_list(value, result): - output.append(item) - else: - output.append(result) - return output - - def _keys_to_dicts(self, key_list, value): - if not key_list: - return None - if len(key_list) == 1: - return {key_list[0]: value} - return {key_list[0]: self._keys_to_dicts(key_list[1:], value)} - - def _rootless_path(self, result, final_data): - used_values = result.used_values - missing_keys = result.missing_keys - template = result.template - invalid_types = result.invalid_types - if ( - "root" not in used_values - or "root" in missing_keys - or "{root" not in template - ): - return - - for invalid_type in invalid_types: - if "root" in invalid_type: - return - - root_keys = self._dict_to_subkeys_list({"root": used_values["root"]}) - if not root_keys: - return - - output = str(result) - for used_root_keys in root_keys: - if not used_root_keys: - continue - - used_value = used_values - root_key = None - for key in used_root_keys: - used_value = used_value[key] - if root_key is None: - root_key = key - else: - root_key += "[{}]".format(key) - - root_key = "{" + root_key + "}" - output = output.replace(str(used_value), root_key) - - return output - - def format(self, data, strict=True): - copy_data = copy.deepcopy(data) - roots = self.roots - if roots: - copy_data["root"] = roots - result = super(AnatomyTemplates, self).format(copy_data) - result.strict = strict - return result - - def format_all(self, in_data, only_keys=True): - """ Solves templates based on entered data. - - Args: - data (dict): Containing keys to be filled into template. - - Returns: - TemplatesResultDict: Output `TemplateResult` have `strict` - attribute set to False so accessing unfilled keys in templates - won't raise any exceptions. - """ - return self.format(in_data, strict=False) - - -class RootItem(FormatObject): - """Represents one item or roots. - - Holds raw data of root item specification. Raw data contain value - for each platform, but current platform value is used when object - is used for formatting of template. - - Args: - root_raw_data (dict): Dictionary containing root values by platform - names. ["windows", "linux" and "darwin"] - name (str, optional): Root name which is representing. Used with - multi root setup otherwise None value is expected. - parent_keys (list, optional): All dictionary parent keys. Values of - `parent_keys` are used for get full key which RootItem is - representing. Used for replacing root value in path with - formattable key. e.g. parent_keys == ["work"] -> {root[work]} - parent (object, optional): It is expected to be `Roots` object. - Value of `parent` won't affect code logic much. - """ - - def __init__( - self, root_raw_data, name=None, parent_keys=None, parent=None - ): - lowered_platform_keys = {} - for key, value in root_raw_data.items(): - lowered_platform_keys[key.lower()] = value - self.raw_data = lowered_platform_keys - self.cleaned_data = self._clean_roots(lowered_platform_keys) - self.name = name - self.parent_keys = parent_keys or [] - self.parent = parent - - self.available_platforms = list(lowered_platform_keys.keys()) - self.value = lowered_platform_keys.get(platform.system().lower()) - self.clean_value = self.clean_root(self.value) - - def __format__(self, *args, **kwargs): - return self.value.__format__(*args, **kwargs) - - def __str__(self): - return str(self.value) - - def __repr__(self): - return self.__str__() - - def __getitem__(self, key): - if isinstance(key, numbers.Number): - return self.value[key] - - additional_info = "" - if self.parent and self.parent.project_name: - additional_info += " for project \"{}\"".format( - self.parent.project_name - ) - - raise AssertionError( - "Root key \"{}\" is missing{}.".format( - key, additional_info - ) - ) - - def full_key(self): - """Full key value for dictionary formatting in template. - - Returns: - str: Return full replacement key for formatting. This helps when - multiple roots are set. In that case e.g. `"root[work]"` is - returned. - """ - if not self.name: - return "root" - - joined_parent_keys = "".join( - ["[{}]".format(key) for key in self.parent_keys] - ) - return "root{}".format(joined_parent_keys) - - def clean_path(self, path): - """Just replace backslashes with forward slashes.""" - return str(path).replace("\\", "/") - - def clean_root(self, root): - """Makes sure root value does not end with slash.""" - if root: - root = self.clean_path(root) - while root.endswith("/"): - root = root[:-1] - return root - - def _clean_roots(self, raw_data): - """Clean all values of raw root item values.""" - cleaned = {} - for key, value in raw_data.items(): - cleaned[key] = self.clean_root(value) - return cleaned - - def path_remapper(self, path, dst_platform=None, src_platform=None): - """Remap path for specific platform. - - Args: - path (str): Source path which need to be remapped. - dst_platform (str, optional): Specify destination platform - for which remapping should happen. - src_platform (str, optional): Specify source platform. This is - recommended to not use and keep unset until you really want - to use specific platform. - roots (dict/RootItem/None, optional): It is possible to remap - path with different roots then instance where method was - called has. - - Returns: - str/None: When path does not contain known root then - None is returned else returns remapped path with "{root}" - or "{root[]}". - """ - cleaned_path = self.clean_path(path) - if dst_platform: - dst_root_clean = self.cleaned_data.get(dst_platform) - if not dst_root_clean: - key_part = "" - full_key = self.full_key() - if full_key != "root": - key_part += "\"{}\" ".format(full_key) - - log.warning( - "Root {}miss platform \"{}\" definition.".format( - key_part, dst_platform - ) - ) - return None - - if cleaned_path.startswith(dst_root_clean): - return cleaned_path - - if src_platform: - src_root_clean = self.cleaned_data.get(src_platform) - if src_root_clean is None: - log.warning( - "Root \"{}\" miss platform \"{}\" definition.".format( - self.full_key(), src_platform - ) - ) - return None - - if not cleaned_path.startswith(src_root_clean): - return None - - subpath = cleaned_path[len(src_root_clean):] - if dst_platform: - # `dst_root_clean` is used from upper condition - return dst_root_clean + subpath - return self.clean_value + subpath - - result, template = self.find_root_template_from_path(path) - if not result: - return None - - def parent_dict(keys, value): - if not keys: - return value - - key = keys.pop(0) - return {key: parent_dict(keys, value)} - - if dst_platform: - format_value = parent_dict(list(self.parent_keys), dst_root_clean) - else: - format_value = parent_dict(list(self.parent_keys), self.value) - - return template.format(**{"root": format_value}) - - def find_root_template_from_path(self, path): - """Replaces known root value with formattable key in path. - - All platform values are checked for this replacement. - - Args: - path (str): Path where root value should be found. - - Returns: - tuple: Tuple contain 2 values: `success` (bool) and `path` (str). - When success it True then path should contain replaced root - value with formattable key. - - Example: - When input path is:: - "C:/windows/path/root/projects/my_project/file.ext" - - And raw data of item looks like:: - { - "windows": "C:/windows/path/root", - "linux": "/mount/root" - } - - Output will be:: - (True, "{root}/projects/my_project/file.ext") - - If any of raw data value wouldn't match path's root output is:: - (False, "C:/windows/path/root/projects/my_project/file.ext") - """ - result = False - output = str(path) - - root_paths = list(self.cleaned_data.values()) - mod_path = self.clean_path(path) - for root_path in root_paths: - # Skip empty paths - if not root_path: - continue - - if mod_path.startswith(root_path): - result = True - replacement = "{" + self.full_key() + "}" - output = replacement + mod_path[len(root_path):] - break - - return (result, output) - - -class Roots: - """Object which should be used for formatting "root" key in templates. - - Args: - anatomy Anatomy: Anatomy object created for a specific project. - """ - - env_prefix = "OPENPYPE_PROJECT_ROOT" - roots_filename = "roots.json" - - def __init__(self, anatomy): - self.anatomy = anatomy - self.loaded_project = None - self._roots = None - - def __format__(self, *args, **kwargs): - return self.roots.__format__(*args, **kwargs) - - def __getitem__(self, key): - return self.roots[key] - - def reset(self): - """Reset current roots value.""" - self._roots = None - - def path_remapper( - self, path, dst_platform=None, src_platform=None, roots=None - ): - """Remap path for specific platform. - - Args: - path (str): Source path which need to be remapped. - dst_platform (str, optional): Specify destination platform - for which remapping should happen. - src_platform (str, optional): Specify source platform. This is - recommended to not use and keep unset until you really want - to use specific platform. - roots (dict/RootItem/None, optional): It is possible to remap - path with different roots then instance where method was - called has. - - Returns: - str/None: When path does not contain known root then - None is returned else returns remapped path with "{root}" - or "{root[]}". - """ - if roots is None: - roots = self.roots - - if roots is None: - raise ValueError("Roots are not set. Can't find path.") - - if "{root" in path: - path = path.format(**{"root": roots}) - # If `dst_platform` is not specified then return else continue. - if not dst_platform: - return path - - if isinstance(roots, RootItem): - return roots.path_remapper(path, dst_platform, src_platform) - - for _root in roots.values(): - result = self.path_remapper( - path, dst_platform, src_platform, _root - ) - if result is not None: - return result - - def find_root_template_from_path(self, path, roots=None): - """Find root value in entered path and replace it with formatting key. - - Args: - path (str): Source path where root will be searched. - roots (Roots/dict, optional): It is possible to use different - roots than instance where method was triggered has. - - Returns: - tuple: Output contains tuple with bool representing success as - first value and path with or without replaced root with - formatting key as second value. - - Raises: - ValueError: When roots are not entered and can't be loaded. - """ - if roots is None: - log.debug( - "Looking for matching root in path \"{}\".".format(path) - ) - roots = self.roots - - if roots is None: - raise ValueError("Roots are not set. Can't find path.") - - if isinstance(roots, RootItem): - return roots.find_root_template_from_path(path) - - for root_name, _root in roots.items(): - success, result = self.find_root_template_from_path(path, _root) - if success: - log.info("Found match in root \"{}\".".format(root_name)) - return success, result - - log.warning("No matching root was found in current setting.") - return (False, path) - - def set_root_environments(self): - """Set root environments for current project.""" - for key, value in self.root_environments().items(): - os.environ[key] = value - - def root_environments(self): - """Use root keys to create unique keys for environment variables. - - Concatenates prefix "OPENPYPE_ROOT" with root keys to create unique - keys. - - Returns: - dict: Result is `{(str): (str)}` dicitonary where key represents - unique key concatenated by keys and value is root value of - current platform root. - - Example: - With raw root values:: - "work": { - "windows": "P:/projects/work", - "linux": "/mnt/share/projects/work", - "darwin": "/darwin/path/work" - }, - "publish": { - "windows": "P:/projects/publish", - "linux": "/mnt/share/projects/publish", - "darwin": "/darwin/path/publish" - } - - Result on windows platform:: - { - "OPENPYPE_ROOT_WORK": "P:/projects/work", - "OPENPYPE_ROOT_PUBLISH": "P:/projects/publish" - } - - Short example when multiroot is not used:: - { - "OPENPYPE_ROOT": "P:/projects" - } - """ - return self._root_environments() - - def all_root_paths(self, roots=None): - """Return all paths for all roots of all platforms.""" - if roots is None: - roots = self.roots - - output = [] - if isinstance(roots, RootItem): - for value in roots.raw_data.values(): - output.append(value) - return output - - for _roots in roots.values(): - output.extend(self.all_root_paths(_roots)) - return output - - def _root_environments(self, keys=None, roots=None): - if not keys: - keys = [] - if roots is None: - roots = self.roots - - if isinstance(roots, RootItem): - key_items = [self.env_prefix] - for _key in keys: - key_items.append(_key.upper()) - - key = "_".join(key_items) - # Make sure key and value does not contain unicode - # - can happen in Python 2 hosts - return {str(key): str(roots.value)} - - output = {} - for _key, _value in roots.items(): - _keys = list(keys) - _keys.append(_key) - output.update(self._root_environments(_keys, _value)) - return output - - def root_environmets_fill_data(self, template=None): - """Environment variable values in dictionary for rootless path. - - Args: - template (str): Template for environment variable key fill. - By default is set to `"${}"`. - """ - if template is None: - template = "${}" - return self._root_environmets_fill_data(template) - - def _root_environmets_fill_data(self, template, keys=None, roots=None): - if keys is None and roots is None: - return { - "root": self._root_environmets_fill_data( - template, [], self.roots - ) - } - - if isinstance(roots, RootItem): - key_items = [Roots.env_prefix] - for _key in keys: - key_items.append(_key.upper()) - key = "_".join(key_items) - return template.format(key) - - output = {} - for key, value in roots.items(): - _keys = list(keys) - _keys.append(key) - output[key] = self._root_environmets_fill_data( - template, _keys, value - ) - return output - - @property - def project_name(self): - """Return project name which will be used for loading root values.""" - return self.anatomy.project_name - - @property - def roots(self): - """Property for filling "root" key in templates. - - This property returns roots for current project or default root values. - Warning: - Default roots value may cause issues when project use different - roots settings. That may happen when project use multiroot - templates but default roots miss their keys. - """ - if self.project_name != self.loaded_project: - self._roots = None - - if self._roots is None: - self._roots = self._discover() - self.loaded_project = self.project_name - return self._roots - - def _discover(self): - """ Loads current project's roots or default. - - Default roots are loaded if project override's does not contain roots. - - Returns: - `RootItem` or `dict` with multiple `RootItem`s when multiroot - setting is used. - """ - - return self._parse_dict(self.anatomy["roots"], parent=self) - - @staticmethod - def _parse_dict(data, key=None, parent_keys=None, parent=None): - """Parse roots raw data into RootItem or dictionary with RootItems. - - Converting raw roots data to `RootItem` helps to handle platform keys. - This method is recursive to be able handle multiroot setup and - is static to be able to load default roots without creating new object. - - Args: - data (dict): Should contain raw roots data to be parsed. - key (str, optional): Current root key. Set by recursion. - parent_keys (list): Parent dictionary keys. Set by recursion. - parent (Roots, optional): Parent object set in `RootItem` - helps to keep RootItem instance updated with `Roots` object. - - Returns: - `RootItem` or `dict` with multiple `RootItem`s when multiroot - setting is used. - """ - if not parent_keys: - parent_keys = [] - is_last = False - for value in data.values(): - if isinstance(value, StringType): - is_last = True - break - - if is_last: - return RootItem(data, key, parent_keys, parent=parent) - - output = {} - for _key, value in data.items(): - _parent_keys = list(parent_keys) - _parent_keys.append(_key) - output[_key] = Roots._parse_dict(value, _key, _parent_keys, parent) - return output +def Anatomy(*args, **kwargs): + from openpype.pipeline import Anatomy + return Anatomy(*args, **kwargs) diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py new file mode 100644 index 0000000000..33dfe64cb0 --- /dev/null +++ b/openpype/pipeline/anatomy.py @@ -0,0 +1,1260 @@ +import os +import re +import copy +import platform +import collections +import numbers + +from openpype.settings.lib import ( + get_default_anatomy_settings, + get_anatomy_settings +) +from openpype.lib.path_templates import ( + TemplateUnsolved, + TemplateResult, + TemplatesDict, + FormatObject, +) +from openpype.lib.log import PypeLogger + +log = PypeLogger.get_logger(__name__) + +try: + StringType = basestring +except NameError: + StringType = str + + +class ProjectNotSet(Exception): + """Exception raised when is created Anatomy without project name.""" + + +class RootCombinationError(Exception): + """This exception is raised when templates has combined root types.""" + + def __init__(self, roots): + joined_roots = ", ".join( + ["\"{}\"".format(_root) for _root in roots] + ) + # TODO better error message + msg = ( + "Combination of root with and" + " without root name in AnatomyTemplates. {}" + ).format(joined_roots) + + super(RootCombinationError, self).__init__(msg) + + +class Anatomy: + """Anatomy module helps to keep project settings. + + Wraps key project specifications, AnatomyTemplates and Roots. + + Args: + project_name (str): Project name to look on overrides. + """ + + root_key_regex = re.compile(r"{(root?[^}]+)}") + root_name_regex = re.compile(r"root\[([^]]+)\]") + + def __init__(self, project_name=None, site_name=None): + if not project_name: + project_name = os.environ.get("AVALON_PROJECT") + + if not project_name: + raise ProjectNotSet(( + "Implementation bug: Project name is not set. Anatomy requires" + " to load data for specific project." + )) + + self.project_name = project_name + + self._data = self._prepare_anatomy_data( + get_anatomy_settings(project_name, site_name) + ) + self._site_name = site_name + self._templates_obj = AnatomyTemplates(self) + self._roots_obj = Roots(self) + + # Anatomy used as dictionary + # - implemented only getters returning copy + def __getitem__(self, key): + return copy.deepcopy(self._data[key]) + + def get(self, key, default=None): + return copy.deepcopy(self._data).get(key, default) + + def keys(self): + return copy.deepcopy(self._data).keys() + + def values(self): + return copy.deepcopy(self._data).values() + + def items(self): + return copy.deepcopy(self._data).items() + + @staticmethod + def default_data(): + """Default project anatomy data. + + Always return fresh loaded data. May be used as data for new project. + + Not used inside Anatomy itself. + """ + return get_default_anatomy_settings(clear_metadata=False) + + @staticmethod + def _prepare_anatomy_data(anatomy_data): + """Prepare anatomy data for further processing. + + Method added to replace `{task}` with `{task[name]}` in templates. + """ + templates_data = anatomy_data.get("templates") + if templates_data: + # Replace `{task}` with `{task[name]}` in templates + value_queue = collections.deque() + value_queue.append(templates_data) + while value_queue: + item = value_queue.popleft() + if not isinstance(item, dict): + continue + + for key in tuple(item.keys()): + value = item[key] + if isinstance(value, dict): + value_queue.append(value) + + elif isinstance(value, StringType): + item[key] = value.replace("{task}", "{task[name]}") + return anatomy_data + + def reset(self): + """Reset values of cached data in templates and roots objects.""" + self._data = self._prepare_anatomy_data( + get_anatomy_settings(self.project_name, self._site_name) + ) + self.templates_obj.reset() + self.roots_obj.reset() + + @property + def templates(self): + """Wrap property `templates` of Anatomy's AnatomyTemplates instance.""" + return self._templates_obj.templates + + @property + def templates_obj(self): + """Return `AnatomyTemplates` object of current Anatomy instance.""" + return self._templates_obj + + def format(self, *args, **kwargs): + """Wrap `format` method of Anatomy's `templates_obj`.""" + return self._templates_obj.format(*args, **kwargs) + + def format_all(self, *args, **kwargs): + """Wrap `format_all` method of Anatomy's `templates_obj`.""" + return self._templates_obj.format_all(*args, **kwargs) + + @property + def roots(self): + """Wrap `roots` property of Anatomy's `roots_obj`.""" + return self._roots_obj.roots + + @property + def roots_obj(self): + """Return `Roots` object of current Anatomy instance.""" + return self._roots_obj + + def root_environments(self): + """Return OPENPYPE_ROOT_* environments for current project in dict.""" + return self._roots_obj.root_environments() + + def root_environmets_fill_data(self, template=None): + """Environment variable values in dictionary for rootless path. + + Args: + template (str): Template for environment variable key fill. + By default is set to `"${}"`. + """ + return self.roots_obj.root_environmets_fill_data(template) + + def find_root_template_from_path(self, *args, **kwargs): + """Wrapper for Roots `find_root_template_from_path`.""" + return self.roots_obj.find_root_template_from_path(*args, **kwargs) + + def path_remapper(self, *args, **kwargs): + """Wrapper for Roots `path_remapper`.""" + return self.roots_obj.path_remapper(*args, **kwargs) + + def all_root_paths(self): + """Wrapper for Roots `all_root_paths`.""" + return self.roots_obj.all_root_paths() + + def set_root_environments(self): + """Set OPENPYPE_ROOT_* environments for current project.""" + self._roots_obj.set_root_environments() + + def root_names(self): + """Return root names for current project.""" + return self.root_names_from_templates(self.templates) + + def _root_keys_from_templates(self, data): + """Extract root key from templates in data. + + Args: + data (dict): Data that may contain templates as string. + + Return: + set: Set of all root names from templates as strings. + + Output example: `{"root[work]", "root[publish]"}` + """ + + output = set() + if isinstance(data, dict): + for value in data.values(): + for root in self._root_keys_from_templates(value): + output.add(root) + + elif isinstance(data, str): + for group in re.findall(self.root_key_regex, data): + output.add(group) + + return output + + def root_value_for_template(self, template): + """Returns value of root key from template.""" + root_templates = [] + for group in re.findall(self.root_key_regex, template): + root_templates.append("{" + group + "}") + + if not root_templates: + return None + + return root_templates[0].format(**{"root": self.roots}) + + def root_names_from_templates(self, templates): + """Extract root names form anatomy templates. + + Returns None if values in templates contain only "{root}". + Empty list is returned if there is no "root" in templates. + Else returns all root names from templates in list. + + RootCombinationError is raised when templates contain both root types, + basic "{root}" and with root name specification "{root[work]}". + + Args: + templates (dict): Anatomy templates where roots are not filled. + + Return: + list/None: List of all root names from templates as strings when + multiroot setup is used, otherwise None is returned. + """ + roots = list(self._root_keys_from_templates(templates)) + # Return empty list if no roots found in templates + if not roots: + return roots + + # Raise exception when root keys have roots with and without root name. + # Invalid output example: ["root", "root[project]", "root[render]"] + if len(roots) > 1 and "root" in roots: + raise RootCombinationError(roots) + + # Return None if "root" without root name in templates + if len(roots) == 1 and roots[0] == "root": + return None + + names = set() + for root in roots: + for group in re.findall(self.root_name_regex, root): + names.add(group) + return list(names) + + def fill_root(self, template_path): + """Fill template path where is only "root" key unfilled. + + Args: + template_path (str): Path with "root" key in. + Example path: "{root}/projects/MyProject/Shot01/Lighting/..." + + Return: + str: formatted path + """ + # NOTE does not care if there are different keys than "root" + return template_path.format(**{"root": self.roots}) + + @classmethod + def fill_root_with_path(cls, rootless_path, root_path): + """Fill path without filled "root" key with passed path. + + This is helper to fill root with different directory path than anatomy + has defined no matter if is single or multiroot. + + Output path is same as input path if `rootless_path` does not contain + unfilled root key. + + Args: + rootless_path (str): Path without filled "root" key. Example: + "{root[work]}/MyProject/..." + root_path (str): What should replace root key in `rootless_path`. + + Returns: + str: Path with filled root. + """ + output = str(rootless_path) + for group in re.findall(cls.root_key_regex, rootless_path): + replacement = "{" + group + "}" + output = output.replace(replacement, root_path) + + return output + + def replace_root_with_env_key(self, filepath, template=None): + """Replace root of path with environment key. + + # Example: + ## Project with roots: + ``` + { + "nas": { + "windows": P:/projects", + ... + } + ... + } + ``` + + ## Entered filepath + "P:/projects/project/asset/task/animation_v001.ma" + + ## Entered template + "<{}>" + + ## Output + "/project/asset/task/animation_v001.ma" + + Args: + filepath (str): Full file path where root should be replaced. + template (str): Optional template for environment key. Must + have one index format key. + Default value if not entered: "${}" + + Returns: + str: Path where root is replaced with environment root key. + + Raise: + ValueError: When project's roots were not found in entered path. + """ + success, rootless_path = self.find_root_template_from_path(filepath) + if not success: + raise ValueError( + "{}: Project's roots were not found in path: {}".format( + self.project_name, filepath + ) + ) + + data = self.root_environmets_fill_data(template) + return rootless_path.format(**data) + + +class AnatomyTemplateUnsolved(TemplateUnsolved): + """Exception for unsolved template when strict is set to True.""" + + msg = "Anatomy template \"{0}\" is unsolved.{1}{2}" + + +class AnatomyTemplateResult(TemplateResult): + rootless = None + + def __new__(cls, result, rootless_path): + new_obj = super(AnatomyTemplateResult, cls).__new__( + cls, + str(result), + result.template, + result.solved, + result.used_values, + result.missing_keys, + result.invalid_types + ) + new_obj.rootless = rootless_path + return new_obj + + def validate(self): + if not self.solved: + raise AnatomyTemplateUnsolved( + self.template, + self.missing_keys, + self.invalid_types + ) + + +class AnatomyTemplates(TemplatesDict): + inner_key_pattern = re.compile(r"(\{@.*?[^{}0]*\})") + inner_key_name_pattern = re.compile(r"\{@(.*?[^{}0]*)\}") + + def __init__(self, anatomy): + super(AnatomyTemplates, self).__init__() + self.anatomy = anatomy + self.loaded_project = None + + def __getitem__(self, key): + return self.templates[key] + + def get(self, key, default=None): + return self.templates.get(key, default) + + def reset(self): + self._raw_templates = None + self._templates = None + self._objected_templates = None + + @property + def project_name(self): + return self.anatomy.project_name + + @property + def roots(self): + return self.anatomy.roots + + @property + def templates(self): + self._validate_discovery() + return self._templates + + @property + def objected_templates(self): + self._validate_discovery() + return self._objected_templates + + def _validate_discovery(self): + if self.project_name != self.loaded_project: + self.reset() + + if self._templates is None: + self._discover() + self.loaded_project = self.project_name + + def _format_value(self, value, data): + if isinstance(value, RootItem): + return self._solve_dict(value, data) + + result = super(AnatomyTemplates, self)._format_value(value, data) + if isinstance(result, TemplateResult): + rootless_path = self._rootless_path(result, data) + result = AnatomyTemplateResult(result, rootless_path) + return result + + def set_templates(self, templates): + if not templates: + self.reset() + return + + self._raw_templates = copy.deepcopy(templates) + templates = copy.deepcopy(templates) + v_queue = collections.deque() + v_queue.append(templates) + while v_queue: + item = v_queue.popleft() + if not isinstance(item, dict): + continue + + for key in tuple(item.keys()): + value = item[key] + if isinstance(value, dict): + v_queue.append(value) + + elif ( + isinstance(value, StringType) + and "{task}" in value + ): + item[key] = value.replace("{task}", "{task[name]}") + + solved_templates = self.solve_template_inner_links(templates) + self._templates = solved_templates + self._objected_templates = self.create_ojected_templates( + solved_templates + ) + + def default_templates(self): + """Return default templates data with solved inner keys.""" + return self.solve_template_inner_links( + self.anatomy["templates"] + ) + + def _discover(self): + """ Loads anatomy templates from yaml. + Default templates are loaded if project is not set or project does + not have set it's own. + TODO: create templates if not exist. + + Returns: + TemplatesResultDict: Contain templates data for current project of + default templates. + """ + + if self.project_name is None: + # QUESTION create project specific if not found? + raise AssertionError(( + "Project \"{0}\" does not have his own templates." + " Trying to use default." + ).format(self.project_name)) + + self.set_templates(self.anatomy["templates"]) + + @classmethod + def replace_inner_keys(cls, matches, value, key_values, key): + """Replacement of inner keys in template values.""" + for match in matches: + anatomy_sub_keys = ( + cls.inner_key_name_pattern.findall(match) + ) + if key in anatomy_sub_keys: + raise ValueError(( + "Unsolvable recursion in inner keys, " + "key: \"{}\" is in his own value." + " Can't determine source, please check Anatomy templates." + ).format(key)) + + for anatomy_sub_key in anatomy_sub_keys: + replace_value = key_values.get(anatomy_sub_key) + if replace_value is None: + raise KeyError(( + "Anatomy templates can't be filled." + " Anatomy key `{0}` has" + " invalid inner key `{1}`." + ).format(key, anatomy_sub_key)) + + valid = isinstance(replace_value, (numbers.Number, StringType)) + if not valid: + raise ValueError(( + "Anatomy templates can't be filled." + " Anatomy key `{0}` has" + " invalid inner key `{1}`" + " with value `{2}`." + ).format(key, anatomy_sub_key, str(replace_value))) + + value = value.replace(match, str(replace_value)) + + return value + + @classmethod + def prepare_inner_keys(cls, key_values): + """Check values of inner keys. + + Check if inner key exist in template group and has valid value. + It is also required to avoid infinite loop with unsolvable recursion + when first inner key's value refers to second inner key's value where + first is used. + """ + keys_to_solve = set(key_values.keys()) + while True: + found = False + for key in tuple(keys_to_solve): + value = key_values[key] + + if isinstance(value, StringType): + matches = cls.inner_key_pattern.findall(value) + if not matches: + keys_to_solve.remove(key) + continue + + found = True + key_values[key] = cls.replace_inner_keys( + matches, value, key_values, key + ) + continue + + elif not isinstance(value, dict): + keys_to_solve.remove(key) + continue + + subdict_found = False + for _key, _value in tuple(value.items()): + matches = cls.inner_key_pattern.findall(_value) + if not matches: + continue + + subdict_found = True + found = True + key_values[key][_key] = cls.replace_inner_keys( + matches, _value, key_values, + "{}.{}".format(key, _key) + ) + + if not subdict_found: + keys_to_solve.remove(key) + + if not found: + break + + return key_values + + @classmethod + def solve_template_inner_links(cls, templates): + """Solve templates inner keys identified by "{@*}". + + Process is split into 2 parts. + First is collecting all global keys (keys in top hierarchy where value + is not dictionary). All global keys are set for all group keys (keys + in top hierarchy where value is dictionary). Value of a key is not + overridden in group if already contain value for the key. + + In second part all keys with "at" symbol in value are replaced with + value of the key afterward "at" symbol from the group. + + Args: + templates (dict): Raw templates data. + + Example: + templates:: + key_1: "value_1", + key_2: "{@key_1}/{filling_key}" + + group_1: + key_3: "value_3/{@key_2}" + + group_2: + key_2": "value_2" + key_4": "value_4/{@key_2}" + + output:: + key_1: "value_1" + key_2: "value_1/{filling_key}" + + group_1: { + key_1: "value_1" + key_2: "value_1/{filling_key}" + key_3: "value_3/value_1/{filling_key}" + + group_2: { + key_1: "value_1" + key_2: "value_2" + key_4: "value_3/value_2" + """ + default_key_values = templates.pop("defaults", {}) + for key, value in tuple(templates.items()): + if isinstance(value, dict): + continue + default_key_values[key] = templates.pop(key) + + # Pop "others" key before before expected keys are processed + other_templates = templates.pop("others") or {} + + keys_by_subkey = {} + for sub_key, sub_value in templates.items(): + key_values = {} + key_values.update(default_key_values) + key_values.update(sub_value) + keys_by_subkey[sub_key] = cls.prepare_inner_keys(key_values) + + for sub_key, sub_value in other_templates.items(): + if sub_key in keys_by_subkey: + log.warning(( + "Key \"{}\" is duplicated in others. Skipping." + ).format(sub_key)) + continue + + key_values = {} + key_values.update(default_key_values) + key_values.update(sub_value) + keys_by_subkey[sub_key] = cls.prepare_inner_keys(key_values) + + default_keys_by_subkeys = cls.prepare_inner_keys(default_key_values) + + for key, value in default_keys_by_subkeys.items(): + keys_by_subkey[key] = value + + return keys_by_subkey + + def _dict_to_subkeys_list(self, subdict, pre_keys=None): + if pre_keys is None: + pre_keys = [] + output = [] + for key in subdict: + value = subdict[key] + result = list(pre_keys) + result.append(key) + if isinstance(value, dict): + for item in self._dict_to_subkeys_list(value, result): + output.append(item) + else: + output.append(result) + return output + + def _keys_to_dicts(self, key_list, value): + if not key_list: + return None + if len(key_list) == 1: + return {key_list[0]: value} + return {key_list[0]: self._keys_to_dicts(key_list[1:], value)} + + def _rootless_path(self, result, final_data): + used_values = result.used_values + missing_keys = result.missing_keys + template = result.template + invalid_types = result.invalid_types + if ( + "root" not in used_values + or "root" in missing_keys + or "{root" not in template + ): + return + + for invalid_type in invalid_types: + if "root" in invalid_type: + return + + root_keys = self._dict_to_subkeys_list({"root": used_values["root"]}) + if not root_keys: + return + + output = str(result) + for used_root_keys in root_keys: + if not used_root_keys: + continue + + used_value = used_values + root_key = None + for key in used_root_keys: + used_value = used_value[key] + if root_key is None: + root_key = key + else: + root_key += "[{}]".format(key) + + root_key = "{" + root_key + "}" + output = output.replace(str(used_value), root_key) + + return output + + def format(self, data, strict=True): + copy_data = copy.deepcopy(data) + roots = self.roots + if roots: + copy_data["root"] = roots + result = super(AnatomyTemplates, self).format(copy_data) + result.strict = strict + return result + + def format_all(self, in_data, only_keys=True): + """ Solves templates based on entered data. + + Args: + data (dict): Containing keys to be filled into template. + + Returns: + TemplatesResultDict: Output `TemplateResult` have `strict` + attribute set to False so accessing unfilled keys in templates + won't raise any exceptions. + """ + return self.format(in_data, strict=False) + + +class RootItem(FormatObject): + """Represents one item or roots. + + Holds raw data of root item specification. Raw data contain value + for each platform, but current platform value is used when object + is used for formatting of template. + + Args: + root_raw_data (dict): Dictionary containing root values by platform + names. ["windows", "linux" and "darwin"] + name (str, optional): Root name which is representing. Used with + multi root setup otherwise None value is expected. + parent_keys (list, optional): All dictionary parent keys. Values of + `parent_keys` are used for get full key which RootItem is + representing. Used for replacing root value in path with + formattable key. e.g. parent_keys == ["work"] -> {root[work]} + parent (object, optional): It is expected to be `Roots` object. + Value of `parent` won't affect code logic much. + """ + + def __init__( + self, root_raw_data, name=None, parent_keys=None, parent=None + ): + lowered_platform_keys = {} + for key, value in root_raw_data.items(): + lowered_platform_keys[key.lower()] = value + self.raw_data = lowered_platform_keys + self.cleaned_data = self._clean_roots(lowered_platform_keys) + self.name = name + self.parent_keys = parent_keys or [] + self.parent = parent + + self.available_platforms = list(lowered_platform_keys.keys()) + self.value = lowered_platform_keys.get(platform.system().lower()) + self.clean_value = self.clean_root(self.value) + + def __format__(self, *args, **kwargs): + return self.value.__format__(*args, **kwargs) + + def __str__(self): + return str(self.value) + + def __repr__(self): + return self.__str__() + + def __getitem__(self, key): + if isinstance(key, numbers.Number): + return self.value[key] + + additional_info = "" + if self.parent and self.parent.project_name: + additional_info += " for project \"{}\"".format( + self.parent.project_name + ) + + raise AssertionError( + "Root key \"{}\" is missing{}.".format( + key, additional_info + ) + ) + + def full_key(self): + """Full key value for dictionary formatting in template. + + Returns: + str: Return full replacement key for formatting. This helps when + multiple roots are set. In that case e.g. `"root[work]"` is + returned. + """ + if not self.name: + return "root" + + joined_parent_keys = "".join( + ["[{}]".format(key) for key in self.parent_keys] + ) + return "root{}".format(joined_parent_keys) + + def clean_path(self, path): + """Just replace backslashes with forward slashes.""" + return str(path).replace("\\", "/") + + def clean_root(self, root): + """Makes sure root value does not end with slash.""" + if root: + root = self.clean_path(root) + while root.endswith("/"): + root = root[:-1] + return root + + def _clean_roots(self, raw_data): + """Clean all values of raw root item values.""" + cleaned = {} + for key, value in raw_data.items(): + cleaned[key] = self.clean_root(value) + return cleaned + + def path_remapper(self, path, dst_platform=None, src_platform=None): + """Remap path for specific platform. + + Args: + path (str): Source path which need to be remapped. + dst_platform (str, optional): Specify destination platform + for which remapping should happen. + src_platform (str, optional): Specify source platform. This is + recommended to not use and keep unset until you really want + to use specific platform. + roots (dict/RootItem/None, optional): It is possible to remap + path with different roots then instance where method was + called has. + + Returns: + str/None: When path does not contain known root then + None is returned else returns remapped path with "{root}" + or "{root[]}". + """ + cleaned_path = self.clean_path(path) + if dst_platform: + dst_root_clean = self.cleaned_data.get(dst_platform) + if not dst_root_clean: + key_part = "" + full_key = self.full_key() + if full_key != "root": + key_part += "\"{}\" ".format(full_key) + + log.warning( + "Root {}miss platform \"{}\" definition.".format( + key_part, dst_platform + ) + ) + return None + + if cleaned_path.startswith(dst_root_clean): + return cleaned_path + + if src_platform: + src_root_clean = self.cleaned_data.get(src_platform) + if src_root_clean is None: + log.warning( + "Root \"{}\" miss platform \"{}\" definition.".format( + self.full_key(), src_platform + ) + ) + return None + + if not cleaned_path.startswith(src_root_clean): + return None + + subpath = cleaned_path[len(src_root_clean):] + if dst_platform: + # `dst_root_clean` is used from upper condition + return dst_root_clean + subpath + return self.clean_value + subpath + + result, template = self.find_root_template_from_path(path) + if not result: + return None + + def parent_dict(keys, value): + if not keys: + return value + + key = keys.pop(0) + return {key: parent_dict(keys, value)} + + if dst_platform: + format_value = parent_dict(list(self.parent_keys), dst_root_clean) + else: + format_value = parent_dict(list(self.parent_keys), self.value) + + return template.format(**{"root": format_value}) + + def find_root_template_from_path(self, path): + """Replaces known root value with formattable key in path. + + All platform values are checked for this replacement. + + Args: + path (str): Path where root value should be found. + + Returns: + tuple: Tuple contain 2 values: `success` (bool) and `path` (str). + When success it True then path should contain replaced root + value with formattable key. + + Example: + When input path is:: + "C:/windows/path/root/projects/my_project/file.ext" + + And raw data of item looks like:: + { + "windows": "C:/windows/path/root", + "linux": "/mount/root" + } + + Output will be:: + (True, "{root}/projects/my_project/file.ext") + + If any of raw data value wouldn't match path's root output is:: + (False, "C:/windows/path/root/projects/my_project/file.ext") + """ + result = False + output = str(path) + + root_paths = list(self.cleaned_data.values()) + mod_path = self.clean_path(path) + for root_path in root_paths: + # Skip empty paths + if not root_path: + continue + + if mod_path.startswith(root_path): + result = True + replacement = "{" + self.full_key() + "}" + output = replacement + mod_path[len(root_path):] + break + + return (result, output) + + +class Roots: + """Object which should be used for formatting "root" key in templates. + + Args: + anatomy Anatomy: Anatomy object created for a specific project. + """ + + env_prefix = "OPENPYPE_PROJECT_ROOT" + roots_filename = "roots.json" + + def __init__(self, anatomy): + self.anatomy = anatomy + self.loaded_project = None + self._roots = None + + def __format__(self, *args, **kwargs): + return self.roots.__format__(*args, **kwargs) + + def __getitem__(self, key): + return self.roots[key] + + def reset(self): + """Reset current roots value.""" + self._roots = None + + def path_remapper( + self, path, dst_platform=None, src_platform=None, roots=None + ): + """Remap path for specific platform. + + Args: + path (str): Source path which need to be remapped. + dst_platform (str, optional): Specify destination platform + for which remapping should happen. + src_platform (str, optional): Specify source platform. This is + recommended to not use and keep unset until you really want + to use specific platform. + roots (dict/RootItem/None, optional): It is possible to remap + path with different roots then instance where method was + called has. + + Returns: + str/None: When path does not contain known root then + None is returned else returns remapped path with "{root}" + or "{root[]}". + """ + if roots is None: + roots = self.roots + + if roots is None: + raise ValueError("Roots are not set. Can't find path.") + + if "{root" in path: + path = path.format(**{"root": roots}) + # If `dst_platform` is not specified then return else continue. + if not dst_platform: + return path + + if isinstance(roots, RootItem): + return roots.path_remapper(path, dst_platform, src_platform) + + for _root in roots.values(): + result = self.path_remapper( + path, dst_platform, src_platform, _root + ) + if result is not None: + return result + + def find_root_template_from_path(self, path, roots=None): + """Find root value in entered path and replace it with formatting key. + + Args: + path (str): Source path where root will be searched. + roots (Roots/dict, optional): It is possible to use different + roots than instance where method was triggered has. + + Returns: + tuple: Output contains tuple with bool representing success as + first value and path with or without replaced root with + formatting key as second value. + + Raises: + ValueError: When roots are not entered and can't be loaded. + """ + if roots is None: + log.debug( + "Looking for matching root in path \"{}\".".format(path) + ) + roots = self.roots + + if roots is None: + raise ValueError("Roots are not set. Can't find path.") + + if isinstance(roots, RootItem): + return roots.find_root_template_from_path(path) + + for root_name, _root in roots.items(): + success, result = self.find_root_template_from_path(path, _root) + if success: + log.info("Found match in root \"{}\".".format(root_name)) + return success, result + + log.warning("No matching root was found in current setting.") + return (False, path) + + def set_root_environments(self): + """Set root environments for current project.""" + for key, value in self.root_environments().items(): + os.environ[key] = value + + def root_environments(self): + """Use root keys to create unique keys for environment variables. + + Concatenates prefix "OPENPYPE_ROOT" with root keys to create unique + keys. + + Returns: + dict: Result is `{(str): (str)}` dicitonary where key represents + unique key concatenated by keys and value is root value of + current platform root. + + Example: + With raw root values:: + "work": { + "windows": "P:/projects/work", + "linux": "/mnt/share/projects/work", + "darwin": "/darwin/path/work" + }, + "publish": { + "windows": "P:/projects/publish", + "linux": "/mnt/share/projects/publish", + "darwin": "/darwin/path/publish" + } + + Result on windows platform:: + { + "OPENPYPE_ROOT_WORK": "P:/projects/work", + "OPENPYPE_ROOT_PUBLISH": "P:/projects/publish" + } + + Short example when multiroot is not used:: + { + "OPENPYPE_ROOT": "P:/projects" + } + """ + return self._root_environments() + + def all_root_paths(self, roots=None): + """Return all paths for all roots of all platforms.""" + if roots is None: + roots = self.roots + + output = [] + if isinstance(roots, RootItem): + for value in roots.raw_data.values(): + output.append(value) + return output + + for _roots in roots.values(): + output.extend(self.all_root_paths(_roots)) + return output + + def _root_environments(self, keys=None, roots=None): + if not keys: + keys = [] + if roots is None: + roots = self.roots + + if isinstance(roots, RootItem): + key_items = [self.env_prefix] + for _key in keys: + key_items.append(_key.upper()) + + key = "_".join(key_items) + # Make sure key and value does not contain unicode + # - can happen in Python 2 hosts + return {str(key): str(roots.value)} + + output = {} + for _key, _value in roots.items(): + _keys = list(keys) + _keys.append(_key) + output.update(self._root_environments(_keys, _value)) + return output + + def root_environmets_fill_data(self, template=None): + """Environment variable values in dictionary for rootless path. + + Args: + template (str): Template for environment variable key fill. + By default is set to `"${}"`. + """ + if template is None: + template = "${}" + return self._root_environmets_fill_data(template) + + def _root_environmets_fill_data(self, template, keys=None, roots=None): + if keys is None and roots is None: + return { + "root": self._root_environmets_fill_data( + template, [], self.roots + ) + } + + if isinstance(roots, RootItem): + key_items = [Roots.env_prefix] + for _key in keys: + key_items.append(_key.upper()) + key = "_".join(key_items) + return template.format(key) + + output = {} + for key, value in roots.items(): + _keys = list(keys) + _keys.append(key) + output[key] = self._root_environmets_fill_data( + template, _keys, value + ) + return output + + @property + def project_name(self): + """Return project name which will be used for loading root values.""" + return self.anatomy.project_name + + @property + def roots(self): + """Property for filling "root" key in templates. + + This property returns roots for current project or default root values. + Warning: + Default roots value may cause issues when project use different + roots settings. That may happen when project use multiroot + templates but default roots miss their keys. + """ + if self.project_name != self.loaded_project: + self._roots = None + + if self._roots is None: + self._roots = self._discover() + self.loaded_project = self.project_name + return self._roots + + def _discover(self): + """ Loads current project's roots or default. + + Default roots are loaded if project override's does not contain roots. + + Returns: + `RootItem` or `dict` with multiple `RootItem`s when multiroot + setting is used. + """ + + return self._parse_dict(self.anatomy["roots"], parent=self) + + @staticmethod + def _parse_dict(data, key=None, parent_keys=None, parent=None): + """Parse roots raw data into RootItem or dictionary with RootItems. + + Converting raw roots data to `RootItem` helps to handle platform keys. + This method is recursive to be able handle multiroot setup and + is static to be able to load default roots without creating new object. + + Args: + data (dict): Should contain raw roots data to be parsed. + key (str, optional): Current root key. Set by recursion. + parent_keys (list): Parent dictionary keys. Set by recursion. + parent (Roots, optional): Parent object set in `RootItem` + helps to keep RootItem instance updated with `Roots` object. + + Returns: + `RootItem` or `dict` with multiple `RootItem`s when multiroot + setting is used. + """ + if not parent_keys: + parent_keys = [] + is_last = False + for value in data.values(): + if isinstance(value, StringType): + is_last = True + break + + if is_last: + return RootItem(data, key, parent_keys, parent=parent) + + output = {} + for _key, value in data.items(): + _parent_keys = list(parent_keys) + _parent_keys.append(_key) + output[_key] = Roots._parse_dict(value, _key, _parent_keys, parent) + return output From f0a5daa6434086453b8b3ff1e5ccd57bf055228b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 29 Jun 2022 14:05:45 +0200 Subject: [PATCH 0905/1227] use six.string_types to determine string type --- openpype/pipeline/anatomy.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index 33dfe64cb0..b5b87b6432 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -5,6 +5,8 @@ import platform import collections import numbers +import six + from openpype.settings.lib import ( get_default_anatomy_settings, get_anatomy_settings @@ -19,11 +21,6 @@ from openpype.lib.log import PypeLogger log = PypeLogger.get_logger(__name__) -try: - StringType = basestring -except NameError: - StringType = str - class ProjectNotSet(Exception): """Exception raised when is created Anatomy without project name.""" @@ -124,7 +121,7 @@ class Anatomy: if isinstance(value, dict): value_queue.append(value) - elif isinstance(value, StringType): + elif isinstance(value, six.string_types): item[key] = value.replace("{task}", "{task[name]}") return anatomy_data @@ -462,7 +459,7 @@ class AnatomyTemplates(TemplatesDict): v_queue.append(value) elif ( - isinstance(value, StringType) + isinstance(value, six.string_types) and "{task}" in value ): item[key] = value.replace("{task}", "{task[name]}") @@ -522,8 +519,10 @@ class AnatomyTemplates(TemplatesDict): " invalid inner key `{1}`." ).format(key, anatomy_sub_key)) - valid = isinstance(replace_value, (numbers.Number, StringType)) - if not valid: + if not ( + isinstance(replace_value, numbers.Number) + or isinstance(replace_value, six.string_types) + ): raise ValueError(( "Anatomy templates can't be filled." " Anatomy key `{0}` has" @@ -550,7 +549,7 @@ class AnatomyTemplates(TemplatesDict): for key in tuple(keys_to_solve): value = key_values[key] - if isinstance(value, StringType): + if isinstance(value, six.string_types): matches = cls.inner_key_pattern.findall(value) if not matches: keys_to_solve.remove(key) @@ -1245,7 +1244,7 @@ class Roots: parent_keys = [] is_last = False for value in data.values(): - if isinstance(value, StringType): + if isinstance(value, six.string_types): is_last = True break From 929fe06127e6960b17327bdffbfaf061e767b93f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 29 Jun 2022 14:13:25 +0200 Subject: [PATCH 0906/1227] added deprecation warning to anatomy lib --- openpype/lib/anatomy.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/openpype/lib/anatomy.py b/openpype/lib/anatomy.py index b62b207ade..6d339f058f 100644 --- a/openpype/lib/anatomy.py +++ b/openpype/lib/anatomy.py @@ -1,3 +1,38 @@ +"""Code related to project Anatomy was moved +to 'openpype.pipeline.anatomy' please change your imports as soon as +possible. File will be probably removed in OpenPype 3.14.* +""" + +import warnings +import functools + + +class AnatomyDeprecatedWarning(DeprecationWarning): + pass + + +def anatomy_deprecated(func): + """Mark functions as deprecated. + + It will result in a warning being emitted when the function is used. + """ + + @functools.wraps(func) + def new_func(*args, **kwargs): + warnings.simplefilter("always", AnatomyDeprecatedWarning) + warnings.warn( + ( + "Deprecated import of 'Anatomy'." + " Class was moved to 'openpype.pipeline.anatomy'." + " Please change your imports of Anatomy in codebase." + ), + category=AnatomyDeprecatedWarning + ) + return func(*args, **kwargs) + return new_func + + +@anatomy_deprecated def Anatomy(*args, **kwargs): - from openpype.pipeline import Anatomy + from openpype.pipeline.anatomy import Anatomy return Anatomy(*args, **kwargs) From ffb2a8c33aed22539675462ad0f040f717dca29c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 29 Jun 2022 14:14:21 +0200 Subject: [PATCH 0907/1227] removed unused method --- openpype/pipeline/anatomy.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index b5b87b6432..8a05a3794d 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -7,10 +7,7 @@ import numbers import six -from openpype.settings.lib import ( - get_default_anatomy_settings, - get_anatomy_settings -) +from openpype.settings.lib import get_anatomy_settings from openpype.lib.path_templates import ( TemplateUnsolved, TemplateResult, @@ -90,16 +87,6 @@ class Anatomy: def items(self): return copy.deepcopy(self._data).items() - @staticmethod - def default_data(): - """Default project anatomy data. - - Always return fresh loaded data. May be used as data for new project. - - Not used inside Anatomy itself. - """ - return get_default_anatomy_settings(clear_metadata=False) - @staticmethod def _prepare_anatomy_data(anatomy_data): """Prepare anatomy data for further processing. From bcaafb479d42d099f22e4f96a9f9fe196b32390c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 29 Jun 2022 14:40:40 +0200 Subject: [PATCH 0908/1227] added Anatomy to openpype.pipeline init file --- openpype/pipeline/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py index 2e441fbf27..2cf785d981 100644 --- a/openpype/pipeline/__init__.py +++ b/openpype/pipeline/__init__.py @@ -6,6 +6,7 @@ from .constants import ( from .mongodb import ( AvalonMongoDB, ) +from .anatomy import Anatomy from .create import ( BaseCreator, @@ -96,6 +97,9 @@ __all__ = ( # --- MongoDB --- "AvalonMongoDB", + # --- Anatomy --- + "Anatomy", + # --- Create --- "BaseCreator", "Creator", From a4e371889a61d3f7a36874e00c92ec99db196258 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 29 Jun 2022 14:44:12 +0200 Subject: [PATCH 0909/1227] fix anatomy result copy --- openpype/pipeline/anatomy.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index 8a05a3794d..73081f18fb 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -369,6 +369,17 @@ class AnatomyTemplateResult(TemplateResult): self.invalid_types ) + def copy(self): + tmp = TemplateResult( + str(self), + self.template, + self.solved, + self.used_values, + self.missing_keys, + self.invalid_types + ) + return self.__class__(tmp, self.rootless) + class AnatomyTemplates(TemplatesDict): inner_key_pattern = re.compile(r"(\{@.*?[^{}0]*\})") From 6d894e8ef0291a93a25719c8de45cc79090544c8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 29 Jun 2022 15:00:17 +0200 Subject: [PATCH 0910/1227] Reduce code duplication - merge animation + pointcache extractor logic --- .../maya/plugins/publish/extract_animation.py | 111 ------------------ .../plugins/publish/extract_pointcache.py | 38 ++++-- 2 files changed, 30 insertions(+), 119 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/publish/extract_animation.py diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py deleted file mode 100644 index b0beb5968e..0000000000 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ /dev/null @@ -1,111 +0,0 @@ -import os - -from maya import cmds - -import openpype.api -from openpype.hosts.maya.api.lib import ( - extract_alembic, - suspended_refresh, - maintained_selection, - iter_visible_in_frame_range -) - - -class ExtractAnimation(openpype.api.Extractor): - """Produce an alembic of just point positions and normals. - - Positions and normals, uvs, creases are preserved, but nothing more, - for plain and predictable point caches. - - Plugin can run locally or remotely (on a farm - if instance is marked with - "farm" it will be skipped in local processing, but processed on farm) - """ - - label = "Extract Animation" - hosts = ["maya"] - families = ["animation"] - targets = ["local", "remote"] - - def process(self, instance): - if instance.data.get("farm"): - self.log.debug("Should be processed on farm, skipping.") - return - - # Collect the out set nodes - out_sets = [node for node in instance if node.endswith("out_SET")] - if len(out_sets) != 1: - raise RuntimeError("Couldn't find exactly one out_SET: " - "{0}".format(out_sets)) - out_set = out_sets[0] - roots = cmds.sets(out_set, query=True) - - # Include all descendants - nodes = roots + cmds.listRelatives(roots, - allDescendents=True, - fullPath=True) or [] - - # Collect the start and end including handles - start = instance.data["frameStartHandle"] - end = instance.data["frameEndHandle"] - - self.log.info("Extracting animation..") - dirname = self.staging_dir(instance) - - parent_dir = self.staging_dir(instance) - filename = "{name}.abc".format(**instance.data) - path = os.path.join(parent_dir, filename) - - options = { - "step": instance.data.get("step", 1.0) or 1.0, - "attr": ["cbId"], - "writeVisibility": True, - "writeCreases": True, - "uvWrite": True, - "selection": True, - "worldSpace": instance.data.get("worldSpace", True), - "writeColorSets": instance.data.get("writeColorSets", False), - "writeFaceSets": instance.data.get("writeFaceSets", False) - } - - if not instance.data.get("includeParentHierarchy", True): - # Set the root nodes if we don't want to include parents - # The roots are to be considered the ones that are the actual - # direct members of the set - options["root"] = roots - - if int(cmds.about(version=True)) >= 2017: - # Since Maya 2017 alembic supports multiple uv sets - write them. - options["writeUVSets"] = True - - if instance.data.get("visibleOnly", False): - # If we only want to include nodes that are visible in the frame - # range then we need to do our own check. Alembic's `visibleOnly` - # flag does not filter out those that are only hidden on some - # frames as it counts "animated" or "connected" visibilities as - # if it's always visible. - nodes = list(iter_visible_in_frame_range(nodes, - start=start, - end=end)) - - with suspended_refresh(): - with maintained_selection(): - cmds.select(nodes, noExpand=True) - extract_alembic(file=path, - startFrame=float(start), - endFrame=float(end), - **options) - - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { - 'name': 'abc', - 'ext': 'abc', - 'files': filename, - "stagingDir": dirname, - } - instance.data["representations"].append(representation) - - instance.context.data["cleanupFullPaths"].append(path) - - self.log.info("Extracted {} to {}".format(instance, dirname)) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 7aa3aaee2a..a7ba5b3745 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -33,7 +33,7 @@ class ExtractAlembic(openpype.api.Extractor): self.log.debug("Should be processed on farm, skipping.") return - nodes = instance[:] + nodes, roots = self.get_members_and_roots(instance) # Collect the start and end including handles start = float(instance.data.get("frameStartHandle", 1)) @@ -46,10 +46,6 @@ class ExtractAlembic(openpype.api.Extractor): attr_prefixes = instance.data.get("attrPrefix", "").split(";") attr_prefixes = [value for value in attr_prefixes if value.strip()] - # Get extra export arguments - writeColorSets = instance.data.get("writeColorSets", False) - writeFaceSets = instance.data.get("writeFaceSets", False) - self.log.info("Extracting pointcache..") dirname = self.staging_dir(instance) @@ -63,8 +59,8 @@ class ExtractAlembic(openpype.api.Extractor): "attrPrefix": attr_prefixes, "writeVisibility": True, "writeCreases": True, - "writeColorSets": writeColorSets, - "writeFaceSets": writeFaceSets, + "writeColorSets": instance.data.get("writeColorSets", False), + "writeFaceSets": instance.data.get("writeFaceSets", False), "uvWrite": True, "selection": True, "worldSpace": instance.data.get("worldSpace", True) @@ -74,7 +70,7 @@ class ExtractAlembic(openpype.api.Extractor): # Set the root nodes if we don't want to include parents # The roots are to be considered the ones that are the actual # direct members of the set - options["root"] = instance.data.get("setMembers") + options["root"] = roots if int(cmds.about(version=True)) >= 2017: # Since Maya 2017 alembic supports multiple uv sets - write them. @@ -112,3 +108,29 @@ class ExtractAlembic(openpype.api.Extractor): instance.context.data["cleanupFullPaths"].append(path) self.log.info("Extracted {} to {}".format(instance, dirname)) + + def get_members_and_roots(self, instance): + return instance[:], instance.data.get("setMembers") + + +class ExtractAnimation(ExtractAlembic): + label = "Extract Animation" + families = ["animation"] + + def get_members_and_roots(self, instance): + + # Collect the out set nodes + out_sets = [node for node in instance if node.endswith("out_SET")] + if len(out_sets) != 1: + raise RuntimeError("Couldn't find exactly one out_SET: " + "{0}".format(out_sets)) + out_set = out_sets[0] + roots = cmds.sets(out_set, query=True) + + # Include all descendants + nodes = roots + cmds.listRelatives(roots, + allDescendents=True, + fullPath=True) or [] + + return nodes, roots + From af2c57674eb9263587ec082db70e12f69ccf2555 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 29 Jun 2022 15:21:21 +0200 Subject: [PATCH 0911/1227] use new Anatomy source in tools --- openpype/tools/loader/widgets.py | 3 +-- openpype/tools/texture_copy/app.py | 3 +-- openpype/tools/workfiles/files_widget.py | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 1f6d8b9fa2..13e18b3757 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -17,8 +17,7 @@ from openpype.client import ( get_thumbnail_id_from_source, get_thumbnail, ) -from openpype.api import Anatomy -from openpype.pipeline import HeroVersionType +from openpype.pipeline import HeroVersionType, Anatomy from openpype.pipeline.thumbnail import get_thumbnail_binary from openpype.pipeline.load import ( discover_loader_plugins, diff --git a/openpype/tools/texture_copy/app.py b/openpype/tools/texture_copy/app.py index 746a72b3ec..a695bb8c4d 100644 --- a/openpype/tools/texture_copy/app.py +++ b/openpype/tools/texture_copy/app.py @@ -6,8 +6,7 @@ import speedcopy from openpype.client import get_project, get_asset_by_name from openpype.lib import Terminal -from openpype.api import Anatomy -from openpype.pipeline import legacy_io +from openpype.pipeline import legacy_io, Anatomy t = Terminal() diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index a7e54471dc..c92e4fe904 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -11,7 +11,6 @@ from openpype.tools.utils import PlaceholderLineEdit from openpype.tools.utils.delegates import PrettyTimeDelegate from openpype.lib import ( emit_event, - Anatomy, get_workfile_template_key, create_workdir_extra_folders, ) @@ -22,6 +21,7 @@ from openpype.lib.avalon_context import ( from openpype.pipeline import ( registered_host, legacy_io, + Anatomy, ) from .model import ( WorkAreaFilesModel, From b6ea950b3a091053b034d90ce82f421e9cb25be9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 29 Jun 2022 15:22:49 +0200 Subject: [PATCH 0912/1227] use new Anatomy import in rest of pipeline --- openpype/pipeline/context_tools.py | 7 ++----- openpype/pipeline/load/utils.py | 2 +- openpype/plugins/load/delete_old_versions.py | 3 +-- openpype/plugins/load/delivery.py | 4 ++-- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index 4a147c230b..047482f6ff 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -14,11 +14,8 @@ from pyblish.lib import MessageHandler import openpype from openpype.modules import load_modules, ModulesManager from openpype.settings import get_project_settings -from openpype.lib import ( - Anatomy, - filter_pyblish_plugins, -) - +from openpype.lib import filter_pyblish_plugins +from .anatomy import Anatomy from . import ( legacy_io, register_loader_plugin_path, diff --git a/openpype/pipeline/load/utils.py b/openpype/pipeline/load/utils.py index 99e5d11f82..e813b7934f 100644 --- a/openpype/pipeline/load/utils.py +++ b/openpype/pipeline/load/utils.py @@ -9,10 +9,10 @@ import numbers import six from bson.objectid import ObjectId -from openpype.lib import Anatomy from openpype.pipeline import ( schema, legacy_io, + Anatomy, ) log = logging.getLogger(__name__) diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index c3e9e9fa0a..7465f53855 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -9,9 +9,8 @@ import qargparse from Qt import QtWidgets, QtCore from openpype import style -from openpype.pipeline import load, AvalonMongoDB +from openpype.pipeline import load, AvalonMongoDB, Anatomy from openpype.lib import StringTemplate -from openpype.api import Anatomy class DeleteOldVersions(load.SubsetLoaderPlugin): diff --git a/openpype/plugins/load/delivery.py b/openpype/plugins/load/delivery.py index 7df07e3f64..0361ab2be5 100644 --- a/openpype/plugins/load/delivery.py +++ b/openpype/plugins/load/delivery.py @@ -3,8 +3,8 @@ from collections import defaultdict from Qt import QtWidgets, QtCore, QtGui -from openpype.pipeline import load, AvalonMongoDB -from openpype.api import Anatomy, config +from openpype.lib import config +from openpype.pipeline import load, AvalonMongoDB, Anatomy from openpype import resources, style from openpype.lib.delivery import ( From 168c3b38a46d656f31c14b940fbd6b533a926766 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 29 Jun 2022 15:28:59 +0200 Subject: [PATCH 0913/1227] modified imports in sync server --- openpype/modules/sync_server/providers/local_drive.py | 5 +++-- openpype/modules/sync_server/sync_server_module.py | 10 ++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/openpype/modules/sync_server/providers/local_drive.py b/openpype/modules/sync_server/providers/local_drive.py index 68f604b39c..172cb338cf 100644 --- a/openpype/modules/sync_server/providers/local_drive.py +++ b/openpype/modules/sync_server/providers/local_drive.py @@ -4,10 +4,11 @@ import shutil import threading import time -from openpype.api import Logger, Anatomy +from openpype.api import Logger +from openpype.pipeline import Anatomy from .abstract_provider import AbstractProvider -log = Logger().get_logger("SyncServer") +log = Logger.get_logger("SyncServer") class LocalDriveHandler(AbstractProvider): diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 698b296a52..4027561d22 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -9,14 +9,12 @@ from collections import deque, defaultdict from openpype.modules import OpenPypeModule from openpype_interfaces import ITrayModule -from openpype.api import ( - Anatomy, +from openpype.settings import ( get_project_settings, get_system_settings, - get_local_site_id ) -from openpype.lib import PypeLogger -from openpype.pipeline import AvalonMongoDB +from openpype.lib import PypeLogger, get_local_site_id +from openpype.pipeline import AvalonMongoDB, Anatomy from openpype.settings.lib import ( get_default_anatomy_settings, get_anatomy_settings @@ -28,7 +26,7 @@ from .providers import lib from .utils import time_function, SyncStatus, SiteAlreadyPresentError -log = PypeLogger().get_logger("SyncServer") +log = PypeLogger.get_logger("SyncServer") class SyncServerModule(OpenPypeModule, ITrayModule): From 05fe6ca5cf53802a2fffd55282ef011341023637 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 29 Jun 2022 15:29:23 +0200 Subject: [PATCH 0914/1227] collect anatomy objec plugin is using new Anatomy import --- openpype/plugins/publish/collect_anatomy_object.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/collect_anatomy_object.py b/openpype/plugins/publish/collect_anatomy_object.py index 2c87918728..b1415098b6 100644 --- a/openpype/plugins/publish/collect_anatomy_object.py +++ b/openpype/plugins/publish/collect_anatomy_object.py @@ -4,11 +4,11 @@ Requires: os.environ -> AVALON_PROJECT Provides: - context -> anatomy (pype.api.Anatomy) + context -> anatomy (openpype.pipeline.anatomy.Anatomy) """ import os -from openpype.api import Anatomy import pyblish.api +from openpype.pipeline import Anatomy class CollectAnatomyObject(pyblish.api.ContextPlugin): From 7bcc7277c394a408ad289a90c131fe8e807eff2f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 29 Jun 2022 15:29:59 +0200 Subject: [PATCH 0915/1227] ftrack is using new source of anatomy --- .../ftrack/event_handlers_server/event_user_assigment.py | 4 ++-- .../ftrack/event_handlers_user/action_create_folders.py | 2 +- .../ftrack/event_handlers_user/action_delete_old_versions.py | 3 +-- .../modules/ftrack/event_handlers_user/action_delivery.py | 3 ++- .../ftrack/event_handlers_user/action_fill_workfile_attr.py | 4 ++-- openpype/modules/ftrack/event_handlers_user/action_rv.py | 2 +- .../event_handlers_user/action_store_thumbnails_to_avalon.py | 3 +-- 7 files changed, 10 insertions(+), 11 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/event_user_assigment.py b/openpype/modules/ftrack/event_handlers_server/event_user_assigment.py index 82b79e986b..88d252e8cf 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_user_assigment.py +++ b/openpype/modules/ftrack/event_handlers_server/event_user_assigment.py @@ -2,11 +2,11 @@ import re import subprocess from openpype.client import get_asset_by_id, get_asset_by_name +from openpype.settings import get_project_settings +from openpype.pipeline import Anatomy from openpype_modules.ftrack.lib import BaseEvent from openpype_modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY -from openpype.api import Anatomy, get_project_settings - class UserAssigmentEvent(BaseEvent): """ diff --git a/openpype/modules/ftrack/event_handlers_user/action_create_folders.py b/openpype/modules/ftrack/event_handlers_user/action_create_folders.py index 81f38e0c39..9806f83773 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_create_folders.py +++ b/openpype/modules/ftrack/event_handlers_user/action_create_folders.py @@ -1,7 +1,7 @@ import os import collections import copy -from openpype.api import Anatomy +from openpype.pipeline import Anatomy from openpype_modules.ftrack.lib import BaseAction, statics_icon diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py b/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py index 3400c509ab..79d04a7854 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py @@ -11,9 +11,8 @@ from openpype.client import ( get_versions, get_representations ) -from openpype.api import Anatomy from openpype.lib import StringTemplate, TemplateUnsolved -from openpype.pipeline import AvalonMongoDB +from openpype.pipeline import AvalonMongoDB, Anatomy from openpype_modules.ftrack.lib import BaseAction, statics_icon diff --git a/openpype/modules/ftrack/event_handlers_user/action_delivery.py b/openpype/modules/ftrack/event_handlers_user/action_delivery.py index 4b799b092b..ad82af39a3 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delivery.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delivery.py @@ -10,12 +10,13 @@ from openpype.client import ( get_versions, get_representations ) -from openpype.api import Anatomy, config +from openpype.pipeline import Anatomy from openpype_modules.ftrack.lib import BaseAction, statics_icon from openpype_modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY from openpype_modules.ftrack.lib.custom_attributes import ( query_custom_attributes ) +from openpype.lib import config from openpype.lib.delivery import ( path_from_representation, get_format_dict, diff --git a/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py b/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py index d30c41a749..d91649d7ba 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py +++ b/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py @@ -11,13 +11,13 @@ from openpype.client import ( get_project, get_assets, ) -from openpype.api import get_project_settings +from openpype.settings import get_project_settings from openpype.lib import ( get_workfile_template_key, get_workdir_data, - Anatomy, StringTemplate, ) +from openpype.pipeline import Anatomy from openpype_modules.ftrack.lib import BaseAction, statics_icon from openpype_modules.ftrack.lib.avalon_sync import create_chunks diff --git a/openpype/modules/ftrack/event_handlers_user/action_rv.py b/openpype/modules/ftrack/event_handlers_user/action_rv.py index 2480ea7f95..d05f0c47f6 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_rv.py +++ b/openpype/modules/ftrack/event_handlers_user/action_rv.py @@ -11,10 +11,10 @@ from openpype.client import ( get_version_by_name, get_representation_by_name ) -from openpype.api import Anatomy from openpype.pipeline import ( get_representation_path, AvalonMongoDB, + Anatomy, ) from openpype_modules.ftrack.lib import BaseAction, statics_icon diff --git a/openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py b/openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py index d655dddcaf..8748f426bd 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py @@ -14,8 +14,7 @@ from openpype.client import ( get_representations ) from openpype_modules.ftrack.lib import BaseAction, statics_icon -from openpype.api import Anatomy -from openpype.pipeline import AvalonMongoDB +from openpype.pipeline import AvalonMongoDB, Anatomy from openpype_modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY From b4daf3bf769df737d31246fe3adaefeedb48b5c7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 29 Jun 2022 15:32:48 +0200 Subject: [PATCH 0916/1227] use new anatomy source in hosts --- openpype/hooks/pre_global_host_data.py | 3 +-- openpype/hosts/hiero/api/lib.py | 5 +++-- .../vendor/husdoutputprocessors/avalon_uri_processor.py | 3 +-- openpype/hosts/maya/api/plugin.py | 2 +- openpype/hosts/nuke/api/lib.py | 8 +++++--- openpype/hosts/tvpaint/plugins/load/load_workfile.py | 2 +- openpype/hosts/unreal/api/rendering.py | 2 +- .../unreal/plugins/publish/collect_render_instances.py | 2 +- .../deadline/plugins/publish/submit_publish_job.py | 2 +- 9 files changed, 15 insertions(+), 14 deletions(-) diff --git a/openpype/hooks/pre_global_host_data.py b/openpype/hooks/pre_global_host_data.py index ea5e290d6f..6577e37cbe 100644 --- a/openpype/hooks/pre_global_host_data.py +++ b/openpype/hooks/pre_global_host_data.py @@ -1,11 +1,10 @@ -from openpype.api import Anatomy from openpype.lib import ( PreLaunchHook, EnvironmentPrepData, prepare_app_environments, prepare_context_environments ) -from openpype.pipeline import AvalonMongoDB +from openpype.pipeline import AvalonMongoDB, Anatomy class GlobalHostDataHook(PreLaunchHook): diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 8c8c31bc4c..2f66f3ddd7 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -19,8 +19,9 @@ from openpype.client import ( get_last_versions, get_representations, ) -from openpype.pipeline import legacy_io -from openpype.api import (Logger, Anatomy, get_anatomy_settings) +from openpype.settings import get_anatomy_settings +from openpype.pipeline import legacy_io, Anatomy +from openpype.api import Logger from . import tags try: diff --git a/openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py b/openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py index 202287f1c3..d7d1c79d73 100644 --- a/openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py +++ b/openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py @@ -5,8 +5,7 @@ import husdoutputprocessors.base as base import colorbleed.usdlib as usdlib from openpype.client import get_asset_by_name -from openpype.api import Anatomy -from openpype.pipeline import legacy_io +from openpype.pipeline import legacy_io, Anatomy class AvalonURIOutputProcessor(base.OutputProcessorBase): diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index f05893a7b4..9280805945 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -9,8 +9,8 @@ from openpype.pipeline import ( LoaderPlugin, get_representation_path, AVALON_CONTAINER_ID, + Anatomy, ) -from openpype.api import Anatomy from openpype.settings import get_project_settings from .pipeline import containerise from . import lib diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 45a1e72703..f565ec8546 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -20,21 +20,23 @@ from openpype.client import ( ) from openpype.api import ( Logger, - Anatomy, BuildWorkfile, get_version_from_path, - get_anatomy_settings, get_workdir_data, get_asset, get_current_project_settings, ) from openpype.tools.utils import host_tools from openpype.lib.path_tools import HostDirmap -from openpype.settings import get_project_settings +from openpype.settings import ( + get_project_settings, + get_anatomy_settings, +) from openpype.modules import ModulesManager from openpype.pipeline import ( discover_legacy_creator_plugins, legacy_io, + Anatomy, ) from . import gizmo_menu diff --git a/openpype/hosts/tvpaint/plugins/load/load_workfile.py b/openpype/hosts/tvpaint/plugins/load/load_workfile.py index 462f12abf0..c6dc765a27 100644 --- a/openpype/hosts/tvpaint/plugins/load/load_workfile.py +++ b/openpype/hosts/tvpaint/plugins/load/load_workfile.py @@ -10,8 +10,8 @@ from openpype.lib import ( from openpype.pipeline import ( registered_host, legacy_io, + Anatomy, ) -from openpype.api import Anatomy from openpype.hosts.tvpaint.api import lib, pipeline, plugin diff --git a/openpype/hosts/unreal/api/rendering.py b/openpype/hosts/unreal/api/rendering.py index b2732506fc..29e4747f6e 100644 --- a/openpype/hosts/unreal/api/rendering.py +++ b/openpype/hosts/unreal/api/rendering.py @@ -2,7 +2,7 @@ import os import unreal -from openpype.api import Anatomy +from openpype.pipeline import Anatomy from openpype.hosts.unreal.api import pipeline diff --git a/openpype/hosts/unreal/plugins/publish/collect_render_instances.py b/openpype/hosts/unreal/plugins/publish/collect_render_instances.py index 9fb45ea7a7..cb28f4bf60 100644 --- a/openpype/hosts/unreal/plugins/publish/collect_render_instances.py +++ b/openpype/hosts/unreal/plugins/publish/collect_render_instances.py @@ -3,7 +3,7 @@ from pathlib import Path import unreal -from openpype.api import Anatomy +from openpype.pipeline import Anatomy from openpype.hosts.unreal.api import pipeline import pyblish.api diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index b54b00d099..9dd1428a63 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -1045,7 +1045,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): get publish_path Args: - anatomy (pype.lib.anatomy.Anatomy): + anatomy (openpype.pipeline.anatomy.Anatomy): template_data (dict): pre-calculated collected data for process asset (string): asset name subset (string): subset name (actually group name of subset) From 19a5ed7be3967bd3ea25167c11b802275e20ac5b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 29 Jun 2022 17:22:21 +0200 Subject: [PATCH 0917/1227] Refactor `iter_visible_in_frame_range` -> `iter_visible_nodes_in_range` --- openpype/hosts/maya/api/lib.py | 2 +- openpype/hosts/maya/plugins/publish/extract_animation.py | 4 ++-- openpype/hosts/maya/plugins/publish/extract_pointcache.py | 4 ++-- openpype/hosts/maya/plugins/publish/validate_visible_only.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 43f738bfc7..34340a13a5 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3224,7 +3224,7 @@ def maintained_time(): cmds.currentTime(ct, edit=True) -def iter_visible_in_frame_range(nodes, start, end): +def iter_visible_nodes_in_range(nodes, start, end): """Yield nodes that are visible in start-end frame range. - Ignores intermediateObjects completely. diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py index b0beb5968e..8ed2d8d7a3 100644 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_animation.py @@ -7,7 +7,7 @@ from openpype.hosts.maya.api.lib import ( extract_alembic, suspended_refresh, maintained_selection, - iter_visible_in_frame_range + iter_visible_nodes_in_range ) @@ -83,7 +83,7 @@ class ExtractAnimation(openpype.api.Extractor): # flag does not filter out those that are only hidden on some # frames as it counts "animated" or "connected" visibilities as # if it's always visible. - nodes = list(iter_visible_in_frame_range(nodes, + nodes = list(iter_visible_nodes_in_range(nodes, start=start, end=end)) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 7aa3aaee2a..775b5e9939 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -7,7 +7,7 @@ from openpype.hosts.maya.api.lib import ( extract_alembic, suspended_refresh, maintained_selection, - iter_visible_in_frame_range + iter_visible_nodes_in_range ) @@ -86,7 +86,7 @@ class ExtractAlembic(openpype.api.Extractor): # flag does not filter out those that are only hidden on some # frames as it counts "animated" or "connected" visibilities as # if it's always visible. - nodes = list(iter_visible_in_frame_range(nodes, + nodes = list(iter_visible_nodes_in_range(nodes, start=start, end=end)) diff --git a/openpype/hosts/maya/plugins/publish/validate_visible_only.py b/openpype/hosts/maya/plugins/publish/validate_visible_only.py index 2a10171113..59a7f976ab 100644 --- a/openpype/hosts/maya/plugins/publish/validate_visible_only.py +++ b/openpype/hosts/maya/plugins/publish/validate_visible_only.py @@ -1,7 +1,7 @@ import pyblish.api import openpype.api -from openpype.hosts.maya.api.lib import iter_visible_in_frame_range +from openpype.hosts.maya.api.lib import iter_visible_nodes_in_range import openpype.hosts.maya.api.action @@ -40,7 +40,7 @@ class ValidateAlembicVisibleOnly(pyblish.api.InstancePlugin): nodes = instance[:] start, end = cls.get_frame_range(instance) - if not any(iter_visible_in_frame_range(nodes, start, end)): + if not any(iter_visible_nodes_in_range(nodes, start, end)): # Return the nodes we have considered so the user can identify # them with the select invalid action return nodes From c0b03113a5e126d2afeee462eb8df51e031a2962 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 30 Jun 2022 10:21:25 +0200 Subject: [PATCH 0918/1227] Added minimal permissions for MongoDB Collected from Discord discussions. --- website/docs/admin_use.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/docs/admin_use.md b/website/docs/admin_use.md index f84905c486..1c4ae9e01c 100644 --- a/website/docs/admin_use.md +++ b/website/docs/admin_use.md @@ -105,6 +105,10 @@ save it in secure way to your systems keyring - on Windows it is **Credential Ma This can be also set beforehand with environment variable `OPENPYPE_MONGO`. If set it takes precedence over the one set in keyring. +:::tip Minimal permissions for DB user +- `readWrite` role to `openpype` and `avalon` databases +- `find` permission on `openpype`, `avalon` and `local` + #### Check for OpenPype version path When connection to MongoDB is made, OpenPype will get various settings from there - one among them is directory location where OpenPype versions are stored. If this directory exists OpenPype tries to From 5822275b6c565a9852ce4a4f824befb1c7c5efe1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 10:34:36 +0200 Subject: [PATCH 0919/1227] move abstract meta pyblish class into openpype.pipeline.publish --- openpype/lib/abstract_metaplugins.py | 35 +++++++++++++++++--- openpype/pipeline/publish/__init__.py | 6 ++++ openpype/pipeline/publish/publish_plugins.py | 14 ++++++++ 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/openpype/lib/abstract_metaplugins.py b/openpype/lib/abstract_metaplugins.py index f8163956ad..6199a96ef1 100644 --- a/openpype/lib/abstract_metaplugins.py +++ b/openpype/lib/abstract_metaplugins.py @@ -1,10 +1,35 @@ -from abc import ABCMeta -from pyblish.plugin import MetaPlugin, ExplicitMetaPlugin +"""Content was moved to 'openpype.pipeline.farm.abstract_metaplugins'. + +Please change your imports as soon as possible. + +File will be probably removed in OpenPype 3.14.* +""" + +import warnings +from openpype.pipeline.publish import ( + AbstractMetaInstancePlugin, + AbstractMetaContextPlugin +) -class AbstractMetaInstancePlugin(ABCMeta, MetaPlugin): +class MetaPluginsDeprecated(DeprecationWarning): pass -class AbstractMetaContextPlugin(ABCMeta, ExplicitMetaPlugin): - pass +warnings.simplefilter("always", MetaPluginsDeprecated) +warnings.warn( + ( + "Content of 'abstract_metaplugins' was moved." + "\nUsing deprecated source of 'abstract_metaplugins'. Content was" + " moved to 'openpype.pipeline.farm.abstract_metaplugins'." + " Please change your imports as soon as possible." + ), + category=MetaPluginsDeprecated, + stacklevel=4 +) + + +__all__ = ( + "AbstractMetaInstancePlugin", + "AbstractMetaContextPlugin", +) diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index af5d7c4a91..03d730f37a 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -1,4 +1,7 @@ from .publish_plugins import ( + AbstractMetaInstancePlugin, + AbstractMetaContextPlugin, + PublishValidationError, PublishXmlValidationError, KnownPublishError, @@ -15,6 +18,9 @@ from .lib import ( __all__ = ( + "AbstractMetaInstancePlugin", + "AbstractMetaContextPlugin", + "PublishValidationError", "PublishXmlValidationError", "KnownPublishError", diff --git a/openpype/pipeline/publish/publish_plugins.py b/openpype/pipeline/publish/publish_plugins.py index 2402a005c2..71a2c675b6 100644 --- a/openpype/pipeline/publish/publish_plugins.py +++ b/openpype/pipeline/publish/publish_plugins.py @@ -1,7 +1,17 @@ +from abc import ABCMeta +from pyblish.plugin import MetaPlugin, ExplicitMetaPlugin from openpype.lib import BoolDef from .lib import load_help_content_from_plugin +class AbstractMetaInstancePlugin(ABCMeta, MetaPlugin): + pass + + +class AbstractMetaContextPlugin(ABCMeta, ExplicitMetaPlugin): + pass + + class PublishValidationError(Exception): """Validation error happened during publishing. @@ -16,6 +26,7 @@ class PublishValidationError(Exception): description(str): Detailed description of an error. It is possible to use Markdown syntax. """ + def __init__(self, message, title=None, description=None, detail=None): self.message = message self.title = title or "< Missing title >" @@ -49,6 +60,7 @@ class KnownPublishError(Exception): Message will be shown in UI for artist. """ + pass @@ -92,6 +104,7 @@ class OpenPypePyblishPluginMixin: Returns: list: Attribute definitions for plugin. """ + return [] @classmethod @@ -116,6 +129,7 @@ class OpenPypePyblishPluginMixin: Args: data(dict): Data from instance or context. """ + return ( data .get("publish_attributes", {}) From 2d8c41cc6e6e53ecc21372f75bfd590397a33a28 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 10:39:05 +0200 Subject: [PATCH 0920/1227] moved render abstractions to openpype.pipeline.publish --- .../plugins/publish/collect_render.py | 6 +- .../plugins/publish/collect_farm_render.py | 8 +- openpype/lib/abstract_collect_render.py | 280 ++---------------- openpype/lib/abstract_expected_files.py | 71 ++--- .../deadline/abstract_submit_deadline.py | 2 +- openpype/pipeline/publish/__init__.py | 11 + .../publish/abstract_collect_render.py | 268 +++++++++++++++++ .../publish/abstract_expected_files.py | 53 ++++ 8 files changed, 386 insertions(+), 313 deletions(-) create mode 100644 openpype/pipeline/publish/abstract_collect_render.py create mode 100644 openpype/pipeline/publish/abstract_expected_files.py diff --git a/openpype/hosts/aftereffects/plugins/publish/collect_render.py b/openpype/hosts/aftereffects/plugins/publish/collect_render.py index 97b3175c57..bb199a61f7 100644 --- a/openpype/hosts/aftereffects/plugins/publish/collect_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/collect_render.py @@ -6,8 +6,8 @@ import attr import pyblish.api from openpype.settings import get_project_settings -from openpype.lib import abstract_collect_render -from openpype.lib.abstract_collect_render import RenderInstance +from openpype.pipeline import publish +from openpype.pipeline.publish import RenderInstance from openpype.hosts.aftereffects.api import get_stub @@ -25,7 +25,7 @@ class AERenderInstance(RenderInstance): file_name = attr.ib(default=None) -class CollectAERender(abstract_collect_render.AbstractCollectRender): +class CollectAERender(publish.AbstractCollectRender): order = pyblish.api.CollectorOrder + 0.405 label = "Collect After Effects Render Layers" diff --git a/openpype/hosts/harmony/plugins/publish/collect_farm_render.py b/openpype/hosts/harmony/plugins/publish/collect_farm_render.py index 3e9e680efd..f6b26eb3e8 100644 --- a/openpype/hosts/harmony/plugins/publish/collect_farm_render.py +++ b/openpype/hosts/harmony/plugins/publish/collect_farm_render.py @@ -4,11 +4,10 @@ from pathlib import Path import attr -import openpype.lib -import openpype.lib.abstract_collect_render -from openpype.lib.abstract_collect_render import RenderInstance from openpype.lib import get_formatted_current_time from openpype.pipeline import legacy_io +from openpype.pipeline import publish +from openpype.pipeline.publish import RenderInstance import openpype.hosts.harmony.api as harmony @@ -20,8 +19,7 @@ class HarmonyRenderInstance(RenderInstance): leadingZeros = attr.ib(default=3) -class CollectFarmRender(openpype.lib.abstract_collect_render. - AbstractCollectRender): +class CollectFarmRender(publish.AbstractCollectRender): """Gather all publishable renders.""" # https://docs.toonboom.com/help/harmony-17/premium/reference/node/output/write-node-image-formats.html diff --git a/openpype/lib/abstract_collect_render.py b/openpype/lib/abstract_collect_render.py index 3d81f6d794..2cc1c23822 100644 --- a/openpype/lib/abstract_collect_render.py +++ b/openpype/lib/abstract_collect_render.py @@ -1,269 +1,33 @@ # -*- coding: utf-8 -*- -"""Collect render template. +"""Content was moved to 'openpype.pipeline.farm.abstract_collect_render'. -TODO: use @dataclass when times come. +Please change your imports as soon as possible. +File will be probably removed in OpenPype 3.14.* """ -from abc import abstractmethod -import attr -import six - -import pyblish.api - -from openpype.pipeline import legacy_io - -from .abstract_metaplugins import AbstractMetaContextPlugin +import warnings +from openpype.pipeline.publish import AbstractCollectRender, RenderInstance -@attr.s -class RenderInstance(object): - """Data collected by collectors. - - This data class later on passed to collected instances. - Those attributes are required later on. - - """ - - # metadata - version = attr.ib() # instance version - time = attr.ib() # time of instance creation (get_formatted_current_time) - source = attr.ib() # path to source scene file - label = attr.ib() # label to show in GUI - subset = attr.ib() # subset name - task = attr.ib() # task name - asset = attr.ib() # asset name (AVALON_ASSET) - attachTo = attr.ib() # subset name to attach render to - setMembers = attr.ib() # list of nodes/members producing render output - publish = attr.ib() # bool, True to publish instance - name = attr.ib() # instance name - - # format settings - resolutionWidth = attr.ib() # resolution width (1920) - resolutionHeight = attr.ib() # resolution height (1080) - pixelAspect = attr.ib() # pixel aspect (1.0) - - # time settings - frameStart = attr.ib() # start frame - frameEnd = attr.ib() # start end - frameStep = attr.ib() # frame step - - handleStart = attr.ib(default=None) # start frame - handleEnd = attr.ib(default=None) # start frame - - # for software (like Harmony) where frame range cannot be set by DB - # handles need to be propagated if exist - ignoreFrameHandleCheck = attr.ib(default=False) - - # -------------------- - # With default values - # metadata - renderer = attr.ib(default="") # renderer - can be used in Deadline - review = attr.ib(default=False) # generate review from instance (bool) - priority = attr.ib(default=50) # job priority on farm - - family = attr.ib(default="renderlayer") - families = attr.ib(default=["renderlayer"]) # list of families - - # format settings - multipartExr = attr.ib(default=False) # flag for multipart exrs - convertToScanline = attr.ib(default=False) # flag for exr conversion - - tileRendering = attr.ib(default=False) # bool: treat render as tiles - tilesX = attr.ib(default=0) # number of tiles in X - tilesY = attr.ib(default=0) # number of tiles in Y - - # submit_publish_job - toBeRenderedOn = attr.ib(default=None) - deadlineSubmissionJob = attr.ib(default=None) - anatomyData = attr.ib(default=None) - outputDir = attr.ib(default=None) - context = attr.ib(default=None) - - @frameStart.validator - def check_frame_start(self, _, value): - """Validate if frame start is not larger then end.""" - if value > self.frameEnd: - raise ValueError("frameStart must be smaller " - "or equal then frameEnd") - - @frameEnd.validator - def check_frame_end(self, _, value): - """Validate if frame end is not less then start.""" - if value < self.frameStart: - raise ValueError("frameEnd must be smaller " - "or equal then frameStart") - - @tilesX.validator - def check_tiles_x(self, _, value): - """Validate if tile x isn't less then 1.""" - if not self.tileRendering: - return - if value < 1: - raise ValueError("tile X size cannot be less then 1") - - if value == 1 and self.tilesY == 1: - raise ValueError("both tiles X a Y sizes are set to 1") - - @tilesY.validator - def check_tiles_y(self, _, value): - """Validate if tile y isn't less then 1.""" - if not self.tileRendering: - return - if value < 1: - raise ValueError("tile Y size cannot be less then 1") - - if value == 1 and self.tilesX == 1: - raise ValueError("both tiles X a Y sizes are set to 1") +class CollectRenderDeprecated(DeprecationWarning): + pass -@six.add_metaclass(AbstractMetaContextPlugin) -class AbstractCollectRender(pyblish.api.ContextPlugin): - """Gather all publishable render layers from renderSetup.""" +warnings.simplefilter("always", CollectRenderDeprecated) +warnings.warn( + ( + "Content of 'abstract_collect_render' was moved." + "\nUsing deprecated source of 'abstract_collect_render'. Content was" + " move to 'openpype.pipeline.farm.abstract_collect_render'." + " Please change your imports as soon as possible." + ), + category=CollectRenderDeprecated, + stacklevel=4 +) - order = pyblish.api.CollectorOrder + 0.01 - label = "Collect Render" - sync_workfile_version = False - def __init__(self, *args, **kwargs): - """Constructor.""" - super(AbstractCollectRender, self).__init__(*args, **kwargs) - self._file_path = None - self._asset = legacy_io.Session["AVALON_ASSET"] - self._context = None - - def process(self, context): - """Entry point to collector.""" - self._context = context - for instance in context: - # make sure workfile instance publishing is enabled - try: - if "workfile" in instance.data["families"]: - instance.data["publish"] = True - # TODO merge renderFarm and render.farm - if ("renderFarm" in instance.data["families"] or - "render.farm" in instance.data["families"]): - instance.data["remove"] = True - except KeyError: - # be tolerant if 'families' is missing. - pass - - self._file_path = context.data["currentFile"].replace("\\", "/") - - render_instances = self.get_instances(context) - for render_instance in render_instances: - exp_files = self.get_expected_files(render_instance) - assert exp_files, "no file names were generated, this is bug" - - # if we want to attach render to subset, check if we have AOV's - # in expectedFiles. If so, raise error as we cannot attach AOV - # (considered to be subset on its own) to another subset - if render_instance.attachTo: - assert isinstance(exp_files, list), ( - "attaching multiple AOVs or renderable cameras to " - "subset is not supported" - ) - - frame_start_render = int(render_instance.frameStart) - frame_end_render = int(render_instance.frameEnd) - if (render_instance.ignoreFrameHandleCheck or - int(context.data['frameStartHandle']) == frame_start_render - and int(context.data['frameEndHandle']) == frame_end_render): # noqa: W503, E501 - - handle_start = context.data['handleStart'] - handle_end = context.data['handleEnd'] - frame_start = context.data['frameStart'] - frame_end = context.data['frameEnd'] - frame_start_handle = context.data['frameStartHandle'] - frame_end_handle = context.data['frameEndHandle'] - else: - handle_start = 0 - handle_end = 0 - frame_start = frame_start_render - frame_end = frame_end_render - frame_start_handle = frame_start_render - frame_end_handle = frame_end_render - - data = { - "handleStart": handle_start, - "handleEnd": handle_end, - "frameStart": frame_start, - "frameEnd": frame_end, - "frameStartHandle": frame_start_handle, - "frameEndHandle": frame_end_handle, - "byFrameStep": int(render_instance.frameStep), - - "author": context.data["user"], - # Add source to allow tracing back to the scene from - # which was submitted originally - "expectedFiles": exp_files, - } - if self.sync_workfile_version: - data["version"] = context.data["version"] - - # add additional data - data = self.add_additional_data(data) - render_instance_dict = attr.asdict(render_instance) - - instance = context.create_instance(render_instance.name) - instance.data["label"] = render_instance.label - instance.data.update(render_instance_dict) - instance.data.update(data) - - self.post_collecting_action() - - @abstractmethod - def get_instances(self, context): - """Get all renderable instances and their data. - - Args: - context (pyblish.api.Context): Context object. - - Returns: - list of :class:`RenderInstance`: All collected renderable instances - (like render layers, write nodes, etc.) - - """ - pass - - @abstractmethod - def get_expected_files(self, render_instance): - """Get list of expected files. - - Returns: - list: expected files. This can be either simple list of files with - their paths, or list of dictionaries, where key is name of AOV - for example and value is list of files for that AOV. - - Example:: - - ['/path/to/file.001.exr', '/path/to/file.002.exr'] - - or as dictionary: - - [ - { - "beauty": ['/path/to/beauty.001.exr', ...], - "mask": ['/path/to/mask.001.exr'] - } - ] - - """ - pass - - def add_additional_data(self, data): - """Add additional data to collected instance. - - This can be overridden by host implementation to add custom - additional data. - - """ - return data - - def post_collecting_action(self): - """Execute some code after collection is done. - - This is useful for example for restoring current render layer. - - """ - pass +__all__ = ( + "AbstractCollectRender", + "RenderInstance" +) diff --git a/openpype/lib/abstract_expected_files.py b/openpype/lib/abstract_expected_files.py index f9f3c17ef5..bb433a8b11 100644 --- a/openpype/lib/abstract_expected_files.py +++ b/openpype/lib/abstract_expected_files.py @@ -1,53 +1,32 @@ # -*- coding: utf-8 -*- -"""Abstract ExpectedFile class definition.""" -from abc import ABCMeta, abstractmethod -import six +"""Content was moved to 'openpype.pipeline.farm.abstract_expected_files'. + +Please change your imports as soon as possible. + +File will be probably removed in OpenPype 3.14.* +""" + +import warnings +from openpype.pipeline.publish import ExpectedFiles -@six.add_metaclass(ABCMeta) -class ExpectedFiles: - """Class grouping functionality for all supported renderers. - - Attributes: - multipart (bool): Flag if multipart exrs are used. - - """ - - multipart = False - - @abstractmethod - def get(self, render_instance): - """Get expected files for given renderer and render layer. - - This method should return dictionary of all files we are expecting - to be rendered from the host. Usually `render_instance` corresponds - to *render layer*. Result can be either flat list with the file - paths or it can be list of dictionaries. Each key corresponds to - for example AOV name or channel, etc. - - Example:: - - ['/path/to/file.001.exr', '/path/to/file.002.exr'] - - or as dictionary: - - [ - { - "beauty": ['/path/to/beauty.001.exr', ...], - "mask": ['/path/to/mask.001.exr'] - } - ] +class ExpectedFilesDeprecated(DeprecationWarning): + pass - Args: - render_instance (:class:`RenderInstance`): Data passed from - collector to determine files. This should be instance of - :class:`abstract_collect_render.RenderInstance` +warnings.simplefilter("always", ExpectedFilesDeprecated) +warnings.warn( + ( + "Content of 'abstract_expected_files' was moved." + "\nUsing deprecated source of 'abstract_expected_files'. Content was" + " move to 'openpype.pipeline.farm.abstract_expected_files'." + " Please change your imports as soon as possible." + ), + category=ExpectedFilesDeprecated, + stacklevel=4 +) - Returns: - list: Full paths to expected rendered files. - list of dict: Path to expected rendered files categorized by - AOVs, etc. - """ - raise NotImplementedError() +__all__ = ( + "ExpectedFiles", +) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 22902d79ea..3f54273a56 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -15,7 +15,7 @@ import attr import requests import pyblish.api -from openpype.lib.abstract_metaplugins import AbstractMetaInstancePlugin +from openpype.pipeline.publish import AbstractMetaInstancePlugin def requests_post(*args, **kwargs): diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index 03d730f37a..aa7fe0bdbf 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -16,6 +16,12 @@ from .lib import ( load_help_content_from_filepath, ) +from .abstract_expected_files import ExpectedFiles +from .abstract_collect_render import ( + RenderInstance, + AbstractCollectRender, +) + __all__ = ( "AbstractMetaInstancePlugin", @@ -31,4 +37,9 @@ __all__ = ( "publish_plugins_discover", "load_help_content_from_plugin", "load_help_content_from_filepath", + + "ExpectedFiles", + + "RenderInstance", + "AbstractCollectRender", ) diff --git a/openpype/pipeline/publish/abstract_collect_render.py b/openpype/pipeline/publish/abstract_collect_render.py new file mode 100644 index 0000000000..2e537227c3 --- /dev/null +++ b/openpype/pipeline/publish/abstract_collect_render.py @@ -0,0 +1,268 @@ +# -*- coding: utf-8 -*- +"""Collect render template. + +TODO: use @dataclass when times come. + +""" +from abc import abstractmethod + +import attr +import six + +import pyblish.api + +from openpype.pipeline import legacy_io +from .publish_plugins import AbstractMetaContextPlugin + + +@attr.s +class RenderInstance(object): + """Data collected by collectors. + + This data class later on passed to collected instances. + Those attributes are required later on. + + """ + + # metadata + version = attr.ib() # instance version + time = attr.ib() # time of instance creation (get_formatted_current_time) + source = attr.ib() # path to source scene file + label = attr.ib() # label to show in GUI + subset = attr.ib() # subset name + task = attr.ib() # task name + asset = attr.ib() # asset name (AVALON_ASSET) + attachTo = attr.ib() # subset name to attach render to + setMembers = attr.ib() # list of nodes/members producing render output + publish = attr.ib() # bool, True to publish instance + name = attr.ib() # instance name + + # format settings + resolutionWidth = attr.ib() # resolution width (1920) + resolutionHeight = attr.ib() # resolution height (1080) + pixelAspect = attr.ib() # pixel aspect (1.0) + + # time settings + frameStart = attr.ib() # start frame + frameEnd = attr.ib() # start end + frameStep = attr.ib() # frame step + + handleStart = attr.ib(default=None) # start frame + handleEnd = attr.ib(default=None) # start frame + + # for software (like Harmony) where frame range cannot be set by DB + # handles need to be propagated if exist + ignoreFrameHandleCheck = attr.ib(default=False) + + # -------------------- + # With default values + # metadata + renderer = attr.ib(default="") # renderer - can be used in Deadline + review = attr.ib(default=False) # generate review from instance (bool) + priority = attr.ib(default=50) # job priority on farm + + family = attr.ib(default="renderlayer") + families = attr.ib(default=["renderlayer"]) # list of families + + # format settings + multipartExr = attr.ib(default=False) # flag for multipart exrs + convertToScanline = attr.ib(default=False) # flag for exr conversion + + tileRendering = attr.ib(default=False) # bool: treat render as tiles + tilesX = attr.ib(default=0) # number of tiles in X + tilesY = attr.ib(default=0) # number of tiles in Y + + # submit_publish_job + toBeRenderedOn = attr.ib(default=None) + deadlineSubmissionJob = attr.ib(default=None) + anatomyData = attr.ib(default=None) + outputDir = attr.ib(default=None) + context = attr.ib(default=None) + + @frameStart.validator + def check_frame_start(self, _, value): + """Validate if frame start is not larger then end.""" + if value > self.frameEnd: + raise ValueError("frameStart must be smaller " + "or equal then frameEnd") + + @frameEnd.validator + def check_frame_end(self, _, value): + """Validate if frame end is not less then start.""" + if value < self.frameStart: + raise ValueError("frameEnd must be smaller " + "or equal then frameStart") + + @tilesX.validator + def check_tiles_x(self, _, value): + """Validate if tile x isn't less then 1.""" + if not self.tileRendering: + return + if value < 1: + raise ValueError("tile X size cannot be less then 1") + + if value == 1 and self.tilesY == 1: + raise ValueError("both tiles X a Y sizes are set to 1") + + @tilesY.validator + def check_tiles_y(self, _, value): + """Validate if tile y isn't less then 1.""" + if not self.tileRendering: + return + if value < 1: + raise ValueError("tile Y size cannot be less then 1") + + if value == 1 and self.tilesX == 1: + raise ValueError("both tiles X a Y sizes are set to 1") + + +@six.add_metaclass(AbstractMetaContextPlugin) +class AbstractCollectRender(pyblish.api.ContextPlugin): + """Gather all publishable render layers from renderSetup.""" + + order = pyblish.api.CollectorOrder + 0.01 + label = "Collect Render" + sync_workfile_version = False + + def __init__(self, *args, **kwargs): + """Constructor.""" + super(AbstractCollectRender, self).__init__(*args, **kwargs) + self._file_path = None + self._asset = legacy_io.Session["AVALON_ASSET"] + self._context = None + + def process(self, context): + """Entry point to collector.""" + self._context = context + for instance in context: + # make sure workfile instance publishing is enabled + try: + if "workfile" in instance.data["families"]: + instance.data["publish"] = True + # TODO merge renderFarm and render.farm + if ("renderFarm" in instance.data["families"] or + "render.farm" in instance.data["families"]): + instance.data["remove"] = True + except KeyError: + # be tolerant if 'families' is missing. + pass + + self._file_path = context.data["currentFile"].replace("\\", "/") + + render_instances = self.get_instances(context) + for render_instance in render_instances: + exp_files = self.get_expected_files(render_instance) + assert exp_files, "no file names were generated, this is bug" + + # if we want to attach render to subset, check if we have AOV's + # in expectedFiles. If so, raise error as we cannot attach AOV + # (considered to be subset on its own) to another subset + if render_instance.attachTo: + assert isinstance(exp_files, list), ( + "attaching multiple AOVs or renderable cameras to " + "subset is not supported" + ) + + frame_start_render = int(render_instance.frameStart) + frame_end_render = int(render_instance.frameEnd) + if (render_instance.ignoreFrameHandleCheck or + int(context.data['frameStartHandle']) == frame_start_render + and int(context.data['frameEndHandle']) == frame_end_render): # noqa: W503, E501 + + handle_start = context.data['handleStart'] + handle_end = context.data['handleEnd'] + frame_start = context.data['frameStart'] + frame_end = context.data['frameEnd'] + frame_start_handle = context.data['frameStartHandle'] + frame_end_handle = context.data['frameEndHandle'] + else: + handle_start = 0 + handle_end = 0 + frame_start = frame_start_render + frame_end = frame_end_render + frame_start_handle = frame_start_render + frame_end_handle = frame_end_render + + data = { + "handleStart": handle_start, + "handleEnd": handle_end, + "frameStart": frame_start, + "frameEnd": frame_end, + "frameStartHandle": frame_start_handle, + "frameEndHandle": frame_end_handle, + "byFrameStep": int(render_instance.frameStep), + + "author": context.data["user"], + # Add source to allow tracing back to the scene from + # which was submitted originally + "expectedFiles": exp_files, + } + if self.sync_workfile_version: + data["version"] = context.data["version"] + + # add additional data + data = self.add_additional_data(data) + render_instance_dict = attr.asdict(render_instance) + + instance = context.create_instance(render_instance.name) + instance.data["label"] = render_instance.label + instance.data.update(render_instance_dict) + instance.data.update(data) + + self.post_collecting_action() + + @abstractmethod + def get_instances(self, context): + """Get all renderable instances and their data. + + Args: + context (pyblish.api.Context): Context object. + + Returns: + list of :class:`RenderInstance`: All collected renderable instances + (like render layers, write nodes, etc.) + + """ + pass + + @abstractmethod + def get_expected_files(self, render_instance): + """Get list of expected files. + + Returns: + list: expected files. This can be either simple list of files with + their paths, or list of dictionaries, where key is name of AOV + for example and value is list of files for that AOV. + + Example:: + + ['/path/to/file.001.exr', '/path/to/file.002.exr'] + + or as dictionary: + + [ + { + "beauty": ['/path/to/beauty.001.exr', ...], + "mask": ['/path/to/mask.001.exr'] + } + ] + + """ + pass + + def add_additional_data(self, data): + """Add additional data to collected instance. + + This can be overridden by host implementation to add custom + additional data. + + """ + return data + + def post_collecting_action(self): + """Execute some code after collection is done. + + This is useful for example for restoring current render layer. + + """ + pass diff --git a/openpype/pipeline/publish/abstract_expected_files.py b/openpype/pipeline/publish/abstract_expected_files.py new file mode 100644 index 0000000000..f9f3c17ef5 --- /dev/null +++ b/openpype/pipeline/publish/abstract_expected_files.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +"""Abstract ExpectedFile class definition.""" +from abc import ABCMeta, abstractmethod +import six + + +@six.add_metaclass(ABCMeta) +class ExpectedFiles: + """Class grouping functionality for all supported renderers. + + Attributes: + multipart (bool): Flag if multipart exrs are used. + + """ + + multipart = False + + @abstractmethod + def get(self, render_instance): + """Get expected files for given renderer and render layer. + + This method should return dictionary of all files we are expecting + to be rendered from the host. Usually `render_instance` corresponds + to *render layer*. Result can be either flat list with the file + paths or it can be list of dictionaries. Each key corresponds to + for example AOV name or channel, etc. + + Example:: + + ['/path/to/file.001.exr', '/path/to/file.002.exr'] + + or as dictionary: + + [ + { + "beauty": ['/path/to/beauty.001.exr', ...], + "mask": ['/path/to/mask.001.exr'] + } + ] + + + Args: + render_instance (:class:`RenderInstance`): Data passed from + collector to determine files. This should be instance of + :class:`abstract_collect_render.RenderInstance` + + Returns: + list: Full paths to expected rendered files. + list of dict: Path to expected rendered files categorized by + AOVs, etc. + + """ + raise NotImplementedError() From a48a7297e2510a6b4f5c872b95ad16c5fa5a93a7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 10:49:13 +0200 Subject: [PATCH 0921/1227] fixed imports in docstrings and warning messages --- openpype/lib/abstract_collect_render.py | 4 ++-- openpype/lib/abstract_expected_files.py | 4 ++-- openpype/lib/abstract_metaplugins.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/lib/abstract_collect_render.py b/openpype/lib/abstract_collect_render.py index 2cc1c23822..e4ff87aa0f 100644 --- a/openpype/lib/abstract_collect_render.py +++ b/openpype/lib/abstract_collect_render.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Content was moved to 'openpype.pipeline.farm.abstract_collect_render'. +"""Content was moved to 'openpype.pipeline.publish.abstract_collect_render'. Please change your imports as soon as possible. @@ -19,7 +19,7 @@ warnings.warn( ( "Content of 'abstract_collect_render' was moved." "\nUsing deprecated source of 'abstract_collect_render'. Content was" - " move to 'openpype.pipeline.farm.abstract_collect_render'." + " move to 'openpype.pipeline.publish.abstract_collect_render'." " Please change your imports as soon as possible." ), category=CollectRenderDeprecated, diff --git a/openpype/lib/abstract_expected_files.py b/openpype/lib/abstract_expected_files.py index bb433a8b11..f24d844fe5 100644 --- a/openpype/lib/abstract_expected_files.py +++ b/openpype/lib/abstract_expected_files.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Content was moved to 'openpype.pipeline.farm.abstract_expected_files'. +"""Content was moved to 'openpype.pipeline.publish.abstract_expected_files'. Please change your imports as soon as possible. @@ -19,7 +19,7 @@ warnings.warn( ( "Content of 'abstract_expected_files' was moved." "\nUsing deprecated source of 'abstract_expected_files'. Content was" - " move to 'openpype.pipeline.farm.abstract_expected_files'." + " move to 'openpype.pipeline.publish.abstract_expected_files'." " Please change your imports as soon as possible." ), category=ExpectedFilesDeprecated, diff --git a/openpype/lib/abstract_metaplugins.py b/openpype/lib/abstract_metaplugins.py index 6199a96ef1..346b5d86b3 100644 --- a/openpype/lib/abstract_metaplugins.py +++ b/openpype/lib/abstract_metaplugins.py @@ -1,4 +1,4 @@ -"""Content was moved to 'openpype.pipeline.farm.abstract_metaplugins'. +"""Content was moved to 'openpype.pipeline.publish.publish_plugins'. Please change your imports as soon as possible. @@ -21,7 +21,7 @@ warnings.warn( ( "Content of 'abstract_metaplugins' was moved." "\nUsing deprecated source of 'abstract_metaplugins'. Content was" - " moved to 'openpype.pipeline.farm.abstract_metaplugins'." + " moved to 'openpype.pipeline.publish.publish_plugins'." " Please change your imports as soon as possible." ), category=MetaPluginsDeprecated, From 3b90f7e507afa4f87304db67fe0b77e647283c22 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 11:07:10 +0200 Subject: [PATCH 0922/1227] log viewer escape characters in log message --- openpype/modules/log_viewer/tray/widgets.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/openpype/modules/log_viewer/tray/widgets.py b/openpype/modules/log_viewer/tray/widgets.py index ed08e62109..c7ac64ab70 100644 --- a/openpype/modules/log_viewer/tray/widgets.py +++ b/openpype/modules/log_viewer/tray/widgets.py @@ -1,3 +1,4 @@ +import html from Qt import QtCore, QtWidgets import qtawesome from .models import LogModel, LogsFilterProxy @@ -286,7 +287,7 @@ class OutputWidget(QtWidgets.QWidget): if level == "debug": line_f = ( " -" - " {{ {loggerName} }}: [" + " {{ {logger_name} }}: [" " {message}" " ]" ) @@ -299,7 +300,7 @@ class OutputWidget(QtWidgets.QWidget): elif level == "warning": line_f = ( "*** WRN:" - " >>> {{ {loggerName} }}: [" + " >>> {{ {logger_name} }}: [" " {message}" " ]" ) @@ -307,16 +308,25 @@ class OutputWidget(QtWidgets.QWidget): line_f = ( "!!! ERR:" " {timestamp}" - " >>> {{ {loggerName} }}: [" + " >>> {{ {logger_name} }}: [" " {message}" " ]" ) + logger_name = log["loggerName"] + timestamp = "" + if not show_timecode: + timestamp = log["timestamp"] + message = log["message"] exc = log.get("exception") if exc: - log["message"] = exc["message"] + message = exc["message"] - line = line_f.format(**log) + line = line_f.format( + message=html.escape(message), + logger_name=logger_name, + timestamp=timestamp + ) if show_timecode: timestamp = log["timestamp"] From aa741a1148c31a8fc76c6fca90ee885b88e7db33 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 11:34:34 +0200 Subject: [PATCH 0923/1227] use query functions in load logic --- openpype/pipeline/load/utils.py | 149 +++++++++++++------------------- 1 file changed, 62 insertions(+), 87 deletions(-) diff --git a/openpype/pipeline/load/utils.py b/openpype/pipeline/load/utils.py index 99e5d11f82..b6e5c38fbe 100644 --- a/openpype/pipeline/load/utils.py +++ b/openpype/pipeline/load/utils.py @@ -6,9 +6,20 @@ import logging import inspect import numbers -import six -from bson.objectid import ObjectId - +from openpype.client import ( + get_project, + get_assets, + get_subsets, + get_versions, + get_version_by_id, + get_last_version_by_subset_id, + get_hero_version_by_subset_id, + get_version_by_name, + get_representations, + get_representation_by_id, + get_representation_by_name, + get_representation_parents +) from openpype.lib import Anatomy from openpype.pipeline import ( schema, @@ -52,13 +63,10 @@ def get_repres_contexts(representation_ids, dbcon=None): Returns: dict: The full representation context by representation id. - keys are repre_id, value is dictionary with full: - asset_doc - version_doc - subset_doc - repre_doc - + keys are repre_id, value is dictionary with full documents of + asset, subset, version and representation. """ + if not dbcon: dbcon = legacy_io @@ -66,26 +74,18 @@ def get_repres_contexts(representation_ids, dbcon=None): if not representation_ids: return contexts - _representation_ids = [] - for repre_id in representation_ids: - if isinstance(repre_id, six.string_types): - repre_id = ObjectId(repre_id) - _representation_ids.append(repre_id) + project_name = dbcon.active_project() + repre_docs = get_representations(project_name, representation_ids) - repre_docs = dbcon.find({ - "type": "representation", - "_id": {"$in": _representation_ids} - }) repre_docs_by_id = {} version_ids = set() for repre_doc in repre_docs: version_ids.add(repre_doc["parent"]) repre_docs_by_id[repre_doc["_id"]] = repre_doc - version_docs = dbcon.find({ - "type": {"$in": ["version", "hero_version"]}, - "_id": {"$in": list(version_ids)} - }) + version_docs = get_versions( + project_name, verison_ids=version_ids, hero=True + ) version_docs_by_id = {} hero_version_docs = [] @@ -99,10 +99,7 @@ def get_repres_contexts(representation_ids, dbcon=None): subset_ids.add(version_doc["parent"]) if versions_for_hero: - _version_docs = dbcon.find({ - "type": "version", - "_id": {"$in": list(versions_for_hero)} - }) + _version_docs = get_versions(project_name, versions_for_hero) _version_data_by_id = { version_doc["_id"]: version_doc["data"] for version_doc in _version_docs @@ -114,26 +111,20 @@ def get_repres_contexts(representation_ids, dbcon=None): version_data = copy.deepcopy(_version_data_by_id[version_id]) version_docs_by_id[hero_version_id]["data"] = version_data - subset_docs = dbcon.find({ - "type": "subset", - "_id": {"$in": list(subset_ids)} - }) + subset_docs = get_subsets(project_name, subset_ids=subset_ids) subset_docs_by_id = {} asset_ids = set() for subset_doc in subset_docs: subset_docs_by_id[subset_doc["_id"]] = subset_doc asset_ids.add(subset_doc["parent"]) - asset_docs = dbcon.find({ - "type": "asset", - "_id": {"$in": list(asset_ids)} - }) + asset_docs = get_assets(project_name, asset_ids) asset_docs_by_id = { asset_doc["_id"]: asset_doc for asset_doc in asset_docs } - project_doc = dbcon.find_one({"type": "project"}) + project_doc = get_project(project_name) for repre_id, repre_doc in repre_docs_by_id.items(): version_doc = version_docs_by_id[repre_doc["parent"]] @@ -173,32 +164,21 @@ def get_subset_contexts(subset_ids, dbcon=None): if not subset_ids: return contexts - _subset_ids = set() - for subset_id in subset_ids: - if isinstance(subset_id, six.string_types): - subset_id = ObjectId(subset_id) - _subset_ids.add(subset_id) - - subset_docs = dbcon.find({ - "type": "subset", - "_id": {"$in": list(_subset_ids)} - }) + project_name = legacy_io.active_project() + subset_docs = get_subsets(project_name, subset_ids) subset_docs_by_id = {} asset_ids = set() for subset_doc in subset_docs: subset_docs_by_id[subset_doc["_id"]] = subset_doc asset_ids.add(subset_doc["parent"]) - asset_docs = dbcon.find({ - "type": "asset", - "_id": {"$in": list(asset_ids)} - }) + asset_docs = get_assets(project_name, asset_ids) asset_docs_by_id = { asset_doc["_id"]: asset_doc for asset_doc in asset_docs } - project_doc = dbcon.find_one({"type": "project"}) + project_doc = get_project(project_name) for subset_id, subset_doc in subset_docs_by_id.items(): asset_doc = asset_docs_by_id[subset_doc["parent"]] @@ -224,16 +204,17 @@ def get_representation_context(representation): Returns: dict: The full representation context. - """ assert representation is not None, "This is a bug" - if isinstance(representation, (six.string_types, ObjectId)): - representation = legacy_io.find_one( - {"_id": ObjectId(str(representation))}) + if not isinstance(representation, dict): + representation = get_representation_by_id(representation) - version, subset, asset, project = legacy_io.parenthood(representation) + project_name = legacy_io.active_project() + version, subset, asset, project = get_representation_parents( + project_name, representation + ) assert all([representation, version, subset, asset, project]), ( "This is a bug" @@ -405,42 +386,36 @@ def update_container(container, version=-1): """Update a container""" # Compute the different version from 'representation' - current_representation = legacy_io.find_one({ - "_id": ObjectId(container["representation"]) - }) + project_name = legacy_io.active_project() + current_representation = get_representation_by_id( + project_name, container["representation"] + ) assert current_representation is not None, "This is a bug" - current_version, subset, asset, project = legacy_io.parenthood( - current_representation) - + current_version = get_version_by_id( + project_name, current_representation["_id"], fields=["parent"] + ) if version == -1: - new_version = legacy_io.find_one({ - "type": "version", - "parent": subset["_id"] - }, sort=[("name", -1)]) + new_version = get_last_version_by_subset_id( + project_name, current_version["parent"], fields=["_id"] + ) + + elif isinstance(version, HeroVersionType): + new_version = get_hero_version_by_subset_id( + project_name, current_version["parent"], fields=["_id"] + ) + else: - if isinstance(version, HeroVersionType): - version_query = { - "parent": subset["_id"], - "type": "hero_version" - } - else: - version_query = { - "parent": subset["_id"], - "type": "version", - "name": version - } - new_version = legacy_io.find_one(version_query) + new_version = get_version_by_name( + project_name, version, current_version["parent"], fields=["_id"] + ) assert new_version is not None, "This is a bug" - new_representation = legacy_io.find_one({ - "type": "representation", - "parent": new_version["_id"], - "name": current_representation["name"] - }) - + new_representation = get_representation_by_name( + project_name, current_representation["name"], new_version["_id"] + ) assert new_representation is not None, "Representation wasn't found" path = get_representation_path(new_representation) @@ -482,10 +457,10 @@ def switch_container(container, representation, loader_plugin=None): )) # Get the new representation to switch to - new_representation = legacy_io.find_one({ - "type": "representation", - "_id": representation["_id"], - }) + project_name = legacy_io.active_project() + new_representation = get_representation_by_id( + project_name, representation["_id"] + ) new_context = get_representation_context(new_representation) if not is_compatible_loader(loader_plugin, new_context): From 2527ac03728d8c85abd63248a3db539f8451b0be Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 11:47:32 +0200 Subject: [PATCH 0924/1227] make sure _get_project_connection has project name passed --- openpype/client/entities.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 28cd994254..a58926499f 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -15,12 +15,15 @@ from bson.objectid import ObjectId from openpype.lib.mongo import OpenPypeMongoConnection -def _get_project_connection(project_name=None): +def _get_project_database(): db_name = os.environ.get("AVALON_DB") or "avalon" - mongodb = OpenPypeMongoConnection.get_mongo_client()[db_name] - if project_name: - return mongodb[project_name] - return mongodb + return OpenPypeMongoConnection.get_mongo_client()[db_name] + + +def _get_project_connection(project_name): + if not project_name: + raise ValueError("Invalid project name {}".format(str(project_name))) + return _get_project_database()[project_name] def _prepare_fields(fields, required_fields=None): @@ -55,7 +58,7 @@ def _convert_ids(in_ids): def get_projects(active=True, inactive=False, fields=None): - mongodb = _get_project_connection() + mongodb = _get_project_database() for project_name in mongodb.collection_names(): if project_name in ("system.indexes",): continue From 63f5ea427a2bd41d38d352bb6f5b132a39c348bc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 11:50:40 +0200 Subject: [PATCH 0925/1227] make sure output of get_representations_parents always returns values for passed repre ids --- openpype/client/entities.py | 5 +++-- openpype/hosts/maya/api/setdress.py | 7 ++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index a58926499f..8b0c259817 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1012,8 +1012,10 @@ def get_representations_parents(project_name, representations): versions_by_subset_id = collections.defaultdict(list) subsets_by_subset_id = {} subsets_by_asset_id = collections.defaultdict(list) + output = {} for representation in representations: repre_id = representation["_id"] + output[repre_id] = (None, None, None, None) version_id = representation["parent"] repres_by_version_id[version_id].append(representation) @@ -1043,7 +1045,6 @@ def get_representations_parents(project_name, representations): project = get_project(project_name) - output = {} for version_id, representations in repres_by_version_id.items(): asset = None subset = None @@ -1083,7 +1084,7 @@ def get_representation_parents(project_name, representation): parents_by_repre_id = get_representations_parents( project_name, [representation] ) - return parents_by_repre_id.get(repre_id) + return parents_by_repre_id[repre_id] def get_thumbnail_id_from_source(project_name, src_type, src_id): diff --git a/openpype/hosts/maya/api/setdress.py b/openpype/hosts/maya/api/setdress.py index bea8f154b1..159bfe9eb3 100644 --- a/openpype/hosts/maya/api/setdress.py +++ b/openpype/hosts/maya/api/setdress.py @@ -296,12 +296,9 @@ def update_package_version(container, version): assert current_representation is not None, "This is a bug" - repre_parents = get_representation_parents( - project_name, current_representation + version_doc, subset_doc, asset_doc, project_doc = ( + get_representation_parents(project_name, current_representation) ) - version_doc = subset_doc = asset_doc = project_doc = None - if repre_parents: - version_doc, subset_doc, asset_doc, project_doc = repre_parents if version == -1: new_version = get_last_version_by_subset_id( From dd1e1960da94ab22b28a68f647a685064a0da479 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 11:54:23 +0200 Subject: [PATCH 0926/1227] fix few issues --- openpype/pipeline/load/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/load/utils.py b/openpype/pipeline/load/utils.py index b6e5c38fbe..3d2f798769 100644 --- a/openpype/pipeline/load/utils.py +++ b/openpype/pipeline/load/utils.py @@ -84,7 +84,7 @@ def get_repres_contexts(representation_ids, dbcon=None): repre_docs_by_id[repre_doc["_id"]] = repre_doc version_docs = get_versions( - project_name, verison_ids=version_ids, hero=True + project_name, version_ids, hero=True ) version_docs_by_id = {} @@ -111,7 +111,7 @@ def get_repres_contexts(representation_ids, dbcon=None): version_data = copy.deepcopy(_version_data_by_id[version_id]) version_docs_by_id[hero_version_id]["data"] = version_data - subset_docs = get_subsets(project_name, subset_ids=subset_ids) + subset_docs = get_subsets(project_name, subset_ids) subset_docs_by_id = {} asset_ids = set() for subset_doc in subset_docs: @@ -164,7 +164,7 @@ def get_subset_contexts(subset_ids, dbcon=None): if not subset_ids: return contexts - project_name = legacy_io.active_project() + project_name = dbcon.active_project() subset_docs = get_subsets(project_name, subset_ids) subset_docs_by_id = {} asset_ids = set() From 81e977129022b2db7ca1f5593756fb0add035cfa Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 30 Jun 2022 11:55:25 +0200 Subject: [PATCH 0927/1227] :construction_worker: clean old files and add version subfolder --- inno_setup.iss | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/inno_setup.iss b/inno_setup.iss index ead9907955..fa050ef1d6 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -18,7 +18,8 @@ AppPublisher=Orbi Tools s.r.o AppPublisherURL=http://pype.club AppSupportURL=http://pype.club AppUpdatesURL=http://pype.club -DefaultDirName={autopf}\{#MyAppName} +DefaultDirName={autopf}\{#MyAppName}\{#AppVer} +UsePreviousAppDir=no DisableProgramGroupPage=yes OutputBaseFilename={#MyAppName}-{#AppVer}-install AllowCancelDuringInstall=yes @@ -27,7 +28,7 @@ AllowCancelDuringInstall=yes PrivilegesRequiredOverridesAllowed=dialog SetupIconFile=igniter\openpype.ico OutputDir=build\ -Compression=lzma +Compression=lzma2 SolidCompression=yes WizardStyle=modern @@ -37,6 +38,11 @@ Name: "english"; MessagesFile: "compiler:Default.isl" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked +[InstallDelete] +; clean everything in previous installation folder +Type: filesandordirs; Name: "{app}\*" + + [Files] Source: "build\{#build}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs ; NOTE: Don't use "Flags: ignoreversion" on any shared system files From c473f6b777022692fdae72cd927f01afbd51ba2e Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 30 Jun 2022 14:17:57 +0200 Subject: [PATCH 0928/1227] :bug: fix unicode encoding hash in tile rendering --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 9964e3c646..3707c5709f 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -710,7 +710,9 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): new_payload["JobInfo"].update(tiles_data["JobInfo"]) new_payload["PluginInfo"].update(tiles_data["PluginInfo"]) - job_hash = hashlib.sha256("{}_{}".format(file_index, file)) + self.log.info("hashing {} - {}".format(file_index, file)) + job_hash = hashlib.sha256( + ("{}_{}".format(file_index, file)).encode("utf-8")) frame_jobs[frame] = job_hash.hexdigest() new_payload["JobInfo"]["ExtraInfo0"] = job_hash.hexdigest() new_payload["JobInfo"]["ExtraInfo1"] = file From 4e7358f73b007d8a53ae55c5a3b255a45b299876 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 14:47:08 +0200 Subject: [PATCH 0929/1227] removed unused function 'get_hierarchy' --- openpype/api.py | 2 - openpype/lib/__init__.py | 2 - openpype/lib/avalon_context.py | 51 ------------------- .../tests/test_lib_restructuralization.py | 1 - 4 files changed, 56 deletions(-) diff --git a/openpype/api.py b/openpype/api.py index 9ce745b653..fac2ae572b 100644 --- a/openpype/api.py +++ b/openpype/api.py @@ -15,7 +15,6 @@ from .lib import ( run_subprocess, version_up, get_asset, - get_hierarchy, get_workdir_data, get_version_from_path, get_last_version_from_path, @@ -101,7 +100,6 @@ __all__ = [ # get contextual data "version_up", "get_asset", - "get_hierarchy", "get_workdir_data", "get_version_from_path", "get_last_version_from_path", diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 8d4e733b7d..fb52a9aca7 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -120,7 +120,6 @@ from .avalon_context import ( is_latest, any_outdated, get_asset, - get_hierarchy, get_linked_assets, get_latest_version, get_system_general_anatomy_data, @@ -292,7 +291,6 @@ __all__ = [ "is_latest", "any_outdated", "get_asset", - "get_hierarchy", "get_linked_assets", "get_latest_version", "get_system_general_anatomy_data", diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index a03f066300..ad34e24644 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -213,57 +213,6 @@ def get_asset(asset_name=None): return asset_document -@with_pipeline_io -def get_hierarchy(asset_name=None): - """ - Obtain asset hierarchy path string from mongo db - - Args: - asset_name (str) - - Returns: - (string): asset hierarchy path - - """ - if not asset_name: - asset_name = legacy_io.Session.get( - "AVALON_ASSET", - os.environ["AVALON_ASSET"] - ) - - asset_entity = legacy_io.find_one({ - "type": 'asset', - "name": asset_name - }) - - not_set = "PARENTS_NOT_SET" - entity_parents = asset_entity.get("data", {}).get("parents", not_set) - - # If entity already have parents then just return joined - if entity_parents != not_set: - return "/".join(entity_parents) - - # Else query parents through visualParents and store result to entity - hierarchy_items = [] - entity = asset_entity - while True: - parent_id = entity.get("data", {}).get("visualParent") - if not parent_id: - break - entity = legacy_io.find_one({"_id": parent_id}) - hierarchy_items.append(entity["name"]) - - # Add parents to entity data for next query - entity_data = asset_entity.get("data", {}) - entity_data["parents"] = hierarchy_items - legacy_io.update_many( - {"_id": asset_entity["_id"]}, - {"$set": {"data": entity_data}} - ) - - return "/".join(hierarchy_items) - - def get_system_general_anatomy_data(): system_settings = get_system_settings() studio_name = system_settings["general"]["studio_name"] diff --git a/openpype/tests/test_lib_restructuralization.py b/openpype/tests/test_lib_restructuralization.py index 94080e550d..ccccc76a08 100644 --- a/openpype/tests/test_lib_restructuralization.py +++ b/openpype/tests/test_lib_restructuralization.py @@ -21,7 +21,6 @@ def test_backward_compatibility(printer): from openpype.lib import is_latest from openpype.lib import any_outdated from openpype.lib import get_asset - from openpype.lib import get_hierarchy from openpype.lib import get_linked_assets from openpype.lib import get_latest_version from openpype.lib import get_ffprobe_streams From cda8d670c003a85f324fb3e0ce160b1745c1703e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 30 Jun 2022 15:08:19 +0200 Subject: [PATCH 0930/1227] Added settings for Harmony ImageSequenceLoader Currently to fix issue with reference representations with wrong names (png_mp4 instead of png), but it could be helpful regardless. --- .../defaults/project_settings/harmony.json | 16 +++++++++++ .../schema_project_harmony.json | 28 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/openpype/settings/defaults/project_settings/harmony.json b/openpype/settings/defaults/project_settings/harmony.json index 1508b02e1b..d843bc8e70 100644 --- a/openpype/settings/defaults/project_settings/harmony.json +++ b/openpype/settings/defaults/project_settings/harmony.json @@ -1,4 +1,20 @@ { + "load": { + "ImageSequenceLoader": { + "family": [ + "shot", + "render", + "image", + "plate", + "reference" + ], + "representations": [ + "jpeg", + "png", + "jpg" + ] + } + }, "publish": { "CollectPalettes": { "allowed_tasks": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json index c049ce3084..311f742f81 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json @@ -5,6 +5,34 @@ "label": "Harmony", "is_file": true, "children": [ + { + "type": "dict", + "collapsible": true, + "key": "load", + "label": "Loader plugins", + "children": [ + { + "type": "dict", + "collapsible": true, + "key": "ImageSequenceLoader", + "label": "Load Image Sequence", + "children": [ + { + "type": "list", + "key": "family", + "label": "Families", + "object_type": "text" + }, + { + "type": "list", + "key": "representations", + "label": "Representations", + "object_type": "text" + } + ] + } + ] + }, { "type": "dict", "collapsible": true, From 752615d0d46992b92270790901cff2acadea701c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Jun 2022 15:14:19 +0200 Subject: [PATCH 0931/1227] general: add prerender to thumbnail exporter --- openpype/plugins/publish/extract_jpeg_exr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index a467728e77..42c4cbe062 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -19,7 +19,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): label = "Extract Thumbnail" order = pyblish.api.ExtractorOrder families = [ - "imagesequence", "render", "render2d", + "imagesequence", "render", "render2d", "prerender", "source", "plate", "take" ] hosts = ["shell", "fusion", "resolve"] From f65bab000f42af9d4d11d0e600ead9dabaa3e5ba Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Jun 2022 15:14:54 +0200 Subject: [PATCH 0932/1227] Nuke: add prerender.farm family to extract review mov --- .../hosts/nuke/plugins/publish/extract_review_data_mov.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py index 5ea7c352b9..fc16e189fb 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py @@ -104,7 +104,10 @@ class ExtractReviewDataMov(openpype.api.Extractor): self, instance, o_name, o_data["extension"], multiple_presets) - if "render.farm" in families: + if ( + "render.farm" in families or + "prerender.farm" in families + ): if "review" in instance.data["families"]: instance.data["families"].remove("review") From e147cd4132e983e9d70c2be02356a75fd5f70f11 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 16:04:25 +0200 Subject: [PATCH 0933/1227] added 'get_workfile_info' to openpype.client which replace 'get_workfile_doc' in openpype.lib --- openpype/client/__init__.py | 4 ++ openpype/client/entities.py | 32 ++++++++++++++++ openpype/lib/avalon_context.py | 59 +++++++++++++++++++++++++++--- openpype/tools/workfiles/window.py | 17 ++++++--- 4 files changed, 100 insertions(+), 12 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index e3b4ef5132..e1c8ea9f03 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -33,6 +33,8 @@ from .entities import ( get_thumbnail, get_thumbnails, get_thumbnail_id_from_source, + + get_workfile_info, ) __all__ = ( @@ -70,4 +72,6 @@ __all__ = ( "get_thumbnail", "get_thumbnails", "get_thumbnail_id_from_source", + + "get_workfile_info", ) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 28cd994254..9771461d2e 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1158,6 +1158,38 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): return conn.find_one(query_filter, _prepare_fields(fields)) +def get_workfile_info( + project_name, asset_id, task_name, filename, fields=None +): + """Document with workfile information. + + Warning: + Query is based on filename and context which does not meant it will + find always right expected result. There is a limited amound of usage + and is recommended to not expect that all workfiles are stored in + database. + + Args: + project_name (str): Name of project where to look for queried entities. + asset_id (str|ObjectId): Id of asset entity. + task_name (str): Task name on asset. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + """ + + if not asset_id or not task_name or not filename: + return None + + query_filter = { + "type": "workfile", + "parent": _convert_id(asset_id), + "task_name": task_name, + "filename": filename + } + conn = _get_project_connection(project_name) + return conn.find_one(query_filter, _prepare_fields(fields)) + + """ ## Custom data storage: - Settings - OP settings overrides and local settings diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index ad34e24644..412d7fa1d3 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -9,7 +9,11 @@ import collections import functools from bson.objectid import ObjectId +import warnings +from openpype.client import ( + get_workfile_info, +) from openpype.settings import ( get_project_settings, get_system_settings @@ -36,6 +40,51 @@ PROJECT_NAME_REGEX = re.compile( ) +class AvalonContextDeprecatedWarning(DeprecationWarning): + pass + + +def deprecated(new_destination): + """Mark functions as deprecated. + + It will result in a warning being emitted when the function is used. + """ + + func = None + if callable(new_destination): + func = new_destination + new_destination = None + + def _decorator(decorated_func): + if new_destination is None: + warning_message = ( + " Please check content of deprecated function to figure out" + " possible replacement." + ) + else: + warning_message = " Please replace your usage with '{}'.".format( + new_destination + ) + + @functools.wraps(decorated_func) + def wrapper(*args, **kwargs): + warnings.simplefilter("always", AvalonContextDeprecatedWarning) + warnings.warn( + ( + "Call to deprecated function '{}'" + "\nFunction was moved or removed.{}" + ).format(decorated_func.__name__, warning_message), + category=AvalonContextDeprecatedWarning, + stacklevel=4 + ) + return decorated_func(*args, **kwargs) + return wrapper + + if func is None: + return _decorator + return _decorator(func) + + def create_project( project_name, project_code, library_project=False, dbcon=None ): @@ -759,6 +808,7 @@ def update_current_task(task=None, asset=None, app=None, template_key=None): @with_pipeline_io +@deprecated("openpype.client.get_workfile_info") def get_workfile_doc(asset_id, task_name, filename, dbcon=None): """Return workfile document for entered context. @@ -775,16 +825,13 @@ def get_workfile_doc(asset_id, task_name, filename, dbcon=None): Returns: dict: Workfile document or None. """ + # Use legacy_io if dbcon is not entered if not dbcon: dbcon = legacy_io - return dbcon.find_one({ - "type": "workfile", - "parent": asset_id, - "task_name": task_name, - "filename": filename - }) + project_name = dbcon.active_project() + return get_workfile_info(project_name, asset_id, task_name, filename) @with_pipeline_io diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index 9f4cea2f8a..c1efe026f2 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -2,10 +2,13 @@ import os import datetime from Qt import QtCore, QtWidgets -from openpype.client import get_asset_by_id, get_asset_by_name +from openpype.client import ( + get_asset_by_id, + get_asset_by_name, + get_workfile_info, +) from openpype import style from openpype.lib import ( - get_workfile_doc, create_workfile_doc, save_workfile_data_to_doc, ) @@ -255,8 +258,9 @@ class Window(QtWidgets.QMainWindow): workfile_doc = None if asset_id and task_name and filepath: filename = os.path.split(filepath)[1] - workfile_doc = get_workfile_doc( - asset_id, task_name, filename, legacy_io + project_name = legacy_io.active_project() + workfile_doc = get_workfile_info( + project_name, asset_id, task_name, filename ) self.side_panel.set_context( asset_id, task_name, filepath, workfile_doc @@ -289,8 +293,9 @@ class Window(QtWidgets.QMainWindow): return filename = os.path.split(filepath)[1] - return get_workfile_doc( - asset_id, task_name, filename, legacy_io + project_name = legacy_io.active_project() + return get_workfile_info( + project_name, asset_id, task_name, filename ) def _create_workfile_doc(self, filepath, force=False): From 9f29726d98b7e87f608dd70aa512b8e0c56df949 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 16:05:22 +0200 Subject: [PATCH 0934/1227] modified and moved project doc validation --- openpype/lib/avalon_context.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 412d7fa1d3..fec87e53d1 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -12,6 +12,7 @@ from bson.objectid import ObjectId import warnings from openpype.client import ( + get_project, get_workfile_info, ) from openpype.settings import ( @@ -114,6 +115,11 @@ def create_project( from openpype.pipeline import AvalonMongoDB from openpype.pipeline.schema import validate + if get_project(project_name, fields=["name"]): + raise ValueError("Project with name \"{}\" already exists".format( + project_name + )) + if dbcon is None: dbcon = AvalonMongoDB() @@ -123,15 +129,6 @@ def create_project( ).format(project_name)) database = dbcon.database - project_doc = database[project_name].find_one( - {"type": "project"}, - {"name": 1} - ) - if project_doc: - raise ValueError("Project with name \"{}\" already exists".format( - project_name - )) - project_doc = { "type": "project", "name": project_name, @@ -154,7 +151,7 @@ def create_project( database[project_name].delete_one({"type": "project"}) raise - project_doc = database[project_name].find_one({"type": "project"}) + project_doc = get_project(project_name) try: # Validate created project document From 5b600b850614b5d67fe6f7eb59c42754b83460b3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 16:11:54 +0200 Subject: [PATCH 0935/1227] is_latest use new query functions --- openpype/lib/avalon_context.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index fec87e53d1..02b4cad6f2 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -13,6 +13,8 @@ import warnings from openpype.client import ( get_project, + get_version_by_id, + get_last_version_by_subset_id, get_workfile_info, ) from openpype.settings import ( @@ -183,23 +185,24 @@ def is_latest(representation): Returns: bool: Whether the representation is of latest version. - """ - version = legacy_io.find_one({"_id": representation['parent']}) + project_name = legacy_io.active_project() + version = get_version_by_id( + project_name, + representation["parent"], + hero=True, + fields=["_id", "type", "parent"] + ) if version["type"] == "hero_version": return True # Get highest version under the parent - highest_version = legacy_io.find_one({ - "type": "version", - "parent": version["parent"] - }, sort=[("name", -1)], projection={"name": True}) + last_version = get_last_version_by_subset_id( + project_name, version["parent"], fields=["_id"] + ) - if version['name'] == highest_version['name']: - return True - else: - return False + return version["_id"] == last_version["_id"] @with_pipeline_io From a0bc06c62084f4116c3e0519522b15f7d4bcde0e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 16:18:12 +0200 Subject: [PATCH 0936/1227] use query functions for rest of avalon context functions --- openpype/lib/avalon_context.py | 232 +++++++++++++-------------------- 1 file changed, 90 insertions(+), 142 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 02b4cad6f2..7b00032027 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -13,8 +13,15 @@ import warnings from openpype.client import ( get_project, + get_assets, + get_asset_by_name, + get_subset_by_name, + get_subsets, get_version_by_id, + get_last_versions, get_last_version_by_subset_id, + get_representations, + get_representation_by_id, get_workfile_info, ) from openpype.settings import ( @@ -210,6 +217,7 @@ def any_outdated(): """Return whether the current scene has any outdated content""" from openpype.pipeline import registered_host + project_name = legacy_io.active_project() checked = set() host = registered_host() for container in host.ls(): @@ -217,12 +225,8 @@ def any_outdated(): if representation in checked: continue - representation_doc = legacy_io.find_one( - { - "_id": ObjectId(representation), - "type": "representation" - }, - projection={"parent": True} + representation_doc = get_representation_by_id( + project_name, representation, fields=["parent"] ) if representation_doc and not is_latest(representation_doc): return True @@ -240,22 +244,20 @@ def any_outdated(): def get_asset(asset_name=None): """ Returning asset document from database by its name. - Doesn't count with duplicities on asset names! + Doesn't count with duplicities on asset names! - Args: - asset_name (str) + Args: + asset_name (str) - Returns: - (MongoDB document) + Returns: + (MongoDB document) """ + + project_name = legacy_io.active_project() if not asset_name: asset_name = legacy_io.Session["AVALON_ASSET"] - asset_document = legacy_io.find_one({ - "name": asset_name, - "type": "asset" - }) - + asset_document = get_asset_by_name(project_name, asset_name) if not asset_document: raise TypeError("Entity \"{}\" was not found in DB".format(asset_name)) @@ -311,11 +313,13 @@ def get_linked_assets(asset_doc): Returns: (list) Asset documents of input links for passed asset doc. """ + link_ids = get_linked_asset_ids(asset_doc) if not link_ids: return [] - return list(legacy_io.find({"_id": {"$in": link_ids}})) + project_name = legacy_io.active_project() + return list(get_assets(project_name, link_ids)) @with_pipeline_io @@ -337,20 +341,14 @@ def get_latest_version(asset_name, subset_name, dbcon=None, project_name=None): dict: Last version document for entered . """ - if not dbcon: - log.debug("Using `legacy_io` for query.") - dbcon = legacy_io - # Make sure is installed - dbcon.install() + if not project_name: + if not dbcon: + log.debug("Using `legacy_io` for query.") + dbcon = legacy_io + # Make sure is installed + dbcon.install() - if project_name and project_name != dbcon.Session.get("AVALON_PROJECT"): - # `legacy_io` has only `_database` attribute - # but `AvalonMongoDB` has `database` - database = getattr(dbcon, "database", dbcon._database) - collection = database[project_name] - else: - project_name = dbcon.Session.get("AVALON_PROJECT") - collection = dbcon + project_name = dbcon.active_project() log.debug(( "Getting latest version for Project: \"{}\" Asset: \"{}\"" @@ -358,19 +356,15 @@ def get_latest_version(asset_name, subset_name, dbcon=None, project_name=None): ).format(project_name, asset_name, subset_name)) # Query asset document id by asset name - asset_doc = collection.find_one( - {"type": "asset", "name": asset_name}, - {"_id": True} - ) + asset_doc = get_asset_by_name(project_name, asset_name, fields=["_id"]) if not asset_doc: log.info( "Asset \"{}\" was not found in Database.".format(asset_name) ) return None - subset_doc = collection.find_one( - {"type": "subset", "name": subset_name, "parent": asset_doc["_id"]}, - {"_id": True} + subset_doc = get_subset_by_name( + project_name, subset_name, asset_doc["_id"] ) if not subset_doc: log.info( @@ -378,10 +372,7 @@ def get_latest_version(asset_name, subset_name, dbcon=None, project_name=None): ) return None - version_doc = collection.find_one( - {"type": "version", "parent": subset_doc["_id"]}, - sort=[("name", -1)], - ) + version_doc = get_last_version_by_subset_id(project_name, subset_doc["_id"]) if not version_doc: log.info( "Subset \"{}\" does not have any version yet.".format(subset_name) @@ -418,28 +409,17 @@ def get_workfile_template_key_from_context( ValueError: When both 'dbcon' and 'project_name' were not passed. """ - if not dbcon: - if not project_name: + if not project_name: + if not dbcon: raise ValueError(( "`get_workfile_template_key_from_context` requires to pass" " one of 'dbcon' or 'project_name' arguments." )) - from openpype.pipeline import AvalonMongoDB - dbcon = AvalonMongoDB() - dbcon.Session["AVALON_PROJECT"] = project_name + project_name = dbcon.active_project() - elif not project_name: - project_name = dbcon.Session["AVALON_PROJECT"] - - asset_doc = dbcon.find_one( - { - "type": "asset", - "name": asset_name - }, - { - "data.tasks": 1 - } + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["data.tasks"] ) asset_tasks = asset_doc.get("data", {}).get("tasks") or {} task_info = asset_tasks.get(task_name) or {} @@ -632,6 +612,7 @@ def get_workdir( Returns: TemplateResult: Workdir path. """ + if not anatomy: anatomy = Anatomy(project_doc["name"]) @@ -659,15 +640,11 @@ def template_data_from_session(session=None): session = legacy_io.Session project_name = session["AVALON_PROJECT"] - project_doc = legacy_io.database[project_name].find_one({ - "type": "project" - }) - asset_doc = legacy_io.database[project_name].find_one({ - "type": "asset", - "name": session["AVALON_ASSET"] - }) + asset_name = session["AVALON_ASSET"] task_name = session["AVALON_TASK"] host_name = session["AVALON_APP"] + project_doc = get_project(project_name) + asset_doc = get_asset_by_name(project_name, asset_name) return get_workdir_data(project_doc, asset_doc, task_name, host_name) @@ -692,8 +669,8 @@ def compute_session_changes( Returns: dict: The required changes in the Session dictionary. - """ + changes = dict() # If no changes, return directly @@ -711,12 +688,9 @@ def compute_session_changes( if not asset_document or not asset_tasks: # Assume asset name - asset_document = legacy_io.find_one( - { - "name": asset, - "type": "asset" - }, - {"data.tasks": True} + project_name = session["AVALON_PROJECT"] + asset_document = get_asset_by_name( + project_name, asset, fields=["data.tasks"] ) assert asset_document, "Asset must exist" @@ -864,12 +838,13 @@ def create_workfile_doc(asset_doc, task_name, filename, workdir, dbcon=None): doc_data = copy.deepcopy(doc_filter) # Prepare project for workdir data - project_doc = dbcon.find_one({"type": "project"}) + project_name = dbcon.active_project() + project_doc = get_project(project_name) workdir_data = get_workdir_data( project_doc, asset_doc, task_name, dbcon.Session["AVALON_APP"] ) # Prepare anatomy - anatomy = Anatomy(project_doc["name"]) + anatomy = Anatomy(project_name) # Get workdir path (result is anatomy.TemplateResult) template_workdir = get_workdir_with_workdir_data( workdir_data, anatomy @@ -984,12 +959,9 @@ class BuildWorkfile: from openpype.pipeline import discover_loader_plugins # Get current asset name and entity + project_name = legacy_io.active_project() current_asset_name = legacy_io.Session["AVALON_ASSET"] - current_asset_entity = legacy_io.find_one({ - "type": "asset", - "name": current_asset_name - }) - + current_asset_entity = get_asset_by_name(project_name, current_asset_name) # Skip if asset was not found if not current_asset_entity: print("Asset entity with name `{}` was not found".format( @@ -1494,7 +1466,7 @@ class BuildWorkfile: return loaded_containers @with_pipeline_io - def _collect_last_version_repres(self, asset_entities): + def _collect_last_version_repres(self, asset_docs): """Collect subsets, versions and representations for asset_entities. Args: @@ -1527,64 +1499,56 @@ class BuildWorkfile: ``` """ - if not asset_entities: - return {} + output = {} + if not asset_docs: + return output - asset_entity_by_ids = {asset["_id"]: asset for asset in asset_entities} + asset_docs_by_ids = {asset["_id"]: asset for asset in asset_docs} - subsets = list(legacy_io.find({ - "type": "subset", - "parent": {"$in": list(asset_entity_by_ids.keys())} - })) + project_name = legacy_io.active_project() + subsets = list(get_subsets( + project_name, asset_ids=asset_docs_by_ids.keys() + )) subset_entity_by_ids = {subset["_id"]: subset for subset in subsets} - sorted_versions = list(legacy_io.find({ - "type": "version", - "parent": {"$in": list(subset_entity_by_ids.keys())} - }).sort("name", -1)) + last_version_by_subset_id = get_last_versions( + project_name, subset_entity_by_ids.keys() + ) + last_version_docs_by_id = { + version["_id"]: version + for version in last_version_by_subset_id.values() + } + repre_docs = get_representations( + project_name, version_ids=last_version_docs_by_id.keys() + ) - subset_id_with_latest_version = [] - last_versions_by_id = {} - for version in sorted_versions: - subset_id = version["parent"] - if subset_id in subset_id_with_latest_version: - continue - subset_id_with_latest_version.append(subset_id) - last_versions_by_id[version["_id"]] = version + for repre_doc in repre_docs: + version_id = repre_doc["parent"] + version_doc = last_version_docs_by_id[version_id] - repres = legacy_io.find({ - "type": "representation", - "parent": {"$in": list(last_versions_by_id.keys())} - }) + subset_id = version_doc["parent"] + subset_doc = subset_entity_by_ids[subset_id] - output = {} - for repre in repres: - version_id = repre["parent"] - version = last_versions_by_id[version_id] - - subset_id = version["parent"] - subset = subset_entity_by_ids[subset_id] - - asset_id = subset["parent"] - asset = asset_entity_by_ids[asset_id] + asset_id = subset_doc["parent"] + asset_doc = asset_docs_by_ids[asset_id] if asset_id not in output: output[asset_id] = { - "asset_entity": asset, + "asset_entity": asset_doc, "subsets": {} } if subset_id not in output[asset_id]["subsets"]: output[asset_id]["subsets"][subset_id] = { - "subset_entity": subset, + "subset_entity": subset_doc, "version": { - "version_entity": version, + "version_entity": version_doc, "repres": [] } } output[asset_id]["subsets"][subset_id]["version"]["repres"].append( - repre + repre_doc ) return output @@ -1790,35 +1754,19 @@ def get_custom_workfile_template_by_string_context( context. (Existence of formatted path is not validated.) """ - if dbcon is None: - from openpype.pipeline import AvalonMongoDB + project_name = None + if anatomy is not None: + project_name = anatomy.project_name - dbcon = AvalonMongoDB() + if not project_name and dbcon is not None: + project_name = dbcon.active_project() - dbcon.install() + if not project_name: + raise ValueError("Can't determina project") - if dbcon.Session["AVALON_PROJECT"] != project_name: - dbcon.Session["AVALON_PROJECT"] = project_name - - project_doc = dbcon.find_one( - {"type": "project"}, - # All we need is "name" and "data.code" keys - { - "name": 1, - "data.code": 1 - } - ) - asset_doc = dbcon.find_one( - { - "type": "asset", - "name": asset_name - }, - # All we need is "name" and "data.tasks" keys - { - "name": 1, - "data.tasks": 1 - } - ) + project_doc = get_project(project_name, fields=["name", "data.code"]) + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["name", "data.tasks"]) return get_custom_workfile_template_by_context( template_profiles, project_doc, asset_doc, task_name, anatomy From 57e0f576c82df6f1510e8954e99db98fb580d1d7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 16:18:19 +0200 Subject: [PATCH 0937/1227] removed unused import --- openpype/lib/avalon_context.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 7b00032027..9fc632aa27 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -7,8 +7,6 @@ import platform import logging import collections import functools - -from bson.objectid import ObjectId import warnings from openpype.client import ( From 13123a41277e08fec2345a888834c254e10c8034 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 16:18:34 +0200 Subject: [PATCH 0938/1227] get_system_general_anatomy_data can accept already prepare system settings --- openpype/lib/avalon_context.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 9fc632aa27..70a4e59279 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -262,8 +262,9 @@ def get_asset(asset_name=None): return asset_document -def get_system_general_anatomy_data(): - system_settings = get_system_settings() +def get_system_general_anatomy_data(system_settings=None): + if not system_settings: + system_settings = get_system_settings() studio_name = system_settings["general"]["studio_name"] studio_code = system_settings["general"]["studio_code"] return { From cb62b175719c9df96ca4641c7a93ad3b1f12777c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 16:29:54 +0200 Subject: [PATCH 0939/1227] added ability to receive all project documents --- openpype/client/__init__.py | 2 ++ openpype/client/entities.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index e1c8ea9f03..4fee889f18 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -1,6 +1,7 @@ from .entities import ( get_projects, get_project, + get_whole_project, get_asset_by_id, get_asset_by_name, @@ -40,6 +41,7 @@ from .entities import ( __all__ = ( "get_projects", "get_project", + "get_whole_project", "get_asset_by_id", "get_asset_by_name", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 9771461d2e..698fba796d 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -94,6 +94,21 @@ def get_project(project_name, active=True, inactive=False, fields=None): return conn.find_one(query_filter, _prepare_fields(fields)) +def get_whole_project(project_name): + """Receive all documents from project. + + Helper that can be used to get all document from whole project. For example + for backups etc. + + Returns: + Cursor: Query cursor as iterable which returns all documents from + project collection. + """ + + conn = _get_project_connection(project_name) + return conn.find({}) + + def get_asset_by_id(project_name, asset_id, fields=None): """Receive asset data by it's id. From 7416508e8056bcab5de784b4dda4fea0c9859f92 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 16:33:21 +0200 Subject: [PATCH 0940/1227] use query functions in project backpack --- openpype/lib/project_backpack.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/lib/project_backpack.py b/openpype/lib/project_backpack.py index 396479c725..f0188e6765 100644 --- a/openpype/lib/project_backpack.py +++ b/openpype/lib/project_backpack.py @@ -24,7 +24,10 @@ from bson.json_util import ( dumps, CANONICAL_JSON_OPTIONS ) - +from openpype.client import ( + get_project, + get_whole_project, +) from openpype.pipeline import AvalonMongoDB DOCUMENTS_FILE_NAME = "database" @@ -55,9 +58,7 @@ def pack_project(project_name, destination_dir=None): """ print("Creating package of project \"{}\"".format(project_name)) # Validate existence of project - dbcon = AvalonMongoDB() - dbcon.Session["AVALON_PROJECT"] = project_name - project_doc = dbcon.find_one({"type": "project"}) + project_doc = get_project(project_name) if not project_doc: raise ValueError("Project \"{}\" was not found in database".format( project_name @@ -118,7 +119,7 @@ def pack_project(project_name, destination_dir=None): temp_docs_json = s.name # Query all project documents and store them to temp json - docs = list(dbcon.find({})) + docs = list(get_whole_project(project_name)) data = dumps( docs, json_options=CANONICAL_JSON_OPTIONS ) @@ -147,7 +148,7 @@ def pack_project(project_name, destination_dir=None): # Cleanup os.remove(temp_docs_json) os.remove(temp_metadata_json) - dbcon.uninstall() + print("*** Packing finished ***") @@ -207,7 +208,7 @@ def unpack_project(path_to_zip, new_root=None): print("Using different root path {}".format(new_root)) root_path = new_root - project_doc = collection.find_one({"type": "project"}) + project_doc = get_project(project_name) roots = project_doc["config"]["roots"] key = tuple(roots.keys())[0] update_key = "config.roots.{}.{}".format(key, low_platform) From 8f46a73ab710ed98a55b475dba44a558213d3a17 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 16:33:25 +0200 Subject: [PATCH 0941/1227] use query functions in applications --- openpype/lib/applications.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index a81bdeca0f..6bdf0a237f 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -11,6 +11,10 @@ from abc import ABCMeta, abstractmethod import six +from openpype.client import ( + get_project, + get_asset_by_name, +) from openpype.settings import ( get_system_settings, get_project_settings, @@ -1313,11 +1317,8 @@ def get_app_environments_for_context( dbcon.install() # Project document - project_doc = dbcon.find_one({"type": "project"}) - asset_doc = dbcon.find_one({ - "type": "asset", - "name": asset_name - }) + project_doc = get_project(project_name) + asset_doc = get_asset_by_name(project_name, asset_name) if modules_manager is None: from openpype.modules import ModulesManager From eed6acd53c36e7c831d0201cb9d45a9dd31768fd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 16:50:36 +0200 Subject: [PATCH 0942/1227] use query function in plugin tools --- openpype/lib/plugin_tools.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index bcbf06a0e8..1d3c1eec6b 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -6,10 +6,10 @@ import logging import re import json -from .profiles_filtering import filter_profiles - +from openpype.client import get_asset_by_id from openpype.settings import get_project_settings +from .profiles_filtering import filter_profiles log = logging.getLogger(__name__) @@ -135,24 +135,17 @@ def get_subset_name( This is legacy function should be replaced with `get_subset_name_with_asset_doc` where asset document is expected. """ - if dbcon is None: - from openpype.pipeline import AvalonMongoDB - dbcon = AvalonMongoDB() - dbcon.Session["AVALON_PROJECT"] = project_name + if project_name is None: + project_name = dbcon.project_name - dbcon.install() - - asset_doc = dbcon.find_one( - {"_id": asset_id}, - {"data.tasks": True} - ) or {} + asset_doc = get_asset_by_id(project_name, asset_id, fields=["data.tasks"]) return get_subset_name_with_asset_doc( family, variant, task_name, - asset_doc, + asset_doc or {}, project_name, host_name, default_template, From 720aba7c44e24cd80cbcb3d498e73ec3cf503659 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 16:53:24 +0200 Subject: [PATCH 0943/1227] modified usdlib to use query functions and use anatomy for formatting --- openpype/lib/usdlib.py | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/openpype/lib/usdlib.py b/openpype/lib/usdlib.py index 86de19b4be..2943f6660a 100644 --- a/openpype/lib/usdlib.py +++ b/openpype/lib/usdlib.py @@ -8,10 +8,9 @@ except ImportError: # Allow to fall back on Multiverse 6.3.0+ pxr usd library from mvpxr import Usd, UsdGeom, Sdf, Kind -from openpype.pipeline import ( - registered_root, - legacy_io, -) +from openpype.client import get_project, get_asset_by_name +from openpype.pipeline import legacy_io +from openpypa.lib import Anatomy log = logging.getLogger(__name__) @@ -128,7 +127,8 @@ def create_model(filename, asset, variant_subsets): """ - asset_doc = legacy_io.find_one({"name": asset, "type": "asset"}) + project_name = legacy_io.active_project() + asset_doc = get_asset_by_name(project_name, asset) assert asset_doc, "Asset not found: %s" % asset variants = [] @@ -178,7 +178,8 @@ def create_shade(filename, asset, variant_subsets): """ - asset_doc = legacy_io.find_one({"name": asset, "type": "asset"}) + project_name = legacy_io.active_project() + asset_doc = get_asset_by_name(project_name, asset) assert asset_doc, "Asset not found: %s" % asset variants = [] @@ -213,7 +214,8 @@ def create_shade_variation(filename, asset, model_variant, shade_variants): """ - asset_doc = legacy_io.find_one({"name": asset, "type": "asset"}) + project_name = legacy_io.active_project() + asset_doc = get_asset_by_name(project_name, asset) assert asset_doc, "Asset not found: %s" % asset variants = [] @@ -313,21 +315,25 @@ def get_usd_master_path(asset, subset, representation): """ - project = legacy_io.find_one( - {"type": "project"}, projection={"config.template.publish": True} + project_name = legacy_io.active_project() + anatomy = Anatomy(project_name) + project_doc = get_project( + project_name, + fields=["name", "data.code"] ) - template = project["config"]["template"]["publish"] if isinstance(asset, dict) and "name" in asset: # Allow explicitly passing asset document asset_doc = asset else: - asset_doc = legacy_io.find_one({"name": asset, "type": "asset"}) + asset_doc = get_asset_by_name(project_name, asset, fields=["name"]) - path = template.format( - **{ - "root": registered_root(), - "project": legacy_io.Session["AVALON_PROJECT"], + formatted_result = anatomy.format( + { + "project": { + "name": project_name, + "code": project_doc.get("data", {}).get("code") + }, "asset": asset_doc["name"], "subset": subset, "representation": representation, @@ -335,6 +341,7 @@ def get_usd_master_path(asset, subset, representation): } ) + path = formatted_result["publish"]["path"] # Remove the version folder subset_folder = os.path.dirname(os.path.dirname(path)) master_folder = os.path.join(subset_folder, "master") From 2296a56118265962c87b9c44964affff9fabb129 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 16:56:15 +0200 Subject: [PATCH 0944/1227] fix anatomy import --- openpype/lib/usdlib.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/lib/usdlib.py b/openpype/lib/usdlib.py index 2943f6660a..20703ee308 100644 --- a/openpype/lib/usdlib.py +++ b/openpype/lib/usdlib.py @@ -9,8 +9,7 @@ except ImportError: from mvpxr import Usd, UsdGeom, Sdf, Kind from openpype.client import get_project, get_asset_by_name -from openpype.pipeline import legacy_io -from openpypa.lib import Anatomy +from openpype.pipeline import legacy_io, Anatomy log = logging.getLogger(__name__) From bdff4d5eb0c03ec2a6687e3baa963f6dc5f50cf4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 17:05:13 +0200 Subject: [PATCH 0945/1227] modified docstring --- openpype/client/entities.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 698fba796d..cb20916153 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1180,9 +1180,8 @@ def get_workfile_info( Warning: Query is based on filename and context which does not meant it will - find always right expected result. There is a limited amound of usage - and is recommended to not expect that all workfiles are stored in - database. + find always right and expected result. Information have limited usage + and is not recommended to use it as source information about workfile. Args: project_name (str): Name of project where to look for queried entities. From 49cb5a3e162a40e31c02d66bbbe2b2ad90a2b246 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 17:28:22 +0200 Subject: [PATCH 0946/1227] hound fixes --- openpype/lib/avalon_context.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 70a4e59279..61976f5a5b 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -371,7 +371,9 @@ def get_latest_version(asset_name, subset_name, dbcon=None, project_name=None): ) return None - version_doc = get_last_version_by_subset_id(project_name, subset_doc["_id"]) + version_doc = get_last_version_by_subset_id( + project_name, subset_doc["_id"] + ) if not version_doc: log.info( "Subset \"{}\" does not have any version yet.".format(subset_name) @@ -960,7 +962,9 @@ class BuildWorkfile: # Get current asset name and entity project_name = legacy_io.active_project() current_asset_name = legacy_io.Session["AVALON_ASSET"] - current_asset_entity = get_asset_by_name(project_name, current_asset_name) + current_asset_entity = get_asset_by_name( + project_name, current_asset_name + ) # Skip if asset was not found if not current_asset_entity: print("Asset entity with name `{}` was not found".format( From 728bfd6571ead30f3971578f5c4767c85fdde378 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 17:56:30 +0200 Subject: [PATCH 0947/1227] normalize workdir --- openpype/lib/avalon_context.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index a03f066300..5ff3b15119 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -604,7 +604,10 @@ def get_workdir_with_workdir_data( anatomy_filled = anatomy.format(workdir_data) # Output is TemplateResult object which contain useful data - return anatomy_filled[template_key]["folder"] + path = anatomy_filled[template_key]["folder"] + if path: + path = os.path.normpath(path) + return path def get_workdir( From 3bf3c0bab4fb73efa3c736df6890609767163151 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 17:56:54 +0200 Subject: [PATCH 0948/1227] modified anatomy imports in lib itself --- openpype/lib/applications.py | 7 ++----- openpype/lib/avalon_context.py | 9 ++++++++- openpype/lib/path_tools.py | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index a81bdeca0f..d229848645 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -20,10 +20,7 @@ from openpype.settings.constants import ( METADATA_KEYS, M_DYNAMIC_KEY_LABEL ) -from . import ( - PypeLogger, - Anatomy -) +from . import PypeLogger from .profiles_filtering import filter_profiles from .local_settings import get_openpype_username from .avalon_context import ( @@ -1305,7 +1302,7 @@ def get_app_environments_for_context( dict: Environments for passed context and application. """ - from openpype.pipeline import AvalonMongoDB + from openpype.pipeline import AvalonMongoDB, Anatomy # Avalon database connection dbcon = AvalonMongoDB() diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 5ff3b15119..a7078f9eca 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -14,7 +14,6 @@ from openpype.settings import ( get_project_settings, get_system_settings ) -from .anatomy import Anatomy from .profiles_filtering import filter_profiles from .events import emit_event from .path_templates import StringTemplate @@ -593,6 +592,7 @@ def get_workdir_with_workdir_data( )) if not anatomy: + from openpype.pipeline import Anatomy anatomy = Anatomy(project_name) if not template_key: @@ -638,6 +638,7 @@ def get_workdir( TemplateResult: Workdir path. """ if not anatomy: + from openpype.pipeline import Anatomy anatomy = Anatomy(project_doc["name"]) workdir_data = get_workdir_data( @@ -750,6 +751,8 @@ def compute_session_changes( @with_pipeline_io def get_workdir_from_session(session=None, template_key=None): + from openpype.pipeline import Anatomy + if session is None: session = legacy_io.Session project_name = session["AVALON_PROJECT"] @@ -856,6 +859,8 @@ def create_workfile_doc(asset_doc, task_name, filename, workdir, dbcon=None): dbcon (AvalonMongoDB): Optionally enter avalon AvalonMongoDB object and `legacy_io` is used if not entered. """ + from openpype.pipeline import Anatomy + # Use legacy_io if dbcon is not entered if not dbcon: dbcon = legacy_io @@ -1676,6 +1681,7 @@ def _get_task_context_data_for_anatomy( """ if anatomy is None: + from openpype.pipeline import Anatomy anatomy = Anatomy(project_doc["name"]) asset_name = asset_doc["name"] @@ -1744,6 +1750,7 @@ def get_custom_workfile_template_by_context( """ if anatomy is None: + from openpype.pipeline import Anatomy anatomy = Anatomy(project_doc["name"]) # get project, asset, task anatomy context data diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index caad20f4d6..4f28be3302 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -9,7 +9,6 @@ import platform from openpype.client import get_project from openpype.settings import get_project_settings -from .anatomy import Anatomy from .profiles_filtering import filter_profiles log = logging.getLogger(__name__) @@ -227,6 +226,7 @@ def fill_paths(path_list, anatomy): def create_project_folders(basic_paths, project_name): + from openpype.pipeline import Anatomy anatomy = Anatomy(project_name) concat_paths = concatenate_splitted_paths(basic_paths, anatomy) From a2b8df6964010f9d835022ef9c28c74f87dfbfbc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 30 Jun 2022 18:04:45 +0200 Subject: [PATCH 0949/1227] normalize when workdir is received from session --- openpype/lib/avalon_context.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index a7078f9eca..616460410e 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -768,7 +768,10 @@ def get_workdir_from_session(session=None, template_key=None): host_name, project_name=project_name ) - return anatomy_filled[template_key]["folder"] + path = anatomy_filled[template_key]["folder"] + if path: + path = os.path.normpath(path) + return path @with_pipeline_io From a9462ac4ed89db38f0895d6ce14190c44ee77cb4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Jun 2022 22:20:02 +0200 Subject: [PATCH 0950/1227] Nuke: fixing metadata slate TC difference --- .../nuke/plugins/publish/extract_slate_frame.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index 6d930d358d..6997180c9f 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -152,6 +152,7 @@ class ExtractSlateFrame(openpype.api.Extractor): self.log.debug("__ first_frame: {}".format(first_frame)) self.log.debug("__ slate_first_frame: {}".format(slate_first_frame)) + above_slate_node = slate_node.dependencies().pop() # fallback if files does not exists if self._check_frames_exists(instance): # Read node @@ -164,8 +165,16 @@ class ExtractSlateFrame(openpype.api.Extractor): r_node["colorspace"].setValue(instance.data["colorspace"]) previous_node = r_node temporary_nodes = [previous_node] + + # adding copy metadata node for correct frame metadata + cm_node = nuke.createNode("CopyMetaData") + cm_node.setInput(0, previous_node) + cm_node.setInput(1, above_slate_node) + previous_node = cm_node + temporary_nodes.append(cm_node) + else: - previous_node = slate_node.dependencies().pop() + previous_node = above_slate_node temporary_nodes = [] # only create colorspace baking if toggled on @@ -221,8 +230,8 @@ class ExtractSlateFrame(openpype.api.Extractor): write_node.name(), int(slate_first_frame), int(slate_first_frame)) # Clean up - for node in temporary_nodes: - nuke.delete(node) + # for node in temporary_nodes: + # nuke.delete(node) def _render_slate_to_sequence(self, instance): # set slate frame From 9d556a9ae8f3f42770c4296cd799a427674db062 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 30 Jun 2022 23:47:27 +0200 Subject: [PATCH 0951/1227] Match set path logic more to Arnold VDB loader from #3433 --- .../maya/plugins/load/load_vdb_to_redshift.py | 52 +++++++------------ 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py b/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py index 7867f49bd1..c6a69dfe35 100644 --- a/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py +++ b/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py @@ -84,7 +84,9 @@ class LoadVDBtoRedShift(load.LoaderPlugin): name="{}RVSShape".format(label), parent=root) - self._apply_settings(volume_node, path=self.fname) + self._set_path(volume_node, + path=self.fname, + representation=context["representation"]) nodes = [root, volume_node] self[:] = nodes @@ -96,36 +98,6 @@ class LoadVDBtoRedShift(load.LoaderPlugin): context=context, loader=self.__class__.__name__) - def _apply_settings(self, - grid_node, - path): - """Apply the settings for the VDB path to the VRayVolumeGrid""" - from maya import cmds - - # The path points to a single file. However the vdb files could be - # either just that single file or a sequence in a folder so we check - # whether it's a sequence - folder = os.path.dirname(path) - files = os.listdir(folder) - is_single_file = len(files) == 1 - if is_single_file: - filename = path - else: - # The path points to the publish .vdb sequence filepath so we - # find the first file in there that ends with .vdb - files = sorted(files) - first = next((x for x in files if x.endswith(".vdb")), None) - if first is None: - raise RuntimeError("Couldn't find first .vdb file of " - "sequence in: %s" % path) - filename = os.path.join(path, first) - - # Tell Redshift whether it should load as sequence or single file - cmds.setAttr(grid_node + ".useFrameExtension", not is_single_file) - - # Set file path - cmds.setAttr(grid_node + ".fileName", filename, type="string") - def update(self, container, representation): from maya import cmds @@ -137,7 +109,7 @@ class LoadVDBtoRedShift(load.LoaderPlugin): assert len(grid_nodes) == 1, "This is a bug" # Update the VRayVolumeGrid - self._apply_settings(grid_nodes[0], path=path) + self._set_path(grid_nodes[0], path=path, representation=representation) # Update container representation cmds.setAttr(container["objectName"] + ".representation", @@ -162,3 +134,19 @@ class LoadVDBtoRedShift(load.LoaderPlugin): def switch(self, container, representation): self.update(container, representation) + + @staticmethod + def _set_path(grid_node, + path, + representation): + """Apply the settings for the VDB path to the RedshiftVolumeShape""" + from maya import cmds + + if not os.path.exists(path): + raise RuntimeError("Path does not exist: %s" % path) + + is_sequence = bool(representation["context"].get("frame")) + cmds.setAttr(grid_node + ".useFrameExtension", is_sequence) + + # Set file path + cmds.setAttr(grid_node + ".fileName", path, type="string") From b35d8886e2b0070abde920cd5a18b8786f7575aa Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 09:16:51 +0200 Subject: [PATCH 0952/1227] use query functions in avalon rest api calls --- openpype/modules/avalon_apps/rest_api.py | 37 +++++++++--------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/openpype/modules/avalon_apps/rest_api.py b/openpype/modules/avalon_apps/rest_api.py index b35f5bf357..a52ce1b6df 100644 --- a/openpype/modules/avalon_apps/rest_api.py +++ b/openpype/modules/avalon_apps/rest_api.py @@ -5,7 +5,12 @@ from bson.objectid import ObjectId from aiohttp.web_response import Response -from openpype.pipeline import AvalonMongoDB +from openpype.client import ( + get_projects, + get_project, + get_assets, + get_asset_by_name, +) from openpype_modules.webserver.base_routes import RestApiEndpoint @@ -14,19 +19,13 @@ class _RestApiEndpoint(RestApiEndpoint): self.resource = resource super(_RestApiEndpoint, self).__init__() - @property - def dbcon(self): - return self.resource.dbcon - class AvalonProjectsEndpoint(_RestApiEndpoint): async def get(self) -> Response: - output = [] - for project_name in self.dbcon.database.collection_names(): - project_doc = self.dbcon.database[project_name].find_one({ - "type": "project" - }) - output.append(project_doc) + output = [ + project_doc + for project_doc in get_projects() + ] return Response( status=200, body=self.resource.encode(output), @@ -36,9 +35,7 @@ class AvalonProjectsEndpoint(_RestApiEndpoint): class AvalonProjectEndpoint(_RestApiEndpoint): async def get(self, project_name) -> Response: - project_doc = self.dbcon.database[project_name].find_one({ - "type": "project" - }) + project_doc = get_project(project_name) if project_doc: return Response( status=200, @@ -53,9 +50,7 @@ class AvalonProjectEndpoint(_RestApiEndpoint): class AvalonAssetsEndpoint(_RestApiEndpoint): async def get(self, project_name) -> Response: - asset_docs = list(self.dbcon.database[project_name].find({ - "type": "asset" - })) + asset_docs = list(get_assets(project_name)) return Response( status=200, body=self.resource.encode(asset_docs), @@ -65,10 +60,7 @@ class AvalonAssetsEndpoint(_RestApiEndpoint): class AvalonAssetEndpoint(_RestApiEndpoint): async def get(self, project_name, asset_name) -> Response: - asset_doc = self.dbcon.database[project_name].find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) if asset_doc: return Response( status=200, @@ -88,9 +80,6 @@ class AvalonRestApiResource: self.module = avalon_module self.server_manager = server_manager - self.dbcon = AvalonMongoDB() - self.dbcon.install() - self.prefix = "/avalon" self.endpoint_defs = ( From f18a9833a62a0f1910976e00c36668be58d6ce0a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 09:40:38 +0200 Subject: [PATCH 0953/1227] use query functions in clockify launcher actions --- .../launcher_actions/ClockifyStart.py | 30 +++++-------- .../clockify/launcher_actions/ClockifySync.py | 44 ++++++++----------- 2 files changed, 29 insertions(+), 45 deletions(-) diff --git a/openpype/modules/clockify/launcher_actions/ClockifyStart.py b/openpype/modules/clockify/launcher_actions/ClockifyStart.py index 4669f98b01..7663aecc31 100644 --- a/openpype/modules/clockify/launcher_actions/ClockifyStart.py +++ b/openpype/modules/clockify/launcher_actions/ClockifyStart.py @@ -1,16 +1,9 @@ -from openpype.api import Logger -from openpype.pipeline import ( - legacy_io, - LauncherAction, -) +from openpype.client import get_asset_by_name +from openpype.pipeline import LauncherAction from openpype_modules.clockify.clockify_api import ClockifyAPI -log = Logger.get_logger(__name__) - - class ClockifyStart(LauncherAction): - name = "clockify_start_timer" label = "Clockify - Start Timer" icon = "clockify_icon" @@ -24,20 +17,19 @@ class ClockifyStart(LauncherAction): return False def process(self, session, **kwargs): - project_name = session['AVALON_PROJECT'] - asset_name = session['AVALON_ASSET'] - task_name = session['AVALON_TASK'] + project_name = session["AVALON_PROJECT"] + asset_name = session["AVALON_ASSET"] + task_name = session["AVALON_TASK"] description = asset_name - asset = legacy_io.find_one({ - 'type': 'asset', - 'name': asset_name - }) - if asset is not None: - desc_items = asset.get('data', {}).get('parents', []) + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["data.parents"] + ) + if asset_doc is not None: + desc_items = asset_doc.get("data", {}).get("parents", []) desc_items.append(asset_name) desc_items.append(task_name) - description = '/'.join(desc_items) + description = "/".join(desc_items) project_id = self.clockapi.get_project_id(project_name) tag_ids = [] diff --git a/openpype/modules/clockify/launcher_actions/ClockifySync.py b/openpype/modules/clockify/launcher_actions/ClockifySync.py index 356bbd0306..c346a1b4f6 100644 --- a/openpype/modules/clockify/launcher_actions/ClockifySync.py +++ b/openpype/modules/clockify/launcher_actions/ClockifySync.py @@ -1,11 +1,6 @@ +from openpype.client import get_projects, get_project from openpype_modules.clockify.clockify_api import ClockifyAPI -from openpype.api import Logger -from openpype.pipeline import ( - legacy_io, - LauncherAction, -) - -log = Logger.get_logger(__name__) +from openpype.pipeline import LauncherAction class ClockifySync(LauncherAction): @@ -22,39 +17,36 @@ class ClockifySync(LauncherAction): return self.have_permissions def process(self, session, **kwargs): - project_name = session.get('AVALON_PROJECT', None) + project_name = session.get("AVALON_PROJECT") or "" projects_to_sync = [] - if project_name.strip() == '' or project_name is None: - for project in legacy_io.projects(): - projects_to_sync.append(project) + if project_name.strip(): + projects_to_sync = [get_project(project_name)] else: - project = legacy_io.find_one({'type': 'project'}) - projects_to_sync.append(project) + projects_to_sync = get_projects() projects_info = {} for project in projects_to_sync: - task_types = project['config']['tasks'].keys() - projects_info[project['name']] = task_types + task_types = project["config"]["tasks"].keys() + projects_info[project["name"]] = task_types clockify_projects = self.clockapi.get_projects() for project_name, task_types in projects_info.items(): - if project_name not in clockify_projects: - response = self.clockapi.add_project(project_name) - if 'id' not in response: - self.log.error('Project {} can\'t be created'.format( - project_name - )) - continue - project_id = response['id'] - else: - project_id = clockify_projects[project_name] + if project_name in clockify_projects: + continue + + response = self.clockapi.add_project(project_name) + if "id" not in response: + self.log.error("Project {} can't be created".format( + project_name + )) + continue clockify_workspace_tags = self.clockapi.get_tags() for task_type in task_types: if task_type not in clockify_workspace_tags: response = self.clockapi.add_tag(task_type) - if 'id' not in response: + if "id" not in response: self.log.error('Task {} can\'t be created'.format( task_type )) From 7dc7bde29334a0debe738af6ef6f07d374f40bcf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 10:48:33 +0200 Subject: [PATCH 0954/1227] subset documents can be queried based on combination of asset id and subset names --- openpype/client/entities.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 28cd994254..5e242ea180 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -381,6 +381,7 @@ def get_subsets( subset_ids=None, subset_names=None, asset_ids=None, + names_by_asset_ids=None, archived=False, fields=None ): @@ -396,6 +397,9 @@ def get_subsets( Filter ignored if 'None' is passed. asset_ids (list[str|ObjectId]): Asset ids under which should look for the subsets. Filter ignored if 'None' is passed. + names_by_asset_ids (dict[ObjectId, list[str]]): Complex filtering + using asset ids and list of subset names under the asset. + archived (bool): Look for archived subsets too. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -429,6 +433,18 @@ def get_subsets( return [] query_filter["name"] = {"$in": list(subset_names)} + if names_by_asset_ids is not None: + or_query = [] + for asset_id, names in names_by_asset_ids.items(): + if asset_id and names: + or_query.append({ + "parent": _convert_id(asset_id), + "name": {"$in": list(names)} + }) + if not or_query: + return [] + query_filter["$or"] = or_query + conn = _get_project_connection(project_name) return conn.find(query_filter, _prepare_fields(fields)) From de4147ef93c2c45631b72664d067d7c070887f48 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 10:49:06 +0200 Subject: [PATCH 0955/1227] get last version does not do double query if only _id, name or parent are requested --- openpype/client/entities.py | 50 ++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 5e242ea180..aed465c46f 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -755,7 +755,10 @@ def get_last_versions(project_name, subset_ids, fields=None): """Latest versions for entered subset_ids. Args: + project_name (str): Name of project where to look for queried entities. subset_ids (list): List of subset ids. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. Returns: dict[ObjectId, int]: Key is subset id and value is last version name. @@ -765,7 +768,34 @@ def get_last_versions(project_name, subset_ids, fields=None): if not subset_ids: return {} - _pipeline = [ + if fields is not None: + fields = list(fields) + if not fields: + return {} + + # Avoid double query if only name and _id are requested + name_needed = False + limit_query = False + if fields: + fields_s = set(fields) + if "name" in fields_s: + name_needed = True + fields_s.remove("name") + + for field in ("_id", "parent"): + if field in fields_s: + fields_s.remove(field) + limit_query = len(fields_s) == 0 + + group_item = { + "_id": "$parent", + "_version_id": {"$last": "$_id"} + } + # Add name if name is needed (only for limit query) + if name_needed: + group_item["name"] = {"$last": "$name"} + + aggregation_pipeline = [ # Find all versions of those subsets {"$match": { "type": "version", @@ -774,16 +804,24 @@ def get_last_versions(project_name, subset_ids, fields=None): # Sorting versions all together {"$sort": {"name": 1}}, # Group them by "parent", but only take the last - {"$group": { - "_id": "$parent", - "_version_id": {"$last": "$_id"} - }} + {"$group": group_item} ] conn = _get_project_connection(project_name) + aggregate_result = conn.aggregate(aggregation_pipeline) + if limit_query: + output = {} + for item in aggregate_result: + subset_id = item["_id"] + item_data = {"_id": item["_version_id"], "parent": subset_id} + if name_needed: + item_data["name"] = item["name"] + output[subset_id] = item_data + return output + version_ids = [ doc["_version_id"] - for doc in conn.aggregate(_pipeline) + for doc in aggregate_result ] fields = _prepare_fields(fields, ["parent"]) From f5cceb3e056e71c2204df75b1eb6b87598f161a1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 10:49:39 +0200 Subject: [PATCH 0956/1227] use query functions in delete old versions --- openpype/plugins/load/delete_old_versions.py | 37 +++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index 7465f53855..039893aa54 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -8,6 +8,7 @@ import ftrack_api import qargparse from Qt import QtWidgets, QtCore +from openpype.client import get_versions, get_representations from openpype import style from openpype.pipeline import load, AvalonMongoDB, Anatomy from openpype.lib import StringTemplate @@ -197,18 +198,10 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): def get_data(self, context, versions_count): subset = context["subset"] asset = context["asset"] - anatomy = Anatomy(context["project"]["name"]) + project_name = context["project"]["name"] + anatomy = Anatomy(project_name) - self.dbcon = AvalonMongoDB() - self.dbcon.Session["AVALON_PROJECT"] = context["project"]["name"] - self.dbcon.install() - - versions = list( - self.dbcon.find({ - "type": "version", - "parent": {"$in": [subset["_id"]]} - }) - ) + versions = list(get_versions(project_name, subset_ids=[subset["_id"]])) versions_by_parent = collections.defaultdict(list) for ent in versions: @@ -267,10 +260,9 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): print(msg) return - repres = list(self.dbcon.find({ - "type": "representation", - "parent": {"$in": version_ids} - })) + repres = list(get_representations( + project_name, version_ids=version_ids + )) self.log.debug( "Collected representations to remove ({})".format(len(repres)) @@ -329,7 +321,7 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): return data - def main(self, data, remove_publish_folder): + def main(self, project_name, data, remove_publish_folder): # Size of files. size = 0 if not data: @@ -366,9 +358,11 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): )) if mongo_changes_bulk: - self.dbcon.bulk_write(mongo_changes_bulk) - - self.dbcon.uninstall() + dbcon = AvalonMongoDB() + dbcon.Session["AVALON_PROJECT"] = project_name + dbcon.install() + dbcon.bulk_write(mongo_changes_bulk) + dbcon.uninstall() # Set attribute `is_published` to `False` on ftrack AssetVersions session = ftrack_api.Session() @@ -422,7 +416,8 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): if not data: continue - size += self.main(data, remove_publish_folder) + project_name = context["project"]["name"] + size += self.main(project_name, data, remove_publish_folder) print("Progressing {}/{}".format(count + 1, len(contexts))) msg = "Total size of files: " + self.sizeof_fmt(size) @@ -448,7 +443,7 @@ class CalculateOldVersions(DeleteOldVersions): ) ] - def main(self, data, remove_publish_folder): + def main(self, project_name, data, remove_publish_folder): size = 0 if not data: From bc2f8387fe5eb31f70aa7c39fd3cb67848d23cbf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 10:50:06 +0200 Subject: [PATCH 0957/1227] delivery is using guery functions --- openpype/plugins/load/delivery.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/openpype/plugins/load/delivery.py b/openpype/plugins/load/delivery.py index 0361ab2be5..7585ea4c59 100644 --- a/openpype/plugins/load/delivery.py +++ b/openpype/plugins/load/delivery.py @@ -3,8 +3,9 @@ from collections import defaultdict from Qt import QtWidgets, QtCore, QtGui +from openpype.client import get_representations from openpype.lib import config -from openpype.pipeline import load, AvalonMongoDB, Anatomy +from openpype.pipeline import load, Anatomy from openpype import resources, style from openpype.lib.delivery import ( @@ -68,17 +69,13 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): self.setStyleSheet(style.load_stylesheet()) - project = contexts[0]["project"]["name"] - self.anatomy = Anatomy(project) + project_name = contexts[0]["project"]["name"] + self.anatomy = Anatomy(project_name) self._representations = None self.log = log self.currently_uploaded = 0 - self.dbcon = AvalonMongoDB() - self.dbcon.Session["AVALON_PROJECT"] = project - self.dbcon.install() - - self._set_representations(contexts) + self._set_representations(project_name, contexts) dropdown = QtWidgets.QComboBox() self.templates = self._get_templates(self.anatomy) @@ -238,13 +235,12 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): return templates - def _set_representations(self, contexts): + def _set_representations(self, project_name, contexts): version_ids = [context["version"]["_id"] for context in contexts] - repres = list(self.dbcon.find({ - "type": "representation", - "parent": {"$in": version_ids} - })) + repres = list(get_representations( + project_name, version_ids=version_ids + )) self._representations = repres From 3f3ae1fd7dabd3908543ec0fb7d8ab66b0e8f9a3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 10:58:44 +0200 Subject: [PATCH 0958/1227] use query functions in collect anatomy instance data --- .../publish/collect_anatomy_instance_data.py | 78 ++++++------------- 1 file changed, 24 insertions(+), 54 deletions(-) diff --git a/openpype/plugins/publish/collect_anatomy_instance_data.py b/openpype/plugins/publish/collect_anatomy_instance_data.py index 6a6ea170b5..c75534cf83 100644 --- a/openpype/plugins/publish/collect_anatomy_instance_data.py +++ b/openpype/plugins/publish/collect_anatomy_instance_data.py @@ -27,6 +27,11 @@ import collections import pyblish.api +from openpype.client import ( + get_assets, + get_subsets, + get_last_versions +) from openpype.pipeline import legacy_io @@ -44,13 +49,14 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): def process(self, context): self.log.info("Collecting anatomy data for all instances.") - self.fill_missing_asset_docs(context) - self.fill_latest_versions(context) + project_name = legacy_io.active_project() + self.fill_missing_asset_docs(context, project_name) + self.fill_latest_versions(context, project_name) self.fill_anatomy_data(context) self.log.info("Anatomy Data collection finished.") - def fill_missing_asset_docs(self, context): + def fill_missing_asset_docs(self, context, project_name): self.log.debug("Qeurying asset documents for instances.") context_asset_doc = context.data.get("assetEntity") @@ -84,10 +90,8 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): self.log.debug("Querying asset documents with names: {}".format( ", ".join(["\"{}\"".format(name) for name in asset_names]) )) - asset_docs = legacy_io.find({ - "type": "asset", - "name": {"$in": asset_names} - }) + + asset_docs = get_assets(project_name, asset_names=asset_names) asset_docs_by_name = { asset_doc["name"]: asset_doc for asset_doc in asset_docs @@ -111,7 +115,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): "Not found asset documents with names \"{}\"." ).format(joined_asset_names)) - def fill_latest_versions(self, context): + def fill_latest_versions(self, context, project_name): """Try to find latest version for each instance's subset. Key "latestVersion" is always set to latest version or `None`. @@ -126,7 +130,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): self.log.debug("Qeurying latest versions for instances.") hierarchy = {} - subset_filters = [] + names_by_asset_ids = collections.defaultdict(set) for instance in context: # Make sure `"latestVersion"` key is set latest_version = instance.data.get("latestVersion") @@ -147,67 +151,33 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): if subset_name not in hierarchy[asset_id]: hierarchy[asset_id][subset_name] = [] hierarchy[asset_id][subset_name].append(instance) - subset_filters.append({ - "parent": asset_id, - "name": subset_name - }) + names_by_asset_ids[asset_id].add(subset_name) subset_docs = [] - if subset_filters: - subset_docs = list(legacy_io.find({ - "type": "subset", - "$or": subset_filters - })) + if names_by_asset_ids: + subset_docs = list(get_subsets( + project_name, names_by_asset_ids=names_by_asset_ids + )) subset_ids = [ subset_doc["_id"] for subset_doc in subset_docs ] - last_version_by_subset_id = self._query_last_versions(subset_ids) + last_version_docs_by_subset_id = get_last_versions( + project_name, subset_ids, fields=["name"] + ) for subset_doc in subset_docs: subset_id = subset_doc["_id"] - last_version = last_version_by_subset_id.get(subset_id) - if last_version is None: + last_version_doc = last_version_docs_by_subset_id.get(subset_id) + if last_version_docs_by_subset_id is None: continue asset_id = subset_doc["parent"] subset_name = subset_doc["name"] _instances = hierarchy[asset_id][subset_name] for _instance in _instances: - _instance.data["latestVersion"] = last_version - - def _query_last_versions(self, subset_ids): - """Retrieve all latest versions for entered subset_ids. - - Args: - subset_ids (list): List of subset ids with type `ObjectId`. - - Returns: - dict: Key is subset id and value is last version name. - """ - _pipeline = [ - # Find all versions of those subsets - {"$match": { - "type": "version", - "parent": {"$in": subset_ids} - }}, - # Sorting versions all together - {"$sort": {"name": 1}}, - # Group them by "parent", but only take the last - {"$group": { - "_id": "$parent", - "_version_id": {"$last": "$_id"}, - "name": {"$last": "$name"} - }} - ] - - last_version_by_subset_id = {} - for doc in legacy_io.aggregate(_pipeline): - subset_id = doc["_id"] - last_version_by_subset_id[subset_id] = doc["name"] - - return last_version_by_subset_id + _instance.data["latestVersion"] = last_version_doc["name"] def fill_anatomy_data(self, context): self.log.debug("Storing anatomy data to instance data.") From d1e7ae25d4e97abd2734ecf5e86d0ce89f6a09e5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 11:35:44 +0200 Subject: [PATCH 0959/1227] added function to receive archived representations --- openpype/client/__init__.py | 2 + openpype/client/entities.py | 146 +++++++++++++++++++++++++++--------- 2 files changed, 113 insertions(+), 35 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index e3b4ef5132..6094b4e0ab 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -29,6 +29,7 @@ from .entities import ( get_representations, get_representation_parents, get_representations_parents, + get_archived_representations, get_thumbnail, get_thumbnails, @@ -66,6 +67,7 @@ __all__ = ( "get_representations", "get_representation_parents", "get_representations_parents", + "get_archived_representations", "get_thumbnail", "get_thumbnails", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index aed465c46f..9d425810b9 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -918,7 +918,7 @@ def get_representation_by_id(project_name, representation_id, fields=None): if not representation_id: return None - repre_types = ["representation", "archived_representations"] + repre_types = ["representation", "archived_representation"] query_filter = { "type": {"$in": repre_types} } @@ -962,43 +962,26 @@ def get_representation_by_name( return conn.find_one(query_filter, _prepare_fields(fields)) -def get_representations( +def _get_representations( project_name, - representation_ids=None, - representation_names=None, - version_ids=None, - extensions=None, - names_by_version_ids=None, - archived=False, - fields=None + representation_ids, + representation_names, + version_ids, + extensions, + names_by_version_ids, + standard, + archived, + fields ): - """Representaion entities data from one project filtered by filters. - - Filters are additive (all conditions must pass to return subset). - - Args: - project_name (str): Name of project where to look for queried entities. - representation_ids (list[str|ObjectId]): Representation ids used as - filter. Filter ignored if 'None' is passed. - representation_names (list[str]): Representations names used as filter. - Filter ignored if 'None' is passed. - version_ids (list[str]): Subset ids used as parent filter. Filter - ignored if 'None' is passed. - extensions (list[str]): Filter by extension of main representation - file (without dot). - names_by_version_ids (dict[ObjectId, list[str]]): Complex filtering - using version ids and list of names under the version. - archived (bool): Output will also contain archived representations. - fields (list[str]): Fields that should be returned. All fields are - returned if 'None' is passed. - - Returns: - Cursor: Iterable cursor yielding all matching representations. - """ - - repre_types = ["representation"] + repre_types = [] + if standard: + repre_types.append("representation") if archived: - repre_types.append("archived_representations") + repre_types.append("archived_representation") + + if not repre_types: + return [] + if len(repre_types) == 1: query_filter = {"type": repre_types[0]} else: @@ -1043,6 +1026,99 @@ def get_representations( return conn.find(query_filter, _prepare_fields(fields)) +def get_representations( + project_name, + representation_ids=None, + representation_names=None, + version_ids=None, + extensions=None, + names_by_version_ids=None, + archived=False, + standard=True, + fields=None +): + """Representaion entities data from one project filtered by filters. + + Filters are additive (all conditions must pass to return subset). + + Args: + project_name (str): Name of project where to look for queried entities. + representation_ids (list[str|ObjectId]): Representation ids used as + filter. Filter ignored if 'None' is passed. + representation_names (list[str]): Representations names used as filter. + Filter ignored if 'None' is passed. + version_ids (list[str]): Subset ids used as parent filter. Filter + ignored if 'None' is passed. + extensions (list[str]): Filter by extension of main representation + file (without dot). + names_by_version_ids (dict[ObjectId, list[str]]): Complex filtering + using version ids and list of names under the version. + archived (bool): Output will also contain archived representations. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Iterable cursor yielding all matching representations. + """ + + return _get_representations( + project_name=project_name, + representation_ids=representation_ids, + representation_names=representation_names, + version_ids=version_ids, + extensions=extensions, + names_by_version_ids=names_by_version_ids, + standard=True, + archived=archived, + fields=fields + ) + + +def get_archived_representations( + project_name, + representation_ids=None, + representation_names=None, + version_ids=None, + extensions=None, + names_by_version_ids=None, + fields=None +): + """Archived representaion entities data from project with applied filters. + + Filters are additive (all conditions must pass to return subset). + + Args: + project_name (str): Name of project where to look for queried entities. + representation_ids (list[str|ObjectId]): Representation ids used as + filter. Filter ignored if 'None' is passed. + representation_names (list[str]): Representations names used as filter. + Filter ignored if 'None' is passed. + version_ids (list[str]): Subset ids used as parent filter. Filter + ignored if 'None' is passed. + extensions (list[str]): Filter by extension of main representation + file (without dot). + names_by_version_ids (dict[ObjectId, list[str]]): Complex filtering + using version ids and list of names under the version. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Iterable cursor yielding all matching representations. + """ + + return _get_representations( + project_name=project_name, + representation_ids=representation_ids, + representation_names=representation_names, + version_ids=version_ids, + extensions=extensions, + names_by_version_ids=names_by_version_ids, + standard=False, + archived=True, + fields=fields + ) + + def get_representations_parents(project_name, representations): """Prepare parents of representation entities. From 12d23347fc20409900d9ac032852765e38b6a842 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 11:45:52 +0200 Subject: [PATCH 0960/1227] integrate hero version use query functions --- .../plugins/publish/integrate_hero_version.py | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/openpype/plugins/publish/integrate_hero_version.py b/openpype/plugins/publish/integrate_hero_version.py index a706b653c4..5f97a9bd41 100644 --- a/openpype/plugins/publish/integrate_hero_version.py +++ b/openpype/plugins/publish/integrate_hero_version.py @@ -8,6 +8,12 @@ from bson.objectid import ObjectId from pymongo import InsertOne, ReplaceOne import pyblish.api +from openpype.client import ( + get_version_by_id, + get_hero_version_by_subset_id, + get_archived_representations, + get_representations, +) from openpype.lib import ( create_hard_link, filter_profiles @@ -85,9 +91,13 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): hero_template )) - self.integrate_instance(instance, template_key, hero_template) + self.integrate_instance( + instance, project_name, template_key, hero_template + ) - def integrate_instance(self, instance, template_key, hero_template): + def integrate_instance( + self, instance, project_name, template_key, hero_template + ): anatomy = instance.context.data["anatomy"] published_repres = instance.data["published_representations"] hero_publish_dir = self.get_publish_dir(instance, template_key) @@ -118,8 +128,8 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): "Published version entity was not sent in representation data." " Querying entity from database." )) - src_version_entity = ( - self.version_from_representations(published_repres) + src_version_entity = self.version_from_representations( + project_name, published_repres ) if not src_version_entity: @@ -170,8 +180,8 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): other_file_paths_mapping.append((file_path, dst_filepath)) # Current version - old_version, old_repres = ( - self.current_hero_ents(src_version_entity) + old_version, old_repres = self.current_hero_ents( + project_name, src_version_entity ) old_repres_by_name = { @@ -223,11 +233,11 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): if old_repres_by_name: old_repres_to_delete = old_repres_by_name - archived_repres = list(legacy_io.find({ + archived_repres = list(get_archived_representations( + project_name, # Check what is type of archived representation - "type": "archived_repsentation", - "parent": new_version_id - })) + version_ids=[new_version_id] + )) archived_repres_by_name = {} for repre in archived_repres: repre_name_low = repre["name"].lower() @@ -586,25 +596,23 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): shutil.copy(src_path, dst_path) - def version_from_representations(self, repres): + def version_from_representations(self, project_name, repres): for repre in repres: - version = legacy_io.find_one({"_id": repre["parent"]}) + version = get_version_by_id(project_name, repre["parent"]) if version: return version - def current_hero_ents(self, version): - hero_version = legacy_io.find_one({ - "parent": version["parent"], - "type": "hero_version" - }) + def current_hero_ents(self, project_name, version): + hero_version = get_hero_version_by_subset_id( + project_name, version["parent"] + ) if not hero_version: return (None, []) - hero_repres = list(legacy_io.find({ - "parent": hero_version["_id"], - "type": "representation" - })) + hero_repres = list(get_representations( + project_name, version_ids=[hero_version["_id"]] + )) return (hero_version, hero_repres) def _update_path(self, anatomy, path, src_file, dst_file): From f0f0a87c5d7e81c226dd22fba6589040cf62bfff Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 11:47:01 +0200 Subject: [PATCH 0961/1227] global plugins are using query functions --- .../publish/collect_avalon_entities.py | 13 +++---- .../publish/collect_scene_loaded_versions.py | 33 +++++++++-------- .../publish/extract_hierarchy_avalon.py | 36 ++++++++++++------- .../plugins/publish/integrate_thumbnail.py | 3 +- .../publish/validate_editorial_asset_name.py | 7 ++-- 5 files changed, 53 insertions(+), 39 deletions(-) diff --git a/openpype/plugins/publish/collect_avalon_entities.py b/openpype/plugins/publish/collect_avalon_entities.py index 3e7843407f..6cd0d136e8 100644 --- a/openpype/plugins/publish/collect_avalon_entities.py +++ b/openpype/plugins/publish/collect_avalon_entities.py @@ -10,6 +10,7 @@ Provides: import pyblish.api +from openpype.client import get_project, get_asset_by_name from openpype.pipeline import legacy_io @@ -25,10 +26,7 @@ class CollectAvalonEntities(pyblish.api.ContextPlugin): asset_name = legacy_io.Session["AVALON_ASSET"] task_name = legacy_io.Session["AVALON_TASK"] - project_entity = legacy_io.find_one({ - "type": "project", - "name": project_name - }) + project_entity = get_project(project_name) assert project_entity, ( "Project '{0}' was not found." ).format(project_name) @@ -39,11 +37,8 @@ class CollectAvalonEntities(pyblish.api.ContextPlugin): if not asset_name: self.log.info("Context is not set. Can't collect global data.") return - asset_entity = legacy_io.find_one({ - "type": "asset", - "name": asset_name, - "parent": project_entity["_id"] - }) + + asset_entity = get_asset_by_name(project_name, asset_name) assert asset_entity, ( "No asset found by the name '{0}' in project '{1}'" ).format(asset_name, project_name) diff --git a/openpype/plugins/publish/collect_scene_loaded_versions.py b/openpype/plugins/publish/collect_scene_loaded_versions.py index bb34e3ce31..5ff2b46e3b 100644 --- a/openpype/plugins/publish/collect_scene_loaded_versions.py +++ b/openpype/plugins/publish/collect_scene_loaded_versions.py @@ -1,7 +1,6 @@ -from bson.objectid import ObjectId - import pyblish.api +from openpype.client import get_representations from openpype.pipeline import ( registered_host, legacy_io, @@ -39,23 +38,29 @@ class CollectSceneLoadedVersions(pyblish.api.ContextPlugin): return loaded_versions = [] - _containers = list(host.ls()) - _repr_ids = [ObjectId(c["representation"]) for c in _containers] - repre_docs = legacy_io.find( - {"_id": {"$in": _repr_ids}}, - projection={"_id": 1, "parent": 1} + containers = list(host.ls()) + repre_ids = { + container["representation"] + for container in containers + } + + project_name = legacy_io.active_project() + repre_docs = get_representations( + project_name, + representation_ids=repre_ids, + fields=["_id", "parent"] ) - version_by_repr = { - str(doc["_id"]): doc["parent"] + repre_doc_by_str_id = { + str(doc["_id"]): doc for doc in repre_docs } # QUESTION should we add same representation id when loaded multiple # times? - for con in _containers: + for con in containers: repre_id = con["representation"] - version_id = version_by_repr.get(repre_id) - if version_id is None: + repre_doc = repre_doc_by_str_id.get(repre_id) + if repre_doc is None: self.log.warning(( "Skipping container," " did not find representation document. {}" @@ -66,8 +71,8 @@ class CollectSceneLoadedVersions(pyblish.api.ContextPlugin): # may have more then one representation that are same version version = { "subsetName": con["name"], - "representation": ObjectId(repre_id), - "version": version_id, + "representation": repre_doc["_id"], + "version": repre_doc["parent"], } loaded_versions.append(version) diff --git a/openpype/plugins/publish/extract_hierarchy_avalon.py b/openpype/plugins/publish/extract_hierarchy_avalon.py index 1f7ce839ed..8d447ba595 100644 --- a/openpype/plugins/publish/extract_hierarchy_avalon.py +++ b/openpype/plugins/publish/extract_hierarchy_avalon.py @@ -1,5 +1,11 @@ from copy import deepcopy import pyblish.api +from openpype.client import ( + get_project, + get_asset_by_id, + get_asset_by_name, + get_archived_assets +) from openpype.pipeline import legacy_io @@ -19,14 +25,14 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): if not legacy_io.Session: legacy_io.install() + project_name = legacy_io.active_project() hierarchy_context = self._get_active_assets(context) self.log.debug("__ hierarchy_context: {}".format(hierarchy_context)) self.project = None - self.import_to_avalon(hierarchy_context) + self.import_to_avalon(project_name, hierarchy_context) - - def import_to_avalon(self, input_data, parent=None): + def import_to_avalon(self, project_name, input_data, parent=None): for name in input_data: self.log.info("input_data[name]: {}".format(input_data[name])) entity_data = input_data[name] @@ -62,7 +68,7 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): update_data = True # Process project if entity_type.lower() == "project": - entity = legacy_io.find_one({"type": "project"}) + entity = get_project(project_name) # TODO: should be in validator? assert (entity is not None), "Did not find project in DB" @@ -79,7 +85,7 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): ) # Else process assset else: - entity = legacy_io.find_one({"type": "asset", "name": name}) + entity = get_asset_by_name(project_name, name) if entity: # Do not override data, only update cur_entity_data = entity.get("data") or {} @@ -103,10 +109,10 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): # Skip updating data update_data = False - archived_entities = legacy_io.find({ - "type": "archived_asset", - "name": name - }) + archived_entities = get_archived_assets( + project_name, + asset_names=[name] + ) unarchive_entity = None for archived_entity in archived_entities: archived_parents = ( @@ -120,7 +126,9 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): if unarchive_entity is None: # Create entity if doesn"t exist - entity = self.create_avalon_asset(name, data) + entity = self.create_avalon_asset( + project_name, name, data + ) else: # Unarchive if entity was archived entity = self.unarchive_entity(unarchive_entity, data) @@ -133,7 +141,9 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): ) if "childs" in entity_data: - self.import_to_avalon(entity_data["childs"], entity) + self.import_to_avalon( + project_name, entity_data["childs"], entity + ) def unarchive_entity(self, entity, data): # Unarchived asset should not use same data @@ -151,7 +161,7 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): ) return new_entity - def create_avalon_asset(self, name, data): + def create_avalon_asset(self, project_name, name, data): item = { "schema": "openpype:asset-3.0", "name": name, @@ -162,7 +172,7 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): self.log.debug("Creating asset: {}".format(item)) entity_id = legacy_io.insert_one(item).inserted_id - return legacy_io.find_one({"_id": entity_id}) + return get_asset_by_id(project_name, entity_id) def _get_active_assets(self, context): """ Returns only asset dictionary. diff --git a/openpype/plugins/publish/integrate_thumbnail.py b/openpype/plugins/publish/integrate_thumbnail.py index 5d6fc561ea..fd50858a91 100644 --- a/openpype/plugins/publish/integrate_thumbnail.py +++ b/openpype/plugins/publish/integrate_thumbnail.py @@ -8,6 +8,7 @@ import six import pyblish.api from bson.objectid import ObjectId +from openpype.client import get_version_by_id from openpype.pipeline import legacy_io @@ -70,7 +71,7 @@ class IntegrateThumbnails(pyblish.api.InstancePlugin): thumbnail_template = anatomy.templates["publish"]["thumbnail"] - version = legacy_io.find_one({"_id": thumb_repre["parent"]}) + version = get_version_by_id(project_name, thumb_repre["parent"]) if not version: raise AssertionError( "There does not exist version with id {}".format( diff --git a/openpype/plugins/publish/validate_editorial_asset_name.py b/openpype/plugins/publish/validate_editorial_asset_name.py index f9cdaebf0c..702e87b58d 100644 --- a/openpype/plugins/publish/validate_editorial_asset_name.py +++ b/openpype/plugins/publish/validate_editorial_asset_name.py @@ -3,6 +3,7 @@ from pprint import pformat import pyblish.api from openpype.pipeline import legacy_io +from openpype.client import get_assets class ValidateEditorialAssetName(pyblish.api.ContextPlugin): @@ -29,8 +30,10 @@ class ValidateEditorialAssetName(pyblish.api.ContextPlugin): if not legacy_io.Session: legacy_io.install() - db_assets = list(legacy_io.find( - {"type": "asset"}, {"name": 1, "data.parents": 1})) + project_name = legacy_io.active_project() + db_assets = list(get_assets( + project_name, fields=["name", "data.parents"] + )) self.log.debug("__ db_assets: {}".format(db_assets)) asset_db_docs = { From 646e9edd9b11fe2d6095d9c511e1e61c096b19c4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 11:49:28 +0200 Subject: [PATCH 0962/1227] integrate asset new is using query functions --- openpype/plugins/publish/integrate_new.py | 53 +++++++++++------------ 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 4c14c17dae..c264f0fd59 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -16,6 +16,14 @@ from pymongo import DeleteOne, InsertOne import pyblish.api import openpype.api +from openpype.client import ( + get_asset_by_name, + get_subset_by_id, + get_version_by_id, + get_version_by_name, + get_representations, + get_archived_representations, +) from openpype.lib.profiles_filtering import filter_profiles from openpype.lib import ( prepare_template_data, @@ -201,6 +209,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): context = instance.context project_entity = instance.data["projectEntity"] + project_name = project_entity["name"] context_asset_name = None context_asset_doc = context.data.get("assetEntity") @@ -210,11 +219,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): asset_name = instance.data["asset"] asset_entity = instance.data.get("assetEntity") if not asset_entity or asset_entity["name"] != context_asset_name: - asset_entity = legacy_io.find_one({ - "type": "asset", - "name": asset_name, - "parent": project_entity["_id"] - }) + asset_entity = get_asset_by_name(project_name, asset_name) assert asset_entity, ( "No asset found by the name \"{0}\" in project \"{1}\"" ).format(asset_name, project_entity["name"]) @@ -270,7 +275,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "Establishing staging directory @ {0}".format(stagingdir) ) - subset = self.get_subset(asset_entity, instance) + subset = self.get_subset(project_name, asset_entity, instance) instance.data["subsetEntity"] = subset version_number = instance.data["version"] @@ -297,11 +302,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): for _repre in repres ] - existing_version = legacy_io.find_one({ - 'type': 'version', - 'parent': subset["_id"], - 'name': version_number - }) + existing_version = get_version_by_name( + project_name, version_number, subset["_id"] + ) if existing_version is None: version_id = legacy_io.insert_one(version).inserted_id @@ -322,10 +325,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): version_id = existing_version['_id'] # Find representations of existing version and archive them - current_repres = list(legacy_io.find({ - "type": "representation", - "parent": version_id - })) + current_repres = list(get_representations( + project_name, version_ids=[version_id] + )) bulk_writes = [] for repre in current_repres: if append_repres: @@ -345,18 +347,17 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # bulk updates if bulk_writes: - project_name = legacy_io.Session["AVALON_PROJECT"] legacy_io.database[project_name].bulk_write( bulk_writes ) - version = legacy_io.find_one({"_id": version_id}) + version = get_version_by_id(project_name, version_id) instance.data["versionEntity"] = version - existing_repres = list(legacy_io.find({ - "parent": version_id, - "type": "archived_representation" - })) + existing_repres = list(get_archived_representations( + project_name, + version_ids=[version_id] + )) instance.data['version'] = version['name'] @@ -792,13 +793,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): create_hard_link(src, dst) - def get_subset(self, asset, instance): + def get_subset(self, project_name, asset, instance): subset_name = instance.data["subset"] - subset = legacy_io.find_one({ - "type": "subset", - "parent": asset["_id"], - "name": subset_name - }) + subset = get_subset_by_name(project_name, subset_name, asset["_id"]) if subset is None: self.log.info("Subset '%s' not found, creating ..." % subset_name) @@ -825,7 +822,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "parent": asset["_id"] }).inserted_id - subset = legacy_io.find_one({"_id": _id}) + subset = get_subset_by_id(project_name, _id) # QUESTION Why is changing of group and updating it's # families in 'get_subset'? From 26bdde12d0d81bf3c5bd60ec69659716a904d0e9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 11:54:17 +0200 Subject: [PATCH 0963/1227] add missing import --- openpype/plugins/publish/integrate_new.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index c264f0fd59..f870220421 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -19,6 +19,7 @@ import openpype.api from openpype.client import ( get_asset_by_name, get_subset_by_id, + get_subset_by_name, get_version_by_id, get_version_by_name, get_representations, From 9bfff7a2cc67e704045e31a5f041677ea89d2514 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 1 Jul 2022 15:23:25 +0300 Subject: [PATCH 0964/1227] Fix asset data key. --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 5 ++--- openpype/hosts/maya/plugins/publish/extract_thumbnail.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index fbf4035505..4c3224e1b4 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -58,8 +58,8 @@ class ExtractPlayblast(openpype.api.Extractor): height_preset = capture_presets["Resolution"]["height"] # Set resolution variables from asset values asset_data = instance.data["assetEntity"]["data"] - asset_width = asset_data.get("width") - asset_height = asset_data.get("height") + asset_width = asset_data.get("resolutionWidth") + asset_height = asset_data.get("resolutionHeight") review_instance_width = instance.data.get("Width") review_instance_height = instance.data.get("Height") preset['camera'] = camera @@ -68,7 +68,6 @@ class ExtractPlayblast(openpype.api.Extractor): # if it is a value other than zero, that value is # used, if not then the asset resolution is # used - if review_instance_width and review_instance_height: preset['width'] = review_instance_width preset['height'] = review_instance_height diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index bc15327aa7..c7d7cf150d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -66,8 +66,8 @@ class ExtractThumbnail(openpype.api.Extractor): height_preset = capture_presets["Resolution"]["height"] # Set resolution variables from asset values asset_data = instance.data["assetEntity"]["data"] - asset_width = asset_data.get("width") - asset_height = asset_data.get("height") + asset_width = asset_data.get("resolutionWidth") + asset_height = asset_data.get("resolutionHeight") review_instance_width = instance.data.get("Width") review_instance_height = instance.data.get("Height") # Tests if project resolution is set, From 0537317fcf96dabb75679fbb9cd9b9f0d0d7ac8c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 1 Jul 2022 15:54:33 +0200 Subject: [PATCH 0965/1227] Shush the hound --- openpype/hosts/maya/plugins/publish/extract_pointcache.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index a7ba5b3745..bcf96eb128 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -133,4 +133,3 @@ class ExtractAnimation(ExtractAlembic): fullPath=True) or [] return nodes, roots - From 45cc1ed9ae9e5ad1a309146d9c5ae1903a34b3cc Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Fri, 1 Jul 2022 16:37:01 +0200 Subject: [PATCH 0966/1227] bugfix: delete_old_version use settings to process ftrack logic. --- openpype/plugins/load/delete_old_versions.py | 65 ++++++++++---------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index 7465f53855..f626775180 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -4,13 +4,14 @@ import uuid import clique from pymongo import UpdateOne -import ftrack_api +import importlib import qargparse from Qt import QtWidgets, QtCore from openpype import style from openpype.pipeline import load, AvalonMongoDB, Anatomy from openpype.lib import StringTemplate +from openpype.settings import get_system_settings class DeleteOldVersions(load.SubsetLoaderPlugin): @@ -370,37 +371,39 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): self.dbcon.uninstall() - # Set attribute `is_published` to `False` on ftrack AssetVersions - session = ftrack_api.Session() - query = ( - "AssetVersion where asset.parent.id is \"{}\"" - " and asset.name is \"{}\"" - " and version is \"{}\"" - ) - for v in data["versions"]: - try: - ftrack_version = session.query( - query.format( - data["asset"]["data"]["ftrackId"], - data["subset"]["name"], - v["name"] - ) - ).one() - except ftrack_api.exception.NoResultFoundError: - continue - - ftrack_version["is_published"] = False - - try: - session.commit() - - except Exception: - msg = ( - "Could not set `is_published` attribute to `False`" - " for selected AssetVersions." + if get_system_settings()["modules"]["ftrack"]["enabled"]: + # Set attribute `is_published` to `False` on ftrack AssetVersions + ftrack_api = importlib.import_module("ftrack_api") + session = ftrack_api.Session() + query = ( + "AssetVersion where asset.parent.id is \"{}\"" + " and asset.name is \"{}\"" + " and version is \"{}\"" ) - self.log.error(msg) - self.message(msg) + for v in data["versions"]: + try: + ftrack_version = session.query( + query.format( + data["asset"]["data"]["ftrackId"], + data["subset"]["name"], + v["name"] + ) + ).one() + except ftrack_api.exception.NoResultFoundError: + continue + + ftrack_version["is_published"] = False + + try: + session.commit() + + except Exception: + msg = ( + "Could not set `is_published` attribute to `False`" + " for selected AssetVersions." + ) + self.log.error(msg) + self.message(msg) return size From 7122675350c58d2ec02fe6a9ceab7f4f86a8fcfb Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 2 Jul 2022 04:00:05 +0000 Subject: [PATCH 0967/1227] [Automated] Bump version --- CHANGELOG.md | 46 +++++++++++++++++++++++++++------------------ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 438a563391..9b5d40a52f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,36 @@ # Changelog -## [3.12.1-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.12.1-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.0...HEAD) +### 📖 Documentation + +- Docs: Added minimal permissions for MongoDB [\#3441](https://github.com/pypeclub/OpenPype/pull/3441) + +**🆕 New features** + +- Maya: Add VDB to Arnold loader [\#3433](https://github.com/pypeclub/OpenPype/pull/3433) + **🚀 Enhancements** +- Blender: Bugfix - Set fps properly on open [\#3426](https://github.com/pypeclub/OpenPype/pull/3426) - Blender: pre pyside install for all platforms [\#3400](https://github.com/pypeclub/OpenPype/pull/3400) +**🐛 Bug fixes** + +- Nuke: prerender reviewable fails [\#3450](https://github.com/pypeclub/OpenPype/pull/3450) +- Maya: fix hashing in Python 3 for tile rendering [\#3447](https://github.com/pypeclub/OpenPype/pull/3447) +- LogViewer: Escape html characters in log message [\#3443](https://github.com/pypeclub/OpenPype/pull/3443) +- Nuke: Slate frame is integrated [\#3427](https://github.com/pypeclub/OpenPype/pull/3427) + +**🔀 Refactored code** + +- Clockify: Use query functions in clockify actions [\#3458](https://github.com/pypeclub/OpenPype/pull/3458) +- General: Use query functions in rest api calls [\#3457](https://github.com/pypeclub/OpenPype/pull/3457) +- General: Use Anatomy after move to pipeline [\#3436](https://github.com/pypeclub/OpenPype/pull/3436) +- General: Anatomy moved to pipeline [\#3435](https://github.com/pypeclub/OpenPype/pull/3435) + ## [3.12.0](https://github.com/pypeclub/OpenPype/tree/3.12.0) (2022-06-28) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.12.0-nightly.3...3.12.0) @@ -24,7 +47,6 @@ - General: Add ability to change user value for templates [\#3366](https://github.com/pypeclub/OpenPype/pull/3366) - Hosts: More options for in-host callbacks [\#3357](https://github.com/pypeclub/OpenPype/pull/3357) - Multiverse: expose some settings to GUI [\#3350](https://github.com/pypeclub/OpenPype/pull/3350) -- Maya: Allow more data to be published along camera 🎥 [\#3304](https://github.com/pypeclub/OpenPype/pull/3304) **🐛 Bug fixes** @@ -57,7 +79,6 @@ - AfterEffects: Use client query functions [\#3374](https://github.com/pypeclub/OpenPype/pull/3374) - TVPaint: Use client query functions [\#3340](https://github.com/pypeclub/OpenPype/pull/3340) - Ftrack: Use client query functions [\#3339](https://github.com/pypeclub/OpenPype/pull/3339) -- Webpublisher: Use client query functions [\#3333](https://github.com/pypeclub/OpenPype/pull/3333) - Standalone Publisher: Use client query functions [\#3330](https://github.com/pypeclub/OpenPype/pull/3330) **Merged pull requests:** @@ -93,17 +114,15 @@ - nuke: adding extract thumbnail settings 3.10 [\#3347](https://github.com/pypeclub/OpenPype/pull/3347) - General: Fix last version function [\#3345](https://github.com/pypeclub/OpenPype/pull/3345) - Deadline: added OPENPYPE\_MONGO to filter [\#3336](https://github.com/pypeclub/OpenPype/pull/3336) -- Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) + +**🔀 Refactored code** + +- Webpublisher: Use client query functions [\#3333](https://github.com/pypeclub/OpenPype/pull/3333) ## [3.11.0](https://github.com/pypeclub/OpenPype/tree/3.11.0) (2022-06-17) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.11.0-nightly.4...3.11.0) -### 📖 Documentation - -- Documentation: Add app key to template documentation [\#3299](https://github.com/pypeclub/OpenPype/pull/3299) -- doc: adding royal render and multiverse to the web site [\#3285](https://github.com/pypeclub/OpenPype/pull/3285) - **🚀 Enhancements** - Settings: Settings can be extracted from UI [\#3323](https://github.com/pypeclub/OpenPype/pull/3323) @@ -111,8 +130,6 @@ - Ftrack: Action to easily create daily review session [\#3310](https://github.com/pypeclub/OpenPype/pull/3310) - TVPaint: Extractor use mark in/out range to render [\#3309](https://github.com/pypeclub/OpenPype/pull/3309) - Ftrack: Delivery action can work on ReviewSessions [\#3307](https://github.com/pypeclub/OpenPype/pull/3307) -- Maya: Look assigner UI improvements [\#3298](https://github.com/pypeclub/OpenPype/pull/3298) -- Ftrack: Action to transfer values of hierarchical attributes [\#3284](https://github.com/pypeclub/OpenPype/pull/3284) **🐛 Bug fixes** @@ -120,17 +137,10 @@ - Houdini: Fix Houdini VDB manage update wrong file attribute name [\#3322](https://github.com/pypeclub/OpenPype/pull/3322) - Nuke: anatomy compatibility issue hacks [\#3321](https://github.com/pypeclub/OpenPype/pull/3321) - hiero: otio p3 compatibility issue - metadata on effect use update 3.11 [\#3314](https://github.com/pypeclub/OpenPype/pull/3314) -- General: Vendorized modules for Python 2 and update poetry lock [\#3305](https://github.com/pypeclub/OpenPype/pull/3305) -- Fix - added local targets to install host [\#3303](https://github.com/pypeclub/OpenPype/pull/3303) -- Settings: Add missing default settings for nuke gizmo [\#3301](https://github.com/pypeclub/OpenPype/pull/3301) -- Maya: Fix swaped width and height in reviews [\#3300](https://github.com/pypeclub/OpenPype/pull/3300) -- Maya: point cache publish handles Maya instances [\#3297](https://github.com/pypeclub/OpenPype/pull/3297) -- Global: extract review slate issues [\#3286](https://github.com/pypeclub/OpenPype/pull/3286) **🔀 Refactored code** - Blender: Use client query functions [\#3331](https://github.com/pypeclub/OpenPype/pull/3331) -- General: Define query functions [\#3288](https://github.com/pypeclub/OpenPype/pull/3288) **Merged pull requests:** diff --git a/openpype/version.py b/openpype/version.py index 633b0b4f33..92cdcf9fdd 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.12.1-nightly.1" +__version__ = "3.12.1-nightly.2" diff --git a/pyproject.toml b/pyproject.toml index 26a7b4bf1e..401b24243b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.12.1-nightly.1" # OpenPype +version = "3.12.1-nightly.2" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From bcdcf5b6af6ebb65c4bf803a30c4ba2afffca5fa Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Sat, 2 Jul 2022 22:16:58 +0200 Subject: [PATCH 0968/1227] resolve: removing small blockers --- openpype/hosts/resolve/api/lib.py | 13 ++-- openpype/hosts/resolve/api/plugin.py | 2 +- .../plugins/create/create_shot_clip.py | 68 ++++++++++--------- .../hosts/resolve/plugins/load/load_clip.py | 2 +- .../plugins/publish/precollect_workfile.py | 3 +- 5 files changed, 45 insertions(+), 43 deletions(-) diff --git a/openpype/hosts/resolve/api/lib.py b/openpype/hosts/resolve/api/lib.py index c4717bd370..93ccdaf812 100644 --- a/openpype/hosts/resolve/api/lib.py +++ b/openpype/hosts/resolve/api/lib.py @@ -319,14 +319,13 @@ def get_current_timeline_items( selected_track_count = timeline.GetTrackCount(track_type) # loop all tracks and get items - _clips = dict() + _clips = {} for track_index in range(1, (int(selected_track_count) + 1)): _track_name = timeline.GetTrackName(track_type, track_index) # filter out all unmathed track names - if track_name: - if _track_name not in track_name: - continue + if track_name and _track_name not in track_name: + continue timeline_items = timeline.GetItemListInTrack( track_type, track_index) @@ -348,12 +347,8 @@ def get_current_timeline_items( "index": clip_index } ti_color = ti.GetClipColor() - if filter is True: - if selecting_color in ti_color: - selected_clips.append(data) - else: + if filter and selecting_color in ti_color or not filter: selected_clips.append(data) - return selected_clips diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 8e1436021c..49b478fb3b 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -506,7 +506,7 @@ class Creator(LegacyCreator): super(Creator, self).__init__(*args, **kwargs) from openpype.api import get_current_project_settings resolve_p_settings = get_current_project_settings().get("resolve") - self.presets = dict() + self.presets = {} if resolve_p_settings: self.presets = resolve_p_settings["create"].get( self.__class__.__name__, {}) diff --git a/openpype/hosts/resolve/plugins/create/create_shot_clip.py b/openpype/hosts/resolve/plugins/create/create_shot_clip.py index 62d5557a50..dbf10c5163 100644 --- a/openpype/hosts/resolve/plugins/create/create_shot_clip.py +++ b/openpype/hosts/resolve/plugins/create/create_shot_clip.py @@ -116,12 +116,13 @@ class CreateShotClip(resolve.Creator): "order": 0}, "vSyncTrack": { "value": gui_tracks, # noqa - "type": "QComboBox", - "label": "Hero track", - "target": "ui", - "toolTip": "Select driving track name which should be mastering all others", # noqa - "order": 1} + "type": "QComboBox", + "label": "Hero track", + "target": "ui", + "toolTip": "Select driving track name which should be mastering all others", # noqa + "order": 1 } + } }, "publishSettings": { "type": "section", @@ -172,28 +173,31 @@ class CreateShotClip(resolve.Creator): "target": "ui", "order": 4, "value": { - "workfileFrameStart": { - "value": 1001, - "type": "QSpinBox", - "label": "Workfiles Start Frame", - "target": "tag", - "toolTip": "Set workfile starting frame number", # noqa - "order": 0}, - "handleStart": { - "value": 0, - "type": "QSpinBox", - "label": "Handle start (head)", - "target": "tag", - "toolTip": "Handle at start of clip", # noqa - "order": 1}, - "handleEnd": { - "value": 0, - "type": "QSpinBox", - "label": "Handle end (tail)", - "target": "tag", - "toolTip": "Handle at end of clip", # noqa - "order": 2}, - } + "workfileFrameStart": { + "value": 1001, + "type": "QSpinBox", + "label": "Workfiles Start Frame", + "target": "tag", + "toolTip": "Set workfile starting frame number", # noqa + "order": 0 + }, + "handleStart": { + "value": 0, + "type": "QSpinBox", + "label": "Handle start (head)", + "target": "tag", + "toolTip": "Handle at start of clip", # noqa + "order": 1 + }, + "handleEnd": { + "value": 0, + "type": "QSpinBox", + "label": "Handle end (tail)", + "target": "tag", + "toolTip": "Handle at end of clip", # noqa + "order": 2 + } + } } } @@ -229,8 +233,10 @@ class CreateShotClip(resolve.Creator): v_sync_track = widget.result["vSyncTrack"]["value"] # sort selected trackItems by - sorted_selected_track_items = list() - unsorted_selected_track_items = list() + sorted_selected_track_items = [] + unsorted_selected_track_items = [] + print("_____ selected ______") + print(self.selected) for track_item_data in self.selected: if track_item_data["track"]["name"] in v_sync_track: sorted_selected_track_items.append(track_item_data) @@ -253,10 +259,10 @@ class CreateShotClip(resolve.Creator): "sq_frame_start": sq_frame_start, "sq_markers": sq_markers } - + print(kwargs) for i, track_item_data in enumerate(sorted_selected_track_items): self.rename_index = i - + self.log.info(track_item_data) # convert track item to timeline media pool item track_item = resolve.PublishClip( self, track_item_data, **kwargs).convert() diff --git a/openpype/hosts/resolve/plugins/load/load_clip.py b/openpype/hosts/resolve/plugins/load/load_clip.py index cf88b14e81..567be2be87 100644 --- a/openpype/hosts/resolve/plugins/load/load_clip.py +++ b/openpype/hosts/resolve/plugins/load/load_clip.py @@ -19,7 +19,7 @@ class LoadClip(resolve.TimelineItemLoader): """ families = ["render2d", "source", "plate", "render", "review"] - representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264", ".mov"] + representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264", "mov"] label = "Load as clip" order = -10 diff --git a/openpype/hosts/resolve/plugins/publish/precollect_workfile.py b/openpype/hosts/resolve/plugins/publish/precollect_workfile.py index a58f288770..53e67aee0e 100644 --- a/openpype/hosts/resolve/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/resolve/plugins/publish/precollect_workfile.py @@ -30,7 +30,8 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): "asset": asset, "subset": "{}{}".format(asset, subset.capitalize()), "item": project, - "family": "workfile" + "family": "workfile", + "families": [] } # create instance with workfile From ad9b01c0c7fb1c40b6d56b889a6a7d766390502d Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Mon, 4 Jul 2022 02:45:58 +0300 Subject: [PATCH 0969/1227] Alter final logic and change attribute naming to more pythonic convention. --- openpype/hosts/maya/plugins/create/create_review.py | 4 ++-- .../hosts/maya/plugins/publish/collect_review.py | 4 ++-- .../hosts/maya/plugins/publish/extract_playblast.py | 13 +++++++------ .../hosts/maya/plugins/publish/extract_thumbnail.py | 10 +++++----- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index 9f6721762b..ba51ffa009 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -35,8 +35,8 @@ class CreateReview(plugin.Creator): for key, value in animation_data.items(): data[key] = value - data["Width"] = self.Width - data["Height"] = self.Height + data["review_width"] = self.Width + data["review_height"] = self.Height data["isolate"] = self.isolate data["keepImages"] = self.keepImages data["imagePlane"] = self.imagePlane diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index b0b5ef37e8..eb872c2935 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -71,8 +71,8 @@ class CollectReview(pyblish.api.InstancePlugin): data['handles'] = instance.data.get('handles', None) data['step'] = instance.data['step'] data['fps'] = instance.data['fps'] - data['Width'] = instance.data['Width'] - data['Height'] = instance.data['Height'] + data['review_width'] = instance.data['review_width'] + data['review_height'] = instance.data['review_height'] data["isolate"] = instance.data["isolate"] cmds.setAttr(str(instance) + '.active', 1) self.log.debug('data {}'.format(instance.context[i].data)) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 4c3224e1b4..0b60e01e45 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -60,8 +60,8 @@ class ExtractPlayblast(openpype.api.Extractor): asset_data = instance.data["assetEntity"]["data"] asset_width = asset_data.get("resolutionWidth") asset_height = asset_data.get("resolutionHeight") - review_instance_width = instance.data.get("Width") - review_instance_height = instance.data.get("Height") + review_instance_width = instance.data.get("review_width") + review_instance_height = instance.data.get("review_height") preset['camera'] = camera # Tests if project resolution is set, @@ -71,13 +71,14 @@ class ExtractPlayblast(openpype.api.Extractor): if review_instance_width and review_instance_height: preset['width'] = review_instance_width preset['height'] = review_instance_height - elif asset_width and asset_height: - preset['width'] = asset_width - preset['height'] = asset_height elif width_preset and height_preset: preset['width'] = width_preset preset['height'] = height_preset - + elif asset_width and asset_height: + preset['width'] = asset_width + preset['height'] = asset_height + + preset['start_frame'] = start preset['end_frame'] = end camera_option = preset.get("camera_option", {}) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index c7d7cf150d..2f7e6c5e05 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -68,8 +68,8 @@ class ExtractThumbnail(openpype.api.Extractor): asset_data = instance.data["assetEntity"]["data"] asset_width = asset_data.get("resolutionWidth") asset_height = asset_data.get("resolutionHeight") - review_instance_width = instance.data.get("Width") - review_instance_height = instance.data.get("Height") + review_instance_width = instance.data.get("review_width") + review_instance_height = instance.data.get("review_height") # Tests if project resolution is set, # if it is a value other than zero, that value is # used, if not then the asset resolution is @@ -77,12 +77,12 @@ class ExtractThumbnail(openpype.api.Extractor): if review_instance_width and review_instance_height: preset['width'] = review_instance_width preset['height'] = review_instance_height - elif asset_width and asset_height: - preset['width'] = asset_width - preset['height'] = asset_height elif width_preset and height_preset: preset['width'] = width_preset preset['height'] = height_preset + elif asset_width and asset_height: + preset['width'] = asset_width + preset['height'] = asset_height stagingDir = self.staging_dir(instance) filename = "{0}".format(instance.name) path = os.path.join(stagingDir, filename) From 8307ea8ea204f30937589a6f7f8bc493081861ab Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Mon, 4 Jul 2022 02:47:10 +0300 Subject: [PATCH 0970/1227] Style fix. --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 0b60e01e45..2c6e76e5ad 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -77,8 +77,6 @@ class ExtractPlayblast(openpype.api.Extractor): elif asset_width and asset_height: preset['width'] = asset_width preset['height'] = asset_height - - preset['start_frame'] = start preset['end_frame'] = end camera_option = preset.get("camera_option", {}) From 4e4bf771724376bf6ff2587d77bb1ebb938d793c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 10:37:36 +0200 Subject: [PATCH 0971/1227] use direct import of ftrack_api --- openpype/plugins/load/delete_old_versions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index f626775180..622e6b41b7 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -4,7 +4,6 @@ import uuid import clique from pymongo import UpdateOne -import importlib import qargparse from Qt import QtWidgets, QtCore @@ -373,7 +372,8 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): if get_system_settings()["modules"]["ftrack"]["enabled"]: # Set attribute `is_published` to `False` on ftrack AssetVersions - ftrack_api = importlib.import_module("ftrack_api") + import ftrack_api + session = ftrack_api.Session() query = ( "AssetVersion where asset.parent.id is \"{}\"" From 5baedfc3a068a338603cc5e956e6446b655e9af0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 10:37:57 +0200 Subject: [PATCH 0972/1227] use ftrack module to determine if ftrack is enabled --- openpype/plugins/load/delete_old_versions.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index 622e6b41b7..2a0b7492bc 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -10,7 +10,7 @@ from Qt import QtWidgets, QtCore from openpype import style from openpype.pipeline import load, AvalonMongoDB, Anatomy from openpype.lib import StringTemplate -from openpype.settings import get_system_settings +from openpype.modules import ModulesManager class DeleteOldVersions(load.SubsetLoaderPlugin): @@ -370,7 +370,9 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): self.dbcon.uninstall() - if get_system_settings()["modules"]["ftrack"]["enabled"]: + modules_manager = ModulesManager() + ftrack_module = modules_manager.modules_by_name.get("ftrack") + if ftrack_module and ftrack_module.enabled: # Set attribute `is_published` to `False` on ftrack AssetVersions import ftrack_api From 409362e4ffeb2d269d0a418643764b965ca14d8a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 10:50:40 +0200 Subject: [PATCH 0973/1227] moveed the ftrack logic into separated method --- openpype/plugins/load/delete_old_versions.py | 103 ++++++++++++------- 1 file changed, 67 insertions(+), 36 deletions(-) diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index 2a0b7492bc..1f48e651b4 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -370,45 +370,76 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): self.dbcon.uninstall() - modules_manager = ModulesManager() - ftrack_module = modules_manager.modules_by_name.get("ftrack") - if ftrack_module and ftrack_module.enabled: - # Set attribute `is_published` to `False` on ftrack AssetVersions - import ftrack_api - - session = ftrack_api.Session() - query = ( - "AssetVersion where asset.parent.id is \"{}\"" - " and asset.name is \"{}\"" - " and version is \"{}\"" - ) - for v in data["versions"]: - try: - ftrack_version = session.query( - query.format( - data["asset"]["data"]["ftrackId"], - data["subset"]["name"], - v["name"] - ) - ).one() - except ftrack_api.exception.NoResultFoundError: - continue - - ftrack_version["is_published"] = False - - try: - session.commit() - - except Exception: - msg = ( - "Could not set `is_published` attribute to `False`" - " for selected AssetVersions." - ) - self.log.error(msg) - self.message(msg) + self._ftrack_delete_versions(data) return size + def _ftrack_delete_versions(self, data): + """Delete version on ftrack. + + Handling of ftrack logic in this plugin is not ideal. But in OP3 it is + almost impossible to solve the issue other way. + + Note: + Asset versions on ftrack are not deleted but marked as + "not published" which cause that they're invisible. + + Args: + data (dict): Data sent to subset loader with full context. + """ + + # First check for ftrack id on asset document + # - skip if ther is none + asset_ftrack_id = data["asset"]["data"].get("ftrackId") + if not asset_ftrack_id: + self.log.info(( + "Asset does not have filled ftrack id. Skipped delete" + " of ftrack version." + )) + return + + # Check if ftrack module is enabled + modules_manager = ModulesManager() + ftrack_module = modules_manager.modules_by_name.get("ftrack") + if not ftrack_module or not ftrack_module.enabled: + return + + import ftrack_api + + session = ftrack_api.Session() + subset_name = data["subset"]["name"] + versions = { + '"{}"'.format(version_doc["name"]) + for version_doc in data["versions"] + } + asset_versions = session.query( + ( + "select id, is_published from AssetVersion where" + " asset.parent.id is \"{}\"" + " and asset.name is \"{}\"" + " and version in (\"{}\")" + ).format( + asset_ftrack_id, + subset_name, + ",".join(versions) + ) + ) + + # Set attribute `is_published` to `False` on ftrack AssetVersions + for asset_version in asset_versions: + asset_version["is_published"] = False + + try: + session.commit() + + except Exception: + msg = ( + "Could not set `is_published` attribute to `False`" + " for selected AssetVersions." + ) + self.log.error(msg) + self.message(msg) + def load(self, contexts, name=None, namespace=None, options=None): try: size = 0 From 9f174cc92c4aeb869d4e847d7558e7cda18302ff Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 11:11:34 +0200 Subject: [PATCH 0974/1227] fix query --- openpype/plugins/load/delete_old_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index 1f48e651b4..6b469357f7 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -417,7 +417,7 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): "select id, is_published from AssetVersion where" " asset.parent.id is \"{}\"" " and asset.name is \"{}\"" - " and version in (\"{}\")" + " and version in ({})" ).format( asset_ftrack_id, subset_name, From 408593bf14d4ae5c5cd022ddfd3c87969178a0c7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 11:12:46 +0200 Subject: [PATCH 0975/1227] define query result --- openpype/plugins/load/delete_old_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index 6b469357f7..0952cd9bd6 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -423,7 +423,7 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): subset_name, ",".join(versions) ) - ) + ).all() # Set attribute `is_published` to `False` on ftrack AssetVersions for asset_version in asset_versions: From 01c48a3c194adf6e83421380d7bf002eca445dcd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 11:40:28 +0200 Subject: [PATCH 0976/1227] trigger 'openpype.project.prepare' event on project preparation --- .../ftrack/event_handlers_server/action_prepare_project.py | 5 +++++ .../ftrack/event_handlers_user/action_prepare_project.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py index 361aa98d16..50cadb7f09 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py @@ -1,4 +1,5 @@ import json +import copy from openpype.client import get_project from openpype.api import ProjectSettings @@ -400,6 +401,10 @@ class PrepareProjectServer(ServerAction): self.log.debug("- Key \"{}\" set to \"{}\"".format(key, value)) session.commit() + event_data = copy.deepcopy(in_data) + event_data["project_name"] = project_name + self.trigger_event("openpype.project.prepared", event_data) + return True diff --git a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py index e9dc11de9f..9cac2f4386 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py @@ -1,4 +1,5 @@ import json +import copy from openpype.client import get_project from openpype.api import ProjectSettings @@ -433,6 +434,10 @@ class PrepareProjectLocal(BaseAction): self.process_identifier() ) self.trigger_action(trigger_identifier, event) + + event_data = copy.deepcopy(in_data) + event_data["project_name"] = project_name + self.trigger_event("openpype.project.prepared", event_data) return True From 91a9dcdbd491d7c4cc4b28a0abcbda3a1fd70a5d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 11:42:00 +0200 Subject: [PATCH 0977/1227] Don't use dictionary as default value --- openpype/modules/ftrack/lib/ftrack_base_handler.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/lib/ftrack_base_handler.py b/openpype/modules/ftrack/lib/ftrack_base_handler.py index 2130abc20c..c0fad6aadc 100644 --- a/openpype/modules/ftrack/lib/ftrack_base_handler.py +++ b/openpype/modules/ftrack/lib/ftrack_base_handler.py @@ -535,7 +535,7 @@ class BaseHandler(object): ) def trigger_event( - self, topic, event_data={}, session=None, source=None, + self, topic, event_data=None, session=None, source=None, event=None, on_error="ignore" ): if session is None: @@ -543,6 +543,9 @@ class BaseHandler(object): if not source and event: source = event.get("source") + + if event_data is None: + event_data = {} # Create and trigger event event = ftrack_api.event.base.Event( topic=topic, From b340bd532d6233c3fadf86295e98eb617412e066 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 11:47:06 +0200 Subject: [PATCH 0978/1227] trigger ftrack event 'openpype.project.created' on project creation --- .../event_handlers_server/action_prepare_project.py | 4 ++++ .../ftrack/event_handlers_user/action_prepare_project.py | 4 ++++ openpype/modules/ftrack/lib/avalon_sync.py | 9 +++++++++ 3 files changed, 17 insertions(+) diff --git a/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py index 50cadb7f09..713a4d9aba 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py @@ -374,6 +374,10 @@ class PrepareProjectServer(ServerAction): project_name, project_code )) create_project(project_name, project_code) + self.trigger_event( + "openpype.project.created", + {"project_name": project_name} + ) project_settings = ProjectSettings(project_name) project_anatomy_settings = project_settings["project_anatomy"] diff --git a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py index 9cac2f4386..e89595109e 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py @@ -400,6 +400,10 @@ class PrepareProjectLocal(BaseAction): project_name, project_code )) create_project(project_name, project_code) + self.trigger_event( + "openpype.project.created", + {"project_name": project_name} + ) project_settings = ProjectSettings(project_name) project_anatomy_settings = project_settings["project_anatomy"] diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 68b5c62c53..6161a2131e 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -443,6 +443,7 @@ class SyncEntitiesFactory: } self.create_list = [] + self.project_created = False self.unarchive_list = [] self.updates = collections.defaultdict(dict) @@ -2016,6 +2017,13 @@ class SyncEntitiesFactory: if len(self.create_list) > 0: self.dbcon.insert_many(self.create_list) + if self.project_created: + event = ftrack_api.event.base.Event( + topic="openpype.project.created", + data={"project_name": self.project_name} + ) + self.session.event_hub.publish(event) + self.session.commit() self.log.debug("* Processing entities for update") @@ -2214,6 +2222,7 @@ class SyncEntitiesFactory: self._avalon_ents_by_name[project_item["name"]] = str(new_id) self.create_list.append(project_item) + self.project_created = True # store mongo id to ftrack entity entity = self.entities_dict[self.ft_project_id]["entity"] From 606b057ab0d16e82e801aaaaddc1744be2256ae8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 12:25:32 +0200 Subject: [PATCH 0979/1227] trigger 'openpype.project.created' in sync actions because session avalon sync is not with event hub --- .../event_handlers_server/action_sync_to_avalon.py | 10 +++++++++- .../event_handlers_user/action_sync_to_avalon.py | 10 +++++++++- openpype/modules/ftrack/lib/avalon_sync.py | 7 ------- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_sync_to_avalon.py b/openpype/modules/ftrack/event_handlers_server/action_sync_to_avalon.py index 58f79e8a2b..df9147bdf7 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_sync_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_server/action_sync_to_avalon.py @@ -1,7 +1,8 @@ import time import sys import json -import traceback + +import ftrack_api from openpype_modules.ftrack.lib import ServerAction from openpype_modules.ftrack.lib.avalon_sync import SyncEntitiesFactory @@ -180,6 +181,13 @@ class SyncToAvalonServer(ServerAction): "* Total time: {}".format(time_7 - time_start) ) + if self.entities_factory.project_created: + event = ftrack_api.event.base.Event( + topic="openpype.project.created", + data={"project_name": project_name} + ) + self.session.event_hub.publish(event) + report = self.entities_factory.report() if report and report.get("items"): default_title = "Synchronization report ({}):".format( diff --git a/openpype/modules/ftrack/event_handlers_user/action_sync_to_avalon.py b/openpype/modules/ftrack/event_handlers_user/action_sync_to_avalon.py index cd2f371f38..e52a061471 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_sync_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_user/action_sync_to_avalon.py @@ -1,7 +1,8 @@ import time import sys import json -import traceback + +import ftrack_api from openpype_modules.ftrack.lib import BaseAction, statics_icon from openpype_modules.ftrack.lib.avalon_sync import SyncEntitiesFactory @@ -184,6 +185,13 @@ class SyncToAvalonLocal(BaseAction): "* Total time: {}".format(time_7 - time_start) ) + if self.entities_factory.project_created: + event = ftrack_api.event.base.Event( + topic="openpype.project.created", + data={"project_name": project_name} + ) + self.session.event_hub.publish(event) + report = self.entities_factory.report() if report and report.get("items"): default_title = "Synchronization report ({}):".format( diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 6161a2131e..f8883cefbd 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -2017,13 +2017,6 @@ class SyncEntitiesFactory: if len(self.create_list) > 0: self.dbcon.insert_many(self.create_list) - if self.project_created: - event = ftrack_api.event.base.Event( - topic="openpype.project.created", - data={"project_name": self.project_name} - ) - self.session.event_hub.publish(event) - self.session.commit() self.log.debug("* Processing entities for update") From 4781fc56b48b6f66dc33803c35ee7d9be56729ec Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 4 Jul 2022 12:50:39 +0200 Subject: [PATCH 0980/1227] remove the global scope add_scripts_menu call from menu.py and add it in the install function of pipeline.py --- openpype/hosts/hiero/api/menu.py | 5 +---- openpype/hosts/hiero/api/pipeline.py | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/hiero/api/menu.py b/openpype/hosts/hiero/api/menu.py index 412f08272e..541a1f1f92 100644 --- a/openpype/hosts/hiero/api/menu.py +++ b/openpype/hosts/hiero/api/menu.py @@ -162,11 +162,8 @@ def add_scripts_menu(): log.warning("Skipping studio menu, no definition found.") return - # run the launcher for Maya menu + # run the launcher for Hiero menu studio_menu = launchforhiero.main(title=_menu.title()) # apply configuration studio_menu.build_from_configuration(studio_menu, config) - - -add_scripts_menu() diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py index 9b628ec70b..b243a38b06 100644 --- a/openpype/hosts/hiero/api/pipeline.py +++ b/openpype/hosts/hiero/api/pipeline.py @@ -48,6 +48,7 @@ def install(): # install menu menu.menu_install() + menu.add_scripts_menu() # register hiero events events.register_hiero_events() From 6c658f7da3fdf9bf716d8adfaf17f69641e78d87 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 4 Jul 2022 14:24:07 +0200 Subject: [PATCH 0981/1227] fix default json project_settings by setting defaults through run_settings UI --- .../defaults/project_settings/hiero.json | 42 ++++++------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index 3b0127c24a..e9e7199330 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -1,32 +1,4 @@ { - "ext_mapping": { - "model": "hrox", - "mayaAscii": "hrox", - "camera": "hrox", - "rig": "hrox", - "workfile": "hrox", - "yetiRig": "hrox" - }, - "hiero-dirmap": { - "enabled": false, - "paths": { - "source-path": [], - "destination-path": [] - } - }, - "scriptsmenu": { - "name": "OpenPype Tools", - "definition": [ - { - "type": "action", - "sourcetype": "python", - "title": "OpenPype Docs", - "command": "import webbrowser;webbrowser.open(url='https://openpype.io/docs/artist_hosts_hiero')", - "tooltip": "Open the OpenPype Hiero user doc page" - } - ] - }, - "create": { "CreateShotClip": { "hierarchy": "{folder}/{sequence}", @@ -79,5 +51,17 @@ ] } }, - "filters": {} + "filters": {}, + "scriptsmenu": { + "name": "OpenPype Tools", + "definition": [ + { + "type": "action", + "sourcetype": "python", + "title": "OpenPype Docs", + "command": "import webbrowser;webbrowser.open(url='https://openpype.io/docs/artist_hosts_hiero')", + "tooltip": "Open the OpenPype Hiero user doc page" + } + ] + } } \ No newline at end of file From eedc67edcc37dcfeae0254e4e856ec4b7df409cf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 16:28:59 +0200 Subject: [PATCH 0982/1227] use project entity from context instead of requery --- .../deadline/plugins/publish/submit_houdini_remote_publish.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_remote_publish.py b/openpype/modules/deadline/plugins/publish/submit_houdini_remote_publish.py index f834ae7e92..fdf67b51bc 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_remote_publish.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_remote_publish.py @@ -55,7 +55,7 @@ class HoudiniSubmitPublishDeadline(pyblish.api.ContextPlugin): scenename = os.path.basename(scene) # Get project code - project = legacy_io.find_one({"type": "project"}) + project = context.data["projectEntity"] code = project["data"].get("code", project["name"]) job_name = "{scene} [PUBLISH]".format(scene=scenename) From 5ba81e8c284e9ee38ceb84dae2aee25d054f8685 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 16:37:36 +0200 Subject: [PATCH 0983/1227] use query function in submit to deadline --- .../plugins/publish/submit_publish_job.py | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 9dd1428a63..b098eaba8e 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -11,6 +11,7 @@ import clique import pyblish.api import openpype.api +from openpype.client import get_representations from openpype.pipeline import ( get_representation_path, legacy_io, @@ -18,15 +19,23 @@ from openpype.pipeline import ( from openpype.pipeline.farm.patterning import match_aov_pattern -def get_resources(version, extension=None): +def get_resources(project_name, version, extension=None): """Get the files from the specific version.""" - query = {"type": "representation", "parent": version["_id"]} + + # TODO this functions seems to be weird + # - it's looking for representation with one extension or first (any) + # representation from a version? + # - not sure how this should work, maybe it does for specific use cases + # but probably can't be used for all resources from 2D workflows + extensions = None if extension: - query["name"] = extension - - representation = legacy_io.find_one(query) - assert representation, "This is a bug" + extensions = [extension] + repre_docs = list(get_representations( + project_name, version_ids=[version["_id"]], extensions=extensions + )) + assert repre_docs, "This is a bug" + representation = repre_docs[0] directory = get_representation_path(representation) print("Source: ", directory) resources = sorted( @@ -330,13 +339,16 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.info("Preparing to copy ...") start = instance.data.get("frameStart") end = instance.data.get("frameEnd") + project_name = legacy_io.active_project() # get latest version of subset # this will stop if subset wasn't published yet version = openpype.api.get_latest_version(instance.data.get("asset"), instance.data.get("subset")) # get its files based on extension - subset_resources = get_resources(version, representation.get("ext")) + subset_resources = get_resources( + project_name, version, representation.get("ext") + ) r_col, _ = clique.assemble(subset_resources) # if override remove all frames we are expecting to be rendered From eb352d0958fda52b2a77ca10bb56a9fb80894e16 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 16:51:54 +0200 Subject: [PATCH 0984/1227] fix 'hero' kwargs --- openpype/lib/avalon_context.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 9ffa5f2732..76ed6cbbd3 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -195,7 +195,6 @@ def is_latest(representation): version = get_version_by_id( project_name, representation["parent"], - hero=True, fields=["_id", "type", "parent"] ) if version["type"] == "hero_version": From 3612ceb9378053e124440661a89944d76c5a4d88 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 19:12:52 +0200 Subject: [PATCH 0985/1227] fix query of current version --- openpype/pipeline/load/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/load/utils.py b/openpype/pipeline/load/utils.py index eca581ba37..2c1b2ea8ea 100644 --- a/openpype/pipeline/load/utils.py +++ b/openpype/pipeline/load/utils.py @@ -394,7 +394,7 @@ def update_container(container, version=-1): assert current_representation is not None, "This is a bug" current_version = get_version_by_id( - project_name, current_representation["_id"], fields=["parent"] + project_name, current_representation["parent"], fields=["parent"] ) if version == -1: new_version = get_last_version_by_subset_id( From 488f58a52ed9d518082e28d30e1687fc562cbce4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Jul 2022 09:15:00 +0200 Subject: [PATCH 0986/1227] Resolve conflicts --- openpype/hosts/maya/plugins/publish/extract_animation.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/publish/extract_animation.py diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py deleted file mode 100644 index e69de29bb2..0000000000 From 3e058c6e8ac79ebb9933d0ad02957b0467f3a578 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Jul 2022 09:20:00 +0200 Subject: [PATCH 0987/1227] Move IntegrateAsset --- openpype/plugins/publish/integrate.py | 832 ++++++++++++++++++++++++++ 1 file changed, 832 insertions(+) create mode 100644 openpype/plugins/publish/integrate.py diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py new file mode 100644 index 0000000000..6ad0849ff7 --- /dev/null +++ b/openpype/plugins/publish/integrate.py @@ -0,0 +1,832 @@ +import os +import logging +import sys +import copy +import clique +import six + +from bson.objectid import ObjectId +from pymongo import DeleteMany, ReplaceOne, InsertOne, UpdateOne +import pyblish.api + +import openpype.api +from openpype.modules import ModulesManager +from openpype.lib.profiles_filtering import filter_profiles +from openpype.lib.file_transaction import FileTransaction +from openpype.pipeline import legacy_io + +log = logging.getLogger(__name__) + + +def assemble(files): + """Convenience `clique.assemble` wrapper for files of a single collection. + + Unlike `clique.assemble` this wrapper does not allow more than a single + Collection nor any remainder files. Errors will be raised when not only + a single collection is assembled. + + Returns: + clique.Collection: A single sequence Collection + + Raises: + ValueError: Error is raised when files do not result in a single + collected Collection. + + """ + # todo: move this to lib? + # Get the sequence as a collection. The files must be of a single + # sequence and have no remainder outside of the collections. + patterns = [clique.PATTERNS["frames"]] + collections, remainder = clique.assemble(files, + minimum_items=1, + patterns=patterns) + if not collections: + raise ValueError("No collections found in files: " + "{}".format(files)) + if remainder: + raise ValueError("Files found not detected as part" + " of a sequence: {}".format(remainder)) + if len(collections) > 1: + raise ValueError("Files in sequence are not part of a" + " single sequence collection: " + "{}".format(collections)) + return collections[0] + + +def get_instance_families(instance): + """Get all families of the instance""" + # todo: move this to lib? + family = instance.data.get("family") + families = [] + if family: + families.append(family) + + for _family in (instance.data.get("families") or []): + if _family not in families: + families.append(_family) + + return families + + +def get_frame_padded(frame, padding): + """Return frame number as string with `padding` amount of padded zeros""" + return "{frame:0{padding}d}".format(padding=padding, frame=frame) + + +def get_first_frame_padded(collection): + """Return first frame as padded number from `clique.Collection`""" + start_frame = next(iter(collection.indexes)) + return get_frame_padded(start_frame, padding=collection.padding) + + +def bulk_write(writes): + """Convenience function to bulk write into active project database""" + project = legacy_io.Session["AVALON_PROJECT"] + return legacy_io._database[project].bulk_write(writes) + + +class IntegrateAsset(pyblish.api.InstancePlugin): + """Register publish in the database and transfer files to destinations. + + Steps: + 1) Register the subset and version + 2) Transfer the representation files to the destination + 3) Register the representation + + Requires: + instance.data['representations'] - must be a list and each member + must be a dictionary with following data: + 'files': list of filenames for sequence, string for single file. + Only the filename is allowed, without the folder path. + 'stagingDir': "path/to/folder/with/files" + 'name': representation name (usually the same as extension) + 'ext': file extension + optional data + "frameStart" + "frameEnd" + 'fps' + "data": additional metadata for each representation. + """ + + label = "Integrate Asset New" + order = pyblish.api.IntegratorOrder + families = ["workfile", + "pointcache", + "camera", + "animation", + "model", + "mayaAscii", + "mayaScene", + "setdress", + "layout", + "ass", + "vdbcache", + "scene", + "vrayproxy", + "vrayscene_layer", + "render", + "prerender", + "imagesequence", + "review", + "rendersetup", + "rig", + "plate", + "look", + "audio", + "yetiRig", + "yeticache", + "nukenodes", + "gizmo", + "source", + "matchmove", + "image", + "assembly", + "fbx", + "textures", + "action", + "harmony.template", + "harmony.palette", + "editorial", + "background", + "camerarig", + "redshiftproxy", + "effect", + "xgen", + "hda", + "usd", + "staticMesh", + "skeletalMesh", + "usdComposition", + "usdOverride", + "simpleUnrealTexture" + ] + exclude_families = ["clip", "render.farm"] + default_template_name = "publish" + + # Representation context keys that should always be written to + # the database even if not used by the destination template + db_representation_context_keys = [ + "project", "asset", "task", "subset", "version", "representation", + "family", "hierarchy", "username" + ] + + # Attributes set by settings + template_name_profiles = None + + def process(self, instance): + + # Exclude instances that also contain families from exclude families + families = set(get_instance_families(instance)) + exclude = families & set(self.exclude_families) + if exclude: + self.log.debug("Instance not integrated due to exclude " + "families found: {}".format(", ".join(exclude))) + return + + file_transactions = FileTransaction(log=self.log) + try: + self.register(instance, file_transactions) + except Exception: + # clean destination + # todo: preferably we'd also rollback *any* changes to the database + file_transactions.rollback() + self.log.critical("Error when registering", exc_info=True) + six.reraise(*sys.exc_info()) + + # Finalizing can't rollback safely so no use for moving it to + # the try, except. + file_transactions.finalize() + + def register(self, instance, file_transactions): + + instance_stagingdir = instance.data.get("stagingDir") + if not instance_stagingdir: + self.log.info(( + "{0} is missing reference to staging directory." + " Will try to get it from representation." + ).format(instance)) + + else: + self.log.debug( + "Establishing staging directory " + "@ {0}".format(instance_stagingdir) + ) + + # Ensure at least one representation is set up for registering. + repres = instance.data.get("representations") + assert repres, "Instance has no representations data" + assert isinstance(repres, (list, tuple)), ( + "Instance 'representations' must be a list, got: {0} {1}".format( + str(type(repres)), str(repres) + ) + ) + + template_name = self.get_template_name(instance) + + subset, subset_writes = self.prepare_subset(instance) + version, version_writes = self.prepare_version(instance, subset) + instance.data["versionEntity"] = version + + # Get existing representations (if any) + existing_repres_by_name = { + repres["name"].lower(): repres for repres in legacy_io.find( + { + "parent": version["_id"], + "type": "representation" + }, + # Only care about id and name of existing representations + projection={"_id": True, "name": True} + ) + } + + # Prepare all representations + prepared_representations = [] + for repre in instance.data["representations"]: + + if "delete" in repre.get("tags", []): + self.log.debug("Skipping representation marked for deletion: " + "{}".format(repre)) + continue + + # todo: reduce/simplify what is returned from this function + prepared = self.prepare_representation(repre, + template_name, + existing_repres_by_name, + version, + instance_stagingdir, + instance) + + for src, dst in prepared["transfers"]: + # todo: add support for hardlink transfers + file_transactions.add(src, dst) + + prepared_representations.append(prepared) + + if not prepared_representations: + # Even though we check `instance.data["representations"]` earlier + # this could still happen if all representations were tagged with + # "delete" and thus are skipped for integration + raise RuntimeError("No representations prepared to publish.") + + # Each instance can also have pre-defined transfers not explicitly + # part of a representation - like texture resources used by a + # .ma representation. Those destination paths are pre-defined, etc. + # todo: should we move or simplify this logic? + resource_destinations = set() + for src, dst in instance.data.get("transfers", []): + file_transactions.add(src, dst, mode=FileTransaction.MODE_COPY) + resource_destinations.add(os.path.abspath(dst)) + for src, dst in instance.data.get("hardlinks", []): + file_transactions.add(src, dst, mode=FileTransaction.MODE_HARDLINK) + resource_destinations.add(os.path.abspath(dst)) + + # Bulk write to the database + # We write the subset and version to the database before the File + # Transaction to reduce the chances of another publish trying to + # publish to the same version number since that chance can greatly + # increase if the file transaction takes a long time. + bulk_write(subset_writes + version_writes) + self.log.info("Subset {subset[name]} and Version {version[name]} " + "written to database..".format(subset=subset, + version=version)) + + # Process all file transfers of all integrations now + self.log.debug("Integrating source files to destination ...") + file_transactions.process() + self.log.debug("Backed up existing files: " + "{}".format(file_transactions.backups)) + self.log.debug("Transferred files: " + "{}".format(file_transactions.transferred)) + self.log.debug("Retrieving Representation Site Sync information ...") + + # Get the accessible sites for Site Sync + manager = ModulesManager() + sync_server_module = manager.modules_by_name["sync_server"] + sites = sync_server_module.compute_resource_sync_sites( + project_name=instance.data["projectEntity"]["name"] + ) + self.log.debug("Sync Server Sites: {}".format(sites)) + + # Compute the resource file infos once (files belonging to the + # version instance instead of an individual representation) so + # we can re-use those file infos per representation + anatomy = instance.context.data["anatomy"] + resource_file_infos = self.get_files_info(resource_destinations, + sites=sites, + anatomy=anatomy) + + # Finalize the representations now the published files are integrated + # Get 'files' info for representations and its attached resources + representation_writes = [] + new_repre_names_low = set() + for prepared in prepared_representations: + representation = prepared["representation"] + transfers = prepared["transfers"] + destinations = [dst for src, dst in transfers] + representation["files"] = self.get_files_info( + destinations, sites=sites, anatomy=anatomy + ) + + # Add the version resource file infos to each representation + representation["files"] += resource_file_infos + + # Set up representation for writing to the database. Since + # we *might* be overwriting an existing entry if the version + # already existed we'll use ReplaceOnce with `upsert=True` + representation_writes.append(ReplaceOne( + filter={"_id": representation["_id"]}, + replacement=representation, + upsert=True + )) + + new_repre_names_low.add(representation["name"].lower()) + + # Delete any existing representations that didn't get any new data + # if the instance is not set to append mode + if not instance.data.get("append", False): + delete_names = set() + for name, existing_repres in existing_repres_by_name.items(): + if name not in new_repre_names_low: + # We add the exact representation name because `name` is + # lowercase for name matching only and not in the database + delete_names.add(existing_repres["name"]) + if delete_names: + representation_writes.append(DeleteMany( + filter={ + "parent": version["_id"], + "name": {"$in": list(delete_names)} + } + )) + + # Write representations to the database + bulk_write(representation_writes) + + # Backwards compatibility + # todo: can we avoid the need to store this? + instance.data["published_representations"] = { + p["representation"]["_id"]: p for p in prepared_representations + } + + self.log.info("Registered {} representations" + "".format(len(prepared_representations))) + + def prepare_subset(self, instance): + asset = instance.data.get("assetEntity") + subset_name = instance.data["subset"] + self.log.debug("Subset: {}".format(subset_name)) + + # Get existing subset if it exists + subset = legacy_io.find_one({ + "type": "subset", + "parent": asset["_id"], + "name": subset_name + }) + + # Define subset data + data = { + "families": get_instance_families(instance) + } + + subset_group = instance.data.get("subsetGroup") + if subset_group: + data["subsetGroup"] = subset_group + + bulk_writes = [] + if subset is None: + # Create a new subset + self.log.info("Subset '%s' not found, creating ..." % subset_name) + subset = { + "_id": ObjectId(), + "schema": "openpype:subset-3.0", + "type": "subset", + "name": subset_name, + "data": data, + "parent": asset["_id"] + } + bulk_writes.append(InsertOne(subset)) + + else: + # Update existing subset data with new data and set in database. + # We also change the found subset in-place so we don't need to + # re-query the subset afterwards + subset["data"].update(data) + bulk_writes.append(UpdateOne( + {"type": "subset", "_id": subset["_id"]}, + {"$set": { + "data": subset["data"] + }} + )) + + self.log.info("Prepared subset: {}".format(subset_name)) + return subset, bulk_writes + + def prepare_version(self, instance, subset): + + version_number = instance.data["version"] + + version = { + "schema": "openpype:version-3.0", + "type": "version", + "parent": subset["_id"], + "name": version_number, + "data": self.create_version_data(instance) + } + + existing_version = legacy_io.find_one({ + 'type': 'version', + 'parent': subset["_id"], + 'name': version_number + }, projection={"_id": True}) + + if existing_version: + self.log.debug("Updating existing version ...") + version["_id"] = existing_version["_id"] + else: + self.log.debug("Creating new version ...") + version["_id"] = ObjectId() + + bulk_writes = [ReplaceOne( + filter={"_id": version["_id"]}, + replacement=version, + upsert=True + )] + + self.log.info("Prepared version: v{0:03d}".format(version["name"])) + + return version, bulk_writes + + def prepare_representation(self, repre, + template_name, + existing_repres_by_name, + version, + instance_stagingdir, + instance): + + # pre-flight validations + if repre["ext"].startswith("."): + raise ValueError("Extension must not start with a dot '.': " + "{}".format(repre["ext"])) + + if repre.get("transfers"): + raise ValueError("Representation is not allowed to have transfers" + "data before integration. They are computed in " + "the integrator" + "Got: {}".format(repre["transfers"])) + + # create template data for Anatomy + template_data = copy.deepcopy(instance.data["anatomyData"]) + + # required representation keys + files = repre['files'] + template_data["representation"] = repre["name"] + template_data["ext"] = repre["ext"] + + # optionals + # retrieve additional anatomy data from representation if exists + for key, anatomy_key in { + # Representation Key: Anatomy data key + "resolutionWidth": "resolution_width", + "resolutionHeight": "resolution_height", + "fps": "fps", + "outputName": "output", + "originalBasename": "originalBasename" + }.items(): + # Allow to take value from representation + # if not found also consider instance.data + if key in repre: + value = repre[key] + elif key in instance.data: + value = instance.data[key] + else: + continue + template_data[anatomy_key] = value + + if repre.get('stagingDir'): + stagingdir = repre['stagingDir'] + else: + # Fall back to instance staging dir if not explicitly + # set for representation in the instance + self.log.debug("Representation uses instance staging dir: " + "{}".format(instance_stagingdir)) + stagingdir = instance_stagingdir + if not stagingdir: + raise ValueError("No staging directory set for representation: " + "{}".format(repre)) + + self.log.debug("Anatomy template name: {}".format(template_name)) + anatomy = instance.context.data['anatomy'] + template = os.path.normpath(anatomy.templates[template_name]["path"]) + + is_udim = bool(repre.get("udim")) + is_sequence_representation = isinstance(files, (list, tuple)) + if is_sequence_representation: + # Collection of files (sequence) + assert not any(os.path.isabs(fname) for fname in files), ( + "Given file names contain full paths" + ) + + src_collection = assemble(files) + + # If the representation has `frameStart` set it renumbers the + # frame indices of the published collection. It will start from + # that `frameStart` index instead. Thus if that frame start + # differs from the collection we want to shift the destination + # frame indices from the source collection. + destination_indexes = list(src_collection.indexes) + destination_padding = len(get_first_frame_padded(src_collection)) + if repre.get("frameStart") is not None and not is_udim: + index_frame_start = int(repre.get("frameStart")) + + render_template = anatomy.templates[template_name] + # todo: should we ALWAYS manage the frame padding even when not + # having `frameStart` set? + frame_start_padding = int( + render_template.get( + "frame_padding", + render_template.get("padding") + ) + ) + + # Shift destination sequence to the start frame + src_start_frame = next(iter(src_collection.indexes)) + shift = index_frame_start - src_start_frame + if shift: + destination_indexes = [ + frame + shift for frame in destination_indexes + ] + destination_padding = frame_start_padding + + # To construct the destination template with anatomy we require + # a Frame or UDIM tile set for the template data. We use the first + # index of the destination for that because that could've shifted + # from the source indexes, etc. + first_index_padded = get_frame_padded(frame=destination_indexes[0], + padding=destination_padding) + if is_udim: + # UDIM representations handle ranges in a different manner + template_data["udim"] = first_index_padded + else: + template_data["frame"] = first_index_padded + + # Construct destination collection from template + anatomy_filled = anatomy.format(template_data) + template_filled = anatomy_filled[template_name]["path"] + repre_context = template_filled.used_values + self.log.debug("Template filled: {}".format(str(template_filled))) + dst_collection = assemble([os.path.normpath(template_filled)]) + + # Update the destination indexes and padding + dst_collection.indexes.clear() + dst_collection.indexes.update(set(destination_indexes)) + dst_collection.padding = destination_padding + assert ( + len(src_collection.indexes) == len(dst_collection.indexes) + ), "This is a bug" + + # Multiple file transfers + transfers = [] + for src_file_name, dst in zip(src_collection, dst_collection): + src = os.path.join(stagingdir, src_file_name) + transfers.append((src, dst)) + + else: + # Single file + fname = files + assert not os.path.isabs(fname), ( + "Given file name is a full path" + ) + + # Manage anatomy template data + template_data.pop("frame", None) + if is_udim: + template_data["udim"] = repre["udim"][0] + + # Construct destination filepath from template + anatomy_filled = anatomy.format(template_data) + template_filled = anatomy_filled[template_name]["path"] + repre_context = template_filled.used_values + dst = os.path.normpath(template_filled) + + # Single file transfer + src = os.path.join(stagingdir, fname) + transfers = [(src, dst)] + + # todo: Are we sure the assumption each representation + # ends up in the same folder is valid? + if not instance.data.get("publishDir"): + instance.data["publishDir"] = ( + anatomy_filled + [template_name] + ["folder"] + ) + + for key in self.db_representation_context_keys: + # Also add these values to the context even if not used by the + # destination template + value = template_data.get(key) + if not value: + continue + repre_context[key] = template_data[key] + + # Explicitly store the full list even though template data might + # have a different value because it uses just a single udim tile + if repre.get("udim"): + repre_context["udim"] = repre.get("udim") # store list + + # Use previous representation's id if there is a name match + existing = existing_repres_by_name.get(repre["name"].lower()) + if existing: + repre_id = existing["_id"] + else: + repre_id = ObjectId() + + # Backwards compatibility: + # Store first transferred destination as published path data + # todo: can we remove this? + # todo: We shouldn't change data that makes its way back into + # instance.data[] until we know the publish actually succeeded + # otherwise `published_path` might not actually be valid? + published_path = transfers[0][1] + repre["published_path"] = published_path # Backwards compatibility + + # todo: `repre` is not the actual `representation` entity + # we should simplify/clarify difference between data above + # and the actual representation entity for the database + data = repre.get("data", {}) + data.update({'path': published_path, 'template': template}) + representation = { + "_id": repre_id, + "schema": "openpype:representation-2.0", + "type": "representation", + "parent": version["_id"], + "name": repre['name'], + "data": data, + + # Imprint shortcut to context for performance reasons. + "context": repre_context + } + + # todo: simplify/streamline which additional data makes its way into + # the representation context + if repre.get("outputName"): + representation["context"]["output"] = repre['outputName'] + + if is_sequence_representation and repre.get("frameStart") is not None: + representation['context']['frame'] = template_data["frame"] + + return { + "representation": representation, + "anatomy_data": template_data, + "transfers": transfers, + # todo: avoid the need for 'published_files' used by Integrate Hero + # backwards compatibility + "published_files": [transfer[1] for transfer in transfers] + } + + def create_version_data(self, instance): + """Create the data dictionary for the version + + Args: + instance: the current instance being published + + Returns: + dict: the required information for version["data"] + """ + + context = instance.context + + # create relative source path for DB + if "source" in instance.data: + source = instance.data["source"] + else: + source = context.data["currentFile"] + anatomy = instance.context.data["anatomy"] + source = self.get_rootless_path(anatomy, source) + self.log.debug("Source: {}".format(source)) + + version_data = { + "families": get_instance_families(instance), + "time": context.data["time"], + "author": context.data["user"], + "source": source, + "comment": context.data.get("comment"), + "machine": context.data.get("machine"), + "fps": instance.data.get("fps", context.data.get("fps")) + } + + # todo: preferably we wouldn't need this "if dict" etc. logic and + # instead be able to rely what the input value is if it's set. + intent_value = context.data.get("intent") + if intent_value and isinstance(intent_value, dict): + intent_value = intent_value.get("value") + + if intent_value: + version_data["intent"] = intent_value + + # Include optional data if present in + optionals = [ + "frameStart", "frameEnd", "step", "handles", + "handleEnd", "handleStart", "sourceHashes" + ] + for key in optionals: + if key in instance.data: + version_data[key] = instance.data[key] + + # Include instance.data[versionData] directly + version_data_instance = instance.data.get('versionData') + if version_data_instance: + version_data.update(version_data_instance) + + return version_data + + def get_template_name(self, instance): + """Return anatomy template name to use for integration""" + # Define publish template name from profiles + filter_criteria = self.get_profile_filter_criteria(instance) + profile = filter_profiles(self.template_name_profiles, + filter_criteria, + logger=self.log) + if profile: + return profile["template_name"] + else: + return self.default_template_name + + def get_profile_filter_criteria(self, instance): + """Return filter criteria for `filter_profiles`""" + # Anatomy data is pre-filled by Collectors + anatomy_data = instance.data["anatomyData"] + + # Task can be optional in anatomy data + task = anatomy_data.get("task", {}) + + # Return filter criteria + return { + "families": anatomy_data["family"], + "tasks": task.get("name"), + "hosts": anatomy_data["app"], + "task_types": task.get("type") + } + + def get_rootless_path(self, anatomy, path): + """Returns, if possible, path without absolute portion from root + (eg. 'c:\' or '/opt/..') + + This information is platform dependent and shouldn't be captured. + Example: + 'c:/projects/MyProject1/Assets/publish...' > + '{root}/MyProject1/Assets...' + + Args: + anatomy: anatomy part from instance + path: path (absolute) + Returns: + path: modified path if possible, or unmodified path + + warning logged + """ + success, rootless_path = anatomy.find_root_template_from_path(path) + if success: + path = rootless_path + else: + self.log.warning(( + "Could not find root path for remapping \"{}\"." + " This may cause issues on farm." + ).format(path)) + return path + + def get_files_info(self, destinations, sites, anatomy): + """Prepare 'files' info portion for representations. + + Arguments: + destinations (list): List of transferred file destinations + sites (list): array of published locations + anatomy: anatomy part from instance + Returns: + output_resources: array of dictionaries to be added to 'files' key + in representation + """ + file_infos = [] + for file_path in destinations: + file_info = self.prepare_file_info(file_path, anatomy, sites=sites) + file_infos.append(file_info) + return file_infos + + def prepare_file_info(self, path, anatomy, sites): + """ Prepare information for one file (asset or resource) + + Arguments: + path: destination url of published file + anatomy: anatomy part from instance + sites: array of published locations, + [ {'name':'studio', 'created_dt':date} by default + keys expected ['studio', 'site1', 'gdrive1'] + + Returns: + dict: file info dictionary + """ + return { + "_id": ObjectId(), + "path": self.get_rootless_path(anatomy, path), + "size": os.path.getsize(path), + "hash": openpype.api.source_hash(path), + "sites": sites + } From fd2d07e94c0fb34730547c396e09ddc314b56983 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Jul 2022 09:22:29 +0200 Subject: [PATCH 0988/1227] Revert integrator to latest develop --- openpype/plugins/publish/integrate_new.py | 1710 +++++++++++++-------- 1 file changed, 1088 insertions(+), 622 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index a07e8a1e0f..4c14c17dae 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -1,111 +1,63 @@ import os +from os.path import getsize import logging import sys import copy import clique +import errno import six +import re +import shutil +from collections import deque, defaultdict +from datetime import datetime from bson.objectid import ObjectId -from pymongo import DeleteMany, ReplaceOne, InsertOne, UpdateOne +from pymongo import DeleteOne, InsertOne import pyblish.api import openpype.api -from openpype.modules import ModulesManager from openpype.lib.profiles_filtering import filter_profiles -from openpype.lib.file_transaction import FileTransaction +from openpype.lib import ( + prepare_template_data, + create_hard_link, + StringTemplate, + TemplateUnsolved +) from openpype.pipeline import legacy_io +# this is needed until speedcopy for linux is fixed +if sys.platform == "win32": + from speedcopy import copyfile +else: + from shutil import copyfile + log = logging.getLogger(__name__) -def assemble(files): - """Convenience `clique.assemble` wrapper for files of a single collection. - - Unlike `clique.assemble` this wrapper does not allow more than a single - Collection nor any remainder files. Errors will be raised when not only - a single collection is assembled. - - Returns: - clique.Collection: A single sequence Collection - - Raises: - ValueError: Error is raised when files do not result in a single - collected Collection. - - """ - # todo: move this to lib? - # Get the sequence as a collection. The files must be of a single - # sequence and have no remainder outside of the collections. - patterns = [clique.PATTERNS["frames"]] - collections, remainder = clique.assemble(files, - minimum_items=1, - patterns=patterns) - if not collections: - raise ValueError("No collections found in files: " - "{}".format(files)) - if remainder: - raise ValueError("Files found not detected as part" - " of a sequence: {}".format(remainder)) - if len(collections) > 1: - raise ValueError("Files in sequence are not part of a" - " single sequence collection: " - "{}".format(collections)) - return collections[0] - - -def get_instance_families(instance): - """Get all families of the instance""" - # todo: move this to lib? - family = instance.data.get("family") - families = [] - if family: - families.append(family) - - for _family in (instance.data.get("families") or []): - if _family not in families: - families.append(_family) - - return families - - -def get_frame_padded(frame, padding): - """Return frame number as string with `padding` amount of padded zeros""" - return "{frame:0{padding}d}".format(padding=padding, frame=frame) - - -def get_first_frame_padded(collection): - """Return first frame as padded number from `clique.Collection`""" - start_frame = next(iter(collection.indexes)) - return get_frame_padded(start_frame, padding=collection.padding) - - -def bulk_write(writes): - """Convenience function to bulk write into active project database""" - project = legacy_io.Session["AVALON_PROJECT"] - return legacy_io._database[project].bulk_write(writes) - - class IntegrateAssetNew(pyblish.api.InstancePlugin): - """Register publish in the database and transfer files to destinations. + """Resolve any dependency issues - Steps: - 1) Register the subset and version - 2) Transfer the representation files to the destination - 3) Register the representation + This plug-in resolves any paths which, if not updated might break + the published file. - Requires: - instance.data['representations'] - must be a list and each member - must be a dictionary with following data: - 'files': list of filenames for sequence, string for single file. - Only the filename is allowed, without the folder path. - 'stagingDir': "path/to/folder/with/files" - 'name': representation name (usually the same as extension) - 'ext': file extension - optional data - "frameStart" - "frameEnd" - 'fps' - "data": additional metadata for each representation. + The order of families is important, when working with lookdev you want to + first publish the texture, update the texture paths in the nodes and then + publish the shading network. Same goes for file dependent assets. + + Requirements for instance to be correctly integrated + + instance.data['representations'] - must be a list and each member + must be a dictionary with following data: + 'files': list of filenames for sequence, string for single file. + Only the filename is allowed, without the folder path. + 'stagingDir': "path/to/folder/with/files" + 'name': representation name (usually the same as extension) + 'ext': file extension + optional data + "frameStart" + "frameEnd" + 'fps' + "data": additional metadata for each representation. """ label = "Integrate Asset New" @@ -140,6 +92,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "source", "matchmove", "image", + "source", "assembly", "fbx", "textures", @@ -156,51 +109,157 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "usd", "staticMesh", "skeletalMesh", - "usdComposition", - "usdOverride", + "mvLook", + "mvUsd", + "mvUsdComposition", + "mvUsdOverride", "simpleUnrealTexture" ] - exclude_families = ["clip", "render.farm"] - default_template_name = "publish" - - # Representation context keys that should always be written to - # the database even if not used by the destination template + exclude_families = ["render.farm"] db_representation_context_keys = [ "project", "asset", "task", "subset", "version", "representation", - "family", "hierarchy", "username" + "family", "hierarchy", "task", "username" ] + default_template_name = "publish" + + # suffix to denote temporary files, use without '.' + TMP_FILE_EXT = 'tmp' + + # file_url : file_size of all published and uploaded files + integrated_file_sizes = {} # Attributes set by settings template_name_profiles = None + subset_grouping_profiles = None def process(self, instance): + for ef in self.exclude_families: + if ( + instance.data["family"] == ef or + ef in instance.data["families"]): + self.log.debug("Excluded family '{}' in '{}' or {}".format( + ef, instance.data["family"], instance.data["families"])) + return - # Exclude instances that also contain families from exclude families - families = set(get_instance_families(instance)) - exclude = families & set(self.exclude_families) - if exclude: - self.log.debug("Instance not integrated due to exclude " - "families found: {}".format(", ".join(exclude))) + # instance should be published on a farm + if instance.data.get("farm"): return - file_transactions = FileTransaction(log=self.log) + # Prepare repsentations that should be integrated + repres = instance.data.get("representations") + # Raise error if instance don't have any representations + if not repres: + raise ValueError( + "Instance {} has no files to transfer".format( + instance.data["family"] + ) + ) + + # Validate type of stored representations + if not isinstance(repres, (list, tuple)): + raise TypeError( + "Instance 'files' must be a list, got: {0} {1}".format( + str(type(repres)), str(repres) + ) + ) + + # Filter representations + filtered_repres = [] + for repre in repres: + if "delete" in repre.get("tags", []): + continue + filtered_repres.append(repre) + + # Skip instance if there are not representations to integrate + # all representations should not be integrated + if not filtered_repres: + self.log.warning(( + "Skipping, there are no representations" + " to integrate for instance {}" + ).format(instance.data["family"])) + return + + self.integrated_file_sizes = {} try: - self.register(instance, file_transactions) + self.register(instance, filtered_repres) + self.log.info("Integrated Asset in to the database ...") + self.log.info("instance.data: {}".format(instance.data)) + self.handle_destination_files(self.integrated_file_sizes, + 'finalize') except Exception: # clean destination - # todo: preferably we'd also rollback *any* changes to the database - file_transactions.rollback() self.log.critical("Error when registering", exc_info=True) + self.handle_destination_files(self.integrated_file_sizes, 'remove') six.reraise(*sys.exc_info()) - # Finalizing can't rollback safely so no use for moving it to - # the try, except. - file_transactions.finalize() + def register(self, instance, repres): + # Required environment variables + anatomy_data = instance.data["anatomyData"] - def register(self, instance, file_transactions): + legacy_io.install() - instance_stagingdir = instance.data.get("stagingDir") - if not instance_stagingdir: + context = instance.context + + project_entity = instance.data["projectEntity"] + + context_asset_name = None + context_asset_doc = context.data.get("assetEntity") + if context_asset_doc: + context_asset_name = context_asset_doc["name"] + + asset_name = instance.data["asset"] + asset_entity = instance.data.get("assetEntity") + if not asset_entity or asset_entity["name"] != context_asset_name: + asset_entity = legacy_io.find_one({ + "type": "asset", + "name": asset_name, + "parent": project_entity["_id"] + }) + assert asset_entity, ( + "No asset found by the name \"{0}\" in project \"{1}\"" + ).format(asset_name, project_entity["name"]) + + instance.data["assetEntity"] = asset_entity + + # update anatomy data with asset specific keys + # - name should already been set + hierarchy = "" + parents = asset_entity["data"]["parents"] + if parents: + hierarchy = "/".join(parents) + anatomy_data["hierarchy"] = hierarchy + + # Make sure task name in anatomy data is same as on instance.data + asset_tasks = ( + asset_entity.get("data", {}).get("tasks") + ) or {} + task_name = instance.data.get("task") + if task_name: + task_info = asset_tasks.get(task_name) or {} + task_type = task_info.get("type") + + project_task_types = project_entity["config"]["tasks"] + task_code = project_task_types.get(task_type, {}).get("short_name") + anatomy_data["task"] = { + "name": task_name, + "type": task_type, + "short": task_code + } + + elif "task" in anatomy_data: + # Just set 'task_name' variable to context task + task_name = anatomy_data["task"]["name"] + task_type = anatomy_data["task"]["type"] + + else: + task_name = None + task_type = None + + # Fill family in anatomy data + anatomy_data["family"] = instance.data.get("family") + + stagingdir = instance.data.get("stagingDir") + if not stagingdir: self.log.info(( "{0} is missing reference to staging directory." " Will try to get it from representation." @@ -208,515 +267,718 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): else: self.log.debug( - "Establishing staging directory " - "@ {0}".format(instance_stagingdir) + "Establishing staging directory @ {0}".format(stagingdir) ) - # Ensure at least one representation is set up for registering. - repres = instance.data.get("representations") - assert repres, "Instance has no representations data" - assert isinstance(repres, (list, tuple)), ( - "Instance 'representations' must be a list, got: {0} {1}".format( - str(type(repres)), str(repres) - ) + subset = self.get_subset(asset_entity, instance) + instance.data["subsetEntity"] = subset + + version_number = instance.data["version"] + self.log.debug("Next version: v{}".format(version_number)) + + version_data = self.create_version_data(context, instance) + + version_data_instance = instance.data.get('versionData') + if version_data_instance: + version_data.update(version_data_instance) + + # TODO rename method from `create_version` to + # `prepare_version` or similar... + version = self.create_version( + subset=subset, + version_number=version_number, + data=version_data ) - template_name = self.get_template_name(instance) + self.log.debug("Creating version ...") - subset, subset_writes = self.prepare_subset(instance) - version, version_writes = self.prepare_version(instance, subset) + new_repre_names_low = [ + _repre["name"].lower() + for _repre in repres + ] + + existing_version = legacy_io.find_one({ + 'type': 'version', + 'parent': subset["_id"], + 'name': version_number + }) + + if existing_version is None: + version_id = legacy_io.insert_one(version).inserted_id + else: + # Check if instance have set `append` mode which cause that + # only replicated representations are set to archive + append_repres = instance.data.get("append", False) + + # Update version data + # TODO query by _id and + legacy_io.update_many({ + 'type': 'version', + 'parent': subset["_id"], + 'name': version_number + }, { + '$set': version + }) + version_id = existing_version['_id'] + + # Find representations of existing version and archive them + current_repres = list(legacy_io.find({ + "type": "representation", + "parent": version_id + })) + bulk_writes = [] + for repre in current_repres: + if append_repres: + # archive only duplicated representations + if repre["name"].lower() not in new_repre_names_low: + continue + # Representation must change type, + # `_id` must be stored to other key and replaced with new + # - that is because new representations should have same ID + repre_id = repre["_id"] + bulk_writes.append(DeleteOne({"_id": repre_id})) + + repre["orig_id"] = repre_id + repre["_id"] = ObjectId() + repre["type"] = "archived_representation" + bulk_writes.append(InsertOne(repre)) + + # bulk updates + if bulk_writes: + project_name = legacy_io.Session["AVALON_PROJECT"] + legacy_io.database[project_name].bulk_write( + bulk_writes + ) + + version = legacy_io.find_one({"_id": version_id}) instance.data["versionEntity"] = version - # Get existing representations (if any) - existing_repres_by_name = { - repres["name"].lower(): repres for repres in legacy_io.find( - { - "parent": version["_id"], - "type": "representation" - }, - # Only care about id and name of existing representations - projection={"_id": True, "name": True} - ) + existing_repres = list(legacy_io.find({ + "parent": version_id, + "type": "archived_representation" + })) + + instance.data['version'] = version['name'] + + intent_value = instance.context.data.get("intent") + if intent_value and isinstance(intent_value, dict): + intent_value = intent_value.get("value") + + if intent_value: + anatomy_data["intent"] = intent_value + + anatomy = instance.context.data['anatomy'] + + # Find the representations to transfer amongst the files + # Each should be a single representation (as such, a single extension) + representations = [] + destination_list = [] + + orig_transfers = [] + if 'transfers' not in instance.data: + instance.data['transfers'] = [] + else: + orig_transfers = list(instance.data['transfers']) + + family = self.main_family_from_instance(instance) + + key_values = { + "families": family, + "tasks": task_name, + "hosts": instance.context.data["hostName"], + "task_types": task_type } - - # Prepare all representations - prepared_representations = [] - for repre in instance.data["representations"]: - - if "delete" in repre.get("tags", []): - self.log.debug("Skipping representation marked for deletion: " - "{}".format(repre)) - continue - - # todo: reduce/simplify what is returned from this function - prepared = self.prepare_representation(repre, - template_name, - existing_repres_by_name, - version, - instance_stagingdir, - instance) - - for src, dst in prepared["transfers"]: - # todo: add support for hardlink transfers - file_transactions.add(src, dst) - - prepared_representations.append(prepared) - - if not prepared_representations: - # Even though we check `instance.data["representations"]` earlier - # this could still happen if all representations were tagged with - # "delete" and thus are skipped for integration - raise RuntimeError("No representations prepared to publish.") - - # Each instance can also have pre-defined transfers not explicitly - # part of a representation - like texture resources used by a - # .ma representation. Those destination paths are pre-defined, etc. - # todo: should we move or simplify this logic? - resource_destinations = set() - for src, dst in instance.data.get("transfers", []): - file_transactions.add(src, dst, mode=FileTransaction.MODE_COPY) - resource_destinations.add(os.path.abspath(dst)) - for src, dst in instance.data.get("hardlinks", []): - file_transactions.add(src, dst, mode=FileTransaction.MODE_HARDLINK) - resource_destinations.add(os.path.abspath(dst)) - - # Bulk write to the database - # We write the subset and version to the database before the File - # Transaction to reduce the chances of another publish trying to - # publish to the same version number since that chance can greatly - # increase if the file transaction takes a long time. - bulk_write(subset_writes + version_writes) - self.log.info("Subset {subset[name]} and Version {version[name]} " - "written to database..".format(subset=subset, - version=version)) - - # Process all file transfers of all integrations now - self.log.debug("Integrating source files to destination ...") - file_transactions.process() - self.log.debug("Backed up existing files: " - "{}".format(file_transactions.backups)) - self.log.debug("Transferred files: " - "{}".format(file_transactions.transferred)) - self.log.debug("Retrieving Representation Site Sync information ...") - - # Get the accessible sites for Site Sync - manager = ModulesManager() - sync_server_module = manager.modules_by_name["sync_server"] - sites = sync_server_module.compute_resource_sync_sites( - project_name=instance.data["projectEntity"]["name"] + profile = filter_profiles( + self.template_name_profiles, + key_values, + logger=self.log ) - self.log.debug("Sync Server Sites: {}".format(sites)) - # Compute the resource file infos once (files belonging to the - # version instance instead of an individual representation) so - # we can re-use those file infos per representation - anatomy = instance.context.data["anatomy"] - resource_file_infos = self.get_files_info(resource_destinations, - sites=sites, - anatomy=anatomy) + template_name = "publish" + if profile: + template_name = profile["template_name"] - # Finalize the representations now the published files are integrated - # Get 'files' info for representations and its attached resources - representation_writes = [] - new_repre_names_low = set() - for prepared in prepared_representations: - representation = prepared["representation"] - transfers = prepared["transfers"] - destinations = [dst for src, dst in transfers] + published_representations = {} + for idx, repre in enumerate(repres): + published_files = [] + + # create template data for Anatomy + template_data = copy.deepcopy(anatomy_data) + if intent_value is not None: + template_data["intent"] = intent_value + + resolution_width = repre.get("resolutionWidth") + resolution_height = repre.get("resolutionHeight") + fps = instance.data.get("fps") + + if resolution_width: + template_data["resolution_width"] = resolution_width + if resolution_width: + template_data["resolution_height"] = resolution_height + if resolution_width: + template_data["fps"] = fps + + if "originalBasename" in instance.data: + template_data.update({ + "originalBasename": instance.data.get("originalBasename") + }) + + files = repre['files'] + if repre.get('stagingDir'): + stagingdir = repre['stagingDir'] + + if repre.get("outputName"): + template_data["output"] = repre['outputName'] + + template_data["representation"] = repre["name"] + + ext = repre["ext"] + if ext.startswith("."): + self.log.warning(( + "Implementaion warning: <\"{}\">" + " Representation's extension stored under \"ext\" key " + " started with dot (\"{}\")." + ).format(repre["name"], ext)) + ext = ext[1:] + repre["ext"] = ext + template_data["ext"] = ext + + self.log.info(template_name) + template = os.path.normpath( + anatomy.templates[template_name]["path"]) + + sequence_repre = isinstance(files, list) + repre_context = None + if sequence_repre: + self.log.debug( + "files: {}".format(files)) + src_collections, remainder = clique.assemble(files) + self.log.debug( + "src_tail_collections: {}".format(str(src_collections))) + src_collection = src_collections[0] + + # Assert that each member has identical suffix + src_head = src_collection.format("{head}") + src_tail = src_collection.format("{tail}") + + # fix dst_padding + valid_files = [x for x in files if src_collection.match(x)] + padd_len = len( + valid_files[0].replace(src_head, "").replace(src_tail, "") + ) + src_padding_exp = "%0{}d".format(padd_len) + + test_dest_files = list() + for i in [1, 2]: + template_data["representation"] = repre['ext'] + if not repre.get("udim"): + template_data["frame"] = src_padding_exp % i + else: + template_data["udim"] = src_padding_exp % i + + anatomy_filled = anatomy.format(template_data) + template_filled = anatomy_filled[template_name]["path"] + if repre_context is None: + repre_context = template_filled.used_values + test_dest_files.append( + os.path.normpath(template_filled) + ) + if not repre.get("udim"): + template_data["frame"] = repre_context["frame"] + else: + template_data["udim"] = repre_context["udim"] + + self.log.debug( + "test_dest_files: {}".format(str(test_dest_files))) + + dst_collections, remainder = clique.assemble(test_dest_files) + dst_collection = dst_collections[0] + dst_head = dst_collection.format("{head}") + dst_tail = dst_collection.format("{tail}") + + index_frame_start = None + + # TODO use frame padding from right template group + if repre.get("frameStart") is not None: + frame_start_padding = int( + anatomy.templates["render"].get( + "frame_padding", + anatomy.templates["render"].get("padding") + ) + ) + + index_frame_start = int(repre.get("frameStart")) + + # exception for slate workflow + if index_frame_start and "slate" in instance.data["families"]: + index_frame_start -= 1 + + dst_padding_exp = src_padding_exp + dst_start_frame = None + collection_start = list(src_collection.indexes)[0] + for i in src_collection.indexes: + # TODO 1.) do not count padding in each index iteration + # 2.) do not count dst_padding from src_padding before + # index_frame_start check + frame_number = i - collection_start + src_padding = src_padding_exp % i + + src_file_name = "{0}{1}{2}".format( + src_head, src_padding, src_tail) + + dst_padding = src_padding_exp % frame_number + + if index_frame_start is not None: + dst_padding_exp = "%0{}d".format(frame_start_padding) + dst_padding = dst_padding_exp % (index_frame_start + frame_number) # noqa: E501 + elif repre.get("udim"): + dst_padding = int(i) + + dst = "{0}{1}{2}".format( + dst_head, + dst_padding, + dst_tail + ) + + self.log.debug("destination: `{}`".format(dst)) + src = os.path.join(stagingdir, src_file_name) + + self.log.debug("source: {}".format(src)) + instance.data["transfers"].append([src, dst]) + + published_files.append(dst) + + # for adding first frame into db + if not dst_start_frame: + dst_start_frame = dst_padding + + # Store used frame value to template data + if repre.get("frame"): + template_data["frame"] = dst_start_frame + + dst = "{0}{1}{2}".format( + dst_head, + dst_start_frame, + dst_tail + ) + repre['published_path'] = dst + + else: + # Single file + # _______ + # | |\ + # | | + # | | + # | | + # |_______| + # + template_data.pop("frame", None) + fname = files + assert not os.path.isabs(fname), ( + "Given file name is a full path" + ) + + template_data["representation"] = repre['ext'] + # Store used frame value to template data + if repre.get("udim"): + template_data["udim"] = repre["udim"][0] + src = os.path.join(stagingdir, fname) + anatomy_filled = anatomy.format(template_data) + template_filled = anatomy_filled[template_name]["path"] + repre_context = template_filled.used_values + dst = os.path.normpath(template_filled) + + instance.data["transfers"].append([src, dst]) + + published_files.append(dst) + repre['published_path'] = dst + self.log.debug("__ dst: {}".format(dst)) + + if not instance.data.get("publishDir"): + instance.data["publishDir"] = ( + anatomy_filled + [template_name] + ["folder"] + ) + if repre.get("udim"): + repre_context["udim"] = repre.get("udim") # store list + + repre["publishedFiles"] = published_files + + for key in self.db_representation_context_keys: + value = template_data.get(key) + if not value: + continue + repre_context[key] = template_data[key] + + # Use previous representation's id if there are any + repre_id = None + repre_name_low = repre["name"].lower() + for _repre in existing_repres: + # NOTE should we check lowered names? + if repre_name_low == _repre["name"]: + repre_id = _repre["orig_id"] + break + + # Create new id if existing representations does not match + if repre_id is None: + repre_id = ObjectId() + + data = repre.get("data") or {} + data.update({'path': dst, 'template': template}) + representation = { + "_id": repre_id, + "schema": "openpype:representation-2.0", + "type": "representation", + "parent": version_id, + "name": repre['name'], + "data": data, + "dependencies": instance.data.get("dependencies", "").split(), + + # Imprint shortcut to context + # for performance reasons. + "context": repre_context + } + + if repre.get("outputName"): + representation["context"]["output"] = repre['outputName'] + + if sequence_repre and repre.get("frameStart") is not None: + representation['context']['frame'] = ( + dst_padding_exp % int(repre.get("frameStart")) + ) + + # any file that should be physically copied is expected in + # 'transfers' or 'hardlinks' + if instance.data.get('transfers', False) or \ + instance.data.get('hardlinks', False): + # could throw exception, will be caught in 'process' + # all integration to DB is being done together lower, + # so no rollback needed + self.log.debug("Integrating source files to destination ...") + self.integrated_file_sizes.update(self.integrate(instance)) + self.log.debug("Integrated files {}". + format(self.integrated_file_sizes)) + + # get 'files' info for representation and all attached resources + self.log.debug("Preparing files information ...") representation["files"] = self.get_files_info( - destinations, sites=sites, anatomy=anatomy - ) + instance, + self.integrated_file_sizes) - # Add the version resource file infos to each representation - representation["files"] += resource_file_infos + self.log.debug("__ representation: {}".format(representation)) + destination_list.append(dst) + self.log.debug("__ destination_list: {}".format(destination_list)) + instance.data['destination_list'] = destination_list + representations.append(representation) + published_representations[repre_id] = { + "representation": representation, + "anatomy_data": template_data, + "published_files": published_files + } + self.log.debug("__ representations: {}".format(representations)) + # reset transfers for next representation + # instance.data['transfers'] is used as a global variable + # in current codebase + instance.data['transfers'] = list(orig_transfers) - # Set up representation for writing to the database. Since - # we *might* be overwriting an existing entry if the version - # already existed we'll use ReplaceOnce with `upsert=True` - representation_writes.append(ReplaceOne( - filter={"_id": representation["_id"]}, - replacement=representation, - upsert=True - )) + # Remove old representations if there are any (before insertion of new) + if existing_repres: + repre_ids_to_remove = [] + for repre in existing_repres: + repre_ids_to_remove.append(repre["_id"]) + legacy_io.delete_many({"_id": {"$in": repre_ids_to_remove}}) - new_repre_names_low.add(representation["name"].lower()) + for rep in instance.data["representations"]: + self.log.debug("__ rep: {}".format(rep)) - # Delete any existing representations that didn't get any new data - # if the instance is not set to append mode - if not instance.data.get("append", False): - delete_names = set() - for name, existing_repres in existing_repres_by_name.items(): - if name not in new_repre_names_low: - # We add the exact representation name because `name` is - # lowercase for name matching only and not in the database - delete_names.add(existing_repres["name"]) - if delete_names: - representation_writes.append(DeleteMany( - filter={ - "parent": version["_id"], - "name": {"$in": list(delete_names)} - } - )) + legacy_io.insert_many(representations) + instance.data["published_representations"] = ( + published_representations + ) + # self.log.debug("Representation: {}".format(representations)) + self.log.info("Registered {} items".format(len(representations))) - # Write representations to the database - bulk_write(representation_writes) + def integrate(self, instance): + """ Move the files. - # Backwards compatibility - # todo: can we avoid the need to store this? - instance.data["published_representations"] = { - p["representation"]["_id"]: p for p in prepared_representations - } + Through `instance.data["transfers"]` - self.log.info("Registered {} representations" - "".format(len(prepared_representations))) + Args: + instance: the instance to integrate + Returns: + integrated_file_sizes: dictionary of destination file url and + its size in bytes + """ + # store destination url and size for reporting and rollback + integrated_file_sizes = {} + transfers = list(instance.data.get("transfers", list())) + for src, dest in transfers: + if os.path.normpath(src) != os.path.normpath(dest): + dest = self.get_dest_temp_url(dest) + self.copy_file(src, dest) + # TODO needs to be updated during site implementation + integrated_file_sizes[dest] = os.path.getsize(dest) - def prepare_subset(self, instance): - asset = instance.data.get("assetEntity") + # Produce hardlinked copies + # Note: hardlink can only be produced between two files on the same + # server/disk and editing one of the two will edit both files at once. + # As such it is recommended to only make hardlinks between static files + # to ensure publishes remain safe and non-edited. + hardlinks = instance.data.get("hardlinks", list()) + for src, dest in hardlinks: + dest = self.get_dest_temp_url(dest) + self.log.debug("Hardlinking file ... {} -> {}".format(src, dest)) + if not os.path.exists(dest): + self.hardlink_file(src, dest) + + # TODO needs to be updated during site implementation + integrated_file_sizes[dest] = os.path.getsize(dest) + + return integrated_file_sizes + + def copy_file(self, src, dst): + """ Copy given source to destination + + Arguments: + src (str): the source file which needs to be copied + dst (str): the destination of the sourc file + Returns: + None + """ + src = os.path.normpath(src) + dst = os.path.normpath(dst) + self.log.debug("Copying file ... {} -> {}".format(src, dst)) + dirname = os.path.dirname(dst) + try: + os.makedirs(dirname) + except OSError as e: + if e.errno == errno.EEXIST: + pass + else: + self.log.critical("An unexpected error occurred.") + six.reraise(*sys.exc_info()) + + # copy file with speedcopy and check if size of files are simetrical + while True: + if not shutil._samefile(src, dst): + copyfile(src, dst) + else: + self.log.critical( + "files are the same {} to {}".format(src, dst) + ) + os.remove(dst) + try: + shutil.copyfile(src, dst) + self.log.debug("Copying files with shutil...") + except OSError as e: + self.log.critical("Cannot copy {} to {}".format(src, dst)) + self.log.critical(e) + six.reraise(*sys.exc_info()) + if str(getsize(src)) in str(getsize(dst)): + break + + def hardlink_file(self, src, dst): + dirname = os.path.dirname(dst) + + try: + os.makedirs(dirname) + except OSError as e: + if e.errno == errno.EEXIST: + pass + else: + self.log.critical("An unexpected error occurred.") + six.reraise(*sys.exc_info()) + + create_hard_link(src, dst) + + def get_subset(self, asset, instance): subset_name = instance.data["subset"] - self.log.debug("Subset: {}".format(subset_name)) - - # Get existing subset if it exists subset = legacy_io.find_one({ "type": "subset", "parent": asset["_id"], "name": subset_name }) - # Define subset data - data = { - "families": get_instance_families(instance) - } - - subset_group = instance.data.get("subsetGroup") - if subset_group: - data["subsetGroup"] = subset_group - - bulk_writes = [] if subset is None: - # Create a new subset self.log.info("Subset '%s' not found, creating ..." % subset_name) - subset = { - "_id": ObjectId(), + self.log.debug("families. %s" % instance.data.get('families')) + self.log.debug( + "families. %s" % type(instance.data.get('families'))) + + family = instance.data.get("family") + families = [] + if family: + families.append(family) + + for _family in (instance.data.get("families") or []): + if _family not in families: + families.append(_family) + + _id = legacy_io.insert_one({ "schema": "openpype:subset-3.0", "type": "subset", "name": subset_name, - "data": data, + "data": { + "families": families + }, "parent": asset["_id"] - } - bulk_writes.append(InsertOne(subset)) + }).inserted_id - else: - # Update existing subset data with new data and set in database. - # We also change the found subset in-place so we don't need to - # re-query the subset afterwards - subset["data"].update(data) - bulk_writes.append(UpdateOne( - {"type": "subset", "_id": subset["_id"]}, - {"$set": { - "data": subset["data"] - }} - )) + subset = legacy_io.find_one({"_id": _id}) - self.log.info("Prepared subset: {}".format(subset_name)) - return subset, bulk_writes + # QUESTION Why is changing of group and updating it's + # families in 'get_subset'? + self._set_subset_group(instance, subset["_id"]) - def prepare_version(self, instance, subset): + # Update families on subset. + families = [instance.data["family"]] + families.extend(instance.data.get("families", [])) + legacy_io.update_many( + {"type": "subset", "_id": ObjectId(subset["_id"])}, + {"$set": {"data.families": families}} + ) - version_number = instance.data["version"] + return subset - version = { - "schema": "openpype:version-3.0", - "type": "version", - "parent": subset["_id"], - "name": version_number, - "data": self.create_version_data(instance) + def _set_subset_group(self, instance, subset_id): + """ + Mark subset as belonging to group in DB. + + Uses Settings > Global > Publish plugins > IntegrateAssetNew + + Args: + instance (dict): processed instance + subset_id (str): DB's subset _id + + """ + # Fist look into instance data + subset_group = instance.data.get("subsetGroup") + if not subset_group: + subset_group = self._get_subset_group(instance) + + if subset_group: + legacy_io.update_many({ + 'type': 'subset', + '_id': ObjectId(subset_id) + }, {'$set': {'data.subsetGroup': subset_group}}) + + def _get_subset_group(self, instance): + """Look into subset group profiles set by settings. + + Attribute 'subset_grouping_profiles' is defined by OpenPype settings. + """ + # Skip if 'subset_grouping_profiles' is empty + if not self.subset_grouping_profiles: + return None + + # QUESTION + # - is there a chance that task name is not filled in anatomy + # data? + # - should we use context task in that case? + anatomy_data = instance.data["anatomyData"] + task_name = None + task_type = None + if "task" in anatomy_data: + task_name = anatomy_data["task"]["name"] + task_type = anatomy_data["task"]["type"] + filtering_criteria = { + "families": instance.data["family"], + "hosts": instance.context.data["hostName"], + "tasks": task_name, + "task_types": task_type } + matching_profile = filter_profiles( + self.subset_grouping_profiles, + filtering_criteria + ) + # Skip if there is not matchin profile + if not matching_profile: + return None - existing_version = legacy_io.find_one({ - 'type': 'version', - 'parent': subset["_id"], - 'name': version_number - }, projection={"_id": True}) + filled_template = None + template = matching_profile["template"] + fill_pairs = ( + ("family", filtering_criteria["families"]), + ("task", filtering_criteria["tasks"]), + ("host", filtering_criteria["hosts"]), + ("subset", instance.data["subset"]), + ("renderlayer", instance.data.get("renderlayer")) + ) + fill_pairs = prepare_template_data(fill_pairs) - if existing_version: - self.log.debug("Updating existing version ...") - version["_id"] = existing_version["_id"] - else: - self.log.debug("Creating new version ...") - version["_id"] = ObjectId() - - bulk_writes = [ReplaceOne( - filter={"_id": version["_id"]}, - replacement=version, - upsert=True - )] - - self.log.info("Prepared version: v{0:03d}".format(version["name"])) - - return version, bulk_writes - - def prepare_representation(self, repre, - template_name, - existing_repres_by_name, - version, - instance_stagingdir, - instance): - - # pre-flight validations - if repre["ext"].startswith("."): - raise ValueError("Extension must not start with a dot '.': " - "{}".format(repre["ext"])) - - if repre.get("transfers"): - raise ValueError("Representation is not allowed to have transfers" - "data before integration. They are computed in " - "the integrator" - "Got: {}".format(repre["transfers"])) - - # create template data for Anatomy - template_data = copy.deepcopy(instance.data["anatomyData"]) - - # required representation keys - files = repre['files'] - template_data["representation"] = repre["name"] - template_data["ext"] = repre["ext"] - - # optionals - # retrieve additional anatomy data from representation if exists - for key, anatomy_key in { - # Representation Key: Anatomy data key - "resolutionWidth": "resolution_width", - "resolutionHeight": "resolution_height", - "fps": "fps", - "outputName": "output", - "originalBasename": "originalBasename" - }.items(): - # Allow to take value from representation - # if not found also consider instance.data - if key in repre: - value = repre[key] - elif key in instance.data: - value = instance.data[key] - else: - continue - template_data[anatomy_key] = value - - if repre.get('stagingDir'): - stagingdir = repre['stagingDir'] - else: - # Fall back to instance staging dir if not explicitly - # set for representation in the instance - self.log.debug("Representation uses instance staging dir: " - "{}".format(instance_stagingdir)) - stagingdir = instance_stagingdir - if not stagingdir: - raise ValueError("No staging directory set for representation: " - "{}".format(repre)) - - self.log.debug("Anatomy template name: {}".format(template_name)) - anatomy = instance.context.data['anatomy'] - template = os.path.normpath(anatomy.templates[template_name]["path"]) - - is_udim = bool(repre.get("udim")) - is_sequence_representation = isinstance(files, (list, tuple)) - if is_sequence_representation: - # Collection of files (sequence) - assert not any(os.path.isabs(fname) for fname in files), ( - "Given file names contain full paths" + try: + filled_template = StringTemplate.format_strict_template( + template, fill_pairs ) + except (KeyError, TemplateUnsolved): + keys = [] + if fill_pairs: + keys = fill_pairs.keys() - src_collection = assemble(files) + msg = "Subset grouping failed. " \ + "Only {} are expected in Settings".format(','.join(keys)) + self.log.warning(msg) - # If the representation has `frameStart` set it renumbers the - # frame indices of the published collection. It will start from - # that `frameStart` index instead. Thus if that frame start - # differs from the collection we want to shift the destination - # frame indices from the source collection. - destination_indexes = list(src_collection.indexes) - destination_padding = len(get_first_frame_padded(src_collection)) - if repre.get("frameStart") is not None and not is_udim: - index_frame_start = int(repre.get("frameStart")) + return filled_template - render_template = anatomy.templates[template_name] - # todo: should we ALWAYS manage the frame padding even when not - # having `frameStart` set? - frame_start_padding = int( - render_template.get( - "frame_padding", - render_template.get("padding") - ) - ) - - # Shift destination sequence to the start frame - src_start_frame = next(iter(src_collection.indexes)) - shift = index_frame_start - src_start_frame - if shift: - destination_indexes = [ - frame + shift for frame in destination_indexes - ] - destination_padding = frame_start_padding - - # To construct the destination template with anatomy we require - # a Frame or UDIM tile set for the template data. We use the first - # index of the destination for that because that could've shifted - # from the source indexes, etc. - first_index_padded = get_frame_padded(frame=destination_indexes[0], - padding=destination_padding) - if is_udim: - # UDIM representations handle ranges in a different manner - template_data["udim"] = first_index_padded - else: - template_data["frame"] = first_index_padded - - # Construct destination collection from template - anatomy_filled = anatomy.format(template_data) - template_filled = anatomy_filled[template_name]["path"] - repre_context = template_filled.used_values - self.log.debug("Template filled: {}".format(str(template_filled))) - dst_collection = assemble([os.path.normpath(template_filled)]) - - # Update the destination indexes and padding - dst_collection.indexes.clear() - dst_collection.indexes.update(set(destination_indexes)) - dst_collection.padding = destination_padding - assert ( - len(src_collection.indexes) == len(dst_collection.indexes) - ), "This is a bug" - - # Multiple file transfers - transfers = [] - for src_file_name, dst in zip(src_collection, dst_collection): - src = os.path.join(stagingdir, src_file_name) - transfers.append((src, dst)) - - else: - # Single file - fname = files - assert not os.path.isabs(fname), ( - "Given file name is a full path" - ) - - # Manage anatomy template data - template_data.pop("frame", None) - if is_udim: - template_data["udim"] = repre["udim"][0] - - # Construct destination filepath from template - anatomy_filled = anatomy.format(template_data) - template_filled = anatomy_filled[template_name]["path"] - repre_context = template_filled.used_values - dst = os.path.normpath(template_filled) - - # Single file transfer - src = os.path.join(stagingdir, fname) - transfers = [(src, dst)] - - # todo: Are we sure the assumption each representation - # ends up in the same folder is valid? - if not instance.data.get("publishDir"): - instance.data["publishDir"] = ( - anatomy_filled - [template_name] - ["folder"] - ) - - for key in self.db_representation_context_keys: - # Also add these values to the context even if not used by the - # destination template - value = template_data.get(key) - if not value: - continue - repre_context[key] = template_data[key] - - # Explicitly store the full list even though template data might - # have a different value because it uses just a single udim tile - if repre.get("udim"): - repre_context["udim"] = repre.get("udim") # store list - - # Use previous representation's id if there is a name match - existing = existing_repres_by_name.get(repre["name"].lower()) - if existing: - repre_id = existing["_id"] - else: - repre_id = ObjectId() - - # Backwards compatibility: - # Store first transferred destination as published path data - # todo: can we remove this? - # todo: We shouldn't change data that makes its way back into - # instance.data[] until we know the publish actually succeeded - # otherwise `published_path` might not actually be valid? - published_path = transfers[0][1] - repre["published_path"] = published_path # Backwards compatibility - - # todo: `repre` is not the actual `representation` entity - # we should simplify/clarify difference between data above - # and the actual representation entity for the database - data = repre.get("data", {}) - data.update({'path': published_path, 'template': template}) - representation = { - "_id": repre_id, - "schema": "openpype:representation-2.0", - "type": "representation", - "parent": version["_id"], - "name": repre['name'], - "data": data, - - # Imprint shortcut to context for performance reasons. - "context": repre_context - } - - # todo: simplify/streamline which additional data makes its way into - # the representation context - if repre.get("outputName"): - representation["context"]["output"] = repre['outputName'] - - if is_sequence_representation and repre.get("frameStart") is not None: - representation['context']['frame'] = template_data["frame"] - - return { - "representation": representation, - "anatomy_data": template_data, - "transfers": transfers, - # todo: avoid the need for 'published_files' used by Integrate Hero - # backwards compatibility - "published_files": [transfer[1] for transfer in transfers] - } - - def create_version_data(self, instance): - """Create the data dictionary for the version + def create_version(self, subset, version_number, data=None): + """ Copy given source to destination Args: + subset (dict): the registered subset of the asset + version_number (int): the version number + + Returns: + dict: collection of data to create a version + """ + + return {"schema": "openpype:version-3.0", + "type": "version", + "parent": subset["_id"], + "name": version_number, + "data": data} + + def create_version_data(self, context, instance): + """Create the data collection for the version + + Args: + context: the current context instance: the current instance being published Returns: - dict: the required information for version["data"] + dict: the required information with instance.data as key """ - context = instance.context + families = [] + current_families = instance.data.get("families", list()) + instance_family = instance.data.get("family", None) + + if instance_family is not None: + families.append(instance_family) + families += current_families # create relative source path for DB - if "source" in instance.data: - source = instance.data["source"] - else: + source = instance.data.get("source") + if not source: source = context.data["currentFile"] anatomy = instance.context.data["anatomy"] source = self.get_rootless_path(anatomy, source) - self.log.debug("Source: {}".format(source)) + self.log.debug("Source: {}".format(source)) version_data = { - "families": get_instance_families(instance), + "families": families, "time": context.data["time"], "author": context.data["user"], "source": source, "comment": context.data.get("comment"), "machine": context.data.get("machine"), - "fps": instance.data.get("fps", context.data.get("fps")) + "fps": context.data.get( + "fps", instance.data.get("fps") + ) } - # todo: preferably we wouldn't need this "if dict" etc. logic and - # instead be able to rely what the input value is if it's set. - intent_value = context.data.get("intent") + intent_value = instance.context.data.get("intent") if intent_value and isinstance(intent_value, dict): intent_value = intent_value.get("value") @@ -732,58 +994,33 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if key in instance.data: version_data[key] = instance.data[key] - # Include instance.data[versionData] directly - version_data_instance = instance.data.get('versionData') - if version_data_instance: - version_data.update(version_data_instance) - return version_data - def get_template_name(self, instance): - """Return anatomy template name to use for integration""" - # Define publish template name from profiles - filter_criteria = self.get_profile_filter_criteria(instance) - profile = filter_profiles(self.template_name_profiles, - filter_criteria, - logger=self.log) - if profile: - return profile["template_name"] - else: - return self.default_template_name - - def get_profile_filter_criteria(self, instance): - """Return filter criteria for `filter_profiles`""" - # Anatomy data is pre-filled by Collectors - anatomy_data = instance.data["anatomyData"] - - # Task can be optional in anatomy data - task = anatomy_data.get("task", {}) - - # Return filter criteria - return { - "families": anatomy_data["family"], - "tasks": task.get("name"), - "hosts": anatomy_data["app"], - "task_types": task.get("type") - } + def main_family_from_instance(self, instance): + """Returns main family of entered instance.""" + family = instance.data.get("family") + if not family: + family = instance.data["families"][0] + return family def get_rootless_path(self, anatomy, path): - """Returns, if possible, path without absolute portion from root - (eg. 'c:\' or '/opt/..') - - This information is platform dependent and shouldn't be captured. - Example: - 'c:/projects/MyProject1/Assets/publish...' > - '{root}/MyProject1/Assets...' + """ Returns, if possible, path without absolute portion from host + (eg. 'c:\' or '/opt/..') + This information is host dependent and shouldn't be captured. + Example: + 'c:/projects/MyProject1/Assets/publish...' > + '{root}/MyProject1/Assets...' Args: - anatomy: anatomy part from instance - path: path (absolute) + anatomy: anatomy part from instance + path: path (absolute) Returns: - path: modified path if possible, or unmodified path - + warning logged + path: modified path if possible, or unmodified path + + warning logged """ - success, rootless_path = anatomy.find_root_template_from_path(path) + success, rootless_path = ( + anatomy.find_root_template_from_path(path) + ) if success: path = rootless_path else: @@ -793,40 +1030,269 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): ).format(path)) return path - def get_files_info(self, destinations, sites, anatomy): - """Prepare 'files' info portion for representations. + def get_files_info(self, instance, integrated_file_sizes): + """ Prepare 'files' portion for attached resources and main asset. + Combining records from 'transfers' and 'hardlinks' parts from + instance. + All attached resources should be added, currently without + Context info. Arguments: - destinations (list): List of transferred file destinations - sites (list): array of published locations - anatomy: anatomy part from instance + instance: the current instance being published + integrated_file_sizes: dictionary of destination path (absolute) + and its file size Returns: output_resources: array of dictionaries to be added to 'files' key in representation """ - file_infos = [] - for file_path in destinations: - file_info = self.prepare_file_info(file_path, anatomy, sites=sites) - file_infos.append(file_info) - return file_infos + resources = list(instance.data.get("transfers", [])) + resources.extend(list(instance.data.get("hardlinks", []))) - def prepare_file_info(self, path, anatomy, sites): + self.log.debug("get_resource_files_info.resources:{}". + format(resources)) + + output_resources = [] + anatomy = instance.context.data["anatomy"] + for _src, dest in resources: + path = self.get_rootless_path(anatomy, dest) + dest = self.get_dest_temp_url(dest) + file_hash = openpype.api.source_hash(dest) + if self.TMP_FILE_EXT and \ + ',{}'.format(self.TMP_FILE_EXT) in file_hash: + file_hash = file_hash.replace(',{}'.format(self.TMP_FILE_EXT), + '') + + file_info = self.prepare_file_info(path, + integrated_file_sizes[dest], + file_hash, + instance=instance) + output_resources.append(file_info) + + return output_resources + + def get_dest_temp_url(self, dest): + """ Enhance destination path with TMP_FILE_EXT to denote temporary + file. + Temporary files will be renamed after successful registration + into DB and full copy to destination + + Arguments: + dest: destination url of published file (absolute) + Returns: + dest: destination path + '.TMP_FILE_EXT' + """ + if self.TMP_FILE_EXT and '.{}'.format(self.TMP_FILE_EXT) not in dest: + dest += '.{}'.format(self.TMP_FILE_EXT) + return dest + + def prepare_file_info(self, path, size=None, file_hash=None, + sites=None, instance=None): """ Prepare information for one file (asset or resource) Arguments: - path: destination url of published file - anatomy: anatomy part from instance - sites: array of published locations, - [ {'name':'studio', 'created_dt':date} by default - keys expected ['studio', 'site1', 'gdrive1'] - + path: destination url of published file (rootless) + size(optional): size of file in bytes + file_hash(optional): hash of file for synchronization validation + sites(optional): array of published locations, + [ {'name':'studio', 'created_dt':date} by default + keys expected ['studio', 'site1', 'gdrive1'] + instance(dict, optional): to get collected settings Returns: - dict: file info dictionary + rec: dictionary with filled info """ - return { + local_site = 'studio' # default + remote_site = None + always_accesible = [] + sync_project_presets = None + + rec = { "_id": ObjectId(), - "path": self.get_rootless_path(anatomy, path), - "size": os.path.getsize(path), - "hash": openpype.api.source_hash(path), - "sites": sites + "path": path } + if size: + rec["size"] = size + + if file_hash: + rec["hash"] = file_hash + + if sites: + rec["sites"] = sites + else: + system_sync_server_presets = ( + instance.context.data["system_settings"] + ["modules"] + ["sync_server"]) + log.debug("system_sett:: {}".format(system_sync_server_presets)) + + if system_sync_server_presets["enabled"]: + sync_project_presets = ( + instance.context.data["project_settings"] + ["global"] + ["sync_server"]) + + if sync_project_presets and sync_project_presets["enabled"]: + local_site, remote_site = self._get_sites(sync_project_presets) + + always_accesible = sync_project_presets["config"]. \ + get("always_accessible_on", []) + + already_attached_sites = {} + meta = {"name": local_site, "created_dt": datetime.now()} + rec["sites"] = [meta] + already_attached_sites[meta["name"]] = meta["created_dt"] + + if sync_project_presets and sync_project_presets["enabled"]: + if remote_site and \ + remote_site not in already_attached_sites.keys(): + # add remote + meta = {"name": remote_site.strip()} + rec["sites"].append(meta) + already_attached_sites[meta["name"]] = None + + # add alternative sites + rec, already_attached_sites = self._add_alternative_sites( + system_sync_server_presets, already_attached_sites, rec) + + # add skeleton for site where it should be always synced to + for always_on_site in set(always_accesible): + if always_on_site not in already_attached_sites.keys(): + meta = {"name": always_on_site.strip()} + rec["sites"].append(meta) + already_attached_sites[meta["name"]] = None + + log.debug("final sites:: {}".format(rec["sites"])) + + return rec + + def _get_sites(self, sync_project_presets): + """Returns tuple (local_site, remote_site)""" + local_site_id = openpype.api.get_local_site_id() + local_site = sync_project_presets["config"]. \ + get("active_site", "studio").strip() + + if local_site == 'local': + local_site = local_site_id + + remote_site = sync_project_presets["config"].get("remote_site") + + if remote_site == 'local': + remote_site = local_site_id + + return local_site, remote_site + + def _add_alternative_sites(self, + system_sync_server_presets, + already_attached_sites, + rec): + """Loop through all configured sites and add alternatives. + + See SyncServerModule.handle_alternate_site + """ + conf_sites = system_sync_server_presets.get("sites", {}) + + alt_site_pairs = self._get_alt_site_pairs(conf_sites) + + already_attached_keys = list(already_attached_sites.keys()) + for added_site in already_attached_keys: + real_created = already_attached_sites[added_site] + for alt_site in alt_site_pairs.get(added_site, []): + if alt_site in already_attached_sites.keys(): + continue + meta = {"name": alt_site} + # alt site inherits state of 'created_dt' + if real_created: + meta["created_dt"] = real_created + rec["sites"].append(meta) + already_attached_sites[meta["name"]] = real_created + + return rec, already_attached_sites + + def _get_alt_site_pairs(self, conf_sites): + """Returns dict of site and its alternative sites. + + If `site` has alternative site, it means that alt_site has 'site' as + alternative site + Args: + conf_sites (dict) + Returns: + (dict): {'site': [alternative sites]...} + """ + alt_site_pairs = defaultdict(list) + for site_name, site_info in conf_sites.items(): + alt_sites = set(site_info.get("alternative_sites", [])) + alt_site_pairs[site_name].extend(alt_sites) + + for alt_site in alt_sites: + alt_site_pairs[alt_site].append(site_name) + + for site_name, alt_sites in alt_site_pairs.items(): + sites_queue = deque(alt_sites) + while sites_queue: + alt_site = sites_queue.popleft() + + # safety against wrong config + # {"SFTP": {"alternative_site": "SFTP"} + if alt_site == site_name or alt_site not in alt_site_pairs: + continue + + for alt_alt_site in alt_site_pairs[alt_site]: + if ( + alt_alt_site != site_name + and alt_alt_site not in alt_sites + ): + alt_sites.append(alt_alt_site) + sites_queue.append(alt_alt_site) + + return alt_site_pairs + + def handle_destination_files(self, integrated_file_sizes, mode): + """ Clean destination files + Called when error happened during integrating to DB or to disk + OR called to rename uploaded files from temporary name to final to + highlight publishing in progress/broken + Used to clean unwanted files + + Arguments: + integrated_file_sizes: dictionary, file urls as keys, size as value + mode: 'remove' - clean files, + 'finalize' - rename files, + remove TMP_FILE_EXT suffix denoting temp file + """ + if integrated_file_sizes: + for file_url, _file_size in integrated_file_sizes.items(): + if not os.path.exists(file_url): + self.log.debug( + "File {} was not found.".format(file_url) + ) + continue + + try: + if mode == 'remove': + self.log.debug("Removing file {}".format(file_url)) + os.remove(file_url) + if mode == 'finalize': + new_name = re.sub( + r'\.{}$'.format(self.TMP_FILE_EXT), + '', + file_url + ) + + if os.path.exists(new_name): + self.log.debug( + "Overwriting file {} to {}".format( + file_url, new_name + ) + ) + shutil.copy(file_url, new_name) + os.remove(file_url) + else: + self.log.debug( + "Renaming file {} to {}".format( + file_url, new_name + ) + ) + os.rename(file_url, new_name) + except OSError: + self.log.error("Cannot {} file {}".format(mode, file_url), + exc_info=True) + six.reraise(*sys.exc_info()) From 271a829f6d441bcf26e6ddaf33510f984dc0c703 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Jul 2022 09:24:38 +0200 Subject: [PATCH 0989/1227] Remove duplicate source family --- openpype/plugins/publish/integrate_new.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 4c14c17dae..fd3cf8882d 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -92,7 +92,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "source", "matchmove", "image", - "source", "assembly", "fbx", "textures", From 148ac26bf961aa8e44ffcd453efbdbb0f4a8df75 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Jul 2022 09:28:00 +0200 Subject: [PATCH 0990/1227] Update USD families with latest develop --- openpype/plugins/publish/integrate.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 6ad0849ff7..6253a3ec11 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -156,8 +156,10 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "usd", "staticMesh", "skeletalMesh", - "usdComposition", - "usdOverride", + "mvLook", + "mvUsd", + "mvUsdComposition", + "mvUsdOverride", "simpleUnrealTexture" ] exclude_families = ["clip", "render.farm"] From 035c4d2f93fd0a29ba8f6f1789a327878861284a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Jul 2022 09:30:07 +0200 Subject: [PATCH 0991/1227] Set up old vs. new integrator per host --- openpype/plugins/publish/integrate.py | 1 + openpype/plugins/publish/integrate_new.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 6253a3ec11..d098147603 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -110,6 +110,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): label = "Integrate Asset New" order = pyblish.api.IntegratorOrder + hosts = ["maya"] families = ["workfile", "pointcache", "camera", diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index fd3cf8882d..c9848abc14 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -62,6 +62,10 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): label = "Integrate Asset New" order = pyblish.api.IntegratorOrder + hosts = ["aftereffects", "blender", "celaction", "flame", "harmony", + "hiero", "houdini", "nuke", "photoshop", "resolve", + "standalonepublisher", "traypublisher", "tvpaint", "unreal", + "webpublisher"] families = ["workfile", "pointcache", "camera", From a3757636e7705b34699adff7e1e23f7ff57284d6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Jul 2022 09:31:53 +0200 Subject: [PATCH 0992/1227] Remove 'intent' context data override @iLLiCiTiT says: Intent should be a dictionary with "value" and "label", to be able tell if you want use value or label of the intent in templates. --- openpype/plugins/publish/collect_anatomy_context_data.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/openpype/plugins/publish/collect_anatomy_context_data.py b/openpype/plugins/publish/collect_anatomy_context_data.py index 8db9d0d3d7..0794adfb67 100644 --- a/openpype/plugins/publish/collect_anatomy_context_data.py +++ b/openpype/plugins/publish/collect_anatomy_context_data.py @@ -92,13 +92,5 @@ class CollectAnatomyContextData(pyblish.api.ContextPlugin): } }) - # todo: some code actually expects the dict itself and others doesn't - # question: what should it be? - intent = context.data.get("intent") - if intent and isinstance(intent, dict): - intent = intent.get("value") - if intent: - context_data["intent"] = intent - self.log.info("Global anatomy Data collected") self.log.debug(json.dumps(context_data, indent=4)) From deaeed3f0b5b7eb602efa0b23a4e34ec1774571a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 Jul 2022 10:35:42 +0200 Subject: [PATCH 0993/1227] nuke: make it clean nodes again --- openpype/hosts/nuke/plugins/publish/extract_slate_frame.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index 6997180c9f..99ade4cf9b 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -230,8 +230,8 @@ class ExtractSlateFrame(openpype.api.Extractor): write_node.name(), int(slate_first_frame), int(slate_first_frame)) # Clean up - # for node in temporary_nodes: - # nuke.delete(node) + for node in temporary_nodes: + nuke.delete(node) def _render_slate_to_sequence(self, instance): # set slate frame From b4697b6e1a0cc778765d617b68d1e516ca7dcea9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Jul 2022 10:36:37 +0200 Subject: [PATCH 0994/1227] Refactor integrator labels --- openpype/plugins/publish/integrate.py | 2 +- openpype/plugins/publish/integrate_new.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index d098147603..5e86eb014a 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -108,7 +108,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "data": additional metadata for each representation. """ - label = "Integrate Asset New" + label = "Integrate Asset" order = pyblish.api.IntegratorOrder hosts = ["maya"] families = ["workfile", diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index c9848abc14..baa14b285c 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -60,7 +60,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "data": additional metadata for each representation. """ - label = "Integrate Asset New" + label = "Integrate Asset (legacy)" order = pyblish.api.IntegratorOrder hosts = ["aftereffects", "blender", "celaction", "flame", "harmony", "hiero", "houdini", "nuke", "photoshop", "resolve", From b96cb503dafcbfed7adf144d863096adf2fd1e39 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 Jul 2022 11:35:07 +0200 Subject: [PATCH 0995/1227] flame: fix loading with missing template part --- openpype/hosts/flame/plugins/load/load_clip.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/plugins/load/load_clip.py b/openpype/hosts/flame/plugins/load/load_clip.py index e0a7297381..0b049214ff 100644 --- a/openpype/hosts/flame/plugins/load/load_clip.py +++ b/openpype/hosts/flame/plugins/load/load_clip.py @@ -2,7 +2,7 @@ import os import flame from pprint import pformat import openpype.hosts.flame.api as opfapi - +from openpype.lib import StringTemplate class LoadClip(opfapi.ClipLoader): """Load a subset to timeline as clip @@ -22,7 +22,7 @@ class LoadClip(opfapi.ClipLoader): # settings reel_group_name = "OpenPype_Reels" reel_name = "Loaded" - clip_name_template = "{asset}_{subset}_{output}" + clip_name_template = "{asset}_{subset}<_{output}>" def load(self, context, name, namespace, options): @@ -36,7 +36,7 @@ class LoadClip(opfapi.ClipLoader): version_data = version.get("data", {}) version_name = version.get("name", None) colorspace = version_data.get("colorspace", None) - clip_name = self.clip_name_template.format( + clip_name = StringTemplate(self.clip_name_template).format( **context["representation"]["context"]) # TODO: settings in imageio From 110ed4d752cdf5a10086b8029dfae118d4ffca2b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 Jul 2022 11:40:31 +0200 Subject: [PATCH 0996/1227] flame: fixing loading name template --- openpype/hosts/flame/plugins/load/load_clip.py | 2 +- openpype/hosts/flame/plugins/load/load_clip_batch.py | 7 ++++--- openpype/settings/defaults/project_settings/flame.json | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/flame/plugins/load/load_clip.py b/openpype/hosts/flame/plugins/load/load_clip.py index 0b049214ff..b12f2f9690 100644 --- a/openpype/hosts/flame/plugins/load/load_clip.py +++ b/openpype/hosts/flame/plugins/load/load_clip.py @@ -37,7 +37,7 @@ class LoadClip(opfapi.ClipLoader): version_name = version.get("name", None) colorspace = version_data.get("colorspace", None) clip_name = StringTemplate(self.clip_name_template).format( - **context["representation"]["context"]) + context["representation"]["context"]) # TODO: settings in imageio # convert colorspace with ocio to flame mapping diff --git a/openpype/hosts/flame/plugins/load/load_clip_batch.py b/openpype/hosts/flame/plugins/load/load_clip_batch.py index 5de3226035..fb4a3dc6e9 100644 --- a/openpype/hosts/flame/plugins/load/load_clip_batch.py +++ b/openpype/hosts/flame/plugins/load/load_clip_batch.py @@ -2,6 +2,7 @@ import os import flame from pprint import pformat import openpype.hosts.flame.api as opfapi +from openpype.lib import StringTemplate class LoadClipBatch(opfapi.ClipLoader): @@ -21,7 +22,7 @@ class LoadClipBatch(opfapi.ClipLoader): # settings reel_name = "OP_LoadedReel" - clip_name_template = "{asset}_{subset}_{output}" + clip_name_template = "{asset}_{subset}<_{output}>" def load(self, context, name, namespace, options): @@ -39,8 +40,8 @@ class LoadClipBatch(opfapi.ClipLoader): if not context["representation"]["context"].get("output"): self.clip_name_template.replace("output", "representation") - clip_name = self.clip_name_template.format( - **context["representation"]["context"]) + clip_name = StringTemplate(self.clip_name_template).format( + context["representation"]["context"]) # TODO: settings in imageio # convert colorspace with ocio to flame mapping diff --git a/openpype/settings/defaults/project_settings/flame.json b/openpype/settings/defaults/project_settings/flame.json index a7836b9c1f..bfdc58d9ee 100644 --- a/openpype/settings/defaults/project_settings/flame.json +++ b/openpype/settings/defaults/project_settings/flame.json @@ -98,7 +98,7 @@ ], "reel_group_name": "OpenPype_Reels", "reel_name": "Loaded", - "clip_name_template": "{asset}_{subset}_{output}" + "clip_name_template": "{asset}_{subset}<_{output}>" }, "LoadClipBatch": { "enabled": true, @@ -121,7 +121,7 @@ "exr16fpdwaa" ], "reel_name": "OP_LoadedReel", - "clip_name_template": "{asset}_{subset}_{output}" + "clip_name_template": "{asset}_{subset}<_{output}>" } } } \ No newline at end of file From 6e3fd1122bc9614d124b8bc49edac70c121bb39c Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Tue, 5 Jul 2022 17:13:11 +0200 Subject: [PATCH 0997/1227] Kitsu bugfix update assets attributes --- .../plugins/publish/collect_kitsu_entities.py | 18 ++++--- openpype/modules/kitsu/utils/sync_service.py | 32 +++++++----- .../modules/kitsu/utils/update_op_with_zou.py | 52 ++++++++++++------- 3 files changed, 65 insertions(+), 37 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index 84c400bde9..d28ded06c7 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -32,11 +32,17 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): context.data["kitsu_project"] = kitsu_project self.log.debug("Collect kitsu project: {}".format(kitsu_project)) - kitsu_asset = gazu.asset.get_asset(zou_asset_data["id"]) - if not kitsu_asset: - raise AssertionError("Asset not found in kitsu!") - context.data["kitsu_asset"] = kitsu_asset - self.log.debug("Collect kitsu asset: {}".format(kitsu_asset)) + entity_type = zou_asset_data["type"] + if entity_type == "Shot": + kitsu_entity = gazu.shot.get_shot(zou_asset_data["id"]) + else: + kitsu_entity = gazu.asset.get_asset(zou_asset_data["id"]) + + if not kitsu_entity: + raise AssertionError(f"{entity_type} not found in kitsu!") + + context.data["kitsu_entity"] = kitsu_entity + self.log.debug(f"Collect kitsu {entity_type}: {kitsu_entity}") if zou_task_data: kitsu_task = gazu.task.get_task(zou_task_data["id"]) @@ -57,7 +63,7 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): ) kitsu_task = gazu.task.get_task_by_name( - kitsu_asset, kitsu_task_type + kitsu_entity, kitsu_task_type ) if not kitsu_task: raise AssertionError("Task not found in kitsu!") diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 6c003942f8..577050c5af 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -165,10 +165,12 @@ class Listener: zou_ids_and_asset_docs[asset["project_id"]] = project_doc # Update - asset_doc_id, asset_update = update_op_assets( + update_op_result = update_op_assets( self.dbcon, project_doc, [asset], zou_ids_and_asset_docs - )[0] - self.dbcon.update_one({"_id": asset_doc_id}, asset_update) + ) + if update_op_result: + asset_doc_id, asset_update = update_op_result[0] + self.dbcon.update_one({"_id": asset_doc_id}, asset_update) def _delete_asset(self, data): """Delete asset of OP DB.""" @@ -212,10 +214,12 @@ class Listener: zou_ids_and_asset_docs[episode["project_id"]] = project_doc # Update - asset_doc_id, asset_update = update_op_assets( + update_op_result = update_op_assets( self.dbcon, project_doc, [episode], zou_ids_and_asset_docs - )[0] - self.dbcon.update_one({"_id": asset_doc_id}, asset_update) + ) + if update_op_result: + asset_doc_id, asset_update = update_op_result[0] + self.dbcon.update_one({"_id": asset_doc_id}, asset_update) def _delete_episode(self, data): """Delete shot of OP DB.""" @@ -260,10 +264,12 @@ class Listener: zou_ids_and_asset_docs[sequence["project_id"]] = project_doc # Update - asset_doc_id, asset_update = update_op_assets( + update_op_result = update_op_assets( self.dbcon, project_doc, [sequence], zou_ids_and_asset_docs - )[0] - self.dbcon.update_one({"_id": asset_doc_id}, asset_update) + ) + if update_op_result: + asset_doc_id, asset_update = update_op_result[0] + self.dbcon.update_one({"_id": asset_doc_id}, asset_update) def _delete_sequence(self, data): """Delete sequence of OP DB.""" @@ -308,10 +314,12 @@ class Listener: zou_ids_and_asset_docs[shot["project_id"]] = project_doc # Update - asset_doc_id, asset_update = update_op_assets( + update_op_result = update_op_assets( self.dbcon, project_doc, [shot], zou_ids_and_asset_docs - )[0] - self.dbcon.update_one({"_id": asset_doc_id}, asset_update) + ) + if update_op_result: + asset_doc_id, asset_update = update_op_result[0] + self.dbcon.update_one({"_id": asset_doc_id}, asset_update) def _delete_shot(self, data): """Delete shot of OP DB.""" diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index cd98c0d204..f8ee6688d2 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -82,22 +82,37 @@ def update_op_assets( item_data["zou"] = item # == Asset settings == - # Frame in, fallback on 0 - frame_in = int(item_data.get("frame_in") or 0) + # Frame in, fallback to project's value or default value (1001) + # TODO: get default from settings/project_anatomy/attributes.json + try: + frame_in = int( + item_data.pop( + "frame_in", project_doc["data"].get("frameStart") + ) + ) + except (TypeError, ValueError): + frame_in = 1001 item_data["frameStart"] = frame_in - item_data.pop("frame_in", None) - # Frame out, fallback on frame_in + duration - frames_duration = int(item.get("nb_frames") or 1) - frame_out = ( - item_data["frame_out"] - if item_data.get("frame_out") - else frame_in + frames_duration - ) - item_data["frameEnd"] = int(frame_out) - item_data.pop("frame_out", None) - # Fps, fallback to project's value when entity fps is deleted - if not item_data.get("fps") and item_doc["data"].get("fps"): - item_data["fps"] = project_doc["data"]["fps"] + # Frames duration, fallback on 0 + try: + frames_duration = int(item_data.pop("nb_frames", 0)) + except (TypeError, ValueError): + frames_duration = 0 + # Frame out, fallback on frame_in + duration or project's value or 1001 + frame_out = item_data.pop("frame_out", None) + if not frame_out: + frame_out = frame_in + frames_duration + try: + frame_out = int(frame_out) + except (TypeError, ValueError): + frame_out = 1001 + item_data["frameEnd"] = frame_out + # Fps, fallback to project's value or default value (25.0) + try: + fps = float(item_data.get("fps", project_doc["data"].get("fps"))) + except (TypeError, ValueError): + fps = 25.0 + item_data["fps"] = fps # Tasks tasks_list = [] @@ -106,7 +121,6 @@ def update_op_assets( tasks_list = all_tasks_for_asset(item) elif item_type == "Shot": tasks_list = all_tasks_for_shot(item) - # TODO frame in and out item_data["tasks"] = { t["task_type_name"]: {"type": t["task_type_name"]} for t in tasks_list @@ -229,9 +243,9 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: project_data.update( { "code": project_code, - "fps": project["fps"], - "resolutionWidth": project["resolution"].split("x")[0], - "resolutionHeight": project["resolution"].split("x")[1], + "fps": float(project["fps"]), + "resolutionWidth": int(project["resolution"].split("x")[0]), + "resolutionHeight": int(project["resolution"].split("x")[1]), "zou_id": project["id"], } ) From 603d193bc823f3fb7c1fc848125844841c65c366 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 6 Jul 2022 04:04:01 +0000 Subject: [PATCH 0998/1227] [Automated] Bump version --- CHANGELOG.md | 27 ++++++++++++--------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b5d40a52f..0b43d2fed1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [3.12.1-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.12.1-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.0...HEAD) @@ -14,8 +14,10 @@ **🚀 Enhancements** +- Windows installer: Clean old files and add version subfolder [\#3445](https://github.com/pypeclub/OpenPype/pull/3445) - Blender: Bugfix - Set fps properly on open [\#3426](https://github.com/pypeclub/OpenPype/pull/3426) - Blender: pre pyside install for all platforms [\#3400](https://github.com/pypeclub/OpenPype/pull/3400) +- Maya: Ability to set resolution for playblasts from asset, and override through review instance. [\#3360](https://github.com/pypeclub/OpenPype/pull/3360) **🐛 Bug fixes** @@ -23,13 +25,20 @@ - Maya: fix hashing in Python 3 for tile rendering [\#3447](https://github.com/pypeclub/OpenPype/pull/3447) - LogViewer: Escape html characters in log message [\#3443](https://github.com/pypeclub/OpenPype/pull/3443) - Nuke: Slate frame is integrated [\#3427](https://github.com/pypeclub/OpenPype/pull/3427) +- Maya: Camera extra data - additional fix for \#3304 [\#3386](https://github.com/pypeclub/OpenPype/pull/3386) +- Harmony: added unc path to zifile command in Harmony [\#3372](https://github.com/pypeclub/OpenPype/pull/3372) +- Maya: Handle excluding `model` family from frame range validator. [\#3370](https://github.com/pypeclub/OpenPype/pull/3370) **🔀 Refactored code** +- Maya: Re-use `maintained\_time` from lib [\#3460](https://github.com/pypeclub/OpenPype/pull/3460) - Clockify: Use query functions in clockify actions [\#3458](https://github.com/pypeclub/OpenPype/pull/3458) - General: Use query functions in rest api calls [\#3457](https://github.com/pypeclub/OpenPype/pull/3457) +- General: Use query functions in load utils [\#3446](https://github.com/pypeclub/OpenPype/pull/3446) - General: Use Anatomy after move to pipeline [\#3436](https://github.com/pypeclub/OpenPype/pull/3436) - General: Anatomy moved to pipeline [\#3435](https://github.com/pypeclub/OpenPype/pull/3435) +- Resolve: Use client query functions [\#3379](https://github.com/pypeclub/OpenPype/pull/3379) +- General: Host implementation defined with class [\#3337](https://github.com/pypeclub/OpenPype/pull/3337) ## [3.12.0](https://github.com/pypeclub/OpenPype/tree/3.12.0) (2022-06-28) @@ -44,7 +53,6 @@ - Webserver: Added CORS middleware [\#3422](https://github.com/pypeclub/OpenPype/pull/3422) - Attribute Defs UI: Files widget show what is allowed to drop in [\#3411](https://github.com/pypeclub/OpenPype/pull/3411) -- General: Add ability to change user value for templates [\#3366](https://github.com/pypeclub/OpenPype/pull/3366) - Hosts: More options for in-host callbacks [\#3357](https://github.com/pypeclub/OpenPype/pull/3357) - Multiverse: expose some settings to GUI [\#3350](https://github.com/pypeclub/OpenPype/pull/3350) @@ -60,7 +68,6 @@ - TVPaint: Make sure exit code is set to not None [\#3382](https://github.com/pypeclub/OpenPype/pull/3382) - Maya: vray device aspect ratio fix [\#3381](https://github.com/pypeclub/OpenPype/pull/3381) - Flame: bunch of publishing issues [\#3377](https://github.com/pypeclub/OpenPype/pull/3377) -- Harmony: added unc path to zifile command in Harmony [\#3372](https://github.com/pypeclub/OpenPype/pull/3372) - Standalone: settings improvements [\#3355](https://github.com/pypeclub/OpenPype/pull/3355) - Nuke: Load full model hierarchy by default [\#3328](https://github.com/pypeclub/OpenPype/pull/3328) @@ -70,9 +77,9 @@ - General: Move editorial lib to pipeline [\#3419](https://github.com/pypeclub/OpenPype/pull/3419) - Kitsu: renaming to plural func sync\_all\_projects [\#3397](https://github.com/pypeclub/OpenPype/pull/3397) - Houdini: Use client query functions [\#3395](https://github.com/pypeclub/OpenPype/pull/3395) -- Hiero: Use client query functions [\#3393](https://github.com/pypeclub/OpenPype/pull/3393) - Nuke: Use client query functions [\#3391](https://github.com/pypeclub/OpenPype/pull/3391) - Maya: Use client query functions [\#3385](https://github.com/pypeclub/OpenPype/pull/3385) +- Fusion: Use client query functions [\#3380](https://github.com/pypeclub/OpenPype/pull/3380) - Harmony: Use client query functions [\#3378](https://github.com/pypeclub/OpenPype/pull/3378) - Celaction: Use client query functions [\#3376](https://github.com/pypeclub/OpenPype/pull/3376) - Photoshop: Use client query functions [\#3375](https://github.com/pypeclub/OpenPype/pull/3375) @@ -98,9 +105,9 @@ **🚀 Enhancements** - Pyblish Pype: Hiding/Close issues [\#3367](https://github.com/pypeclub/OpenPype/pull/3367) +- General: Add ability to change user value for templates [\#3366](https://github.com/pypeclub/OpenPype/pull/3366) - Ftrack: Removed requirement of pypeclub role from default settings [\#3354](https://github.com/pypeclub/OpenPype/pull/3354) - Kitsu: Prevent crash on missing frames information [\#3352](https://github.com/pypeclub/OpenPype/pull/3352) -- Ftrack: Open browser from tray [\#3320](https://github.com/pypeclub/OpenPype/pull/3320) **🐛 Bug fixes** @@ -126,26 +133,16 @@ **🚀 Enhancements** - Settings: Settings can be extracted from UI [\#3323](https://github.com/pypeclub/OpenPype/pull/3323) -- updated poetry installation source [\#3316](https://github.com/pypeclub/OpenPype/pull/3316) -- Ftrack: Action to easily create daily review session [\#3310](https://github.com/pypeclub/OpenPype/pull/3310) -- TVPaint: Extractor use mark in/out range to render [\#3309](https://github.com/pypeclub/OpenPype/pull/3309) -- Ftrack: Delivery action can work on ReviewSessions [\#3307](https://github.com/pypeclub/OpenPype/pull/3307) **🐛 Bug fixes** - General: Handle empty source key on instance [\#3342](https://github.com/pypeclub/OpenPype/pull/3342) - Houdini: Fix Houdini VDB manage update wrong file attribute name [\#3322](https://github.com/pypeclub/OpenPype/pull/3322) -- Nuke: anatomy compatibility issue hacks [\#3321](https://github.com/pypeclub/OpenPype/pull/3321) -- hiero: otio p3 compatibility issue - metadata on effect use update 3.11 [\#3314](https://github.com/pypeclub/OpenPype/pull/3314) **🔀 Refactored code** - Blender: Use client query functions [\#3331](https://github.com/pypeclub/OpenPype/pull/3331) -**Merged pull requests:** - -- Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) - ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.10.0-nightly.6...3.10.0) diff --git a/openpype/version.py b/openpype/version.py index 92cdcf9fdd..3cb7f4572b 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.12.1-nightly.2" +__version__ = "3.12.1-nightly.3" diff --git a/pyproject.toml b/pyproject.toml index 401b24243b..4803249923 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.12.1-nightly.2" # OpenPype +version = "3.12.1-nightly.3" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 4ddebd51790b994048667edbbf150b557823a360 Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Wed, 6 Jul 2022 14:49:08 +0200 Subject: [PATCH 0999/1227] Kitsu update tasks with zou key --- openpype/modules/kitsu/utils/update_op_with_zou.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index f8ee6688d2..de74b0c677 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -122,7 +122,7 @@ def update_op_assets( elif item_type == "Shot": tasks_list = all_tasks_for_shot(item) item_data["tasks"] = { - t["task_type_name"]: {"type": t["task_type_name"]} + t["task_type_name"]: {"type": t["task_type_name"], "zou": t} for t in tasks_list } From f76d84dff177c31f46d369f0ccd97d0a253251a7 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 6 Jul 2022 22:57:02 +0300 Subject: [PATCH 1000/1227] Append "Depth of Field" to plablast options and function. --- openpype/hosts/maya/api/lib.py | 4 ++++ openpype/settings/defaults/project_settings/maya.json | 1 + .../schemas/projects_schema/schemas/schema_maya_capture.json | 5 +++++ openpype/vendor/python/common/capture.py | 3 ++- 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index cd41ba3ffd..a159554b54 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2522,6 +2522,9 @@ def load_capture_preset(data=None): temp_options2['multiSampleEnable'] = False temp_options2['multiSampleCount'] = preset[id][key] + if key == 'renderDepthOfField': + temp_options2['renderDepthOfField'] = preset[id][key] + if key == 'ssaoEnable': if preset[id][key] is True: temp_options2['ssaoEnable'] = True @@ -2618,6 +2621,7 @@ def load_capture_preset(data=None): 'motionBlurSampleCount', 'motionBlurShutterOpenFraction', 'lineAAEnable', + 'renderDepthOfField' ]: temp_options.pop(key, None) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index bb7719dc30..39a8688267 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -496,6 +496,7 @@ "override_viewport_options": true, "displayLights": "default", "textureMaxResolution": 1024, + "renderDepthOfField": true, "shadows": true, "textures": true, "twoSidedLighting": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 0a63315622..7a40f349cc 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -204,6 +204,11 @@ { "type": "splitter" }, + { + "type":"boolean", + "key": "renderDepthOfField", + "label": "Depth of Field" + }, { "type": "splitter" }, diff --git a/openpype/vendor/python/common/capture.py b/openpype/vendor/python/common/capture.py index 6b4c40a6e8..4d9e1da3e4 100644 --- a/openpype/vendor/python/common/capture.py +++ b/openpype/vendor/python/common/capture.py @@ -380,7 +380,8 @@ Viewport2Options = { "transparencyAlgorithm": 1, "transparencyQuality": 0.33, "useMaximumHardwareLights": True, - "vertexAnimationCache": 0 + "vertexAnimationCache": 0, + "renderDepthOfField": 0 } From d9d8f7c315b6592d305cd8b4d045a0def6f49644 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 7 Jul 2022 15:37:21 +0200 Subject: [PATCH 1001/1227] fix add missing project name in args --- openpype/pipeline/load/utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/load/utils.py b/openpype/pipeline/load/utils.py index 2c1b2ea8ea..2c213aff6f 100644 --- a/openpype/pipeline/load/utils.py +++ b/openpype/pipeline/load/utils.py @@ -208,10 +208,12 @@ def get_representation_context(representation): assert representation is not None, "This is a bug" - if not isinstance(representation, dict): - representation = get_representation_by_id(representation) - project_name = legacy_io.active_project() + if not isinstance(representation, dict): + representation = get_representation_by_id( + project_name, representation + ) + version, subset, asset, project = get_representation_parents( project_name, representation ) From 5ad1f8ba7e110a8679df6581069c3a00035130df Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 7 Jul 2022 15:45:57 +0200 Subject: [PATCH 1002/1227] thumbnails --- .../{extract_jpeg_exr.py => extract_thumbnail.py} | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) rename openpype/plugins/publish/{extract_jpeg_exr.py => extract_thumbnail.py} (92%) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_thumbnail.py similarity index 92% rename from openpype/plugins/publish/extract_jpeg_exr.py rename to openpype/plugins/publish/extract_thumbnail.py index 42c4cbe062..7a438ca701 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -71,18 +71,12 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): if not is_oiio_supported(): thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa else: - # Check if the file can be read by OIIO - oiio_tool_path = get_oiio_tools_path() - args = [ - oiio_tool_path, "--info", "-i", full_output_path - ] - returncode = execute(args, silent=True) # If the input can read by OIIO then use OIIO method for # conversion otherwise use ffmpeg - if returncode == 0: - self.log.info("Input can be read by OIIO, converting with oiiotool now.") # noqa - thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa - else: + self.log.info("Trying to convert with OIIO") # noqa + thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa + + if not thumbnail_created: self.log.info("Converting with FFMPEG because input can't be read by OIIO.") # noqa thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa From 0665e5b75963036189fa8dfdae8f91af7e32c174 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 11:14:07 +0200 Subject: [PATCH 1003/1227] pass create context after settings --- openpype/pipeline/create/context.py | 2 +- openpype/pipeline/create/creator_plugins.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 12cd9bbc68..9f387ec676 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -873,9 +873,9 @@ class CreateContext: continue creator = creator_class( - self, system_settings, project_settings, + self, self.headless ) creators[creator_identifier] = creator diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 8006d4f4f8..1d09116fb6 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -70,7 +70,7 @@ class BaseCreator: host_name = None def __init__( - self, create_context, system_settings, project_settings, headless=False + self, system_settings, project_settings, create_context, headless=False ): # Reference to CreateContext self.create_context = create_context From 2e8e4f8f54fb026fffbff30e45532056e900f5a5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 11:30:49 +0200 Subject: [PATCH 1004/1227] pass project settings before system settings --- openpype/pipeline/create/context.py | 2 +- openpype/pipeline/create/creator_plugins.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 9f387ec676..260856286b 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -873,8 +873,8 @@ class CreateContext: continue creator = creator_class( - system_settings, project_settings, + system_settings, self, self.headless ) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 1d09116fb6..41847ac322 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -70,7 +70,7 @@ class BaseCreator: host_name = None def __init__( - self, system_settings, project_settings, create_context, headless=False + self, project_settings, system_settings, create_context, headless=False ): # Reference to CreateContext self.create_context = create_context From 56764f638fe9283d33a057d7ff22dfb1c055e992 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 11:34:08 +0200 Subject: [PATCH 1005/1227] modified after effects creator init to use new args order --- .../hosts/aftereffects/plugins/create/create_render.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index 215c148f37..1019709dd6 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -17,11 +17,8 @@ class RenderCreator(Creator): create_allow_context_change = True - def __init__( - self, create_context, system_settings, project_settings, headless=False - ): - super(RenderCreator, self).__init__(create_context, system_settings, - project_settings, headless) + def __init__(self, project_settings, *args, **kwargs): + super(RenderCreator, self).__init__(project_settings, *args, **kwargs) self._default_variants = (project_settings["aftereffects"] ["create"] ["RenderCreator"] From 4e81bd27a7759d2d4aa5aa4741dcf2881a8c1650 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 10:49:35 +0200 Subject: [PATCH 1006/1227] create plugins have access to project name --- openpype/pipeline/create/context.py | 4 ++++ openpype/pipeline/create/creator_plugins.py | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 260856286b..9d870a2234 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -748,6 +748,10 @@ class CreateContext: def host_name(self): return os.environ["AVALON_APP"] + @property + def project_name(self): + return self.dbcon.active_project() + @property def log(self): """Dynamic access to logger.""" diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 41847ac322..b888feb606 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -92,6 +92,12 @@ class BaseCreator: """Family that plugin represents.""" pass + @property + def project_name(self): + """Family that plugin represents.""" + + self.create_context.project_name + @property def log(self): if self._log is None: From bde50a36297ec2c7bc31b3ba64757008d7070ac4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 11:15:09 +0200 Subject: [PATCH 1007/1227] use project_name attribute --- openpype/pipeline/create/context.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 9d870a2234..7758a660d3 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -843,9 +843,8 @@ class CreateContext: self.plugins_with_defs = plugins_with_defs # Prepare settings - project_name = self.dbcon.Session["AVALON_PROJECT"] system_settings = get_system_settings() - project_settings = get_project_settings(project_name) + project_settings = get_project_settings(self.project_name) # Discover and prepare creators creators = {} From 85485f26d8f7d3b387f67f26e31d9ff075ad817d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 17:15:38 +0200 Subject: [PATCH 1008/1227] fix return of value --- openpype/pipeline/create/creator_plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index b888feb606..4c7b9f8d94 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -96,7 +96,7 @@ class BaseCreator: def project_name(self): """Family that plugin represents.""" - self.create_context.project_name + return self.create_context.project_name @property def log(self): From 68582819812675cd9461be89ceb9a56a7d7f2d1b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 17:33:52 +0200 Subject: [PATCH 1009/1227] better handling when context change is not enabled --- .../tools/publisher/widgets/create_dialog.py | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 3a68835dc7..b5e178b409 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -616,7 +616,7 @@ class CreateDialog(QtWidgets.QDialog): prereq_available = False creator_btn_tooltips.append("Creator is not selected") - if self._asset_doc is None: + if self._context_change_is_enabled() and self._asset_doc is None: # QUESTION how to handle invalid asset? prereq_available = False creator_btn_tooltips.append("Context is not selected") @@ -1010,13 +1010,18 @@ class CreateDialog(QtWidgets.QDialog): if variant_value is None: variant_value = self.variant_input.text() - self.create_btn.setEnabled(True) if not self._compiled_name_pattern.match(variant_value): self.create_btn.setEnabled(False) self._set_variant_state_property("invalid") self.subset_name_input.setText("< Invalid variant >") return + if not self._context_change_is_enabled(): + self.create_btn.setEnabled(True) + self._set_variant_state_property("") + self.subset_name_input.setText("< Valid variant >") + return + project_name = self.controller.project_name task_name = self._get_task_name() @@ -1034,6 +1039,7 @@ class CreateDialog(QtWidgets.QDialog): self.subset_name_input.setText(subset_name) + self.create_btn.setEnabled(True) self._validate_subset_name(subset_name, variant_value) def _validate_subset_name(self, subset_name, variant_value): @@ -1145,10 +1151,17 @@ class CreateDialog(QtWidgets.QDialog): creator_label = index.data(QtCore.Qt.DisplayRole) creator_identifier = index.data(CREATOR_IDENTIFIER_ROLE) family = index.data(FAMILY_ROLE) - subset_name = self.subset_name_input.text() variant = self.variant_input.text() - asset_name = self._get_asset_name() - task_name = self._get_task_name() + # Care about subset name only if context change is enabled + if self._context_change_is_enabled(): + subset_name = self.subset_name_input.text() + asset_name = self._get_asset_name() + task_name = self._get_task_name() + else: + subset_name = variant + asset_name = None + task_name = None + pre_create_data = self._pre_create_widget.current_value() # Where to define these data? # - what data show be stored? From 6bd6c3c17586786e1acb16b01ea5fc55ac6b235a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 17:35:22 +0200 Subject: [PATCH 1010/1227] subset name is None when context is not enabled --- openpype/tools/publisher/widgets/create_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index b5e178b409..ed2f65114b 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -1158,7 +1158,7 @@ class CreateDialog(QtWidgets.QDialog): asset_name = self._get_asset_name() task_name = self._get_task_name() else: - subset_name = variant + subset_name = None asset_name = None task_name = None From 8849e5ac7f2f1707e206cbfc6d8dceca3a3d562c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 18:54:23 +0200 Subject: [PATCH 1011/1227] made clear prerequirements checks --- openpype/tools/publisher/widgets/create_dialog.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index ed2f65114b..d25b42d04d 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -465,7 +465,7 @@ class CreateDialog(QtWidgets.QDialog): desc_width_anim_timer = QtCore.QTimer() desc_width_anim_timer.setInterval(10) - prereq_timer.timeout.connect(self._on_prereq_timer) + prereq_timer.timeout.connect(self._invalidate_prereq) desc_width_anim_timer.timeout.connect(self._on_desc_animation) @@ -600,16 +600,16 @@ class CreateDialog(QtWidgets.QDialog): self._tasks_widget.set_asset_name(asset_name) self._tasks_widget.select_task_name(task_name) - self._invalidate_prereq() + self._invalidate_prereq_deffered() - def _invalidate_prereq(self): + def _invalidate_prereq_deffered(self): self._prereq_timer.start() def _on_asset_filter_height_change(self, height): self._creators_header_widget.setMinimumHeight(height) self._creators_header_widget.setMaximumHeight(height) - def _on_prereq_timer(self): + def _invalidate_prereq(self): prereq_available = True creator_btn_tooltips = [] if self.creators_model.rowCount() < 1: @@ -726,11 +726,11 @@ class CreateDialog(QtWidgets.QDialog): asset_name = self._assets_widget.get_selected_asset_name() self._tasks_widget.set_asset_name(asset_name) if self._context_change_is_enabled(): - self._invalidate_prereq() + self._invalidate_prereq_deffered() def _on_task_change(self): if self._context_change_is_enabled(): - self._invalidate_prereq() + self._invalidate_prereq_deffered() def _on_current_session_context_request(self): self._assets_widget.set_current_session_asset() From 9bec04254732d440f89c91f72da4f61e4cddcc76 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 18:55:00 +0200 Subject: [PATCH 1012/1227] added sorting for creators --- openpype/tools/publisher/widgets/create_dialog.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index d25b42d04d..4cc63f50a8 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -342,7 +342,9 @@ class CreateDialog(QtWidgets.QDialog): creators_view = QtWidgets.QListView(self) creators_model = QtGui.QStandardItemModel() - creators_view.setModel(creators_model) + creators_sort_model = QtCore.QSortFilterProxyModel() + creators_sort_model.setSourceModel(creators_model) + creators_view.setModel(creators_sort_model) variant_widget = VariantInputsWidget(self) @@ -516,6 +518,7 @@ class CreateDialog(QtWidgets.QDialog): self.creators_model = creators_model self.creators_view = creators_view self.create_btn = create_btn + self._creators_sort_model = creators_sort_model self._creator_short_desc_widget = creator_short_desc_widget self._pre_create_widget = pre_create_widget @@ -703,6 +706,7 @@ class CreateDialog(QtWidgets.QDialog): if self.creators_model.rowCount() < 1: return + self._creators_sort_model.sort(0) # Make sure there is a selection indexes = self.creators_view.selectedIndexes() if not indexes: From 231446b012ef84f3492dcbd48ef20f85aa039445 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 18:56:23 +0200 Subject: [PATCH 1013/1227] disable creators view only if there are not creators --- openpype/tools/publisher/widgets/create_dialog.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 4cc63f50a8..8e43887238 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -615,7 +615,12 @@ class CreateDialog(QtWidgets.QDialog): def _invalidate_prereq(self): prereq_available = True creator_btn_tooltips = [] - if self.creators_model.rowCount() < 1: + + available_creators = self.creators_model.rowCount() > 0 + if available_creators != self.creators_view.isEnabled(): + self.creators_view.setEnabled(available_creators) + + if not available_creators: prereq_available = False creator_btn_tooltips.append("Creator is not selected") @@ -628,7 +633,6 @@ class CreateDialog(QtWidgets.QDialog): self._prereq_available = prereq_available self.create_btn.setEnabled(prereq_available) - self.creators_view.setEnabled(prereq_available) self.variant_input.setEnabled(prereq_available) self.variant_hints_btn.setEnabled(prereq_available) From 65b089f52ca0f96414f895b815ad22e98714f5a4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 18:57:06 +0200 Subject: [PATCH 1014/1227] trigger invalidation of prerequiremets if context widget changed enable state --- openpype/tools/publisher/widgets/create_dialog.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 8e43887238..757ee58468 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -576,7 +576,10 @@ class CreateDialog(QtWidgets.QDialog): def _set_context_enabled(self, enabled): self._assets_widget.set_enabled(enabled) self._tasks_widget.set_enabled(enabled) + check_prereq = self._context_widget.isEnabled() != enabled self._context_widget.setEnabled(enabled) + if check_prereq: + self._invalidate_prereq() def refresh(self): # Get context before refresh to keep selection of asset and From e833a0323d970a2b6baee5f91b584357f785c258 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 18:57:26 +0200 Subject: [PATCH 1015/1227] removed else statement --- openpype/tools/publisher/widgets/create_dialog.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 757ee58468..68c4888ec3 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -1164,14 +1164,13 @@ class CreateDialog(QtWidgets.QDialog): family = index.data(FAMILY_ROLE) variant = self.variant_input.text() # Care about subset name only if context change is enabled + subset_name = None + asset_name = None + task_name = None if self._context_change_is_enabled(): subset_name = self.subset_name_input.text() asset_name = self._get_asset_name() task_name = self._get_task_name() - else: - subset_name = None - asset_name = None - task_name = None pre_create_data = self._pre_create_widget.current_value() # Where to define these data? From 50d8fc0761424e6053c3529edaa31966277d5527 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 18:58:17 +0200 Subject: [PATCH 1016/1227] use first index from sorting model instead of creators model --- openpype/tools/publisher/widgets/create_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 68c4888ec3..d03b207727 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -717,7 +717,7 @@ class CreateDialog(QtWidgets.QDialog): # Make sure there is a selection indexes = self.creators_view.selectedIndexes() if not indexes: - index = self.creators_model.index(0, 0) + index = self._creators_sort_model.index(0, 0) self.creators_view.setCurrentIndex(index) else: index = indexes[0] From bf6868826b88c4215c84a48323403a4d2736eb54 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 18:58:38 +0200 Subject: [PATCH 1017/1227] converted some attributes to private --- .../tools/publisher/widgets/create_dialog.py | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index d03b207727..d4740b2493 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -515,10 +515,10 @@ class CreateDialog(QtWidgets.QDialog): self.variant_hints_group = variant_hints_group self._creators_header_widget = creators_header_widget - self.creators_model = creators_model - self.creators_view = creators_view - self.create_btn = create_btn + self._creators_model = creators_model self._creators_sort_model = creators_sort_model + self._creators_view = creators_view + self._create_btn = create_btn self._creator_short_desc_widget = creator_short_desc_widget self._pre_create_widget = pre_create_widget @@ -619,9 +619,9 @@ class CreateDialog(QtWidgets.QDialog): prereq_available = True creator_btn_tooltips = [] - available_creators = self.creators_model.rowCount() > 0 - if available_creators != self.creators_view.isEnabled(): - self.creators_view.setEnabled(available_creators) + available_creators = self._creators_model.rowCount() > 0 + if available_creators != self._creators_view.isEnabled(): + self._creators_view.setEnabled(available_creators) if not available_creators: prereq_available = False @@ -635,14 +635,15 @@ class CreateDialog(QtWidgets.QDialog): if prereq_available != self._prereq_available: self._prereq_available = prereq_available - self.create_btn.setEnabled(prereq_available) + self._create_btn.setEnabled(prereq_available) + self.variant_input.setEnabled(prereq_available) self.variant_hints_btn.setEnabled(prereq_available) tooltip = "" if creator_btn_tooltips: tooltip = "\n".join(creator_btn_tooltips) - self.create_btn.setToolTip(tooltip) + self._create_btn.setToolTip(tooltip) self._on_variant_change() @@ -680,8 +681,8 @@ class CreateDialog(QtWidgets.QDialog): # Refresh creators and add their families to list existing_items = {} old_creators = set() - for row in range(self.creators_model.rowCount()): - item = self.creators_model.item(row, 0) + for row in range(self._creators_model.rowCount()): + item = self._creators_model.item(row, 0) identifier = item.data(CREATOR_IDENTIFIER_ROLE) existing_items[identifier] = item old_creators.add(identifier) @@ -698,7 +699,7 @@ class CreateDialog(QtWidgets.QDialog): item.setFlags( QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable ) - self.creators_model.appendRow(item) + self._creators_model.appendRow(item) label = creator.label or identifier item.setData(label, QtCore.Qt.DisplayRole) @@ -708,17 +709,17 @@ class CreateDialog(QtWidgets.QDialog): # Remove families that are no more available for identifier in (old_creators - new_creators): item = existing_items[identifier] - self.creators_model.takeRow(item.row()) + self._creators_model.takeRow(item.row()) - if self.creators_model.rowCount() < 1: + if self._creators_model.rowCount() < 1: return self._creators_sort_model.sort(0) # Make sure there is a selection - indexes = self.creators_view.selectedIndexes() + indexes = self._creators_view.selectedIndexes() if not indexes: index = self._creators_sort_model.index(0, 0) - self.creators_view.setCurrentIndex(index) + self._creators_view.setCurrentIndex(index) else: index = indexes[0] @@ -1022,13 +1023,13 @@ class CreateDialog(QtWidgets.QDialog): variant_value = self.variant_input.text() if not self._compiled_name_pattern.match(variant_value): - self.create_btn.setEnabled(False) + self._create_btn.setEnabled(False) self._set_variant_state_property("invalid") self.subset_name_input.setText("< Invalid variant >") return if not self._context_change_is_enabled(): - self.create_btn.setEnabled(True) + self._create_btn.setEnabled(True) self._set_variant_state_property("") self.subset_name_input.setText("< Valid variant >") return @@ -1043,14 +1044,14 @@ class CreateDialog(QtWidgets.QDialog): variant_value, task_name, asset_doc, project_name ) except TaskNotSetError: - self.create_btn.setEnabled(False) + self._create_btn.setEnabled(False) self._set_variant_state_property("invalid") self.subset_name_input.setText("< Missing task >") return self.subset_name_input.setText(subset_name) - self.create_btn.setEnabled(True) + self._create_btn.setEnabled(True) self._validate_subset_name(subset_name, variant_value) def _validate_subset_name(self, subset_name, variant_value): @@ -1105,8 +1106,8 @@ class CreateDialog(QtWidgets.QDialog): self._set_variant_state_property(property_value) variant_is_valid = variant_value.strip() != "" - if variant_is_valid != self.create_btn.isEnabled(): - self.create_btn.setEnabled(variant_is_valid) + if variant_is_valid != self._create_btn.isEnabled(): + self._create_btn.setEnabled(variant_is_valid) def _set_variant_state_property(self, state): current_value = self.variant_input.property("state") @@ -1151,11 +1152,11 @@ class CreateDialog(QtWidgets.QDialog): self._update_help_btn() def _on_create(self): - indexes = self.creators_view.selectedIndexes() + indexes = self._creators_view.selectedIndexes() if not indexes or len(indexes) > 1: return - if not self.create_btn.isEnabled(): + if not self._create_btn.isEnabled(): return index = indexes[0] From a62eeb807d64817920c38f2474d327d4113f9db2 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 9 Jul 2022 03:46:02 +0000 Subject: [PATCH 1018/1227] [Automated] Bump version --- CHANGELOG.md | 41 ++++++++++++++++++----------------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b43d2fed1..9a0c058f73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [3.12.1-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.12.1-nightly.4](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.0...HEAD) @@ -14,31 +14,43 @@ **🚀 Enhancements** +- General: Creator Plugins have access to project [\#3476](https://github.com/pypeclub/OpenPype/pull/3476) +- General: Better arguments order in creator init [\#3475](https://github.com/pypeclub/OpenPype/pull/3475) +- Ftrack: Trigger custom ftrack events on project creation and preparation [\#3465](https://github.com/pypeclub/OpenPype/pull/3465) - Windows installer: Clean old files and add version subfolder [\#3445](https://github.com/pypeclub/OpenPype/pull/3445) - Blender: Bugfix - Set fps properly on open [\#3426](https://github.com/pypeclub/OpenPype/pull/3426) +- Hiero: Add custom scripts menu [\#3425](https://github.com/pypeclub/OpenPype/pull/3425) - Blender: pre pyside install for all platforms [\#3400](https://github.com/pypeclub/OpenPype/pull/3400) - Maya: Ability to set resolution for playblasts from asset, and override through review instance. [\#3360](https://github.com/pypeclub/OpenPype/pull/3360) **🐛 Bug fixes** +- General: thumbnail extractor fix [\#3474](https://github.com/pypeclub/OpenPype/pull/3474) +- Kitsu: bugfix with sync-service ans publish plugins [\#3473](https://github.com/pypeclub/OpenPype/pull/3473) +- Flame: solved problem with multi-selected loading [\#3470](https://github.com/pypeclub/OpenPype/pull/3470) +- General: Fix query function in update logic [\#3468](https://github.com/pypeclub/OpenPype/pull/3468) +- Resolve: removed few bugs [\#3464](https://github.com/pypeclub/OpenPype/pull/3464) +- General: Delete old versions is safer when ftrack is disabled [\#3462](https://github.com/pypeclub/OpenPype/pull/3462) +- Nuke: fixing metadata slate TC difference [\#3455](https://github.com/pypeclub/OpenPype/pull/3455) - Nuke: prerender reviewable fails [\#3450](https://github.com/pypeclub/OpenPype/pull/3450) - Maya: fix hashing in Python 3 for tile rendering [\#3447](https://github.com/pypeclub/OpenPype/pull/3447) - LogViewer: Escape html characters in log message [\#3443](https://github.com/pypeclub/OpenPype/pull/3443) - Nuke: Slate frame is integrated [\#3427](https://github.com/pypeclub/OpenPype/pull/3427) - Maya: Camera extra data - additional fix for \#3304 [\#3386](https://github.com/pypeclub/OpenPype/pull/3386) -- Harmony: added unc path to zifile command in Harmony [\#3372](https://github.com/pypeclub/OpenPype/pull/3372) - Maya: Handle excluding `model` family from frame range validator. [\#3370](https://github.com/pypeclub/OpenPype/pull/3370) **🔀 Refactored code** +- Maya: Merge animation + pointcache extractor logic [\#3461](https://github.com/pypeclub/OpenPype/pull/3461) - Maya: Re-use `maintained\_time` from lib [\#3460](https://github.com/pypeclub/OpenPype/pull/3460) +- General: Use query functions in global plugins [\#3459](https://github.com/pypeclub/OpenPype/pull/3459) - Clockify: Use query functions in clockify actions [\#3458](https://github.com/pypeclub/OpenPype/pull/3458) - General: Use query functions in rest api calls [\#3457](https://github.com/pypeclub/OpenPype/pull/3457) - General: Use query functions in load utils [\#3446](https://github.com/pypeclub/OpenPype/pull/3446) - General: Use Anatomy after move to pipeline [\#3436](https://github.com/pypeclub/OpenPype/pull/3436) - General: Anatomy moved to pipeline [\#3435](https://github.com/pypeclub/OpenPype/pull/3435) +- Fusion: Use client query functions [\#3380](https://github.com/pypeclub/OpenPype/pull/3380) - Resolve: Use client query functions [\#3379](https://github.com/pypeclub/OpenPype/pull/3379) -- General: Host implementation defined with class [\#3337](https://github.com/pypeclub/OpenPype/pull/3337) ## [3.12.0](https://github.com/pypeclub/OpenPype/tree/3.12.0) (2022-06-28) @@ -53,6 +65,7 @@ - Webserver: Added CORS middleware [\#3422](https://github.com/pypeclub/OpenPype/pull/3422) - Attribute Defs UI: Files widget show what is allowed to drop in [\#3411](https://github.com/pypeclub/OpenPype/pull/3411) +- General: Add ability to change user value for templates [\#3366](https://github.com/pypeclub/OpenPype/pull/3366) - Hosts: More options for in-host callbacks [\#3357](https://github.com/pypeclub/OpenPype/pull/3357) - Multiverse: expose some settings to GUI [\#3350](https://github.com/pypeclub/OpenPype/pull/3350) @@ -68,8 +81,8 @@ - TVPaint: Make sure exit code is set to not None [\#3382](https://github.com/pypeclub/OpenPype/pull/3382) - Maya: vray device aspect ratio fix [\#3381](https://github.com/pypeclub/OpenPype/pull/3381) - Flame: bunch of publishing issues [\#3377](https://github.com/pypeclub/OpenPype/pull/3377) +- Harmony: added unc path to zifile command in Harmony [\#3372](https://github.com/pypeclub/OpenPype/pull/3372) - Standalone: settings improvements [\#3355](https://github.com/pypeclub/OpenPype/pull/3355) -- Nuke: Load full model hierarchy by default [\#3328](https://github.com/pypeclub/OpenPype/pull/3328) **🔀 Refactored code** @@ -77,16 +90,13 @@ - General: Move editorial lib to pipeline [\#3419](https://github.com/pypeclub/OpenPype/pull/3419) - Kitsu: renaming to plural func sync\_all\_projects [\#3397](https://github.com/pypeclub/OpenPype/pull/3397) - Houdini: Use client query functions [\#3395](https://github.com/pypeclub/OpenPype/pull/3395) +- Hiero: Use client query functions [\#3393](https://github.com/pypeclub/OpenPype/pull/3393) - Nuke: Use client query functions [\#3391](https://github.com/pypeclub/OpenPype/pull/3391) - Maya: Use client query functions [\#3385](https://github.com/pypeclub/OpenPype/pull/3385) -- Fusion: Use client query functions [\#3380](https://github.com/pypeclub/OpenPype/pull/3380) - Harmony: Use client query functions [\#3378](https://github.com/pypeclub/OpenPype/pull/3378) - Celaction: Use client query functions [\#3376](https://github.com/pypeclub/OpenPype/pull/3376) - Photoshop: Use client query functions [\#3375](https://github.com/pypeclub/OpenPype/pull/3375) - AfterEffects: Use client query functions [\#3374](https://github.com/pypeclub/OpenPype/pull/3374) -- TVPaint: Use client query functions [\#3340](https://github.com/pypeclub/OpenPype/pull/3340) -- Ftrack: Use client query functions [\#3339](https://github.com/pypeclub/OpenPype/pull/3339) -- Standalone Publisher: Use client query functions [\#3330](https://github.com/pypeclub/OpenPype/pull/3330) **Merged pull requests:** @@ -105,7 +115,6 @@ **🚀 Enhancements** - Pyblish Pype: Hiding/Close issues [\#3367](https://github.com/pypeclub/OpenPype/pull/3367) -- General: Add ability to change user value for templates [\#3366](https://github.com/pypeclub/OpenPype/pull/3366) - Ftrack: Removed requirement of pypeclub role from default settings [\#3354](https://github.com/pypeclub/OpenPype/pull/3354) - Kitsu: Prevent crash on missing frames information [\#3352](https://github.com/pypeclub/OpenPype/pull/3352) @@ -120,28 +129,14 @@ - General: Create only one thumbnail per instance [\#3351](https://github.com/pypeclub/OpenPype/pull/3351) - nuke: adding extract thumbnail settings 3.10 [\#3347](https://github.com/pypeclub/OpenPype/pull/3347) - General: Fix last version function [\#3345](https://github.com/pypeclub/OpenPype/pull/3345) -- Deadline: added OPENPYPE\_MONGO to filter [\#3336](https://github.com/pypeclub/OpenPype/pull/3336) - -**🔀 Refactored code** - -- Webpublisher: Use client query functions [\#3333](https://github.com/pypeclub/OpenPype/pull/3333) ## [3.11.0](https://github.com/pypeclub/OpenPype/tree/3.11.0) (2022-06-17) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.11.0-nightly.4...3.11.0) -**🚀 Enhancements** - -- Settings: Settings can be extracted from UI [\#3323](https://github.com/pypeclub/OpenPype/pull/3323) - **🐛 Bug fixes** - General: Handle empty source key on instance [\#3342](https://github.com/pypeclub/OpenPype/pull/3342) -- Houdini: Fix Houdini VDB manage update wrong file attribute name [\#3322](https://github.com/pypeclub/OpenPype/pull/3322) - -**🔀 Refactored code** - -- Blender: Use client query functions [\#3331](https://github.com/pypeclub/OpenPype/pull/3331) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) diff --git a/openpype/version.py b/openpype/version.py index 3cb7f4572b..3239b0e2a2 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.12.1-nightly.3" +__version__ = "3.12.1-nightly.4" diff --git a/pyproject.toml b/pyproject.toml index 4803249923..f5bd7cc946 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.12.1-nightly.3" # OpenPype +version = "3.12.1-nightly.4" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 5cf03a43cb5794e1b00a5bd729d62ba8257fb5b4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 11 Jul 2022 11:40:24 +0200 Subject: [PATCH 1019/1227] deffer workfiles tool show using qtimer --- openpype/hosts/nuke/api/lib.py | 60 ++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index f565ec8546..0929415c00 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -10,6 +10,7 @@ from collections import OrderedDict import clique import nuke +from Qt import QtCore, QtWidgets from openpype.client import ( get_project, @@ -27,6 +28,7 @@ from openpype.api import ( get_current_project_settings, ) from openpype.tools.utils import host_tools +from openpype.lib import env_value_to_bool from openpype.lib.path_tools import HostDirmap from openpype.settings import ( get_project_settings, @@ -63,7 +65,10 @@ class Context: main_window = None context_label = None project_name = os.getenv("AVALON_PROJECT") + # Workfile related code workfiles_launched = False + workfiles_tool_timer = None + # Seems unused _project_doc = None @@ -2384,12 +2389,19 @@ def select_nodes(nodes): def launch_workfiles_app(): - '''Function letting start workfiles after start of host - ''' - from openpype.lib import ( - env_value_to_bool - ) - from .pipeline import get_main_window + """Show workfiles tool on nuke launch. + + Trigger to show workfiles tool on application launch. Can be executed only + once all other calls are ignored. + + Workfiles tool show is deffered after application initialization using + QTimer. + """ + + if Context.workfiles_launched: + return + + Context.workfiles_launched = True # get all imortant settings open_at_start = env_value_to_bool( @@ -2400,10 +2412,38 @@ def launch_workfiles_app(): if not open_at_start: return - if not Context.workfiles_launched: - Context.workfiles_launched = True - main_window = get_main_window() - host_tools.show_workfiles(parent=main_window) + # Show workfiles tool using timer + # - this will be probably triggered during initialization in that case + # the application is not be able to show uis so it must be + # deffered using timer + # - timer should be processed when initialization ends + # When applications starts to process events. + timer = QtCore.QTimer() + timer.timeout.connect(_launch_workfile_app) + timer.setInterval(100) + Context.workfiles_tool_timer = timer + timer.start() + + +def _launch_workfile_app(): + # Safeguard to not show window when application is still starting up + # or is already closing down. + closing_down = QtWidgets.QApplication.closingDown() + starting_up = QtWidgets.QApplication.startingUp() + + # Stop the timer if application finished start up of is closing down + if closing_down or not starting_up: + Context.workfiles_tool_timer.stop() + Context.workfiles_tool_timer = None + + # Skip if application is starting up or closing down + if starting_up or closing_down: + return + + from .pipeline import get_main_window + + main_window = get_main_window() + host_tools.show_workfiles(parent=main_window) def process_workfile_builder(): From 4dbc094e5980a2453992516e0f32d6cf868ec7f7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 11 Jul 2022 11:40:38 +0200 Subject: [PATCH 1020/1227] trigger launch workfiles app on install --- openpype/hosts/nuke/api/pipeline.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index 2785eb65cd..2e3621ba8f 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -120,8 +120,9 @@ def install(): nuke.addOnCreate(workfile_settings.set_context_settings, nodeClass="Root") nuke.addOnCreate(workfile_settings.set_favorites, nodeClass="Root") nuke.addOnCreate(process_workfile_builder, nodeClass="Root") - nuke.addOnCreate(launch_workfiles_app, nodeClass="Root") + _install_menu() + launch_workfiles_app() def uninstall(): From 85e89526d5019a151156a6686dc169708d6e8d4c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 11 Jul 2022 17:42:54 +0200 Subject: [PATCH 1021/1227] added attribute instance label on instance --- openpype/pipeline/create/context.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 7758a660d3..61cbe8caff 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -496,6 +496,13 @@ class CreatedInstance: def subset_name(self): return self._data["subset"] + @property + def instance_label(self): + label = self._data.get("label") + if not label: + label = self.subset_name + return label + @property def creator_identifier(self): return self.creator.identifier From 6feeca860b167785a5fb4306c4a50d30ae996087 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 11 Jul 2022 17:46:45 +0200 Subject: [PATCH 1022/1227] use 'label' to display instance in views --- openpype/pipeline/create/context.py | 2 +- openpype/tools/publisher/widgets/card_view_widgets.py | 7 ++++--- openpype/tools/publisher/widgets/list_view_widgets.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 61cbe8caff..7f0341c127 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -497,7 +497,7 @@ class CreatedInstance: return self._data["subset"] @property - def instance_label(self): + def label(self): label = self._data.get("label") if not label: label = self.subset_name diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 086cd5c59c..3c294c9c7c 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -303,13 +303,14 @@ class InstanceCardWidget(CardWidget): self._last_variant = variant self._last_subset_name = subset_name # Make `variant` bold - found_parts = set(re.findall(variant, subset_name, re.IGNORECASE)) + label = self.instance.label + found_parts = set(re.findall(variant, label, re.IGNORECASE)) if found_parts: for part in found_parts: replacement = "{}".format(part) - subset_name = subset_name.replace(part, replacement) + label = label.replace(part, replacement) - self._label_widget.setText(subset_name) + self._label_widget.setText(label) # HTML text will cause that label start catch mouse clicks # - disabling with changing interaction flag self._label_widget.setTextInteractionFlags( diff --git a/openpype/tools/publisher/widgets/list_view_widgets.py b/openpype/tools/publisher/widgets/list_view_widgets.py index 6bddaf66c8..1b1a19599a 100644 --- a/openpype/tools/publisher/widgets/list_view_widgets.py +++ b/openpype/tools/publisher/widgets/list_view_widgets.py @@ -113,7 +113,7 @@ class InstanceListItemWidget(QtWidgets.QWidget): self.instance = instance - subset_name_label = QtWidgets.QLabel(instance["subset"], self) + subset_name_label = QtWidgets.QLabel(instance.label, self) subset_name_label.setObjectName("ListViewSubsetName") active_checkbox = NiceCheckbox(parent=self) From 74e57f9f49cdbb4812cd6d0c5348c5877f4e61e1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 11 Jul 2022 18:15:46 +0200 Subject: [PATCH 1023/1227] OP-3446 - implemented render_mov_batch in TrayPublisher --- openpype/hosts/traypublisher/api/batch_lib.py | 61 ++++++ .../plugins/create/create_mov_batch.py | 191 ++++++++++++++++++ .../plugins/publish/collect_mov_batch.py | 34 ++++ .../project_settings/traypublisher.json | 15 +- .../schema_project_traypublisher.json | 71 +++++++ 5 files changed, 371 insertions(+), 1 deletion(-) create mode 100644 openpype/hosts/traypublisher/api/batch_lib.py create mode 100644 openpype/hosts/traypublisher/plugins/create/create_mov_batch.py create mode 100644 openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py diff --git a/openpype/hosts/traypublisher/api/batch_lib.py b/openpype/hosts/traypublisher/api/batch_lib.py new file mode 100644 index 0000000000..2486d405bd --- /dev/null +++ b/openpype/hosts/traypublisher/api/batch_lib.py @@ -0,0 +1,61 @@ +# Helper functions to find matching asset for (multiple) processed source files +import os +import collections + +from openpype.client import get_assets + + +def get_children_assets_by_name(project_name, top_asset_doc): + """ Get all children for 'top_asset_doc' by theirs name + + Args: + project_name (str) + top_asset_doc (asset doc) (eg dict) + Returns: + (dict) {"shot1": shot1_asset_doc} + """ + assets_by_parent_id = get_asset_docs_by_parent_id(project_name) + _children_docs = get_children_docs( + assets_by_parent_id, top_asset_doc + ) + children_docs = { + children_doc["name"].lower(): children_doc + for children_doc in _children_docs + } + return children_docs + + +def get_asset_docs_by_parent_id(project_name): + """ Query all assets for project and store them by parent's id to list + + Args: + project_name (str) + Returns: + (dict) { _id of parent :[asset_doc1, asset_doc2]} + """ + asset_docs_by_parent_id = collections.defaultdict(list) + for asset_doc in get_assets(project_name): + parent_id = asset_doc["data"]["visualParent"] + asset_docs_by_parent_id[parent_id].append(asset_doc) + return asset_docs_by_parent_id + + +def get_children_docs(documents_by_parent_id, parent_doc): + """ Recursively find all children in reverse order + + Last children first. + Args: + documents_by_parent_id (dict) + parent_doc (asset doc, eg dict) + Returns + (list) of asset docs + """ + output = [] + children = documents_by_parent_id.get(parent_doc["_id"]) or tuple() + for child in children: + output.extend( + get_children_docs(documents_by_parent_id, child) + ) + output.append(parent_doc) + return output + diff --git a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py b/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py new file mode 100644 index 0000000000..5297d73ba9 --- /dev/null +++ b/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py @@ -0,0 +1,191 @@ +import copy +import os +import re + +from openpype.client import get_assets +from openpype.hosts.traypublisher.api import pipeline +from openpype.lib import FileDef, TextDef, get_subset_name_with_asset_doc +from openpype.pipeline import ( + CreatedInstance +) + +from openpype.hosts.traypublisher.api.plugin import TrayPublishCreator + + +class BatchMovCreator(TrayPublishCreator): + """Creates instances from .mov file(s).""" + identifier = "render_mov_batch" + label = "Batch Mov" + family = "render" + description = "Publish batch of movs" + host_name = "traypublisher" + + create_allow_context_change = False + version_regex = re.compile(r"^(.+)_v([0-9]+)$") + + default_tasks = ["Compositing"] + + extensions = [".mov"] + + def __init__(self, project_settings, *args, **kwargs): + super(BatchMovCreator, self).__init__(project_settings, + *args, **kwargs) + self._default_variants = (project_settings["traypublisher"] + ["BatchMovCreator"] + ["default_variants"]) + + def get_icon(self): + return "fa.file" + + def create(self, subset_name, data, pre_create_data): + file_paths = pre_create_data.get("filepath") + if not file_paths: + return + + for file_info in file_paths: + instance_data = copy.deepcopy(data) + file_name = file_info["filenames"][0] + filepath = os.path.join(file_info["directory"], file_name) + instance_data["creator_attributes"] = {"filepath": filepath} + + asset_doc, version = self.get_asset_doc_from_file_name( + file_name, self.project_name) + + subset_name, task_name = self._get_subset_and_task( + asset_doc, data["variant"], self.project_name) + + instance_data["task"] = task_name + instance_data["asset"] = asset_doc["name"] + + # Create new instance + new_instance = CreatedInstance(self.family, subset_name, + instance_data, self) + # Host implementation of storing metadata about instance + pipeline.HostContext.add_instance(new_instance.data_to_store()) + # Add instance to current context + self._add_instance_to_context(new_instance) + + def get_asset_doc_from_file_name(self, source_filename, project_name): + """Try to parse out asset name from file name provided. + + Artists might provide various file name formats. + Currently handled: + - chair.mov + - chair_v001.mov + - my_chair_to_upload.mov + """ + version = None + asset_name = os.path.splitext(source_filename)[0] + # Always first check if source filename is in assets + matching_asset_doc = self._get_asset_by_name_case_not_sensitive( + project_name, asset_name) + + if matching_asset_doc is None: + matching_asset_doc, version = ( + self._parse_with_version(project_name, asset_name)) + + if matching_asset_doc is None: + matching_asset_doc = self._parse_containing(project_name, + asset_name) + + if matching_asset_doc is None: + raise ValueError( + "Cannot guess asset name from {}".format(source_filename)) + + return matching_asset_doc, version + + def _parse_with_version(self, project_name, asset_name): + """Try to parse asset name from a file name containing version too + + Eg. 'chair_v001.mov' >> 'chair', 1 + """ + self.log.debug(( + "Asset doc by \"{}\" was not found, trying version regex." + ).format(asset_name)) + + matching_asset_doc = version_number = None + + regex_result = self.version_regex.findall(asset_name) + if regex_result: + _asset_name, _version_number = regex_result[0] + matching_asset_doc = self._get_asset_by_name_case_not_sensitive( + project_name, _asset_name) + if matching_asset_doc: + version_number = int(_version_number) + + return matching_asset_doc, version_number + + def _parse_containing(self, project_name, asset_name): + """Look if file name contains any existing asset name""" + for asset_doc in get_assets(project_name, fields=["name"]): + if asset_doc["name"].lower() in asset_name.lower(): + return get_assets(project_name, + asset_names=[asset_doc["name"]]) + + def _get_subset_and_task(self, asset_doc, variant, project_name): + """Create subset name according to standard template process""" + task_name = self._get_task_name(asset_doc) + + subset_name = get_subset_name_with_asset_doc( + self.family, + variant, + task_name, + asset_doc, + project_name + ) + + return subset_name, task_name + + def _get_task_name(self, asset_doc): + """Get applicable task from 'asset_doc' """ + available_task_names = {} + asset_tasks = asset_doc.get("data", {}).get("tasks") or {} + for task_name in asset_tasks.keys(): + available_task_names[task_name.lower()] = task_name + + task_name = None + for _task_name in self.default_tasks: + _task_name_low = _task_name.lower() + if _task_name_low in available_task_names: + task_name = available_task_names[_task_name_low] + break + + return task_name + + def get_default_variants(self): + return self._default_variants + + def get_instance_attr_defs(self): + return [] + + def get_pre_create_attr_defs(self): + # Use same attributes as for instance attributes + return [ + FileDef( + "filepath", + folders=False, + single_item=False, + extensions=self.extensions, + label="Filepath" + ) + ] + + def get_detail_description(self): + return """# Publish batch of .mov to multiple assets. + + File names must then contain only asset name, or asset name + version. + (eg. 'chair.mov', 'chair_v001.mov', not really safe `my_chair_v001.mov` + """ + + def _get_asset_by_name_case_not_sensitive(self, project_name, asset_name): + """Handle more cases in file names""" + asset_name = re.compile(asset_name, re.IGNORECASE) + + assets = list(get_assets(project_name, asset_names=[asset_name])) + if assets: + if len(assets) > 1: + self.log.warning("Too many records found for {}".format( + asset_name)) + return + + return assets.pop() diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py b/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py new file mode 100644 index 0000000000..2a5e356684 --- /dev/null +++ b/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py @@ -0,0 +1,34 @@ +import os + +import pyblish.api +from openpype.pipeline import OpenPypePyblishPluginMixin + + +class CollectMovBatch( + pyblish.api.InstancePlugin, OpenPypePyblishPluginMixin +): + """Collect file url for batch mov and create representation.""" + + label = "Collect Mov Batch Files" + order = pyblish.api.CollectorOrder + + hosts = ["traypublisher"] + + def process(self, instance): + if not instance.data.get("creator_identifier") == "render_mov_batch": + return + + file_url = instance.data["creator_attributes"]["filepath"] + file_name = os.path.basename(file_url) + _, ext = os.path.splitext(file_name) + + repre = { + "name": ext[1:], + "ext": ext[1:], + "files": file_name, + "stagingDir": os.path.dirname(file_url) + } + + instance.data["representations"].append(repre) + + self.log.debug("instance.data {}".format(instance.data)) diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 0b54cfd39e..6d2d32a037 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -31,5 +31,18 @@ ".aep" ] } - ] + ], + "BatchMovCreator": { + "family": "render_mov_batch", + "identifier": "", + "label": "Batch Mov", + "icon": "fa.file", + "default_variants": [], + "description": "", + "detailed_description": "", + "default_tasks": "Compositing", + "extensions": [ + ".mov" + ] + } } \ No newline at end of file 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..7cb74d86a7 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -78,6 +78,77 @@ } ] } + }, + { + "type": "dict", + "collapsible": true, + "key": "BatchMovCreator", + "label": "Batch Mov Creator", + "use_label_wrap": true, + "collapsible_key": true, + "children": [ + { + "type": "text", + "key": "family", + "label": "Family" + }, + { + "type": "text", + "key": "identifier", + "label": "Identifier", + "placeholder": "< Use 'Family' >", + "tooltip": "All creators must have unique identifier.\nBy default is used 'family' but if you need to have more creators with same families\nyou have to set identifier too." + }, + { + "type": "text", + "key": "label", + "label": "Label" + }, + { + "type": "text", + "key": "icon", + "label": "Icon" + }, + { + "type": "list", + "key": "default_variants", + "label": "Default variants", + "object_type": { + "type": "text" + } + }, + { + "type": "separator" + }, + { + "type": "text", + "key": "description", + "label": "Description" + }, + { + "type": "text", + "key": "detailed_description", + "label": "Detailed Description", + "multiline": true + }, + { + "type": "separator" + }, + { + "type": "text", + "key": "default_tasks", + "label": "Default task" + }, + { + "type": "list", + "key": "extensions", + "label": "Extensions", + "use_label_wrap": true, + "collapsible_key": true, + "collapsed": false, + "object_type": "text" + } + ] } ] } From 620060b7d6a07ef9264429a56ff797b233085a99 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 11 Jul 2022 18:19:27 +0200 Subject: [PATCH 1024/1227] use plist.load as plist.readPlist is deprecated --- openpype/lib/applications.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index 82f77ef9e3..f46197e15f 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -665,7 +665,11 @@ class ApplicationExecutable: if os.path.exists(plist_filepath): import plistlib - parsed_plist = plistlib.readPlist(plist_filepath) + if hasattr(plistlib, "load"): + with open(plist_filepath, "rb") as stream: + parsed_plist = plistlib.load(stream) + else: + parsed_plist = plistlib.readPlist(plist_filepath) executable_filename = parsed_plist.get("CFBundleExecutable") if executable_filename: From e0c2499cf95eb0d5d9d591781d9eb1b7618d660c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 11 Jul 2022 18:48:42 +0200 Subject: [PATCH 1025/1227] align creator attributes from top to bottom --- openpype/tools/publisher/widgets/widgets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 7096b9fb50..5a5f8c4c37 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1225,6 +1225,7 @@ class CreatorAttrsWidget(QtWidgets.QWidget): different creators. If creator have same (similar) definitions their widgets are merged into one (different label does not count). """ + def __init__(self, controller, parent): super(CreatorAttrsWidget, self).__init__(parent) @@ -1275,6 +1276,7 @@ class CreatorAttrsWidget(QtWidgets.QWidget): content_layout = QtWidgets.QGridLayout(content_widget) content_layout.setColumnStretch(0, 0) content_layout.setColumnStretch(1, 1) + content_layout.setAlignment(QtCore.Qt.AlignTop) row = 0 for attr_def, attr_instances, values in result: From a7c945f51f484a848401072fd230334710f3b734 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 12 Jul 2022 11:01:52 +0200 Subject: [PATCH 1026/1227] OP-3446 - fix return type --- .../traypublisher/plugins/create/create_mov_batch.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py b/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py index 5297d73ba9..d796b304a3 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py +++ b/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py @@ -2,11 +2,12 @@ import copy import os import re -from openpype.client import get_assets +from openpype.client import get_assets, get_asset_by_name from openpype.hosts.traypublisher.api import pipeline -from openpype.lib import FileDef, TextDef, get_subset_name_with_asset_doc +from openpype.lib import FileDef, get_subset_name_with_asset_doc from openpype.pipeline import ( - CreatedInstance + CreatedInstance, + CreatorError ) from openpype.hosts.traypublisher.api.plugin import TrayPublishCreator @@ -89,7 +90,7 @@ class BatchMovCreator(TrayPublishCreator): asset_name) if matching_asset_doc is None: - raise ValueError( + raise CreatorError( "Cannot guess asset name from {}".format(source_filename)) return matching_asset_doc, version @@ -119,8 +120,7 @@ class BatchMovCreator(TrayPublishCreator): """Look if file name contains any existing asset name""" for asset_doc in get_assets(project_name, fields=["name"]): if asset_doc["name"].lower() in asset_name.lower(): - return get_assets(project_name, - asset_names=[asset_doc["name"]]) + return get_asset_by_name(project_name, asset_doc["name"]) def _get_subset_and_task(self, asset_doc, variant, project_name): """Create subset name according to standard template process""" From c3384c4005132c2eb973d0bda8a191bb3fd6791c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 12 Jul 2022 11:02:06 +0200 Subject: [PATCH 1027/1227] OP-3446 - remove obsolete methods --- openpype/hosts/traypublisher/api/batch_lib.py | 61 ------------------- 1 file changed, 61 deletions(-) delete mode 100644 openpype/hosts/traypublisher/api/batch_lib.py diff --git a/openpype/hosts/traypublisher/api/batch_lib.py b/openpype/hosts/traypublisher/api/batch_lib.py deleted file mode 100644 index 2486d405bd..0000000000 --- a/openpype/hosts/traypublisher/api/batch_lib.py +++ /dev/null @@ -1,61 +0,0 @@ -# Helper functions to find matching asset for (multiple) processed source files -import os -import collections - -from openpype.client import get_assets - - -def get_children_assets_by_name(project_name, top_asset_doc): - """ Get all children for 'top_asset_doc' by theirs name - - Args: - project_name (str) - top_asset_doc (asset doc) (eg dict) - Returns: - (dict) {"shot1": shot1_asset_doc} - """ - assets_by_parent_id = get_asset_docs_by_parent_id(project_name) - _children_docs = get_children_docs( - assets_by_parent_id, top_asset_doc - ) - children_docs = { - children_doc["name"].lower(): children_doc - for children_doc in _children_docs - } - return children_docs - - -def get_asset_docs_by_parent_id(project_name): - """ Query all assets for project and store them by parent's id to list - - Args: - project_name (str) - Returns: - (dict) { _id of parent :[asset_doc1, asset_doc2]} - """ - asset_docs_by_parent_id = collections.defaultdict(list) - for asset_doc in get_assets(project_name): - parent_id = asset_doc["data"]["visualParent"] - asset_docs_by_parent_id[parent_id].append(asset_doc) - return asset_docs_by_parent_id - - -def get_children_docs(documents_by_parent_id, parent_doc): - """ Recursively find all children in reverse order - - Last children first. - Args: - documents_by_parent_id (dict) - parent_doc (asset doc, eg dict) - Returns - (list) of asset docs - """ - output = [] - children = documents_by_parent_id.get(parent_doc["_id"]) or tuple() - for child in children: - output.extend( - get_children_docs(documents_by_parent_id, child) - ) - output.append(parent_doc) - return output - From 24e26ce63a478941dd4334c7558b92c5eb853b95 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 12 Jul 2022 11:05:35 +0200 Subject: [PATCH 1028/1227] OP-3446 - fill defaults_variant explicitly No need to overwrite method --- .../traypublisher/plugins/create/create_mov_batch.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py b/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py index d796b304a3..1577b622ab 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py +++ b/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py @@ -31,9 +31,9 @@ class BatchMovCreator(TrayPublishCreator): def __init__(self, project_settings, *args, **kwargs): super(BatchMovCreator, self).__init__(project_settings, *args, **kwargs) - self._default_variants = (project_settings["traypublisher"] - ["BatchMovCreator"] - ["default_variants"]) + self.default_variants = (project_settings["traypublisher"] + ["BatchMovCreator"] + ["default_variants"]) def get_icon(self): return "fa.file" @@ -152,9 +152,6 @@ class BatchMovCreator(TrayPublishCreator): return task_name - def get_default_variants(self): - return self._default_variants - def get_instance_attr_defs(self): return [] From 89eddfa63d91eb1de766363b9d9c8c7899d39ad1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 12 Jul 2022 11:17:55 +0200 Subject: [PATCH 1029/1227] OP-3446 - fix pulling configuration from Settings --- .../traypublisher/plugins/create/create_mov_batch.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py b/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py index 1577b622ab..e54fc44acc 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py +++ b/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py @@ -24,10 +24,6 @@ class BatchMovCreator(TrayPublishCreator): create_allow_context_change = False version_regex = re.compile(r"^(.+)_v([0-9]+)$") - default_tasks = ["Compositing"] - - extensions = [".mov"] - def __init__(self, project_settings, *args, **kwargs): super(BatchMovCreator, self).__init__(project_settings, *args, **kwargs) @@ -35,6 +31,14 @@ class BatchMovCreator(TrayPublishCreator): ["BatchMovCreator"] ["default_variants"]) + self.default_tasks = (project_settings["traypublisher"] + ["BatchMovCreator"] + ["default_tasks"]) + + self.extensions = (project_settings["traypublisher"] + ["BatchMovCreator"] + ["extensions"]) + def get_icon(self): return "fa.file" From 1fe8c96c261e9687b5aaca52e53d88897fef540a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 12 Jul 2022 11:18:31 +0200 Subject: [PATCH 1030/1227] OP-3446 - modifying Settings to list --- .../settings/defaults/project_settings/traypublisher.json | 4 ++-- .../projects_schema/schema_project_traypublisher.json | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 6d2d32a037..36526d01b0 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -37,10 +37,10 @@ "identifier": "", "label": "Batch Mov", "icon": "fa.file", - "default_variants": [], + "default_variants": ["Main"], "description": "", "detailed_description": "", - "default_tasks": "Compositing", + "default_tasks": ["Compositing"], "extensions": [ ".mov" ] 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 7cb74d86a7..308883d46f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -135,9 +135,12 @@ "type": "separator" }, { - "type": "text", + "type": "list", "key": "default_tasks", - "label": "Default task" + "label": "Default tasks", + "object_type": { + "type": "text" + } }, { "type": "list", From 20b0292af170360db2aecb0483afe3ae13dd3bcd Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 12 Jul 2022 11:19:27 +0200 Subject: [PATCH 1031/1227] OP-3446 - removed unneeded, comes from class --- openpype/hosts/traypublisher/plugins/create/create_mov_batch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py b/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py index e54fc44acc..fdada96c87 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py +++ b/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py @@ -19,7 +19,6 @@ class BatchMovCreator(TrayPublishCreator): label = "Batch Mov" family = "render" description = "Publish batch of movs" - host_name = "traypublisher" create_allow_context_change = False version_regex = re.compile(r"^(.+)_v([0-9]+)$") From 7abbc26184b054a56118d0a8323b309e10e61179 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 11:36:13 +0200 Subject: [PATCH 1032/1227] first sequence frame is taken from the input sequence instead of output frame --- openpype/plugins/publish/extract_review.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index b6e5fee1fe..107a0994a3 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -446,8 +446,11 @@ class ExtractReview(pyblish.api.InstancePlugin): with_audio = False input_is_sequence = self.input_is_sequence(repre) + first_sequence_frame = None input_allow_bg = False if input_is_sequence and repre["files"]: + cols, _ = clique.assemble(repre["files"]) + first_sequence_frame = list(sorted(cols[0].indexes))[0] ext = os.path.splitext(repre["files"][0])[1].replace(".", "") if ext in self.alpha_exts: input_allow_bg = True @@ -467,6 +470,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "resolution_height": instance.data.get("resolutionHeight"), "origin_repre": repre, "input_is_sequence": input_is_sequence, + "first_sequence_frame": first_sequence_frame, "input_allow_bg": input_allow_bg, "with_audio": with_audio, "without_handles": without_handles, @@ -545,9 +549,9 @@ class ExtractReview(pyblish.api.InstancePlugin): if temp_data["input_is_sequence"]: # Set start frame of input sequence (just frame in filename) # - definition of input filepath - ffmpeg_input_args.append( - "-start_number {}".format(temp_data["output_frame_start"]) - ) + ffmpeg_input_args.extend([ + "-start_number", str(temp_data["first_sequence_frame"]) + ]) # TODO add fps mapping `{fps: fraction}` ? # - e.g.: { From 0524ce303623074414bdabfd4b15ec41b1e00d9e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 11:54:39 +0200 Subject: [PATCH 1033/1227] added some comment related to first frame --- openpype/plugins/publish/extract_review.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 107a0994a3..a184f0f3d1 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -446,11 +446,17 @@ class ExtractReview(pyblish.api.InstancePlugin): with_audio = False input_is_sequence = self.input_is_sequence(repre) - first_sequence_frame = None input_allow_bg = False + first_sequence_frame = None if input_is_sequence and repre["files"]: + # Calculate first frame that should be used cols, _ = clique.assemble(repre["files"]) - first_sequence_frame = list(sorted(cols[0].indexes))[0] + input_frames = list(sorted(cols[0].indexes)) + # WARNING: This is an issue as we don't know if first frame + # is with or without handles! + # - in theory we should add handle start but how do we know we can? + first_sequence_frame = input_frames[0] + ext = os.path.splitext(repre["files"][0])[1].replace(".", "") if ext in self.alpha_exts: input_allow_bg = True From ab24d18f31840bfe0e4e2a95085f9e3d0ca8bdbd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 12:02:05 +0200 Subject: [PATCH 1034/1227] try partially solve issue with handles --- openpype/plugins/publish/extract_review.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index a184f0f3d1..1b6e2a1d61 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -452,10 +452,16 @@ class ExtractReview(pyblish.api.InstancePlugin): # Calculate first frame that should be used cols, _ = clique.assemble(repre["files"]) input_frames = list(sorted(cols[0].indexes)) + first_sequence_frame = input_frames[0] # WARNING: This is an issue as we don't know if first frame # is with or without handles! - # - in theory we should add handle start but how do we know we can? - first_sequence_frame = input_frames[0] + # - handle start is added but how do not know if we should + output_duration = (output_frame_end - output_frame_start) + 1 + if ( + without_handles + and len(input_frames) - handle_start >= output_duration + ): + first_sequence_frame += handle_start ext = os.path.splitext(repre["files"][0])[1].replace(".", "") if ext in self.alpha_exts: From 636206f9d2bab1e22337ebbd09c7db68b99e2fa2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 12 Jul 2022 12:19:22 +0200 Subject: [PATCH 1035/1227] Update openpype/hosts/traypublisher/plugins/create/create_mov_batch.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../plugins/create/create_mov_batch.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py b/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py index fdada96c87..20d3ecbd7c 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py +++ b/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py @@ -26,17 +26,12 @@ class BatchMovCreator(TrayPublishCreator): def __init__(self, project_settings, *args, **kwargs): super(BatchMovCreator, self).__init__(project_settings, *args, **kwargs) - self.default_variants = (project_settings["traypublisher"] - ["BatchMovCreator"] - ["default_variants"]) - - self.default_tasks = (project_settings["traypublisher"] - ["BatchMovCreator"] - ["default_tasks"]) - - self.extensions = (project_settings["traypublisher"] - ["BatchMovCreator"] - ["extensions"]) + creator_settings = ( + project_settings["traypublisher"]["BatchMovCreator"] + ) + self.default_variants = creator_settings["default_variants"] + self.default_tasks = creator_settings["default_tasks"] + self.extensions = creator_settings["extensions"] def get_icon(self): return "fa.file" From 6936f836882159e48d81a17272df1249cc11b806 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 12:38:28 +0200 Subject: [PATCH 1036/1227] fix issue with changing instance label --- .../tools/publisher/widgets/list_view_widgets.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/tools/publisher/widgets/list_view_widgets.py b/openpype/tools/publisher/widgets/list_view_widgets.py index 1b1a19599a..8cc6dc45eb 100644 --- a/openpype/tools/publisher/widgets/list_view_widgets.py +++ b/openpype/tools/publisher/widgets/list_view_widgets.py @@ -132,7 +132,7 @@ class InstanceListItemWidget(QtWidgets.QWidget): active_checkbox.stateChanged.connect(self._on_active_change) - self._subset_name_label = subset_name_label + self._instance_label_widget = subset_name_label self._active_checkbox = active_checkbox self._has_valid_context = None @@ -146,8 +146,8 @@ class InstanceListItemWidget(QtWidgets.QWidget): state = "" if not valid: state = "invalid" - self._subset_name_label.setProperty("state", state) - self._subset_name_label.style().polish(self._subset_name_label) + self._instance_label_widget.setProperty("state", state) + self._instance_label_widget.style().polish(self._instance_label_widget) def is_active(self): """Instance is activated.""" @@ -176,9 +176,9 @@ class InstanceListItemWidget(QtWidgets.QWidget): def update_instance_values(self): """Update instance data propagated to widgets.""" # Check subset name - subset_name = self.instance["subset"] - if subset_name != self._subset_name_label.text(): - self._subset_name_label.setText(subset_name) + label = self.instance.label + if label != self._instance_label_widget.text(): + self._instance_label_widget.setText(label) # Check active state self.set_active(self.instance["active"]) # Check valid states From 64d80f2d05c7639505db50a9c512d01d6e1cf224 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Jul 2022 13:40:24 +0200 Subject: [PATCH 1037/1227] 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 43424e0fa963b5506e7e60e924c750aaa9bbf4a2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 14:28:40 +0200 Subject: [PATCH 1038/1227] creator plugin can set group label for ui --- openpype/pipeline/create/creator_plugins.py | 45 ++++++++++++++++----- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 4c7b9f8d94..8fa7b98efa 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -1,5 +1,4 @@ import copy -import logging from abc import ( ABCMeta, @@ -47,6 +46,9 @@ class BaseCreator: # Label shown in UI label = None + group_label = None + # Cached group label after first call 'get_group_label' + _group_label = None # Variable to store logger _log = None @@ -85,11 +87,13 @@ class BaseCreator: Default implementation returns plugin's family. """ + return self.family @abstractproperty def family(self): """Family that plugin represents.""" + pass @property @@ -98,6 +102,16 @@ class BaseCreator: return self.create_context.project_name + def get_group_label(self): + if self._group_label is None: + if self.group_label: + self._group_label = self.group_label + elif self.label: + self._group_label = self.label + else: + self._group_label = self.identifier + return self._group_label + @property def log(self): if self._log is None: @@ -121,6 +135,7 @@ class BaseCreator: - must expect all data that were passed to init in previous implementation """ + pass @abstractmethod @@ -147,6 +162,7 @@ class BaseCreator: self._add_instance_to_context(instance) ``` """ + pass @abstractmethod @@ -154,9 +170,10 @@ class BaseCreator: """Store changes of existing instances so they can be recollected. Args: - update_list(list): Gets list of tuples. Each item + update_list(List[UpdateData]): Gets list of tuples. Each item contain changed instance and it's changes. """ + pass @abstractmethod @@ -167,9 +184,10 @@ class BaseCreator: 'True' if did so. Args: - instance(list): Instance objects which should be + instance(List[CreatedInstance]): Instance objects which should be removed. """ + pass def get_icon(self): @@ -177,6 +195,7 @@ class BaseCreator: Can return path to image file or awesome icon name. """ + return self.icon def get_dynamic_data( @@ -187,6 +206,7 @@ class BaseCreator: These may be get dynamically created based on current context of workfile. """ + return {} def get_subset_name( @@ -211,6 +231,7 @@ class BaseCreator: project_name(str): Project name. host_name(str): Which host creates subset. """ + dynamic_data = self.get_dynamic_data( variant, task_name, asset_doc, project_name, host_name ) @@ -237,9 +258,10 @@ class BaseCreator: keys/values when plugin attributes change. Returns: - list: Attribute definitions that can be tweaked for + List[AbtractAttrDef]: Attribute definitions that can be tweaked for created instance. """ + return self.instance_attr_defs @@ -297,6 +319,7 @@ class Creator(BaseCreator): Returns: str: Short description of family. """ + return self.description def get_detail_description(self): @@ -307,6 +330,7 @@ class Creator(BaseCreator): Returns: str: Detailed description of family for artist. """ + return self.detailed_description def get_default_variants(self): @@ -318,8 +342,9 @@ class Creator(BaseCreator): By default returns `default_variants` value. Returns: - list: Whisper variants for user input. + List[str]: Whisper variants for user input. """ + return copy.deepcopy(self.default_variants) def get_default_variant(self): @@ -338,11 +363,13 @@ class Creator(BaseCreator): """Plugin attribute definitions needed for creation. Attribute definitions of plugin that define how creation will work. Values of these definitions are passed to `create` method. - NOTE: - Convert method should be implemented which should care about updating - keys/values when plugin attributes change. + + Note: + Convert method should be implemented which should care about + updating keys/values when plugin attributes change. + Returns: - list: Attribute definitions that can be tweaked for + List[AbtractAttrDef]: Attribute definitions that can be tweaked for created instance. """ return self.pre_create_attr_defs From cf23aad3cfcc13b209f683b4e6da81c261c3ebb8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 14:28:53 +0200 Subject: [PATCH 1039/1227] instance has group_label attribute which can be used in UI --- openpype/pipeline/create/context.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 7f0341c127..a6d3282726 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -503,6 +503,13 @@ class CreatedInstance: label = self.subset_name return label + @property + def group_label(self): + label = self._data.get("group_label") + if label: + return label + return self.creator.get_group_label() + @property def creator_identifier(self): return self.creator.identifier From b29973f9cc3562ec0fdf11cf704e025ea5ff7687 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 14:41:30 +0200 Subject: [PATCH 1040/1227] added some docstrings --- openpype/pipeline/create/creator_plugins.py | 47 ++++++++++++++++++--- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 8fa7b98efa..49d928ded1 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -103,17 +103,34 @@ class BaseCreator: return self.create_context.project_name def get_group_label(self): + """Group label under which are instances grouped in UI. + + Default implementation use attributes in this order: + - 'group_label' -> 'label' -> 'identifier' + Keep in mind that 'identifier' use 'family' by default. + + Returns: + str: Group label that can be used for grouping of instances in UI. + Group label can be overriden by instance itself. + """ + if self._group_label is None: + label = self.identifier if self.group_label: - self._group_label = self.group_label + label = self.group_label elif self.label: - self._group_label = self.label - else: - self._group_label = self.identifier + label = self.label + self._group_label = label return self._group_label @property def log(self): + """Logger of the plugin. + + Returns: + logging.Logger: Logger with name of the plugin. + """ + if self._log is None: from openpype.api import Logger @@ -121,10 +138,30 @@ class BaseCreator: return self._log def _add_instance_to_context(self, instance): - """Helper method to ad d""" + """Helper method to add instance to create context. + + Instances should be stored to DCC workfile metadata to be able reload + them and also stored to CreateContext in which is creator plugin + existing at the moment to be able use it without refresh of + CreateContext. + + Args: + instance (CreatedInstance): New created instance. + """ + self.create_context.creator_adds_instance(instance) def _remove_instance_from_context(self, instance): + """Helper method to remove instance from create context. + + Instances must be removed from DCC workfile metadat aand from create + context in which plugin is existing at the moment of removement to + propagate the change without restarting create context. + + Args: + instance (CreatedInstance): Instance which should be removed. + """ + self.create_context.creator_removed_instance(instance) @abstractmethod From d8497b4be4c554e245641856425baf98bcb68974 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 14:43:50 +0200 Subject: [PATCH 1041/1227] use group label in UI --- openpype/tools/publisher/widgets/card_view_widgets.py | 2 +- openpype/tools/publisher/widgets/list_view_widgets.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 3c294c9c7c..b6fcee7edb 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -436,7 +436,7 @@ class InstanceCardView(AbstractInstanceView): instances_by_group = collections.defaultdict(list) identifiers_by_group = collections.defaultdict(set) for instance in self.controller.instances: - group_name = instance.creator_label + group_name = instance.group_label instances_by_group[group_name].append(instance) identifiers_by_group[group_name].add( instance.creator_identifier diff --git a/openpype/tools/publisher/widgets/list_view_widgets.py b/openpype/tools/publisher/widgets/list_view_widgets.py index 1b1a19599a..2701413c7a 100644 --- a/openpype/tools/publisher/widgets/list_view_widgets.py +++ b/openpype/tools/publisher/widgets/list_view_widgets.py @@ -519,7 +519,7 @@ class InstanceListView(AbstractInstanceView): instances_by_group_name = collections.defaultdict(list) group_names = set() for instance in self.controller.instances: - group_label = instance.creator_label + group_label = instance.group_label group_names.add(group_label) instances_by_group_name[group_label].append(instance) From 2e94247180b6cff6449f79dc3b6c3e8208ae4144 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 14:46:29 +0200 Subject: [PATCH 1042/1227] changed attribute name '_group_label' -> '_cached_group_label' --- openpype/pipeline/create/creator_plugins.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 49d928ded1..91b9d80234 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -48,7 +48,7 @@ class BaseCreator: label = None group_label = None # Cached group label after first call 'get_group_label' - _group_label = None + _cached_group_label = None # Variable to store logger _log = None @@ -114,14 +114,14 @@ class BaseCreator: Group label can be overriden by instance itself. """ - if self._group_label is None: + if self._cached_group_label is None: label = self.identifier if self.group_label: label = self.group_label elif self.label: label = self.label - self._group_label = label - return self._group_label + self._cached_group_label = label + return self._cached_group_label @property def log(self): From dfb041d8524fe8b1cc415a5be86a49cb0148f529 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 15:06:46 +0200 Subject: [PATCH 1043/1227] 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 48e33db9224460f1ab72aa80a6bc775684a02ab4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Jul 2022 16:02:13 +0200 Subject: [PATCH 1044/1227] Use group key --- openpype/pipeline/create/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index a6d3282726..aecdb04635 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -505,7 +505,7 @@ class CreatedInstance: @property def group_label(self): - label = self._data.get("group_label") + label = self._data.get("group") if label: return label return self.creator.get_group_label() From dada2f4831045370265037a8b1c01c43f4dd2f92 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 16:06:35 +0200 Subject: [PATCH 1045/1227] added function which extract project name based on project id --- .../modules/kitsu/utils/update_op_with_zou.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index de74b0c677..c39d1c5e36 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -33,6 +33,20 @@ def create_op_asset(gazu_entity: dict) -> dict: } +def get_kitsu_project_name(project_id: str): + """Get project name based on project id in kitsu. + + Args: + project_id (str): Id of project in Kitsu. + + Returns: + str: Project name which has project in Kitsu. + """ + + project = gazu.project.get_project(project_id) + return project["name"] + + def set_op_project(dbcon: AvalonMongoDB, project_id: str): """Set project context. @@ -40,9 +54,8 @@ def set_op_project(dbcon: AvalonMongoDB, project_id: str): dbcon (AvalonMongoDB): Connection to DB project_id (str): Project zou ID """ - project = gazu.project.get_project(project_id) - project_name = project["name"] - dbcon.Session["AVALON_PROJECT"] = project_name + + dbcon.Session["AVALON_PROJECT"] = get_kitsu_project_name(project_id) def update_op_assets( From b4d11d4ae709fc65f41ec9c09cbf5e52a183677c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 16:07:10 +0200 Subject: [PATCH 1046/1227] use project name function to drop project collection --- openpype/modules/kitsu/utils/sync_service.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 577050c5af..677d269bca 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -7,6 +7,7 @@ from .credentials import validate_credentials from .update_op_with_zou import ( create_op_asset, set_op_project, + get_kitsu_project_name, write_project_to_op, update_op_assets, ) @@ -124,12 +125,11 @@ class Listener: def _delete_project(self, data): """Delete project.""" - project_doc = self.dbcon.find_one( - {"type": "project", "data.zou_id": data["project_id"]} - ) + + project_name = get_kitsu_project_name(data["project_id"]) # Delete project collection - self.dbcon.database[project_doc["name"]].drop() + self.dbcon.database[project_name].drop() # == Asset == From ee370cb7a315237608f3b60a6b6778d3fc900852 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 16:07:40 +0200 Subject: [PATCH 1047/1227] change project in session instead of replacing AvalonMongoDB with pymongo.Collection --- openpype/modules/kitsu/utils/sync_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 677d269bca..d93197a4bc 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -120,7 +120,7 @@ class Listener: # Write into DB if update_project: - self.dbcon = self.dbcon.database[project_name] + self.dbcon.Session["AVALON_PROJECT"] = project_name self.dbcon.bulk_write([update_project]) def _delete_project(self, data): From e2920ffdb53df5dd8d68b8800436f19edca2ff9c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 16:08:15 +0200 Subject: [PATCH 1048/1227] use query functions in sync service --- openpype/modules/kitsu/utils/sync_service.py | 29 +++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index d93197a4bc..38c1176df9 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -2,6 +2,10 @@ import os import gazu +from openpype.client import ( + get_project, + get_assets +) from openpype.pipeline import AvalonMongoDB from .credentials import validate_credentials from .update_op_with_zou import ( @@ -150,7 +154,8 @@ class Listener: def _update_asset(self, data): """Update asset into OP DB.""" set_op_project(self.dbcon, data["project_id"]) - project_doc = self.dbcon.find_one({"type": "project"}) + project_name = self.dbcon.active_project() + project_doc = get_project(project_name) # Get gazu entity asset = gazu.asset.get_asset(data["asset_id"]) @@ -159,7 +164,7 @@ class Listener: # Query all assets of the local project zou_ids_and_asset_docs = { asset_doc["data"]["zou"]["id"]: asset_doc - for asset_doc in self.dbcon.find({"type": "asset"}) + for asset_doc in get_assets(project_name) if asset_doc["data"].get("zou", {}).get("id") } zou_ids_and_asset_docs[asset["project_id"]] = project_doc @@ -199,7 +204,8 @@ class Listener: def _update_episode(self, data): """Update episode into OP DB.""" set_op_project(self.dbcon, data["project_id"]) - project_doc = self.dbcon.find_one({"type": "project"}) + project_name = self.dbcon.active_project() + project_doc = get_project(project_name) # Get gazu entity episode = gazu.shot.get_episode(data["episode_id"]) @@ -208,7 +214,7 @@ class Listener: # Query all assets of the local project zou_ids_and_asset_docs = { asset_doc["data"]["zou"]["id"]: asset_doc - for asset_doc in self.dbcon.find({"type": "asset"}) + for asset_doc in get_assets(project_name) if asset_doc["data"].get("zou", {}).get("id") } zou_ids_and_asset_docs[episode["project_id"]] = project_doc @@ -249,7 +255,8 @@ class Listener: def _update_sequence(self, data): """Update sequence into OP DB.""" set_op_project(self.dbcon, data["project_id"]) - project_doc = self.dbcon.find_one({"type": "project"}) + project_name = self.dbcon.active_project() + project_doc = get_project(project_name) # Get gazu entity sequence = gazu.shot.get_sequence(data["sequence_id"]) @@ -258,7 +265,7 @@ class Listener: # Query all assets of the local project zou_ids_and_asset_docs = { asset_doc["data"]["zou"]["id"]: asset_doc - for asset_doc in self.dbcon.find({"type": "asset"}) + for asset_doc in get_assets(project_name) if asset_doc["data"].get("zou", {}).get("id") } zou_ids_and_asset_docs[sequence["project_id"]] = project_doc @@ -299,7 +306,8 @@ class Listener: def _update_shot(self, data): """Update shot into OP DB.""" set_op_project(self.dbcon, data["project_id"]) - project_doc = self.dbcon.find_one({"type": "project"}) + project_name = self.dbcon.active_project() + project_doc = get_project(project_name) # Get gazu entity shot = gazu.shot.get_shot(data["shot_id"]) @@ -308,7 +316,7 @@ class Listener: # Query all assets of the local project zou_ids_and_asset_docs = { asset_doc["data"]["zou"]["id"]: asset_doc - for asset_doc in self.dbcon.find({"type": "asset"}) + for asset_doc in get_assets(project_name) if asset_doc["data"].get("zou", {}).get("id") } zou_ids_and_asset_docs[shot["project_id"]] = project_doc @@ -359,10 +367,11 @@ class Listener: def _delete_task(self, data): """Delete task of OP DB.""" - set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data["project_id"]) + project_name = self.dbcon.active_project() # Find asset doc - asset_docs = [doc for doc in self.dbcon.find({"type": "asset"})] + asset_docs = list(get_assets(project_name)) for doc in asset_docs: # Match task for name, task in doc["data"]["tasks"].items(): From d393d6c8956af8365afe5309780762740ff0205c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 16:09:30 +0200 Subject: [PATCH 1049/1227] a little bit more complicated way how to get asset matching zou id --- openpype/modules/kitsu/utils/sync_service.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 38c1176df9..3848eda7ae 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -343,14 +343,25 @@ class Listener: """Create new task into OP DB.""" # Get project entity set_op_project(self.dbcon, data["project_id"]) + project_name = self.dbcon.active_project() # Get gazu entity task = gazu.task.get_task(data["task_id"]) # Find asset doc - asset_doc = self.dbcon.find_one( - {"type": "asset", "data.zou.id": task["entity"]["id"]} + parent_name = task["entity"]["name"] + parent_zou_id = task["entity"]["id"] + asset_docs = get_assets( + project_name, + asset_names=[parent_name], + fields=["_id", "data.zou.id", "data.tasks"] ) + asset_doc = None + for _asset_doc in asset_docs: + doc_zou_id = _asset_doc.get("data", {}).get("zou", {}).get("id") + if doc_zou_id == parent_zou_id: + asset_doc = _asset_doc + break # Update asset tasks with new one asset_tasks = asset_doc["data"].get("tasks") From 20c2cedf75436e463cc6799ab1f3bcb579116abc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 16:09:47 +0200 Subject: [PATCH 1050/1227] use query functions in op to zou sync --- openpype/modules/kitsu/utils/update_zou_with_op.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_zou_with_op.py b/openpype/modules/kitsu/utils/update_zou_with_op.py index 81d421206f..b7bc418c98 100644 --- a/openpype/modules/kitsu/utils/update_zou_with_op.py +++ b/openpype/modules/kitsu/utils/update_zou_with_op.py @@ -6,6 +6,7 @@ from typing import List import gazu from pymongo import UpdateOne +from openpype.client import get_project from openpype.pipeline import AvalonMongoDB from openpype.api import get_project_settings from openpype.modules.kitsu.utils.credentials import validate_credentials @@ -53,9 +54,7 @@ def sync_zou_from_op_project( """ # Get project doc if not provided if not project_doc: - project_doc = dbcon.database[project_name].find_one( - {"type": "project"} - ) + project_doc = get_project(project_name) # Get all entities from zou print(f"Synchronizing {project_name}...") @@ -96,7 +95,7 @@ def sync_zou_from_op_project( dbcon.Session["AVALON_PROJECT"] = project_name asset_docs = { asset_doc["_id"]: asset_doc - for asset_doc in dbcon.find({"type": "asset"}) + for asset_doc in get_assets(project_name) } # Create new assets From 96863fa8aed63dee0b72f6a38e532525f5b6863d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 16:10:37 +0200 Subject: [PATCH 1051/1227] use query functions in zou to op sync --- .../modules/kitsu/utils/update_op_with_zou.py | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index c39d1c5e36..86dee3ce65 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -10,6 +10,12 @@ from gazu.task import ( all_tasks_for_shot, ) +from openpype.client import ( + get_project, + get_assets, + get_asset_by_id, + get_asset_by_name +) from openpype.pipeline import AvalonMongoDB from openpype.api import get_project_settings from openpype.lib import create_project @@ -85,9 +91,7 @@ def update_op_assets( if not item_doc: # Create asset op_asset = create_op_asset(item) insert_result = dbcon.insert_one(op_asset) - item_doc = dbcon.find_one( - {"type": "asset", "_id": insert_result.inserted_id} - ) + item_doc = get_asset_by_id(project_name, insert_result.inserted_id) # Update asset item_data = deepcopy(item_doc["data"]) @@ -235,7 +239,7 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: UpdateOne: Update instance for the project """ project_name = project["name"] - project_doc = dbcon.database[project_name].find_one({"type": "project"}) + project_doc = get_project(project_name) if not project_doc: print(f"Creating project '{project_name}'") project_doc = create_project(project_name, project_name, dbcon=dbcon) @@ -332,19 +336,20 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): bulk_writes.append(write_project_to_op(project, dbcon)) # Try to find project document - dbcon.Session["AVALON_PROJECT"] = project["name"] - project_doc = dbcon.find_one({"type": "project"}) + project_name = project["name"] + dbcon.Session["AVALON_PROJECT"] = project_name + project_doc = get_project(project_name) # Query all assets of the local project zou_ids_and_asset_docs = { asset_doc["data"]["zou"]["id"]: asset_doc - for asset_doc in dbcon.find({"type": "asset"}) + for asset_doc in get_assets(project_name) if asset_doc["data"].get("zou", {}).get("id") } zou_ids_and_asset_docs[project["id"]] = project_doc # Create entities root folders - project_module_settings = get_project_settings(project["name"])["kitsu"] + project_module_settings = get_project_settings(project_name)["kitsu"] for entity_type, root in project_module_settings["entities_root"].items(): parent_folders = root.split("/") direct_parent_doc = None @@ -384,7 +389,7 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): zou_ids_and_asset_docs.update( { asset_doc["data"]["zou"]["id"]: asset_doc - for asset_doc in dbcon.find({"type": "asset"}) + for asset_doc in get_assets(projec_name) if asset_doc["data"].get("zou") } ) From 767dc3dbaba27063289c6853c306acdda8493869 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 16:11:26 +0200 Subject: [PATCH 1052/1227] a little bit more complicated queries using zou id --- .../modules/kitsu/utils/update_op_with_zou.py | 53 +++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 86dee3ce65..bf3705447c 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -179,14 +179,32 @@ def update_op_assets( ) if visual_parent_doc_id is None: # Find root folder doc - root_folder_doc = dbcon.find_one( - { - "type": "asset", - "name": entity_parent_folders[-1], - "data.root_of": substitute_item_type, - }, - ["_id"], + root_folder_docs = get_assets( + project_name, + asset_name=[entity_parent_folders[-1]], + fields=["_id", "data.root_of"] ) + # NOTE: Not sure why it's checking for entity type? + # OP3 does not support multiple assets with same names so type + # filtering is irelevant. + # This way mimics previous implementation: + # ``` + # root_folder_doc = dbcon.find_one( + # { + # "type": "asset", + # "name": entity_parent_folders[-1], + # "data.root_of": substitute_item_type, + # }, + # ["_id"], + # ) + # ``` + root_folder_doc = None + for folder_doc in root_folder_docs: + root_of = folder_doc.get("data", {}).get("root_of") + if root_of == substitute_item_type: + root_folder_doc = folder_doc + break + if root_folder_doc: visual_parent_doc_id = root_folder_doc["_id"] @@ -354,9 +372,26 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): parent_folders = root.split("/") direct_parent_doc = None for i, folder in enumerate(parent_folders, 1): - parent_doc = dbcon.find_one( - {"type": "asset", "name": folder, "data.root_of": entity_type} + parent_doc = get_asset_by_name( + project_name, folder, fields=["_id", "data.root_of"] ) + # NOTE: Not sure why it's checking for entity type? + # OP3 does not support multiple assets with same names so type + # filtering is irelevant. + # Also all of the entities could find be queried at once using + # 'get_assets'. + # This way mimics previous implementation: + # ``` + # parent_doc = dbcon.find_one( + # {"type": "asset", "name": folder, "data.root_of": entity_type} + # ) + # ``` + if ( + parent_doc + and parent_doc.get("data", {}).get("root_of") != entity_type + ): + parent_doc = None + if not parent_doc: direct_parent_doc = dbcon.insert_one( { From 498ed608ebce7d639fcc091da979307f0f21da62 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Jul 2022 16:24:22 +0200 Subject: [PATCH 1053/1227] 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 1054/1227] 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 2988af04ffcd1a42a4ca40fe4a2dbe80f163ffff Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 16:57:49 +0200 Subject: [PATCH 1055/1227] added missing import --- openpype/modules/kitsu/utils/update_zou_with_op.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_zou_with_op.py b/openpype/modules/kitsu/utils/update_zou_with_op.py index b7bc418c98..57d7094e95 100644 --- a/openpype/modules/kitsu/utils/update_zou_with_op.py +++ b/openpype/modules/kitsu/utils/update_zou_with_op.py @@ -6,7 +6,7 @@ from typing import List import gazu from pymongo import UpdateOne -from openpype.client import get_project +from openpype.client import get_project, get_assets from openpype.pipeline import AvalonMongoDB from openpype.api import get_project_settings from openpype.modules.kitsu.utils.credentials import validate_credentials From 5e14a54d8248b7ddb3f02b778d89ca4a391f15c8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 17:02:54 +0200 Subject: [PATCH 1056/1227] fix typo --- openpype/modules/kitsu/utils/update_op_with_zou.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index bf3705447c..f56a131b8e 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -424,7 +424,7 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): zou_ids_and_asset_docs.update( { asset_doc["data"]["zou"]["id"]: asset_doc - for asset_doc in get_assets(projec_name) + for asset_doc in get_assets(project_name) if asset_doc["data"].get("zou") } ) From 9d4d8873358bcb6fde08e46817f47a09d858c68d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 17:13:50 +0200 Subject: [PATCH 1057/1227] 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 1058/1227] 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 1059/1227] 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 1060/1227] 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 1061/1227] 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 bfd565e29d40f0daca2ddbdb01d036d9dc6b543a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 12 Jul 2022 17:57:07 +0200 Subject: [PATCH 1062/1227] OP-3446 - add traypublisher to host enum --- openpype/settings/entities/enum_entity.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/settings/entities/enum_entity.py b/openpype/settings/entities/enum_entity.py index 92a397afba..03998677ce 100644 --- a/openpype/settings/entities/enum_entity.py +++ b/openpype/settings/entities/enum_entity.py @@ -169,6 +169,7 @@ class HostsEnumEntity(BaseEnumEntity): "tvpaint", "unreal", "standalonepublisher", + "traypublisher", "webpublisher" ] From 4fe4ac163e96d7a08cce31d30a8d8fdb85b35cc1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 12 Jul 2022 17:57:47 +0200 Subject: [PATCH 1063/1227] OP-3446 - add traypublisher ftrack setting --- .../defaults/project_settings/ftrack.json | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index 831c34835e..eb90778353 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -268,6 +268,49 @@ } ] }, + { + "hosts": [ + "traypublisher" + ], + "families": [], + "task_types": [], + "tasks": [], + "add_ftrack_family": true, + "advanced_filtering": [] + }, + { + "hosts": [ + "traypublisher" + ], + "families": [ + "matchmove", + "shot" + ], + "task_types": [], + "tasks": [], + "add_ftrack_family": false, + "advanced_filtering": [] + }, + { + "hosts": [ + "traypublisher" + ], + "families": [ + "plate" + ], + "task_types": [], + "tasks": [], + "add_ftrack_family": false, + "advanced_filtering": [ + { + "families": [ + "clip", + "review" + ], + "add_ftrack_family": true + } + ] + }, { "hosts": [ "maya" From cec639cf7ed62ed28b37c4839752e72d0e845e9b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 12 Jul 2022 18:04:55 +0200 Subject: [PATCH 1064/1227] OP-3446 - updated settings for TrayPublisher --- .../project_settings/traypublisher.json | 6 --- .../schema_project_traypublisher.json | 39 ------------------- 2 files changed, 45 deletions(-) diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 36526d01b0..cb3d3d1d1a 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -33,13 +33,7 @@ } ], "BatchMovCreator": { - "family": "render_mov_batch", - "identifier": "", - "label": "Batch Mov", - "icon": "fa.file", "default_variants": ["Main"], - "description": "", - "detailed_description": "", "default_tasks": ["Compositing"], "extensions": [ ".mov" 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 308883d46f..d4ad57767a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -87,28 +87,6 @@ "use_label_wrap": true, "collapsible_key": true, "children": [ - { - "type": "text", - "key": "family", - "label": "Family" - }, - { - "type": "text", - "key": "identifier", - "label": "Identifier", - "placeholder": "< Use 'Family' >", - "tooltip": "All creators must have unique identifier.\nBy default is used 'family' but if you need to have more creators with same families\nyou have to set identifier too." - }, - { - "type": "text", - "key": "label", - "label": "Label" - }, - { - "type": "text", - "key": "icon", - "label": "Icon" - }, { "type": "list", "key": "default_variants", @@ -117,23 +95,6 @@ "type": "text" } }, - { - "type": "separator" - }, - { - "type": "text", - "key": "description", - "label": "Description" - }, - { - "type": "text", - "key": "detailed_description", - "label": "Detailed Description", - "multiline": true - }, - { - "type": "separator" - }, { "type": "list", "key": "default_tasks", From 018896f9239f3fb3a036512a892a8f48593dca85 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 18:28:03 +0200 Subject: [PATCH 1065/1227] 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 e8f30eea9a8914ed09360d56bcd7b2a60d8367fc Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Jul 2022 18:30:46 +0200 Subject: [PATCH 1066/1227] Added typing notation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Félix David --- openpype/modules/kitsu/utils/update_op_with_zou.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index f56a131b8e..a68d6d31c3 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -39,7 +39,7 @@ def create_op_asset(gazu_entity: dict) -> dict: } -def get_kitsu_project_name(project_id: str): +def get_kitsu_project_name(project_id: str)->str: """Get project name based on project id in kitsu. Args: From 77ffca938a7cd4a3600dd8167bf4e2f346c11fd7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Jul 2022 18:53:58 +0200 Subject: [PATCH 1067/1227] 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 1068/1227] 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 1069/1227] 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 1070/1227] 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 0222a1518d1131e31d586f47dbf4f11b1c1ebbd0 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 13 Jul 2022 03:57:50 +0000 Subject: [PATCH 1071/1227] [Automated] Bump version --- CHANGELOG.md | 30 +++++++++++------------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a0c058f73..55ee51b38a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [3.12.1-nightly.4](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.12.1-nightly.5](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.0...HEAD) @@ -14,6 +14,9 @@ **🚀 Enhancements** +- TrayPublisher: Added more options for grouping of instances [\#3494](https://github.com/pypeclub/OpenPype/pull/3494) +- NewPublisher: Align creator attributes from top to bottom [\#3487](https://github.com/pypeclub/OpenPype/pull/3487) +- NewPublisher: Added ability to use label of instance [\#3484](https://github.com/pypeclub/OpenPype/pull/3484) - General: Creator Plugins have access to project [\#3476](https://github.com/pypeclub/OpenPype/pull/3476) - General: Better arguments order in creator init [\#3475](https://github.com/pypeclub/OpenPype/pull/3475) - Ftrack: Trigger custom ftrack events on project creation and preparation [\#3465](https://github.com/pypeclub/OpenPype/pull/3465) @@ -25,6 +28,11 @@ **🐛 Bug fixes** +- TrayPublisher: Keep use instance label in list view [\#3493](https://github.com/pypeclub/OpenPype/pull/3493) +- General: Extract review use first frame of input sequence [\#3491](https://github.com/pypeclub/OpenPype/pull/3491) +- General: Fix Plist loading for application launch [\#3485](https://github.com/pypeclub/OpenPype/pull/3485) +- Nuke: Workfile tools open on start [\#3479](https://github.com/pypeclub/OpenPype/pull/3479) +- New Publisher: Disabled context change allows creation [\#3478](https://github.com/pypeclub/OpenPype/pull/3478) - General: thumbnail extractor fix [\#3474](https://github.com/pypeclub/OpenPype/pull/3474) - Kitsu: bugfix with sync-service ans publish plugins [\#3473](https://github.com/pypeclub/OpenPype/pull/3473) - Flame: solved problem with multi-selected loading [\#3470](https://github.com/pypeclub/OpenPype/pull/3470) @@ -46,7 +54,9 @@ - General: Use query functions in global plugins [\#3459](https://github.com/pypeclub/OpenPype/pull/3459) - Clockify: Use query functions in clockify actions [\#3458](https://github.com/pypeclub/OpenPype/pull/3458) - General: Use query functions in rest api calls [\#3457](https://github.com/pypeclub/OpenPype/pull/3457) +- General: Use query functions in openpype lib functions [\#3454](https://github.com/pypeclub/OpenPype/pull/3454) - General: Use query functions in load utils [\#3446](https://github.com/pypeclub/OpenPype/pull/3446) +- General: Move publish plugin and publish render abstractions [\#3442](https://github.com/pypeclub/OpenPype/pull/3442) - General: Use Anatomy after move to pipeline [\#3436](https://github.com/pypeclub/OpenPype/pull/3436) - General: Anatomy moved to pipeline [\#3435](https://github.com/pypeclub/OpenPype/pull/3435) - Fusion: Use client query functions [\#3380](https://github.com/pypeclub/OpenPype/pull/3380) @@ -66,8 +76,6 @@ - Webserver: Added CORS middleware [\#3422](https://github.com/pypeclub/OpenPype/pull/3422) - Attribute Defs UI: Files widget show what is allowed to drop in [\#3411](https://github.com/pypeclub/OpenPype/pull/3411) - General: Add ability to change user value for templates [\#3366](https://github.com/pypeclub/OpenPype/pull/3366) -- Hosts: More options for in-host callbacks [\#3357](https://github.com/pypeclub/OpenPype/pull/3357) -- Multiverse: expose some settings to GUI [\#3350](https://github.com/pypeclub/OpenPype/pull/3350) **🐛 Bug fixes** @@ -82,7 +90,6 @@ - Maya: vray device aspect ratio fix [\#3381](https://github.com/pypeclub/OpenPype/pull/3381) - Flame: bunch of publishing issues [\#3377](https://github.com/pypeclub/OpenPype/pull/3377) - Harmony: added unc path to zifile command in Harmony [\#3372](https://github.com/pypeclub/OpenPype/pull/3372) -- Standalone: settings improvements [\#3355](https://github.com/pypeclub/OpenPype/pull/3355) **🔀 Refactored code** @@ -107,16 +114,9 @@ [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.11.1-nightly.1...3.11.1) -**🆕 New features** - -- Flame: custom export temp folder [\#3346](https://github.com/pypeclub/OpenPype/pull/3346) -- Nuke: removing third-party plugins [\#3344](https://github.com/pypeclub/OpenPype/pull/3344) - **🚀 Enhancements** - Pyblish Pype: Hiding/Close issues [\#3367](https://github.com/pypeclub/OpenPype/pull/3367) -- Ftrack: Removed requirement of pypeclub role from default settings [\#3354](https://github.com/pypeclub/OpenPype/pull/3354) -- Kitsu: Prevent crash on missing frames information [\#3352](https://github.com/pypeclub/OpenPype/pull/3352) **🐛 Bug fixes** @@ -125,19 +125,11 @@ - Nuke: Fix missing variable in extract thumbnail [\#3363](https://github.com/pypeclub/OpenPype/pull/3363) - Nuke: Fix precollect writes [\#3361](https://github.com/pypeclub/OpenPype/pull/3361) - AE- fix validate\_scene\_settings and renderLocal [\#3358](https://github.com/pypeclub/OpenPype/pull/3358) -- deadline: fixing misidentification of revieables [\#3356](https://github.com/pypeclub/OpenPype/pull/3356) -- General: Create only one thumbnail per instance [\#3351](https://github.com/pypeclub/OpenPype/pull/3351) -- nuke: adding extract thumbnail settings 3.10 [\#3347](https://github.com/pypeclub/OpenPype/pull/3347) -- General: Fix last version function [\#3345](https://github.com/pypeclub/OpenPype/pull/3345) ## [3.11.0](https://github.com/pypeclub/OpenPype/tree/3.11.0) (2022-06-17) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.11.0-nightly.4...3.11.0) -**🐛 Bug fixes** - -- General: Handle empty source key on instance [\#3342](https://github.com/pypeclub/OpenPype/pull/3342) - ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.10.0-nightly.6...3.10.0) diff --git a/openpype/version.py b/openpype/version.py index 3239b0e2a2..08bb4706cc 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.12.1-nightly.4" +__version__ = "3.12.1-nightly.5" diff --git a/pyproject.toml b/pyproject.toml index f5bd7cc946..1251299612 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.12.1-nightly.4" # OpenPype +version = "3.12.1-nightly.5" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 8bbf693a92a247894a10ef531ac433b3740ac154 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 13 Jul 2022 09:03:41 +0200 Subject: [PATCH 1072/1227] add unpack and pack tools --- tools/pack_project.ps1 | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 tools/pack_project.ps1 diff --git a/tools/pack_project.ps1 b/tools/pack_project.ps1 new file mode 100644 index 0000000000..36ec3cb96b --- /dev/null +++ b/tools/pack_project.ps1 @@ -0,0 +1,39 @@ +<# +.SYNOPSIS + Helper script OpenPype Packing project. + +.DESCRIPTION + Once you are happy with the project and want to preserve it for future work, just change the project name on line 38 and copy the file into .\OpenPype\tools. Then use the cmd form .EXAMPLE + +.EXAMPLE + +PS> .\tools\run_pack_project.ps1 + +#> +$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" + +# make sure Poetry is in PATH +if (-not (Test-Path 'env:POETRY_HOME')) { + $env:POETRY_HOME = "$openpype_root\.poetry" +} +$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" + +Set-Location -Path $openpype_root + +Write-Host ">>> " -NoNewline -ForegroundColor Green +Write-Host "Reading Poetry ... " -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" +} else { + Write-Host "OK" -ForegroundColor Green +} + +& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" pack-project --project "OP02_VFX_demo" +Set-Location -Path $current_dir \ No newline at end of file From 9a3a01c9ed6afeec9015b3dd06c5ef2e2438cc60 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 13 Jul 2022 09:03:50 +0200 Subject: [PATCH 1073/1227] add unpack tool --- tools/unpack_project.ps1.lnk | Bin 0 -> 1426 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tools/unpack_project.ps1.lnk diff --git a/tools/unpack_project.ps1.lnk b/tools/unpack_project.ps1.lnk new file mode 100644 index 0000000000000000000000000000000000000000..56eee50ca235b0ea76eca52ed0040137a07f9786 GIT binary patch literal 1426 zcma)6Nla5w6ulruP?QphqQQWe7!+DM$QVq(Dv(G@^%ueVBh=p-3T^+VzX+HpPPi}< z7aBAqCImFPkOeN(2ohYVi6aX&xO0fcpsBdf6%x;T9cXYNuerk;{<-&^d;4mLC@w7? znrMP+svM<6%7TA?w)yjnZYplvb~woFi%o7mzn%~}7#A{8?TAmKOzZpF$*k#8w`OoE zk#hC=tLrG=Tq;Hn%{!fB;1gB&J(5aws>`pElaeTbevCibt8J2OIf4!+%G}GLS42N7 zqUUdjQINJh5!4B6SkXoqYGs)lM6m(SXzU~5Mh(IWUNBwId5JhPHPj4i0c>6^(^ zOA7FOxN8^ph|;6?mTVJt4FzP`qeky-5duJ+urt0t*ps0d=dq)*dyWFgQ^w61-)Z38 zNURmucUe?tu~wLOA2;Z%0?N8CoGsgn;0jeNQDZD9d{e_5wIfpnd`i?xjqv=4(t`XY zMATufa!xXEx)s7KLt=ItgpC$RI%ElCef3bG2DaYqbIKhmq5F7qiM-SzE07rBRpc~? z?LNgJwKgh#xy2;~bc%{AY(Z!piP7SS|Bx!i5F#$>64_)%Yltz8E5Ik8ApZ@ehW+YY?o$ z$8!ypV>QL*(b?4(H(&9-bYq=AS($M1AOhY51Ee7QHn3R%#E4g-kYBZg?-fxItRC-1 z5rmPTMaQw1q!_ajoYiwAJ#6SLBdms!7+K@pIozvM->5FvUEAJyegoE Date: Wed, 13 Jul 2022 09:19:34 +0200 Subject: [PATCH 1074/1227] fix forgotten variables --- openpype/lib/project_backpack.py | 2 +- tools/pack_project.ps1 | 2 +- tools/unpack_project.ps1 | 39 +++++++++++++++++++++++++++++++ tools/unpack_project.ps1.lnk | Bin 1426 -> 0 bytes 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 tools/unpack_project.ps1 delete mode 100644 tools/unpack_project.ps1.lnk diff --git a/openpype/lib/project_backpack.py b/openpype/lib/project_backpack.py index f0188e6765..ff2f1d4b88 100644 --- a/openpype/lib/project_backpack.py +++ b/openpype/lib/project_backpack.py @@ -53,7 +53,7 @@ def pack_project(project_name, destination_dir=None): Args: project_name(str): Project that should be packaged. - destination_dir(str): Optinal path where zip will be stored. Project's + destination_dir(str): Optional path where zip will be stored. Project's root is used if not passed. """ print("Creating package of project \"{}\"".format(project_name)) diff --git a/tools/pack_project.ps1 b/tools/pack_project.ps1 index 36ec3cb96b..856247f7ca 100644 --- a/tools/pack_project.ps1 +++ b/tools/pack_project.ps1 @@ -35,5 +35,5 @@ if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { Write-Host "OK" -ForegroundColor Green } -& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" pack-project --project "OP02_VFX_demo" +& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" pack-project --project $ARGS Set-Location -Path $current_dir \ No newline at end of file diff --git a/tools/unpack_project.ps1 b/tools/unpack_project.ps1 new file mode 100644 index 0000000000..e7b9e87a7f --- /dev/null +++ b/tools/unpack_project.ps1 @@ -0,0 +1,39 @@ +<# +.SYNOPSIS + Helper script OpenPype Unpacking project. + +.DESCRIPTION + Make sure you had dropped the project from your db and removed the poject data in case you were having them previously. Then on line 38 change the to any path where the zip with project is - usually we are having it here https://drive.google.com/drive/u/0/folders/0AKE4mxImOsAGUk9PVA . Copy the file into .\OpenPype\tools. Then use the cmd form .EXAMPLE + +.EXAMPLE + +PS> .\tools\run_unpack_project.ps1 + +#> +$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" + +# make sure Poetry is in PATH +if (-not (Test-Path 'env:POETRY_HOME')) { + $env:POETRY_HOME = "$openpype_root\.poetry" +} +$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" + +Set-Location -Path $openpype_root + +Write-Host ">>> " -NoNewline -ForegroundColor Green +Write-Host "Reading Poetry ... " -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" +} else { + Write-Host "OK" -ForegroundColor Green +} + +& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" unpack-project --zipfile $ARGS +Set-Location -Path $current_dir \ No newline at end of file diff --git a/tools/unpack_project.ps1.lnk b/tools/unpack_project.ps1.lnk deleted file mode 100644 index 56eee50ca235b0ea76eca52ed0040137a07f9786..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1426 zcma)6Nla5w6ulruP?QphqQQWe7!+DM$QVq(Dv(G@^%ueVBh=p-3T^+VzX+HpPPi}< z7aBAqCImFPkOeN(2ohYVi6aX&xO0fcpsBdf6%x;T9cXYNuerk;{<-&^d;4mLC@w7? znrMP+svM<6%7TA?w)yjnZYplvb~woFi%o7mzn%~}7#A{8?TAmKOzZpF$*k#8w`OoE zk#hC=tLrG=Tq;Hn%{!fB;1gB&J(5aws>`pElaeTbevCibt8J2OIf4!+%G}GLS42N7 zqUUdjQINJh5!4B6SkXoqYGs)lM6m(SXzU~5Mh(IWUNBwId5JhPHPj4i0c>6^(^ zOA7FOxN8^ph|;6?mTVJt4FzP`qeky-5duJ+urt0t*ps0d=dq)*dyWFgQ^w61-)Z38 zNURmucUe?tu~wLOA2;Z%0?N8CoGsgn;0jeNQDZD9d{e_5wIfpnd`i?xjqv=4(t`XY zMATufa!xXEx)s7KLt=ItgpC$RI%ElCef3bG2DaYqbIKhmq5F7qiM-SzE07rBRpc~? z?LNgJwKgh#xy2;~bc%{AY(Z!piP7SS|Bx!i5F#$>64_)%Yltz8E5Ik8ApZ@ehW+YY?o$ z$8!ypV>QL*(b?4(H(&9-bYq=AS($M1AOhY51Ee7QHn3R%#E4g-kYBZg?-fxItRC-1 z5rmPTMaQw1q!_ajoYiwAJ#6SLBdms!7+K@pIozvM->5FvUEAJyegoE Date: Wed, 13 Jul 2022 07:26:22 +0000 Subject: [PATCH 1075/1227] [Automated] Bump version --- CHANGELOG.md | 15 +++++---------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55ee51b38a..59c51396e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,9 @@ # Changelog -## [3.12.1-nightly.5](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.12.1-nightly.6](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.0...HEAD) -### 📖 Documentation - -- Docs: Added minimal permissions for MongoDB [\#3441](https://github.com/pypeclub/OpenPype/pull/3441) - **🆕 New features** - Maya: Add VDB to Arnold loader [\#3433](https://github.com/pypeclub/OpenPype/pull/3433) @@ -24,7 +20,7 @@ - Blender: Bugfix - Set fps properly on open [\#3426](https://github.com/pypeclub/OpenPype/pull/3426) - Hiero: Add custom scripts menu [\#3425](https://github.com/pypeclub/OpenPype/pull/3425) - Blender: pre pyside install for all platforms [\#3400](https://github.com/pypeclub/OpenPype/pull/3400) -- Maya: Ability to set resolution for playblasts from asset, and override through review instance. [\#3360](https://github.com/pypeclub/OpenPype/pull/3360) +- Maya: Add additional playblast options to review Extractor. [\#3384](https://github.com/pypeclub/OpenPype/pull/3384) **🐛 Bug fixes** @@ -40,7 +36,6 @@ - Resolve: removed few bugs [\#3464](https://github.com/pypeclub/OpenPype/pull/3464) - General: Delete old versions is safer when ftrack is disabled [\#3462](https://github.com/pypeclub/OpenPype/pull/3462) - Nuke: fixing metadata slate TC difference [\#3455](https://github.com/pypeclub/OpenPype/pull/3455) -- Nuke: prerender reviewable fails [\#3450](https://github.com/pypeclub/OpenPype/pull/3450) - Maya: fix hashing in Python 3 for tile rendering [\#3447](https://github.com/pypeclub/OpenPype/pull/3447) - LogViewer: Escape html characters in log message [\#3443](https://github.com/pypeclub/OpenPype/pull/3443) - Nuke: Slate frame is integrated [\#3427](https://github.com/pypeclub/OpenPype/pull/3427) @@ -59,8 +54,8 @@ - General: Move publish plugin and publish render abstractions [\#3442](https://github.com/pypeclub/OpenPype/pull/3442) - General: Use Anatomy after move to pipeline [\#3436](https://github.com/pypeclub/OpenPype/pull/3436) - General: Anatomy moved to pipeline [\#3435](https://github.com/pypeclub/OpenPype/pull/3435) -- Fusion: Use client query functions [\#3380](https://github.com/pypeclub/OpenPype/pull/3380) - Resolve: Use client query functions [\#3379](https://github.com/pypeclub/OpenPype/pull/3379) +- Photoshop: Use client query functions [\#3375](https://github.com/pypeclub/OpenPype/pull/3375) ## [3.12.0](https://github.com/pypeclub/OpenPype/tree/3.12.0) (2022-06-28) @@ -68,6 +63,7 @@ ### 📖 Documentation +- Docs: Added minimal permissions for MongoDB [\#3441](https://github.com/pypeclub/OpenPype/pull/3441) - Fix typo in documentation: pyenv on mac [\#3417](https://github.com/pypeclub/OpenPype/pull/3417) - Linux: update OIIO package [\#3401](https://github.com/pypeclub/OpenPype/pull/3401) @@ -100,9 +96,9 @@ - Hiero: Use client query functions [\#3393](https://github.com/pypeclub/OpenPype/pull/3393) - Nuke: Use client query functions [\#3391](https://github.com/pypeclub/OpenPype/pull/3391) - Maya: Use client query functions [\#3385](https://github.com/pypeclub/OpenPype/pull/3385) +- Fusion: Use client query functions [\#3380](https://github.com/pypeclub/OpenPype/pull/3380) - Harmony: Use client query functions [\#3378](https://github.com/pypeclub/OpenPype/pull/3378) - Celaction: Use client query functions [\#3376](https://github.com/pypeclub/OpenPype/pull/3376) -- Photoshop: Use client query functions [\#3375](https://github.com/pypeclub/OpenPype/pull/3375) - AfterEffects: Use client query functions [\#3374](https://github.com/pypeclub/OpenPype/pull/3374) **Merged pull requests:** @@ -124,7 +120,6 @@ - Harmony: audio validator has wrong logic [\#3364](https://github.com/pypeclub/OpenPype/pull/3364) - Nuke: Fix missing variable in extract thumbnail [\#3363](https://github.com/pypeclub/OpenPype/pull/3363) - Nuke: Fix precollect writes [\#3361](https://github.com/pypeclub/OpenPype/pull/3361) -- AE- fix validate\_scene\_settings and renderLocal [\#3358](https://github.com/pypeclub/OpenPype/pull/3358) ## [3.11.0](https://github.com/pypeclub/OpenPype/tree/3.11.0) (2022-06-17) diff --git a/openpype/version.py b/openpype/version.py index 08bb4706cc..26c7e4fa34 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.12.1-nightly.5" +__version__ = "3.12.1-nightly.6" diff --git a/pyproject.toml b/pyproject.toml index 1251299612..ed7799a7ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.12.1-nightly.5" # OpenPype +version = "3.12.1-nightly.6" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From f9b23a27d20b07b549fcf43277bfc796d73e6da8 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 13 Jul 2022 07:43:12 +0000 Subject: [PATCH 1076/1227] [Automated] Release --- CHANGELOG.md | 16 ++++++++++------ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59c51396e3..cc5bf39a29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ # Changelog -## [3.12.1-nightly.6](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.12.1](https://github.com/pypeclub/OpenPype/tree/3.12.1) (2022-07-13) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.0...HEAD) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.0...3.12.1) + +### 📖 Documentation + +- Docs: Added minimal permissions for MongoDB [\#3441](https://github.com/pypeclub/OpenPype/pull/3441) **🆕 New features** @@ -36,11 +40,13 @@ - Resolve: removed few bugs [\#3464](https://github.com/pypeclub/OpenPype/pull/3464) - General: Delete old versions is safer when ftrack is disabled [\#3462](https://github.com/pypeclub/OpenPype/pull/3462) - Nuke: fixing metadata slate TC difference [\#3455](https://github.com/pypeclub/OpenPype/pull/3455) +- Nuke: prerender reviewable fails [\#3450](https://github.com/pypeclub/OpenPype/pull/3450) - Maya: fix hashing in Python 3 for tile rendering [\#3447](https://github.com/pypeclub/OpenPype/pull/3447) - LogViewer: Escape html characters in log message [\#3443](https://github.com/pypeclub/OpenPype/pull/3443) - Nuke: Slate frame is integrated [\#3427](https://github.com/pypeclub/OpenPype/pull/3427) - Maya: Camera extra data - additional fix for \#3304 [\#3386](https://github.com/pypeclub/OpenPype/pull/3386) - Maya: Handle excluding `model` family from frame range validator. [\#3370](https://github.com/pypeclub/OpenPype/pull/3370) +- Harmony: audio validator has wrong logic [\#3364](https://github.com/pypeclub/OpenPype/pull/3364) **🔀 Refactored code** @@ -54,8 +60,8 @@ - General: Move publish plugin and publish render abstractions [\#3442](https://github.com/pypeclub/OpenPype/pull/3442) - General: Use Anatomy after move to pipeline [\#3436](https://github.com/pypeclub/OpenPype/pull/3436) - General: Anatomy moved to pipeline [\#3435](https://github.com/pypeclub/OpenPype/pull/3435) +- Fusion: Use client query functions [\#3380](https://github.com/pypeclub/OpenPype/pull/3380) - Resolve: Use client query functions [\#3379](https://github.com/pypeclub/OpenPype/pull/3379) -- Photoshop: Use client query functions [\#3375](https://github.com/pypeclub/OpenPype/pull/3375) ## [3.12.0](https://github.com/pypeclub/OpenPype/tree/3.12.0) (2022-06-28) @@ -63,7 +69,6 @@ ### 📖 Documentation -- Docs: Added minimal permissions for MongoDB [\#3441](https://github.com/pypeclub/OpenPype/pull/3441) - Fix typo in documentation: pyenv on mac [\#3417](https://github.com/pypeclub/OpenPype/pull/3417) - Linux: update OIIO package [\#3401](https://github.com/pypeclub/OpenPype/pull/3401) @@ -96,9 +101,9 @@ - Hiero: Use client query functions [\#3393](https://github.com/pypeclub/OpenPype/pull/3393) - Nuke: Use client query functions [\#3391](https://github.com/pypeclub/OpenPype/pull/3391) - Maya: Use client query functions [\#3385](https://github.com/pypeclub/OpenPype/pull/3385) -- Fusion: Use client query functions [\#3380](https://github.com/pypeclub/OpenPype/pull/3380) - Harmony: Use client query functions [\#3378](https://github.com/pypeclub/OpenPype/pull/3378) - Celaction: Use client query functions [\#3376](https://github.com/pypeclub/OpenPype/pull/3376) +- Photoshop: Use client query functions [\#3375](https://github.com/pypeclub/OpenPype/pull/3375) - AfterEffects: Use client query functions [\#3374](https://github.com/pypeclub/OpenPype/pull/3374) **Merged pull requests:** @@ -117,7 +122,6 @@ **🐛 Bug fixes** - Nuke: bake streams with slate on farm [\#3368](https://github.com/pypeclub/OpenPype/pull/3368) -- Harmony: audio validator has wrong logic [\#3364](https://github.com/pypeclub/OpenPype/pull/3364) - Nuke: Fix missing variable in extract thumbnail [\#3363](https://github.com/pypeclub/OpenPype/pull/3363) - Nuke: Fix precollect writes [\#3361](https://github.com/pypeclub/OpenPype/pull/3361) diff --git a/openpype/version.py b/openpype/version.py index 26c7e4fa34..c7b0de0381 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.12.1-nightly.6" +__version__ = "3.12.1" diff --git a/pyproject.toml b/pyproject.toml index ed7799a7ba..4bdaaab4ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.12.1-nightly.6" # OpenPype +version = "3.12.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From c9e714a7afc37f98c9cb4792fa42b53b1b2272c3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Jul 2022 12:00:21 +0200 Subject: [PATCH 1077/1227] 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 1078/1227] 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 1079/1227] 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 1080/1227] 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 1081/1227] 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 1082/1227] 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 927674be1afa804225b1a407b02de0b5ff5146e9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 13 Jul 2022 15:11:58 +0200 Subject: [PATCH 1083/1227] OP-3446 - store source file as a 'source' This will be stored in DB in version. Potentially it could be used to populate Ftrack Note. --- .../hosts/traypublisher/plugins/publish/collect_mov_batch.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py b/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py index 2a5e356684..c81d1f77a5 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py @@ -31,4 +31,6 @@ class CollectMovBatch( instance.data["representations"].append(repre) + instance.data["source"] = file_url + self.log.debug("instance.data {}".format(instance.data)) From 450a20dcca1b74d01c23beebdd170b356a8ccb34 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 13 Jul 2022 15:28:01 +0200 Subject: [PATCH 1084/1227] 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 1085/1227] 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 1086/1227] 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 1087/1227] 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 1088/1227] 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 1089/1227] 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 1090/1227] 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 1091/1227] 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 45473c5a832b4db881ca328ad89324ada93ae0e5 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 13 Jul 2022 16:24:08 +0200 Subject: [PATCH 1092/1227] add host and families settings to integrators --- .../defaults/project_settings/global.json | 171 ++++++++++++++++++ .../schemas/schema_global_publish.json | 79 ++++++++ 2 files changed, 250 insertions(+) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 4e9b61100e..545c792d47 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -171,6 +171,177 @@ ] }, "IntegrateAssetNew": { + "hosts": [ + "aftereffects", + "blender", + "celaction", + "flame", + "fusion", + "harmony", + "hiero", + "houdini", + "nuke", + "photoshop", + "resolve", + "tvpaint", + "unreal", + "standalonepublisher", + "webpublisher" + ], + "families": [ + "workfile", + "pointcache", + "camera", + "animation", + "model", + "mayaAscii", + "mayaScene", + "setdress", + "layout", + "ass", + "vdbcache", + "scene", + "vrayproxy", + "vrayscene_layer", + "render", + "prerender", + "imagesequence", + "review", + "rendersetup", + "rig", + "plate", + "look", + "audio", + "yetiRig", + "yeticache", + "nukenodes", + "gizmo", + "source", + "matchmove", + "image", + "assembly", + "fbx", + "textures", + "action", + "harmony.template", + "harmony.palette", + "editorial", + "background", + "camerarig", + "redshiftproxy", + "effect", + "xgen", + "hda", + "usd", + "staticMesh", + "skeletalMesh", + "mvLook", + "mvUsd", + "mvUsdComposition", + "mvUsdOverride", + "simpleUnrealTexture" + ], + "template_name_profiles": [ + { + "families": [], + "hosts": [], + "task_types": [], + "tasks": [], + "template_name": "publish" + }, + { + "families": [ + "review", + "render", + "prerender" + ], + "hosts": [], + "task_types": [], + "tasks": [], + "template_name": "render" + }, + { + "families": [ + "simpleUnrealTexture" + ], + "hosts": [ + "standalonepublisher" + ], + "task_types": [], + "tasks": [], + "template_name": "simpleUnrealTexture" + }, + { + "families": [ + "staticMesh", + "skeletalMesh" + ], + "hosts": [ + "maya" + ], + "task_types": [], + "tasks": [], + "template_name": "maya2unreal" + } + ] + }, + "IntegrateAsset": { + "hosts": [ + "maya" + ], + "families": [ + "workfile", + "pointcache", + "camera", + "animation", + "model", + "mayaAscii", + "mayaScene", + "setdress", + "layout", + "ass", + "vdbcache", + "scene", + "vrayproxy", + "vrayscene_layer", + "render", + "prerender", + "imagesequence", + "review", + "rendersetup", + "rig", + "plate", + "look", + "audio", + "yetiRig", + "yeticache", + "nukenodes", + "gizmo", + "source", + "matchmove", + "image", + "assembly", + "fbx", + "textures", + "action", + "harmony.template", + "harmony.palette", + "editorial", + "background", + "camerarig", + "redshiftproxy", + "effect", + "xgen", + "hda", + "usd", + "staticMesh", + "skeletalMesh", + "mvLook", + "mvUsd", + "mvUsdComposition", + "mvUsdOverride", + "simpleUnrealTexture" + ], "template_name_profiles": [ { "families": [], diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index e368916cc9..71eed2e2de 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -587,6 +587,85 @@ "label": "IntegrateAssetNew", "is_group": true, "children": [ + { + "type": "list", + "key": "hosts", + "label": "Hosts", + "object_type": "text" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "type": "list", + "key": "template_name_profiles", + "label": "Template name profiles", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "label", + "label": "" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "type": "hosts-enum", + "key": "hosts", + "label": "Hosts", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "tasks", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "type": "separator" + }, + { + "type": "text", + "key": "template_name", + "label": "Template name" + } + ] + } + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "IntegrateAsset", + "label": "IntegrateAsset", + "is_group": true, + "children": [ + { + "type": "list", + "key": "hosts", + "label": "Hosts", + "object_type": "text" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, { "type": "list", "key": "template_name_profiles", From be4ac5b56b9c815a268696ff23f072d14b029192 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 13 Jul 2022 17:25:51 +0200 Subject: [PATCH 1093/1227] 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 1094/1227] 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 1095/1227] :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 1096/1227] :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 1097/1227] 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 1098/1227] 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 1099/1227] 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 1100/1227] 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 1101/1227] 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 1102/1227] 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 1103/1227] 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 1104/1227] 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 1105/1227] 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 1106/1227] 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 1107/1227] 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 1108/1227] 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 1109/1227] 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 1110/1227] 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 1111/1227] 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 1112/1227] 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 1113/1227] 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 1114/1227] 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 1115/1227] 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 1116/1227] 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 1117/1227] 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 1118/1227] 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 1119/1227] 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 1120/1227] 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 1121/1227] 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 c3649e0a571048cb0660656ce9dcf10176399b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 14 Jul 2022 16:31:32 +0200 Subject: [PATCH 1122/1227] :bug: fix rfm api context for getting displays and multipart flag --- openpype/hosts/maya/api/lib_renderproducts.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 2d3bda5245..a8337ccf4d 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -1087,7 +1087,7 @@ class RenderProductsRenderman(ARenderProducts): "d_tiff": "tif" } - displays = get_displays()["displays"] + displays = get_displays(override_dst="render")["displays"] for name, display in displays.items(): enabled = display["params"]["enable"]["value"] if not enabled: @@ -1106,9 +1106,16 @@ class RenderProductsRenderman(ARenderProducts): display["driverNode"]["type"], "exr") for camera in cameras: - product = RenderProduct(productName=aov_name, - ext=extensions, - camera=camera) + # Create render product and set it as multipart only on + # display types supporting it. In all other cases, Renderman + # will create separate output per channel. + product = RenderProduct( + productName=aov_name, + ext=extensions, + camera=camera, + multipart=display["driverNode"]["type"] in ["d_openexr", "d_deepexr", "d_tiff"] # noqa + ) + products.append(product) return products From fbd239299f70105486db3276c218adfcccdf1e82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 14 Jul 2022 17:00:26 +0200 Subject: [PATCH 1123/1227] :recycle: comment on non-multipart code and raise exception --- openpype/hosts/maya/api/lib_renderproducts.py | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index a8337ccf4d..0bc8682290 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -1109,12 +1109,28 @@ class RenderProductsRenderman(ARenderProducts): # Create render product and set it as multipart only on # display types supporting it. In all other cases, Renderman # will create separate output per channel. - product = RenderProduct( - productName=aov_name, - ext=extensions, - camera=camera, - multipart=display["driverNode"]["type"] in ["d_openexr", "d_deepexr", "d_tiff"] # noqa - ) + if display["driverNode"]["type"] in ["d_openexr", "d_deepexr", "d_tiff"]: # noqa + product = RenderProduct( + productName=aov_name, + ext=extensions, + camera=camera, + multipart=True + ) + else: + # this code should handle the case where no multipart + # capable format is selected. But since it involves + # shady logic to determine what channel become what + # lets not do that as all productions will use exr anyway. + """ + for channel in display['params']['displayChannels']['value']: # noqa + product = RenderProduct( + productName="{}_{}".format(aov_name, channel), + ext=extensions, + camera=camera, + multipart=False + ) + """ + raise UnsupportedImageFormatException("Only exr, deep exr and tiff formats are supported.") products.append(product) @@ -1208,3 +1224,7 @@ class UnsupportedRendererException(Exception): Raised when requesting data from unsupported renderer. """ + + +class UnsupportedImageFormatException(Exception): + """Custom exception to report unsupported output image format.""" From ef5b571ea6dbfae4df12a18f8412a4e90a88cf1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 14 Jul 2022 17:17:25 +0200 Subject: [PATCH 1124/1227] :dog: hound fix --- openpype/hosts/maya/api/lib_renderproducts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 0bc8682290..123b934428 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -1130,7 +1130,8 @@ class RenderProductsRenderman(ARenderProducts): multipart=False ) """ - raise UnsupportedImageFormatException("Only exr, deep exr and tiff formats are supported.") + raise UnsupportedImageFormatException( + "Only exr, deep exr and tiff formats are supported.") products.append(product) From f346fb8cfd3b59d85b961cba5026cd8e0f66e21a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Jul 2022 17:38:01 +0200 Subject: [PATCH 1125/1227] 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 1126/1227] 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 1127/1227] 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 1128/1227] 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 1129/1227] 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 a6c029d9fb1462054ca74817393beaebf32c7346 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Jul 2022 10:13:32 +0200 Subject: [PATCH 1130/1227] skip validation of zou id --- openpype/modules/kitsu/utils/sync_service.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 3848eda7ae..441b95a7ec 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -4,7 +4,8 @@ import gazu from openpype.client import ( get_project, - get_assets + get_assets, + get_asset_by_name ) from openpype.pipeline import AvalonMongoDB from .credentials import validate_credentials @@ -350,18 +351,8 @@ class Listener: # Find asset doc parent_name = task["entity"]["name"] - parent_zou_id = task["entity"]["id"] - asset_docs = get_assets( - project_name, - asset_names=[parent_name], - fields=["_id", "data.zou.id", "data.tasks"] - ) - asset_doc = None - for _asset_doc in asset_docs: - doc_zou_id = _asset_doc.get("data", {}).get("zou", {}).get("id") - if doc_zou_id == parent_zou_id: - asset_doc = _asset_doc - break + + asset_doc = get_asset_by_name(project_name, parent_name) # Update asset tasks with new one asset_tasks = asset_doc["data"].get("tasks") From c210c93b325aaef0aac5f90fddbe5253a4702166 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Jul 2022 10:15:32 +0200 Subject: [PATCH 1131/1227] changes from comments --- openpype/modules/kitsu/utils/update_op_with_zou.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index a68d6d31c3..4695a49159 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -39,14 +39,14 @@ def create_op_asset(gazu_entity: dict) -> dict: } -def get_kitsu_project_name(project_id: str)->str: +def get_kitsu_project_name(project_id: str) -> str: """Get project name based on project id in kitsu. Args: - project_id (str): Id of project in Kitsu. + project_id (str): UUID of project in Kitsu. Returns: - str: Project name which has project in Kitsu. + str: Name of Kitsu project. """ project = gazu.project.get_project(project_id) @@ -178,7 +178,7 @@ def update_op_assets( asset_doc_ids[parent_zou_id]["_id"] if parent_zou_id else None ) if visual_parent_doc_id is None: - # Find root folder doc + # Find root folder docs root_folder_docs = get_assets( project_name, asset_name=[entity_parent_folders[-1]], From baa1256b380630b0d23f4a57ed34431e947d4c85 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Jul 2022 11:22:22 +0200 Subject: [PATCH 1132/1227] 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 1133/1227] 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 1134/1227] 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 1135/1227] 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 9f2bbbbe386d874cee06fb8b684d377cfb5cd339 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 15 Jul 2022 13:12:09 +0200 Subject: [PATCH 1136/1227] OP-3589 - added thumbnail extract to traypublisher Added check if thumbnail representation is not present, created by different plugin by any chance. ExtractThumbnailSP should be removed when SP is removed, no need to copy and have 2 plugins. --- openpype/plugins/publish/extract_thumbnail.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 7a438ca701..e6df5b3ee0 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -22,7 +22,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "imagesequence", "render", "render2d", "prerender", "source", "plate", "take" ] - hosts = ["shell", "fusion", "resolve"] + hosts = ["shell", "fusion", "resolve", "traypublisher"] enabled = False # presetable attribute @@ -46,6 +46,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): self.log.info("Skipping - no review set on instance.") return + if self._has_thumbnail_already(instance): + self.log.info("Thumbnail representation already present.") + return + filtered_repres = self._get_filtered_repres(instance) for repre in filtered_repres: repre_files = repre["files"] @@ -102,6 +106,14 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # There is no need to create more then one thumbnail break + def _has_thumbnail_already(self, instance): + for repre in instance.data.get("representations", []): + self.log.info("repre {}".format(repre)) + if repre["name"] == "thumbnail": + return True + + return False + def _get_filtered_repres(self, instance): filtered_repres = [] src_repres = instance.data.get("representations") or [] From a8c219211d5f2dddaf6b4dd31fd59d1137bbd0c1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 15 Jul 2022 13:25:42 +0200 Subject: [PATCH 1137/1227] OP-3589 - copied ValidateFrameRange from SP SP will be removed in the future, copied this to TrayPublisher to keep functionality. --- .../publish/help/validate_frame_ranges.xml | 15 ++++ .../plugins/publish/validate_frame_ranges.py | 72 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 openpype/hosts/traypublisher/plugins/publish/help/validate_frame_ranges.xml create mode 100644 openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py diff --git a/openpype/hosts/traypublisher/plugins/publish/help/validate_frame_ranges.xml b/openpype/hosts/traypublisher/plugins/publish/help/validate_frame_ranges.xml new file mode 100644 index 0000000000..933df1c7c5 --- /dev/null +++ b/openpype/hosts/traypublisher/plugins/publish/help/validate_frame_ranges.xml @@ -0,0 +1,15 @@ + + + +Invalid frame range + +## Invalid frame range + +Expected duration or '{duration}' frames set in database, workfile contains only '{found}' frames. + +### How to repair? + +Modify configuration in the database or tweak frame range in the workfile. + + + \ No newline at end of file diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py new file mode 100644 index 0000000000..89289fc6d4 --- /dev/null +++ b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py @@ -0,0 +1,72 @@ +import re + +import pyblish.api + +import openpype.api +from openpype import lib +from openpype.pipeline import ( + PublishXmlValidationError, + OptionalPyblishPluginMixin +) + + +class ValidateFrameRange(OptionalPyblishPluginMixin, + pyblish.api.InstancePlugin): + """Validating frame range of rendered files against state in DB.""" + + label = "Validate Frame Range" + hosts = ["traypublisher"] + families = ["render"] + order = openpype.api.ValidateContentsOrder + + optional = True + # published data might be sequence (.mov, .mp4) in that counting files + # doesnt make sense + check_extensions = ["exr", "dpx", "jpg", "jpeg", "png", "tiff", "tga", + "gif", "svg"] + skip_timelines_check = [] # skip for specific task names (regex) + + def process(self, instance): + # Skip the instance if is not active by data on the instance + if not self.is_active(instance.data): + return + + if any(re.search(pattern, instance.data["task"]) + for pattern in self.skip_timelines_check): + self.log.info("Skipping for {} task".format(instance.data["task"])) + + asset_data = lib.get_asset(instance.data["asset"])["data"] + frame_start = asset_data["frameStart"] + frame_end = asset_data["frameEnd"] + handle_start = asset_data["handleStart"] + handle_end = asset_data["handleEnd"] + duration = (frame_end - frame_start + 1) + handle_start + handle_end + + repre = instance.data.get("representations", [None]) + if not repre: + self.log.info("No representations, skipping.") + return + + ext = repre[0]['ext'].replace(".", '') + + if not ext or ext.lower() not in self.check_extensions: + self.log.warning("Cannot check for extension {}".format(ext)) + return + + files = instance.data.get("representations", [None])[0]["files"] + if isinstance(files, str): + files = [files] + frames = len(files) + + msg = "Frame duration from DB:'{}' ". format(int(duration)) +\ + " doesn't match number of files:'{}'".format(frames) +\ + " Please change frame range for Asset or limit no. of files" + + formatting_data = {"duration": duration, + "found": frames} + if frames != duration: + raise PublishXmlValidationError(self, msg, + formatting_data=formatting_data) + + self.log.debug("Valid ranges expected '{}' - found '{}'". + format(int(duration), frames)) From 870cc2bc0607b36328f03c2b75be4c5b12e39d6e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Jul 2022 13:34:26 +0200 Subject: [PATCH 1138/1227] 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" From 0111f4ae0f70b48e1098f8e28ed50ac969f1a8f3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Jul 2022 14:38:59 +0200 Subject: [PATCH 1139/1227] added option to launch openpype with interactive console --- openpype/cli.py | 16 +++++++++++++++- openpype/pype_commands.py | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/openpype/cli.py b/openpype/cli.py index 2aa4a46929..d6970f2509 100644 --- a/openpype/cli.py +++ b/openpype/cli.py @@ -2,7 +2,7 @@ """Package for handling pype command line arguments.""" import os import sys - +import code import click # import sys @@ -424,3 +424,17 @@ def pack_project(project, dirpath): def unpack_project(zipfile, root): """Create a package of project with all files and database dump.""" PypeCommands().unpack_project(zipfile, root) + + +@main.command() +def interactive(): + """Interative (Python like) console. + + Helpfull command not only for development to directly work with python + interpreter. + + Warning: + Executable 'openpype_gui' on windows won't work. + """ + + code.interact() diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index 90c582a319..124eacbe39 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -7,7 +7,7 @@ import time from openpype.lib import PypeLogger from openpype.api import get_app_environments_for_context -from openpype.lib.plugin_tools import parse_json, get_batch_asset_task_info +from openpype.lib.plugin_tools import get_batch_asset_task_info from openpype.lib.remote_publish import ( get_webpublish_conn, start_webpublish_log, From b60acdd6d77d488464ffdfad871b076926d94896 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Jul 2022 14:45:32 +0200 Subject: [PATCH 1140/1227] added banner to interpreter --- openpype/cli.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/cli.py b/openpype/cli.py index d6970f2509..9a2dfaa141 100644 --- a/openpype/cli.py +++ b/openpype/cli.py @@ -437,4 +437,9 @@ def interactive(): Executable 'openpype_gui' on windows won't work. """ - code.interact() + from openpype.version import __version__ + + banner = "OpenPype {}\nPython {} on {}".format( + __version__, sys.version, sys.platform + ) + code.interact(banner) From a395a85b67065e34c445e2b522afedb8d0f6cf42 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 15 Jul 2022 15:07:11 +0200 Subject: [PATCH 1141/1227] OP-3446 - explicitly adding review Creator now should handle adding review on instance and on applicable representation tags. --- .../plugins/create/create_mov_batch.py | 15 +++++++++++++-- .../plugins/publish/collect_mov_batch.py | 17 +++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py b/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py index 20d3ecbd7c..67f8848e05 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py +++ b/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py @@ -4,7 +4,7 @@ import re from openpype.client import get_assets, get_asset_by_name from openpype.hosts.traypublisher.api import pipeline -from openpype.lib import FileDef, get_subset_name_with_asset_doc +from openpype.lib import FileDef, BoolDef, get_subset_name_with_asset_doc from openpype.pipeline import ( CreatedInstance, CreatorError @@ -151,7 +151,13 @@ class BatchMovCreator(TrayPublishCreator): return task_name def get_instance_attr_defs(self): - return [] + return [ + BoolDef( + "add_review_family", + default=True, + label="Review" + ) + ] def get_pre_create_attr_defs(self): # Use same attributes as for instance attributes @@ -162,6 +168,11 @@ class BatchMovCreator(TrayPublishCreator): single_item=False, extensions=self.extensions, label="Filepath" + ), + BoolDef( + "add_review_family", + default=True, + label="Review" ) ] diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py b/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py index c81d1f77a5..e4011d0003 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py @@ -2,12 +2,17 @@ import os import pyblish.api from openpype.pipeline import OpenPypePyblishPluginMixin +from openpype.lib import BoolDef class CollectMovBatch( pyblish.api.InstancePlugin, OpenPypePyblishPluginMixin ): - """Collect file url for batch mov and create representation.""" + """Collect file url for batch mov and create representation. + + Adds review on instance and to repre.tags based on value of toggle button + on creator. + """ label = "Collect Mov Batch Files" order = pyblish.api.CollectorOrder @@ -18,7 +23,9 @@ class CollectMovBatch( if not instance.data.get("creator_identifier") == "render_mov_batch": return - file_url = instance.data["creator_attributes"]["filepath"] + creator_attributes = instance.data["creator_attributes"] + + file_url = creator_attributes["filepath"] file_name = os.path.basename(file_url) _, ext = os.path.splitext(file_name) @@ -29,6 +36,12 @@ class CollectMovBatch( "stagingDir": os.path.dirname(file_url) } + if creator_attributes["add_review_family"]: + if not repre.get("tags"): + repre["tags"] = [] + repre["tags"].append("review") + instance.data["families"].append("review") + instance.data["representations"].append(repre) instance.data["source"] = file_url From a1b93b1d4812cd44dc20f8f69e5f6953d91e7d52 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 15 Jul 2022 15:08:16 +0200 Subject: [PATCH 1142/1227] OP-3446 - Hound --- .../hosts/traypublisher/plugins/publish/collect_mov_batch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py b/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py index e4011d0003..99065d2408 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py @@ -2,7 +2,6 @@ import os import pyblish.api from openpype.pipeline import OpenPypePyblishPluginMixin -from openpype.lib import BoolDef class CollectMovBatch( From b9be23496924fe4ac99e764b11a82db348ef0b3e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Jul 2022 15:19:07 +0200 Subject: [PATCH 1143/1227] removed default host used on deregister of host --- openpype/pipeline/context_tools.py | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index e719e46514..fd4dc6e3fd 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -240,29 +240,7 @@ def registered_host(): def deregister_host(): - _registered_host["_"] = default_host() - - -def default_host(): - """A default host, in place of anything better - - This may be considered as reference for the - interface a host must implement. It also ensures - that the system runs, even when nothing is there - to support it. - - """ - - host = types.ModuleType("defaultHost") - - def ls(): - return list() - - host.__dict__.update({ - "ls": ls - }) - - return host + _registered_host["_"] = None def debug_host(): From 636e46cfd673f13bba211024bf1b56180f17abad Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Jul 2022 15:29:18 +0200 Subject: [PATCH 1144/1227] implemented functions to query project and asset documents based on current context --- openpype/pipeline/context_tools.py | 52 ++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index fd4dc6e3fd..80ad939ccd 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -10,6 +10,11 @@ import pyblish.api from pyblish.lib import MessageHandler import openpype +from openpype.client import ( + get_project, + get_asset_by_id, + get_asset_by_name, +) from openpype.modules import load_modules, ModulesManager from openpype.settings import get_project_settings from openpype.lib import filter_pyblish_plugins @@ -282,3 +287,50 @@ def debug_host(): }) return host + + +def get_current_project(fields=None): + """Helper function to get project document based on global Session. + + This function should be called only in process where host is installed. + + Returns: + dict: Project document. + None: Project is not set. + """ + + project_name = legacy_io.active_project() + return get_project(project_name, fields=fields) + + +def get_current_project_asset(asset_name=None, asset_id=None, fields=None): + """Helper function to get asset document based on global Session. + + This function should be called only in process where host is installed. + + Asset is found out based on passed asset name or id (not both). Asset name + is not used for filtering if asset id is passed. When both asset name and + id are missing then asset name from current process is used. + + Args: + asset_name (str): Name of asset used for filter. + asset_id (Union[str, ObjectId]): Asset document id. If entered then + is used as only filter. + fields (Union[List[str], None]): Limit returned data of asset documents + to specific keys. + + Returns: + dict: Asset document. + None: Asset is not set or not exist. + """ + + project_name = legacy_io.active_project() + if asset_id: + return get_asset_by_id(project_name, asset_id, fields=fields) + + if not asset_name: + asset_name = legacy_io.Session.get("AVALON_ASSET") + # Skip if is not set even on context + if not asset_name: + return None + return get_asset_by_name(project_name, asset_name, fields=fields) From 35bd841939a78a5d963bb2972c4b14b0bace13b4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Jul 2022 15:31:51 +0200 Subject: [PATCH 1145/1227] marked 'get_asset' as deprecated --- openpype/lib/avalon_context.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 76ed6cbbd3..7ed22d6de6 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -236,7 +236,7 @@ def any_outdated(): return False -@with_pipeline_io +@deprecated("openpype.pipeline.context_tools.get_current_project_asset") def get_asset(asset_name=None): """ Returning asset document from database by its name. @@ -249,15 +249,9 @@ def get_asset(asset_name=None): (MongoDB document) """ - project_name = legacy_io.active_project() - if not asset_name: - asset_name = legacy_io.Session["AVALON_ASSET"] + from openpype.pipeline.context_tools import get_current_project_asset - asset_document = get_asset_by_name(project_name, asset_name) - if not asset_document: - raise TypeError("Entity \"{}\" was not found in DB".format(asset_name)) - - return asset_document + return get_current_project_asset(asset_name=asset_name) def get_system_general_anatomy_data(system_settings=None): From de0c0effe60e38f07bc47f577f8e5fb67f61814c Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Fri, 15 Jul 2022 15:39:54 +0200 Subject: [PATCH 1146/1227] reencode with concat, fix audio --- .../plugins/publish/extract_review_slate.py | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 28685c2e90..737b7db295 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -285,36 +285,32 @@ class ExtractReviewSlate(openpype.api.Extractor): audio_channels, audio_sample_rate, audio_channel_layout, + input_frame_rate ) # replace slate with silent slate for concat slate_v_path = slate_silent_path - # create ffmpeg concat text file path - conc_text_file = input_file.replace(ext, "") + "_concat" + ".txt" - conc_text_path = os.path.join( - os.path.normpath(stagingdir), conc_text_file) - _remove_at_end.append(conc_text_path) - self.log.debug("__ conc_text_path: {}".format(conc_text_path)) - - new_line = "\n" - with open(conc_text_path, "w") as conc_text_f: - conc_text_f.writelines([ - "file {}".format( - slate_v_path.replace("\\", "/")), - new_line, - "file {}".format(input_path.replace("\\", "/")) - ]) - - # concat slate and videos together + # concat slate and videos together with concat filter + # this will reencode the output + if input_audio: + fmap = [ + "[0:v] [0:a] [1:v] [1:a] concat=n=2:v=1:a=1 [v] [a]", + "-map", '[v]', + "-map", '[a]' + ] + else: + fmap = [ + "[0:v] [1:v] concat=n=2:v=1:a=0 [v]", + "-map", '[v]' + ] concat_args = [ ffmpeg_path, - "-y", - "-f", "concat", - "-safe", "0", - "-i", conc_text_path, - "-c", "copy", + "-i", slate_v_path, + "-i", input_path, + "-filter_complex", ] + concat_args.extend(fmap) if offset_timecode: concat_args.extend(["-timecode", offset_timecode]) # NOTE: Added because of OP Atom demuxers @@ -328,6 +324,10 @@ class ExtractReviewSlate(openpype.api.Extractor): copy_args = ( "-metadata", "-metadata:s:v:0", + "-codec:v", + "-pixfmt", + "-b:v", + "-b:a", ) args = source_ffmpeg_cmd.split(" ") for indx, arg in enumerate(args): @@ -335,12 +335,14 @@ class ExtractReviewSlate(openpype.api.Extractor): concat_args.append(arg) # assumes arg has one parameter concat_args.append(args[indx + 1]) + concat_args.append("-y") # add final output path concat_args.append(output_path) # ffmpeg concat subprocess self.log.debug( - "Executing concat: {}".format(" ".join(concat_args)) + "Executing concat filter: {}".format + (" ".join(concat_args)) ) openpype.api.run_subprocess( concat_args, logger=self.log @@ -488,9 +490,10 @@ class ExtractReviewSlate(openpype.api.Extractor): audio_channels, audio_sample_rate, audio_channel_layout, + input_frame_rate ): # Get duration of one frame in micro seconds - items = audio_sample_rate.split("/") + items = input_frame_rate.split("/") if len(items) == 1: one_frame_duration = 1.0 / float(items[0]) elif len(items) == 2: From ad8a7c86e4b655014e6dc776c813e9966cb9e1f3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Jul 2022 15:57:01 +0200 Subject: [PATCH 1147/1227] use 'get_current_project_asset' in hosts --- openpype/hosts/harmony/api/pipeline.py | 5 ++++- openpype/hosts/hiero/api/plugin.py | 3 ++- openpype/hosts/houdini/api/lib.py | 4 ++-- openpype/hosts/maya/api/lib.py | 14 ++++++++------ .../hosts/maya/plugins/create/create_render.py | 6 +++--- .../maya/plugins/publish/validate_maya_units.py | 10 +++++++--- openpype/hosts/nuke/api/lib.py | 4 ++-- .../hosts/nuke/plugins/publish/validate_script.py | 10 +++++----- openpype/hosts/resolve/api/plugin.py | 4 ++-- .../plugins/publish/collect_editorial.py | 3 ++- .../plugins/publish/collect_editorial_instances.py | 8 ++++++-- .../plugins/publish/validate_frame_ranges.py | 5 +++-- .../hosts/unreal/plugins/load/load_animation.py | 9 ++++++--- openpype/hosts/unreal/plugins/load/load_layout.py | 5 +++-- 14 files changed, 55 insertions(+), 35 deletions(-) diff --git a/openpype/hosts/harmony/api/pipeline.py b/openpype/hosts/harmony/api/pipeline.py index 86b5753f7e..94ca134205 100644 --- a/openpype/hosts/harmony/api/pipeline.py +++ b/openpype/hosts/harmony/api/pipeline.py @@ -15,6 +15,7 @@ from openpype.pipeline import ( deregister_creator_plugin_path, AVALON_CONTAINER_ID, ) +from openpype.pipeline.context_tools import get_current_project_asset import openpype.hosts.harmony import openpype.hosts.harmony.api as harmony @@ -50,7 +51,9 @@ def get_asset_settings(): dict: Scene data. """ - asset_data = lib.get_asset()["data"] + + asset_doc = get_current_project_asset() + asset_data = asset_doc["data"] fps = asset_data.get("fps") frame_start = asset_data.get("frameStart") frame_end = asset_data.get("frameEnd") diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index add416d04e..28a9dfb492 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -10,6 +10,7 @@ import qargparse import openpype.api as openpype from openpype.pipeline import LoaderPlugin, LegacyCreator +from openpype.pipeline.context_tools import get_current_project_asset from . import lib log = openpype.Logger().get_logger(__name__) @@ -484,7 +485,7 @@ class ClipLoader: """ asset_name = self.context["representation"]["context"]["asset"] - asset_doc = openpype.get_asset(asset_name) + asset_doc = get_current_project_asset(asset_name) log.debug("__ asset_doc: {}".format(pformat(asset_doc))) self.data["assetData"] = asset_doc["data"] diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index dd8a5ba473..c8a7f92bb9 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -5,8 +5,8 @@ from contextlib import contextmanager import six from openpype.client import get_asset_by_name -from openpype.api import get_asset from openpype.pipeline import legacy_io +from openpype.pipeline.context_tools import get_current_project_asset import hou @@ -16,7 +16,7 @@ log = logging.getLogger(__name__) def get_asset_fps(): """Return current asset fps.""" - return get_asset()["data"].get("fps") + return get_current_project_asset()["data"].get("fps") def set_id(node, unique_id, overwrite=False): diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index e4221978c0..58e160cb2f 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -23,7 +23,6 @@ from openpype.client import ( get_last_versions, get_representation_by_name ) -from openpype import lib from openpype.api import get_anatomy_settings from openpype.pipeline import ( legacy_io, @@ -33,6 +32,7 @@ from openpype.pipeline import ( load_container, registered_host, ) +from openpype.pipeline.context_tools import get_current_project_asset from .commands import reset_frame_range @@ -2174,7 +2174,7 @@ def reset_scene_resolution(): project_name = legacy_io.active_project() project_doc = get_project(project_name) project_data = project_doc["data"] - asset_data = lib.get_asset()["data"] + asset_data = get_current_project_asset()["data"] # Set project resolution width_key = "resolutionWidth" @@ -2208,7 +2208,8 @@ def set_context_settings(): project_name = legacy_io.active_project() project_doc = get_project(project_name) project_data = project_doc["data"] - asset_data = lib.get_asset()["data"] + asset_doc = get_current_project_asset(fields=["data.fps"]) + asset_data = asset_doc.get("data", {}) # Set project fps fps = asset_data.get("fps", project_data.get("fps", 25)) @@ -2233,7 +2234,7 @@ def validate_fps(): """ - fps = lib.get_asset()["data"]["fps"] + fps = get_current_project_asset(fields=["data.fps"])["data"]["fps"] # TODO(antirotor): This is hack as for framerates having multiple # decimal places. FTrack is ceiling decimal values on # fps to two decimal places but Maya 2019+ is reporting those fps @@ -3051,8 +3052,9 @@ def update_content_on_context_change(): This will update scene content to match new asset on context change """ scene_sets = cmds.listSets(allSets=True) - new_asset = legacy_io.Session["AVALON_ASSET"] - new_data = lib.get_asset()["data"] + asset_doc = get_current_project_asset() + new_asset = asset_doc["name"] + new_data = asset_doc["data"] for s in scene_sets: try: if cmds.getAttr("{}.id".format(s)) == "pyblish.avalon.instance": diff --git a/openpype/hosts/maya/plugins/create/create_render.py b/openpype/hosts/maya/plugins/create/create_render.py index 93ee6679e5..de07a0b23d 100644 --- a/openpype/hosts/maya/plugins/create/create_render.py +++ b/openpype/hosts/maya/plugins/create/create_render.py @@ -15,13 +15,13 @@ from openpype.hosts.maya.api import ( from openpype.lib import requests_get from openpype.api import ( get_system_settings, - get_project_settings, - get_asset) + get_project_settings) from openpype.modules import ModulesManager from openpype.pipeline import ( CreatorError, legacy_io, ) +from openpype.pipeline.context_tools import get_current_project_asset class CreateRender(plugin.Creator): @@ -413,7 +413,7 @@ class CreateRender(plugin.Creator): prefix, type="string") - asset = get_asset() + asset = get_current_project_asset() if renderer == "arnold": # set format to exr diff --git a/openpype/hosts/maya/plugins/publish/validate_maya_units.py b/openpype/hosts/maya/plugins/publish/validate_maya_units.py index d5a8c350d5..5f67adec76 100644 --- a/openpype/hosts/maya/plugins/publish/validate_maya_units.py +++ b/openpype/hosts/maya/plugins/publish/validate_maya_units.py @@ -2,8 +2,8 @@ import maya.cmds as cmds import pyblish.api import openpype.api -from openpype import lib import openpype.hosts.maya.api.lib as mayalib +from openpype.pipeline.context_tools import get_current_project_asset from math import ceil @@ -41,7 +41,9 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): # now flooring the value? fps = float_round(context.data.get('fps'), 2, ceil) - asset_fps = lib.get_asset()["data"]["fps"] + # TODO repace query with using 'context.data["assetEntity"]' + asset_doc = get_current_project_asset() + asset_fps = asset_doc["data"]["fps"] self.log.info('Units (linear): {0}'.format(linearunits)) self.log.info('Units (angular): {0}'.format(angularunits)) @@ -91,5 +93,7 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): cls.log.debug(current_linear) cls.log.info("Setting time unit to match project") - asset_fps = lib.get_asset()["data"]["fps"] + # TODO repace query with using 'context.data["assetEntity"]' + asset_doc = get_current_project_asset() + asset_fps = asset_doc["data"]["fps"] mayalib.set_scene_fps(asset_fps) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 0929415c00..7be7c1169c 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -24,7 +24,6 @@ from openpype.api import ( BuildWorkfile, get_version_from_path, get_workdir_data, - get_asset, get_current_project_settings, ) from openpype.tools.utils import host_tools @@ -40,6 +39,7 @@ from openpype.pipeline import ( legacy_io, Anatomy, ) +from openpype.pipeline.context_tools import get_current_project_asset from . import gizmo_menu @@ -1766,7 +1766,7 @@ class WorkfileSettings(object): kwargs.get("asset_name") or legacy_io.Session["AVALON_ASSET"] ) - self._asset_entity = get_asset(self._asset) + self._asset_entity = get_current_project_asset(self._asset) self._root_node = root_node or nuke.root() self._nodes = self.get_nodes(nodes=nodes) diff --git a/openpype/hosts/nuke/plugins/publish/validate_script.py b/openpype/hosts/nuke/plugins/publish/validate_script.py index 9bda0da85e..b8d7494b9d 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_script.py +++ b/openpype/hosts/nuke/plugins/publish/validate_script.py @@ -1,7 +1,6 @@ import pyblish.api -from openpype.client import get_project, get_asset_by_id -from openpype import lib +from openpype.client import get_project, get_asset_by_id, get_asset_by_name from openpype.pipeline import legacy_io @@ -17,10 +16,11 @@ class ValidateScript(pyblish.api.InstancePlugin): def process(self, instance): ctx_data = instance.context.data - asset_name = ctx_data["asset"] - asset = lib.get_asset(asset_name) - asset_data = asset["data"] project_name = legacy_io.active_project() + asset_name = ctx_data["asset"] + # TODO repace query with using 'instance.data["assetEntity"]' + asset = get_asset_by_name(project_name, asset_name) + asset_data = asset["data"] # These attributes will be checked attributes = [ diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 49b478fb3b..b03125d502 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -4,11 +4,11 @@ import uuid import qargparse from Qt import QtWidgets, QtCore -import openpype.api as pype from openpype.pipeline import ( LegacyCreator, LoaderPlugin, ) +from openpype.pipeline.context_tools import get_current_project_asset from openpype.hosts import resolve from . import lib @@ -375,7 +375,7 @@ class ClipLoader: """ asset_name = self.context["representation"]["context"]["asset"] - self.data["assetData"] = pype.get_asset(asset_name)["data"] + self.data["assetData"] = get_current_project_asset(asset_name)["data"] def load(self): # create project bin for the media to be imported into diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial.py index 0a1d29ccdc..8633d4bf9d 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial.py @@ -19,6 +19,7 @@ import os import opentimelineio as otio import pyblish.api from openpype import lib as plib +from openpype.pipeline.context_tools import get_current_project_asset class OTIO_View(pyblish.api.Action): @@ -116,7 +117,7 @@ class CollectEditorial(pyblish.api.InstancePlugin): if extension == ".edl": # EDL has no frame rate embedded so needs explicit # frame rate else 24 is asssumed. - kwargs["rate"] = plib.get_asset()["data"]["fps"] + kwargs["rate"] = get_current_project_asset()["data"]["fps"] instance.data["otio_timeline"] = otio.adapters.read_from_file( file_path, **kwargs) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial_instances.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial_instances.py index d0d36bb717..3237fbbe12 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial_instances.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial_instances.py @@ -1,8 +1,12 @@ import os +from copy import deepcopy + import opentimelineio as otio import pyblish.api + from openpype import lib as plib -from copy import deepcopy +from openpype.pipeline.context_tools import get_current_project_asset + class CollectInstances(pyblish.api.InstancePlugin): """Collect instances from editorial's OTIO sequence""" @@ -48,7 +52,7 @@ class CollectInstances(pyblish.api.InstancePlugin): # get timeline otio data timeline = instance.data["otio_timeline"] - fps = plib.get_asset()["data"]["fps"] + fps = get_current_project_asset()["data"]["fps"] tracks = timeline.each_child( descended_from_type=otio.schema.Track diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_frame_ranges.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_frame_ranges.py index 005157af62..ff7f60354e 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_frame_ranges.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_frame_ranges.py @@ -3,8 +3,8 @@ import re import pyblish.api import openpype.api -from openpype import lib from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline.context_tools import get_current_project_asset class ValidateFrameRange(pyblish.api.InstancePlugin): @@ -27,7 +27,8 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): for pattern in self.skip_timelines_check): self.log.info("Skipping for {} task".format(instance.data["task"])) - asset_data = lib.get_asset(instance.data["asset"])["data"] + # TODO repace query with using 'instance.data["assetEntity"]' + asset_data = get_current_project_asset(instance.data["asset"])["data"] frame_start = asset_data["frameStart"] frame_end = asset_data["frameEnd"] handle_start = asset_data["handleStart"] diff --git a/openpype/hosts/unreal/plugins/load/load_animation.py b/openpype/hosts/unreal/plugins/load/load_animation.py index da2830bc52..1fe0bef462 100644 --- a/openpype/hosts/unreal/plugins/load/load_animation.py +++ b/openpype/hosts/unreal/plugins/load/load_animation.py @@ -8,13 +8,13 @@ from unreal import EditorAssetLibrary from unreal import MovieSceneSkeletalAnimationTrack from unreal import MovieSceneSkeletalAnimationSection +from openpype.pipeline.context_tools import get_current_project_asset from openpype.pipeline import ( get_representation_path, AVALON_CONTAINER_ID ) from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline -from openpype.api import get_asset class AnimationFBXLoader(plugin.Loader): @@ -53,6 +53,8 @@ class AnimationFBXLoader(plugin.Loader): if not actor: return None + asset_doc = get_current_project_asset(fields=["data.fps"]) + task.set_editor_property('filename', self.fname) task.set_editor_property('destination_path', asset_dir) task.set_editor_property('destination_name', asset_name) @@ -80,7 +82,7 @@ class AnimationFBXLoader(plugin.Loader): task.options.anim_sequence_import_data.set_editor_property( 'use_default_sample_rate', False) task.options.anim_sequence_import_data.set_editor_property( - 'custom_sample_rate', get_asset()["data"].get("fps")) + 'custom_sample_rate', asset_doc.get("data", {}).get("fps")) task.options.anim_sequence_import_data.set_editor_property( 'import_custom_attribute', True) task.options.anim_sequence_import_data.set_editor_property( @@ -246,6 +248,7 @@ class AnimationFBXLoader(plugin.Loader): def update(self, container, representation): name = container["asset_name"] source_path = get_representation_path(representation) + asset_doc = get_current_project_asset(fields=["data.fps"]) destination_path = container["namespace"] task = unreal.AssetImportTask() @@ -279,7 +282,7 @@ class AnimationFBXLoader(plugin.Loader): task.options.anim_sequence_import_data.set_editor_property( 'use_default_sample_rate', False) task.options.anim_sequence_import_data.set_editor_property( - 'custom_sample_rate', get_asset()["data"].get("fps")) + 'custom_sample_rate', asset_doc.get("data", {}).get("fps")) task.options.anim_sequence_import_data.set_editor_property( 'import_custom_attribute', True) task.options.anim_sequence_import_data.set_editor_property( diff --git a/openpype/hosts/unreal/plugins/load/load_layout.py b/openpype/hosts/unreal/plugins/load/load_layout.py index 3f16a68ead..01d589c69b 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout.py +++ b/openpype/hosts/unreal/plugins/load/load_layout.py @@ -20,7 +20,7 @@ from openpype.pipeline import ( AVALON_CONTAINER_ID, legacy_io, ) -from openpype.api import get_asset +from openpype.pipeline.context_tools import get_current_project_asset from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline @@ -225,6 +225,7 @@ class LayoutLoader(plugin.Loader): anim_path = f"{asset_dir}/animations/{anim_file_name}" + asset_doc = get_current_project_asset() # Import animation task = unreal.AssetImportTask() task.options = unreal.FbxImportUI() @@ -259,7 +260,7 @@ class LayoutLoader(plugin.Loader): task.options.anim_sequence_import_data.set_editor_property( 'use_default_sample_rate', False) task.options.anim_sequence_import_data.set_editor_property( - 'custom_sample_rate', get_asset()["data"].get("fps")) + 'custom_sample_rate', asset_doc.get("data", {}).get("fps")) task.options.anim_sequence_import_data.set_editor_property( 'import_custom_attribute', True) task.options.anim_sequence_import_data.set_editor_property( From b5d7ae0d2a38d93ba5014c9a1aec455b9ca982ce Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Fri, 15 Jul 2022 16:29:05 +0200 Subject: [PATCH 1148/1227] no need to copy codec and pixel format --- openpype/plugins/publish/extract_review_slate.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 737b7db295..2edaf10e6b 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -324,8 +324,6 @@ class ExtractReviewSlate(openpype.api.Extractor): copy_args = ( "-metadata", "-metadata:s:v:0", - "-codec:v", - "-pixfmt", "-b:v", "-b:a", ) From e8b4a3389e9ac0095bdafcdd008398dc69aac38c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Jul 2022 16:33:47 +0200 Subject: [PATCH 1149/1227] added comment do harmony plugin --- .../hosts/harmony/plugins/publish/validate_scene_settings.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py b/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py index 4c3a6c4465..936533abd6 100644 --- a/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py +++ b/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py @@ -55,6 +55,10 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): def process(self, instance): """Plugin entry point.""" + + # TODO 'get_asset_settings' could expect asset document as argument + # which is available on 'context.data["assetEntity"]' + # - the same approach can be used in 'ValidateSceneSettingsRepair' expected_settings = harmony.get_asset_settings() self.log.info("scene settings from DB:".format(expected_settings)) From a081a0f3e1e21f6ced4ff4ee3b74d40cd97e788e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 15 Jul 2022 17:43:05 +0200 Subject: [PATCH 1150/1227] Added wrapper around cmds.setAttr Logs and captures exception when attribute is not possible to set (when locked) --- openpype/vendor/python/common/capture.py | 32 ++++++++++++++++-------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/openpype/vendor/python/common/capture.py b/openpype/vendor/python/common/capture.py index 4d9e1da3e4..71b86a5f1a 100644 --- a/openpype/vendor/python/common/capture.py +++ b/openpype/vendor/python/common/capture.py @@ -403,7 +403,7 @@ def apply_view(panel, **options): camera_options = options.get("camera_options", {}) _iteritems = getattr(camera_options, "iteritems", camera_options.items) for key, value in _iteritems: - cmds.setAttr("{0}.{1}".format(camera, key), value) + _safe_setAttr("{0}.{1}".format(camera, key), value) # Viewport options viewport_options = options.get("viewport_options", {}) @@ -417,7 +417,7 @@ def apply_view(panel, **options): ) for key, value in _iteritems(): attr = "hardwareRenderingGlobals.{0}".format(key) - cmds.setAttr(attr, value) + _safe_setAttr(attr, value) def parse_active_panel(): @@ -551,10 +551,10 @@ def apply_scene(**options): cmds.playbackOptions(maxTime=options["end_frame"]) if "width" in options: - cmds.setAttr("defaultResolution.width", options["width"]) + _safe_setAttr("defaultResolution.width", options["width"]) if "height" in options: - cmds.setAttr("defaultResolution.height", options["height"]) + _safe_setAttr("defaultResolution.height", options["height"]) if "compression" in options: cmds.optionVar( @@ -665,7 +665,7 @@ def _applied_camera_options(options, panel): _iteritems = getattr(options, "iteritems", options.items) for opt, value in _iteritems(): - cmds.setAttr(camera + "." + opt, value) + _safe_setAttr(camera + "." + opt, value) try: yield @@ -673,7 +673,7 @@ def _applied_camera_options(options, panel): if old_options: _iteritems = getattr(old_options, "iteritems", old_options.items) for opt, value in _iteritems(): - cmds.setAttr(camera + "." + opt, value) + _safe_setAttr(camera + "." + opt, value) @contextlib.contextmanager @@ -760,7 +760,7 @@ def _applied_viewport2_options(options): # Apply settings _iteritems = getattr(options, "iteritems", options.items) for opt, value in _iteritems(): - cmds.setAttr("hardwareRenderingGlobals." + opt, value) + _safe_setAttr("hardwareRenderingGlobals." + opt, value) try: yield @@ -768,7 +768,7 @@ def _applied_viewport2_options(options): # Restore previous settings _iteritems = getattr(original, "iteritems", original.items) for opt, value in _iteritems(): - cmds.setAttr("hardwareRenderingGlobals." + opt, value) + _safe_setAttr("hardwareRenderingGlobals." + opt, value) @contextlib.contextmanager @@ -802,14 +802,14 @@ def _maintain_camera(panel, camera): else: state = dict((camera, cmds.getAttr(camera + ".rnd")) for camera in cmds.ls(type="camera")) - cmds.setAttr(camera + ".rnd", True) + _safe_setAttr(camera + ".rnd", True) try: yield finally: _iteritems = getattr(state, "iteritems", state.items) for camera, renderable in _iteritems(): - cmds.setAttr(camera + ".rnd", renderable) + _safe_setAttr(camera + ".rnd", renderable) @contextlib.contextmanager @@ -846,6 +846,18 @@ def _in_standalone(): return not hasattr(cmds, "about") or cmds.about(batch=True) +def _safe_setAttr(*args, **kwargs): + """Wrapper to handle failures when attribute is locked. + + Temporary hotfix until better approach (store value, unlock, set new, + return old, lock again) is implemented. + """ + try: + cmds.setAttr(*args, **kwargs) + except RuntimeError: + print("Cannot setAttr {}!".format(args)) + + # -------------------------------- # # Apply version specific settings From 1f18e5c9d35606276b1325f741662cd49e131a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 15 Jul 2022 18:06:37 +0200 Subject: [PATCH 1151/1227] :recycle: move submodules --- .gitmodules | 8 ++++---- vendor/powershell/BurntToast | 1 - vendor/powershell/PSWriteColor | 1 - vendor/powershell/README.md | 0 4 files changed, 4 insertions(+), 6 deletions(-) delete mode 160000 vendor/powershell/BurntToast delete mode 160000 vendor/powershell/PSWriteColor delete mode 100644 vendor/powershell/README.md diff --git a/.gitmodules b/.gitmodules index b515851c81..6a5d29ec02 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,7 @@ -[submodule "vendor/powershell/BurntToast"] - path = vendor/powershell/BurntToast +[submodule "tools/modules/powershell/BurntToast"] + path = tools/modules/powershell/PSWriteColor url = https://github.com/Windos/BurntToast.git -[submodule "vendor/powershell/PSWriteColor"] - path = vendor/powershell/PSWriteColor +[submodule "tools/modules/powershell/PSWriteColor"] + path = tools/modules/powershell/PSWriteColor url = https://github.com/EvotecIT/PSWriteColor.git diff --git a/vendor/powershell/BurntToast b/vendor/powershell/BurntToast deleted file mode 160000 index ae0acdd870..0000000000 --- a/vendor/powershell/BurntToast +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ae0acdd870a2fd8d9f0d147de22dc36d6c5e399e diff --git a/vendor/powershell/PSWriteColor b/vendor/powershell/PSWriteColor deleted file mode 160000 index 12eda384eb..0000000000 --- a/vendor/powershell/PSWriteColor +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 12eda384ebd7a7954e15855e312215c009c97114 diff --git a/vendor/powershell/README.md b/vendor/powershell/README.md deleted file mode 100644 index e69de29bb2..0000000000 From 47079516f892a3bf2e550746c87a73e2ff389524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 15 Jul 2022 18:14:38 +0200 Subject: [PATCH 1152/1227] :truck: set scripts to new path --- .gitmodules | 2 +- tools/build.ps1 | 2 +- tools/build_win_installer.ps1 | 2 +- tools/create_env.ps1 | 2 +- tools/create_zip.ps1 | 2 +- tools/fetch_thirdparty_libs.ps1 | 2 +- tools/make_docs.ps1 | 2 +- tools/modules/powershell/BurntToast | 1 + tools/run_mongo.ps1 | 2 +- tools/run_project_manager.ps1 | 2 +- tools/run_settings.ps1 | 2 +- tools/run_tests.ps1 | 2 +- tools/run_tray.ps1 | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 160000 tools/modules/powershell/BurntToast diff --git a/.gitmodules b/.gitmodules index 6a5d29ec02..dfd89cdb3c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,5 +1,5 @@ [submodule "tools/modules/powershell/BurntToast"] - path = tools/modules/powershell/PSWriteColor + path = tools/modules/powershell/BurntToast url = https://github.com/Windos/BurntToast.git [submodule "tools/modules/powershell/PSWriteColor"] diff --git a/tools/build.ps1 b/tools/build.ps1 index efb41e6c1b..442328b8dc 100644 --- a/tools/build.ps1 +++ b/tools/build.ps1 @@ -33,7 +33,7 @@ $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:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell" function Start-Progress { param([ScriptBlock]$code) diff --git a/tools/build_win_installer.ps1 b/tools/build_win_installer.ps1 index 8024a5a3b2..d7325edfc4 100644 --- a/tools/build_win_installer.ps1 +++ b/tools/build_win_installer.ps1 @@ -16,7 +16,7 @@ $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:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell" function Start-Progress { param([ScriptBlock]$code) diff --git a/tools/create_env.ps1 b/tools/create_env.ps1 index c0cbe9775b..2b2f0c3904 100644 --- a/tools/create_env.ps1 +++ b/tools/create_env.ps1 @@ -30,7 +30,7 @@ $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" +$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell" function Exit-WithCode($exitcode) { diff --git a/tools/create_zip.ps1 b/tools/create_zip.ps1 index b4b66424ca..7b852b7c54 100644 --- a/tools/create_zip.ps1 +++ b/tools/create_zip.ps1 @@ -24,7 +24,7 @@ $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:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell" function Exit-WithCode($exitcode) { # Only exit this host process if it's a child of another PowerShell parent process... diff --git a/tools/fetch_thirdparty_libs.ps1 b/tools/fetch_thirdparty_libs.ps1 index 41a3585ff9..05eb073fdd 100644 --- a/tools/fetch_thirdparty_libs.ps1 +++ b/tools/fetch_thirdparty_libs.ps1 @@ -16,7 +16,7 @@ $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:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell" $env:_INSIDE_OPENPYPE_TOOL = "1" diff --git a/tools/make_docs.ps1 b/tools/make_docs.ps1 index d356f081de..43ecd0c09c 100644 --- a/tools/make_docs.ps1 +++ b/tools/make_docs.ps1 @@ -49,7 +49,7 @@ $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:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell" Write-Host $art -ForegroundColor DarkGreen diff --git a/tools/modules/powershell/BurntToast b/tools/modules/powershell/BurntToast new file mode 160000 index 0000000000..f58c9a26d6 --- /dev/null +++ b/tools/modules/powershell/BurntToast @@ -0,0 +1 @@ +Subproject commit f58c9a26d6ede30ecc7998e92b26974887e945fe diff --git a/tools/run_mongo.ps1 b/tools/run_mongo.ps1 index 934ce67181..b6b091a9d1 100644 --- a/tools/run_mongo.ps1 +++ b/tools/run_mongo.ps1 @@ -16,7 +16,7 @@ $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:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell" $art = @" diff --git a/tools/run_project_manager.ps1 b/tools/run_project_manager.ps1 index 2932358c2a..c1813e4ed9 100644 --- a/tools/run_project_manager.ps1 +++ b/tools/run_project_manager.ps1 @@ -36,7 +36,7 @@ $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:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell" $env:_INSIDE_OPENPYPE_TOOL = "1" diff --git a/tools/run_settings.ps1 b/tools/run_settings.ps1 index 918ea367ab..c74ae1ea3a 100644 --- a/tools/run_settings.ps1 +++ b/tools/run_settings.ps1 @@ -16,7 +16,7 @@ $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:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell" $env:_INSIDE_OPENPYPE_TOOL = "1" diff --git a/tools/run_tests.ps1 b/tools/run_tests.ps1 index 7995c6a8e9..4fa598c413 100644 --- a/tools/run_tests.ps1 +++ b/tools/run_tests.ps1 @@ -16,7 +16,7 @@ $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:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell" function Exit-WithCode($exitcode) { # Only exit this host process if it's a child of another PowerShell parent process... diff --git a/tools/run_tray.ps1 b/tools/run_tray.ps1 index 7dee3d0064..40157c4e81 100644 --- a/tools/run_tray.ps1 +++ b/tools/run_tray.ps1 @@ -15,7 +15,7 @@ $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:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell" $env:_INSIDE_OPENPYPE_TOOL = "1" From fce30b519f7c6df3a59a2c8328d314c2cf7b2aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 15 Jul 2022 18:15:31 +0200 Subject: [PATCH 1153/1227] :heavy_plus_sign: add PSWriteColor to right place --- tools/modules/powershell/PSWriteColor | 1 + 1 file changed, 1 insertion(+) create mode 160000 tools/modules/powershell/PSWriteColor diff --git a/tools/modules/powershell/PSWriteColor b/tools/modules/powershell/PSWriteColor new file mode 160000 index 0000000000..12eda384eb --- /dev/null +++ b/tools/modules/powershell/PSWriteColor @@ -0,0 +1 @@ +Subproject commit 12eda384ebd7a7954e15855e312215c009c97114 From 623e5d18c0d2290d68104e64656b9a6cd1d48602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 15 Jul 2022 18:19:21 +0200 Subject: [PATCH 1154/1227] :hammer: catch exception for toasts --- tools/build.ps1 | 6 ++++-- tools/build_win_installer.ps1 | 6 +++--- tools/create_env.ps1 | 7 ++++--- tools/fetch_thirdparty_libs.ps1 | 5 ++++- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/tools/build.ps1 b/tools/build.ps1 index 442328b8dc..195b2dc75e 100644 --- a/tools/build.ps1 +++ b/tools/build.ps1 @@ -189,6 +189,8 @@ 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." - +try +{ + 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." +} catch {} 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 d7325edfc4..b9d1ca2d3f 100644 --- a/tools/build_win_installer.ps1 +++ b/tools/build_win_installer.ps1 @@ -171,7 +171,7 @@ if ($LASTEXITCODE -ne 0) { Write-Color -Text ">>> ", "Restoring current directory" -Color Green, Gray Set-Location -Path $current_dir - -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." - +try { + 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." +} catch {} 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 2b2f0c3904..3f956e5c6a 100644 --- a/tools/create_env.ps1 +++ b/tools/create_env.ps1 @@ -180,7 +180,8 @@ if ($LASTEXITCODE -ne 0) { } $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.", "All done in $($endTime - $startTime) secs." - +try +{ + New-BurntToastNotification -AppLogo "$openpype_root/openpype/resources/icons/openpype_icon.png" -Text "OpenPype", "Virtual environment created.", "All done in $( $endTime - $startTime ) secs." +} catch {} Write-Color -Text ">>> ", "Virtual environment created." -Color Green, White diff --git a/tools/fetch_thirdparty_libs.ps1 b/tools/fetch_thirdparty_libs.ps1 index 05eb073fdd..4df007ad67 100644 --- a/tools/fetch_thirdparty_libs.ps1 +++ b/tools/fetch_thirdparty_libs.ps1 @@ -38,4 +38,7 @@ $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." +try +{ + New-BurntToastNotification -AppLogo "$openpype_root/openpype/resources/icons/openpype_icon.png" -Text "OpenPype", "Dependencies downloaded", "All done in $( $endTime - $startTime ) secs." +} catch {} \ No newline at end of file From 1c71fe206d2a531fbd921fd49368a74a966e3426 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Jul 2022 18:20:01 +0200 Subject: [PATCH 1155/1227] added interactive command to documentation --- website/docs/admin_openpype_commands.md | 1 + 1 file changed, 1 insertion(+) diff --git a/website/docs/admin_openpype_commands.md b/website/docs/admin_openpype_commands.md index 53b4799d6e..53fc12410f 100644 --- a/website/docs/admin_openpype_commands.md +++ b/website/docs/admin_openpype_commands.md @@ -45,6 +45,7 @@ For more information [see here](admin_use.md#run-openpype). | publish | Pype takes JSON from provided path and use it to publish data in it. | [📑](#publish-arguments) | | extractenvironments | Extract environment variables for entered context to a json file. | [📑](#extractenvironments-arguments) | | run | Execute given python script within OpenPype environment. | [📑](#run-arguments) | +| interactive | Start python like interactive console session. | | | projectmanager | Launch Project Manager UI | [📑](#projectmanager-arguments) | | settings | Open Settings UI | [📑](#settings-arguments) | | standalonepublisher | Open Standalone Publisher UI | [📑](#standalonepublisher-arguments) | From 188556a7253837838e6ae60c72cd2d73b5bd6da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Fri, 15 Jul 2022 18:34:43 +0200 Subject: [PATCH 1156/1227] :pencil2: fix typos in arguments --- tools/run_mongo.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/run_mongo.ps1 b/tools/run_mongo.ps1 index b6b091a9d1..c64ff75969 100644 --- a/tools/run_mongo.ps1 +++ b/tools/run_mongo.ps1 @@ -50,7 +50,7 @@ function Exit-WithCode($exitcode) { function Find-Mongo ($preferred_version) { $defaultPath = "C:\Program Files\MongoDB\Server" - Write-Color -Text ">>> ", "Detecting MongoDB ... " -Color Geen, Gray -NoNewline + Write-Color -Text ">>> ", "Detecting MongoDB ... " -Color Green, 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 @@ -61,7 +61,7 @@ function Find-Mongo ($preferred_version) { Write-Color -Text "OK" -Color Green $use_version = $mongoVersions[-1] foreach ($v in $mongoVersions) { - Write-Color -Text " - found [ ", $v, " ]" - Color Cyan, White, Cyan -NoNewLine + Write-Color -Text " - found [ ", $v, " ]" -Color Cyan, White, Cyan -NoNewLine $version = Split-Path $v -Leaf if ($preferred_version -eq $version) { @@ -110,6 +110,6 @@ $preferred_version = "5.0" $mongoPath = Find-Mongo $preferred_version Write-Color -Text ">>> ", "Using DB path: ", "[ ", "$($dbpath)", " ]" -Color Green, Gray, Cyan, White, Cyan -Write-Color -Text ">>> ", "Port: ", "[ ", "$($port)", " ]", -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 ace2bf4ecb1ac6eb43205090f9c670a9332d3927 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 16 Jul 2022 03:49:48 +0000 Subject: [PATCH 1157/1227] [Automated] Bump version --- CHANGELOG.md | 54 +++++++++++++++++++++++---------------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc5bf39a29..95427e9ea9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,35 @@ # Changelog +## [3.12.2-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.1...HEAD) + +**🚀 Enhancements** + +- Ftrack: Automatic daily review session creation can define trigger hour [\#3516](https://github.com/pypeclub/OpenPype/pull/3516) +- Ftrack: add source into Note [\#3509](https://github.com/pypeclub/OpenPype/pull/3509) +- Ftrack: Trigger custom ftrack topic of project structure creation [\#3506](https://github.com/pypeclub/OpenPype/pull/3506) +- Settings UI: Add extract to file action on project view [\#3505](https://github.com/pypeclub/OpenPype/pull/3505) +- General: Event system [\#3499](https://github.com/pypeclub/OpenPype/pull/3499) +- NewPublisher: Keep plugins with mismatch target in report [\#3498](https://github.com/pypeclub/OpenPype/pull/3498) +- Nuke: load clip with options from settings [\#3497](https://github.com/pypeclub/OpenPype/pull/3497) +- Migrate basic families to the new Tray Publisher [\#3469](https://github.com/pypeclub/OpenPype/pull/3469) + +**🐛 Bug fixes** + +- General: Fix hash of centos oiio archive [\#3519](https://github.com/pypeclub/OpenPype/pull/3519) +- TrayPublisher: Simple creation enhancements and fixes [\#3513](https://github.com/pypeclub/OpenPype/pull/3513) +- NewPublisher: Publish attributes are properly collected [\#3510](https://github.com/pypeclub/OpenPype/pull/3510) +- TrayPublisher: Make sure host name is filled [\#3504](https://github.com/pypeclub/OpenPype/pull/3504) +- NewPublisher: Groups work and enum multivalue [\#3501](https://github.com/pypeclub/OpenPype/pull/3501) + +**🔀 Refactored code** + +- TimersManager: Use query functions [\#3495](https://github.com/pypeclub/OpenPype/pull/3495) + ## [3.12.1](https://github.com/pypeclub/OpenPype/tree/3.12.1) (2022-07-13) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.0...3.12.1) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.12.1-nightly.6...3.12.1) ### 📖 Documentation @@ -45,8 +72,6 @@ - LogViewer: Escape html characters in log message [\#3443](https://github.com/pypeclub/OpenPype/pull/3443) - Nuke: Slate frame is integrated [\#3427](https://github.com/pypeclub/OpenPype/pull/3427) - Maya: Camera extra data - additional fix for \#3304 [\#3386](https://github.com/pypeclub/OpenPype/pull/3386) -- Maya: Handle excluding `model` family from frame range validator. [\#3370](https://github.com/pypeclub/OpenPype/pull/3370) -- Harmony: audio validator has wrong logic [\#3364](https://github.com/pypeclub/OpenPype/pull/3364) **🔀 Refactored code** @@ -76,7 +101,6 @@ - Webserver: Added CORS middleware [\#3422](https://github.com/pypeclub/OpenPype/pull/3422) - Attribute Defs UI: Files widget show what is allowed to drop in [\#3411](https://github.com/pypeclub/OpenPype/pull/3411) -- General: Add ability to change user value for templates [\#3366](https://github.com/pypeclub/OpenPype/pull/3366) **🐛 Bug fixes** @@ -87,10 +111,7 @@ - Nuke: Collect representation files based on Write [\#3407](https://github.com/pypeclub/OpenPype/pull/3407) - General: Filter representations before integration start [\#3398](https://github.com/pypeclub/OpenPype/pull/3398) - Maya: look collector typo [\#3392](https://github.com/pypeclub/OpenPype/pull/3392) -- TVPaint: Make sure exit code is set to not None [\#3382](https://github.com/pypeclub/OpenPype/pull/3382) - Maya: vray device aspect ratio fix [\#3381](https://github.com/pypeclub/OpenPype/pull/3381) -- Flame: bunch of publishing issues [\#3377](https://github.com/pypeclub/OpenPype/pull/3377) -- Harmony: added unc path to zifile command in Harmony [\#3372](https://github.com/pypeclub/OpenPype/pull/3372) **🔀 Refactored code** @@ -101,30 +122,11 @@ - Hiero: Use client query functions [\#3393](https://github.com/pypeclub/OpenPype/pull/3393) - Nuke: Use client query functions [\#3391](https://github.com/pypeclub/OpenPype/pull/3391) - Maya: Use client query functions [\#3385](https://github.com/pypeclub/OpenPype/pull/3385) -- Harmony: Use client query functions [\#3378](https://github.com/pypeclub/OpenPype/pull/3378) -- Celaction: Use client query functions [\#3376](https://github.com/pypeclub/OpenPype/pull/3376) -- Photoshop: Use client query functions [\#3375](https://github.com/pypeclub/OpenPype/pull/3375) -- AfterEffects: Use client query functions [\#3374](https://github.com/pypeclub/OpenPype/pull/3374) - -**Merged pull requests:** - -- Sync Queue: Added far future value for null values for dates [\#3371](https://github.com/pypeclub/OpenPype/pull/3371) -- Maya - added support for single frame playblast review [\#3369](https://github.com/pypeclub/OpenPype/pull/3369) ## [3.11.1](https://github.com/pypeclub/OpenPype/tree/3.11.1) (2022-06-20) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.11.1-nightly.1...3.11.1) -**🚀 Enhancements** - -- Pyblish Pype: Hiding/Close issues [\#3367](https://github.com/pypeclub/OpenPype/pull/3367) - -**🐛 Bug fixes** - -- Nuke: bake streams with slate on farm [\#3368](https://github.com/pypeclub/OpenPype/pull/3368) -- Nuke: Fix missing variable in extract thumbnail [\#3363](https://github.com/pypeclub/OpenPype/pull/3363) -- Nuke: Fix precollect writes [\#3361](https://github.com/pypeclub/OpenPype/pull/3361) - ## [3.11.0](https://github.com/pypeclub/OpenPype/tree/3.11.0) (2022-06-17) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.11.0-nightly.4...3.11.0) diff --git a/openpype/version.py b/openpype/version.py index c7b0de0381..e9206379e1 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.12.1" +__version__ = "3.12.2-nightly.1" diff --git a/pyproject.toml b/pyproject.toml index 078503a284..19d65b50f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.12.1" # OpenPype +version = "3.12.2-nightly.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 971aef4342e8353a83fc2fe9bf2500a48069aa3d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Jul 2022 10:35:56 +0200 Subject: [PATCH 1158/1227] Removed query list from python file --- openpype/client/entities.py | 604 ------------------------------------ 1 file changed, 604 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 9d65355d1b..38552d9a56 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1348,622 +1348,18 @@ def get_workfile_info( - openpype/hosts/maya/api/shader_definition_editor.py - openpype/hosts/maya/plugins/publish/validate_model_name.py -## Global launch hooks -- openpype/hooks/pre_global_host_data.py - Query: - - project - - asset - -## Global load plugins -- openpype/plugins/load/delete_old_versions.py - Query: - - versions - - representations -- openpype/plugins/load/delivery.py - Query: - - representations - ## Global publish plugins -- openpype/plugins/publish/collect_avalon_entities.py - Query: - - asset - - project -- openpype/plugins/publish/collect_anatomy_instance_data.py - Query: - - assets - - subsets - - last version -- openpype/plugins/publish/collect_scene_loaded_versions.py - Query: - - representations - openpype/plugins/publish/extract_hierarchy_avalon.py - Query: - - asset - - assets - - project Create: - asset Update: - asset -- openpype/plugins/publish/integrate_hero_version.py - Query: - - version - - hero version - - representations -- openpype/plugins/publish/integrate_new.py - Query: - - asset - - subset - - version - - representations -- openpype/plugins/publish/integrate_thumbnail.py - Query: - - version -- openpype/plugins/publish/validate_editorial_asset_name.py - Query: - - assets ## Lib -- openpype/lib/applications.py - Query: - - project - - asset - openpype/lib/avalon_context.py - Query: - - project - - asset - - linked assets (new function get_linked_assets?) - - subset - - subsets - - version - - versions - - last version - - representations - - linked representations (new function get_linked_ids_for_representations) Update: - workfile data -- openpype/lib/plugin_tools.py - Query: - - asset - openpype/lib/project_backpack.py - Query: - - project - - everything from mongo Update: - project -- openpype/lib/usdlib.py - Query: - - project - - asset - -## Pipeline -- openpype/pipeline/load/utils.py - Query: - - project - - assets - - subsets - - version - - versions - - representation - - representations -- openpype/pipeline/mongodb.py - Query: - - project -- openpype/pipeline/thumbnail.py - Query: - - project - -## Hosts -### Aftereffects -- openpype/hosts/aftereffects/plugins/create/workfile_creator.py - Query: - - asset - -### Blender -- openpype/hosts/blender/api/pipeline.py - Query: - - asset -- openpype/hosts/blender/plugins/publish/extract_layout.py - Query: - - representation - -### Celaction -- openpype/hosts/celaction/plugins/publish/collect_audio.py - Query: - - subsets - - last versions - - representations - -### Fusion -- openpype/hosts/fusion/api/lib.py - Query: - - asset - - subset - - version - - representation -- openpype/hosts/fusion/plugins/load/load_sequence.py - Query: - - version -- openpype/hosts/fusion/scripts/fusion_switch_shot.py - Query: - - project - - asset - - versions -- openpype/hosts/fusion/utility_scripts/switch_ui.py - Query: - - assets - -### Harmony -- openpype/hosts/harmony/api/pipeline.py - Query: - - representation - -### Hiero -- openpype/hosts/hiero/api/lib.py - Query: - - project - - version - - versions - - representation -- openpype/hosts/hiero/api/tags.py - Query: - - task types - - assets -- openpype/hosts/hiero/plugins/load/load_clip.py - Query: - - version - - versions -- openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py - Query: - - assets - -### Houdini -- openpype/hosts/houdini/api/lib.py - Query: - - asset -- openpype/hosts/houdini/api/usd.py - Query: - - asset -- openpype/hosts/houdini/plugins/create/create_hda.py - Query: - - asset - - subsets -- openpype/hosts/houdini/plugins/publish/collect_usd_bootstrap.py - Query: - - asset - - subset -- openpype/hosts/houdini/plugins/publish/extract_usd_layered.py - Query: - - asset - - subset - - version - - representation -- openpype/hosts/houdini/plugins/publish/validate_usd_shade_model_exists.py - Query: - - asset - - subset -- openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py - Query: - - project - - asset - -### Maya -- openpype/hosts/maya/api/action.py - Query: - - asset -- openpype/hosts/maya/api/commands.py - Query: - - asset - - project -- openpype/hosts/maya/api/lib.py - Query: - - project - - asset - - subset - - subsets - - version - - representation -- openpype/hosts/maya/api/setdress.py - Query: - - version - - representation -- openpype/hosts/maya/plugins/inventory/import_modelrender.py - Query: - - representation -- openpype/hosts/maya/plugins/load/load_audio.py - Query: - - asset - - subset - - version -- openpype/hosts/maya/plugins/load/load_image_plane.py - Query: - - asset - - subset - - version -- openpype/hosts/maya/plugins/load/load_look.py - Query: - - representation -- openpype/hosts/maya/plugins/load/load_vrayproxy.py - Query: - - representation -- openpype/hosts/maya/plugins/load/load_yeti_cache.py - Query: - - representation -- openpype/hosts/maya/plugins/publish/collect_review.py - Query: - - subsets -- openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py - Query: - - assets -- openpype/hosts/maya/plugins/publish/validate_node_ids_related.py - Query: - - asset -- openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py - Query: - - asset - - subset - -### Nuke -- openpype/hosts/nuke/api/command.py - Query: - - project - - asset -- openpype/hosts/nuke/api/lib.py - Query: - - project - - asset - - version - - versions - - representation -- openpype/hosts/nuke/plugins/load/load_backdrop.py - Query: - - version - - versions -- openpype/hosts/nuke/plugins/load/load_camera_abc.py - Query: - - version - - versions -- openpype/hosts/nuke/plugins/load/load_clip.py - Query: - - version - - versions -- openpype/hosts/nuke/plugins/load/load_effects_ip.py - Query: - - version - - versions -- openpype/hosts/nuke/plugins/load/load_effects.py - Query: - - version - - versions -- openpype/hosts/nuke/plugins/load/load_gizmo_ip.py - Query: - - version - - versions -- openpype/hosts/nuke/plugins/load/load_gizmo.py - Query: - - version - - versions -- openpype/hosts/nuke/plugins/load/load_image.py - Query: - - version - - versions -- openpype/hosts/nuke/plugins/load/load_model.py - Query: - - version - - versions -- openpype/hosts/nuke/plugins/load/load_script_precomp.py - Query: - - version - - versions -- openpype/hosts/nuke/plugins/publish/collect_reads.py - Query: - - asset -- openpype/hosts/nuke/plugins/publish/precollect_instances.py - Query: - - asset -- openpype/hosts/nuke/plugins/publish/precollect_writes.py - Query: - - representation -- openpype/hosts/nuke/plugins/publish/validate_script.py - Query: - - asset - - project - -### Photoshop -- openpype/hosts/photoshop/plugins/create/workfile_creator.py - Query: - - asset - -### Resolve -- openpype/hosts/resolve/plugins/load/load_clip.py - Query: - - version - - versions - -### Standalone publisher -- openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py - Query: - - asset -- openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py - Query: - - assets -- openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py - Query: - - project - - asset -- openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py - Query: - - assets - -### TVPaint -- openpype/hosts/tvpaint/api/pipeline.py - Query: - - project - - asset -- openpype/hosts/tvpaint/plugins/load/load_workfile.py - Query: - - project - - asset -- openpype/hosts/tvpaint/plugins/publish/collect_instances.py - Query: - - asset -- openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py - Query: - - asset -- openpype/hosts/tvpaint/plugins/publish/collect_workfile.py - Query: - - asset - -### Unreal -- openpype/hosts/unreal/plugins/load/load_camera.py - Query: - - asset - - assets -- openpype/hosts/unreal/plugins/load/load_layout.py - Query: - - asset - - assets -- openpype/hosts/unreal/plugins/publish/extract_layout.py - Query: - - representation - -### Webpublisher -- openpype/hosts/webpublisher/webserver_service/webpublish_routes.py - Query: - - assets -- openpype/hosts/webpublisher/plugins/publish/collect_published_files.py - Query: - - last versions - -## Tools -openpype/tools/assetlinks/widgets.py -- SimpleLinkView - Query: - - get_versions - - get_subsets - - get_assets - - get_output_link_versions - -openpype/tools/creator/window.py -- CreatorWindow - Query: - - get_asset_by_name - - get_subsets - -openpype/tools/launcher/models.py -- LauncherModel - Query: - - get_project - - get_assets - -openpype/tools/libraryloader/app.py -- LibraryLoaderWindow - Query: - - get_project - -openpype/tools/loader/app.py -- LoaderWindow - Query: - - get_project -- show - Query: - - get_projects - -openpype/tools/loader/model.py -- SubsetsModel - Query: - - get_assets - - get_subsets - - get_last_versions - - get_versions - - get_hero_versions - - get_version_by_name -- RepresentationModel - Query: - - get_representations - - sync server specific queries (separated into multiple functions?) - - NOT REPLACED - -openpype/tools/loader/widgets.py -- FamilyModel - Query: - - get_subset_families -- VersionTextEdit - Query: - - get_subset_by_id - - get_version_by_id -- SubsetWidget - Query: - - get_subsets - - get_representations - Update: - - Subset groups (combination of asset id and subset names) -- RepresentationWidget - Query: - - get_subsets - - get_versions - - get_representations -- ThumbnailWidget - Query: - - get_thumbnail_id_from_source - - get_thumbnail - -openpype/tools/mayalookassigner/app.py -- MayaLookAssignerWindow - Query: - - get_last_version_by_subset_id - -openpype/tools/mayalookassigner/commands.py -- create_items_from_nodes - Query: - - get_asset_by_id - -openpype/tools/mayalookassigner/vray_proxies.py -- get_look_relationships - Query: - - get_representation_by_name -- load_look - Query: - - get_representation_by_name -- vrayproxy_assign_look - Query: - - get_last_version_by_subset_name - -openpype/tools/project_manager/project_manager/model.py -- HierarchyModel - Query: - - get_asset_ids_with_subsets - - get_project - - get_assets - -openpype/tools/project_manager/project_manager/view.py -- ProjectDocCache - Query: - - get_project - -openpype/tools/project_manager/project_manager/widgets.py -- CreateProjectDialog - Query: - - get_projects - -openpype/tools/publisher/widgets/create_dialog.py -- CreateDialog - Query: - - get_asset_by_name - - get_subsets - -openpype/tools/publisher/control.py -- AssetDocsCache - Query: - - get_assets - -openpype/tools/sceneinventory/model.py -- InventoryModel - Query: - - get_asset_by_id - - get_subset_by_id - - get_version_by_id - - get_last_version_by_subset_id - - get_representation - -openpype/tools/sceneinventory/switch_dialog.py -- SwitchAssetDialog - Query: - - get_asset_by_name - - get_assets - - get_subset_by_name - - get_subsets - - get_versions - - get_hero_versions - - get_last_versions - - get_representations - -openpype/tools/sceneinventory/view.py -- SceneInventoryView - Query: - - get_version_by_id - - get_versions - - get_hero_versions - - get_representation_by_id - - get_representations - -openpype/tools/standalonepublish/widgets/model_asset.py -- AssetModel - Query: - - get_assets - -openpype/tools/standalonepublish/widgets/widget_asset.py -- AssetWidget - Query: - - get_project - - get_asset_by_id - -openpype/tools/standalonepublish/widgets/widget_family.py -- FamilyWidget - Query: - - get_asset_by_name - - get_subset_by_name - - get_subsets - - get_last_version_by_subset_id - -openpype/tools/standalonepublish/app.py -- Window - Query: - - get_asset_by_id - -openpype/tools/texture_copy/app.py -- TextureCopy - Query: - - get_project - - get_asset_by_name - -openpype/tools/workfiles/files_widget.py -- FilesWidget - Query: - - get_asset_by_id - -openpype/tools/workfiles/model.py -- PublishFilesModel - Query: - - get_subsets - - get_versions - - get_representations - -openpype/tools/workfiles/save_as_dialog.py -- build_workfile_data - Query: - - get_project - - get_asset_by_name - -openpype/tools/workfiles/window.py -- Window - Query: - - get_asset_by_id - - get_asset_by_name - -openpype/tools/utils/assets_widget.py -- AssetModel - Query: - - get_project - - get_assets - -openpype/tools/utils/delegates.py -- VersionDelegate - Query: - - get_versions - - get_hero_versions - -openpype/tools/utils/lib.py -- GroupsConfig - Query: - - get_project -- FamilyConfigCache - Query: - - get_asset_by_name - -openpype/tools/utils/tasks_widget.py -- TasksModel - Query: - - get_project - - get_asset_by_id """ From 44611981d40b02065c0a03509b7d45c25900fe66 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Jul 2022 10:37:16 +0200 Subject: [PATCH 1159/1227] modified return types in docstrings --- openpype/client/entities.py | 176 +++++++++++++++++++----------------- 1 file changed, 91 insertions(+), 85 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 38552d9a56..ebd9b4821d 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -117,8 +117,8 @@ def get_asset_by_id(project_name, asset_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. - asset_id (str|ObjectId): Asset's id. - fields (list[str]): Fields that should be returned. All fields are + asset_id (Union[str, ObjectId]): Asset's id. + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -141,7 +141,7 @@ def get_asset_by_name(project_name, asset_name, fields=None): Args: project_name (str): Name of project where to look for queried entities. asset_name (str): Asset's name. - fields (list[str]): Fields that should be returned. All fields are + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -178,12 +178,13 @@ def _get_assets( Args: project_name (str): Name of project where to look for queried entities. - asset_ids (list[str|ObjectId]): Asset ids that should be found. - asset_names (list[str]): Name assets that should be found. - parent_ids (list[str|ObjectId]): Parent asset ids. + asset_ids (Iterable[Union[str, ObjectId]]): Asset ids that should + be found. + asset_names (Iterable[str]): Name assets that should be found. + parent_ids (Iterable[Union[str, ObjectId]]): Parent asset ids. standard (bool): Query standart assets (type 'asset'). archived (bool): Query archived assets (type 'archived_asset'). - fields (list[str]): Fields that should be returned. All fields are + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -244,11 +245,12 @@ def get_assets( Args: project_name (str): Name of project where to look for queried entities. - asset_ids (list[str|ObjectId]): Asset ids that should be found. - asset_names (list[str]): Name assets that should be found. - parent_ids (list[str|ObjectId]): Parent asset ids. + asset_ids (Iterable[Union[str, ObjectId]]): Asset ids that should + be found. + asset_names (Iterable[str]): Name assets that should be found. + parent_ids (Iterable[Union[str, ObjectId]]): Parent asset ids. archived (bool): Add also archived assets. - fields (list[str]): Fields that should be returned. All fields are + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -283,10 +285,11 @@ def get_archived_assets( Args: project_name (str): Name of project where to look for queried entities. - asset_ids (list[str|ObjectId]): Asset ids that should be found. - asset_names (list[str]): Name assets that should be found. - parent_ids (list[str|ObjectId]): Parent asset ids. - fields (list[str]): Fields that should be returned. All fields are + asset_ids (Iterable[Union[str, ObjectId]]): Asset ids that should + be found. + asset_names (Iterable[str]): Name assets that should be found. + parent_ids (Iterable[Union[str, ObjectId]]): Parent asset ids. + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -304,10 +307,11 @@ def get_asset_ids_with_subsets(project_name, asset_ids=None): Args: project_name (str): Name of project where to look for queried entities. - asset_ids (list[str|ObjectId]): Look only for entered asset ids. + asset_ids (Iterable[Union[str, ObjectId]]): Look only for entered + asset ids. Returns: - List[ObjectId]: Asset ids that have existing subsets. + Iterable[ObjectId]: Asset ids that have existing subsets. """ subset_query = { @@ -345,8 +349,8 @@ def get_subset_by_id(project_name, subset_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. - subset_id (str|ObjectId): Id of subset which should be found. - fields (list[str]): Fields that should be returned. All fields are + subset_id (Union[str, ObjectId]): Id of subset which should be found. + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -369,8 +373,8 @@ def get_subset_by_name(project_name, subset_name, asset_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. subset_name (str): Name of subset. - asset_id (str|ObjectId): Id of parent asset. - fields (list[str]): Fields that should be returned. All fields are + asset_id (Union[str, ObjectId]): Id of parent asset. + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -409,16 +413,16 @@ def get_subsets( Args: project_name (str): Name of project where to look for queried entities. - subset_ids (list[str|ObjectId]): Subset ids that should be queried. + subset_ids (Iterable[Union[str, ObjectId]]): Subset ids that should be + queried. Filter ignored if 'None' is passed. + subset_names (Iterable[str]): Subset names that should be queried. Filter ignored if 'None' is passed. - subset_names (list[str]): Subset names that should be queried. - Filter ignored if 'None' is passed. - asset_ids (list[str|ObjectId]): Asset ids under which should look for - the subsets. Filter ignored if 'None' is passed. - names_by_asset_ids (dict[ObjectId, list[str]]): Complex filtering + asset_ids (Iterable[Union[str, ObjectId]]): Asset ids under which should + look for the subsets. Filter ignored if 'None' is passed. + names_by_asset_ids (dict[ObjectId, List[str]]): Complex filtering using asset ids and list of subset names under the asset. archived (bool): Look for archived subsets too. - fields (list[str]): Fields that should be returned. All fields are + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -472,8 +476,8 @@ def get_subset_families(project_name, subset_ids=None): Args: project_name (str): Name of project where to look for queried entities. - subset_ids (list[str|ObjectId]): Subset ids that should be queried. - All subsets from project are used if 'None' is passed. + subset_ids (Iterable[Union[str, ObjectId]]): Subset ids that should + be queried. All subsets from project are used if 'None' is passed. Returns: set[str]: Main families of matching subsets. @@ -508,8 +512,8 @@ def get_version_by_id(project_name, version_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. - version_id (str|ObjectId): Id of version which should be found. - fields (list[str]): Fields that should be returned. All fields are + version_id (Union[str, ObjectId]): Id of version which should be found. + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -535,8 +539,8 @@ def get_version_by_name(project_name, version, subset_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. version (int): name of version entity (it's version). - subset_id (str|ObjectId): Id of version which should be found. - fields (list[str]): Fields that should be returned. All fields are + subset_id (Union[str, ObjectId]): Id of version which should be found. + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -621,14 +625,14 @@ def get_versions( Args: project_name (str): Name of project where to look for queried entities. - version_ids (list[str|ObjectId]): Version ids that will be queried. + version_ids (Iterable[Union[str, ObjectId]]): Version ids that will + be queried. Filter ignored if 'None' is passed. + subset_ids (Iterable[str]): Subset ids that will be queried. Filter ignored if 'None' is passed. - subset_ids (list[str]): Subset ids that will be queried. - Filter ignored if 'None' is passed. - versions (list[int]): Version names (as integers). + versions (Iterable[int]): Version names (as integers). Filter ignored if 'None' is passed. hero (bool): Look also for hero versions. - fields (list[str]): Fields that should be returned. All fields are + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -651,8 +655,9 @@ def get_hero_version_by_subset_id(project_name, subset_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. - subset_id (str|ObjectId): Subset id under which is hero version. - fields (list[str]): Fields that should be returned. All fields are + subset_id (Union[str, ObjectId]): Subset id under which + is hero version. + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -681,8 +686,8 @@ def get_hero_version_by_id(project_name, version_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. - version_id (str|ObjectId): Hero version id. - fields (list[str]): Fields that should be returned. All fields are + version_id (Union[str, ObjectId]): Hero version id. + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -716,11 +721,11 @@ def get_hero_versions( Args: project_name (str): Name of project where to look for queried entities. - subset_ids (list[str|ObjectId]): Subset ids for which should look for - hero versions. Filter ignored if 'None' is passed. - version_ids (list[str|ObjectId]): Hero version ids. Filter ignored if - 'None' is passed. - fields (list[str]): Fields that should be returned. All fields are + subset_ids (Iterable[Union[str, ObjectId]]): Subset ids for which + should look for hero versions. Filter ignored if 'None' is passed. + version_ids (Iterable[Union[str, ObjectId]]): Hero version ids. Filter + ignored if 'None' is passed. + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -746,13 +751,13 @@ def get_output_link_versions(project_name, version_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. - version_id (str|ObjectId): Version id which can be used as input link - for other versions. - fields (list[str]): Fields that should be returned. All fields are + version_id (Union[str, ObjectId]): Version id which can be used + as input link for other versions. + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: - Cursor|list: Iterable cursor yielding versions that are used as input + Iterable: Iterable cursor yielding versions that are used as input links for passed version. """ @@ -774,8 +779,8 @@ def get_last_versions(project_name, subset_ids, fields=None): Args: project_name (str): Name of project where to look for queried entities. - subset_ids (list): List of subset ids. - fields (list[str]): Fields that should be returned. All fields are + subset_ids (Iterable[Union[str, ObjectId]]): List of subset ids. + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -859,8 +864,8 @@ def get_last_version_by_subset_id(project_name, subset_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. - subset_id (str|ObjectId): Id of version which should be found. - fields (list[str]): Fields that should be returned. All fields are + subset_id (Union[str, ObjectId]): Id of version which should be found. + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -889,10 +894,10 @@ def get_last_version_by_subset_name( Args: project_name (str): Name of project where to look for queried entities. subset_name (str): Name of subset. - asset_id (str|ObjectId): Asset id which is parent of passed + asset_id (Union[str, ObjectId]): Asset id which is parent of passed subset name. asset_name (str): Asset name which is parent of passed subset name. - fields (list[str]): Fields that should be returned. All fields are + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -923,8 +928,8 @@ def get_representation_by_id(project_name, representation_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. - representation_id (str|ObjectId): Representation id. - fields (list[str]): Fields that should be returned. All fields are + representation_id (Union[str, ObjectId]): Representation id. + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -956,8 +961,8 @@ def get_representation_by_name( Args: project_name (str): Name of project where to look for queried entities. representation_name (str): Representation name. - version_id (str|ObjectId): Id of parent version entity. - fields (list[str]): Fields that should be returned. All fields are + version_id (Union[str, ObjectId]): Id of parent version entity. + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -1061,18 +1066,18 @@ def get_representations( Args: project_name (str): Name of project where to look for queried entities. - representation_ids (list[str|ObjectId]): Representation ids used as - filter. Filter ignored if 'None' is passed. - representation_names (list[str]): Representations names used as filter. - Filter ignored if 'None' is passed. - version_ids (list[str]): Subset ids used as parent filter. Filter + representation_ids (Iterable[Union[str, ObjectId]]): Representation ids + used as filter. Filter ignored if 'None' is passed. + representation_names (Iterable[str]): Representations names used + as filter. Filter ignored if 'None' is passed. + version_ids (Iterable[str]): Subset ids used as parent filter. Filter ignored if 'None' is passed. - extensions (list[str]): Filter by extension of main representation + extensions (Iterable[str]): Filter by extension of main representation file (without dot). names_by_version_ids (dict[ObjectId, list[str]]): Complex filtering using version ids and list of names under the version. archived (bool): Output will also contain archived representations. - fields (list[str]): Fields that should be returned. All fields are + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -1107,17 +1112,17 @@ def get_archived_representations( Args: project_name (str): Name of project where to look for queried entities. - representation_ids (list[str|ObjectId]): Representation ids used as - filter. Filter ignored if 'None' is passed. - representation_names (list[str]): Representations names used as filter. - Filter ignored if 'None' is passed. - version_ids (list[str]): Subset ids used as parent filter. Filter + representation_ids (Iterable[Union[str, ObjectId]]): Representation ids + used as filter. Filter ignored if 'None' is passed. + representation_names (Iterable[str]): Representations names used + as filter. Filter ignored if 'None' is passed. + version_ids (Iterable[str]): Subset ids used as parent filter. Filter ignored if 'None' is passed. - extensions (list[str]): Filter by extension of main representation + extensions (Iterable[str]): Filter by extension of main representation file (without dot). - names_by_version_ids (dict[ObjectId, list[str]]): Complex filtering + names_by_version_ids (dict[ObjectId, List[str]]): Complex filtering using version ids and list of names under the version. - fields (list[str]): Fields that should be returned. All fields are + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -1145,7 +1150,7 @@ def get_representations_parents(project_name, representations): Args: project_name (str): Name of project where to look for queried entities. - representations (list[dict]): Representation entities with at least + representations (List[dict]): Representation entities with at least '_id' and 'parent' keys. Returns: @@ -1238,7 +1243,7 @@ def get_thumbnail_id_from_source(project_name, src_type, src_id): Args: project_name (str): Name of project where to look for queried entities. src_type (str): Type of source entity ('asset', 'version'). - src_id (str|objectId): Id of source entity. + src_id (Union[str, ObjectId]): Id of source entity. Returns: ObjectId: Thumbnail id assigned to entity. @@ -1265,8 +1270,9 @@ def get_thumbnails(project_name, thumbnail_ids, fields=None): Args: project_name (str): Name of project where to look for queried entities. - thumbnail_ids (list[str|ObjectId]): Ids of thumbnail entities. - fields (list[str]): Fields that should be returned. All fields are + thumbnail_ids (Iterable[Union[str, ObjectId]]): Ids of thumbnail + entities. + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -1291,8 +1297,8 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. - thumbnail_id (str|ObjectId): Id of thumbnail entity. - fields (list[str]): Fields that should be returned. All fields are + thumbnail_id (Union[str, ObjectId]): Id of thumbnail entity. + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -1319,9 +1325,9 @@ def get_workfile_info( Args: project_name (str): Name of project where to look for queried entities. - asset_id (str|ObjectId): Id of asset entity. + asset_id (Union[str, ObjectId]): Id of asset entity. task_name (str): Task name on asset. - fields (list[str]): Fields that should be returned. All fields are + fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. """ From 340c20e64ca1e9239b9dc1d067fbdefac053be5e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Jul 2022 10:43:20 +0200 Subject: [PATCH 1160/1227] fix line length --- openpype/client/entities.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index ebd9b4821d..e7eeadcf48 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -417,8 +417,8 @@ def get_subsets( queried. Filter ignored if 'None' is passed. subset_names (Iterable[str]): Subset names that should be queried. Filter ignored if 'None' is passed. - asset_ids (Iterable[Union[str, ObjectId]]): Asset ids under which should - look for the subsets. Filter ignored if 'None' is passed. + asset_ids (Iterable[Union[str, ObjectId]]): Asset ids under which + should look for the subsets. Filter ignored if 'None' is passed. names_by_asset_ids (dict[ObjectId, List[str]]): Complex filtering using asset ids and list of subset names under the asset. archived (bool): Look for archived subsets too. From eed26c09fc0385a907703622ddfb6f29339b6860 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 18 Jul 2022 10:53:48 +0200 Subject: [PATCH 1161/1227] OP-3589 - renamed method --- openpype/plugins/publish/extract_thumbnail.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index e6df5b3ee0..7933595b89 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -46,7 +46,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): self.log.info("Skipping - no review set on instance.") return - if self._has_thumbnail_already(instance): + if self._already_has_thumbnail(instance): self.log.info("Thumbnail representation already present.") return @@ -106,7 +106,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # There is no need to create more then one thumbnail break - def _has_thumbnail_already(self, instance): + def _already_has_thumbnail(self, instance): for repre in instance.data.get("representations", []): self.log.info("repre {}".format(repre)) if repre["name"] == "thumbnail": From 247db779fe416c663d0b8e4ae100284fe9977476 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 18 Jul 2022 10:56:08 +0200 Subject: [PATCH 1162/1227] Update openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../traypublisher/plugins/publish/validate_frame_ranges.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py index 89289fc6d4..bb6b906e8d 100644 --- a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py +++ b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py @@ -58,9 +58,10 @@ class ValidateFrameRange(OptionalPyblishPluginMixin, files = [files] frames = len(files) - msg = "Frame duration from DB:'{}' ". format(int(duration)) +\ - " doesn't match number of files:'{}'".format(frames) +\ - " Please change frame range for Asset or limit no. of files" + msg = ( + "Frame duration from DB:'{}' doesn't match number of files:'{}'" + " Please change frame range for Asset or limit no. of files" + ). format(int(duration), frames) formatting_data = {"duration": duration, "found": frames} From cdabfbe6f9d0ea90df1a44525bf0ae88a45c825d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 18 Jul 2022 10:56:25 +0200 Subject: [PATCH 1163/1227] Update openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../traypublisher/plugins/publish/validate_frame_ranges.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py index bb6b906e8d..0d7081139d 100644 --- a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py +++ b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py @@ -35,7 +35,8 @@ class ValidateFrameRange(OptionalPyblishPluginMixin, for pattern in self.skip_timelines_check): self.log.info("Skipping for {} task".format(instance.data["task"])) - asset_data = lib.get_asset(instance.data["asset"])["data"] + asset_doc = instance.data["assetEntity"] + asset_data = asset_doc["data"] frame_start = asset_data["frameStart"] frame_end = asset_data["frameEnd"] handle_start = asset_data["handleStart"] From ef2284e507cd43db6b14518244333d10db1091b6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 18 Jul 2022 10:57:01 +0200 Subject: [PATCH 1164/1227] Update openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../plugins/publish/validate_frame_ranges.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py index 0d7081139d..6d48e8352c 100644 --- a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py +++ b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py @@ -43,18 +43,19 @@ class ValidateFrameRange(OptionalPyblishPluginMixin, handle_end = asset_data["handleEnd"] duration = (frame_end - frame_start + 1) + handle_start + handle_end - repre = instance.data.get("representations", [None]) - if not repre: + repres = instance.data.get("representations") + if not repres: self.log.info("No representations, skipping.") return - - ext = repre[0]['ext'].replace(".", '') + + first_repre = repres[0] + ext = first_repre['ext'].replace(".", '') if not ext or ext.lower() not in self.check_extensions: self.log.warning("Cannot check for extension {}".format(ext)) return - files = instance.data.get("representations", [None])[0]["files"] + files = first_repre["files"] if isinstance(files, str): files = [files] frames = len(files) From 247eaf792bb21fd294bf95bb64423965834a4b00 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 18 Jul 2022 11:01:30 +0200 Subject: [PATCH 1165/1227] Check only if skip_timelines_check is filled --- .../traypublisher/plugins/publish/validate_frame_ranges.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py index 6d48e8352c..65b6128cbe 100644 --- a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py +++ b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py @@ -31,8 +31,9 @@ class ValidateFrameRange(OptionalPyblishPluginMixin, if not self.is_active(instance.data): return - if any(re.search(pattern, instance.data["task"]) - for pattern in self.skip_timelines_check): + if (self.skip_timelines_check and + any(re.search(pattern, instance.data["task"]) + for pattern in self.skip_timelines_check)): self.log.info("Skipping for {} task".format(instance.data["task"])) asset_doc = instance.data["assetEntity"] From e21338424ed92c4b59422f487de6de501d756c3c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 18 Jul 2022 11:02:50 +0200 Subject: [PATCH 1166/1227] Hound --- .../traypublisher/plugins/publish/validate_frame_ranges.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py index 65b6128cbe..947624100a 100644 --- a/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py +++ b/openpype/hosts/traypublisher/plugins/publish/validate_frame_ranges.py @@ -3,7 +3,6 @@ import re import pyblish.api import openpype.api -from openpype import lib from openpype.pipeline import ( PublishXmlValidationError, OptionalPyblishPluginMixin @@ -48,7 +47,7 @@ class ValidateFrameRange(OptionalPyblishPluginMixin, if not repres: self.log.info("No representations, skipping.") return - + first_repre = repres[0] ext = first_repre['ext'].replace(".", '') From 4f6646d6c7b712e8ad6678a410da41772642e868 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 18 Jul 2022 11:11:08 +0200 Subject: [PATCH 1167/1227] Update openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../hosts/traypublisher/plugins/publish/collect_mov_batch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py b/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py index 99065d2408..d24659aa8b 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py @@ -19,7 +19,7 @@ class CollectMovBatch( hosts = ["traypublisher"] def process(self, instance): - if not instance.data.get("creator_identifier") == "render_mov_batch": + if instance.data.get("creator_identifier") != "render_mov_batch": return creator_attributes = instance.data["creator_attributes"] From 9ef9c79e8fb7cf6e2a783abe3eba1ba13f1eaa6d Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 18 Jul 2022 11:34:30 +0200 Subject: [PATCH 1168/1227] move all hosts and families to the new integrator --- .../defaults/project_settings/global.json | 83 ++++--------------- .../schemas/schema_global_publish.json | 4 +- 2 files changed, 18 insertions(+), 69 deletions(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 545c792d47..d923fc65c9 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -172,74 +172,8 @@ }, "IntegrateAssetNew": { "hosts": [ - "aftereffects", - "blender", - "celaction", - "flame", - "fusion", - "harmony", - "hiero", - "houdini", - "nuke", - "photoshop", - "resolve", - "tvpaint", - "unreal", - "standalonepublisher", - "webpublisher" ], "families": [ - "workfile", - "pointcache", - "camera", - "animation", - "model", - "mayaAscii", - "mayaScene", - "setdress", - "layout", - "ass", - "vdbcache", - "scene", - "vrayproxy", - "vrayscene_layer", - "render", - "prerender", - "imagesequence", - "review", - "rendersetup", - "rig", - "plate", - "look", - "audio", - "yetiRig", - "yeticache", - "nukenodes", - "gizmo", - "source", - "matchmove", - "image", - "assembly", - "fbx", - "textures", - "action", - "harmony.template", - "harmony.palette", - "editorial", - "background", - "camerarig", - "redshiftproxy", - "effect", - "xgen", - "hda", - "usd", - "staticMesh", - "skeletalMesh", - "mvLook", - "mvUsd", - "mvUsdComposition", - "mvUsdOverride", - "simpleUnrealTexture" ], "template_name_profiles": [ { @@ -287,7 +221,22 @@ }, "IntegrateAsset": { "hosts": [ - "maya" + "maya", + "aftereffects", + "blender", + "celaction", + "flame", + "fusion", + "harmony", + "hiero", + "houdini", + "nuke", + "photoshop", + "resolve", + "tvpaint", + "unreal", + "standalonepublisher", + "webpublisher" ], "families": [ "workfile", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 71eed2e2de..5e3978a2df 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -584,7 +584,7 @@ "type": "dict", "collapsible": true, "key": "IntegrateAssetNew", - "label": "IntegrateAssetNew", + "label": "IntegrateAsset (Legacy)", "is_group": true, "children": [ { @@ -651,7 +651,7 @@ "type": "dict", "collapsible": true, "key": "IntegrateAsset", - "label": "IntegrateAsset", + "label": "Integrate Asset", "is_group": true, "children": [ { From 7646c54da87fe04570ba67027bbf5af308cc7b83 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 18 Jul 2022 11:36:09 +0200 Subject: [PATCH 1169/1227] move subset group collecting to early integrator --- ...{collect_subset_group.py => integrate_subset_group.py} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename openpype/plugins/publish/{collect_subset_group.py => integrate_subset_group.py} (94%) diff --git a/openpype/plugins/publish/collect_subset_group.py b/openpype/plugins/publish/integrate_subset_group.py similarity index 94% rename from openpype/plugins/publish/collect_subset_group.py rename to openpype/plugins/publish/integrate_subset_group.py index 56cd7de94e..4b566e8908 100644 --- a/openpype/plugins/publish/collect_subset_group.py +++ b/openpype/plugins/publish/integrate_subset_group.py @@ -17,12 +17,12 @@ from openpype.lib import ( ) -class CollectSubsetGroup(pyblish.api.InstancePlugin): - """Collect Subset Group for publish.""" +class IntegrateSubsetGroup(pyblish.api.InstancePlugin): + """Integrate Subset Group for publish.""" # Run after CollectAnatomyInstanceData - order = pyblish.api.CollectorOrder + 0.495 - label = "Collect Subset Group" + order = pyblish.api.IntegratorOrder - 0.1 + label = "Subset Group" # Attributes set by settings subset_grouping_profiles = None From 0c59f1539872981ff896119ef7bb729c4c08064d Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 18 Jul 2022 11:38:28 +0200 Subject: [PATCH 1170/1227] rename old integrator to integrate legacy --- .../plugins/publish/{integrate_new.py => integrate_legacy.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename openpype/plugins/publish/{integrate_new.py => integrate_legacy.py} (100%) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_legacy.py similarity index 100% rename from openpype/plugins/publish/integrate_new.py rename to openpype/plugins/publish/integrate_legacy.py From ca2f554a1c4b167d444bbdde1c962f4f9bd7198d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Jul 2022 17:27:49 +0200 Subject: [PATCH 1171/1227] escape html chars from label in widgets --- openpype/tools/publisher/publish_report_viewer/model.py | 4 +++- openpype/tools/publisher/widgets/card_view_widgets.py | 3 ++- openpype/tools/publisher/widgets/list_view_widgets.py | 7 +++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/openpype/tools/publisher/publish_report_viewer/model.py b/openpype/tools/publisher/publish_report_viewer/model.py index a88129a358..bd03376c55 100644 --- a/openpype/tools/publisher/publish_report_viewer/model.py +++ b/openpype/tools/publisher/publish_report_viewer/model.py @@ -1,4 +1,5 @@ import uuid +import html from Qt import QtCore, QtGui import pyblish.api @@ -45,7 +46,8 @@ class InstancesModel(QtGui.QStandardItemModel): all_removed = True for instance_item in instance_items: item = QtGui.QStandardItem(instance_item.label) - item.setData(instance_item.label, ITEM_LABEL_ROLE) + instance_label = html.escape(instance_item.label) + item.setData(instance_label, ITEM_LABEL_ROLE) item.setData(instance_item.errored, ITEM_ERRORED_ROLE) item.setData(instance_item.id, ITEM_ID_ROLE) item.setData(instance_item.removed, INSTANCE_REMOVED_ROLE) diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index b6fcee7edb..5a6878ddca 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -22,6 +22,7 @@ Only one item can be selected at a time. import re import collections +import html from Qt import QtWidgets, QtCore @@ -303,7 +304,7 @@ class InstanceCardWidget(CardWidget): self._last_variant = variant self._last_subset_name = subset_name # Make `variant` bold - label = self.instance.label + label = html.escape(self.instance.label) found_parts = set(re.findall(variant, label, re.IGNORECASE)) if found_parts: for part in found_parts: diff --git a/openpype/tools/publisher/widgets/list_view_widgets.py b/openpype/tools/publisher/widgets/list_view_widgets.py index 3476ee487e..3e4fd5b72d 100644 --- a/openpype/tools/publisher/widgets/list_view_widgets.py +++ b/openpype/tools/publisher/widgets/list_view_widgets.py @@ -23,6 +23,7 @@ selection can be enabled disabled using checkbox or keyboard key presses: ``` """ import collections +import html from Qt import QtWidgets, QtCore, QtGui @@ -113,7 +114,9 @@ class InstanceListItemWidget(QtWidgets.QWidget): self.instance = instance - subset_name_label = QtWidgets.QLabel(instance.label, self) + instance_label = html.escape(instance.label) + + subset_name_label = QtWidgets.QLabel(instance_label, self) subset_name_label.setObjectName("ListViewSubsetName") active_checkbox = NiceCheckbox(parent=self) @@ -178,7 +181,7 @@ class InstanceListItemWidget(QtWidgets.QWidget): # Check subset name label = self.instance.label if label != self._instance_label_widget.text(): - self._instance_label_widget.setText(label) + self._instance_label_widget.setText(html.escape(label)) # Check active state self.set_active(self.instance["active"]) # Check valid states From ddf07d790886a54e21eb3af0358102bbfc693643 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Jul 2022 17:30:02 +0200 Subject: [PATCH 1172/1227] handle cases when task is not set and subset name requires it --- .../plugins/create/create_mov_batch.py | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py b/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py index 67f8848e05..840b0647f9 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py +++ b/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py @@ -4,7 +4,12 @@ import re from openpype.client import get_assets, get_asset_by_name from openpype.hosts.traypublisher.api import pipeline -from openpype.lib import FileDef, BoolDef, get_subset_name_with_asset_doc +from openpype.lib import ( + FileDef, + BoolDef, + get_subset_name_with_asset_doc, + TaskNotSetError, +) from openpype.pipeline import ( CreatedInstance, CreatorError @@ -124,13 +129,27 @@ class BatchMovCreator(TrayPublishCreator): """Create subset name according to standard template process""" task_name = self._get_task_name(asset_doc) - subset_name = get_subset_name_with_asset_doc( - self.family, - variant, - task_name, - asset_doc, - project_name - ) + try: + subset_name = get_subset_name_with_asset_doc( + self.family, + variant, + task_name, + asset_doc, + project_name + ) + except TaskNotSetError: + # Create instance with fake task + # - instance will be marked as invalid so it can't be published + # but user have ability to change it + # NOTE: This expect that there is not task 'Undefined' on asset + task_name = "Undefined" + subset_name = get_subset_name_with_asset_doc( + self.family, + variant, + task_name, + asset_doc, + project_name + ) return subset_name, task_name @@ -178,7 +197,7 @@ class BatchMovCreator(TrayPublishCreator): def get_detail_description(self): return """# Publish batch of .mov to multiple assets. - + File names must then contain only asset name, or asset name + version. (eg. 'chair.mov', 'chair_v001.mov', not really safe `my_chair_v001.mov` """ From 84781c12e570b085f533421c5c8ef0712f611504 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Jul 2022 18:12:04 +0200 Subject: [PATCH 1173/1227] call 'bulk_write' directly on legacy_io --- openpype/plugins/publish/integrate.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 5e86eb014a..790f96d419 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -79,12 +79,6 @@ def get_first_frame_padded(collection): return get_frame_padded(start_frame, padding=collection.padding) -def bulk_write(writes): - """Convenience function to bulk write into active project database""" - project = legacy_io.Session["AVALON_PROJECT"] - return legacy_io._database[project].bulk_write(writes) - - class IntegrateAsset(pyblish.api.InstancePlugin): """Register publish in the database and transfer files to destinations. @@ -288,7 +282,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # Transaction to reduce the chances of another publish trying to # publish to the same version number since that chance can greatly # increase if the file transaction takes a long time. - bulk_write(subset_writes + version_writes) + legacy_io.bulk_write(subset_writes + version_writes) self.log.info("Subset {subset[name]} and Version {version[name]} " "written to database..".format(subset=subset, version=version)) @@ -362,7 +356,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): )) # Write representations to the database - bulk_write(representation_writes) + legacy_io.bulk_write(representation_writes) # Backwards compatibility # todo: can we avoid the need to store this? From 842cf06bf95458537d22eae3031bb7c64586e308 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Jul 2022 18:13:33 +0200 Subject: [PATCH 1174/1227] new integrator can tell legacy one that should not process the instance --- openpype/plugins/publish/integrate.py | 2 +- openpype/plugins/publish/integrate_legacy.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 790f96d419..71032a1d96 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -171,7 +171,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): template_name_profiles = None def process(self, instance): - + instance.data["processedWithNewIntegrator"] = True # Exclude instances that also contain families from exclude families families = set(get_instance_families(instance)) exclude = families & set(self.exclude_families) diff --git a/openpype/plugins/publish/integrate_legacy.py b/openpype/plugins/publish/integrate_legacy.py index 797479af45..18e4035602 100644 --- a/openpype/plugins/publish/integrate_legacy.py +++ b/openpype/plugins/publish/integrate_legacy.py @@ -145,6 +145,10 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): subset_grouping_profiles = None def process(self, instance): + if instance.data.get("processedWithNewIntegrator"): + self.log.info("Instance was already processed with new integrator") + return + for ef in self.exclude_families: if ( instance.data["family"] == ef or From a1784fc25e2b433e1f32e35a36588205b9904e98 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Jul 2022 18:26:27 +0200 Subject: [PATCH 1175/1227] representations are checked before instance registration begins --- openpype/plugins/publish/integrate.py | 80 +++++++++++++++++---------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 71032a1d96..97f99bdba7 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -14,6 +14,7 @@ from openpype.modules import ModulesManager from openpype.lib.profiles_filtering import filter_profiles from openpype.lib.file_transaction import FileTransaction from openpype.pipeline import legacy_io +from openpype.pipeline.publish import KnownPublishError log = logging.getLogger(__name__) @@ -172,6 +173,17 @@ class IntegrateAsset(pyblish.api.InstancePlugin): def process(self, instance): instance.data["processedWithNewIntegrator"] = True + + filtered_repres = self.filter_representations(instance) + # Skip instance if there are not representations to integrate + # all representations should not be integrated + if not filtered_repres: + self.log.warning(( + "Skipping, there are no representations" + " to integrate for instance {}" + ).format(instance.data["family"])) + return + # Exclude instances that also contain families from exclude families families = set(get_instance_families(instance)) exclude = families & set(self.exclude_families) @@ -182,7 +194,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): file_transactions = FileTransaction(log=self.log) try: - self.register(instance, file_transactions) + self.register(instance, file_transactions, filtered_repres) except Exception: # clean destination # todo: preferably we'd also rollback *any* changes to the database @@ -194,8 +206,35 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # the try, except. file_transactions.finalize() - def register(self, instance, file_transactions): + def filter_representations(self, instance): + # Prepare repsentations that should be integrated + repres = instance.data.get("representations") + # Raise error if instance don't have any representations + if not repres: + raise KnownPublishError( + "Instance {} has no representations to integrate".format( + instance.data["family"] + ) + ) + # Validate type of stored representations + if not isinstance(repres, (list, tuple)): + raise TypeError( + "Instance 'files' must be a list, got: {0} {1}".format( + str(type(repres)), str(repres) + ) + ) + + # Filter representations + filtered_repres = [] + for repre in repres: + if "delete" in repre.get("tags", []): + continue + filtered_repres.append(repre) + + return filtered_repres + + def register(self, instance, file_transactions, filtered_repres): instance_stagingdir = instance.data.get("stagingDir") if not instance_stagingdir: self.log.info(( @@ -209,15 +248,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "@ {0}".format(instance_stagingdir) ) - # Ensure at least one representation is set up for registering. - repres = instance.data.get("representations") - assert repres, "Instance has no representations data" - assert isinstance(repres, (list, tuple)), ( - "Instance 'representations' must be a list, got: {0} {1}".format( - str(type(repres)), str(repres) - ) - ) - template_name = self.get_template_name(instance) subset, subset_writes = self.prepare_subset(instance) @@ -238,20 +268,15 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # Prepare all representations prepared_representations = [] - for repre in instance.data["representations"]: - - if "delete" in repre.get("tags", []): - self.log.debug("Skipping representation marked for deletion: " - "{}".format(repre)) - continue - + for repre in filtered_repres: # todo: reduce/simplify what is returned from this function - prepared = self.prepare_representation(repre, - template_name, - existing_repres_by_name, - version, - instance_stagingdir, - instance) + prepared = self.prepare_representation( + repre, + template_name, + existing_repres_by_name, + version, + instance_stagingdir, + instance) for src, dst in prepared["transfers"]: # todo: add support for hardlink transfers @@ -259,12 +284,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): prepared_representations.append(prepared) - if not prepared_representations: - # Even though we check `instance.data["representations"]` earlier - # this could still happen if all representations were tagged with - # "delete" and thus are skipped for integration - raise RuntimeError("No representations prepared to publish.") - # Each instance can also have pre-defined transfers not explicitly # part of a representation - like texture resources used by a # .ma representation. Those destination paths are pre-defined, etc. @@ -273,6 +292,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): for src, dst in instance.data.get("transfers", []): file_transactions.add(src, dst, mode=FileTransaction.MODE_COPY) resource_destinations.add(os.path.abspath(dst)) + for src, dst in instance.data.get("hardlinks", []): file_transactions.add(src, dst, mode=FileTransaction.MODE_HARDLINK) resource_destinations.add(os.path.abspath(dst)) From 7016ca41f7809a1d3729e4999ddf4c1c0dfe1299 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Jul 2022 18:27:37 +0200 Subject: [PATCH 1176/1227] use already prepared modules from context --- openpype/plugins/publish/integrate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 97f99bdba7..8fe5138963 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -317,8 +317,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): self.log.debug("Retrieving Representation Site Sync information ...") # Get the accessible sites for Site Sync - manager = ModulesManager() - sync_server_module = manager.modules_by_name["sync_server"] + modules_by_name = instance.context.data["openPypeModules"] + sync_server_module = modules_by_name["sync_server"] sites = sync_server_module.compute_resource_sync_sites( project_name=instance.data["projectEntity"]["name"] ) From d04abc3767f7c5990c6e0a8a420229bc034075f0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Jul 2022 18:27:54 +0200 Subject: [PATCH 1177/1227] use host name from context data --- openpype/plugins/publish/integrate.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 8fe5138963..e76adb55b8 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -310,10 +310,10 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # Process all file transfers of all integrations now self.log.debug("Integrating source files to destination ...") file_transactions.process() - self.log.debug("Backed up existing files: " - "{}".format(file_transactions.backups)) - self.log.debug("Transferred files: " - "{}".format(file_transactions.transferred)) + self.log.debug( + "Backed up existing files: {}".format(file_transactions.backups)) + self.log.debug( + "Transferred files: {}".format(file_transactions.transferred)) self.log.debug("Retrieving Representation Site Sync information ...") # Get the accessible sites for Site Sync @@ -780,8 +780,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): return { "families": anatomy_data["family"], "tasks": task.get("name"), - "hosts": anatomy_data["app"], - "task_types": task.get("type") + "task_types": task.get("type"), + "hosts": instance.context["hostName"], } def get_rootless_path(self, anatomy, path): From 79c01bdf1a83f4b5d4b57ce0afcc237f696a6387 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 18 Jul 2022 18:28:29 +0200 Subject: [PATCH 1178/1227] skip instances marked to be integrated on farm --- openpype/plugins/publish/integrate.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index e76adb55b8..e3a81091ba 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -172,8 +172,15 @@ class IntegrateAsset(pyblish.api.InstancePlugin): template_name_profiles = None def process(self, instance): + # Mark instance as processed for legacy integrator instance.data["processedWithNewIntegrator"] = True + # Instance should be integrated on a farm + if instance.data.get("farm"): + self.log.info( + "Instance is marked to be processed on farm. Skipping") + return + filtered_repres = self.filter_representations(instance) # Skip instance if there are not representations to integrate # all representations should not be integrated From f398fae425d66c1b9934e9ec016fa1634761f633 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 19 Jul 2022 09:48:22 +0200 Subject: [PATCH 1179/1227] simplified settings for skipping of families --- .../defaults/project_settings/global.json | 119 +----------------- .../schemas/schema_global_publish.json | 60 +-------- 2 files changed, 7 insertions(+), 172 deletions(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index d923fc65c9..bdcf85d1b2 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -171,10 +171,6 @@ ] }, "IntegrateAssetNew": { - "hosts": [ - ], - "families": [ - ], "template_name_profiles": [ { "families": [], @@ -220,120 +216,7 @@ ] }, "IntegrateAsset": { - "hosts": [ - "maya", - "aftereffects", - "blender", - "celaction", - "flame", - "fusion", - "harmony", - "hiero", - "houdini", - "nuke", - "photoshop", - "resolve", - "tvpaint", - "unreal", - "standalonepublisher", - "webpublisher" - ], - "families": [ - "workfile", - "pointcache", - "camera", - "animation", - "model", - "mayaAscii", - "mayaScene", - "setdress", - "layout", - "ass", - "vdbcache", - "scene", - "vrayproxy", - "vrayscene_layer", - "render", - "prerender", - "imagesequence", - "review", - "rendersetup", - "rig", - "plate", - "look", - "audio", - "yetiRig", - "yeticache", - "nukenodes", - "gizmo", - "source", - "matchmove", - "image", - "assembly", - "fbx", - "textures", - "action", - "harmony.template", - "harmony.palette", - "editorial", - "background", - "camerarig", - "redshiftproxy", - "effect", - "xgen", - "hda", - "usd", - "staticMesh", - "skeletalMesh", - "mvLook", - "mvUsd", - "mvUsdComposition", - "mvUsdOverride", - "simpleUnrealTexture" - ], - "template_name_profiles": [ - { - "families": [], - "hosts": [], - "task_types": [], - "tasks": [], - "template_name": "publish" - }, - { - "families": [ - "review", - "render", - "prerender" - ], - "hosts": [], - "task_types": [], - "tasks": [], - "template_name": "render" - }, - { - "families": [ - "simpleUnrealTexture" - ], - "hosts": [ - "standalonepublisher" - ], - "task_types": [], - "tasks": [], - "template_name": "simpleUnrealTexture" - }, - { - "families": [ - "staticMesh", - "skeletalMesh" - ], - "hosts": [ - "maya" - ], - "task_types": [], - "tasks": [], - "template_name": "maya2unreal" - } - ] + "skip_host_families": [] }, "IntegrateHeroVersion": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 5e3978a2df..41eb04be4e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -587,18 +587,6 @@ "label": "IntegrateAsset (Legacy)", "is_group": true, "children": [ - { - "type": "list", - "key": "hosts", - "label": "Hosts", - "object_type": "text" - }, - { - "key": "families", - "label": "Families", - "type": "list", - "object_type": "text" - }, { "type": "list", "key": "template_name_profiles", @@ -656,58 +644,22 @@ "children": [ { "type": "list", - "key": "hosts", - "label": "Hosts", - "object_type": "text" - }, - { - "key": "families", - "label": "Families", - "type": "list", - "object_type": "text" - }, - { - "type": "list", - "key": "template_name_profiles", - "label": "Template name profiles", + "key": "skip_host_families", + "label": "Skip hosts and families", "use_label_wrap": true, "object_type": { "type": "dict", "children": [ { - "type": "label", - "label": "" + "type": "hosts-enum", + "key": "host", + "label": "Host" }, { + "type": "list", "key": "families", "label": "Families", - "type": "list", "object_type": "text" - }, - { - "type": "hosts-enum", - "key": "hosts", - "label": "Hosts", - "multiselection": true - }, - { - "key": "task_types", - "label": "Task types", - "type": "task-types-enum" - }, - { - "key": "tasks", - "label": "Task names", - "type": "list", - "object_type": "text" - }, - { - "type": "separator" - }, - { - "type": "text", - "key": "template_name", - "label": "Template name" } ] } From 1a024d3552723245c273362793ecee6b99f29823 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 19 Jul 2022 09:56:52 +0200 Subject: [PATCH 1180/1227] use settings to decide if new integrator should skip instances --- openpype/plugins/publish/integrate.py | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index e3a81091ba..0b725750aa 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -167,11 +167,15 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "project", "asset", "task", "subset", "version", "representation", "family", "hierarchy", "username" ] + skip_host_families = [] # Attributes set by settings template_name_profiles = None def process(self, instance): + if self._temp_skip_instance_by_settings(instance): + return + # Mark instance as processed for legacy integrator instance.data["processedWithNewIntegrator"] = True @@ -213,6 +217,39 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # the try, except. file_transactions.finalize() + def _temp_skip_instance_by_settings(self, instance): + """Decide if instance will be processed with new or legacy integrator. + + This is temporary solution until we test all usecases with new (this) + integrator plugin. + """ + + host_name = instance.context.data["hostName"] + instance_family = instance.data["family"] + instance_families = set(instance.data.get("families") or []) + + skip = False + for item in self.skip_host_families: + if item["host"] != host_name: + continue + + families = set(item["families"]) + if instance_family in families: + skip = True + break + + for family in instance_families: + if family in families: + skip = True + break + + if skip: + break + + if skip: + self.log.debug("Instance is marked to be skipped by settings.") + return skip + def filter_representations(self, instance): # Prepare repsentations that should be integrated repres = instance.data.get("representations") From 0ebd4b7c9afffa3174ae876f94ca22fa52f83627 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 19 Jul 2022 09:57:00 +0200 Subject: [PATCH 1181/1227] added remaining hosts to integrator hosts --- openpype/plugins/publish/integrate.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 0b725750aa..3c61d01858 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -105,7 +105,10 @@ class IntegrateAsset(pyblish.api.InstancePlugin): label = "Integrate Asset" order = pyblish.api.IntegratorOrder - hosts = ["maya"] + hosts = ["aftereffects", "blender", "celaction", "flame", "harmony", + "hiero", "houdini", "nuke", "photoshop", "resolve", + "standalonepublisher", "traypublisher", "tvpaint", "unreal", + "webpublisher"] families = ["workfile", "pointcache", "camera", From 6c457b2ed1331bb193a572803670836b40f16667 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 19 Jul 2022 10:10:22 +0200 Subject: [PATCH 1182/1227] use settings for publish templates from legacy integrator --- openpype/plugins/publish/integrate.py | 33 ++++++++++++++++++++------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 3c61d01858..1b8015c946 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -172,9 +172,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): ] skip_host_families = [] - # Attributes set by settings - template_name_profiles = None - def process(self, instance): if self._temp_skip_instance_by_settings(instance): return @@ -807,13 +804,33 @@ class IntegrateAsset(pyblish.api.InstancePlugin): """Return anatomy template name to use for integration""" # Define publish template name from profiles filter_criteria = self.get_profile_filter_criteria(instance) - profile = filter_profiles(self.template_name_profiles, - filter_criteria, - logger=self.log) + template_name_profiles = self._get_template_name_profiles(instance) + profile = filter_profiles( + template_name_profiles, + filter_criteria, + logger=self.log + ) + if profile: return profile["template_name"] - else: - return self.default_template_name + return self.default_template_name + + def _get_template_name_profiles(self, instance): + """Receive profiles for publish template keys. + + Reuse template name profiles from legacy integrator. Goal is to move + the profile settings out of plugin settings but until that happens we + want to be able set it at one place and don't break backwards + compatibility (more then once). + """ + + return ( + instance.context["project_settings"] + ["global"] + ["publish"] + ["IntegrateAssetNew"] + ["template_name_profiles"] + ) def get_profile_filter_criteria(self, instance): """Return filter criteria for `filter_profiles`""" From 30db574170a526759ac6c40c574aac0ed41bcdea Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 19 Jul 2022 10:18:41 +0200 Subject: [PATCH 1183/1227] fixed data access --- openpype/plugins/publish/integrate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 1b8015c946..c9eb26d0b7 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -825,7 +825,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): """ return ( - instance.context["project_settings"] + instance.context.data["project_settings"] ["global"] ["publish"] ["IntegrateAssetNew"] @@ -845,7 +845,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "families": anatomy_data["family"], "tasks": task.get("name"), "task_types": task.get("type"), - "hosts": instance.context["hostName"], + "hosts": instance.context.data["hostName"], } def get_rootless_path(self, anatomy, path): From 3c35cbc700102270c55adfa8b249ad9f75ebd226 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 19 Jul 2022 10:23:13 +0200 Subject: [PATCH 1184/1227] make sure legacy integrator happens after new integrator --- openpype/plugins/publish/integrate.py | 1 - openpype/plugins/publish/integrate_legacy.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index c9eb26d0b7..cfaff4067b 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -10,7 +10,6 @@ from pymongo import DeleteMany, ReplaceOne, InsertOne, UpdateOne import pyblish.api import openpype.api -from openpype.modules import ModulesManager from openpype.lib.profiles_filtering import filter_profiles from openpype.lib.file_transaction import FileTransaction from openpype.pipeline import legacy_io diff --git a/openpype/plugins/publish/integrate_legacy.py b/openpype/plugins/publish/integrate_legacy.py index 18e4035602..34e81a3839 100644 --- a/openpype/plugins/publish/integrate_legacy.py +++ b/openpype/plugins/publish/integrate_legacy.py @@ -70,7 +70,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): """ label = "Integrate Asset (legacy)" - order = pyblish.api.IntegratorOrder + # Make sure it happens after new integrator + order = pyblish.api.IntegratorOrder + 0.00001 hosts = ["aftereffects", "blender", "celaction", "flame", "harmony", "hiero", "houdini", "nuke", "photoshop", "resolve", "standalonepublisher", "traypublisher", "tvpaint", "unreal", From dc569c6d65e2a211681a4f0d17aea1874cef9268 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 19 Jul 2022 10:30:31 +0200 Subject: [PATCH 1185/1227] rename and move CollectSubsetGroup to IntegrateSubsetGroup in settings --- .../defaults/project_settings/global.json | 22 ++-- .../schemas/schema_global_publish.json | 110 +++++++++--------- 2 files changed, 66 insertions(+), 66 deletions(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index bdcf85d1b2..9247c6ceb6 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -20,17 +20,6 @@ ], "skip_hosts_headless_publish": [] }, - "CollectSubsetGroup": { - "subset_grouping_profiles": [ - { - "families": [], - "hosts": [], - "task_types": [], - "tasks": [], - "template": "" - } - ] - }, "ValidateEditorialAssetName": { "enabled": true, "optional": false @@ -170,6 +159,17 @@ } ] }, + "IntegrateSubsetGroup": { + "subset_grouping_profiles": [ + { + "families": [], + "hosts": [], + "task_types": [], + "tasks": [], + "template": "" + } + ] + }, "IntegrateAssetNew": { "template_name_profiles": [ { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 41eb04be4e..af08bbec3c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -39,61 +39,6 @@ } ] }, - { - "type": "dict", - "collapsible": true, - "key": "CollectSubsetGroup", - "label": "Collect Subset Group", - "is_group": true, - "children": [ - { - "type": "list", - "key": "subset_grouping_profiles", - "label": "Subset grouping profiles", - "use_label_wrap": true, - "object_type": { - "type": "dict", - "children": [ - { - "type": "label", - "label": "Set all published instances as a part of specific group named according to 'Template'.
Implemented all variants of placeholders [{task},{family},{host},{subset},{renderlayer}]" - }, - { - "key": "families", - "label": "Families", - "type": "list", - "object_type": "text" - }, - { - "type": "hosts-enum", - "key": "hosts", - "label": "Hosts", - "multiselection": true - }, - { - "key": "task_types", - "label": "Task types", - "type": "task-types-enum" - }, - { - "key": "tasks", - "label": "Task names", - "type": "list", - "object_type": "text" - }, - { - "type": "separator" - }, - { - "type": "text", - "key": "template", - "label": "Template" - } - ] - } - } - ] - }, { "type": "dict", "collapsible": true, @@ -580,6 +525,61 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "IntegrateSubsetGroup", + "label": "Integrate Subset Group", + "is_group": true, + "children": [ + { + "type": "list", + "key": "subset_grouping_profiles", + "label": "Subset grouping profiles", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "label", + "label": "Set all published instances as a part of specific group named according to 'Template'.
Implemented all variants of placeholders [{task},{family},{host},{subset},{renderlayer}]" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "type": "hosts-enum", + "key": "hosts", + "label": "Hosts", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "tasks", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "type": "separator" + }, + { + "type": "text", + "key": "template", + "label": "Template" + } + ] + } + } + ] + }, { "type": "dict", "collapsible": true, From 76c594af9a39f22bbbb00156c09d67bd9d0d2d0b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 19 Jul 2022 10:30:48 +0200 Subject: [PATCH 1186/1227] check for existence of subset group on instance before profiling --- .../plugins/publish/integrate_subset_group.py | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/openpype/plugins/publish/integrate_subset_group.py b/openpype/plugins/publish/integrate_subset_group.py index 4b566e8908..910cb060a6 100644 --- a/openpype/plugins/publish/integrate_subset_group.py +++ b/openpype/plugins/publish/integrate_subset_group.py @@ -37,18 +37,22 @@ class IntegrateSubsetGroup(pyblish.api.InstancePlugin): if not self.subset_grouping_profiles: return - # Skip if there is no matching profile - filter_criteria = self.get_profile_filter_criteria(instance) - profile = filter_profiles(self.subset_grouping_profiles, - filter_criteria, - logger=self.log) - if not profile: - return - if instance.data.get("subsetGroup"): # If subsetGroup is already set then allow that value to remain - self.log.debug("Skipping collect subset group due to existing " - "value: {}".format(instance.data["subsetGroup"])) + self.log.debug(( + "Skipping collect subset group due to existing value: {}" + ).format(instance.data["subsetGroup"])) + return + + # Skip if there is no matching profile + filter_criteria = self.get_profile_filter_criteria(instance) + profile = filter_profiles( + self.subset_grouping_profiles, + filter_criteria, + logger=self.log + ) + + if not profile: return template = profile["template"] @@ -68,9 +72,9 @@ class IntegrateSubsetGroup(pyblish.api.InstancePlugin): ) except (KeyError, TemplateUnsolved): keys = fill_pairs.keys() - msg = "Subset grouping failed. " \ - "Only {} are expected in Settings".format(','.join(keys)) - self.log.warning(msg) + self.log.warning(( + "Subset grouping failed. Only {} are expected in Settings" + ).format(','.join(keys))) if filled_template: instance.data["subsetGroup"] = filled_template From 508b3e7d8eccb554b5a6ed9f1660d611b780633d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Jul 2022 10:47:10 +0200 Subject: [PATCH 1187/1227] Update openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../hosts/traypublisher/plugins/publish/collect_mov_batch.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py b/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py index d24659aa8b..e6f33bc619 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py @@ -32,12 +32,11 @@ class CollectMovBatch( "name": ext[1:], "ext": ext[1:], "files": file_name, - "stagingDir": os.path.dirname(file_url) + "stagingDir": os.path.dirname(file_url), + "tags": [] } if creator_attributes["add_review_family"]: - if not repre.get("tags"): - repre["tags"] = [] repre["tags"].append("review") instance.data["families"].append("review") From fa10d36f589d1961f9dcb425129a0a4a69399b86 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Jul 2022 10:47:43 +0200 Subject: [PATCH 1188/1227] Better creation of instance Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../hosts/traypublisher/plugins/create/create_mov_batch.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py b/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py index 840b0647f9..bbabd73415 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py +++ b/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py @@ -64,10 +64,7 @@ class BatchMovCreator(TrayPublishCreator): # Create new instance new_instance = CreatedInstance(self.family, subset_name, instance_data, self) - # Host implementation of storing metadata about instance - pipeline.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_asset_doc_from_file_name(self, source_filename, project_name): """Try to parse out asset name from file name provided. From b4bd2542ff86dea9ce0a480ca6d1524900d6847f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Jul 2022 10:57:30 +0200 Subject: [PATCH 1189/1227] OP-3446 - renamed from mov to movie Workflow should handle not only .mov, but .mp4s or any movie format. --- ...eate_mov_batch.py => create_movie_batch.py} | 18 +++++++++++------- ...ect_mov_batch.py => collect_movie_batch.py} | 4 ++-- .../project_settings/traypublisher.json | 2 +- .../schema_project_traypublisher.json | 4 ++-- 4 files changed, 16 insertions(+), 12 deletions(-) rename openpype/hosts/traypublisher/plugins/create/{create_mov_batch.py => create_movie_batch.py} (94%) rename openpype/hosts/traypublisher/plugins/publish/{collect_mov_batch.py => collect_movie_batch.py} (97%) diff --git a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py b/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py similarity index 94% rename from openpype/hosts/traypublisher/plugins/create/create_mov_batch.py rename to openpype/hosts/traypublisher/plugins/create/create_movie_batch.py index bbabd73415..63b9b2ef28 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_mov_batch.py +++ b/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py @@ -18,21 +18,25 @@ from openpype.pipeline import ( from openpype.hosts.traypublisher.api.plugin import TrayPublishCreator -class BatchMovCreator(TrayPublishCreator): - """Creates instances from .mov file(s).""" - identifier = "render_mov_batch" - label = "Batch Mov" +class BatchMovieCreator(TrayPublishCreator): + """Creates instances from movie file(s). + + Intended for .mov files, but should work for any video file. + Doesn't handle image sequences though. + """ + identifier = "render_movie_batch" + label = "Batch Movies" family = "render" - description = "Publish batch of movs" + description = "Publish batch of video files" create_allow_context_change = False version_regex = re.compile(r"^(.+)_v([0-9]+)$") def __init__(self, project_settings, *args, **kwargs): - super(BatchMovCreator, self).__init__(project_settings, + super(BatchMovieCreator, self).__init__(project_settings, *args, **kwargs) creator_settings = ( - project_settings["traypublisher"]["BatchMovCreator"] + project_settings["traypublisher"]["BatchMovieCreator"] ) self.default_variants = creator_settings["default_variants"] self.default_tasks = creator_settings["default_tasks"] diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py b/openpype/hosts/traypublisher/plugins/publish/collect_movie_batch.py similarity index 97% rename from openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py rename to openpype/hosts/traypublisher/plugins/publish/collect_movie_batch.py index e6f33bc619..45ccbd92d4 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_mov_batch.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_movie_batch.py @@ -4,7 +4,7 @@ import pyblish.api from openpype.pipeline import OpenPypePyblishPluginMixin -class CollectMovBatch( +class CollectMovieBatch( pyblish.api.InstancePlugin, OpenPypePyblishPluginMixin ): """Collect file url for batch mov and create representation. @@ -19,7 +19,7 @@ class CollectMovBatch( hosts = ["traypublisher"] def process(self, instance): - if instance.data.get("creator_identifier") != "render_mov_batch": + if instance.data.get("creator_identifier") != "render_movie_batch": return creator_attributes = instance.data["creator_attributes"] diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index cbe58f49d6..8bf3e3b306 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -236,7 +236,7 @@ "extensions": [] } ], - "BatchMovCreator": { + "BatchMovieCreator": { "default_variants": ["Main"], "default_tasks": ["Compositing"], "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 50ba246c97..e38aa64e04 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -87,8 +87,8 @@ { "type": "dict", "collapsible": true, - "key": "BatchMovCreator", - "label": "Batch Mov Creator", + "key": "BatchMovieCreator", + "label": "Batch Movie Creator", "use_label_wrap": true, "collapsible_key": true, "children": [ From 96be94c0c2b3aa2657bfe832b28904fbaec8ff53 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Jul 2022 11:27:16 +0200 Subject: [PATCH 1190/1227] OP-3446 - added label to Settings Updated label for collector --- .../traypublisher/plugins/publish/collect_movie_batch.py | 4 ++-- .../projects_schema/schema_project_traypublisher.json | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_movie_batch.py b/openpype/hosts/traypublisher/plugins/publish/collect_movie_batch.py index 45ccbd92d4..f37e04d1c9 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_movie_batch.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_movie_batch.py @@ -7,13 +7,13 @@ from openpype.pipeline import OpenPypePyblishPluginMixin class CollectMovieBatch( pyblish.api.InstancePlugin, OpenPypePyblishPluginMixin ): - """Collect file url for batch mov and create representation. + """Collect file url for batch movies and create representation. Adds review on instance and to repre.tags based on value of toggle button on creator. """ - label = "Collect Mov Batch Files" + label = "Collect Movie Batch Files" order = pyblish.api.CollectorOrder hosts = ["traypublisher"] 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 e38aa64e04..8f0f864dc2 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -89,9 +89,12 @@ "collapsible": true, "key": "BatchMovieCreator", "label": "Batch Movie Creator", - "use_label_wrap": true, "collapsible_key": true, "children": [ + { + "type": "label", + "label": "Allows to publish multiple video files in one go.
Name of matching asset is parsed from file names ('asset.mov', 'asset_v001.mov', 'my_asset_to_publish.mov')" + }, { "type": "list", "key": "default_variants", From 505b42ace2a7e07bfff9cb191e0f5f873d6af181 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Jul 2022 11:29:34 +0200 Subject: [PATCH 1191/1227] OP-3446 - Hound --- .../hosts/traypublisher/plugins/create/create_movie_batch.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py b/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py index 63b9b2ef28..c5f0d6b75e 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py +++ b/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py @@ -3,7 +3,6 @@ import os import re from openpype.client import get_assets, get_asset_by_name -from openpype.hosts.traypublisher.api import pipeline from openpype.lib import ( FileDef, BoolDef, @@ -34,7 +33,7 @@ class BatchMovieCreator(TrayPublishCreator): def __init__(self, project_settings, *args, **kwargs): super(BatchMovieCreator, self).__init__(project_settings, - *args, **kwargs) + *args, **kwargs) creator_settings = ( project_settings["traypublisher"]["BatchMovieCreator"] ) From c59a9cb05f641ccf8ec3638d69b1cf764cee9262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 19 Jul 2022 14:40:01 +0200 Subject: [PATCH 1192/1227] Fix: shot duplicate name using shot's hierarchy (ep, seq) --- openpype/modules/kitsu/utils/update_op_with_zou.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 4695a49159..f43bf07e25 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -181,7 +181,7 @@ def update_op_assets( # Find root folder docs root_folder_docs = get_assets( project_name, - asset_name=[entity_parent_folders[-1]], + asset_names=[entity_parent_folders[-1]], fields=["_id", "data.root_of"] ) # NOTE: Not sure why it's checking for entity type? @@ -221,6 +221,14 @@ def update_op_assets( parent_entity = parent_doc["data"]["zou"] parent_zou_id = parent_entity["parent_id"] + # Item name + if item_type == "Asset": + item_name = item_doc["name"] + elif item_type == "Shot": + # Name with parents hierarchy "({episode}_){sequence}_{shot}" + # to avoid duplicate name issue + item_name = "_".join(item_data["parents"] + [item_doc["name"]]) + # Set root folders parents item_data["parents"] = entity_parent_folders + item_data["parents"] @@ -234,7 +242,7 @@ def update_op_assets( item_doc["_id"], { "$set": { - "name": item["name"], + "name": item_name, "data": item_data, "parent": asset_doc_ids[item["project_id"]]["_id"], } From 9f41a512cb4ab27884ee2ad0465c23837f8ccad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 19 Jul 2022 14:44:25 +0200 Subject: [PATCH 1193/1227] black --- openpype/modules/kitsu/utils/update_op_with_zou.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index f43bf07e25..9a5dd7db61 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -14,7 +14,7 @@ from openpype.client import ( get_project, get_assets, get_asset_by_id, - get_asset_by_name + get_asset_by_name, ) from openpype.pipeline import AvalonMongoDB from openpype.api import get_project_settings @@ -182,7 +182,7 @@ def update_op_assets( root_folder_docs = get_assets( project_name, asset_names=[entity_parent_folders[-1]], - fields=["_id", "data.root_of"] + fields=["_id", "data.root_of"], ) # NOTE: Not sure why it's checking for entity type? # OP3 does not support multiple assets with same names so type @@ -224,7 +224,7 @@ def update_op_assets( # Item name if item_type == "Asset": item_name = item_doc["name"] - elif item_type == "Shot": + elif item_type == "Shot": # Name with parents hierarchy "({episode}_){sequence}_{shot}" # to avoid duplicate name issue item_name = "_".join(item_data["parents"] + [item_doc["name"]]) From 2bf3cf1b3094bb7402e7e3a8de55267fd4440edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 19 Jul 2022 14:54:36 +0200 Subject: [PATCH 1194/1227] Fix wrong name for sequence --- openpype/modules/kitsu/utils/update_op_with_zou.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 9a5dd7db61..b2f0caf52b 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -221,13 +221,12 @@ def update_op_assets( parent_entity = parent_doc["data"]["zou"] parent_zou_id = parent_entity["parent_id"] - # Item name - if item_type == "Asset": - item_name = item_doc["name"] - elif item_type == "Shot": + if item_type in ["Shot", "Sequence"]: # Name with parents hierarchy "({episode}_){sequence}_{shot}" # to avoid duplicate name issue item_name = "_".join(item_data["parents"] + [item_doc["name"]]) + else: + item_name = item_doc["name"] # Set root folders parents item_data["parents"] = entity_parent_folders + item_data["parents"] From 5940fd0937941ba0fea391be0889bfa86bedc806 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 20 Jul 2022 03:58:48 +0000 Subject: [PATCH 1195/1227] [Automated] Bump version --- CHANGELOG.md | 15 ++++++++------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95427e9ea9..e8da885473 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,29 @@ # Changelog -## [3.12.2-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.12.2-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.1...HEAD) **🚀 Enhancements** +- General: Interactive console in cli [\#3526](https://github.com/pypeclub/OpenPype/pull/3526) - Ftrack: Automatic daily review session creation can define trigger hour [\#3516](https://github.com/pypeclub/OpenPype/pull/3516) - Ftrack: add source into Note [\#3509](https://github.com/pypeclub/OpenPype/pull/3509) - Ftrack: Trigger custom ftrack topic of project structure creation [\#3506](https://github.com/pypeclub/OpenPype/pull/3506) - Settings UI: Add extract to file action on project view [\#3505](https://github.com/pypeclub/OpenPype/pull/3505) +- Add pack and unpack convenience scripts [\#3502](https://github.com/pypeclub/OpenPype/pull/3502) - General: Event system [\#3499](https://github.com/pypeclub/OpenPype/pull/3499) - NewPublisher: Keep plugins with mismatch target in report [\#3498](https://github.com/pypeclub/OpenPype/pull/3498) - Nuke: load clip with options from settings [\#3497](https://github.com/pypeclub/OpenPype/pull/3497) +- TrayPublisher: implemented render\_mov\_batch [\#3486](https://github.com/pypeclub/OpenPype/pull/3486) - Migrate basic families to the new Tray Publisher [\#3469](https://github.com/pypeclub/OpenPype/pull/3469) **🐛 Bug fixes** +- Additional fixes for powershell scripts [\#3525](https://github.com/pypeclub/OpenPype/pull/3525) +- Maya: Added wrapper around cmds.setAttr [\#3523](https://github.com/pypeclub/OpenPype/pull/3523) - General: Fix hash of centos oiio archive [\#3519](https://github.com/pypeclub/OpenPype/pull/3519) +- Maya: Renderman display output fix [\#3514](https://github.com/pypeclub/OpenPype/pull/3514) - TrayPublisher: Simple creation enhancements and fixes [\#3513](https://github.com/pypeclub/OpenPype/pull/3513) - NewPublisher: Publish attributes are properly collected [\#3510](https://github.com/pypeclub/OpenPype/pull/3510) - TrayPublisher: Make sure host name is filled [\#3504](https://github.com/pypeclub/OpenPype/pull/3504) @@ -25,6 +31,7 @@ **🔀 Refactored code** +- General: Client docstrings cleanup [\#3529](https://github.com/pypeclub/OpenPype/pull/3529) - TimersManager: Use query functions [\#3495](https://github.com/pypeclub/OpenPype/pull/3495) ## [3.12.1](https://github.com/pypeclub/OpenPype/tree/3.12.1) (2022-07-13) @@ -51,7 +58,6 @@ - Blender: Bugfix - Set fps properly on open [\#3426](https://github.com/pypeclub/OpenPype/pull/3426) - Hiero: Add custom scripts menu [\#3425](https://github.com/pypeclub/OpenPype/pull/3425) - Blender: pre pyside install for all platforms [\#3400](https://github.com/pypeclub/OpenPype/pull/3400) -- Maya: Add additional playblast options to review Extractor. [\#3384](https://github.com/pypeclub/OpenPype/pull/3384) **🐛 Bug fixes** @@ -71,7 +77,6 @@ - Maya: fix hashing in Python 3 for tile rendering [\#3447](https://github.com/pypeclub/OpenPype/pull/3447) - LogViewer: Escape html characters in log message [\#3443](https://github.com/pypeclub/OpenPype/pull/3443) - Nuke: Slate frame is integrated [\#3427](https://github.com/pypeclub/OpenPype/pull/3427) -- Maya: Camera extra data - additional fix for \#3304 [\#3386](https://github.com/pypeclub/OpenPype/pull/3386) **🔀 Refactored code** @@ -85,8 +90,6 @@ - General: Move publish plugin and publish render abstractions [\#3442](https://github.com/pypeclub/OpenPype/pull/3442) - General: Use Anatomy after move to pipeline [\#3436](https://github.com/pypeclub/OpenPype/pull/3436) - General: Anatomy moved to pipeline [\#3435](https://github.com/pypeclub/OpenPype/pull/3435) -- Fusion: Use client query functions [\#3380](https://github.com/pypeclub/OpenPype/pull/3380) -- Resolve: Use client query functions [\#3379](https://github.com/pypeclub/OpenPype/pull/3379) ## [3.12.0](https://github.com/pypeclub/OpenPype/tree/3.12.0) (2022-06-28) @@ -111,7 +114,6 @@ - Nuke: Collect representation files based on Write [\#3407](https://github.com/pypeclub/OpenPype/pull/3407) - General: Filter representations before integration start [\#3398](https://github.com/pypeclub/OpenPype/pull/3398) - Maya: look collector typo [\#3392](https://github.com/pypeclub/OpenPype/pull/3392) -- Maya: vray device aspect ratio fix [\#3381](https://github.com/pypeclub/OpenPype/pull/3381) **🔀 Refactored code** @@ -121,7 +123,6 @@ - Houdini: Use client query functions [\#3395](https://github.com/pypeclub/OpenPype/pull/3395) - Hiero: Use client query functions [\#3393](https://github.com/pypeclub/OpenPype/pull/3393) - Nuke: Use client query functions [\#3391](https://github.com/pypeclub/OpenPype/pull/3391) -- Maya: Use client query functions [\#3385](https://github.com/pypeclub/OpenPype/pull/3385) ## [3.11.1](https://github.com/pypeclub/OpenPype/tree/3.11.1) (2022-06-20) diff --git a/openpype/version.py b/openpype/version.py index e9206379e1..dd5ad97449 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.12.2-nightly.1" +__version__ = "3.12.2-nightly.2" diff --git a/pyproject.toml b/pyproject.toml index 19d65b50f9..9552242694 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.12.2-nightly.1" # OpenPype +version = "3.12.2-nightly.2" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From fca8030092e9e9fd165d2bdf2039b662be218499 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 20 Jul 2022 09:47:54 +0200 Subject: [PATCH 1196/1227] normalize path from get_workdir where needed --- .../hosts/flame/plugins/publish/integrate_batch_group.py | 6 ++++-- openpype/lib/applications.py | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/integrate_batch_group.py b/openpype/hosts/flame/plugins/publish/integrate_batch_group.py index da9553cc2a..032de99540 100644 --- a/openpype/hosts/flame/plugins/publish/integrate_batch_group.py +++ b/openpype/hosts/flame/plugins/publish/integrate_batch_group.py @@ -324,5 +324,7 @@ class IntegrateBatchGroup(pyblish.api.InstancePlugin): project_doc = instance.data["projectEntity"] asset_entity = instance.data["assetEntity"] - return get_workdir( - project_doc, asset_entity, task_data["name"], "flame") + workdir = get_workdir( + project_doc, asset_entity, task_data["name"], "flame" + ) + return os.path.normpath(workdir) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index f46197e15f..dafc3b479b 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -1638,6 +1638,7 @@ def prepare_context_environments(data, env_group=None): "Error in anatomy.format: {}".format(str(exc)) ) + workdir = os.path.normpath(workdir) if not os.path.exists(workdir): log.debug( "Creating workdir folder: \"{}\"".format(workdir) From 37a02fe8e19e4faeaa2a63e3cced19efe5aa1fce Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 20 Jul 2022 09:48:21 +0200 Subject: [PATCH 1197/1227] 'get_workdir_with_workdir_data' returns 'TemplateResult' --- openpype/lib/avalon_context.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 76ed6cbbd3..605ebb0b99 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -582,10 +582,7 @@ def get_workdir_with_workdir_data( anatomy_filled = anatomy.format(workdir_data) # Output is TemplateResult object which contain useful data - path = anatomy_filled[template_key]["folder"] - if path: - path = os.path.normpath(path) - return path + return anatomy_filled[template_key]["folder"] def get_workdir( From afd26a82e376a6445177d63d7769394c22b5efa6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 20 Jul 2022 09:48:42 +0200 Subject: [PATCH 1198/1227] pass anatomy to 'get_workdir' in flame integrator --- openpype/hosts/flame/plugins/publish/integrate_batch_group.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/plugins/publish/integrate_batch_group.py b/openpype/hosts/flame/plugins/publish/integrate_batch_group.py index 032de99540..bf6e81523d 100644 --- a/openpype/hosts/flame/plugins/publish/integrate_batch_group.py +++ b/openpype/hosts/flame/plugins/publish/integrate_batch_group.py @@ -323,8 +323,9 @@ class IntegrateBatchGroup(pyblish.api.InstancePlugin): def _get_shot_task_dir_path(self, instance, task_data): project_doc = instance.data["projectEntity"] asset_entity = instance.data["assetEntity"] + anatomy = instance.context.data["anatomy"] workdir = get_workdir( - project_doc, asset_entity, task_data["name"], "flame" + project_doc, asset_entity, task_data["name"], "flame", anatomy ) return os.path.normpath(workdir) From 5963066a7e1ba2849092215697470e7e0ae734bd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 20 Jul 2022 10:11:56 +0200 Subject: [PATCH 1199/1227] TemplateResult has 'normalized' method --- openpype/lib/path_templates.py | 13 +++++++++++++ openpype/pipeline/anatomy.py | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/openpype/lib/path_templates.py b/openpype/lib/path_templates.py index 5c40aa4549..c1282016ef 100644 --- a/openpype/lib/path_templates.py +++ b/openpype/lib/path_templates.py @@ -409,6 +409,19 @@ class TemplateResult(str): self.invalid_types ) + def normalized(self): + """Convert to normalized path.""" + + cls = self.__class__ + return cls( + os.path.normpath(self), + self.template, + self.solved, + self.used_values, + self.missing_keys, + self.invalid_types + ) + class TemplatesResultDict(dict): """Holds and wrap TemplateResults for easy bug report.""" diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index 73081f18fb..08db4749b3 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -380,6 +380,19 @@ class AnatomyTemplateResult(TemplateResult): ) return self.__class__(tmp, self.rootless) + def normalized(self): + """Convert to normalized path.""" + + tmp = TemplateResult( + os.path.normpath(self), + self.template, + self.solved, + self.used_values, + self.missing_keys, + self.invalid_types + ) + return self.__class__(tmp, self.rootless) + class AnatomyTemplates(TemplatesDict): inner_key_pattern = re.compile(r"(\{@.*?[^{}0]*\})") From ab34b6f1cf1b1e56b770dcac4191ca69c3a58a5b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 20 Jul 2022 10:12:56 +0200 Subject: [PATCH 1200/1227] use normalized 'TemplateResult' output in 'get_workdir_with_workdir_data' --- .../hosts/flame/plugins/publish/integrate_batch_group.py | 3 +-- openpype/lib/applications.py | 1 - openpype/lib/avalon_context.py | 5 ++++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/integrate_batch_group.py b/openpype/hosts/flame/plugins/publish/integrate_batch_group.py index bf6e81523d..b59107f155 100644 --- a/openpype/hosts/flame/plugins/publish/integrate_batch_group.py +++ b/openpype/hosts/flame/plugins/publish/integrate_batch_group.py @@ -325,7 +325,6 @@ class IntegrateBatchGroup(pyblish.api.InstancePlugin): asset_entity = instance.data["assetEntity"] anatomy = instance.context.data["anatomy"] - workdir = get_workdir( + return get_workdir( project_doc, asset_entity, task_data["name"], "flame", anatomy ) - return os.path.normpath(workdir) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index dafc3b479b..f46197e15f 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -1638,7 +1638,6 @@ def prepare_context_environments(data, env_group=None): "Error in anatomy.format: {}".format(str(exc)) ) - workdir = os.path.normpath(workdir) if not os.path.exists(workdir): log.debug( "Creating workdir folder: \"{}\"".format(workdir) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 605ebb0b99..2944b2506e 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -582,7 +582,10 @@ def get_workdir_with_workdir_data( anatomy_filled = anatomy.format(workdir_data) # Output is TemplateResult object which contain useful data - return anatomy_filled[template_key]["folder"] + output = anatomy_filled[template_key]["folder"] + if output: + return output.normalized() + return output def get_workdir( From 6ed18c6250e083a827be395cfc9746ffe7bdd2fa Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 20 Jul 2022 13:13:23 +0200 Subject: [PATCH 1201/1227] stop context timer when context is set --- openpype/tools/workfiles/window.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index c1efe026f2..f5edff2fa5 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -331,6 +331,7 @@ class Window(QtWidgets.QMainWindow): if self.assets_widget.refreshing: return + self._set_context_timer.stop() self._context_to_set, context = None, self._context_to_set if "asset" in context: asset_doc = get_asset_by_name( From 58611f89bda27a745defb77a54fe27ff3555ffb7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 20 Jul 2022 13:15:12 +0200 Subject: [PATCH 1202/1227] global function 'show_workfiles' just pass args and kwargs --- openpype/tools/utils/host_tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index ae23e4d089..41c4478d57 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -395,9 +395,9 @@ def show_tool_by_name(tool_name, parent=None, *args, **kwargs): _SingletonPoint.show_tool_by_name(tool_name, parent, *args, **kwargs) -def show_workfiles(parent=None, use_context=None, save=None): +def show_workfiles(*args, **kwargs): _SingletonPoint.show_tool_by_name( - "workfiles", parent, use_context=use_context, save=save + "workfiles", *args, **kwargs ) From 8e4627b44dd6e1a667b742415c6d00946dba3220 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 20 Jul 2022 13:15:37 +0200 Subject: [PATCH 1203/1227] set icon of workfiles tool --- openpype/tools/workfiles/window.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index f5edff2fa5..e86b716765 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -1,6 +1,6 @@ import os import datetime -from Qt import QtCore, QtWidgets +from Qt import QtCore, QtWidgets, QtGui from openpype.client import ( get_asset_by_id, @@ -8,6 +8,7 @@ from openpype.client import ( get_workfile_info, ) from openpype import style +from openpype import resources from openpype.lib import ( create_workfile_doc, save_workfile_data_to_doc, @@ -153,6 +154,8 @@ class Window(QtWidgets.QMainWindow): if not parent: window_flags |= QtCore.Qt.WindowStaysOnTopHint self.setWindowFlags(window_flags) + icon = QtGui.QIcon(resources.get_openpype_icon_filepath()) + self.setWindowIcon(icon) # Create pages widget and set it as central widget pages_widget = QtWidgets.QStackedWidget(self) From 55ff302d21a49b13281972129a944adcb998bf89 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 20 Jul 2022 13:15:58 +0200 Subject: [PATCH 1204/1227] changed workfiles window to QWidget --- openpype/tools/workfiles/window.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index e86b716765..588daf069f 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -143,23 +143,19 @@ class SidePanelWidget(QtWidgets.QWidget): return self._workfile_doc, data -class Window(QtWidgets.QMainWindow): +class Window(QtWidgets.QWidget): """Work Files Window""" title = "Work Files" def __init__(self, parent=None): super(Window, self).__init__(parent=parent) self.setWindowTitle(self.title) - window_flags = QtCore.Qt.Window | QtCore.Qt.WindowCloseButtonHint - if not parent: - window_flags |= QtCore.Qt.WindowStaysOnTopHint - self.setWindowFlags(window_flags) icon = QtGui.QIcon(resources.get_openpype_icon_filepath()) self.setWindowIcon(icon) + self.setWindowFlags(self.windowFlags() | QtCore.Qt.Window) # Create pages widget and set it as central widget pages_widget = QtWidgets.QStackedWidget(self) - self.setCentralWidget(pages_widget) home_page_widget = QtWidgets.QWidget(pages_widget) home_body_widget = QtWidgets.QWidget(home_page_widget) @@ -194,6 +190,9 @@ class Window(QtWidgets.QMainWindow): # the files widget has a filter field which tasks does not. tasks_widget.setContentsMargins(0, 32, 0, 0) + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.addWidget(pages_widget, 1) + # Set context after asset widget is refreshed # - to do so it is necessary to wait until refresh is done set_context_timer = QtCore.QTimer() From 3fac738361abb9446424fcb244d9768c2706f070 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 20 Jul 2022 13:16:15 +0200 Subject: [PATCH 1205/1227] show workfile just calls ensure visible on workfiles tool --- openpype/tools/utils/host_tools.py | 25 +++-------------- openpype/tools/workfiles/window.py | 43 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 41c4478d57..52d15a59f7 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -60,31 +60,14 @@ class HostToolsHelper: return self._workfiles_tool - def show_workfiles(self, parent=None, use_context=None, save=None): + def show_workfiles( + self, parent=None, use_context=None, save=None, on_top=None + ): """Workfiles tool for changing context and saving workfiles.""" - if use_context is None: - use_context = True - - if save is None: - save = True with qt_app_context(): workfiles_tool = self.get_workfiles_tool(parent) - workfiles_tool.set_save_enabled(save) - - if not workfiles_tool.isVisible(): - workfiles_tool.show() - - if use_context: - context = { - "asset": legacy_io.Session["AVALON_ASSET"], - "task": legacy_io.Session["AVALON_TASK"] - } - workfiles_tool.set_context(context) - - # Pull window to the front. - workfiles_tool.raise_() - workfiles_tool.activateWindow() + workfiles_tool.ensure_visible(use_context, save, on_top) def get_loader_tool(self, parent): """Create, cache and return loader tool window.""" diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index 588daf069f..0b0d67e589 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -229,6 +229,49 @@ class Window(QtWidgets.QWidget): self._first_show = True self._context_to_set = None + def ensure_visible( + self, use_context=None, save=None, on_top=None + ): + if save is None: + save = True + + self.set_save_enabled(save) + + if self.isVisible(): + use_context = False + elif use_context is None: + use_context = True + + if on_top is None and self._first_show: + on_top = self.parent() is None + + window_flags = self.windowFlags() + new_window_flags = window_flags + if on_top is True: + new_window_flags = window_flags | QtCore.Qt.WindowStaysOnTopHint + elif on_top is False: + new_window_flags = window_flags & ~QtCore.Qt.WindowStaysOnTopHint + + if new_window_flags != window_flags: + # Note this is not propagated after initialization of widget in + # some Qt builds + self.setWindowFlags(new_window_flags) + self.show() + + elif not self.isVisible(): + self.show() + + if use_context is None or use_context is True: + context = { + "asset": legacy_io.Session["AVALON_ASSET"], + "task": legacy_io.Session["AVALON_TASK"] + } + self.set_context(context) + + # Pull window to the front. + self.raise_() + self.activateWindow() + @property def project_name(self): return legacy_io.Session["AVALON_PROJECT"] From b128e98c8f6269a779ec87a66d35cf8b4d8bf6d1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 20 Jul 2022 13:16:57 +0200 Subject: [PATCH 1206/1227] nuke does not pass parent widget for workfiles tool and tell it to change on top flag --- openpype/hosts/nuke/api/lib.py | 10 ++++++---- openpype/hosts/nuke/api/pipeline.py | 10 +++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 0929415c00..9b24c9fb38 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2440,10 +2440,12 @@ def _launch_workfile_app(): if starting_up or closing_down: return - from .pipeline import get_main_window - - main_window = get_main_window() - host_tools.show_workfiles(parent=main_window) + # Make sure on top is enabled on first show so the window is not hidden + # under main nuke window + # - this happened on Centos 7 and it is because the focus of nuke + # changes to the main window after showing because of initialization + # which moves workfiles tool under it + host_tools.show_workfiles(parent=None, on_top=True) def process_workfile_builder(): diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index 2e3621ba8f..0afc56d2f7 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -142,6 +142,14 @@ def uninstall(): _uninstall_menu() +def _show_workfiles(): + # Make sure parent is not set + # - this makes Workfiles tool as separated window which + # avoid issues with reopening + # - it is possible to explicitly change on top flag of the tool + host_tools.show_workfiles(parent=None, on_top=False) + + def _install_menu(): # uninstall original avalon menu main_window = get_main_window() @@ -158,7 +166,7 @@ def _install_menu(): menu.addSeparator() menu.addCommand( "Work Files...", - lambda: host_tools.show_workfiles(parent=main_window) + _show_workfiles ) menu.addSeparator() From 037ed71f60eba9d5947aef8c973e8b791596b942 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 20 Jul 2022 13:52:15 +0200 Subject: [PATCH 1207/1227] keep subset group template settings but mark them as deprecated with hint where to move the value --- .../defaults/project_settings/global.json | 9 ++++ .../schemas/schema_global_publish.json | 46 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 9247c6ceb6..e509db2791 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -171,6 +171,15 @@ ] }, "IntegrateAssetNew": { + "subset_grouping_profiles": [ + { + "families": [], + "hosts": [], + "task_types": [], + "tasks": [], + "template": "" + } + ], "template_name_profiles": [ { "families": [], diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index af08bbec3c..b9d0b7daba 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -587,6 +587,52 @@ "label": "IntegrateAsset (Legacy)", "is_group": true, "children": [ + { + "type": "label", + "label": "NOTE: Subset grouping profiles settings were moved to
Integrate Subset Group. Please move values there." + }, + { + "type": "list", + "key": "subset_grouping_profiles", + "label": "Subset grouping profiles (DEPRECATED)", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "type": "hosts-enum", + "key": "hosts", + "label": "Hosts", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "tasks", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "type": "separator" + }, + { + "type": "text", + "key": "template", + "label": "Template" + } + ] + } + }, { "type": "list", "key": "template_name_profiles", From bedca7eaf98212d1a5450b097029d25dfdaf4d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 20 Jul 2022 16:30:03 +0200 Subject: [PATCH 1208/1227] :wrench: add relevant Maya validators to Settings add missing validators and add ability to set them optional if needed --- .../validate_review_subset_uniqueness.py | 4 +- .../plugins/publish/validate_setdress_root.py | 3 +- .../defaults/project_settings/maya.json | 178 +++++++++++++++++- .../schemas/schema_maya_publish.json | 155 +++++++++++++++ 4 files changed, 329 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_review_subset_uniqueness.py b/openpype/hosts/maya/plugins/publish/validate_review_subset_uniqueness.py index d70096ee45..04cc9ab5fb 100644 --- a/openpype/hosts/maya/plugins/publish/validate_review_subset_uniqueness.py +++ b/openpype/hosts/maya/plugins/publish/validate_review_subset_uniqueness.py @@ -6,7 +6,7 @@ from openpype.pipeline import PublishXmlValidationError class ValidateReviewSubsetUniqueness(pyblish.api.ContextPlugin): - """Validates that nodes has common root.""" + """Validates that review subset has unique name.""" order = openpype.api.ValidateContentsOrder hosts = ["maya"] @@ -17,7 +17,7 @@ class ValidateReviewSubsetUniqueness(pyblish.api.ContextPlugin): subset_names = [] for instance in context: - self.log.info("instance:: {}".format(instance.data)) + self.log.debug("Instance: {}".format(instance.data)) if instance.data.get('publish'): subset_names.append(instance.data.get('subset')) diff --git a/openpype/hosts/maya/plugins/publish/validate_setdress_root.py b/openpype/hosts/maya/plugins/publish/validate_setdress_root.py index 0b4842d208..8e23a7c04f 100644 --- a/openpype/hosts/maya/plugins/publish/validate_setdress_root.py +++ b/openpype/hosts/maya/plugins/publish/validate_setdress_root.py @@ -4,8 +4,7 @@ import openpype.api class ValidateSetdressRoot(pyblish.api.InstancePlugin): - """ - """ + """Validate if set dress top root node is published.""" order = openpype.api.ValidateContentsOrder label = "SetDress Root" diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 5976c6a823..c96acbff6d 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -205,10 +205,15 @@ "enabled": true, "optional": true, "active": true, - "exclude_families": ["model", "rig", "staticMesh"] + "exclude_families": [ + "model", + "rig", + "staticMesh" + ] }, "ValidateShaderName": { "enabled": false, + "optional": true, "regex": "(?P.*)_(.*)_SHD" }, "ValidateShadingEngine": { @@ -222,6 +227,7 @@ }, "ValidateLoadedPlugin": { "enabled": false, + "optional": true, "whitelist_native_plugins": false, "authorized_plugins": [] }, @@ -236,6 +242,7 @@ }, "ValidateUnrealStaticMeshName": { "enabled": true, + "optional": true, "validate_mesh": false, "validate_collision": true }, @@ -252,6 +259,81 @@ "redshift_render_attributes": [], "renderman_render_attributes": [] }, + "ValidateCurrentRenderLayerIsRenderable": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateRenderImageRule": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateRenderNoDefaultCameras": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateRenderSingleCamera": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateRenderLayerAOVs": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateStepSize": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateVRayDistributedRendering": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateVrayReferencedAOVs": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateVRayTranslatorEnabled": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateVrayProxy": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateVrayProxyMembers": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateYetiRenderScriptCallbacks": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateYetiRigCacheState": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateYetiRigInputShapesInInstance": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateYetiRigSettings": { + "enabled": true, + "optional": false, + "active": true + }, "ValidateModelName": { "enabled": false, "database": true, @@ -270,6 +352,7 @@ }, "ValidateTransformNamingSuffix": { "enabled": true, + "optional": true, "SUFFIX_NAMING_TABLE": { "mesh": [ "_GEO", @@ -293,7 +376,7 @@ "ALLOW_IF_NOT_IN_SUFFIX_TABLE": true }, "ValidateColorSets": { - "enabled": false, + "enabled": true, "optional": true, "active": true }, @@ -337,6 +420,16 @@ "optional": true, "active": true }, + "ValidateMeshNoNegativeScale": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateMeshNonZeroEdgeLength": { + "enabled": true, + "optional": true, + "active": true + }, "ValidateMeshNormalsUnlocked": { "enabled": false, "optional": true, @@ -359,22 +452,22 @@ }, "ValidateNoNamespace": { "enabled": true, - "optional": true, + "optional": false, "active": true }, "ValidateNoNullTransforms": { "enabled": true, - "optional": true, + "optional": false, "active": true }, "ValidateNoUnknownNodes": { "enabled": true, - "optional": true, + "optional": false, "active": true }, "ValidateNodeNoGhosting": { "enabled": false, - "optional": true, + "optional": false, "active": true }, "ValidateShapeDefaultNames": { @@ -402,6 +495,21 @@ "optional": true, "active": true }, + "ValidateNoVRayMesh": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateUnrealMeshTriangulated": { + "enabled": false, + "optional": true, + "active": true + }, + "ValidateAlembicVisibleOnly": { + "enabled": true, + "optional": false, + "active": true + }, "ExtractAlembic": { "enabled": true, "families": [ @@ -425,8 +533,34 @@ "optional": true, "active": true }, + "ValidateAnimationContent": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateOutRelatedNodeIds": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateRigControllersArnoldAttributes": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateSkeletalMeshHierarchy": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateSkinclusterDeformerSet": { + "enabled": true, + "optional": false, + "active": true + }, "ValidateRigOutSetNodeIds": { "enabled": true, + "optional": false, "allow_history_only": false }, "ValidateCameraAttributes": { @@ -439,14 +573,44 @@ "optional": true, "active": true }, + "ValidateAssemblyNamespaces": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateAssemblyModelTransforms": { + "enabled": true, + "optional": false, + "active": true + }, "ValidateAssRelativePaths": { "enabled": true, + "optional": false, + "active": true + }, + "ValidateInstancerContent": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateInstancerFrameRanges": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateNoDefaultCameras": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateUnrealUpAxis": { + "enabled": false, "optional": true, "active": true }, "ValidateCameraContents": { "enabled": true, - "optional": true, + "optional": false, "validate_shapes": true }, "ExtractPlayblast": { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 84182973a1..53247f6bd4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -107,6 +107,11 @@ "key": "enabled", "label": "Enabled" }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, { "type": "label", "label": "Shader name regex can use named capture group asset to validate against current asset name.

Example:
^.*(?P=<asset>.+)_SHD

" @@ -159,6 +164,11 @@ "key": "enabled", "label": "Enabled" }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, { "type": "boolean", "key": "whitelist_native_plugins", @@ -246,6 +256,11 @@ "key": "enabled", "label": "Enabled" }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, { "type": "boolean", "key": "validate_mesh", @@ -332,6 +347,72 @@ } ] }, + { + "type": "schema_template", + "name": "template_publish_plugin", + "template_data": [ + { + "key": "ValidateCurrentRenderLayerIsRenderable", + "label": "Validate Current Render Layer Has Renderable Camera" + }, + { + "key": "ValidateRenderImageRule", + "label": "Validate Images File Rule (Workspace)" + }, + { + "key": "ValidateRenderNoDefaultCameras", + "label": "Validate No Default Cameras Renderable" + }, + { + "key": "ValidateRenderSingleCamera", + "label": "Validate Render Single Camera" + }, + { + "key": "ValidateRenderLayerAOVs", + "label": "Validate Render Passes / AOVs Are Registered" + }, + { + "key": "ValidateStepSize", + "label": "Validate Step Size" + }, + { + "key": "ValidateVRayDistributedRendering", + "label": "VRay Distributed Rendering" + }, + { + "key": "ValidateVrayReferencedAOVs", + "label": "VRay Referenced AOVs" + }, + { + "key": "ValidateVRayTranslatorEnabled", + "label": "VRay Translator Settings" + }, + { + "key": "ValidateVrayProxy", + "label": "VRay Proxy Settings" + }, + { + "key": "ValidateVrayProxyMembers", + "label": "VRay Proxy Members" + }, + { + "key": "ValidateYetiRenderScriptCallbacks", + "label": "Yeti Render Script Callbacks" + }, + { + "key": "ValidateYetiRigCacheState", + "label": "Yeti Rig Cache State" + }, + { + "key": "ValidateYetiRigInputShapesInInstance", + "label": "Yeti Rig Input Shapes In Instance" + }, + { + "key": "ValidateYetiRigSettings", + "label": "Yeti Rig Settings" + } + ] + }, { "type": "collapsible-wrap", "label": "Model", @@ -416,6 +497,11 @@ "key": "enabled", "label": "Enabled" }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, { "type": "label", "label": "Validates transform suffix based on the type of its children shapes." @@ -472,6 +558,14 @@ "key": "ValidateMeshNonManifold", "label": "ValidateMeshNonManifold" }, + { + "key": "ValidateMeshNoNegativeScale", + "label": "Validate Mesh No Negative Scale" + }, + { + "key": "ValidateMeshNonZeroEdgeLength", + "label": "Validate Mesh Edge Length Non Zero" + }, { "key": "ValidateMeshNormalsUnlocked", "label": "ValidateMeshNormalsUnlocked" @@ -525,6 +619,18 @@ { "key": "ValidateUniqueNames", "label": "ValidateUniqueNames" + }, + { + "key": "ValidateNoVRayMesh", + "label": "Validate No V-Ray Proxies (VRayMesh)" + }, + { + "key": "ValidateUnrealMeshTriangulated", + "label": "Validate if Mesh is Triangulated" + }, + { + "key": "ValidateAlembicVisibleOnly", + "label": "Validate Alembic visible node" } ] }, @@ -573,6 +679,26 @@ { "key": "ValidateRigControllers", "label": "Validate Rig Controllers" + }, + { + "key": "ValidateAnimationContent", + "label": "Validate Animation Content" + }, + { + "key": "ValidateOutRelatedNodeIds", + "label": "Validate Animation Out Set Related Node Ids" + }, + { + "key": "ValidateRigControllersArnoldAttributes", + "label": "Validate Rig Controllers (Arnold Attributes)" + }, + { + "key": "ValidateSkeletalMeshHierarchy", + "label": "Validate Skeletal Mesh Top Node" + }, + { + "key": "ValidateSkinclusterDeformerSet", + "label": "Validate Skincluster Deformer Relationships" } ] }, @@ -589,6 +715,11 @@ "key": "enabled", "label": "Enabled" }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, { "type": "boolean", "key": "allow_history_only", @@ -611,9 +742,33 @@ "key": "ValidateAssemblyName", "label": "Validate Assembly Name" }, + { + "key": "ValidateAssemblyNamespaces", + "label": "Validate Assembly Namespaces" + }, + { + "key": "ValidateAssemblyModelTransforms", + "label": "Validate Assembly Model Transforms" + }, { "key": "ValidateAssRelativePaths", "label": "ValidateAssRelativePaths" + }, + { + "key": "ValidateInstancerContent", + "label": "Validate Instancer Content" + }, + { + "key": "ValidateInstancerFrameRanges", + "label": "Validate Instancer Cache Frame Ranges" + }, + { + "key": "ValidateNoDefaultCameras", + "label": "Validate No Default Cameras" + }, + { + "key": "ValidateUnrealUpAxis", + "label": "Validate Unreal Up-Axis check" } ] }, From 59395883170cac8ec8c11ca7b66ccd40d499cbc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 20 Jul 2022 17:30:40 +0200 Subject: [PATCH 1209/1227] Change: Asset is put under an AssetType folder --- .../modules/kitsu/utils/update_op_with_zou.py | 60 ++++++++++++------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 4695a49159..e0ff87adf7 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -14,7 +14,7 @@ from openpype.client import ( get_project, get_assets, get_asset_by_id, - get_asset_by_name + get_asset_by_name, ) from openpype.pipeline import AvalonMongoDB from openpype.api import get_project_settings @@ -154,17 +154,23 @@ def update_op_assets( parent_zou_id = substitute_parent_item["parent_id"] else: parent_zou_id = ( - item.get("parent_id") + # For Asset, put under asset type directory + item.get("entity_type_id") + if item_type == "Asset" + else None + # Else, fallback on usual hierarchy + or item.get("parent_id") or item.get("episode_id") or item.get("source_id") - ) # TODO check consistency + ) - # Substitute Episode and Sequence by Shot - substitute_item_type = ( - "shots" - if item_type in ["Episode", "Sequence"] - else f"{item_type.lower()}s" - ) + # Substitute item type for general classification (assets or shots) + if item_type in ["Asset", "AssetType"]: + substitute_item_type = "assets" + elif item_type in ["Episode", "Sequence"]: + substitute_item_type = "shots" + else: + substitute_item_type = f"{item_type.lower()}s" entity_parent_folders = [ f for f in project_module_settings["entities_root"] @@ -181,8 +187,8 @@ def update_op_assets( # Find root folder docs root_folder_docs = get_assets( project_name, - asset_name=[entity_parent_folders[-1]], - fields=["_id", "data.root_of"] + asset_names=[entity_parent_folders[-1]], + fields=["_id", "data.root_of"], ) # NOTE: Not sure why it's checking for entity type? # OP3 does not support multiple assets with same names so type @@ -219,7 +225,7 @@ def update_op_assets( # Get parent entity parent_entity = parent_doc["data"]["zou"] - parent_zou_id = parent_entity["parent_id"] + parent_zou_id = parent_entity.get("parent_id") # Set root folders parents item_data["parents"] = entity_parent_folders + item_data["parents"] @@ -236,7 +242,7 @@ def update_op_assets( "$set": { "name": item["name"], "data": item_data, - "parent": asset_doc_ids[item["project_id"]]["_id"], + "parent": project_doc["_id"], } }, ) @@ -327,6 +333,10 @@ def sync_all_projects(login: str, password: str): def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): """Update OP project in DB with Zou data. + `root_of` is meant to sort entities by type for a better readability in the data tree. It + puts all shot like (Shot and Episode and Sequence) and asset entities under two different root + folders or hierarchy, defined in settings. + Args: dbcon (AvalonMongoDB): MongoDB connection project (dict): Project dict got using gazu. @@ -341,12 +351,17 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): # Get all assets from zou all_assets = gazu.asset.all_assets_for_project(project) + all_asset_types = gazu.asset.all_asset_types_for_project(project) all_episodes = gazu.shot.all_episodes_for_project(project) all_seqs = gazu.shot.all_sequences_for_project(project) all_shots = gazu.shot.all_shots_for_project(project) all_entities = [ item - for item in all_assets + all_episodes + all_seqs + all_shots + for item in all_assets + + all_asset_types + + all_episodes + + all_seqs + + all_shots if naming_pattern.match(item["name"]) ] @@ -401,21 +416,20 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): "data": { "root_of": entity_type, "parents": parent_folders[:i], - "visualParent": direct_parent_doc, + "visualParent": direct_parent_doc.inserted_id + if direct_parent_doc + else None, "tasks": {}, }, } ) # Create - to_insert = [] - to_insert.extend( - [ - create_op_asset(item) - for item in all_entities - if item["id"] not in zou_ids_and_asset_docs.keys() - ] - ) + to_insert = [ + create_op_asset(item) + for item in all_entities + if item["id"] not in zou_ids_and_asset_docs.keys() + ] if to_insert: # Insert doc in DB dbcon.insert_many(to_insert) From 02cc2166c1ee165b48d697c876675302c8cb77db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 20 Jul 2022 17:37:19 +0200 Subject: [PATCH 1210/1227] docstring linting line length --- openpype/modules/kitsu/utils/update_op_with_zou.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index e0ff87adf7..cabf4e4d18 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -333,9 +333,9 @@ def sync_all_projects(login: str, password: str): def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): """Update OP project in DB with Zou data. - `root_of` is meant to sort entities by type for a better readability in the data tree. It - puts all shot like (Shot and Episode and Sequence) and asset entities under two different root - folders or hierarchy, defined in settings. + `root_of` is meant to sort entities by type for a better readability in + the data tree. It puts all shot like (Shot and Episode and Sequence) and + asset entities under two different root folders or hierarchy, defined in settings. Args: dbcon (AvalonMongoDB): MongoDB connection From a3144c9d75e3372e1d7a3008f54e2695c81a51d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 20 Jul 2022 17:40:16 +0200 Subject: [PATCH 1211/1227] docstring linting line length --- openpype/modules/kitsu/utils/update_op_with_zou.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index cabf4e4d18..46e0fa38f3 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -333,7 +333,7 @@ def sync_all_projects(login: str, password: str): def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): """Update OP project in DB with Zou data. - `root_of` is meant to sort entities by type for a better readability in + `root_of` is meant to sort entities by type for a better readability in the data tree. It puts all shot like (Shot and Episode and Sequence) and asset entities under two different root folders or hierarchy, defined in settings. From af45aff844ab2ff28ccc76ec99d3e5a9803c92e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 20 Jul 2022 17:56:37 +0200 Subject: [PATCH 1212/1227] docstring linting line length --- openpype/modules/kitsu/utils/update_op_with_zou.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 46e0fa38f3..7262d2ee1a 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -335,7 +335,8 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): `root_of` is meant to sort entities by type for a better readability in the data tree. It puts all shot like (Shot and Episode and Sequence) and - asset entities under two different root folders or hierarchy, defined in settings. + asset entities under two different root folders or hierarchy, defined in + settings. Args: dbcon (AvalonMongoDB): MongoDB connection From 480d1968124ccf4fe2bc70b109145b016292fe25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 20 Jul 2022 17:56:55 +0200 Subject: [PATCH 1213/1227] docstring linting line length --- openpype/modules/kitsu/utils/update_op_with_zou.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 7262d2ee1a..7bfbd42f6a 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -335,7 +335,7 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): `root_of` is meant to sort entities by type for a better readability in the data tree. It puts all shot like (Shot and Episode and Sequence) and - asset entities under two different root folders or hierarchy, defined in + asset entities under two different root folders or hierarchy, defined in settings. Args: From 92b611d76aabb35fefb3f34430a9679b5c9b28da Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 21 Jul 2022 09:58:17 +0200 Subject: [PATCH 1214/1227] Raise AttributeError instead of ImportError on missing attribute in 'openpype_interfaces' --- openpype/modules/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index b9ccec13cc..1bd343fd07 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -49,6 +49,7 @@ class _ModuleClass(object): Object of this class can be stored to `sys.modules` and used for storing dynamically imported modules. """ + def __init__(self, name): # Call setattr on super class super(_ModuleClass, self).__setattr__("name", name) @@ -116,12 +117,13 @@ class _InterfacesClass(_ModuleClass): - this is because interfaces must be available even if are missing implementation """ + def __getattr__(self, attr_name): if attr_name not in self.__attributes__: if attr_name in ("__path__", "__file__"): return None - raise ImportError(( + raise AttributeError(( "cannot import name '{}' from 'openpype_interfaces'" ).format(attr_name)) From 635164b00c6573396096c72268e71a4a062688ff Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 21 Jul 2022 10:40:39 +0200 Subject: [PATCH 1215/1227] added HiddenCreator description --- website/docs/dev_publishing.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/website/docs/dev_publishing.md b/website/docs/dev_publishing.md index 8ee3b7e85f..c949fa8570 100644 --- a/website/docs/dev_publishing.md +++ b/website/docs/dev_publishing.md @@ -66,7 +66,7 @@ Another optional function is **get_current_context**. This function is handy in Main responsibility of create plugin is to create, update, collect and remove instance metadata and propagate changes to create context. Has access to **CreateContext** (`self.create_context`) that discovered the plugin so has also access to other creators and instances. Create plugins have a lot of responsibility so it is recommended to implement common code per host. #### *BaseCreator* -Base implementation of creator plugin. It is not recommended to use this class as base for production plugins but rather use one of **AutoCreator** and **Creator** variants. +Base implementation of creator plugin. It is not recommended to use this class as base for production plugins but rather use one of **HiddenCreator**, **AutoCreator** and **Creator** variants. **Abstractions** - **`family`** (class attr) - Tells what kind of instance will be created. @@ -92,7 +92,7 @@ def collect_instances(self): self._add_instance_to_context(instance) ``` -- **`create`** (method) - Create a new object of **CreatedInstance** store its metadata to the workfile and add the instance into the created context. Failed Creating should raise **CreatorError** if an error happens that artists can fix or give them some useful information. Triggers and implementation differs for **Creator** and **AutoCreator**. +- **`create`** (method) - Create a new object of **CreatedInstance** store its metadata to the workfile and add the instance into the created context. Failed Creating should raise **CreatorError** if an error happens that artists can fix or give them some useful information. Triggers and implementation differs for **Creator**, **HiddenCreator** and **AutoCreator**. - **`update_instances`** (method) - Update data of instances. Receives tuple with **instance** and **changes**. ```python @@ -199,6 +199,20 @@ class RenderLayerCreator(Creator): - **`get_dynamic_data`** (method) - Can be used to extend data for subset templates which may be required in some cases. +#### *HiddenCreator* +Creator which is not showed in UI so artist can't trigger it directly but is available for other creators. This creator is primarily meant for cases when creation should create different types of instances. For example during editorial publishing where input is single edl file but should create 2 or more kind of instances each with different family, attributes and abilities. Arguments for creation were limited to `instance_data` and `source_data`. Data of `instance_data` should follow what is sent to other creators and `source_data` can be used to send custom data defined by main creator. It is expected that `HiddenCreator` has specific main or "parent" creator. + +```python +def create(self, instance_data, source_data): + variant = instance_data["variant"] + task_name = instance_data["task"] + asset_name = instance_data["asset"] + asset_doc = get_asset_by_name(self.project_name, asset_name) + self.get_subset_name( + variant, task_name, asset_doc, self.project_name, self.host_name) +``` + + #### *AutoCreator* Creator that is triggered on reset of create context. Can be used for families that are expected to be created automatically without artist interaction (e.g. **workfile**). Method `create` is triggered after collecting all creators. From a8d99a6f91d8fc44bda465a6a5f3f5425931eb75 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 21 Jul 2022 10:41:56 +0200 Subject: [PATCH 1216/1227] changed imports of 'attribute_deffinitions' --- website/docs/dev_publishing.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/website/docs/dev_publishing.md b/website/docs/dev_publishing.md index c949fa8570..5266ece72c 100644 --- a/website/docs/dev_publishing.md +++ b/website/docs/dev_publishing.md @@ -172,11 +172,11 @@ class RenderLayerCreator(Creator): icon = "fa5.building" ``` -- **`get_instance_attr_defs`** (method) - Attribute definitions of instance. Creator can define attribute values with default values for each instance. These attributes may affect how instances will be instance processed during publishing. Attribute defiitions can be used from `openpype.pipeline.lib.attribute_definitions` (NOTE: Will be moved to `openpype.lib.attribute_definitions` soon). Attribute definitions define basic types of values for different cases e.g. boolean, number, string, enumerator, etc. Default implementation returns **instance_attr_defs**. +- **`get_instance_attr_defs`** (method) - Attribute definitions of instance. Creator can define attribute values with default values for each instance. These attributes may affect how instances will be instance processed during publishing. Attribute defiitions can be used from `openpype.lib.attribute_definitions`. Attribute definitions define basic types of values for different cases e.g. boolean, number, string, enumerator, etc. Default implementation returns **instance_attr_defs**. - **`instance_attr_defs`** (attr) - Attribute for default implementation of **get_instance_attr_defs**. ```python -from openpype.pipeline import attribute_definitions +from openpype.lib import attribute_definitions class RenderLayerCreator(Creator): @@ -311,7 +311,8 @@ class BulkRenderCreator(Creator): - **`pre_create_attr_defs`** (attr) - Attribute for default implementation of **get_pre_create_attr_defs**. ```python -from openpype.pipeline import Creator, attribute_definitions +from openpype.lib import attribute_definitions +from openpype.pipeline.create import Creator class CreateRender(Creator): @@ -484,10 +485,8 @@ Possible attribute definitions can be found in `openpype/pipeline/lib/attribute_ ```python import pyblish.api -from openpype.pipeline import ( - OpenPypePyblishPluginMixin, - attribute_definitions, -) +from openpype.lib import attribute_definitions +from openpype.pipeline import OpenPypePyblishPluginMixin # Example context plugin From 6d093b92d9db498ab40dbc2b5bdc7a93f7581ebb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 21 Jul 2022 10:42:12 +0200 Subject: [PATCH 1217/1227] changed queries and access to current session --- website/docs/dev_publishing.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/website/docs/dev_publishing.md b/website/docs/dev_publishing.md index 5266ece72c..f11a2c3047 100644 --- a/website/docs/dev_publishing.md +++ b/website/docs/dev_publishing.md @@ -248,14 +248,14 @@ def create(self): # - variant can be filled from settings variant = self._variant_name # Only place where we can look for current context - project_name = io.Session["AVALON_PROJECT"] - asset_name = io.Session["AVALON_ASSET"] - task_name = io.Session["AVALON_TASK"] - host_name = io.Session["AVALON_APP"] + project_name = self.project_name + asset_name = legacy_io.Session["AVALON_ASSET"] + task_name = legacy_io.Session["AVALON_TASK"] + host_name = legacy_io.Session["AVALON_APP"] # Create new instance if does not exist yet if existing_instance is None: - asset_doc = io.find_one({"type": "asset", "name": asset_name}) + asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( variant, task_name, asset_doc, project_name, host_name ) @@ -278,7 +278,7 @@ def create(self): existing_instance["asset"] != asset_name or existing_instance["task"] != task_name ): - asset_doc = io.find_one({"type": "asset", "name": asset_name}) + asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( variant, task_name, asset_doc, project_name, host_name ) From dfa6328d74fbbd806769d3689ccc1b2f85dc757e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 21 Jul 2022 11:58:42 +0200 Subject: [PATCH 1218/1227] remove metadata from default environment values --- openpype/settings/defaults/system_settings/general.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/settings/defaults/system_settings/general.json b/openpype/settings/defaults/system_settings/general.json index a06947ba77..909ffc1ee4 100644 --- a/openpype/settings/defaults/system_settings/general.json +++ b/openpype/settings/defaults/system_settings/general.json @@ -2,11 +2,7 @@ "studio_name": "Studio name", "studio_code": "stu", "admin_password": "", - "environment": { - "__environment_keys__": { - "global": [] - } - }, + "environment": {}, "log_to_server": true, "disk_mapping": { "windows": [], From 78b4bbadc92ec29167af3487e1b597e07a40f35e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 22 Jul 2022 11:39:22 +0200 Subject: [PATCH 1219/1227] add continuos arguments next to each other --- openpype/plugins/publish/extract_review_slate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 2edaf10e6b..28deb360be 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -295,12 +295,14 @@ class ExtractReviewSlate(openpype.api.Extractor): # this will reencode the output if input_audio: fmap = [ + "-filter_complex", "[0:v] [0:a] [1:v] [1:a] concat=n=2:v=1:a=1 [v] [a]", "-map", '[v]', "-map", '[a]' ] else: fmap = [ + "-filter_complex", "[0:v] [1:v] concat=n=2:v=1:a=0 [v]", "-map", '[v]' ] @@ -308,7 +310,6 @@ class ExtractReviewSlate(openpype.api.Extractor): ffmpeg_path, "-i", slate_v_path, "-i", input_path, - "-filter_complex", ] concat_args.extend(fmap) if offset_timecode: From f99f811ddd4194caadd27a481aef766eae2e5727 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 22 Jul 2022 11:39:37 +0200 Subject: [PATCH 1220/1227] add `-y` into base of ffmpeg arguments --- openpype/plugins/publish/extract_review_slate.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 28deb360be..90dad00b97 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -308,6 +308,7 @@ class ExtractReviewSlate(openpype.api.Extractor): ] concat_args = [ ffmpeg_path, + "-y", "-i", slate_v_path, "-i", input_path, ] @@ -319,6 +320,7 @@ class ExtractReviewSlate(openpype.api.Extractor): # - keep format of output if format_args: concat_args.extend(format_args) + # Use arguments from ffmpeg preset source_ffmpeg_cmd = repre.get("ffmpeg_cmd") if source_ffmpeg_cmd: @@ -334,7 +336,7 @@ class ExtractReviewSlate(openpype.api.Extractor): concat_args.append(arg) # assumes arg has one parameter concat_args.append(args[indx + 1]) - concat_args.append("-y") + # add final output path concat_args.append(output_path) From fcbf46d345bcef7363bc4f590d476136f478b6ce Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 22 Jul 2022 14:20:56 +0200 Subject: [PATCH 1221/1227] Add normaal animation --- website/src/css/custom.css | 4 ++-- website/src/pages/index.js | 16 ++++++++++------ website/static/img/logo_normaal.png | Bin 0 -> 13468 bytes 3 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 website/static/img/logo_normaal.png diff --git a/website/src/css/custom.css b/website/src/css/custom.css index e8dd86256b..58c9305bc7 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -196,12 +196,12 @@ html[data-theme='dark'] .header-github-link::before { padding: 20px } -.showcase .client { +.showcase .studio { display: flex; justify-content: space-between; } -.showcase .client img { +.showcase .studio img { max-height: 110px; padding: 20px; max-width: 160px; diff --git a/website/src/pages/index.js b/website/src/pages/index.js index ae7119e928..52302ec285 100644 --- a/website/src/pages/index.js +++ b/website/src/pages/index.js @@ -65,13 +65,17 @@ const collab = [ image: '/img/clothcat.png', infoLink: 'https://www.clothcatanimation.com/' }, { - title: 'Ellipse Studio', - image: '/img/ellipse-studio.png', - infoLink: 'http://www.dargaudmedia.com' + title: 'Ellipse Animation', + image: '/img/ellipse_animation.svg', + infoLink: 'http://www.ellipseanimation.com' }, { title: 'J Cube Inc', image: '/img/jcube_logo_bw.png', infoLink: 'https://j-cube.jp' + }, { + title: 'Normaal Animation', + image: '/img/logo_normaal.png', + infoLink: 'https://j-cube.jp' } ]; @@ -191,10 +195,10 @@ function Service({imageUrl, title, description}) { ); } -function Client({title, image, infoLink}) { +function Studio({title, image, infoLink}) { const imgUrl = useBaseUrl(image); return ( - + ); @@ -490,7 +494,7 @@ function Home() {

Studios using openPype

{studios.map((props, idx) => ( - + ))}

n)d3@oZpYFsmbJyGIDGW; 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 R3jP~ZQC3Z+PRcy&{{SdSemwvH diff --git a/openpype/resources/app_icons/nukestudio.png b/openpype/resources/app_icons/nukestudio.png index 99c95f59ff858d67fb92263f594f4b71bda87a66..601d4a591d7fd21a0c9c6a1850e569ca05e523ef 100644 GIT binary patch literal 101945 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 literal 46080 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 HIsgA3TR*cj diff --git a/openpype/resources/app_icons/nukex.png b/openpype/resources/app_icons/nukex.png index 1c5a83c8ab2e22da3e6afe8a9561441c4e0e4022..980f150124faae3715eb4d0018d3368f1091a60f 100644 GIT binary patch literal 99787 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≶y*UWJeRW_RcYBCht7M?Kk$4b?RkwBCiuA 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 a`-EZgF*J|OX($H!`pHTvNz{oM2mc>0LFUT< literal 44709 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

=26e?*z3RuFR6Ga0 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 CH}}2( literal 0 HcmV?d00001 From 2487c1f1cd8e759977d1ced49e4ed78a91d4dace Mon Sep 17 00:00:00 2001 From: pberto Date: Fri, 13 May 2022 13:28:35 +0900 Subject: [PATCH 0236/1227] cosmetics: removed two trailing whitespaces --- .../hosts/maya/plugins/publish/extract_multiverse_usd_comp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py index c686b2a600..0e5074f127 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py @@ -122,12 +122,12 @@ class ExtractMultiverseUsdComposition(openpype.api.Extractor): comp_write_opts = multiverse.CompositionWriteOptions() - """ + """ OP tells MV to write to a staging directory, and then moves the file to it's final publish directory. By default, MV write relative paths, but these paths will break when the referencing file moves. This option forces writes to absolute paths, which is ok within OP - because all published assets have static paths, and MV can only + because all published assets have static paths, and MV can only reference published assets. When a proper UsdAssetResolver is used, this won't be needed. """ From ababf4053e8de157b9edcc9227d662e476f3e497 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 13 May 2022 11:24:36 +0200 Subject: [PATCH 0237/1227] flame: splitting function into smaller parts --- .../publish/extract_subset_resources.py | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 0e04336211..9ad3b21687 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -60,12 +60,7 @@ class ExtractSubsetResources(openpype.api.Extractor): export_presets_mapping = {} def process(self, instance): - if ( - self.keep_original_representation - and "representations" not in instance.data - or not self.keep_original_representation - ): - instance.data["representations"] = [] + self._make_representation_data(instance) # flame objects segment = instance.data["item"] @@ -92,7 +87,6 @@ class ExtractSubsetResources(openpype.api.Extractor): handles = max(handle_start, handle_end) # get media source range with handles - source_end_handles = instance.data["sourceEndH"] source_start_handles = instance.data["sourceStartH"] source_end_handles = instance.data["sourceEndH"] @@ -109,27 +103,7 @@ class ExtractSubsetResources(openpype.api.Extractor): for unique_name, preset_config in export_presets.items(): modify_xml_data = {} - # get activating attributes - activated_preset = preset_config["active"] - filter_path_regex = preset_config.get("filter_path_regex") - - self.log.info( - "Preset `{}` is active `{}` with filter `{}`".format( - unique_name, activated_preset, filter_path_regex - ) - ) - self.log.debug( - "__ clip_path: `{}`".format(clip_path)) - - # skip if not activated presete - if not activated_preset: - continue - - # exclude by regex filter if any - if ( - filter_path_regex - and not re.search(filter_path_regex, clip_path) - ): + if self._should_skip(preset_config, clip_path, unique_name): continue # get all presets attributes @@ -147,18 +121,10 @@ class ExtractSubsetResources(openpype.api.Extractor): ) ) - # get attribures related loading in integrate_batch_group - load_to_batch_group = preset_config.get( - "load_to_batch_group") - batch_group_loader_name = preset_config.get( - "batch_group_loader_name") - - # convert to None if empty string - if batch_group_loader_name == "": - batch_group_loader_name = None - # get frame range with handles for representation range frame_start_handle = frame_start - handle_start + + # calculate duration with handles source_duration_handles = ( source_end_handles - source_start_handles) @@ -272,8 +238,10 @@ class ExtractSubsetResources(openpype.api.Extractor): "data": { "colorspace": color_out }, - "load_to_batch_group": load_to_batch_group, - "batch_group_loader_name": batch_group_loader_name + "load_to_batch_group": preset_config.get( + "load_to_batch_group"), + "batch_group_loader_name": preset_config.get( + "batch_group_loader_name") or None } # collect all available content of export dir @@ -328,6 +296,38 @@ class ExtractSubsetResources(openpype.api.Extractor): self.log.debug("All representations: {}".format( pformat(instance.data["representations"]))) + def _should_skip(self, preset_config, clip_path, unique_name): + # get activating attributes + activated_preset = preset_config["active"] + filter_path_regex = preset_config.get("filter_path_regex") + + self.log.info( + "Preset `{}` is active `{}` with filter `{}`".format( + unique_name, activated_preset, filter_path_regex + ) + ) + self.log.debug( + "__ clip_path: `{}`".format(clip_path)) + + # skip if not activated presete + if not activated_preset: + return True + + # exclude by regex filter if any + if ( + filter_path_regex + and not re.search(filter_path_regex, clip_path) + ): + return True + + def _make_representation_data(self, instance): + if ( + self.keep_original_representation + and "representations" not in instance.data + or not self.keep_original_representation + ): + instance.data["representations"] = [] + def _unfolds_nested_folders(self, stage_dir, files_list, ext): """Unfolds nested folders From 0ae531d4cec91870774169f51ce373bb73e7261f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 13 May 2022 11:25:29 +0200 Subject: [PATCH 0238/1227] flame: fixing small hickups removing frame range input form reference --- openpype/hosts/flame/otio/flame_export.py | 7 ++++--- openpype/lib/editorial.py | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index 08478d4b98..ffb82b97c2 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -205,8 +205,9 @@ def create_otio_markers(otio_item, item): otio_item.markers.append(otio_marker) -def create_otio_reference(clip_data, duration, fps=None): +def create_otio_reference(clip_data, fps=None): metadata = _get_metadata(clip_data) + duration = int(clip_data["source_duration"]) # get file info for path and start frame frame_start = 0 @@ -307,7 +308,7 @@ def create_otio_clip(clip_data): # secondly check if any change of speed if source_duration != _clip_record_duration: - retime_speed = float(source_duration / _clip_record_duration) + retime_speed = float(source_duration) / float(_clip_record_duration) log.debug("_ retime_speed: {}".format(retime_speed)) speed *= retime_speed @@ -319,7 +320,7 @@ def create_otio_clip(clip_data): # create media reference media_reference = create_otio_reference( - clip_data, source_duration, media_fps) + clip_data, media_fps) # creatae source range source_range = create_otio_time_range( diff --git a/openpype/lib/editorial.py b/openpype/lib/editorial.py index 1ee21deedc..4979bac159 100644 --- a/openpype/lib/editorial.py +++ b/openpype/lib/editorial.py @@ -269,16 +269,16 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): "retime": True, "speed": time_scalar, "timewarps": time_warp_nodes, - "handleStart": handle_start, - "handleEnd": handle_end + "handleStart": round(handle_start), + "handleEnd": round(handle_end) } } returning_dict = { "mediaIn": media_in_trimmed, "mediaOut": media_out_trimmed, - "handleStart": handle_start, - "handleEnd": handle_end + "handleStart": round(handle_start), + "handleEnd": round(handle_end) } # add version data only if retime From 43aee1989c197b74ce692b64581b233371aa8003 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 13 May 2022 11:55:03 +0200 Subject: [PATCH 0239/1227] flame: fixing padding if it is higher then needed --- openpype/hosts/flame/api/lib.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index f2f5db184b..6dc7d3d887 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -855,22 +855,24 @@ class MediaInfoFile(object): # add `[` in front to make sure it want capture # shot name with the same number - number_from_path = "[" + self._separate_number(feed_basename, feed_ext) + number_from_path = self._separate_number(feed_basename, feed_ext) + search_number_pattern = "[" + number_from_path # convert to multiple collections _continues_colls = collection.separate() for _coll in _continues_colls: - coll_to_text = self._format_collection(_coll) + coll_to_text = self._format_collection(_coll, len(number_from_path)) self.log.debug("__ coll_to_text: {}".format(coll_to_text)) - if number_from_path in coll_to_text: + if search_number_pattern in coll_to_text: return coll_to_text @staticmethod - def _format_collection(collection): + def _format_collection(collection, padding=None): + padding = padding or collection.padding # if no holes then return collection head = collection.format("{head}") tail = collection.format("{tail}") range_template = "[{{:0{0}d}}-{{:0{0}d}}]".format( - len(str(max(collection.indexes)))) + padding) ranges = range_template.format( min(collection.indexes), max(collection.indexes) From 30123edb55ddbda74259318c6b85e0df19da0cd8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 13 May 2022 20:10:51 +0200 Subject: [PATCH 0240/1227] flame: adding xml element if it is missing and its path possible --- openpype/hosts/flame/api/render_utils.py | 41 +++++++++++++++++++++--- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/api/render_utils.py b/openpype/hosts/flame/api/render_utils.py index 473fb2f985..9957550af9 100644 --- a/openpype/hosts/flame/api/render_utils.py +++ b/openpype/hosts/flame/api/render_utils.py @@ -1,5 +1,8 @@ import os from xml.etree import ElementTree as ET +import openpype.api as openpype + +log = openpype.Logger.get_logger(__name__) def export_clip(export_path, clip, preset_path, **kwargs): @@ -143,10 +146,40 @@ def modify_preset_file(xml_path, staging_dir, data): # change xml following data keys with open(xml_path, "r") as datafile: - tree = ET.parse(datafile) + _root = ET.parse(datafile) + for key, value in data.items(): - for element in tree.findall(".//{}".format(key)): - element.text = str(value) - tree.write(temp_path) + try: + if "/" in key: + if not key.startswith("./"): + key = ".//" + key + + split_key_path = key.split("/") + element_key = split_key_path[-1] + parent_obj_path = "/".join(split_key_path[:-1]) + + parent_obj = _root.find(parent_obj_path) + element_obj = parent_obj.find(element_key) + if not element_obj: + append_element(parent_obj, element_key, value) + else: + finds = _root.findall(".//{}".format(key)) + if not finds: + raise AttributeError + for element in finds: + element.text = str(value) + except AttributeError: + log.warning( + "Cannot create attribute: {}: {}. Skipping".format( + key, value + )) + _root.write(temp_path) return temp_path + + +def append_element(root_element_obj, key, value): + new_element_obj = ET.Element(key) + log.debug("__ new_element_obj: {}".format(new_element_obj)) + new_element_obj.text = str(value) + root_element_obj.insert(0, new_element_obj) From 851df8155f43d4fd6639ee71ab3a3f023e06c7a5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 13 May 2022 20:11:20 +0200 Subject: [PATCH 0241/1227] flame: treat thumbnail as poster frame --- .../publish/extract_subset_resources.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 9ad3b21687..eea575ea88 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -168,10 +168,6 @@ class ExtractSubsetResources(openpype.api.Extractor): # add any xml overrides collected form segment.comment modify_xml_data.update(instance.data["xml_overrides"]) - self.log.debug("__ modify_xml_data: {}".format(pformat( - modify_xml_data - ))) - export_kwargs = {} # validate xml preset file is filled if preset_file == "": @@ -198,11 +194,13 @@ class ExtractSubsetResources(openpype.api.Extractor): preset_dir, preset_file )) - preset_path = opfapi.modify_preset_file( - preset_orig_xml_path, staging_dir, modify_xml_data) - # define kwargs based on preset type if "thumbnail" in unique_name: + modify_xml_data.update({ + "video/posterFrame": True, + "video/useFrameAsPoster": 1, + "namePattern": "__thumbnail" + }) thumb_frame_number = int(in_mark + ( source_duration_handles / 2)) @@ -218,6 +216,12 @@ class ExtractSubsetResources(openpype.api.Extractor): "out_mark": out_mark }) + self.log.debug("__ modify_xml_data: {}".format( + pformat(modify_xml_data) + )) + preset_path = opfapi.modify_preset_file( + preset_orig_xml_path, staging_dir, modify_xml_data) + # get and make export dir paths export_dir_path = str(os.path.join( staging_dir, unique_name From d7454044c92f54392b3c8108a466bb6a5758b830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Marinov?= Date: Fri, 13 May 2022 21:02:44 +0200 Subject: [PATCH 0242/1227] Nuke: add pointcache and animation to loader --- openpype/hosts/nuke/plugins/load/load_model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_model.py b/openpype/hosts/nuke/plugins/load/load_model.py index 9788bb25d2..89b58585ef 100644 --- a/openpype/hosts/nuke/plugins/load/load_model.py +++ b/openpype/hosts/nuke/plugins/load/load_model.py @@ -15,13 +15,13 @@ from openpype.hosts.nuke.api import ( class AlembicModelLoader(load.LoaderPlugin): """ - This will load alembic model into script. + This will load alembic model or anim into script. """ - families = ["model"] + families = ["model","pointcache","animation"] representations = ["abc"] - label = "Load Alembic Model" + label = "Load Alembic Model or Anim" icon = "cube" color = "orange" node_color = "0x4ecd91ff" From dfb3e53743b3bc4229b1c82beb1ff0ecbffc7323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Marinov?= Date: Fri, 13 May 2022 21:07:45 +0200 Subject: [PATCH 0243/1227] fix missing whitespaces --- openpype/hosts/nuke/plugins/load/load_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/load/load_model.py b/openpype/hosts/nuke/plugins/load/load_model.py index 89b58585ef..8aaa7221eb 100644 --- a/openpype/hosts/nuke/plugins/load/load_model.py +++ b/openpype/hosts/nuke/plugins/load/load_model.py @@ -18,7 +18,7 @@ class AlembicModelLoader(load.LoaderPlugin): This will load alembic model or anim into script. """ - families = ["model","pointcache","animation"] + families = ["model", "pointcache", "animation"] representations = ["abc"] label = "Load Alembic Model or Anim" From 631ccf6318916d9c7ad98830dba3007c073e70db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Mon, 16 May 2022 10:24:39 +0200 Subject: [PATCH 0244/1227] Update openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../kitsu/plugins/publish/integrate_kitsu_review.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index 57e0286b00..a036f5f9cc 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -25,12 +25,9 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): return # Add review representations as preview of comment - for representation in [ - r - for r in instance.data.get("representations", []) - if "review" in r.get("tags", []) - ]: - + for representation in instance.data.get("representations", []): + if "review" not in r.get("tags", []): + continue review_path = representation.get("published_path") self.log.debug("Found review at: {}".format(review_path)) From 15aa5709ae18c3cca83e73fef9eaa557684a0a5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Mon, 16 May 2022 10:25:41 +0200 Subject: [PATCH 0245/1227] cleaning --- .../modules/kitsu/plugins/publish/integrate_kitsu_review.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index a036f5f9cc..bf80095225 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -26,8 +26,10 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): # Add review representations as preview of comment for representation in instance.data.get("representations", []): - if "review" not in r.get("tags", []): + # Skip if not tagged as review + if "review" not in representation.get("tags", []): continue + review_path = representation.get("published_path") self.log.debug("Found review at: {}".format(review_path)) From 7d13de97f694ee735ffc4de63749c285c898b7e0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 16 May 2022 11:14:02 +0200 Subject: [PATCH 0246/1227] Flame: add handles including to settings --- openpype/settings/defaults/project_settings/flame.json | 3 ++- .../schemas/projects_schema/schema_project_flame.json | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/flame.json b/openpype/settings/defaults/project_settings/flame.json index dd8c05d460..a7836b9c1f 100644 --- a/openpype/settings/defaults/project_settings/flame.json +++ b/openpype/settings/defaults/project_settings/flame.json @@ -16,7 +16,8 @@ "vSyncOn": false, "workfileFrameStart": 1001, "handleStart": 5, - "handleEnd": 5 + "handleEnd": 5, + "includeHandles": false } }, "publish": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json index ace404b47a..ca62679b3d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json @@ -123,6 +123,11 @@ "type": "number", "key": "handleEnd", "label": "Handle end (tail)" + }, + { + "type": "boolean", + "key": "includeHandles", + "label": "Enable handles including" } ] } From 3895008eae62c2ebba2afe60b0f0320bc7604cc0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 16 May 2022 11:26:14 +0200 Subject: [PATCH 0247/1227] flame: implementing handles include switch --- openpype/hosts/flame/api/plugin.py | 3 +++ openpype/version.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 11108ba49f..efbabb6a55 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -360,6 +360,7 @@ class PublishableClip: driving_layer_default = "" index_from_segment_default = False use_shot_name_default = False + include_handles_default = False def __init__(self, segment, **kwargs): self.rename_index = kwargs["rename_index"] @@ -493,6 +494,8 @@ class PublishableClip: "reviewTrack", {}).get("value") or self.review_track_default self.audio = self.ui_inputs.get( "audio", {}).get("value") or False + self.include_handles = self.ui_inputs.get( + "includeHandles", {}).get("value") or self.include_handles_default # build subset name from layer name if self.subset_name == "[ track name ]": diff --git a/openpype/version.py b/openpype/version.py index 662adf28ca..fe2a90bdd1 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.10.0-nightly.2" +__version__ = "3.10.0-nightly.2-upp220513-1+staging" From 5c4541ca0ae6af1561ecd2e405d840579d4264ae Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 16 May 2022 11:31:06 +0200 Subject: [PATCH 0248/1227] flame: implementing handles including --- openpype/hosts/flame/api/plugin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 11108ba49f..efbabb6a55 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -360,6 +360,7 @@ class PublishableClip: driving_layer_default = "" index_from_segment_default = False use_shot_name_default = False + include_handles_default = False def __init__(self, segment, **kwargs): self.rename_index = kwargs["rename_index"] @@ -493,6 +494,8 @@ class PublishableClip: "reviewTrack", {}).get("value") or self.review_track_default self.audio = self.ui_inputs.get( "audio", {}).get("value") or False + self.include_handles = self.ui_inputs.get( + "includeHandles", {}).get("value") or self.include_handles_default # build subset name from layer name if self.subset_name == "[ track name ]": From 45c445d438106b1ed3d989224778884f7796974b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 16 May 2022 11:33:35 +0200 Subject: [PATCH 0249/1227] removing version change by excident --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index fe2a90bdd1..662adf28ca 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.10.0-nightly.2-upp220513-1+staging" +__version__ = "3.10.0-nightly.2" From e6286166fba934945756357ee08b5d6d89f95cde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Mon, 16 May 2022 11:33:35 +0200 Subject: [PATCH 0250/1227] remove default intent effect on review integration --- .../modules/kitsu/plugins/publish/integrate_kitsu_note.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index 980589365d..9e067a8ecb 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -11,11 +11,6 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): # families = ["kitsu"] def process(self, context): - # Check if work version for user - is_work_version = bool(context.data.get("intent", {}).get("value")) - if is_work_version: - self.log.info("Work version, nothing pushed to Kitsu.") - return # Get comment text body publish_comment = context.data.get("comment") From 5e68cfad86cb3781f3acdc66f681651a11b84945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Marinov?= Date: Mon, 16 May 2022 11:43:44 +0200 Subject: [PATCH 0251/1227] More straightforward label --- openpype/hosts/nuke/plugins/load/load_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/load/load_model.py b/openpype/hosts/nuke/plugins/load/load_model.py index 8aaa7221eb..2f54595cb0 100644 --- a/openpype/hosts/nuke/plugins/load/load_model.py +++ b/openpype/hosts/nuke/plugins/load/load_model.py @@ -21,7 +21,7 @@ class AlembicModelLoader(load.LoaderPlugin): families = ["model", "pointcache", "animation"] representations = ["abc"] - label = "Load Alembic Model or Anim" + label = "Load Alembic" icon = "cube" color = "orange" node_color = "0x4ecd91ff" From 3b15167adb602394ad32dcdbeec7f8f0bbb242a3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 16 May 2022 11:54:29 +0200 Subject: [PATCH 0252/1227] Flame: adding new attribute to ui includeHandles --- openpype/hosts/flame/plugins/create/create_shot_clip.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py index 11c00dab42..fa239ea420 100644 --- a/openpype/hosts/flame/plugins/create/create_shot_clip.py +++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py @@ -268,6 +268,14 @@ class CreateShotClip(opfapi.Creator): "target": "tag", "toolTip": "Handle at end of clip", # noqa "order": 2 + }, + "includeHandles": { + "value": False, + "type": "QCheckBox", + "label": "Include handles", + "target": "tag", + "toolTip": "By default handles are excluded", # noqa + "order": 3 } } } From 3ef846b1623dabfdbcb6cb464c097ec6b19bd473 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 16 May 2022 12:59:55 +0200 Subject: [PATCH 0253/1227] flame: fixing head and tail calculation --- .../publish/collect_timeline_instances.py | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py index 5174f9db48..306d2da203 100644 --- a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py @@ -58,12 +58,16 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): clip_name = clip_data["segment_name"] self.log.debug("clip_name: {}".format(clip_name)) + # get otio clip data + otio_data = self._get_otio_clip_instance_data(clip_data) or {} + self.log.debug("__ otio_data: {}".format(pformat(otio_data))) + # get file path file_path = clip_data["fpath"] first_frame = opfapi.get_frame_from_filename(file_path) or 0 - head, tail = self._get_head_tail(clip_data, first_frame) + head, tail = self._get_head_tail(clip_data, otio_data["otioClip"]) # solve handles length marker_data["handleStart"] = min( @@ -76,6 +80,9 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): # add marker data to instance data inst_data = dict(marker_data.items()) + # add ocio_data to instance data + inst_data.update(otio_data) + asset = marker_data["asset"] subset = marker_data["subset"] @@ -105,13 +112,6 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): task["name"]: {"type": task["type"]} for task in self.add_tasks} }) - - # get otio clip data - otio_data = self._get_otio_clip_instance_data(clip_data) or {} - self.log.debug("__ otio_data: {}".format(pformat(otio_data))) - - # add to instance data - inst_data.update(otio_data) self.log.debug("__ inst_data: {}".format(pformat(inst_data))) # add resolution @@ -236,20 +236,31 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): return split_comments - def _get_head_tail(self, clip_data, first_frame): + def _get_head_tail(self, clip_data, otio_clip): # calculate head and tail with forward compatibility head = clip_data.get("segment_head") tail = clip_data.get("segment_tail") + self.log.debug("__ head: `{}`".format(head)) + self.log.debug("__ tail: `{}`".format(tail)) # HACK: it is here to serve for versions bellow 2021.1 - if not head: - head = int(clip_data["source_in"]) - int(first_frame) - if not tail: - tail = int( - clip_data["source_duration"] - ( - head + clip_data["record_duration"] - ) - ) + if not any([head, tail]): + otio_source_range = otio_clip.source_range + otio_avalable_range = otio_clip.available_range() + range_convert = openpype.lib.otio_range_to_frame_range + src_start, src_end = range_convert(otio_source_range) + av_start, av_end = range_convert(otio_avalable_range) + av_range = av_end - av_start + av_tail = av_range - src_end + + self.log.debug("__ src_start: `{}`".format(src_start)) + self.log.debug("__ src_end: `{}`".format(src_end)) + self.log.debug("__ av_range: `{}`".format(av_range)) + self.log.debug("__ av_tail: `{}`".format(av_tail)) + + head = src_start + tail = av_tail + return head, tail def _get_resolution_to_data(self, data, context): From 05cb2e4bd938ee1c533bb39625f4d3bbb7b2dad4 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 16 May 2022 13:49:39 +0200 Subject: [PATCH 0254/1227] waiting approval status can be set in project settings --- .../plugins/publish/integrate_kitsu_note.py | 9 ++++--- .../defaults/project_settings/kitsu.json | 5 ++++ .../projects_schema/schema_project_kitsu.json | 25 +++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index 9e067a8ecb..876eb6bf29 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -9,6 +9,7 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder label = "Kitsu Note and Status" # families = ["kitsu"] + waiting_for_approval_status = "wfa" def process(self, context): @@ -20,11 +21,13 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): self.log.debug("Comment is `{}`".format(publish_comment)) # Get Waiting for Approval status - kitsu_status = gazu.task.get_task_status_by_short_name("wfa") + kitsu_status = gazu.task.get_task_status_by_short_name( + self.waiting_for_approval_status + ) if not kitsu_status: self.log.info( - "Cannot find 'Waiting For Approval' status." - "The status will not be changed" + "Cannot find {} status. The status will not be " + "changed!".format(self.waiting_for_approval_status) ) kitsu_status = context.data["kitsu_task"].get("task_status") self.log.debug("Kitsu status: {}".format(kitsu_status)) diff --git a/openpype/settings/defaults/project_settings/kitsu.json b/openpype/settings/defaults/project_settings/kitsu.json index a37146e1d2..2f1566d89a 100644 --- a/openpype/settings/defaults/project_settings/kitsu.json +++ b/openpype/settings/defaults/project_settings/kitsu.json @@ -7,5 +7,10 @@ "episode": "E##", "sequence": "SQ##", "shot": "SH##" + }, + "publish": { + "IntegrateKitsuNote": { + "waiting_for_approval_status": "wfa" + } } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json b/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json index 8d71d0ecd6..cffd7ff578 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json @@ -43,6 +43,31 @@ "label": "Shot:" } ] + }, + { + "type": "dict", + "collapsible": true, + "key": "publish", + "label": "Publish plugins", + "children": [ + { + "type": "label", + "label": "Integrator" + }, + { + "type": "dict", + "collapsible": true, + "key": "IntegrateKitsuNote", + "label": "Integrate Kitsu Note", + "children": [ + { + "type": "text", + "key": "waiting_for_approval_status", + "label": "Waiting for Aproval Status:" + } + ] + } + ] } ] } From 83de346d990ac9e86c10cf85548483d5db17e176 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 16 May 2022 14:59:37 +0200 Subject: [PATCH 0255/1227] Implement update, remove, switch + fix vdb sequence support --- .../maya/plugins/load/load_vdb_to_redshift.py | 87 +++++++++++++++++-- 1 file changed, 81 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py b/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py index 70bd9d22e2..7867f49bd1 100644 --- a/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py +++ b/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py @@ -1,11 +1,21 @@ import os from openpype.api import get_project_settings -from openpype.pipeline import load +from openpype.pipeline import ( + load, + get_representation_path +) class LoadVDBtoRedShift(load.LoaderPlugin): - """Load OpenVDB in a Redshift Volume Shape""" + """Load OpenVDB in a Redshift Volume Shape + + Note that the RedshiftVolumeShape is created without a RedshiftVolume + shader assigned. To get the Redshift volume to render correctly assign + a RedshiftVolume shader (in the Hypershade) and set the density, scatter + and emission channels to the channel names of the volumes in the VDB file. + + """ families = ["vdbcache"] representations = ["vdb"] @@ -55,7 +65,7 @@ class LoadVDBtoRedShift(load.LoaderPlugin): # Root group label = "{}:{}".format(namespace, name) - root = cmds.group(name=label, empty=True) + root = cmds.createNode("transform", name=label) settings = get_project_settings(os.environ['AVALON_PROJECT']) colors = settings['maya']['load']['colors'] @@ -74,9 +84,7 @@ class LoadVDBtoRedShift(load.LoaderPlugin): name="{}RVSShape".format(label), parent=root) - cmds.setAttr("{}.fileName".format(volume_node), - self.fname, - type="string") + self._apply_settings(volume_node, path=self.fname) nodes = [root, volume_node] self[:] = nodes @@ -87,3 +95,70 @@ class LoadVDBtoRedShift(load.LoaderPlugin): nodes=nodes, context=context, loader=self.__class__.__name__) + + def _apply_settings(self, + grid_node, + path): + """Apply the settings for the VDB path to the VRayVolumeGrid""" + from maya import cmds + + # The path points to a single file. However the vdb files could be + # either just that single file or a sequence in a folder so we check + # whether it's a sequence + folder = os.path.dirname(path) + files = os.listdir(folder) + is_single_file = len(files) == 1 + if is_single_file: + filename = path + else: + # The path points to the publish .vdb sequence filepath so we + # find the first file in there that ends with .vdb + files = sorted(files) + first = next((x for x in files if x.endswith(".vdb")), None) + if first is None: + raise RuntimeError("Couldn't find first .vdb file of " + "sequence in: %s" % path) + filename = os.path.join(path, first) + + # Tell Redshift whether it should load as sequence or single file + cmds.setAttr(grid_node + ".useFrameExtension", not is_single_file) + + # Set file path + cmds.setAttr(grid_node + ".fileName", filename, type="string") + + def update(self, container, representation): + from maya import cmds + + path = get_representation_path(representation) + + # Find VRayVolumeGrid + members = cmds.sets(container['objectName'], query=True) + grid_nodes = cmds.ls(members, type="RedshiftVolumeShape", long=True) + assert len(grid_nodes) == 1, "This is a bug" + + # Update the VRayVolumeGrid + self._apply_settings(grid_nodes[0], path=path) + + # Update container representation + cmds.setAttr(container["objectName"] + ".representation", + str(representation["_id"]), + type="string") + + def remove(self, container): + from maya import cmds + + # Get all members of the avalon container, ensure they are unlocked + # and delete everything + members = cmds.sets(container['objectName'], query=True) + cmds.lockNode(members, lock=False) + cmds.delete([container['objectName']] + members) + + # Clean up the namespace + try: + cmds.namespace(removeNamespace=container['namespace'], + deleteNamespaceContent=True) + except RuntimeError: + pass + + def switch(self, container, representation): + self.update(container, representation) From 0699906344a8399eeb0f7c10c6b61963ce3eb3e2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 16 May 2022 15:20:57 +0200 Subject: [PATCH 0256/1227] Flame: implementing handles inclusion to publishing --- .../plugins/publish/collect_timeline_instances.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py index 306d2da203..4bca0dcf93 100644 --- a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py @@ -36,6 +36,7 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): for segment in selected_segments: # get openpype tag data marker_data = opfapi.get_segment_data_marker(segment) + self.log.debug("__ marker_data: {}".format( pformat(marker_data))) @@ -75,6 +76,8 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): marker_data["handleEnd"] = min( marker_data["handleEnd"], abs(tail)) + workfile_start = self._set_workfile_start(marker_data) + with_audio = bool(marker_data.pop("audio")) # add marker data to instance data @@ -105,6 +108,7 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): "families": families, "publish": marker_data["publish"], "fps": self.fps, + "workfileFrameStart": workfile_start, "sourceFirstFrame": int(first_frame), "path": file_path, "flameAddTasks": self.add_tasks, @@ -145,6 +149,17 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): if marker_data.get("reviewTrack") is not None: instance.data["reviewAudio"] = True + @staticmethod + def _set_workfile_start(data): + include_handles = data.get("includeHandles") + workfile_start = data["workfileFrameStart"] + handle_start = data["handleStart"] + + if include_handles: + workfile_start += handle_start + + return workfile_start + def _get_comment_attributes(self, segment): comment = segment.comment.get_value() From 0b0a9ca2815251ba423f543e376a38e5805c0aba Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 16 May 2022 15:46:47 +0200 Subject: [PATCH 0257/1227] by default use task status if not specified in config --- .../plugins/publish/integrate_kitsu_note.py | 34 ++++++++++++------- .../defaults/project_settings/kitsu.json | 3 +- .../projects_schema/schema_project_kitsu.json | 9 +++-- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index 876eb6bf29..ae559e660e 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from distutils.log import debug import gazu import pyblish.api @@ -9,7 +10,8 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder label = "Kitsu Note and Status" # families = ["kitsu"] - waiting_for_approval_status = "wfa" + set_status_note = False + note_status_shortname = "wfa" def process(self, context): @@ -20,21 +22,29 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): self.log.debug("Comment is `{}`".format(publish_comment)) - # Get Waiting for Approval status - kitsu_status = gazu.task.get_task_status_by_short_name( - self.waiting_for_approval_status - ) - if not kitsu_status: - self.log.info( - "Cannot find {} status. The status will not be " - "changed!".format(self.waiting_for_approval_status) + # Get note status, by default uses the task status for the note + # if it is not specified in the configuration + note_status = context.data["kitsu_task"]["task_status_id"] + if self.set_status_note: + kitsu_status = gazu.task.get_task_status_by_short_name( + self.note_status_shortname ) - kitsu_status = context.data["kitsu_task"].get("task_status") - self.log.debug("Kitsu status: {}".format(kitsu_status)) + if not kitsu_status: + self.log.info( + "Cannot find {} status. The status will not be " + "changed!".format(self.note_status_shortname) + ) + else: + note_status = kitsu_status + self.log.info("Note Kitsu status: {}".format(note_status)) # Add comment to kitsu task + self.log.debug("Add new note in taks id {}".format( + context.data["kitsu_task"]['id'])) kitsu_comment = gazu.task.add_comment( - context.data["kitsu_task"], kitsu_status, comment=publish_comment + context.data["kitsu_task"], + note_status, + comment=publish_comment ) context.data["kitsu_comment"] = kitsu_comment diff --git a/openpype/settings/defaults/project_settings/kitsu.json b/openpype/settings/defaults/project_settings/kitsu.json index 2f1566d89a..ba02d8d259 100644 --- a/openpype/settings/defaults/project_settings/kitsu.json +++ b/openpype/settings/defaults/project_settings/kitsu.json @@ -10,7 +10,8 @@ }, "publish": { "IntegrateKitsuNote": { - "waiting_for_approval_status": "wfa" + "set_status_note": false, + "note_status_shortname": "wfa" } } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json b/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json index cffd7ff578..014a1b7886 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json @@ -60,10 +60,15 @@ "key": "IntegrateKitsuNote", "label": "Integrate Kitsu Note", "children": [ + { + "type": "boolean", + "key": "set_status_note", + "label": "Set status on note" + }, { "type": "text", - "key": "waiting_for_approval_status", - "label": "Waiting for Aproval Status:" + "key": "note_status_shortname", + "label": "Note shortname" } ] } From 196182f5c5ffdc5bb02d86dedf46fb27b704f64e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 16 May 2022 20:57:51 +0200 Subject: [PATCH 0258/1227] general: expose lib editorial function to lib init --- openpype/lib/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 3c1d71ecd5..8d4e733b7d 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -203,6 +203,7 @@ from .editorial import ( is_overlapping_otio_ranges, otio_range_to_frame_range, otio_range_with_handles, + get_media_range_with_retimes, convert_to_padded_path, trim_media_range, range_from_frames, @@ -382,6 +383,7 @@ __all__ = [ "otio_range_with_handles", "convert_to_padded_path", "otio_range_to_frame_range", + "get_media_range_with_retimes", "trim_media_range", "range_from_frames", "frames_to_secons", From 6ee42b1d19b890ee583f4a10f8efab81ebec043e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 16 May 2022 20:58:20 +0200 Subject: [PATCH 0259/1227] flame: make head and tail with retimed value --- .../publish/collect_timeline_instances.py | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py index 4bca0dcf93..012cb110ec 100644 --- a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py @@ -1,8 +1,8 @@ import re import pyblish -import openpype import openpype.hosts.flame.api as opfapi from openpype.hosts.flame.otio import flame_export +import openpype.lib as oplib # # developer reload modules from pprint import pformat @@ -68,7 +68,12 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): first_frame = opfapi.get_frame_from_filename(file_path) or 0 - head, tail = self._get_head_tail(clip_data, otio_data["otioClip"]) + head, tail = self._get_head_tail( + clip_data, + otio_data["otioClip"], + marker_data["handleStart"], + marker_data["handleEnd"] + ) # solve handles length marker_data["handleStart"] = min( @@ -251,7 +256,7 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): return split_comments - def _get_head_tail(self, clip_data, otio_clip): + def _get_head_tail(self, clip_data, otio_clip, handle_start, handle_end): # calculate head and tail with forward compatibility head = clip_data.get("segment_head") tail = clip_data.get("segment_tail") @@ -260,21 +265,14 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): # HACK: it is here to serve for versions bellow 2021.1 if not any([head, tail]): - otio_source_range = otio_clip.source_range - otio_avalable_range = otio_clip.available_range() - range_convert = openpype.lib.otio_range_to_frame_range - src_start, src_end = range_convert(otio_source_range) - av_start, av_end = range_convert(otio_avalable_range) - av_range = av_end - av_start - av_tail = av_range - src_end + retimed_attributes = oplib.get_media_range_with_retimes( + otio_clip, handle_start, handle_end) + self.log.debug( + ">> retimed_attributes: {}".format(retimed_attributes)) - self.log.debug("__ src_start: `{}`".format(src_start)) - self.log.debug("__ src_end: `{}`".format(src_end)) - self.log.debug("__ av_range: `{}`".format(av_range)) - self.log.debug("__ av_tail: `{}`".format(av_tail)) - - head = src_start - tail = av_tail + # retimed head and tail + head = int(retimed_attributes["handleStart"]) + tail = int(retimed_attributes["handleEnd"]) return head, tail @@ -366,7 +364,7 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): continue if otio_clip.name not in segment.name.get_value(): continue - if openpype.lib.is_overlapping_otio_ranges( + if oplib.is_overlapping_otio_ranges( parent_range, timeline_range, strict=True): # add pypedata marker to otio_clip metadata From 257c58988181a9dcd39bf85e143a08eb686115a7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 16 May 2022 20:58:44 +0200 Subject: [PATCH 0260/1227] flame: change editorial function to lib --- openpype/plugins/publish/collect_otio_subset_resources.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index 7c11462ef0..53d327a51d 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -10,8 +10,7 @@ import os import clique import opentimelineio as otio import pyblish.api -import openpype -from openpype.lib import editorial +import openpype.lib as oplib class CollectOtioSubsetResources(pyblish.api.InstancePlugin): @@ -43,7 +42,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): available_duration = otio_avalable_range.duration.value # get available range trimmed with processed retimes - retimed_attributes = editorial.get_media_range_with_retimes( + retimed_attributes = oplib.get_media_range_with_retimes( otio_clip, handle_start, handle_end) self.log.debug( ">> retimed_attributes: {}".format(retimed_attributes)) @@ -145,7 +144,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): # in case it is file sequence but not new OTIO schema # `ImageSequenceReference` path = media_ref.target_url - collection_data = openpype.lib.make_sequence_collection( + collection_data = oplib.make_sequence_collection( path, trimmed_media_range_h, metadata) self.staging_dir, collection = collection_data From 056b92599ec28afb7de04d5c53021f716f056dfe Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 16 May 2022 21:09:00 +0200 Subject: [PATCH 0261/1227] global: fixing false editorial namespace --- openpype/plugins/publish/collect_otio_subset_resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index 53d327a51d..78e2a6428c 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -64,7 +64,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): a_frame_end_h = media_out + handle_end # create trimmed otio time range - trimmed_media_range_h = editorial.range_from_frames( + trimmed_media_range_h = oplib.range_from_frames( a_frame_start_h, (a_frame_end_h - a_frame_start_h + 1), media_fps ) From d636a4144dd4cfff4517dd6b39f6d9524160ed0d Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 17 May 2022 12:35:06 +0300 Subject: [PATCH 0262/1227] Replace plugin name with more descriptive one. --- openpype/plugins/publish/extract_jpeg_exr.py | 2 +- openpype/settings/defaults/project_settings/global.json | 2 +- .../projects_schema/schemas/schema_global_publish.json | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index ae29f8b95b..84cdd186dc 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -15,7 +15,7 @@ from openpype.lib import ( import shutil -class ExtractJpegEXR(pyblish.api.InstancePlugin): +class ExtractThumbnail(pyblish.api.InstancePlugin): """Create jpg thumbnail from sequence using ffmpeg""" label = "Extract Jpeg EXR" diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 7b223798f1..cedd0eed99 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -33,7 +33,7 @@ "enabled": false, "profiles": [] }, - "ExtractJpegEXR": { + "ExtractThumbnail": { "enabled": true, "ffmpeg_args": { "input": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 061874e31c..a3cbf0cfcd 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -126,8 +126,8 @@ "type": "dict", "collapsible": true, "checkbox_key": "enabled", - "key": "ExtractJpegEXR", - "label": "ExtractJpegEXR", + "key": "ExtractThumbnail", + "label": "ExtractThumbnail", "is_group": true, "children": [ { From 78c70819156fc0ad6896619abc8b4d2cc017e589 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 17 May 2022 12:38:04 +0300 Subject: [PATCH 0263/1227] Change label name. --- openpype/plugins/publish/extract_jpeg_exr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 84cdd186dc..11dfca8eb2 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -18,7 +18,7 @@ import shutil class ExtractThumbnail(pyblish.api.InstancePlugin): """Create jpg thumbnail from sequence using ffmpeg""" - label = "Extract Jpeg EXR" + label = "Extract Thumbnail" order = pyblish.api.ExtractorOrder families = [ "imagesequence", "render", "render2d", From 5059c0cedff88d33f7c0044f0020fa03d1cdca48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Hector?= Date: Tue, 17 May 2022 11:43:28 +0200 Subject: [PATCH 0264/1227] Update openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Félix David --- .../modules/kitsu/plugins/publish/integrate_kitsu_note.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index ae559e660e..78c5170856 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -29,14 +29,14 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): kitsu_status = gazu.task.get_task_status_by_short_name( self.note_status_shortname ) - if not kitsu_status: + if kitsu_status: + note_status = kitsu_status + self.log.info("Note Kitsu status: {}".format(note_status)) + else: self.log.info( "Cannot find {} status. The status will not be " "changed!".format(self.note_status_shortname) ) - else: - note_status = kitsu_status - self.log.info("Note Kitsu status: {}".format(note_status)) # Add comment to kitsu task self.log.debug("Add new note in taks id {}".format( From 667cff319d70ea699ded5d009872588f5d5ffee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 17 May 2022 11:49:54 +0200 Subject: [PATCH 0265/1227] black --- .../kitsu/plugins/publish/integrate_kitsu_note.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index 78c5170856..3cd1f450ca 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -22,7 +22,7 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): self.log.debug("Comment is `{}`".format(publish_comment)) - # Get note status, by default uses the task status for the note + # Get note status, by default uses the task status for the note # if it is not specified in the configuration note_status = context.data["kitsu_task"]["task_status_id"] if self.set_status_note: @@ -39,12 +39,13 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): ) # Add comment to kitsu task - self.log.debug("Add new note in taks id {}".format( - context.data["kitsu_task"]['id'])) + self.log.debug( + "Add new note in taks id {}".format( + context.data["kitsu_task"]["id"] + ) + ) kitsu_comment = gazu.task.add_comment( - context.data["kitsu_task"], - note_status, - comment=publish_comment + context.data["kitsu_task"], note_status, comment=publish_comment ) context.data["kitsu_comment"] = kitsu_comment From 6976546505590a59072095f675778c9e8a71fe03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 17 May 2022 11:51:58 +0200 Subject: [PATCH 0266/1227] cleaning --- openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index 3cd1f450ca..ea98e0b7cc 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from distutils.log import debug import gazu import pyblish.api From ea00dc0c6a41c21d7744b996c40a4eca84e1dcb8 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 17 May 2022 17:07:33 +0200 Subject: [PATCH 0267/1227] fix support for plugin location --- openpype/hosts/unreal/__init__.py | 3 ++- openpype/hosts/unreal/hooks/pre_workfile_preparation.py | 8 +++++++- openpype/hosts/unreal/lib.py | 6 +++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/unreal/__init__.py b/openpype/hosts/unreal/__init__.py index 533f315df3..bedf5a29f7 100644 --- a/openpype/hosts/unreal/__init__.py +++ b/openpype/hosts/unreal/__init__.py @@ -9,7 +9,8 @@ def add_implementation_envs(env, _app): os.path.dirname(os.path.abspath(openpype.hosts.__file__)), "unreal", "integration" ) - env["OPENPYPE_UNREAL_PLUGIN"] = unreal_plugin_path + if not env.get("OPENPYPE_UNREAL_PLUGIN"): + env["OPENPYPE_UNREAL_PLUGIN"] = unreal_plugin_path # Set default environments if are not set via settings defaults = { diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index f07e96551c..fa0562a3a0 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -25,7 +25,7 @@ class UnrealPrelaunchHook(PreLaunchHook): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.signature = "( {} )".format(self.__class__.__name__) + self.signature = f"( {self.__class__.__name__} )" def _get_work_filename(self): # Use last workfile if was found @@ -99,6 +99,7 @@ class UnrealPrelaunchHook(PreLaunchHook): f"character ({unreal_project_name}). Appending 'P'" )) unreal_project_name = f"P{unreal_project_name}" + unreal_project_filename = f'{unreal_project_name}.uproject' project_path = Path(os.path.join(workdir, unreal_project_name)) @@ -138,6 +139,11 @@ class UnrealPrelaunchHook(PreLaunchHook): )) # Set "OPENPYPE_UNREAL_PLUGIN" to current process environment for # execution of `create_unreal_project` + if self.launch_context.env.get("OPENPYPE_UNREAL_PLUGIN"): + self.log.info(( + f"{self.signature} using OpenPype plugin from " + f"{self.launch_context.env.get('OPENPYPE_UNREAL_PLUGIN')}" + )) env_key = "OPENPYPE_UNREAL_PLUGIN" if self.launch_context.env.get(env_key): os.environ[env_key] = self.launch_context.env[env_key] diff --git a/openpype/hosts/unreal/lib.py b/openpype/hosts/unreal/lib.py index 906002b38f..fdf3acb37b 100644 --- a/openpype/hosts/unreal/lib.py +++ b/openpype/hosts/unreal/lib.py @@ -280,7 +280,7 @@ def create_unreal_project(project_name: str, python_path = None if platform.system().lower() == "windows": python_path = engine_path / ("Engine/Binaries/ThirdParty/" - "Python3/Win64/pythonw.exe") + "Python3/Win64/python.exe") if platform.system().lower() == "linux": python_path = engine_path / ("Engine/Binaries/ThirdParty/" @@ -294,8 +294,8 @@ def create_unreal_project(project_name: str, raise NotImplementedError("Unsupported platform") if not python_path.exists(): raise RuntimeError(f"Unreal Python not found at {python_path}") - subprocess.run( - [python_path.as_posix(), "-m", "pip", "install", "pyside2"]) + out = subprocess.check_call( + [python_path.as_posix(), "-m", "pip", "install", "--user", "pyside2"]) if dev_mode or preset["dev_mode"]: _prepare_cpp_project(project_file, engine_path) From 55cdecd95137a197664c9ed6307d746d2d7e95b6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:24:00 +0200 Subject: [PATCH 0268/1227] Implement support for Redshift Proxy export in Houdini Tested with Houdini 19.0.589 + Redshift 3.5.01 --- openpype/hosts/houdini/api/lib.py | 2 + .../plugins/create/create_redshift_proxy.py | 49 +++++++++++++++++++ .../houdini/plugins/publish/collect_frames.py | 2 +- .../plugins/publish/collect_output_node.py | 3 ++ .../plugins/publish/extract_redshift_proxy.py | 48 ++++++++++++++++++ 5 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 openpype/hosts/houdini/plugins/create/create_redshift_proxy.py create mode 100644 openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 603519069a..96ca019f8f 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -130,6 +130,8 @@ def get_output_parameter(node): elif node_type == "arnold": if node.evalParm("ar_ass_export_enable"): return node.parm("ar_ass_file") + elif node_type == "Redshift_Proxy_Output": + return node.parm("RS_archive_file") raise TypeError("Node type '%s' not supported" % node_type) diff --git a/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py b/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py new file mode 100644 index 0000000000..52c81240fa --- /dev/null +++ b/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py @@ -0,0 +1,49 @@ +import hou +from openpype.hosts.houdini.api import plugin + + +class CreateRedshiftProxy(plugin.Creator): + """Redshift Proxy""" + + label = "Redshift Proxy" + family = "redshiftproxy" + icon = "magic" + + def __init__(self, *args, **kwargs): + super(CreateRedshiftProxy, self).__init__(*args, **kwargs) + + # Remove the active, we are checking the bypass flag of the nodes + self.data.pop("active", None) + + # Redshift provides a `Redshift_Proxy_Output` node type which shows + # a limited set of parameters by default and is set to extract a + # Redshift Proxy. However when "imprinting" extra parameters needed + # for OpenPype it starts showing all its parameters again. It's unclear + # why this happens. + # TODO: Somehow enforce so that it only shows the original limited + # attributes of the Redshift_Proxy_Output node type + self.data.update({"node_type": "Redshift_Proxy_Output"}) + + def _process(self, instance): + """Creator main entry point. + + Args: + instance (hou.Node): Created Houdini instance. + + """ + parms = { + "RS_archive_file": '$HIP/pyblish/`chs("subset")`.$F4.rs', + } + + if self.nodes: + node = self.nodes[0] + path = node.path() + parms["RS_archive_sopPath"] = path + + instance.setParms(parms) + + # Lock some Avalon attributes + to_lock = ["family", "id"] + for name in to_lock: + parm = instance.parm(name) + parm.lock(True) diff --git a/openpype/hosts/houdini/plugins/publish/collect_frames.py b/openpype/hosts/houdini/plugins/publish/collect_frames.py index fac40b4d2b..9bd43d8a09 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_frames.py +++ b/openpype/hosts/houdini/plugins/publish/collect_frames.py @@ -20,7 +20,7 @@ class CollectFrames(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder label = "Collect Frames" - families = ["vdbcache", "imagesequence", "ass"] + families = ["vdbcache", "imagesequence", "ass", "redshiftproxy"] def process(self, instance): diff --git a/openpype/hosts/houdini/plugins/publish/collect_output_node.py b/openpype/hosts/houdini/plugins/publish/collect_output_node.py index 938ee81cc3..0130c0a8da 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/collect_output_node.py @@ -12,6 +12,7 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin): "imagesequence", "usd", "usdrender", + "redshiftproxy" ] hosts = ["houdini"] @@ -54,6 +55,8 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin): else: out_node = node.parm("loppath").evalAsNode() + elif node_type == "Redshift_Proxy_Output": + out_node = node.parm("RS_archive_sopPath").evalAsNode() else: raise ValueError( "ROP node type '%s' is" " not supported." % node_type diff --git a/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py new file mode 100644 index 0000000000..eb7e0d5677 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py @@ -0,0 +1,48 @@ +import os + +import pyblish.api +import openpype.api +from openpype.hosts.houdini.api.lib import render_rop + + +class ExtractRedshiftProxy(openpype.api.Extractor): + + order = pyblish.api.ExtractorOrder + 0.1 + label = "Extract Redshift Proxy" + families = ["redshiftproxy"] + hosts = ["houdini"] + + def process(self, instance): + + ropnode = instance[0] + + # Get the filename from the filename parameter + # `.evalParm(parameter)` will make sure all tokens are resolved + output = ropnode.evalParm("RS_archive_file") + staging_dir = os.path.normpath(os.path.dirname(output)) + instance.data["stagingDir"] = staging_dir + file_name = os.path.basename(output) + + self.log.info("Writing Redshift Proxy '%s' to '%s'" % (file_name, + staging_dir)) + + render_rop(ropnode) + + output = instance.data["frames"] + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + "name": "rs", + "ext": "rs", + "files": output, + "stagingDir": staging_dir, + } + + # A single frame may also be rendered without start/end frame. + if "frameStart" in instance.data and "frameEnd" in instance.data: + representation["frameStart"] = instance.data["frameStart"] + representation["frameEnd"] = instance.data["frameEnd"] + + instance.data["representations"].append(representation) \ No newline at end of file From be1453b4e7971ad3f20f87de0167865b38746340 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:27:40 +0200 Subject: [PATCH 0269/1227] Fix new line --- .../hosts/houdini/plugins/publish/extract_redshift_proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py index eb7e0d5677..c754d60c59 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py @@ -45,4 +45,4 @@ class ExtractRedshiftProxy(openpype.api.Extractor): representation["frameStart"] = instance.data["frameStart"] representation["frameEnd"] = instance.data["frameEnd"] - instance.data["representations"].append(representation) \ No newline at end of file + instance.data["representations"].append(representation) From 0f84e7d92a661f99e347bedb110928f014b37eec Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:28:40 +0200 Subject: [PATCH 0270/1227] Remove unused import --- openpype/hosts/houdini/plugins/create/create_redshift_proxy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py b/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py index 52c81240fa..da4d80bf2b 100644 --- a/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py +++ b/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py @@ -1,4 +1,3 @@ -import hou from openpype.hosts.houdini.api import plugin From 606ef6415d229e7ee29f2776749fc33b288e0dd8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:42:55 +0200 Subject: [PATCH 0271/1227] Fix popping of `handles` --- openpype/hosts/maya/plugins/create/create_yeti_cache.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/create/create_yeti_cache.py b/openpype/hosts/maya/plugins/create/create_yeti_cache.py index 86e13b95b2..e8c3203f21 100644 --- a/openpype/hosts/maya/plugins/create/create_yeti_cache.py +++ b/openpype/hosts/maya/plugins/create/create_yeti_cache.py @@ -22,7 +22,8 @@ class CreateYetiCache(plugin.Creator): # Add animation data without step and handles anim_data = lib.collect_animation_data() anim_data.pop("step") - anim_data.pop("handles") + anim_data.pop("handleStart") + anim_data.pop("handleEnd") self.data.update(anim_data) # Add samples From e4d54aaa7a8304b426b67b707565451ce5e5599d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:44:27 +0200 Subject: [PATCH 0272/1227] Fix invalid refactored usage --- openpype/hosts/maya/plugins/publish/extract_yeti_rig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py index d12567a55a..6b5054a198 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py @@ -167,7 +167,7 @@ class ExtractYetiRig(openpype.api.Extractor): resources = instance.data.get("resources", {}) with disconnect_plugs(settings, members): with yetigraph_attribute_values(resources_dir, resources): - with maya.attribute_values(attr_value): + with lib.attribute_values(attr_value): cmds.select(nodes, noExpand=True) cmds.file(maya_path, force=True, From 0f97c9f3d388a8973dce13e68fb9d263c0441b48 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:50:41 +0200 Subject: [PATCH 0273/1227] Time values are required for exporting without errors --- openpype/hosts/maya/plugins/publish/extract_yeti_cache.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py b/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py index 0d85708789..b0a60b77f4 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py @@ -29,9 +29,9 @@ class ExtractYetiCache(openpype.api.Extractor): data_file = os.path.join(dirname, "yeti.fursettings") # Collect information for writing cache - start_frame = instance.data.get("frameStartHandle") - end_frame = instance.data.get("frameEndHandle") - preroll = instance.data.get("preroll") + start_frame = instance.data["frameStartHandle"] + end_frame = instance.data["frameEndHandle"] + preroll = instance.data["preroll"] if preroll > 0: start_frame -= preroll From 6fb9bb1558401273e2437e3e50a18877c296851e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:54:41 +0200 Subject: [PATCH 0274/1227] Allow empty input_SET --- openpype/hosts/maya/plugins/publish/extract_yeti_rig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py index 6b5054a198..f981c4fe50 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py @@ -157,7 +157,7 @@ class ExtractYetiRig(openpype.api.Extractor): input_set = next(i for i in instance if i == "input_SET") # Get all items - set_members = cmds.sets(input_set, query=True) + set_members = cmds.sets(input_set, query=True) or [] set_members += cmds.listRelatives(set_members, allDescendents=True, fullPath=True) or [] From 2a2dbd243408f4eed8863fd86967a39080b4931d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:56:45 +0200 Subject: [PATCH 0275/1227] Force required frame values for a single frame cache extract for `yetiRig` family. --- .../hosts/maya/plugins/publish/collect_yeti_rig.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_yeti_rig.py b/openpype/hosts/maya/plugins/publish/collect_yeti_rig.py index 029432223b..bc15edd9e0 100644 --- a/openpype/hosts/maya/plugins/publish/collect_yeti_rig.py +++ b/openpype/hosts/maya/plugins/publish/collect_yeti_rig.py @@ -43,11 +43,12 @@ class CollectYetiRig(pyblish.api.InstancePlugin): instance.data["resources"] = yeti_resources - # Force frame range for export - instance.data["frameStart"] = cmds.playbackOptions( - query=True, animationStartTime=True) - instance.data["frameEnd"] = cmds.playbackOptions( - query=True, animationStartTime=True) + # Force frame range for yeti cache export for the rig + start = cmds.playbackOptions(query=True, animationStartTime=True) + for key in ["frameStart", "frameEnd", + "frameStartHandle", "frameEndHandle"]: + instance.data[key] = start + instance.data["preroll"] = 0 def collect_input_connections(self, instance): """Collect the inputs for all nodes in the input_SET""" From 8e1cc8cef675b909223db7c5d5d39d5aac7deb13 Mon Sep 17 00:00:00 2001 From: DMO Date: Wed, 18 May 2022 16:58:59 +0900 Subject: [PATCH 0276/1227] Added mvLook publish. This extracts the look information from the UsdCompoundShape - it generates "OP look" compatible data structures and lets the rest of the OP publish as normal. It also generates a .usda file with the overrides containing material assignments as needed. --- .../plugins/create/create_multiverse_look.py | 14 + .../publish/collect_multiverse_look.py | 323 ++++++++++++++++++ .../maya/plugins/publish/extract_look.py | 2 +- .../publish/extract_multiverse_look.py | 134 ++++++++ .../plugins/publish/collect_resources_path.py | 1 + openpype/plugins/publish/integrate_new.py | 1 + 6 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 openpype/hosts/maya/plugins/create/create_multiverse_look.py create mode 100644 openpype/hosts/maya/plugins/publish/collect_multiverse_look.py create mode 100644 openpype/hosts/maya/plugins/publish/extract_multiverse_look.py diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_look.py b/openpype/hosts/maya/plugins/create/create_multiverse_look.py new file mode 100644 index 0000000000..b2b9b5bb29 --- /dev/null +++ b/openpype/hosts/maya/plugins/create/create_multiverse_look.py @@ -0,0 +1,14 @@ +from openpype.hosts.maya.api import plugin, lib + + +class CreateMultiverseLook(plugin.Creator): + """Create Multiverse Look""" + + name = "mvLook" + label = "Multiverse Look" + family = "mvLook" + icon = "cubes" + + def __init__(self, *args, **kwargs): + super(CreateMultiverseLook, self).__init__(*args, **kwargs) + self.data["fileFormat"] = ["usda", "usd"] diff --git a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py new file mode 100644 index 0000000000..9c1569e216 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py @@ -0,0 +1,323 @@ +import glob +import os +import re + +from maya import cmds +import pyblish.api +from openpype.hosts.maya.api import lib + +def get_look_attrs(node): + """Returns attributes of a node that are important for the look. + + These are the "changed" attributes (those that have edits applied + in the current scene). + + Returns: + list: Attribute names to extract + + """ + # When referenced get only attributes that are "changed since file open" + # which includes any reference edits, otherwise take *all* user defined + # attributes + is_referenced = cmds.referenceQuery(node, isNodeReferenced=True) + result = cmds.listAttr(node, userDefined=True, + changedSinceFileOpen=is_referenced) or [] + + # `cbId` is added when a scene is saved, ignore by default + if "cbId" in result: + result.remove("cbId") + + # For shapes allow render stat changes + if cmds.objectType(node, isAType="shape"): + attrs = cmds.listAttr(node, changedSinceFileOpen=True) or [] + for attr in attrs: + if attr in SHAPE_ATTRS: + result.append(attr) + elif attr.startswith('ai'): + result.append(attr) + + return result + + +def node_uses_image_sequence(node): + """Return whether file node uses an image sequence or single image. + + Determine if a node uses an image sequence or just a single image, + not always obvious from its file path alone. + + Args: + node (str): Name of the Maya node + + Returns: + bool: True if node uses an image sequence + + """ + + # useFrameExtension indicates an explicit image sequence + node_path = get_file_node_path(node).lower() + + # The following tokens imply a sequence + patterns = ["", "", "", "u_v", ".tif will return as /path/to/texture.*.tif. + + Args: + path (str): the image sequence path + + Returns: + str: Return glob string that matches the filename pattern. + + """ + + if path is None: + return path + + # If any of the patterns, convert the pattern + patterns = { + "": "", + "": "", + "": "", + "#": "#", + "u_v": "|", + "", + "": "" + } + + lower = path.lower() + has_pattern = False + for pattern, regex_pattern in patterns.items(): + if pattern in lower: + path = re.sub(regex_pattern, "*", path, flags=re.IGNORECASE) + has_pattern = True + + if has_pattern: + return path + + base = os.path.basename(path) + matches = list(re.finditer(r'\d+', base)) + if matches: + match = matches[-1] + new_base = '{0}*{1}'.format(base[:match.start()], + base[match.end():]) + head = os.path.dirname(path) + return os.path.join(head, new_base) + else: + return path + + +def get_file_node_path(node): + """Get the file path used by a Maya file node. + + Args: + node (str): Name of the Maya file node + + Returns: + str: the file path in use + + """ + # if the path appears to be sequence, use computedFileTextureNamePattern, + # this preserves the <> tag + if cmds.attributeQuery('computedFileTextureNamePattern', + node=node, + exists=True): + plug = '{0}.computedFileTextureNamePattern'.format(node) + texture_pattern = cmds.getAttr(plug) + + patterns = ["", + "", + "u_v", + "", + ""] + lower = texture_pattern.lower() + if any(pattern in lower for pattern in patterns): + return texture_pattern + + if cmds.nodeType(node) == 'aiImage': + return cmds.getAttr('{0}.filename'.format(node)) + if cmds.nodeType(node) == 'RedshiftNormalMap': + return cmds.getAttr('{}.tex0'.format(node)) + + # otherwise use fileTextureName + return cmds.getAttr('{0}.fileTextureName'.format(node)) + + +def get_file_node_files(node): + """Return the file paths related to the file node + + Note: + Will only return existing files. Returns an empty list + if not valid existing files are linked. + + Returns: + list: List of full file paths. + + """ + + path = get_file_node_path(node) + path = cmds.workspace(expandName=path) + if node_uses_image_sequence(node): + glob_pattern = seq_to_glob(path) + return glob.glob(glob_pattern) + elif os.path.exists(path): + return [path] + else: + return [] + + +class CollectMultiverseLookData(pyblish.api.InstancePlugin): + """Collect Multiverse Look + + """ + + order = pyblish.api.CollectorOrder + 0.2 + label = 'Collect Multiverse Look' + families = ["mvLook"] + + def process(self, instance): + # Load plugin first + cmds.loadPlugin("MultiverseForMaya", quiet=True) + import multiverse + + self.log.info("Processing mvLook for '{}' / '{}'".format( + instance, instance.data['name'])) + + nodes = set() + for node in instance: + # We want only mvUsdCompoundShape nodes. + nodes_of_interest = cmds.ls(node, + dag=True, + shapes=False, + type="mvUsdCompoundShape", + noIntermediate=True, + long=True) + nodes.update(nodes_of_interest) + + files = [] + sets = {} + instance.data["resources"] = [] + + for node in nodes: + self.log.info("Getting resources for '{}'".format(node)) + + # We know what nodes + overrides = multiverse.ListMaterialOverridePrims(node) + for override in overrides: + matOver = multiverse.GetMaterialOverride(node, override) + + if isinstance(matOver, multiverse.MaterialSourceShadingGroup): + shadingGroup = matOver.shadingGroupName + self.log.debug("ShadingGroup = '{}'".format(shadingGroup)) + sets[shadingGroup] = {"uuid": lib.get_id(shadingGroup), "members": list()} + + history = cmds.listHistory(shadingGroup) + files = cmds.ls(history, type="file", long=True) + + for f in files: + resources = self.collect_resource(f) + instance.data["resources"].append(resources) + + elif isinstance(matOver, multiverse.MaterialSourceUsdPath): + # TODO: Handle this later. + pass + else: + # TODO: What to do here? + pass + + # Store data on the instance + instance.data["lookData"] = { + "attributes": [], + "relationships": sets + } + + self.log.info("-------------------------") + self.log.info("Instance: {},{}".format(type(instance),instance)) + self.log.info("Instance.data: {},{}".format(type(instance.data),instance.data)) + for k in instance.data.keys(): + v = instance.data[k] + self.log.info(" data: {},{}".format(k,v)) + self.log.info("-------------------------") + + + def collect_resource(self, node): + """Collect the link to the file(s) used (resource) + Args: + node (str): name of the node + + Returns: + dict + """ + + self.log.debug("processing: {}".format(node)) + if cmds.nodeType(node) not in ["file", "aiImage", "RedshiftNormalMap"]: + self.log.error( + "Unsupported file node: {}".format(cmds.nodeType(node))) + raise AssertionError("Unsupported file node") + + if cmds.nodeType(node) == 'file': + self.log.debug(" - file node") + attribute = "{}.fileTextureName".format(node) + computed_attribute = "{}.computedFileTextureNamePattern".format(node) + elif cmds.nodeType(node) == 'aiImage': + self.log.debug("aiImage node") + attribute = "{}.filename".format(node) + computed_attribute = attribute + elif cmds.nodeType(node) == 'RedshiftNormalMap': + self.log.debug("RedshiftNormalMap node") + attribute = "{}.tex0".format(node) + computed_attribute = attribute + + source = cmds.getAttr(attribute) + self.log.info(" - file source: {}".format(source)) + color_space_attr = "{}.colorSpace".format(node) + try: + color_space = cmds.getAttr(color_space_attr) + except ValueError: + # node doesn't have colorspace attribute + color_space = "Raw" + # Compare with the computed file path, e.g. the one with the + # pattern in it, to generate some logging information about this + # difference + # computed_attribute = "{}.computedFileTextureNamePattern".format(node) + computed_source = cmds.getAttr(computed_attribute) + if source != computed_source: + self.log.debug("Detected computed file pattern difference " + "from original pattern: {0} " + "({1} -> {2})".format(node, + source, + computed_source)) + + # We replace backslashes with forward slashes because V-Ray + # can't handle the UDIM files with the backslashes in the + # paths as the computed patterns + source = source.replace("\\", "/") + + files = get_file_node_files(node) + if len(files) == 0: + self.log.error("No valid files found from node `%s`" % node) + + self.log.info("collection of resource done:") + self.log.info(" - node: {}".format(node)) + self.log.info(" - attribute: {}".format(attribute)) + self.log.info(" - source: {}".format(source)) + self.log.info(" - file: {}".format(files)) + self.log.info(" - color space: {}".format(color_space)) + + # Define the resource + return {"node": node, + "attribute": attribute, + "source": source, # required for resources + "files": files, + "color_space": color_space} # required for resources \ No newline at end of file diff --git a/openpype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py index 881705b92c..c427eacd98 100644 --- a/openpype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -146,7 +146,7 @@ class ExtractLook(openpype.api.Extractor): label = "Extract Look (Maya Scene + JSON)" hosts = ["maya"] - families = ["look"] + families = ["look","mvLook"] order = pyblish.api.ExtractorOrder + 0.2 scene_type = "ma" look_data_type = "json" diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py new file mode 100644 index 0000000000..34c72f0915 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py @@ -0,0 +1,134 @@ +import os +import six + +from maya import cmds + +import openpype.api +from openpype.hosts.maya.api.lib import maintained_selection + + +class ExtractMultiverseLook(openpype.api.Extractor): + """Extractor for Multiverse USD look data into a Maya Scene.""" + + label = "Extract Multiverse USD Look" + hosts = ["maya"] + families = ["mvLook"] + scene_type = "usda" + file_formats = ["usda", "usd"] + + @property + def options(self): + """Overridable options for Multiverse USD Export + + Given in the following format + - {NAME: EXPECTED TYPE} + + If the overridden option's type does not match, + the option is not included and a warning is logged. + + """ + + return { + "writeAll": bool, + "writeTransforms": bool, + "writeVisibility": bool, + "writeAttributes": bool, + "writeMaterials": bool, + "writeVariants": bool, + "writeVariantsDefinition": bool, + "writeActiveState": bool, + "writeNamespaces": bool, + "numTimeSamples": int, + "timeSamplesSpan": float + } + + @property + def default_options(self): + """The default options for Multiverse USD extraction.""" + + return { + "writeAll": False, + "writeTransforms": False, + "writeVisibility": False, + "writeAttributes": False, + "writeMaterials": True, + "writeVariants": False, + "writeVariantsDefinition": False, + "writeActiveState": False, + "writeNamespaces": False, + "numTimeSamples": 1, + "timeSamplesSpan": 0.0 + } + + def get_file_format(self, instance): + fileFormat = instance.data["fileFormat"] + if fileFormat in range(len(self.file_formats)): + self.scene_type = self.file_formats[fileFormat] + + def process(self, instance): + # Load plugin first + cmds.loadPlugin("MultiverseForMaya", quiet=True) + + # Define output file path + staging_dir = self.staging_dir(instance) + self.get_file_format(instance) + file_name = "{0}.{1}".format(instance.name, self.scene_type) + file_path = os.path.join(staging_dir, file_name) + file_path = file_path.replace('\\', '/') + + # Parse export options + options = self.default_options + self.log.info("Export options: {0}".format(options)) + + # Perform extraction + self.log.info("Performing extraction ...") + + with maintained_selection(): + members = instance.data("setMembers") + members = cmds.ls(members, + dag=True, + shapes=False, + type="mvUsdCompoundShape", + noIntermediate=True, + long=True) + self.log.info('Collected object {}'.format(members)) + if len(members) > 1: + self.log.error('More than one member: {}'.format(members)) + + import multiverse + + over_write_opts = multiverse.OverridesWriteOptions() + options_discard_keys = { + "numTimeSamples", + "timeSamplesSpan", + "frameStart", + "frameEnd", + "handleStart", + "handleEnd", + "step", + "fps" + } + for key, value in options.items(): + if key in options_discard_keys: + continue + setattr(over_write_opts, key, value) + + for member in members: + # @TODO: Make sure there is only one here. + + self.log.debug("Writing Override for '{}'".format(member)) + multiverse.WriteOverrides(file_path, member, over_write_opts) + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': self.scene_type, + 'ext': self.scene_type, + 'files': file_name, + 'stagingDir': staging_dir + } + instance.data["representations"].append(representation) + + self.log.info("Extracted instance {} to {}".format( + instance.name, file_path)) diff --git a/openpype/plugins/publish/collect_resources_path.py b/openpype/plugins/publish/collect_resources_path.py index 89df031fb0..8bdf70b529 100644 --- a/openpype/plugins/publish/collect_resources_path.py +++ b/openpype/plugins/publish/collect_resources_path.py @@ -41,6 +41,7 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): "rig", "plate", "look", + "mvLook", "yetiRig", "yeticache", "nukenodes", diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index bf13a4050e..4712c2e6bb 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -106,6 +106,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "effect", "xgen", "hda", + "mvLook", "usd", "staticMesh", "skeletalMesh", From b62627bf558e09f7e8e2b127a82486517a608488 Mon Sep 17 00:00:00 2001 From: DMO Date: Wed, 18 May 2022 17:08:17 +0900 Subject: [PATCH 0277/1227] Change the default of composition/override to usda. --- .../hosts/maya/plugins/create/create_multiverse_usd_comp.py | 3 ++- .../hosts/maya/plugins/create/create_multiverse_usd_over.py | 3 ++- .../hosts/maya/plugins/publish/extract_multiverse_usd_comp.py | 3 ++- .../hosts/maya/plugins/publish/extract_multiverse_usd_over.py | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py index accdffad46..0a8219938b 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py @@ -15,7 +15,8 @@ class CreateMultiverseUsdComp(plugin.Creator): # Add animation data first, since it maintains order. self.data.update(lib.collect_animation_data(True)) - self.data["fileFormat"] = ["usd", "usda"] + # Order of `fileFormat` must match extract_multiverse_usd_comp.py + self.data["fileFormat"] = ["usda", "usd"] self.data["stripNamespaces"] = False self.data["mergeTransformAndShape"] = False self.data["flattenContent"] = False diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py index 25892f68bb..9477cd7fed 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd_over.py @@ -15,7 +15,8 @@ class CreateMultiverseUsdOver(plugin.Creator): # Add animation data first, since it maintains order. self.data.update(lib.collect_animation_data(True)) - self.data["fileFormat"] = ["usd", "usda"] + # Order of `fileFormat` must match extract_multiverse_usd_over.py + self.data["fileFormat"] = ["usda", "usd"] self.data["writeAll"] = False self.data["writeTransforms"] = True self.data["writeVisibility"] = True diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py index c686b2a600..695ea9a33f 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py @@ -13,7 +13,8 @@ class ExtractMultiverseUsdComposition(openpype.api.Extractor): hosts = ["maya"] families = ["usdComposition"] scene_type = "usd" - file_formats = ["usd", "usda"] + # Order of `fileFormat` must match create_multiverse_usd_comp.py + file_formats = ["usda", "usd"] @property def options(self): diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py index be0a7be52f..3b101ab6cf 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py @@ -13,7 +13,8 @@ class ExtractMultiverseUsdOverride(openpype.api.Extractor): hosts = ["maya"] families = ["usdOverride"] scene_type = "usd" - file_formats = ["usd", "usda"] + # Order of `fileFormat` must match create_multiverse_usd_over.py + file_formats = ["usda", "usd"] @property def options(self): From 229852dfb52187ee22282427fd0282b81a65edba Mon Sep 17 00:00:00 2001 From: DMO Date: Wed, 18 May 2022 18:27:21 +0900 Subject: [PATCH 0278/1227] Adding new asset/composition options to the creators. --- openpype/hosts/maya/plugins/create/create_multiverse_usd.py | 1 + .../hosts/maya/plugins/create/create_multiverse_usd_comp.py | 1 + openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py | 2 ++ .../hosts/maya/plugins/publish/extract_multiverse_usd_comp.py | 2 ++ 4 files changed, 6 insertions(+) diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py index 1d53328760..a82a73cbdb 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py @@ -46,6 +46,7 @@ class CreateMultiverseUsd(plugin.Creator): self.data["writeShadingNetworks"] = False self.data["writeTransformMatrix"] = True self.data["writeUsdAttributes"] = False + self.data["writeInstancesAsReferences"] = False self.data["timeVaryingTopology"] = False self.data["customMaterialNamespace"] = '' self.data["numTimeSamples"] = 1 diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py index 0a8219938b..9d00ad1cfa 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py @@ -20,6 +20,7 @@ class CreateMultiverseUsdComp(plugin.Creator): self.data["stripNamespaces"] = False self.data["mergeTransformAndShape"] = False self.data["flattenContent"] = False + self.data["writeAsCompoundLayers"] = False self.data["writePendingOverrides"] = False self.data["numTimeSamples"] = 1 self.data["timeSamplesSpan"] = 0.0 diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py index 1748dd00dc..4bc0322fa8 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py @@ -59,6 +59,7 @@ class ExtractMultiverseUsd(openpype.api.Extractor): "writeShadingNetworks": bool, "writeTransformMatrix": bool, "writeUsdAttributes": bool, + "writeInstancesAsReferences": bool, "timeVaryingTopology": bool, "customMaterialNamespace": str, "numTimeSamples": int, @@ -100,6 +101,7 @@ class ExtractMultiverseUsd(openpype.api.Extractor): "writeShadingNetworks": False, "writeTransformMatrix": True, "writeUsdAttributes": False, + "writeInstancesAsReferences": False, "timeVaryingTopology": False, "customMaterialNamespace": str(), "numTimeSamples": 1, diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py index 695ea9a33f..54f23ae774 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py @@ -32,6 +32,7 @@ class ExtractMultiverseUsdComposition(openpype.api.Extractor): "stripNamespaces": bool, "mergeTransformAndShape": bool, "flattenContent": bool, + "writeAsCompoundLayers": bool, "writePendingOverrides": bool, "numTimeSamples": int, "timeSamplesSpan": float @@ -45,6 +46,7 @@ class ExtractMultiverseUsdComposition(openpype.api.Extractor): "stripNamespaces": True, "mergeTransformAndShape": False, "flattenContent": False, + "writeAsCompoundLayers": False, "writePendingOverrides": False, "numTimeSamples": 1, "timeSamplesSpan": 0.0 From 9fc70adfffeb0569f8c0f32f52eb80c722f923b7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 18 May 2022 13:07:33 +0200 Subject: [PATCH 0279/1227] flame: abs number only if not 0 --- .../plugins/publish/collect_timeline_instances.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py index 012cb110ec..0aca7c38d5 100644 --- a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py @@ -75,11 +75,17 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): marker_data["handleEnd"] ) + # make sure value is absolute + if head != 0: + head = abs(head) + if tail != 0: + tail = abs(tail) + # solve handles length marker_data["handleStart"] = min( - marker_data["handleStart"], abs(head)) + marker_data["handleStart"], head) marker_data["handleEnd"] = min( - marker_data["handleEnd"], abs(tail)) + marker_data["handleEnd"], tail) workfile_start = self._set_workfile_start(marker_data) From 8f7428dd95d7fe461151821f901fa3059ff5309a Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 18 May 2022 15:07:53 +0300 Subject: [PATCH 0280/1227] Add OIIO --- openpype/plugins/publish/extract_jpeg_exr.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 11dfca8eb2..2d3ad1e8a8 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -3,6 +3,7 @@ import os import pyblish.api from openpype.lib import ( get_ffmpeg_tool_path, + get_oiio_tools_path, run_subprocess, path_to_subprocess_arg, @@ -29,6 +30,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # presetable attribute ffmpeg_args = None + oiio_args = None def process(self, instance): self.log.info("subset {}".format(instance.data['subset'])) @@ -119,7 +121,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.append(path_to_subprocess_arg(full_output_path)) subprocess_command = " ".join(jpeg_items) - + # run subprocess self.log.debug("{}".format(subprocess_command)) try: # temporary until oiiotool is supported cross platform From a64b3f5b7696534b3384a21143be9842397da482 Mon Sep 17 00:00:00 2001 From: "felix.wang" Date: Wed, 18 May 2022 16:31:14 -0700 Subject: [PATCH 0281/1227] Revert "Add toggle button for Loaders' family filter widget." This reverts commit c5aa315c30413cc4e78cf113bff159a00f51164d. --- openpype/tools/libraryloader/app.py | 9 +-------- openpype/tools/loader/app.py | 9 +-------- openpype/tools/loader/widgets.py | 6 ------ 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index 99dfce36e3..7fda6bd6f9 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -82,15 +82,11 @@ class LibraryLoaderWindow(QtWidgets.QDialog): families_filter_view = FamilyListView( dbcon, self.family_config_cache, left_side_splitter ) - families_toggle_button = QtWidgets.QPushButton("Toggle All") - left_side_splitter.addWidget(projects_combobox) left_side_splitter.addWidget(assets_widget) left_side_splitter.addWidget(families_filter_view) - left_side_splitter.addWidget(families_toggle_button) left_side_splitter.setStretchFactor(1, 65) - left_side_splitter.setStretchFactor(2, 30) - left_side_splitter.setStretchFactor(3, 5) + left_side_splitter.setStretchFactor(2, 35) # --- Middle part --- # Subsets widget @@ -165,9 +161,6 @@ class LibraryLoaderWindow(QtWidgets.QDialog): families_filter_view.active_changed.connect( self._on_family_filter_change ) - families_toggle_button.clicked.connect( - families_filter_view.toggle_all - ) assets_widget.selection_changed.connect(self.on_assetschanged) assets_widget.refresh_triggered.connect(self.on_assetschanged) subsets_widget.active_changed.connect(self.on_subsetschanged) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index 57baeae061..bb589c199d 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -71,14 +71,10 @@ class LoaderWindow(QtWidgets.QDialog): families_filter_view = FamilyListView( legacy_io, self.family_config_cache, left_side_splitter ) - families_toggle_button = QtWidgets.QPushButton("Toggle All") - left_side_splitter.addWidget(assets_widget) left_side_splitter.addWidget(families_filter_view) - left_side_splitter.addWidget(families_toggle_button) left_side_splitter.setStretchFactor(0, 65) - left_side_splitter.setStretchFactor(1, 30) - left_side_splitter.setStretchFactor(2, 5) + left_side_splitter.setStretchFactor(1, 35) # --- Middle part --- # Subsets widget @@ -159,9 +155,6 @@ class LoaderWindow(QtWidgets.QDialog): families_filter_view.active_changed.connect( self._on_family_filter_change ) - families_toggle_button.clicked.connect( - families_filter_view.toggle_all - ) assets_widget.selection_changed.connect(self.on_assetschanged) assets_widget.refresh_triggered.connect(self.on_assetschanged) subsets_widget.active_changed.connect(self.on_subsetschanged) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index b5f1df1e36..42fb62b632 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -1070,12 +1070,6 @@ class FamilyListView(QtWidgets.QListView): def set_all_checked(self): self._set_checkstates(True, self._get_all_indexes()) - def toggle_all(self): - if self.get_enabled_families(): - self.set_all_unchecked() - else: - self.set_all_checked() - def _get_all_indexes(self): indexes = [] model = self._family_model From 6b9983fdede5e7e086e62798abddabdec3afcb85 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 19 May 2022 14:19:30 +0100 Subject: [PATCH 0282/1227] Added support for both UE4 and 5 Plugin won't compile in UE4 yet. UE5 needs different modules, not available in UE4. --- .../unreal/hooks/pre_workfile_preparation.py | 12 ++--- openpype/hosts/unreal/lib.py | 48 ++++++++++++------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index fa0562a3a0..5be04fc841 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -71,7 +71,7 @@ class UnrealPrelaunchHook(PreLaunchHook): if int(engine_version.split(".")[0]) < 4 and \ int(engine_version.split(".")[1]) < 26: raise ApplicationLaunchFailed(( - f"{self.signature} Old unsupported version of UE4 " + f"{self.signature} Old unsupported version of UE " f"detected - {engine_version}")) except ValueError: # there can be string in minor version and in that case @@ -104,14 +104,14 @@ class UnrealPrelaunchHook(PreLaunchHook): project_path = Path(os.path.join(workdir, unreal_project_name)) self.log.info(( - f"{self.signature} requested UE4 version: " + f"{self.signature} requested UE version: " f"[ {engine_version} ]" )) detected = unreal_lib.get_engine_versions(self.launch_context.env) detected_str = ', '.join(detected.keys()) or 'none' self.log.info(( - f"{self.signature} detected UE4 versions: " + f"{self.signature} detected UE versions: " f"[ {detected_str} ]" )) if not detected: @@ -124,10 +124,10 @@ class UnrealPrelaunchHook(PreLaunchHook): f"detected [ {engine_version} ]" )) - ue4_path = unreal_lib.get_editor_executable_path( - Path(detected[engine_version])) + ue_path = unreal_lib.get_editor_executable_path( + Path(detected[engine_version]), engine_version) - self.launch_context.launch_args = [ue4_path.as_posix()] + self.launch_context.launch_args = [ue_path.as_posix()] project_path.mkdir(parents=True, exist_ok=True) project_file = project_path / unreal_project_filename diff --git a/openpype/hosts/unreal/lib.py b/openpype/hosts/unreal/lib.py index fdf3acb37b..f220d8dedf 100644 --- a/openpype/hosts/unreal/lib.py +++ b/openpype/hosts/unreal/lib.py @@ -70,19 +70,22 @@ def get_engine_versions(env=None): return OrderedDict() -def get_editor_executable_path(engine_path: Path) -> Path: - """Get UE4 Editor executable path.""" - ue4_path = engine_path / "Engine/Binaries" +def get_editor_executable_path(engine_path: Path, engine_version: str) -> Path: + """Get UE Editor executable path.""" + ue_path = engine_path / "Engine/Binaries" if platform.system().lower() == "windows": - ue4_path /= "Win64/UnrealEditor.exe" + if engine_version.split(".")[0] == "4": + ue_path /= "Win64/UE4Editor.exe" + elif engine_version.split(".")[0] == "5": + ue_path /= "Win64/UnrealEditor.exe" elif platform.system().lower() == "linux": - ue4_path /= "Linux/UE4Editor" + ue_path /= "Linux/UE4Editor" elif platform.system().lower() == "darwin": - ue4_path /= "Mac/UE4Editor" + ue_path /= "Mac/UE4Editor" - return ue4_path + return ue_path def _win_get_engine_versions(): @@ -208,22 +211,26 @@ def create_unreal_project(project_name: str, # created in different UE4 version. When user convert such project # to his UE4 version, Engine ID is replaced in uproject file. If some # other user tries to open it, it will present him with similar error. - ue4_modules = Path() + ue_modules = Path() if platform.system().lower() == "windows": - ue4_modules = Path(os.path.join(engine_path, "Engine", "Binaries", - "Win64", "UE4Editor.modules")) + ue_modules_path = engine_path / "Engine/Binaries/Win64" + if ue_version.split(".")[0] == "4": + ue_modules_path /= "UE4Editor.modules" + elif ue_version.split(".")[0] == "5": + ue_modules_path /= "UnrealEditor.modules" + ue_modules = Path(ue_modules_path) if platform.system().lower() == "linux": - ue4_modules = Path(os.path.join(engine_path, "Engine", "Binaries", + ue_modules = Path(os.path.join(engine_path, "Engine", "Binaries", "Linux", "UE4Editor.modules")) if platform.system().lower() == "darwin": - ue4_modules = Path(os.path.join(engine_path, "Engine", "Binaries", + ue_modules = Path(os.path.join(engine_path, "Engine", "Binaries", "Mac", "UE4Editor.modules")) - if ue4_modules.exists(): + if ue_modules.exists(): print("--- Loading Engine ID from modules file ...") - with open(ue4_modules, "r") as mp: + with open(ue_modules, "r") as mp: loaded_modules = json.load(mp) if loaded_modules.get("BuildId"): @@ -298,10 +305,11 @@ def create_unreal_project(project_name: str, [python_path.as_posix(), "-m", "pip", "install", "--user", "pyside2"]) if dev_mode or preset["dev_mode"]: - _prepare_cpp_project(project_file, engine_path) + _prepare_cpp_project(project_file, engine_path, ue_version) -def _prepare_cpp_project(project_file: Path, engine_path: Path) -> None: +def _prepare_cpp_project( + project_file: Path, engine_path: Path, ue_version: str) -> None: """Prepare CPP Unreal Project. This function will add source files needed for project to be @@ -420,8 +428,12 @@ class {1}_API A{0}GameModeBase : public AGameModeBase with open(sources_dir / f"{project_name}GameModeBase.h", mode="w") as f: f.write(game_mode_h) - u_build_tool = Path( - engine_path / "Engine/Binaries/DotNET/UnrealBuildTool/UnrealBuildTool.exe") + u_build_tool_path = engine_path / "Engine/Binaries/DotNET" + if ue_version.split(".")[0] == "4": + u_build_tool_path /= "UnrealBuildTool.exe" + elif ue_version.split(".")[0] == "5": + u_build_tool_path /= "UnrealBuildTool/UnrealBuildTool.exe" + u_build_tool = Path(u_build_tool_path) u_header_tool = None arch = "Win64" From 13b4b18d162ed4307408e9d2f64869403c740724 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 19 May 2022 17:05:55 +0200 Subject: [PATCH 0283/1227] OP-2787 - WIP implementation --- openpype/api.py | 2 + openpype/hosts/maya/api/pipeline.py | 7 + .../maya/plugins/create/create_animation.py | 4 + .../maya/plugins/create/create_pointcache.py | 4 + .../maya/plugins/publish/collect_animation.py | 3 + .../plugins/publish/collect_pointcache.py | 14 ++ .../maya/plugins/publish/extract_animation.py | 8 + .../plugins/publish/extract_pointcache.py | 8 + openpype/lib/remote_publish.py | 2 +- .../submit_maya_remote_publish_deadline.py | 137 ++++++++++++++++++ openpype/plugin.py | 12 ++ openpype/plugins/publish/integrate_new.py | 3 + openpype/scripts/remote_publish.py | 11 ++ 13 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 openpype/hosts/maya/plugins/publish/collect_pointcache.py create mode 100644 openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py create mode 100644 openpype/scripts/remote_publish.py diff --git a/openpype/api.py b/openpype/api.py index 9ce745b653..e049a683c6 100644 --- a/openpype/api.py +++ b/openpype/api.py @@ -44,6 +44,7 @@ from . import resources from .plugin import ( Extractor, + Integrator, ValidatePipelineOrder, ValidateContentsOrder, @@ -86,6 +87,7 @@ __all__ = [ # plugin classes "Extractor", + "Integrator", # ordering "ValidatePipelineOrder", "ValidateContentsOrder", diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index b0e8fac635..b75af29523 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -71,8 +71,15 @@ def install(): if lib.IS_HEADLESS: log.info(("Running in headless mode, skipping Maya " "save/open/new callback installation..")) + + # Register default "local" target + print("Registering pyblish target: farm") + pyblish.api.register_target("farm") return + print("Registering pyblish target: local") + pyblish.api.register_target("local") + _set_project() _register_callbacks() diff --git a/openpype/hosts/maya/plugins/create/create_animation.py b/openpype/hosts/maya/plugins/create/create_animation.py index 11a668cfc8..5cd1f7090a 100644 --- a/openpype/hosts/maya/plugins/create/create_animation.py +++ b/openpype/hosts/maya/plugins/create/create_animation.py @@ -38,3 +38,7 @@ class CreateAnimation(plugin.Creator): # Default to exporting world-space self.data["worldSpace"] = True + + # Default to not send to farm. + self.data["farm"] = False + self.data["priority"] = 50 diff --git a/openpype/hosts/maya/plugins/create/create_pointcache.py b/openpype/hosts/maya/plugins/create/create_pointcache.py index ede152f1ef..e876015adb 100644 --- a/openpype/hosts/maya/plugins/create/create_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_pointcache.py @@ -28,3 +28,7 @@ class CreatePointCache(plugin.Creator): # Add options for custom attributes self.data["attr"] = "" self.data["attrPrefix"] = "" + + # Default to not send to farm. + self.data["farm"] = False + self.data["priority"] = 50 diff --git a/openpype/hosts/maya/plugins/publish/collect_animation.py b/openpype/hosts/maya/plugins/publish/collect_animation.py index 9b1e38fd0a..b442113fbc 100644 --- a/openpype/hosts/maya/plugins/publish/collect_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_animation.py @@ -55,3 +55,6 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): # Store data in the instance for the validator instance.data["out_hierarchy"] = hierarchy + + if instance.data.get("farm"): + instance.data["families"].append("deadline") diff --git a/openpype/hosts/maya/plugins/publish/collect_pointcache.py b/openpype/hosts/maya/plugins/publish/collect_pointcache.py new file mode 100644 index 0000000000..b55babe372 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/collect_pointcache.py @@ -0,0 +1,14 @@ +import pyblish.api + + +class CollectPointcache(pyblish.api.InstancePlugin): + """Collect pointcache data for instance.""" + + order = pyblish.api.CollectorOrder + 0.4 + families = ["pointcache"] + label = "Collect Pointcache" + hosts = ["maya"] + + def process(self, instance): + if instance.data.get("farm"): + instance.data["families"].append("deadline") \ No newline at end of file diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py index 8a8bd67cd8..87f2d35192 100644 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_animation.py @@ -23,6 +23,14 @@ class ExtractAnimation(openpype.api.Extractor): families = ["animation"] def process(self, instance): + if instance.data.get("farm"): + path = os.path.join( + os.path.dirname(instance.context.data["currentFile"]), + "cache", + instance.data["name"] + ".abc" + ) + instance.data["expectedFiles"] = [os.path.normpath(path)] + return # Collect the out set nodes out_sets = [node for node in instance if node.endswith("out_SET")] diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 60502fdde1..7ad4c6dfa9 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -25,6 +25,14 @@ class ExtractAlembic(openpype.api.Extractor): "vrayproxy"] def process(self, instance): + if instance.data.get("farm"): + path = os.path.join( + os.path.dirname(instance.context.data["currentFile"]), + "cache", + instance.data["name"] + ".abc" + ) + instance.data["expectedFiles"] = [os.path.normpath(path)] + return nodes = instance[:] diff --git a/openpype/lib/remote_publish.py b/openpype/lib/remote_publish.py index 8a42daf4e9..da2497e1a5 100644 --- a/openpype/lib/remote_publish.py +++ b/openpype/lib/remote_publish.py @@ -228,7 +228,7 @@ def _get_close_plugin(close_plugin_name, log): if plugin.__name__ == close_plugin_name: return plugin - log.warning("Close plugin not found, app might not close.") + log.debug("Close plugin not found, app might not close.") def get_task_data(batch_dir): diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py new file mode 100644 index 0000000000..761bc8cc95 --- /dev/null +++ b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py @@ -0,0 +1,137 @@ +import os +import requests + +from maya import cmds + +from openpype.pipeline import legacy_io + +import pyblish.api + + +class MayaSubmitRemotePublishDeadline(pyblish.api.ContextPlugin): + """Submit Maya scene to perform a local publish in Deadline. + + Publishing in Deadline can be helpful for scenes that publish very slow. + This way it can process in the background on another machine without the + Artist having to wait for the publish to finish on their local machine. + + Submission is done through the Deadline Web Service. + + Different from `ProcessSubmittedJobOnFarm` which creates publish job + depending on metadata json containing context and instance data of + rendered files. + """ + + label = "Submit Scene to Deadline" + order = pyblish.api.IntegratorOrder + hosts = ["maya"] + families = ["deadline"] + + # custom deadline attributes + deadline_department = "" + deadline_pool = "" + deadline_pool_secondary = "" + deadline_group = "" + deadline_chunk_size = 1 + deadline_priority = 50 + + def process(self, context): + + # Ensure no errors so far + assert all(result["success"] for result in context.data["results"]), ( + "Errors found, aborting integration..") + + # Note that `publish` data member might change in the future. + # See: https://github.com/pyblish/pyblish-base/issues/307 + actives = [i for i in context if i.data["publish"]] + instance_names = sorted(instance.name for instance in actives) + + if not instance_names: + self.log.warning("No active instances found. " + "Skipping submission..") + return + + scene = context.data["currentFile"] + scenename = os.path.basename(scene) + + # Get project code + project_name = legacy_io.Session["AVALON_PROJECT"] + + job_name = "{scene} [PUBLISH]".format(scene=scenename) + batch_name = "{code} - {scene}".format(code=project_name, + scene=scenename) + + # Generate the payload for Deadline submission + payload = { + "JobInfo": { + "Plugin": "MayaBatch", + "BatchName": batch_name, + "Priority": 50, + "Name": job_name, + "UserName": context.data["user"], + # "Comment": instance.context.data.get("comment", ""), + # "InitialStatus": state + "Department": self.deadline_department, + "ChunkSize": self.deadline_chunk_size, + "Priority": self.deadline_priority, + + "Group": self.deadline_group, + + }, + "PluginInfo": { + + "Build": None, # Don't force build + "StrictErrorChecking": True, + "ScriptJob": True, + + # Inputs + "SceneFile": scene, + "ScriptFilename": "{OPENPYPE_ROOT}/scripts/remote_publish.py", + + # Mandatory for Deadline + "Version": cmds.about(version=True), + + # Resolve relative references + "ProjectPath": cmds.workspace(query=True, + rootDirectory=True), + + }, + + # Mandatory for Deadline, may be empty + "AuxFiles": [] + } + + # Include critical environment variables with submission + api.Session + keys = [ + "FTRACK_API_USER", + "FTRACK_API_KEY", + "FTRACK_SERVER" + ] + environment = dict({key: os.environ[key] for key in keys + if key in os.environ}, **legacy_io.Session) + + # TODO replace legacy_io with context.data ? + environment["AVALON_PROJECT"] = legacy_io.Session["AVALON_PROJECT"] + environment["AVALON_ASSET"] = legacy_io.Session["AVALON_ASSET"] + environment["AVALON_TASK"] = legacy_io.Session["AVALON_TASK"] + environment["AVALON_APP_NAME"] = os.environ.get("AVALON_APP_NAME") + environment["OPENPYPE_LOG_NO_COLORS"] = "1" + environment["OPENPYPE_USERNAME"] = context.data["user"] + environment["OPENPYPE_PUBLISH_JOB"] = "1" + environment["OPENPYPE_RENDER_JOB"] = "0" + environment["PYBLISH_ACTIVE_INSTANCES"] = ",".join(instance_names) + + payload["JobInfo"].update({ + "EnvironmentKeyValue%d" % index: "{key}={value}".format( + key=key, + value=environment[key] + ) for index, key in enumerate(environment) + }) + + self.log.info("Submitting Deadline job ...") + deadline_url = context.data["defaultDeadline"] + assert deadline_url, "Requires Deadline Webservice URL" + url = "{}/api/jobs".format(deadline_url) + response = requests.post(url, json=payload, timeout=10) + if not response.ok: + raise Exception(response.text) diff --git a/openpype/plugin.py b/openpype/plugin.py index bb9bc2ff85..f1ee626ffb 100644 --- a/openpype/plugin.py +++ b/openpype/plugin.py @@ -18,6 +18,16 @@ class InstancePlugin(pyblish.api.InstancePlugin): super(InstancePlugin, cls).process(cls, *args, **kwargs) +class Integrator(InstancePlugin): + """Integrator base class. + + Wraps pyblish instance plugin. Targets set to "local" which means all + integrators should run on "local" publishes, by default. + "farm" targets could be used for integrators that should run on a farm. + """ + targets = ["local"] + + class Extractor(InstancePlugin): """Extractor base class. @@ -28,6 +38,8 @@ class Extractor(InstancePlugin): """ + targets = ["local"] + order = 2.0 def staging_dir(self, instance): diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index bf13a4050e..1a4112107a 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -139,6 +139,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): ef, instance.data["family"], instance.data["families"])) return + if "deadline" in instance.data["families"]: + return + self.integrated_file_sizes = {} try: self.register(instance) diff --git a/openpype/scripts/remote_publish.py b/openpype/scripts/remote_publish.py new file mode 100644 index 0000000000..b54c8d931b --- /dev/null +++ b/openpype/scripts/remote_publish.py @@ -0,0 +1,11 @@ +try: + from openpype.api import Logger + import openpype.lib.remote_publish +except ImportError as exc: + # Ensure Deadline fails by output an error that contains "Fatal Error:" + raise ImportError("Fatal Error: %s" % exc) + +if __name__ == "__main__": + # Perform remote publish with thorough error checking + log = Logger.get_logger(__name__) + openpype.lib.remote_publish.publish(log) From 0b7e3b6606a5e369e7aa2d9d4ae465d218f38016 Mon Sep 17 00:00:00 2001 From: DMO Date: Fri, 20 May 2022 10:34:53 +0900 Subject: [PATCH 0284/1227] Fixed formatting errors, removed unneeded code, added comments. --- .../publish/collect_multiverse_look.py | 46 ++++++++----------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py index 9c1569e216..643a3eae61 100644 --- a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py @@ -6,6 +6,7 @@ from maya import cmds import pyblish.api from openpype.hosts.maya.api import lib + def get_look_attrs(node): """Returns attributes of a node that are important for the look. @@ -191,20 +192,19 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): cmds.loadPlugin("MultiverseForMaya", quiet=True) import multiverse - self.log.info("Processing mvLook for '{}' / '{}'".format( - instance, instance.data['name'])) + self.log.info("Processing mvLook for '{}'".format(instance)) nodes = set() for node in instance: # We want only mvUsdCompoundShape nodes. nodes_of_interest = cmds.ls(node, - dag=True, - shapes=False, - type="mvUsdCompoundShape", - noIntermediate=True, - long=True) + dag=True, + shapes=False, + type="mvUsdCompoundShape", + noIntermediate=True, + long=True) nodes.update(nodes_of_interest) - + files = [] sets = {} instance.data["resources"] = [] @@ -212,16 +212,21 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): for node in nodes: self.log.info("Getting resources for '{}'".format(node)) - # We know what nodes + # We know what nodes need to be collected, now we need to + # extract the materials overrides. overrides = multiverse.ListMaterialOverridePrims(node) for override in overrides: matOver = multiverse.GetMaterialOverride(node, override) if isinstance(matOver, multiverse.MaterialSourceShadingGroup): + # We now need to grab the shadingGroup so add it to the + # sets we pass down the pipe. shadingGroup = matOver.shadingGroupName self.log.debug("ShadingGroup = '{}'".format(shadingGroup)) - sets[shadingGroup] = {"uuid": lib.get_id(shadingGroup), "members": list()} + sets[shadingGroup] = {"uuid": lib.get_id( + shadingGroup), "members": list()} + # The SG may reference files, add those too! history = cmds.listHistory(shadingGroup) files = cmds.ls(history, type="file", long=True) @@ -229,28 +234,16 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): resources = self.collect_resource(f) instance.data["resources"].append(resources) - elif isinstance(matOver, multiverse.MaterialSourceUsdPath): + elif isinstance(matOver, multiverse.MaterialSourceUsdPath): # TODO: Handle this later. pass - else: - # TODO: What to do here? - pass - # Store data on the instance + # Store data on the instance for validators, extractos, etc. instance.data["lookData"] = { "attributes": [], "relationships": sets } - self.log.info("-------------------------") - self.log.info("Instance: {},{}".format(type(instance),instance)) - self.log.info("Instance.data: {},{}".format(type(instance.data),instance.data)) - for k in instance.data.keys(): - v = instance.data[k] - self.log.info(" data: {},{}".format(k,v)) - self.log.info("-------------------------") - - def collect_resource(self, node): """Collect the link to the file(s) used (resource) Args: @@ -269,7 +262,8 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): if cmds.nodeType(node) == 'file': self.log.debug(" - file node") attribute = "{}.fileTextureName".format(node) - computed_attribute = "{}.computedFileTextureNamePattern".format(node) + computed_attribute = "{}.computedFileTextureNamePattern".format( + node) elif cmds.nodeType(node) == 'aiImage': self.log.debug("aiImage node") attribute = "{}.filename".format(node) @@ -320,4 +314,4 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): "attribute": attribute, "source": source, # required for resources "files": files, - "color_space": color_space} # required for resources \ No newline at end of file + "color_space": color_space} # required for resources From fe40624135dae296d0f4099edbdaf8be38742b79 Mon Sep 17 00:00:00 2001 From: DMO Date: Fri, 20 May 2022 10:38:45 +0900 Subject: [PATCH 0285/1227] Minor cleanup. --- .../maya/plugins/publish/collect_multiverse_look.py | 13 ++++++++++++- openpype/hosts/maya/plugins/publish/extract_look.py | 2 +- .../maya/plugins/publish/extract_multiverse_look.py | 1 - 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py index 643a3eae61..c5ea6a2253 100644 --- a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py @@ -6,6 +6,17 @@ from maya import cmds import pyblish.api from openpype.hosts.maya.api import lib +SHAPE_ATTRS = ["castsShadows", + "receiveShadows", + "motionBlur", + "primaryVisibility", + "smoothShading", + "visibleInReflections", + "visibleInRefractions", + "doubleSided", + "opposite"] + +SHAPE_ATTRS = set(SHAPE_ATTRS) def get_look_attrs(node): """Returns attributes of a node that are important for the look. @@ -92,7 +103,7 @@ def seq_to_glob(path): "": "", "#": "#", "u_v": "|", - "", + "", #noqa - copied from collect_look.py "": "" } diff --git a/openpype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py index c427eacd98..afb2c3b3d6 100644 --- a/openpype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -146,7 +146,7 @@ class ExtractLook(openpype.api.Extractor): label = "Extract Look (Maya Scene + JSON)" hosts = ["maya"] - families = ["look","mvLook"] + families = ["look", "mvLook"] order = pyblish.api.ExtractorOrder + 0.2 scene_type = "ma" look_data_type = "json" diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py index 34c72f0915..5ba840f2b7 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py @@ -1,5 +1,4 @@ import os -import six from maya import cmds From d46c919281145a7d07ffb5aca1ef16f7407c6e46 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 12 May 2022 11:46:55 +0200 Subject: [PATCH 0286/1227] general: calculation of duration should not exclude one frame From ddfee503677c3ad6e653c9346cd742520667d0d9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 12 May 2022 11:47:23 +0200 Subject: [PATCH 0287/1227] hiero: fitting new duration calculation From f4ebcdb27856888794c59c142c43806b863cb61b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 19 May 2022 12:22:35 +0200 Subject: [PATCH 0288/1227] Hiero: small bugs - track name was not equal and was catching similar names too - publish action in timeline submenu was broken - parse_container was returning false data even it should not --- openpype/hosts/hiero/api/lib.py | 7 ++++--- openpype/hosts/hiero/api/pipeline.py | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 2a4cd03b76..be02c7c793 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -118,7 +118,7 @@ def get_current_track(sequence, name, audio=False): # get track by name track = None for _track in tracks: - if _track.name() in name: + if _track.name() == name: track = _track if not track: @@ -126,6 +126,7 @@ def get_current_track(sequence, name, audio=False): track = hiero.core.VideoTrack(name) else: track = hiero.core.AudioTrack(name) + sequence.addTrack(track) return track @@ -497,7 +498,7 @@ class PyblishSubmission(hiero.exporters.FnSubmission.Submission): from . import publish # Add submission to Hiero module for retrieval in plugins. hiero.submission = self - publish() + publish(hiero.ui.mainWindow()) def add_submission(): @@ -527,7 +528,7 @@ class PublishAction(QtWidgets.QAction): # from getting picked up when not using the "Export" dialog. if hasattr(hiero, "submission"): del hiero.submission - publish() + publish(hiero.ui.mainWindow()) def eventHandler(self, event): # Add the Menu to the right-click menu diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py index 8025ebff05..9b628ec70b 100644 --- a/openpype/hosts/hiero/api/pipeline.py +++ b/openpype/hosts/hiero/api/pipeline.py @@ -143,6 +143,11 @@ def parse_container(track_item, validate=True): """ # convert tag metadata to normal keys names data = lib.get_track_item_pype_data(track_item) + if ( + not data + or data.get("id") != "pyblish.avalon.container" + ): + return if validate and data and data.get("schema"): schema.validate(data) From c09038984190085b6182cf075455d503692e10ca Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 19 May 2022 19:46:34 +0200 Subject: [PATCH 0289/1227] Hiero: add new `get_timeline_selection` function --- openpype/hosts/hiero/api/__init__.py | 2 ++ openpype/hosts/hiero/api/lib.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/openpype/hosts/hiero/api/__init__.py b/openpype/hosts/hiero/api/__init__.py index f3c32b268c..fc2d017f04 100644 --- a/openpype/hosts/hiero/api/__init__.py +++ b/openpype/hosts/hiero/api/__init__.py @@ -27,6 +27,7 @@ from .lib import ( get_track_items, get_current_project, get_current_sequence, + get_timeline_selection, get_current_track, get_track_item_pype_tag, set_track_item_pype_tag, @@ -80,6 +81,7 @@ __all__ = [ "get_track_items", "get_current_project", "get_current_sequence", + "get_timeline_selection", "get_current_track", "get_track_item_pype_tag", "set_track_item_pype_tag", diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index be02c7c793..115a926d84 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -96,6 +96,12 @@ def get_current_sequence(name=None, new=False): return sequence +def get_timeline_selection(): + active_sequence = hiero.ui.activeSequence() + timeline_editor = hiero.ui.getTimelineEditor(active_sequence) + return list(timeline_editor.selection()) + + def get_current_track(sequence, name, audio=False): """ Get current track in context of active project. From 9dd13425c36511873aeefb46f88f721381d91cc6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 19 May 2022 19:47:15 +0200 Subject: [PATCH 0290/1227] Hiero: removing event slowing down work with timeline --- openpype/hosts/hiero/api/events.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/hiero/api/events.py b/openpype/hosts/hiero/api/events.py index 7fab3edfc8..59fd278a81 100644 --- a/openpype/hosts/hiero/api/events.py +++ b/openpype/hosts/hiero/api/events.py @@ -109,8 +109,9 @@ def register_hiero_events(): # hiero.core.events.registerInterest("kShutdown", shutDown) # hiero.core.events.registerInterest("kStartup", startupCompleted) - hiero.core.events.registerInterest( - ("kSelectionChanged", "kTimeline"), selection_changed_timeline) + # INFO: was disabled because it was slowing down timeline operations + # hiero.core.events.registerInterest( + # ("kSelectionChanged", "kTimeline"), selection_changed_timeline) # workfiles try: From c70c6d99110dce5f7d880cc0d285e2a03dfbf583 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 19 May 2022 19:48:12 +0200 Subject: [PATCH 0291/1227] Hiero: fixing one frame difference otio clip and media --- openpype/hosts/hiero/api/otio/hiero_export.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/hiero/api/otio/hiero_export.py b/openpype/hosts/hiero/api/otio/hiero_export.py index 64fb81aed4..46e1204324 100644 --- a/openpype/hosts/hiero/api/otio/hiero_export.py +++ b/openpype/hosts/hiero/api/otio/hiero_export.py @@ -151,7 +151,7 @@ def create_otio_reference(clip): padding = media_source.filenamePadding() file_head = media_source.filenameHead() is_sequence = not media_source.singleFile() - frame_duration = media_source.duration() - 1 + frame_duration = media_source.duration() fps = utils.get_rate(clip) or self.project_fps extension = os.path.splitext(path)[-1] @@ -277,7 +277,7 @@ def create_otio_clip(track_item): # flip if speed is in minus source_in = track_item.sourceIn() if speed > 0 else track_item.sourceOut() - duration = int(track_item.duration()) + duration = int(track_item.duration()) - 1 fps = utils.get_rate(track_item) or self.project_fps name = track_item.name() From cf6ea949acca41fcd04a0155134252c21816939a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 19 May 2022 19:48:40 +0200 Subject: [PATCH 0292/1227] global: hierarchy fps should be taken from instance.data --- openpype/plugins/publish/collect_hierarchy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_hierarchy.py b/openpype/plugins/publish/collect_hierarchy.py index a96d444be6..8398a2815a 100644 --- a/openpype/plugins/publish/collect_hierarchy.py +++ b/openpype/plugins/publish/collect_hierarchy.py @@ -62,7 +62,7 @@ class CollectHierarchy(pyblish.api.ContextPlugin): "frameEnd": instance.data["frameEnd"], "clipIn": instance.data["clipIn"], "clipOut": instance.data["clipOut"], - 'fps': instance.context.data["fps"], + "fps": instance.data["fps"], "resolutionWidth": instance.data["resolutionWidth"], "resolutionHeight": instance.data["resolutionHeight"], "pixelAspect": instance.data["pixelAspect"] From 191444167c025f0f1bf20b6d15dbd480eff1243e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 19 May 2022 19:49:52 +0200 Subject: [PATCH 0293/1227] Hiero: moving order bit lower under core plugins --- openpype/hosts/hiero/plugins/publish/precollect_workfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_workfile.py b/openpype/hosts/hiero/plugins/publish/precollect_workfile.py index b9f58c15f6..c9bfb86810 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_workfile.py @@ -16,7 +16,7 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): """Inject the current working file into context""" label = "Precollect Workfile" - order = pyblish.api.CollectorOrder - 0.5 + order = pyblish.api.CollectorOrder - 0.491 def process(self, context): @@ -84,6 +84,7 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): "colorspace": self.get_colorspace(project), "fps": fps } + self.log.debug("__ context_data: {}".format(pformat(context_data))) context.data.update(context_data) self.log.info("Creating instance: {}".format(instance)) From b55bf81d352790c46ba748a7781bc75cda88afb1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 19 May 2022 19:50:50 +0200 Subject: [PATCH 0294/1227] Hiero: adding timeline selected to precollector --- .../hosts/hiero/plugins/publish/precollect_instances.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index 46f0b2440e..1ef7e5f538 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -19,9 +19,12 @@ class PrecollectInstances(pyblish.api.ContextPlugin): def process(self, context): self.otio_timeline = context.data["otioTimeline"] - + timeline_selection = phiero.get_timeline_selection() selected_timeline_items = phiero.get_track_items( - selected=True, check_tagged=True, check_enabled=True) + selection=timeline_selection, + check_tagged=True, + check_enabled=True + ) # only return enabled track items if not selected_timeline_items: From 68439301dc5f24372ccd69202bcea3a3040ce733 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 19 May 2022 19:51:21 +0200 Subject: [PATCH 0295/1227] Hiero: one frame diff fix, with code improvements --- .../hosts/hiero/plugins/publish/precollect_instances.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index 1ef7e5f538..e54d050f0d 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -295,9 +295,9 @@ class PrecollectInstances(pyblish.api.ContextPlugin): for otio_clip in self.otio_timeline.each_clip(): track_name = otio_clip.parent().name parent_range = otio_clip.range_in_parent() - if ti_track_name not in track_name: + if ti_track_name != track_name: continue - if otio_clip.name not in track_item.name(): + if otio_clip.name != track_item.name(): continue self.log.debug("__ parent_range: {}".format(parent_range)) self.log.debug("__ timeline_range: {}".format(timeline_range)) @@ -317,7 +317,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin): speed = track_item.playbackSpeed() timeline = phiero.get_current_sequence() frame_start = int(track_item.timelineIn()) - frame_duration = int(track_item.sourceDuration() / speed) + frame_duration = int((track_item.duration() - 1) / speed) fps = timeline.framerate().toFloat() return hiero_export.create_otio_time_range( From 2798469e36c3f915692cb0cfdea5e0cb5f16900a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 19 May 2022 19:54:17 +0200 Subject: [PATCH 0296/1227] Hiero: refactory of get_track_items with better validation --- openpype/hosts/hiero/api/lib.py | 140 ++++++++++++++++++++------------ 1 file changed, 89 insertions(+), 51 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 115a926d84..15142daa09 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -1,7 +1,10 @@ """ Host specific functions where host api is connected """ + +import contextlib import os +from pprint import pformat import re import sys import platform @@ -139,7 +142,7 @@ def get_current_track(sequence, name, audio=False): def get_track_items( - selected=False, + selection=False, sequence_name=None, track_item_name=None, track_name=None, @@ -150,7 +153,7 @@ def get_track_items( """Get all available current timeline track items. Attribute: - selected (bool)[optional]: return only selected items on timeline + selection (list)[optional]: list of selected track items sequence_name (str)[optional]: return only clips from input sequence track_item_name (str)[optional]: return only item with input name track_name (str)[optional]: return only items from track name @@ -162,32 +165,33 @@ def get_track_items( Return: list or hiero.core.TrackItem: list of track items or single track item """ - return_list = list() - track_items = list() + track_type = track_type or "video" + selection = selection or [] + return_list = [] # get selected track items or all in active sequence - if selected: - try: - selected_items = list(hiero.selection) - for item in selected_items: - if track_name and track_name in item.parent().name(): - # filter only items fitting input track name - track_items.append(item) - elif not track_name: - # or add all if no track_name was defined - track_items.append(item) - except AttributeError: - pass + if selection: + with contextlib.suppress(AttributeError): + for track_item in selection: + log.info("___ track_item: {}".format(track_item)) + # make sure only trackitems are selected + if not isinstance(track_item, hiero.core.TrackItem): + continue + + if _validate_all_atrributes( + track_item, + track_item_name, + track_name, + track_type, + check_enabled, + check_tagged + ): + log.info("___ valid trackitem: {}".format(track_item)) + return_list.append(track_item) - # check if any collected track items are - # `core.Hiero.Python.TrackItem` instance - if track_items: - any_track_item = track_items[0] - if not isinstance(any_track_item, hiero.core.TrackItem): - selected_items = [] # collect all available active sequence track items - if not track_items: + if not return_list: sequence = get_current_sequence(name=sequence_name) # get all available tracks from sequence tracks = list(sequence.audioTracks()) + list(sequence.videoTracks()) @@ -198,42 +202,76 @@ def get_track_items( if check_enabled and not track.isEnabled(): continue # and all items in track - for item in track.items(): - if check_tagged and not item.tags(): + for track_item in track.items(): + # make sure no subtrackitem is also track items + if not isinstance(track_item, hiero.core.TrackItem): continue - # check if track item is enabled - if check_enabled: - if not item.isEnabled(): - continue - if track_item_name: - if track_item_name in item.name(): - return item - # make sure only track items with correct track names are added - if track_name and track_name in track.name(): - # filter out only defined track_name items - track_items.append(item) - elif not track_name: - # or add all if no track_name is defined - track_items.append(item) + if not _validate_all_atrributes( + track_item, + track_item_name, + track_name, + track_type, + check_enabled, + check_tagged + ): + return_list.append(track_item) - # filter out only track items with defined track_type - for track_item in track_items: - if track_type and track_type == "video" and isinstance( + return return_list + + +def _validate_all_atrributes( + track_item, + track_item_name, + track_name, + track_type, + check_enabled, + check_tagged +): + def _validate_correct_name_track_item(): + if track_item_name and track_item_name in track_item.name(): + return True + elif not track_item_name: + return True + + def _validate_tagged_track_item(): + if check_tagged and track_item.tags(): + return True + elif not check_tagged: + return True + + def _validate_enabled_track_item(): + if check_enabled and track_item.isEnabled(): + return True + elif not check_enabled: + return True + + def _validate_parent_track_item(): + if track_name and track_name in track_item.parent().name(): + # filter only items fitting input track name + return True + elif not track_name: + # or add all if no track_name was defined + return True + + def _validate_type_track_item(): + if track_type == "video" and isinstance( track_item.parent(), hiero.core.VideoTrack): # only video track items are allowed - return_list.append(track_item) - elif track_type and track_type == "audio" and isinstance( + return True + elif track_type == "audio" and isinstance( track_item.parent(), hiero.core.AudioTrack): # only audio track items are allowed - return_list.append(track_item) - elif not track_type: - # add all if no track_type is defined - return_list.append(track_item) + return True - # return output list but make sure all items are TrackItems - return [_i for _i in return_list - if type(_i) == hiero.core.TrackItem] + # check if track item is enabled + return all([ + _validate_enabled_track_item(), + _validate_type_track_item(), + _validate_tagged_track_item(), + _validate_parent_track_item(), + _validate_correct_name_track_item() + ]) def get_track_item_pype_tag(track_item): From 13599837176687e49a57762414aad97e56e9125a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 19 May 2022 19:55:16 +0200 Subject: [PATCH 0297/1227] Hiero: fixing events --- openpype/hosts/hiero/api/lib.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 15142daa09..d3e6441705 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -1027,7 +1027,7 @@ def sync_clip_name_to_data_asset(track_items_list): print("asset was changed in clip: {}".format(ti_name)) -def check_inventory_versions(): +def check_inventory_versions(track_items=None): """ Actual version color idetifier of Loaded containers @@ -1038,14 +1038,15 @@ def check_inventory_versions(): """ from . import parse_container + track_item = track_items or get_track_items() # presets clip_color_last = "green" clip_color = "red" # get all track items from current timeline - for track_item in get_track_items(): + for track_item in track_item: container = parse_container(track_item) - + log.info("___> container: {}".format(pformat(container))) if container: # get representation from io representation = legacy_io.find_one({ @@ -1083,29 +1084,31 @@ def selection_changed_timeline(event): timeline_editor = event.sender selection = timeline_editor.selection() - selection = [ti for ti in selection - if isinstance(ti, hiero.core.TrackItem)] + track_items = get_track_items( + selection=selection, + track_type="video", + check_enabled=True, + check_locked=True, + check_tagged=True + ) # run checking function - sync_clip_name_to_data_asset(selection) - - # also mark old versions of loaded containers - check_inventory_versions() + sync_clip_name_to_data_asset(track_items) def before_project_save(event): track_items = get_track_items( - selected=False, track_type="video", check_enabled=True, check_locked=True, - check_tagged=True) + check_tagged=True + ) # run checking function sync_clip_name_to_data_asset(track_items) # also mark old versions of loaded containers - check_inventory_versions() + check_inventory_versions(track_items) def get_main_window(): From 733ad125b5798d01fe9dac9d3d24e6256d614386 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 19 May 2022 19:56:15 +0200 Subject: [PATCH 0298/1227] Hiero: one frame diff during loading --- openpype/hosts/hiero/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index 54e66bf99a..35e9d54810 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -500,7 +500,7 @@ class ClipLoader: track_item.setSource(clip) track_item.setSourceIn(self.handle_start) track_item.setTimelineIn(self.timeline_in) - track_item.setSourceOut(self.media_duration - self.handle_end) + track_item.setSourceOut((self.media_duration + 1) - self.handle_end) track_item.setTimelineOut(self.timeline_out) track_item.setPlaybackSpeed(1) self.active_track.addTrackItem(track_item) From 91af28612e367d412e70a20ece03481ed4b976e9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 20 May 2022 11:21:16 +0200 Subject: [PATCH 0299/1227] Hiero: poping found clip --- openpype/hosts/hiero/plugins/load/load_clip.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/hiero/plugins/load/load_clip.py b/openpype/hosts/hiero/plugins/load/load_clip.py index da4326c8c1..a3365253b3 100644 --- a/openpype/hosts/hiero/plugins/load/load_clip.py +++ b/openpype/hosts/hiero/plugins/load/load_clip.py @@ -3,10 +3,6 @@ from openpype.pipeline import ( get_representation_path, ) import openpype.hosts.hiero.api as phiero -# from openpype.hosts.hiero.api import plugin, lib -# reload(lib) -# reload(plugin) -# reload(phiero) class LoadClip(phiero.SequenceLoader): @@ -106,7 +102,7 @@ class LoadClip(phiero.SequenceLoader): name = container['name'] namespace = container['namespace'] track_item = phiero.get_track_items( - track_item_name=namespace) + track_item_name=namespace).pop() version = legacy_io.find_one({ "type": "version", "_id": representation["parent"] @@ -157,7 +153,7 @@ class LoadClip(phiero.SequenceLoader): # load clip to timeline and get main variables namespace = container['namespace'] track_item = phiero.get_track_items( - track_item_name=namespace) + track_item_name=namespace).pop() track = track_item.parent() # remove track item from track From 053287bc616f1370602383f2aa4891fe80358599 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 19 May 2022 20:00:01 +0200 Subject: [PATCH 0300/1227] Hiero: better logging and improving code --- openpype/hosts/hiero/api/plugin.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index 35e9d54810..174a25102f 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -1,4 +1,5 @@ import os +from pprint import pformat import re from copy import deepcopy @@ -400,7 +401,8 @@ class ClipLoader: # inject asset data to representation dict self._get_asset_data() - log.debug("__init__ self.data: `{}`".format(self.data)) + log.info("__init__ self.data: `{}`".format(pformat(self.data))) + log.info("__init__ options: `{}`".format(pformat(options))) # add active components to class if self.new_sequence: @@ -482,7 +484,9 @@ class ClipLoader: """ asset_name = self.context["representation"]["context"]["asset"] - self.data["assetData"] = openpype.get_asset(asset_name)["data"] + asset_doc = openpype.get_asset(asset_name) + log.debug("__ asset_doc: {}".format(pformat(asset_doc))) + self.data["assetData"] = asset_doc["data"] def _make_track_item(self, source_bin_item, audio=False): """ Create track item with """ @@ -527,7 +531,8 @@ class ClipLoader: if self.sequencial_load: last_track_item = lib.get_track_items( sequence_name=self.active_sequence.name(), - track_name=self.active_track.name()) + track_name=self.active_track.name() + ) if len(last_track_item) == 0: last_timeline_out = 0 else: @@ -541,6 +546,8 @@ class ClipLoader: self.timeline_in = int(self.data["assetData"]["clipIn"]) self.timeline_out = int(self.data["assetData"]["clipOut"]) + log.debug("__ self.timeline_in: {}".format(self.timeline_in)) + log.debug("__ self.timeline_out: {}".format(self.timeline_out)) # check if slate is included # either in version data families or by calculating frame diff slate_on = next( @@ -553,6 +560,7 @@ class ClipLoader: (self.timeline_out - self.timeline_in + 1) + self.handle_start + self.handle_end) < self.media_duration) + log.debug("__ slate_on: {}".format(slate_on)) # if slate is on then remove the slate frame from beginning if slate_on: self.media_duration -= 1 @@ -599,8 +607,8 @@ class Creator(LegacyCreator): rename_index = None def __init__(self, *args, **kwargs): - import openpype.hosts.hiero.api as phiero super(Creator, self).__init__(*args, **kwargs) + import openpype.hosts.hiero.api as phiero self.presets = openpype.get_current_project_settings()[ "hiero"]["create"].get(self.__class__.__name__, {}) @@ -609,7 +617,10 @@ class Creator(LegacyCreator): self.sequence = phiero.get_current_sequence() if (self.options or {}).get("useSelection"): - self.selected = phiero.get_track_items(selected=True) + timeline_selection = phiero.get_timeline_selection() + self.selected = phiero.get_track_items( + selection=timeline_selection + ) else: self.selected = phiero.get_track_items() @@ -716,6 +727,10 @@ class PublishClip: else: self.tag_data.update({"reviewTrack": None}) + log.debug("___ self.tag_data: {}".format( + pformat(self.tag_data) + )) + # create pype tag on track_item and add data lib.imprint(self.track_item, self.tag_data) From d27448e7e36d751f16b458bde328065706378092 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 19 May 2022 20:42:29 +0200 Subject: [PATCH 0301/1227] Hiero: handles and slate detection - handles should be int - slate only if in families on version --- openpype/hosts/hiero/api/plugin.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index 174a25102f..e3ec6f3cf1 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -524,9 +524,12 @@ class ClipLoader: self.handle_start = self.data["versionData"].get("handleStart") self.handle_end = self.data["versionData"].get("handleEnd") if self.handle_start is None: - self.handle_start = int(self.data["assetData"]["handleStart"]) + self.handle_start = self.data["assetData"]["handleStart"] if self.handle_end is None: - self.handle_end = int(self.data["assetData"]["handleEnd"]) + self.handle_end = self.data["assetData"]["handleEnd"] + + self.handle_start = int(self.handle_start) + self.handle_end = int(self.handle_end) if self.sequencial_load: last_track_item = lib.get_track_items( @@ -556,9 +559,7 @@ class ClipLoader: if "slate" in f), # if nothing was found then use default None # so other bool could be used - None) or bool(int( - (self.timeline_out - self.timeline_in + 1) - + self.handle_start + self.handle_end) < self.media_duration) + None) log.debug("__ slate_on: {}".format(slate_on)) # if slate is on then remove the slate frame from beginning From cac79e69031f5c9a0e082ad0b4117ce3b5339a78 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 19 May 2022 20:42:53 +0200 Subject: [PATCH 0302/1227] Hiero: fix timewarp lookup to be list --- openpype/lib/editorial.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/lib/editorial.py b/openpype/lib/editorial.py index 1ee21deedc..5fe498bf6a 100644 --- a/openpype/lib/editorial.py +++ b/openpype/lib/editorial.py @@ -218,6 +218,7 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): "name": name } tw_node.update(metadata) + tw_node["lookup"] = list(lookup) # get first and last frame offsets offset_in += lookup[0] From ffe1bff6599e0de5c0e07b30b4878714bd26e91a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 19 May 2022 21:06:11 +0200 Subject: [PATCH 0303/1227] Hiero: fix by reversing validation --- openpype/hosts/hiero/api/lib.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index d3e6441705..4dc8d26c2a 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -207,7 +207,7 @@ def get_track_items( if not isinstance(track_item, hiero.core.TrackItem): continue - if not _validate_all_atrributes( + if _validate_all_atrributes( track_item, track_item_name, track_name, @@ -1046,7 +1046,6 @@ def check_inventory_versions(track_items=None): # get all track items from current timeline for track_item in track_item: container = parse_container(track_item) - log.info("___> container: {}".format(pformat(container))) if container: # get representation from io representation = legacy_io.find_one({ From 15726cfef92bb465a794d7ccd3acb8cdc3e828bd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 19 May 2022 21:06:42 +0200 Subject: [PATCH 0304/1227] Hiero: simplify code for slate detection --- openpype/hosts/hiero/api/plugin.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index e3ec6f3cf1..8c61baa04b 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -551,17 +551,11 @@ class ClipLoader: log.debug("__ self.timeline_in: {}".format(self.timeline_in)) log.debug("__ self.timeline_out: {}".format(self.timeline_out)) - # check if slate is included - # either in version data families or by calculating frame diff - slate_on = next( - # check iterate if slate is in families - (f for f in self.context["version"]["data"]["families"] - if "slate" in f), - # if nothing was found then use default None - # so other bool could be used - None) + # check if slate is included + slate_on = "slate" in self.context["version"]["data"]["families"] log.debug("__ slate_on: {}".format(slate_on)) + # if slate is on then remove the slate frame from beginning if slate_on: self.media_duration -= 1 @@ -581,7 +575,7 @@ class ClipLoader: # there were some cases were hiero was not creating it source_bin_item = None for item in self.active_bin.items(): - if self.data["clip_name"] in item.name(): + if self.data["clip_name"] == item.name(): source_bin_item = item if not source_bin_item: log.warning("Problem with created Source clip: `{}`".format( From 38d4c3fa67effbc25847ee8706c7a1714cfa54a8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 19 May 2022 21:17:14 +0200 Subject: [PATCH 0305/1227] Hiero: lib code refactory --- openpype/hosts/hiero/api/lib.py | 37 +++++++++++++-------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 4dc8d26c2a..758df43968 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -4,7 +4,6 @@ Host specific functions where host api is connected import contextlib import os -from pprint import pformat import re import sys import platform @@ -92,7 +91,7 @@ def get_current_sequence(name=None, new=False): if not sequence: # if nothing found create new with input name sequence = get_current_sequence(name, True) - elif not name and not new: + else: # if name is none and new is False then return current open sequence sequence = hiero.ui.activeSequence() @@ -189,7 +188,6 @@ def get_track_items( log.info("___ valid trackitem: {}".format(track_item)) return_list.append(track_item) - # collect all available active sequence track items if not return_list: sequence = get_current_sequence(name=sequence_name) @@ -311,7 +309,7 @@ def set_track_item_pype_tag(track_item, data=None): "editable": "0", "note": "OpenPype data container", "icon": "openpype_icon.png", - "metadata": {k: v for k, v in data.items()} + "metadata": dict(data.items()) } # get available pype tag if any _tag = get_track_item_pype_tag(track_item) @@ -369,7 +367,7 @@ def get_track_item_pype_data(track_item): log.warning(msg) value = v - data.update({key: value}) + data[key] = value return data @@ -938,32 +936,32 @@ def apply_colorspace_clips(): def is_overlapping(ti_test, ti_original, strict=False): - covering_exp = bool( + covering_exp = ( (ti_test.timelineIn() <= ti_original.timelineIn()) and (ti_test.timelineOut() >= ti_original.timelineOut()) ) - inside_exp = bool( + inside_exp = ( (ti_test.timelineIn() >= ti_original.timelineIn()) and (ti_test.timelineOut() <= ti_original.timelineOut()) ) - overlaying_right_exp = bool( + overlaying_right_exp = ( (ti_test.timelineIn() < ti_original.timelineOut()) and (ti_test.timelineOut() >= ti_original.timelineOut()) ) - overlaying_left_exp = bool( + overlaying_left_exp = ( (ti_test.timelineOut() > ti_original.timelineIn()) and (ti_test.timelineIn() <= ti_original.timelineIn()) ) - if not strict: + if strict: + return covering_exp + else: return any(( covering_exp, inside_exp, overlaying_right_exp, overlaying_left_exp )) - else: - return covering_exp def get_sequence_pattern_and_padding(file): @@ -981,17 +979,12 @@ def get_sequence_pattern_and_padding(file): """ foundall = re.findall( r"(#+)|(%\d+d)|(?<=[^a-zA-Z0-9])(\d+)(?=\.\w+$)", file) - if foundall: - found = sorted(list(set(foundall[0])))[-1] - - if "%" in found: - padding = int(re.findall(r"\d+", found)[-1]) - else: - padding = len(found) - - return found, padding - else: + if not foundall: return None, None + found = sorted(list(set(foundall[0])))[-1] + + padding = int(re.findall(r"\d+", found)[-1]) if "%" in found else len(found) + return found, padding def sync_clip_name_to_data_asset(track_items_list): From 90948931b08dba9e4698ffc45f6a3be307996853 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 19 May 2022 19:49:08 +0200 Subject: [PATCH 0306/1227] Hiero: removing old code From 61be6857603c8061283bcbed938f07c5bd525eee Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 20 May 2022 12:35:46 +0200 Subject: [PATCH 0307/1227] replaced persistent editors with added ability of any edit trigger --- .../tools/project_manager/project_manager/view.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 25174232bc..6f5b9dc3f7 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -139,6 +139,7 @@ class HierarchyView(QtWidgets.QTreeView): self.setAlternatingRowColors(True) self.setSelectionMode(HierarchyView.ExtendedSelection) self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.setEditTriggers(HierarchyView.AllEditTriggers) column_delegates = {} column_key_to_index = {} @@ -301,16 +302,6 @@ class HierarchyView(QtWidgets.QTreeView): def rowsInserted(self, parent_index, start, end): super(HierarchyView, self).rowsInserted(parent_index, start, end) - for row in range(start, end + 1): - for key, column in self._column_key_to_index.items(): - if key not in self.persistent_columns: - continue - col_index = self._source_model.index(row, column, parent_index) - if bool( - self._source_model.flags(col_index) - & QtCore.Qt.ItemIsEditable - ): - self.openPersistentEditor(col_index) # Expand parent on insert if not self.isExpanded(parent_index): From 7c3fdcc633f80b73084b0fbf4ba6f51d4c8c8a71 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 20 May 2022 12:59:38 +0200 Subject: [PATCH 0308/1227] Hiero: tag should use deepcopy for metadata --- openpype/hosts/hiero/api/lib.py | 7 ++++--- openpype/hosts/hiero/api/tags.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 758df43968..5b2f6c814d 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -3,6 +3,7 @@ Host specific functions where host api is connected """ import contextlib +from copy import deepcopy import os import re import sys @@ -288,7 +289,7 @@ def get_track_item_pype_tag(track_item): return None for tag in _tags: # return only correct tag defined by global name - if tag.name() in self.pype_tag_name: + if tag.name() == self.pype_tag_name: return tag @@ -344,9 +345,9 @@ def get_track_item_pype_data(track_item): return None # get tag metadata attribute - tag_data = tag.metadata() + tag_data = deepcopy(dict(tag.metadata())) # convert tag metadata to normal keys names and values to correct types - for k, v in dict(tag_data).items(): + for k, v in tag_data.items(): key = k.replace("tag.", "") try: diff --git a/openpype/hosts/hiero/api/tags.py b/openpype/hosts/hiero/api/tags.py index 8877b92b9d..8c6ff2a77b 100644 --- a/openpype/hosts/hiero/api/tags.py +++ b/openpype/hosts/hiero/api/tags.py @@ -86,7 +86,7 @@ def update_tag(tag, data): # due to hiero bug we have to make sure keys which are not existent in # data are cleared of value by `None` - for _mk in mtd.keys(): + for _mk in mtd.dict().keys(): if _mk.replace("tag.", "") not in data_mtd.keys(): mtd.setValue(_mk, str(None)) From be9f6cf5c5ddd7e833f5dd5c31e690706767e493 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 20 May 2022 15:08:56 +0200 Subject: [PATCH 0309/1227] Hiero: frame difference issue --- openpype/hosts/hiero/api/otio/hiero_export.py | 2 +- openpype/hosts/hiero/api/plugin.py | 2 +- openpype/lib/editorial.py | 2 +- openpype/plugins/publish/collect_otio_frame_ranges.py | 8 ++++---- openpype/plugins/publish/collect_otio_subset_resources.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/hiero/api/otio/hiero_export.py b/openpype/hosts/hiero/api/otio/hiero_export.py index 46e1204324..1e4088d9c0 100644 --- a/openpype/hosts/hiero/api/otio/hiero_export.py +++ b/openpype/hosts/hiero/api/otio/hiero_export.py @@ -277,7 +277,7 @@ def create_otio_clip(track_item): # flip if speed is in minus source_in = track_item.sourceIn() if speed > 0 else track_item.sourceOut() - duration = int(track_item.duration()) - 1 + duration = int(track_item.duration()) fps = utils.get_rate(track_item) or self.project_fps name = track_item.name() diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index 8c61baa04b..add416d04e 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -504,7 +504,7 @@ class ClipLoader: track_item.setSource(clip) track_item.setSourceIn(self.handle_start) track_item.setTimelineIn(self.timeline_in) - track_item.setSourceOut((self.media_duration + 1) - self.handle_end) + track_item.setSourceOut((self.media_duration) - self.handle_end) track_item.setTimelineOut(self.timeline_out) track_item.setPlaybackSpeed(1) self.active_track.addTrackItem(track_item) diff --git a/openpype/lib/editorial.py b/openpype/lib/editorial.py index 5fe498bf6a..2c877b9d0d 100644 --- a/openpype/lib/editorial.py +++ b/openpype/lib/editorial.py @@ -255,7 +255,7 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): media_in + source_in + offset_in) media_out_trimmed = ( media_in + source_in + ( - (source_range.duration.value * abs( + ((source_range.duration.value - 1) * abs( time_scalar)) + offset_out)) # calculate available handles diff --git a/openpype/plugins/publish/collect_otio_frame_ranges.py b/openpype/plugins/publish/collect_otio_frame_ranges.py index ee7b7957ad..8eaf9d6f29 100644 --- a/openpype/plugins/publish/collect_otio_frame_ranges.py +++ b/openpype/plugins/publish/collect_otio_frame_ranges.py @@ -55,13 +55,13 @@ class CollectOtioFrameRanges(pyblish.api.InstancePlugin): "frameStart": frame_start, "frameEnd": frame_end, "clipIn": tl_start, - "clipOut": tl_end, + "clipOut": tl_end - 1, "clipInH": tl_start_h, - "clipOutH": tl_end_h, + "clipOutH": tl_end_h - 1, "sourceStart": src_starting_from + src_start, - "sourceEnd": src_starting_from + src_end, + "sourceEnd": src_starting_from + src_end - 1, "sourceStartH": src_starting_from + src_start_h, - "sourceEndH": src_starting_from + src_end_h, + "sourceEndH": src_starting_from + src_end_h - 1, } instance.data.update(data) self.log.debug( diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index 7c11462ef0..b89a076a44 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -66,7 +66,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): # create trimmed otio time range trimmed_media_range_h = editorial.range_from_frames( - a_frame_start_h, (a_frame_end_h - a_frame_start_h + 1), + a_frame_start_h, (a_frame_end_h - a_frame_start_h) + 1, media_fps ) trimmed_duration = trimmed_media_range_h.duration.value From f35079aaed7eb74ccca74645c800d20d8f225801 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 20 May 2022 16:18:39 +0200 Subject: [PATCH 0310/1227] global: remove exclude family for `clip` --- openpype/plugins/publish/integrate_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index bf13a4050e..353314fff2 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -113,7 +113,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "usdOverride", "simpleUnrealTexture" ] - exclude_families = ["clip", "render.farm"] + exclude_families = ["render.farm"] db_representation_context_keys = [ "project", "asset", "task", "subset", "version", "representation", "family", "hierarchy", "task", "username" From 87182f5a3e66cbae791f49c2f8cc0692f8dff3bf Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 20 May 2022 17:00:48 +0200 Subject: [PATCH 0311/1227] global: otio duration is one frame longer --- openpype/plugins/publish/extract_otio_trimming_video.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_otio_trimming_video.py b/openpype/plugins/publish/extract_otio_trimming_video.py index 30b57e2c69..e8e2994f36 100644 --- a/openpype/plugins/publish/extract_otio_trimming_video.py +++ b/openpype/plugins/publish/extract_otio_trimming_video.py @@ -80,7 +80,7 @@ class ExtractOTIOTrimmingVideo(openpype.api.Extractor): video_path = input_file_path frame_start = otio_range.start_time.value input_fps = otio_range.start_time.rate - frame_duration = (otio_range.duration.value + 1) + frame_duration = otio_range.duration.value - 1 sec_start = openpype.lib.frames_to_secons(frame_start, input_fps) sec_duration = openpype.lib.frames_to_secons(frame_duration, input_fps) From 7b65184389640165d7268996dc069600722fe60f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 20 May 2022 17:45:15 +0200 Subject: [PATCH 0312/1227] OP-2787 - replaced target farm with remote Target farm is being used for rendering, this should better differentiate it. --- openpype/hosts/maya/api/pipeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index b75af29523..c2fe8a95a5 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -73,8 +73,8 @@ def install(): "save/open/new callback installation..")) # Register default "local" target - print("Registering pyblish target: farm") - pyblish.api.register_target("farm") + print("Registering pyblish target: remote") + pyblish.api.register_target("remote") return print("Registering pyblish target: local") From c3e13a9e198a7082439588d6b3f6550bbdf98675 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 20 May 2022 18:07:26 +0200 Subject: [PATCH 0313/1227] modules have ability to modify environments before launch --- openpype/lib/applications.py | 33 ++++++++++++++++++++++++++++----- openpype/modules/base.py | 19 +++++++++++++++++++ 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index 6ade33b59c..a81bdeca0f 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -1282,7 +1282,13 @@ class EnvironmentPrepData(dict): def get_app_environments_for_context( - project_name, asset_name, task_name, app_name, env_group=None, env=None + project_name, + asset_name, + task_name, + app_name, + env_group=None, + env=None, + modules_manager=None ): """Prepare environment variables by context. Args: @@ -1293,10 +1299,12 @@ def get_app_environments_for_context( by ApplicationManager. env (dict): Initial environment variables. `os.environ` is used when not passed. + modules_manager (ModulesManager): Initialized modules manager. Returns: dict: Environments for passed context and application. """ + from openpype.pipeline import AvalonMongoDB # Avalon database connection @@ -1311,6 +1319,11 @@ def get_app_environments_for_context( "name": asset_name }) + if modules_manager is None: + from openpype.modules import ModulesManager + + modules_manager = ModulesManager() + # Prepare app object which can be obtained only from ApplciationManager app_manager = ApplicationManager() app = app_manager.applications[app_name] @@ -1334,7 +1347,7 @@ def get_app_environments_for_context( "env": env }) - prepare_app_environments(data, env_group) + prepare_app_environments(data, env_group, modules_manager) prepare_context_environments(data, env_group) # Discard avalon connection @@ -1355,9 +1368,12 @@ def _merge_env(env, current_env): return result -def _add_python_version_paths(app, env, logger): +def _add_python_version_paths(app, env, logger, modules_manager): """Add vendor packages specific for a Python version.""" + for module in modules_manager.get_enabled_modules(): + module.modify_application_launch_arguments(app, env) + # Skip adding if host name is not set if not app.host_name: return @@ -1390,7 +1406,9 @@ def _add_python_version_paths(app, env, logger): env["PYTHONPATH"] = os.pathsep.join(python_paths) -def prepare_app_environments(data, env_group=None, implementation_envs=True): +def prepare_app_environments( + data, env_group=None, implementation_envs=True, modules_manager=None +): """Modify launch environments based on launched app and context. Args: @@ -1403,7 +1421,12 @@ def prepare_app_environments(data, env_group=None, implementation_envs=True): log = data["log"] source_env = data["env"].copy() - _add_python_version_paths(app, source_env, log) + if modules_manager is None: + from openpype.modules import ModulesManager + + modules_manager = ModulesManager() + + _add_python_version_paths(app, source_env, log, modules_manager) # Use environments from local settings filtered_local_envs = {} diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 5b49649359..d591df6768 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -370,6 +370,7 @@ def _load_modules(): class _OpenPypeInterfaceMeta(ABCMeta): """OpenPypeInterface meta class to print proper string.""" + def __str__(self): return "<'OpenPypeInterface.{}'>".format(self.__name__) @@ -388,6 +389,7 @@ class OpenPypeInterface: OpenPype modules which means they have to have implemented methods defined in the interface. By default interface does not have any abstract parts. """ + pass @@ -432,10 +434,12 @@ class OpenPypeModule: It is not recommended to override __init__ that's why specific method was implemented. """ + pass def connect_with_modules(self, enabled_modules): """Connect with other enabled modules.""" + pass def get_global_environments(self): @@ -443,8 +447,22 @@ class OpenPypeModule: Environment variables that can be get only from system settings. """ + return {} + def modify_application_launch_arguments(self, app, env): + """Give option to modify launch environments before application launch. + + Implementation is optional. To change environments modify passed + dictionary of environments. + + Args: + app (Application): Application that is launcher. + env (dict): Current environemnt variables. + """ + + pass + def cli(self, module_click_group): """Add commands to click group. @@ -465,6 +483,7 @@ class OpenPypeModule: def mycommand(): print("my_command") """ + pass From 6fc240734c799a37c349f7a6e8945f7feea50ab5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 20 May 2022 18:10:40 +0200 Subject: [PATCH 0314/1227] ftrack module modify application launch environments in module instead of in prelaunch hook --- openpype/modules/base.py | 4 +- openpype/modules/ftrack/ftrack_module.py | 34 +++++++++++++++ .../ftrack/launch_hooks/pre_python2_vendor.py | 43 ------------------- 3 files changed, 36 insertions(+), 45 deletions(-) delete mode 100644 openpype/modules/ftrack/launch_hooks/pre_python2_vendor.py diff --git a/openpype/modules/base.py b/openpype/modules/base.py index d591df6768..96c1b84a54 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -450,14 +450,14 @@ class OpenPypeModule: return {} - def modify_application_launch_arguments(self, app, env): + def modify_application_launch_arguments(self, application, env): """Give option to modify launch environments before application launch. Implementation is optional. To change environments modify passed dictionary of environments. Args: - app (Application): Application that is launcher. + application (Application): Application that is launched. env (dict): Current environemnt variables. """ diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 5c38df2e03..f99e189082 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -88,6 +88,40 @@ class FtrackModule( """Implementation of `ILaunchHookPaths`.""" return os.path.join(FTRACK_MODULE_DIR, "launch_hooks") + def modify_application_launch_arguments(self, application, env): + if not application.use_python_2: + return + + self.log.info("Adding Ftrack Python 2 packages to PYTHONPATH.") + + # Prepare vendor dir path + python_2_vendor = os.path.join(FTRACK_MODULE_DIR, "python2_vendor") + + # Add Python 2 modules + python_paths = [ + # `python-ftrack-api` + os.path.join(python_2_vendor, "ftrack-python-api", "source"), + # `arrow` + os.path.join(python_2_vendor, "arrow"), + # `builtins` from `python-future` + # - `python-future` is strict Python 2 module that cause crashes + # of Python 3 scripts executed through OpenPype + # (burnin script etc.) + os.path.join(python_2_vendor, "builtins"), + # `backports.functools_lru_cache` + os.path.join( + python_2_vendor, "backports.functools_lru_cache" + ) + ] + + # Load PYTHONPATH from current launch context + python_path = env.get("PYTHONPATH") + if python_path: + python_paths.append(python_path) + + # Set new PYTHONPATH to launch context environments + env["PYTHONPATH"] = os.pathsep.join(python_paths) + def connect_with_modules(self, enabled_modules): for module in enabled_modules: if not hasattr(module, "get_ftrack_event_handler_paths"): diff --git a/openpype/modules/ftrack/launch_hooks/pre_python2_vendor.py b/openpype/modules/ftrack/launch_hooks/pre_python2_vendor.py deleted file mode 100644 index 0dd894bebf..0000000000 --- a/openpype/modules/ftrack/launch_hooks/pre_python2_vendor.py +++ /dev/null @@ -1,43 +0,0 @@ -import os -from openpype.lib import PreLaunchHook -from openpype_modules.ftrack import FTRACK_MODULE_DIR - - -class PrePython2Support(PreLaunchHook): - """Add python ftrack api module for Python 2 to PYTHONPATH. - - Path to vendor modules is added to the beggining of PYTHONPATH. - """ - - def execute(self): - if not self.application.use_python_2: - return - - self.log.info("Adding Ftrack Python 2 packages to PYTHONPATH.") - - # Prepare vendor dir path - python_2_vendor = os.path.join(FTRACK_MODULE_DIR, "python2_vendor") - - # Add Python 2 modules - python_paths = [ - # `python-ftrack-api` - os.path.join(python_2_vendor, "ftrack-python-api", "source"), - # `arrow` - os.path.join(python_2_vendor, "arrow"), - # `builtins` from `python-future` - # - `python-future` is strict Python 2 module that cause crashes - # of Python 3 scripts executed through OpenPype (burnin script etc.) - os.path.join(python_2_vendor, "builtins"), - # `backports.functools_lru_cache` - os.path.join( - python_2_vendor, "backports.functools_lru_cache" - ) - ] - - # Load PYTHONPATH from current launch context - python_path = self.launch_context.env.get("PYTHONPATH") - if python_path: - python_paths.append(python_path) - - # Set new PYTHONPATH to launch context environments - self.launch_context.env["PYTHONPATH"] = os.pathsep.join(python_paths) From feb07912c50728054a5078c23a870f445969f947 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 20 May 2022 18:27:50 +0200 Subject: [PATCH 0315/1227] Fix yeti publish and load for caches --- .../maya/plugins/load/load_yeti_cache.py | 244 +++++++++--------- .../plugins/publish/extract_yeti_cache.py | 38 +-- .../maya/plugins/publish/extract_yeti_rig.py | 4 +- 3 files changed, 146 insertions(+), 140 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_yeti_cache.py b/openpype/hosts/maya/plugins/load/load_yeti_cache.py index fb903785ae..9752188551 100644 --- a/openpype/hosts/maya/plugins/load/load_yeti_cache.py +++ b/openpype/hosts/maya/plugins/load/load_yeti_cache.py @@ -1,15 +1,13 @@ import os import json import re -import glob from collections import defaultdict -from pprint import pprint +import clique from maya import cmds from openpype.api import get_project_settings from openpype.pipeline import ( - legacy_io, load, get_representation_path ) @@ -17,7 +15,15 @@ from openpype.hosts.maya.api import lib from openpype.hosts.maya.api.pipeline import containerise +def set_attribute(node, attr, value): + """Wrapper of set attribute which ignores None values""" + if value is None: + return + lib.set_attribute(node, attr, value) + + class YetiCacheLoader(load.LoaderPlugin): + """Load Yeti Cache with one or more Yeti nodes""" families = ["yeticache", "yetiRig"] representations = ["fur"] @@ -28,6 +34,16 @@ class YetiCacheLoader(load.LoaderPlugin): color = "orange" def load(self, context, name=None, namespace=None, data=None): + """Loads a .fursettings file defining how to load .fur sequences + + A single yeticache or yetiRig can have more than a single pgYetiMaya + nodes and thus load more than a single yeti.fur sequence. + + The .fursettings file defines what the node names should be and also + what "cbId" attribute they should receive to match the original source + and allow published looks to also work for Yeti rigs and its caches. + + """ try: family = context["representation"]["context"]["family"] @@ -43,22 +59,11 @@ class YetiCacheLoader(load.LoaderPlugin): if not cmds.pluginInfo("pgYetiMaya", query=True, loaded=True): cmds.loadPlugin("pgYetiMaya", quiet=True) - # Get JSON - fbase = re.search(r'^(.+)\.(\d+|#+)\.fur', self.fname) - if not fbase: - raise RuntimeError('Cannot determine fursettings file path') - settings_fname = "{}.fursettings".format(fbase.group(1)) - with open(settings_fname, "r") as fp: - fursettings = json.load(fp) - - # Check if resources map exists - # Get node name from JSON - if "nodes" not in fursettings: - raise RuntimeError("Encountered invalid data, expect 'nodes' in " - "fursettings.") - - node_data = fursettings["nodes"] - nodes = self.create_nodes(namespace, node_data) + # Create Yeti cache nodes according to settings + settings = self.read_settings(self.fname) + nodes = [] + for node in settings["nodes"]: + nodes.extend(self.create_node(namespace, node)) group_name = "{}:{}".format(namespace, name) group_node = cmds.group(nodes, name=group_name) @@ -111,28 +116,14 @@ class YetiCacheLoader(load.LoaderPlugin): def update(self, container, representation): - legacy_io.install() namespace = container["namespace"] container_node = container["objectName"] - fur_settings = legacy_io.find_one( - {"parent": representation["parent"], "name": "fursettings"} - ) - - pprint({"parent": representation["parent"], "name": "fursettings"}) - pprint(fur_settings) - assert fur_settings is not None, ( - "cannot find fursettings representation" - ) - - settings_fname = get_representation_path(fur_settings) path = get_representation_path(representation) - # Get all node data - with open(settings_fname, "r") as fp: - settings = json.load(fp) + settings = self.read_settings(path) # Collect scene information of asset - set_members = cmds.sets(container["objectName"], query=True) + set_members = lib.get_container_members(container) container_root = lib.get_container_transforms(container, members=set_members, root=True) @@ -147,7 +138,7 @@ class YetiCacheLoader(load.LoaderPlugin): # Re-assemble metadata with cbId as keys meta_data_lookup = {n["cbId"]: n for n in settings["nodes"]} - # Compare look ups and get the nodes which ar not relevant any more + # Delete nodes by "cbId" that are not in the updated version to_delete_lookup = {cb_id for cb_id in scene_lookup.keys() if cb_id not in meta_data_lookup} if to_delete_lookup: @@ -163,25 +154,18 @@ class YetiCacheLoader(load.LoaderPlugin): fullPath=True) or [] to_remove.extend(shapes + transforms) - # Remove id from look uop + # Remove id from lookup scene_lookup.pop(_id, None) cmds.delete(to_remove) - # replace frame in filename with %04d - RE_frame = re.compile(r"(\d+)(\.fur)$") - file_name = re.sub(RE_frame, r"%04d\g<2>", os.path.basename(path)) - for cb_id, data in meta_data_lookup.items(): - - # Update cache file name - data["attrs"]["cacheFileName"] = os.path.join( - os.path.dirname(path), file_name) + for cb_id, node_settings in meta_data_lookup.items(): if cb_id not in scene_lookup: - + # Create new nodes self.log.info("Creating new nodes ..") - new_nodes = self.create_nodes(namespace, [data]) + new_nodes = self.create_node(namespace, node_settings) cmds.sets(new_nodes, addElement=container_node) cmds.parent(new_nodes, container_root) @@ -218,14 +202,8 @@ class YetiCacheLoader(load.LoaderPlugin): children=True) yeti_node = yeti_nodes[0] - for attr, value in data["attrs"].items(): - # handle empty attribute strings. Those are reported - # as None, so their type is NoneType and this is not - # supported on attributes in Maya. We change it to - # empty string. - if value is None: - value = "" - lib.set_attribute(attr, value, yeti_node) + for attr, value in node_settings["attrs"].items(): + set_attribute(attr, value, yeti_node) cmds.setAttr("{}.representation".format(container_node), str(representation["_id"]), @@ -235,7 +213,6 @@ class YetiCacheLoader(load.LoaderPlugin): self.update(container, representation) # helper functions - def create_namespace(self, asset): """Create a unique namespace Args: @@ -253,100 +230,129 @@ class YetiCacheLoader(load.LoaderPlugin): return namespace - def validate_cache(self, filename, pattern="%04d"): - """Check if the cache has more than 1 frame + def get_cache_node_filepath(self, root, node_name): + """Get the cache file path for one of the yeti nodes. - All caches with more than 1 frame need to be called with `%04d` - If the cache has only one frame we return that file name as we assume + All caches with more than 1 frame need cache file name set with `%04d` + If the cache has only one frame we return the file name as we assume it is a snapshot. + This expects the files to be named after the "node name" through + exports with in Yeti. + Args: - filename(str) - pattern(str) + root(str): Folder containing cache files to search in. + node_name(str): Node name to search cache files for Returns: - str + str: Cache file path value needed for cacheFileName attribute """ - glob_pattern = filename.replace(pattern, "*") + name = node_name.replace(":", "_") + pattern = r"^({name})(\.[0-4]+)?(\.fur)$".format(name=re.escape(name)) - escaped = re.escape(filename) - re_pattern = escaped.replace(pattern, "-?[0-9]+") - - files = glob.glob(glob_pattern) - files = [str(f) for f in files if re.match(re_pattern, f)] + files = [fname for fname in os.listdir(root) if re.match(pattern, + fname)] + if not files: + self.log.error("Could not find cache files for '{}' " + "with pattern {}".format(node_name, pattern)) + return if len(files) == 1: - return files[0] - elif len(files) == 0: - self.log.error("Could not find cache files for '%s'" % filename) + # Single file + return os.path.join(root, files[0]) - return filename + # Get filename for the sequence with padding + collections, remainder = clique.assemble(files) + assert not remainder, "This is a bug" + assert len(collections) == 1, "This is a bug" + collection = collections[0] - def create_nodes(self, namespace, settings): + # Assume padding from the first frame since clique returns 0 if the + # sequence contains no files padded with a zero at the start (e.g. + # a sequence starting at 1001) + padding = len(str(collection.indexes[0])) + + fname = "{head}%0{padding}d{tail}".format(collection.head, + padding, + collection.tail) + + return os.path.join(root, fname) + + def create_node(self, namespace, node_settings): """Create nodes with the correct namespace and settings Args: namespace(str): namespace - settings(list): list of dictionaries + node_settings(dict): Single "nodes" entry from .fursettings file. Returns: - list + list: Created nodes """ - nodes = [] - for node_settings in settings: - # Create pgYetiMaya node - original_node = node_settings["name"] - node_name = "{}:{}".format(namespace, original_node) - yeti_node = cmds.createNode("pgYetiMaya", name=node_name) + # Get original names and ids + orig_transform_name = node_settings["transform"]["name"] + orig_shape_name = node_settings["name"] - # Create transform node - transform_node = node_name.rstrip("Shape") + # Add namespace + transform_name = "{}:{}".format(namespace, orig_transform_name) + shape_name = "{}:{}".format(namespace, orig_shape_name) - lib.set_id(transform_node, node_settings["transform"]["cbId"]) - lib.set_id(yeti_node, node_settings["cbId"]) + # Create pgYetiMaya node + transform_node = cmds.createNode("transform", + name=transform_name) + yeti_node = cmds.createNode("pgYetiMaya", + name=shape_name, + parent=transform_node) - nodes.extend([transform_node, yeti_node]) + lib.set_id(transform_node, node_settings["transform"]["cbId"]) + lib.set_id(yeti_node, node_settings["cbId"]) - # Ensure the node has no namespace identifiers - attributes = node_settings["attrs"] + nodes.extend([transform_node, yeti_node]) - # Check if cache file name is stored + # Update attributes with defaults + attributes = node_settings["attrs"] + attributes.update({ + "viewportDensity": 0.1, + "verbosity": 2, + "fileMode": 1, - # get number of # in path and convert it to C prinf format - # like %04d expected by Yeti - fbase = re.search(r'^(.+)\.(\d+|#+)\.fur', self.fname) - if not fbase: - raise RuntimeError('Cannot determine file path') - padding = len(fbase.group(2)) - if "cacheFileName" not in attributes: - cache = "{}.%0{}d.fur".format(fbase.group(1), padding) + # Fix render stats, like Yeti's own + # ../scripts/pgYetiNode.mel script + "visibleInReflections": True, + "visibleInRefractions": True + }) - self.validate_cache(cache) - attributes["cacheFileName"] = cache + # Apply attributes to pgYetiMaya node + for attr, value in attributes.items(): + set_attribute(attr, value, yeti_node) - # Update attributes with requirements - attributes.update({"viewportDensity": 0.1, - "verbosity": 2, - "fileMode": 1}) - - # Apply attributes to pgYetiMaya node - for attr, value in attributes.items(): - if value is None: - continue - lib.set_attribute(attr, value, yeti_node) - - # Fix for : YETI-6 - # Fixes the render stats (this is literally taken from Perigrene's - # ../scripts/pgYetiNode.mel script) - cmds.setAttr("{}.visibleInReflections".format(yeti_node), True) - cmds.setAttr("{}.visibleInRefractions".format(yeti_node), True) - - # Connect to the time node - cmds.connectAttr("time1.outTime", "%s.currentTime" % yeti_node) + # Connect to the time node + cmds.connectAttr("time1.outTime", "%s.currentTime" % yeti_node) return nodes + + def read_settings(self, path): + """Read .fursettings file and compute some additional attributes""" + + with open(path, "r") as fp: + fur_settings = json.load(fp) + + if "nodes" not in fur_settings: + raise RuntimeError("Encountered invalid data, " + "expected 'nodes' in fursettings.") + + # Compute the cache file name values we want to set for the nodes + root = os.path.dirname(path) + for node in fur_settings["nodes"]: + cache_filename = self.get_cache_node_filepath( + root=root, node_name=node["name"]) + + attrs = node.get("attrs", {}) # allow 'attrs' to not exist + attrs["cacheFileName"] = cache_filename + node["attrs"] = attrs + + return fur_settings diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py b/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py index b0a60b77f4..cf6db00e9a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py @@ -25,9 +25,6 @@ class ExtractYetiCache(openpype.api.Extractor): # Define extract output file path dirname = self.staging_dir(instance) - # Yeti related staging dirs - data_file = os.path.join(dirname, "yeti.fursettings") - # Collect information for writing cache start_frame = instance.data["frameStartHandle"] end_frame = instance.data["frameEndHandle"] @@ -57,32 +54,35 @@ class ExtractYetiCache(openpype.api.Extractor): cache_files = [x for x in os.listdir(dirname) if x.endswith(".fur")] self.log.info("Writing metadata file") - settings = instance.data.get("fursettings", None) - if settings is not None: - with open(data_file, "w") as fp: - json.dump(settings, fp, ensure_ascii=False) + settings = instance.data["fursettings"] + fursettings_path = os.path.join(dirname, "yeti.fursettings") + with open(fursettings_path, "w") as fp: + json.dump(settings, fp, ensure_ascii=False) # build representations if "representations" not in instance.data: instance.data["representations"] = [] self.log.info("cache files: {}".format(cache_files[0])) - instance.data["representations"].append( - { - 'name': 'fur', - 'ext': 'fur', - 'files': cache_files[0] if len(cache_files) == 1 else cache_files, - 'stagingDir': dirname, - 'frameStart': int(start_frame), - 'frameEnd': int(end_frame) - } - ) + + # Workaround: We do not explicitly register these files with the + # representation solely so that we can write multiple sequences + # a single Subset without renaming - it's a bit of a hack + # TODO: Implement better way to manage this sort of integration + if 'transfers' not in instance.data: + instance.data['transfers'] = [] + + publish_dir = instance.data["publishDir"] + for cache_filename in cache_files: + src = os.path.join(dirname, cache_filename) + dst = os.path.join(publish_dir, os.path.basename(cache_filename)) + instance.data['transfers'].append([src, dst]) instance.data["representations"].append( { - 'name': 'fursettings', + 'name': 'fur', 'ext': 'fursettings', - 'files': os.path.basename(data_file), + 'files': os.path.basename(fursettings_path), 'stagingDir': dirname } ) diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py index f981c4fe50..6e21bffa4e 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py @@ -124,8 +124,8 @@ class ExtractYetiRig(openpype.api.Extractor): settings_path = os.path.join(dirname, "yeti.rigsettings") # Yeti related staging dirs - maya_path = os.path.join( - dirname, "yeti_rig.{}".format(self.scene_type)) + maya_path = os.path.join(dirname, + "yeti_rig.{}".format(self.scene_type)) self.log.info("Writing metadata file") From 87f7fa5470ed5fb74433633810bf379d92a3b58e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 20 May 2022 18:32:02 +0200 Subject: [PATCH 0316/1227] Format name correctly for sequences on load --- openpype/hosts/maya/plugins/load/load_yeti_cache.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_yeti_cache.py b/openpype/hosts/maya/plugins/load/load_yeti_cache.py index 9752188551..8435ba2493 100644 --- a/openpype/hosts/maya/plugins/load/load_yeti_cache.py +++ b/openpype/hosts/maya/plugins/load/load_yeti_cache.py @@ -269,15 +269,8 @@ class YetiCacheLoader(load.LoaderPlugin): assert len(collections) == 1, "This is a bug" collection = collections[0] - # Assume padding from the first frame since clique returns 0 if the - # sequence contains no files padded with a zero at the start (e.g. - # a sequence starting at 1001) - padding = len(str(collection.indexes[0])) - - fname = "{head}%0{padding}d{tail}".format(collection.head, - padding, - collection.tail) - + # Formats name as {head}%d{tail} like cache.%04d.fur + fname = collection.format("{head}{padding}{tail}") return os.path.join(root, fname) def create_node(self, namespace, node_settings): From 38d58182fc9f7d908eca61ab573b7562d1e8af97 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 20 May 2022 18:44:11 +0200 Subject: [PATCH 0317/1227] OP-2787 - added updating of script url Remote publish requires path to script which is known only on DL node. Injection of env var is required for remote publish. --- .../repository/custom/plugins/GlobalJobPreLoad.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index eeb1f7744c..bcd853f374 100644 --- a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -87,6 +87,13 @@ def inject_openpype_environment(deadlinePlugin): for key, value in contents.items(): deadlinePlugin.SetProcessEnvironmentVariable(key, value) + script_url = job.GetJobPluginInfoKeyValue("ScriptFilename") + if script_url: + + script_url = script_url.format(**contents).replace("\\", "/") + print(">>> Setting script path {}".format(script_url)) + job.SetJobPluginInfoKeyValue("ScriptFilename", script_url) + print(">>> Removing temporary file") os.remove(export_url) @@ -196,16 +203,19 @@ def __main__(deadlinePlugin): job.GetJobEnvironmentKeyValue('OPENPYPE_RENDER_JOB') or '0' openpype_publish_job = \ job.GetJobEnvironmentKeyValue('OPENPYPE_PUBLISH_JOB') or '0' + openpype_remote_job = \ + job.GetJobEnvironmentKeyValue('OPENPYPE_REMOTE_JOB') or '0' print("--- Job type - render {}".format(openpype_render_job)) print("--- Job type - publish {}".format(openpype_publish_job)) + print("--- Job type - remote {}".format(openpype_remote_job)) if openpype_publish_job == '1' and openpype_render_job == '1': raise RuntimeError("Misconfiguration. Job couldn't be both " + "render and publish.") if openpype_publish_job == '1': inject_render_job_id(deadlinePlugin) - elif openpype_render_job == '1': + elif openpype_render_job == '1' or openpype_remote_job == '1': inject_openpype_environment(deadlinePlugin) else: pype(deadlinePlugin) # backward compatibility with Pype2 From 529c31c4f912d66bece87a17eb597c1ec5dd86ad Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 20 May 2022 19:02:17 +0200 Subject: [PATCH 0318/1227] OP-2787 - updated validator Checks for exactly 1 out set. --- .../hosts/maya/plugins/publish/validate_animation_content.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/validate_animation_content.py b/openpype/hosts/maya/plugins/publish/validate_animation_content.py index bcea761a01..7638c44b87 100644 --- a/openpype/hosts/maya/plugins/publish/validate_animation_content.py +++ b/openpype/hosts/maya/plugins/publish/validate_animation_content.py @@ -30,6 +30,10 @@ class ValidateAnimationContent(pyblish.api.InstancePlugin): assert 'out_hierarchy' in instance.data, "Missing `out_hierarchy` data" + out_sets = [node for node in instance if node.endswith("out_SET")] + msg = "Couldn't find exactly one out_SET: {0}".format(out_sets) + assert len(out_sets) == 1, msg + # All nodes in the `out_hierarchy` must be among the nodes that are # in the instance. The nodes in the instance are found from the top # group, as such this tests whether all nodes are under that top group. From 62ac633da95772488f94ea4b9d53c2c5a74e9b9c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 20 May 2022 19:05:35 +0200 Subject: [PATCH 0319/1227] OP-2787 - used settings from ProcessSubmittedJobOnFarm --- .../submit_maya_remote_publish_deadline.py | 67 +++++++++---------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py index 761bc8cc95..b11698f8e8 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py @@ -4,18 +4,22 @@ import requests from maya import cmds from openpype.pipeline import legacy_io +from openpype.settings import get_project_settings import pyblish.api -class MayaSubmitRemotePublishDeadline(pyblish.api.ContextPlugin): +class MayaSubmitRemotePublishDeadline(pyblish.api.InstancePlugin): """Submit Maya scene to perform a local publish in Deadline. Publishing in Deadline can be helpful for scenes that publish very slow. This way it can process in the background on another machine without the Artist having to wait for the publish to finish on their local machine. - Submission is done through the Deadline Web Service. + Submission is done through the Deadline Web Service. DL then triggers + `openpype/scripts/remote_publish.py`. + + Each publishable instance creates its own full publish job. Different from `ProcessSubmittedJobOnFarm` which creates publish job depending on metadata json containing context and instance data of @@ -27,31 +31,24 @@ class MayaSubmitRemotePublishDeadline(pyblish.api.ContextPlugin): hosts = ["maya"] families = ["deadline"] - # custom deadline attributes - deadline_department = "" - deadline_pool = "" - deadline_pool_secondary = "" - deadline_group = "" - deadline_chunk_size = 1 - deadline_priority = 50 - - def process(self, context): + def process(self, instance): + settings = get_project_settings(os.getenv("AVALON_PROJECT")) + # use setting for publish job on farm, no reason to have it separately + deadline_publish_job_sett = (settings["deadline"] + ["publish"] + ["ProcessSubmittedJobOnFarm"]) # Ensure no errors so far - assert all(result["success"] for result in context.data["results"]), ( - "Errors found, aborting integration..") + assert (all(result["success"] + for result in instance.context.data["results"]), + ("Errors found, aborting integration..")) - # Note that `publish` data member might change in the future. - # See: https://github.com/pyblish/pyblish-base/issues/307 - actives = [i for i in context if i.data["publish"]] - instance_names = sorted(instance.name for instance in actives) - - if not instance_names: + if not instance.data["publish"]: self.log.warning("No active instances found. " "Skipping submission..") return - scene = context.data["currentFile"] + scene = instance.context.data["currentFile"] scenename = os.path.basename(scene) # Get project code @@ -66,17 +63,15 @@ class MayaSubmitRemotePublishDeadline(pyblish.api.ContextPlugin): "JobInfo": { "Plugin": "MayaBatch", "BatchName": batch_name, - "Priority": 50, "Name": job_name, - "UserName": context.data["user"], - # "Comment": instance.context.data.get("comment", ""), + "UserName": instance.context.data["user"], + "Comment": instance.context.data.get("comment", ""), # "InitialStatus": state - "Department": self.deadline_department, - "ChunkSize": self.deadline_chunk_size, - "Priority": self.deadline_priority, - - "Group": self.deadline_group, - + "Department": deadline_publish_job_sett["deadline_department"], + "ChunkSize": deadline_publish_job_sett["deadline_chunk_size"], + "Priority": deadline_publish_job_sett["deadline_priority"], + "Group": deadline_publish_job_sett["deadline_group"], + "Pool": deadline_publish_job_sett["deadline_pool"], }, "PluginInfo": { @@ -86,7 +81,7 @@ class MayaSubmitRemotePublishDeadline(pyblish.api.ContextPlugin): # Inputs "SceneFile": scene, - "ScriptFilename": "{OPENPYPE_ROOT}/scripts/remote_publish.py", + "ScriptFilename": "{OPENPYPE_REPOS_ROOT}/openpype/scripts/remote_publish.py", # noqa # Mandatory for Deadline "Version": cmds.about(version=True), @@ -116,10 +111,9 @@ class MayaSubmitRemotePublishDeadline(pyblish.api.ContextPlugin): environment["AVALON_TASK"] = legacy_io.Session["AVALON_TASK"] environment["AVALON_APP_NAME"] = os.environ.get("AVALON_APP_NAME") environment["OPENPYPE_LOG_NO_COLORS"] = "1" - environment["OPENPYPE_USERNAME"] = context.data["user"] - environment["OPENPYPE_PUBLISH_JOB"] = "1" - environment["OPENPYPE_RENDER_JOB"] = "0" - environment["PYBLISH_ACTIVE_INSTANCES"] = ",".join(instance_names) + environment["OPENPYPE_REMOTE_JOB"] = "1" + environment["OPENPYPE_USERNAME"] = instance.context.data["user"] + environment["OPENPYPE_PUBLISH_SUBSET"] = instance.data["subset"] payload["JobInfo"].update({ "EnvironmentKeyValue%d" % index: "{key}={value}".format( @@ -129,7 +123,10 @@ class MayaSubmitRemotePublishDeadline(pyblish.api.ContextPlugin): }) self.log.info("Submitting Deadline job ...") - deadline_url = context.data["defaultDeadline"] + deadline_url = instance.context.data["defaultDeadline"] + # if custom one is set in instance, use that + if instance.data.get("deadlineUrl"): + deadline_url = instance.data.get("deadlineUrl") assert deadline_url, "Requires Deadline Webservice URL" url = "{}/api/jobs".format(deadline_url) response = requests.post(url, json=payload, timeout=10) From bf75d18a7b6852415cbb71b69a8a6a05c0ea3754 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 20 May 2022 19:07:21 +0200 Subject: [PATCH 0320/1227] OP-2787 - added collector for remote publishable instances Filters instances from a workfile and marks only these that should be published on a farm. --- .../publish/collect_publishable_instances.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 openpype/modules/deadline/plugins/publish/collect_publishable_instances.py diff --git a/openpype/modules/deadline/plugins/publish/collect_publishable_instances.py b/openpype/modules/deadline/plugins/publish/collect_publishable_instances.py new file mode 100644 index 0000000000..9a467428fd --- /dev/null +++ b/openpype/modules/deadline/plugins/publish/collect_publishable_instances.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +"""Collect instances that should be processed and published on DL. + +""" +import os + +import pyblish.api + + +class CollectDeadlinePublishableInstances(pyblish.api.InstancePlugin): + """Collect instances that should be processed and published on DL. + + Some long running publishes (not just renders) could be offloaded to DL, + this plugin compares theirs name against env variable, marks only + publishable by farm. + + Triggered only when running only in headless mode, eg on a farm. + """ + + order = pyblish.api.CollectorOrder + 0.499 + label = "Collect Deadline Publishable Instance" + targets = ["remote"] + + def process(self, instance): + self.log.debug("CollectDeadlinePublishableInstances") + publish_inst = os.environ.get("OPENPYPE_PUBLISH_SUBSET", '') + assert (publish_inst, + "OPENPYPE_PUBLISH_SUBSET env var required for " + "remote publishing") + + subset_name = instance.data["subset"] + if subset_name == publish_inst: + self.log.debug("Publish {}".format(subset_name)) + instance.data["publish"] = True + instance.data["farm"] = False + instance.data["families"].remove("deadline") + else: + self.log.debug("Skipping {}".format(subset_name)) + instance.data["publish"] = False From 72d8633266048ba19b78d425c879aa0325ba042b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 20 May 2022 19:09:35 +0200 Subject: [PATCH 0321/1227] OP-2787 - changed flag from family to farm It probably makes more sense to check specific flag than a family. --- openpype/plugins/publish/integrate_new.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 1a4112107a..b5a7f11904 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -139,7 +139,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): ef, instance.data["family"], instance.data["families"])) return - if "deadline" in instance.data["families"]: + # instance should be published on a farm + if instance.data["farm"]: return self.integrated_file_sizes = {} From 6b71ff1909c3d32e5bebc0595580f1dcde1c1180 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 20 May 2022 19:25:55 +0200 Subject: [PATCH 0322/1227] OP-2787 - removed deadline family deadline family is not used anymore anywhere, filtering on integrate is being done on instance.data["farm"] flag. --- .../maya/plugins/publish/collect_animation.py | 3 --- .../maya/plugins/publish/collect_pointcache.py | 14 -------------- 2 files changed, 17 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/publish/collect_pointcache.py diff --git a/openpype/hosts/maya/plugins/publish/collect_animation.py b/openpype/hosts/maya/plugins/publish/collect_animation.py index b442113fbc..9b1e38fd0a 100644 --- a/openpype/hosts/maya/plugins/publish/collect_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_animation.py @@ -55,6 +55,3 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): # Store data in the instance for the validator instance.data["out_hierarchy"] = hierarchy - - if instance.data.get("farm"): - instance.data["families"].append("deadline") diff --git a/openpype/hosts/maya/plugins/publish/collect_pointcache.py b/openpype/hosts/maya/plugins/publish/collect_pointcache.py deleted file mode 100644 index b55babe372..0000000000 --- a/openpype/hosts/maya/plugins/publish/collect_pointcache.py +++ /dev/null @@ -1,14 +0,0 @@ -import pyblish.api - - -class CollectPointcache(pyblish.api.InstancePlugin): - """Collect pointcache data for instance.""" - - order = pyblish.api.CollectorOrder + 0.4 - families = ["pointcache"] - label = "Collect Pointcache" - hosts = ["maya"] - - def process(self, instance): - if instance.data.get("farm"): - instance.data["families"].append("deadline") \ No newline at end of file From a176228396083c4eefc1de798c579c67ff65fd4d Mon Sep 17 00:00:00 2001 From: Jiri Sindelar <45896205+jrsndl@users.noreply.github.com> Date: Mon, 23 May 2022 13:19:53 +0200 Subject: [PATCH 0323/1227] Update openpype/plugins/publish/extract_review_slate.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/extract_review_slate.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 832799601c..52d988a6b0 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -130,8 +130,9 @@ class ExtractReviewSlate(openpype.api.Extractor): input_audio = True break # Get duration of one frame in micro seconds - one_frame_duration = "40000us" - if input_frame_rate: + if not input_frame_rate: + one_frame_duration = "40000us" + else: items = input_frame_rate.split("/") if len(items) == 1: one_frame_duration = float(1.0) / float(items[0]) From bf80eee17816bf1876d32d0d37c486ec9db54aa3 Mon Sep 17 00:00:00 2001 From: Jiri Sindelar <45896205+jrsndl@users.noreply.github.com> Date: Mon, 23 May 2022 13:20:10 +0200 Subject: [PATCH 0324/1227] Update openpype/plugins/publish/extract_review_slate.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/extract_review_slate.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 52d988a6b0..3bf7d00f7b 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -138,6 +138,8 @@ class ExtractReviewSlate(openpype.api.Extractor): one_frame_duration = float(1.0) / float(items[0]) elif len(items) == 2: one_frame_duration = float(items[1]) / float(items[0]) + else: + one_frame_duration = 0 one_frame_duration *= 1000000 one_frame_duration = str(int(one_frame_duration)) + "us" self.log.debug( From 9244389b585b654f92dbf76a8e5ed2692ac4cb3b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 23 May 2022 17:16:44 +0200 Subject: [PATCH 0325/1227] OP-2790 - safer querying of farm flag --- openpype/plugins/publish/integrate_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index b5a7f11904..fa0582c65a 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -140,7 +140,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): return # instance should be published on a farm - if instance.data["farm"]: + if instance.data.get("farm"): return self.integrated_file_sizes = {} From 5d6ce5592dfcb02444e9d7f86e9feab6eaebf53d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 23 May 2022 17:47:54 +0200 Subject: [PATCH 0326/1227] Hiero: rolled back py3 compatible code make it py27 working --- openpype/hosts/hiero/api/lib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 5b2f6c814d..ae0aef9e9b 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -171,7 +171,7 @@ def get_track_items( # get selected track items or all in active sequence if selection: - with contextlib.suppress(AttributeError): + try: for track_item in selection: log.info("___ track_item: {}".format(track_item)) # make sure only trackitems are selected @@ -188,6 +188,8 @@ def get_track_items( ): log.info("___ valid trackitem: {}".format(track_item)) return_list.append(track_item) + except AttributeError: + pass # collect all available active sequence track items if not return_list: From 2e5acf6221e5063517870c93046979e79b243e49 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 23 May 2022 17:50:09 +0200 Subject: [PATCH 0327/1227] hound --- openpype/hosts/hiero/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index ae0aef9e9b..d19cefd2da 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -2,7 +2,6 @@ Host specific functions where host api is connected """ -import contextlib from copy import deepcopy import os import re @@ -986,7 +985,8 @@ def get_sequence_pattern_and_padding(file): return None, None found = sorted(list(set(foundall[0])))[-1] - padding = int(re.findall(r"\d+", found)[-1]) if "%" in found else len(found) + padding = int( + re.findall(r"\d+", found)[-1]) if "%" in found else len(found) return found, padding From c0ee519dba5f24c8040bc08f910c7024697b621d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 24 May 2022 12:59:33 +0200 Subject: [PATCH 0328/1227] Hound --- openpype/hosts/flame/api/lib.py | 12 +++++++----- openpype/hosts/hiero/api/lib.py | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 6dc7d3d887..d59308ad6c 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -779,7 +779,6 @@ class MediaInfoFile(object): feed_dir = os.path.dirname(path) feed_ext = os.path.splitext(feed_basename)[1][1:].lower() - with maintained_temp_file_path(".clip") as tmp_path: self.log.info("Temp File: {}".format(tmp_path)) self._generate_media_info_file(tmp_path, feed_ext, feed_dir) @@ -827,9 +826,11 @@ class MediaInfoFile(object): # make sure partial input basename is having correct extensoon if not partialname: - raise AttributeError("Wrong input attributes. Basename - {}, Ext - {}".format( - feed_basename, feed_ext - )) + raise AttributeError( + "Wrong input attributes. Basename - {}, Ext - {}".format( + feed_basename, feed_ext + ) + ) # get all related files files = [ @@ -860,7 +861,8 @@ class MediaInfoFile(object): # convert to multiple collections _continues_colls = collection.separate() for _coll in _continues_colls: - coll_to_text = self._format_collection(_coll, len(number_from_path)) + coll_to_text = self._format_collection( + _coll, len(number_from_path)) self.log.debug("__ coll_to_text: {}".format(coll_to_text)) if search_number_pattern in coll_to_text: return coll_to_text diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 5b2f6c814d..5a9f38bf92 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -984,7 +984,8 @@ def get_sequence_pattern_and_padding(file): return None, None found = sorted(list(set(foundall[0])))[-1] - padding = int(re.findall(r"\d+", found)[-1]) if "%" in found else len(found) + padding = int( + re.findall(r"\d+", found)[-1]) if "%" in found else len(found) return found, padding From 0e0bbd56992a8ee8e7c32d90e56606623bb3781c Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 24 May 2022 15:31:01 +0100 Subject: [PATCH 0329/1227] Fix camera in UE5 --- .../hosts/unreal/plugins/load/load_camera.py | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index b33e45b6e9..0072dd9e73 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -57,6 +57,33 @@ class CameraLoader(plugin.Loader): min_frame_j, max_frame_j + 1) + def _import_camera( + self, world, sequence, bindings, import_fbx_settings, import_filename + ): + ue_version = unreal.SystemLibrary.get_engine_version().split('.') + ue_major = int(ue_version[0]) + ue_minor = int(ue_version[1]) + + if ue_major == 4 and ue_minor <= 26: + unreal.SequencerTools.import_fbx( + world, + sequence, + bindings, + import_fbx_settings, + import_filename + ) + elif (ue_major == 4 and ue_minor >= 27) or ue_major == 5: + unreal.SequencerTools.import_level_sequence_fbx( + world, + sequence, + bindings, + import_fbx_settings, + import_filename + ) + else: + raise NotImplementedError( + f"Unreal version {ue_major} not supported") + def load(self, context, name, namespace, data): """ Load and containerise representation into Content Browser. @@ -228,7 +255,7 @@ class CameraLoader(plugin.Loader): settings.set_editor_property('reduce_keys', False) if cam_seq: - unreal.SequencerTools.import_fbx( + self._import_camera( EditorLevelLibrary.get_editor_world(), cam_seq, cam_seq.get_bindings(), @@ -388,7 +415,7 @@ class CameraLoader(plugin.Loader): sub_scene.set_sequence(new_sequence) - unreal.SequencerTools.import_fbx( + self._import_camera( EditorLevelLibrary.get_editor_world(), new_sequence, new_sequence.get_bindings(), From 9c72873a9a8ac462abf463cc86c1c04dcfecaf8b Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 24 May 2022 15:32:29 +0100 Subject: [PATCH 0330/1227] Fix animations in UE5 --- openpype/hosts/unreal/plugins/load/load_animation.py | 12 ++++++++---- openpype/hosts/unreal/plugins/load/load_layout.py | 6 ++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_animation.py b/openpype/hosts/unreal/plugins/load/load_animation.py index 60c1526d3d..54b43c500c 100644 --- a/openpype/hosts/unreal/plugins/load/load_animation.py +++ b/openpype/hosts/unreal/plugins/load/load_animation.py @@ -77,13 +77,15 @@ class AnimationFBXLoader(plugin.Loader): task.options.anim_sequence_import_data.set_editor_property( 'import_meshes_in_bone_hierarchy', False) task.options.anim_sequence_import_data.set_editor_property( - 'use_default_sample_rate', True) + 'use_default_sample_rate', False) + task.options.anim_sequence_import_data.set_editor_property( + 'custom_sample_rate', 25.0) # TODO: get from database task.options.anim_sequence_import_data.set_editor_property( 'import_custom_attribute', True) task.options.anim_sequence_import_data.set_editor_property( 'import_bone_tracks', True) task.options.anim_sequence_import_data.set_editor_property( - 'remove_redundant_keys', True) + 'remove_redundant_keys', False) task.options.anim_sequence_import_data.set_editor_property( 'convert_scene', True) @@ -279,13 +281,15 @@ class AnimationFBXLoader(plugin.Loader): task.options.anim_sequence_import_data.set_editor_property( 'import_meshes_in_bone_hierarchy', False) task.options.anim_sequence_import_data.set_editor_property( - 'use_default_sample_rate', True) + 'use_default_sample_rate', False) + task.options.anim_sequence_import_data.set_editor_property( + 'custom_sample_rate', 25.0) # TODO: get from database task.options.anim_sequence_import_data.set_editor_property( 'import_custom_attribute', True) task.options.anim_sequence_import_data.set_editor_property( 'import_bone_tracks', True) task.options.anim_sequence_import_data.set_editor_property( - 'remove_redundant_keys', True) + 'remove_redundant_keys', False) task.options.anim_sequence_import_data.set_editor_property( 'convert_scene', True) diff --git a/openpype/hosts/unreal/plugins/load/load_layout.py b/openpype/hosts/unreal/plugins/load/load_layout.py index 412f77e3a9..49611c6c05 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout.py +++ b/openpype/hosts/unreal/plugins/load/load_layout.py @@ -262,13 +262,15 @@ class LayoutLoader(plugin.Loader): task.options.anim_sequence_import_data.set_editor_property( 'import_meshes_in_bone_hierarchy', False) task.options.anim_sequence_import_data.set_editor_property( - 'use_default_sample_rate', True) + 'use_default_sample_rate', False) + task.options.anim_sequence_import_data.set_editor_property( + 'custom_sample_rate', 25.0) # TODO: get from database task.options.anim_sequence_import_data.set_editor_property( 'import_custom_attribute', True) task.options.anim_sequence_import_data.set_editor_property( 'import_bone_tracks', True) task.options.anim_sequence_import_data.set_editor_property( - 'remove_redundant_keys', True) + 'remove_redundant_keys', False) task.options.anim_sequence_import_data.set_editor_property( 'convert_scene', True) From 1b76f86d6691b0106ce2ae9022666979103cf57d Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 24 May 2022 15:35:47 +0100 Subject: [PATCH 0331/1227] Fix render create in UE5 --- openpype/hosts/unreal/plugins/create/create_render.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/unreal/plugins/create/create_render.py b/openpype/hosts/unreal/plugins/create/create_render.py index 3b6c7a9f1e..a3e125a94e 100644 --- a/openpype/hosts/unreal/plugins/create/create_render.py +++ b/openpype/hosts/unreal/plugins/create/create_render.py @@ -22,17 +22,24 @@ class CreateRender(Creator): ar = unreal.AssetRegistryHelpers.get_asset_registry() + # The asset name is the the third element of the path which contains + # the map. + # The index of the split path is 3 because the first element is an + # empty string, as the path begins with "/Content". + a = unreal.EditorUtilityLibrary.get_selected_assets()[0] + asset_name = a.get_path_name().split("/")[3] + # Get the master sequence and the master level. # There should be only one sequence and one level in the directory. filter = unreal.ARFilter( class_names=["LevelSequence"], - package_paths=[f"/Game/OpenPype/{self.data['asset']}"], + package_paths=[f"/Game/OpenPype/{asset_name}"], recursive_paths=False) sequences = ar.get_assets(filter) ms = sequences[0].get_editor_property('object_path') filter = unreal.ARFilter( class_names=["World"], - package_paths=[f"/Game/OpenPype/{self.data['asset']}"], + package_paths=[f"/Game/OpenPype/{asset_name}"], recursive_paths=False) levels = ar.get_assets(filter) ml = levels[0].get_editor_property('object_path') From 5ee40df9c13b0eaddd94810b5177061d2a1dde87 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 24 May 2022 17:25:30 +0200 Subject: [PATCH 0332/1227] Nuke: passing bake presets to instance data for downstream processing --- .../nuke/plugins/publish/extract_review_data_mov.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py index 2a79d600ba..384ed5f2ef 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py @@ -1,4 +1,5 @@ import os +from pprint import pformat import re import pyblish.api import openpype @@ -50,6 +51,7 @@ class ExtractReviewDataMov(openpype.api.Extractor): with maintained_selection(): generated_repres = [] for o_name, o_data in self.outputs.items(): + self.log.debug("o_name: {}, o_data: {}".format(o_name, pformat(o_data))) f_families = o_data["filter"]["families"] f_task_types = o_data["filter"]["task_types"] f_subsets = o_data["filter"]["subsets"] @@ -88,7 +90,13 @@ class ExtractReviewDataMov(openpype.api.Extractor): # check if settings have more then one preset # so we dont need to add outputName to representation # in case there is only one preset - multiple_presets = bool(len(self.outputs.keys()) > 1) + multiple_presets = len(self.outputs.keys()) > 1 + + # adding bake presets to instance data for other plugins + if not instance.data.get("bakePresets"): + instance.data["bakePresets"] = {} + # add preset to bakePresets + instance.data["bakePresets"][o_name] = o_data # create exporter instance exporter = plugin.ExporterReviewMov( From 3da82c845ff13a85884744988a529cc9ca4abb52 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 24 May 2022 17:26:05 +0200 Subject: [PATCH 0333/1227] Nuke: multi bake stream slate creation --- .../plugins/publish/extract_slate_frame.py | 70 +++++++++++++------ 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index fb52fc18b4..ed936abb91 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -1,4 +1,5 @@ import os +from pprint import pformat import nuke import copy @@ -15,7 +16,7 @@ class ExtractSlateFrame(openpype.api.Extractor): """ - order = pyblish.api.ExtractorOrder - 0.001 + order = pyblish.api.ExtractorOrder + 0.011 label = "Extract Slate Frame" families = ["slate"] @@ -40,15 +41,33 @@ class ExtractSlateFrame(openpype.api.Extractor): self.log.debug("instance.data[families]: {}".format( instance.data["families"])) - self.render_slate(instance) + if instance.data.get("bakePresets"): + for o_name, o_data in instance.data["bakePresets"].items(): + self.log.info("_ o_name: {}, o_data: {}".format(o_name, pformat(o_data))) + self.render_slate(instance, o_name, **o_data) + else: + viewer_process_swithes = { + "bake_viewer_process": True, + "bake_viewer_input_process": True + } + self.render_slate(instance, None, **viewer_process_swithes) + + def render_slate(self, instance, output_name=None, **kwargs): + # solve output name if any is set + _output_name = output_name or "" + if _output_name: + _output_name = "_" + _output_name + + bake_viewer_process = kwargs["bake_viewer_process"] + bake_viewer_input_process_node = kwargs[ + "bake_viewer_input_process"] - def render_slate(self, instance): node_subset_name = instance.data.get("name", None) node = instance[0] # group node self.log.info("Creating staging dir...") if "representations" not in instance.data: - instance.data["representations"] = list() + instance.data["representations"] = [] staging_dir = os.path.normpath( os.path.dirname(instance.data['path'])) @@ -76,7 +95,7 @@ class ExtractSlateFrame(openpype.api.Extractor): "{head}{padding}{tail}")) fhead = collection.format("{head}") - collected_frames_len = int(len(collection.indexes)) + collected_frames_len = len(collection.indexes) # get first and last frame first_frame = min(collection.indexes) - 1 @@ -100,24 +119,34 @@ class ExtractSlateFrame(openpype.api.Extractor): previous_node = node - # get input process and connect it to baking - ipn = self.get_view_process_node() - if ipn is not None: - ipn.setInput(0, previous_node) - previous_node = ipn - temporary_nodes.append(ipn) + # only create colorspace baking if toggled on + if bake_viewer_process: + if bake_viewer_input_process_node: + # get input process and connect it to baking + ipn = self.get_view_process_node() + if ipn is not None: + ipn.setInput(0, previous_node) + previous_node = ipn + temporary_nodes.append(ipn) - if not self.viewer_lut_raw: - dag_node = nuke.createNode("OCIODisplay") - dag_node.setInput(0, previous_node) - previous_node = dag_node - temporary_nodes.append(dag_node) + if not self.viewer_lut_raw: + dag_node = nuke.createNode("OCIODisplay") + dag_node.setInput(0, previous_node) + previous_node = dag_node + temporary_nodes.append(dag_node) # create write node write_node = nuke.createNode("Write") - file = fhead + "slate.png" + file = fhead[:-1] + _output_name + "_slate.png" path = os.path.join(staging_dir, file).replace("\\", "/") - instance.data["slateFrame"] = path + + # add slate path to `slateFrames` instance data attr + if not instance.data.get("slateFrames"): + instance.data["slateFrames"] = {} + + instance.data["slateFrames"][output_name or "*"] = path + + # create write node write_node["file"].setValue(path) write_node["file_type"].setValue("png") write_node["raw"].setValue(1) @@ -132,9 +161,6 @@ class ExtractSlateFrame(openpype.api.Extractor): # also render slate as sequence frame nuke.execute(node_subset_name, int(first_frame), int(last_frame)) - self.log.debug( - "slate frame path: {}".format(instance.data["slateFrame"])) - # Clean up for node in temporary_nodes: nuke.delete(node) @@ -221,5 +247,5 @@ class ExtractSlateFrame(openpype.api.Extractor): )) except NameError: self.log.warning(( - "Failed to set value \"{}\" on node attribute \"{}\"" + "Failed to set value \"{0}\" on node attribute \"{0}\"" ).format(value)) From 05b96bd0237c663adb14311ef83d4981aae7a14d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 24 May 2022 17:26:41 +0200 Subject: [PATCH 0334/1227] Nuke: multi bake stream thumbnail creation --- .../nuke/plugins/publish/extract_thumbnail.py | 51 +++++++++++++------ 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index ef6d486ca2..03ae57f281 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -17,7 +17,7 @@ class ExtractThumbnail(openpype.api.Extractor): """ - order = pyblish.api.ExtractorOrder + 0.01 + order = pyblish.api.ExtractorOrder + 0.011 label = "Extract Thumbnail" families = ["review"] @@ -35,9 +35,26 @@ class ExtractThumbnail(openpype.api.Extractor): self.log.debug("instance.data[families]: {}".format( instance.data["families"])) - self.render_thumbnail(instance) + if instance.data.get("bakePresets"): + for o_name, o_data in instance.data["bakePresets"].items(): + self.render_thumbnail(instance, o_name, **o_data) + else: + viewer_process_swithes = { + "bake_viewer_process": True, + "bake_viewer_input_process": True + } + self.render_thumbnail(instance, None, **viewer_process_swithes) + + def render_thumbnail(self, instance, output_name=None, **kwargs): + # solve output name if any is set + output_name = output_name or "" + if output_name: + output_name = "_" + output_name + + bake_viewer_process = kwargs["bake_viewer_process"] + bake_viewer_input_process_node = kwargs[ + "bake_viewer_input_process"] - def render_thumbnail(self, instance): node = instance[0] # group node self.log.info("Creating staging dir...") @@ -89,15 +106,7 @@ class ExtractThumbnail(openpype.api.Extractor): else: previous_node = node - # get input process and connect it to baking - ipn = self.get_view_process_node() - if ipn is not None: - ipn.setInput(0, previous_node) - previous_node = ipn - temporary_nodes.append(ipn) - reformat_node = nuke.createNode("Reformat") - ref_node = self.nodes.get("Reformat", None) if ref_node: for k, v in ref_node: @@ -110,14 +119,24 @@ class ExtractThumbnail(openpype.api.Extractor): previous_node = reformat_node temporary_nodes.append(reformat_node) - dag_node = nuke.createNode("OCIODisplay") - dag_node.setInput(0, previous_node) - previous_node = dag_node - temporary_nodes.append(dag_node) + # only create colorspace baking if toggled on + if bake_viewer_process: + if bake_viewer_input_process_node: + # get input process and connect it to baking + ipn = self.get_view_process_node() + if ipn is not None: + ipn.setInput(0, previous_node) + previous_node = ipn + temporary_nodes.append(ipn) + + dag_node = nuke.createNode("OCIODisplay") + dag_node.setInput(0, previous_node) + previous_node = dag_node + temporary_nodes.append(dag_node) # create write node write_node = nuke.createNode("Write") - file = fhead + "jpg" + file = fhead[:-1] + output_name + ".jpg" name = "thumbnail" path = os.path.join(staging_dir, file).replace("\\", "/") instance.data["thumbnail"] = path From 6b90f0a2c1a66583d73d525f7c5d33eebc3ed197 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 24 May 2022 17:27:19 +0200 Subject: [PATCH 0335/1227] Global: multi bake stream slate processing --- .../plugins/publish/extract_review_slate.py | 68 +++++++++++++------ 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 49f0eac41d..af1d2b2983 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -1,4 +1,6 @@ import os +from pprint import pformat +import re import openpype.api import pyblish from openpype.lib import ( @@ -30,27 +32,11 @@ class ExtractReviewSlate(openpype.api.Extractor): raise RuntimeError("Burnin needs already created mov to work on.") suffix = "_slate" - slate_path = inst_data.get("slateFrame") + slates_data = inst_data["slateFrames"] + self.log.info("_ slates_data: {}".format(pformat(slates_data))) + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") - slate_streams = get_ffprobe_streams(slate_path, self.log) - # Try to find first stream with defined 'width' and 'height' - # - this is to avoid order of streams where audio can be as first - # - there may be a better way (checking `codec_type`?)+ - slate_width = None - slate_height = None - for slate_stream in slate_streams: - if "width" in slate_stream and "height" in slate_stream: - slate_width = int(slate_stream["width"]) - slate_height = int(slate_stream["height"]) - break - - # Raise exception of any stream didn't define input resolution - if slate_width is None: - raise AssertionError(( - "FFprobe couldn't read resolution from input file: \"{}\"" - ).format(slate_path)) - if "reviewToWidth" in inst_data: use_legacy_code = True else: @@ -77,6 +63,12 @@ class ExtractReviewSlate(openpype.api.Extractor): input_path, self.log ) + # get slate data + slate_path = self._get_slate_path(input_file, slates_data) + self.log.info("_ slate_path: {}".format(slate_path)) + + slate_width, slate_height = self._get_slates_resolution(slate_path) + # Try to find first stream with defined 'width' and 'height' # - this is to avoid order of streams where audio can be as first # - there may be a better way (checking `codec_type`?) @@ -309,6 +301,44 @@ class ExtractReviewSlate(openpype.api.Extractor): self.log.debug(inst_data["representations"]) + def _get_slate_path(self, input_file, slates_data): + slate_path = None + for sl_n, _slate_path in slates_data.items(): + if "*" in sl_n: + slate_path = _slate_path + break + elif re.search(sl_n, input_file): + slate_path = _slate_path + break + + if not slate_path: + raise AttributeError( + "Missing slates paths: {}".format(slates_data)) + + return slate_path + + + def _get_slates_resolution(self, slate_path): + slate_streams = get_ffprobe_streams(slate_path, self.log) + # Try to find first stream with defined 'width' and 'height' + # - this is to avoid order of streams where audio can be as first + # - there may be a better way (checking `codec_type`?)+ + slate_width = None + slate_height = None + for slate_stream in slate_streams: + if "width" in slate_stream and "height" in slate_stream: + slate_width = int(slate_stream["width"]) + slate_height = int(slate_stream["height"]) + break + + # Raise exception of any stream didn't define input resolution + if slate_width is None: + raise AssertionError(( + "FFprobe couldn't read resolution from input file: \"{}\"" + ).format(slate_path)) + + return (slate_width, slate_height) + def add_video_filter_args(self, args, inserting_arg): """ Fixing video filter argumets to be one long string From 8453f9710f1fb305d917723eeee4cfea5c007062 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 25 May 2022 06:27:52 +0300 Subject: [PATCH 0336/1227] Add profiles handling in schema aand for extractor --- openpype/plugins/publish/extract_jpeg_exr.py | 27 ++++++++++++- .../defaults/project_settings/deadline.json | 2 +- .../defaults/project_settings/global.json | 12 +++++- .../schemas/schema_global_publish.json | 38 +++++++++++++++++++ 4 files changed, 76 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 2d3ad1e8a8..c0363caf5b 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -1,10 +1,15 @@ import os import pyblish.api +from openpype.pipeline import ( + legacy_io, + KnownPublishError +) from openpype.lib import ( get_ffmpeg_tool_path, get_oiio_tools_path, + filter_profiles, run_subprocess, path_to_subprocess_arg, @@ -33,6 +38,26 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): oiio_args = None def process(self, instance): + task_name = instance.data.get("task", legacy_io.Session["AVALON_TASK"]) + host_name = legacy_io.Session["AVALON_APP"] + family = instance.data["family"] + filtering_criteria = { + "hosts": host_name, + "families": family, + "tasks": task_name + } + profile = filter_profiles(self.profiles, filtering_criteria, + logger=self.log) + if not profile: + return + + oiio_path = get_oiio_tools_path() + # Raise an exception when oiiotool is not available + + if not os.path.exists(oiio_path): + KnownPublishError( + "OpenImageIO tool is not available on this machine." + ) self.log.info("subset {}".format(instance.data['subset'])) # skip crypto passes. @@ -121,7 +146,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.append(path_to_subprocess_arg(full_output_path)) subprocess_command = " ".join(jpeg_items) - + # run subprocess self.log.debug("{}".format(subprocess_command)) try: # temporary until oiiotool is supported cross platform diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index f0b2a7e555..5c5a14bf21 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -98,4 +98,4 @@ } } } -} +} \ No newline at end of file diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index cedd0eed99..4ad56d8086 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -40,7 +40,17 @@ "-apply_trc gamma22" ], "output": [] - } + }, + "thumbnail_extraction_profiles": [ + { + "families": [ + "" + ], + "hosts": [], + "task_types": [], + "tasks": [] + } + ] }, "ExtractReview": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index a3cbf0cfcd..4149c99348 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -152,6 +152,44 @@ "label": "FFmpeg output arguments" } ] + }, + { + "type": "list", + "key": "thumbnail_extraction_profiles", + "label": "Thumbnail Extraction profiles", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "label", + "label": "" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "type": "hosts-enum", + "key": "hosts", + "label": "Hosts", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "tasks", + "label": "Task names", + "type": "list", + "object_type": "text" + } + ] + } } ] }, From 0040f02ef71bd6156139c184170041d94f146a40 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 25 May 2022 06:58:22 +0300 Subject: [PATCH 0337/1227] Style fix --- openpype/plugins/publish/extract_jpeg_exr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index c0363caf5b..0ad39aa720 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -53,7 +53,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): oiio_path = get_oiio_tools_path() # Raise an exception when oiiotool is not available - + if not os.path.exists(oiio_path): KnownPublishError( "OpenImageIO tool is not available on this machine." From 8e0b0156cad9fdf8d2c439bb4dc4078f50d468df Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 25 May 2022 12:24:20 +0300 Subject: [PATCH 0338/1227] Fix comment typo --- openpype/lib/transcoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index adb9bb2c3a..ee9a0f08de 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -533,7 +533,7 @@ def convert_input_paths_for_ffmpeg( output_dir, logger=None ): - """Contert source file to format supported in ffmpeg. + """Convert source file to format supported in ffmpeg. Currently can convert only exrs. The input filepaths should be files with same type. Information about input is loaded only from first found From 043ba41de7ba619be04b98e321deaa16487407d5 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 25 May 2022 12:59:53 +0300 Subject: [PATCH 0339/1227] Remove whitespace --- openpype/plugins/publish/extract_jpeg_exr.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 0ad39aa720..070e9e1e08 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -144,7 +144,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # output file jpeg_items.append(path_to_subprocess_arg(full_output_path)) - subprocess_command = " ".join(jpeg_items) # run subprocess From 73a0d6fc70c847ab6114b1985dd0013282fb9a9a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 25 May 2022 12:08:01 +0200 Subject: [PATCH 0340/1227] extracted some functionality into separated functions --- .../plugins/publish/extract_review_slate.py | 275 +++++++++++------- 1 file changed, 171 insertions(+), 104 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 7b2df4dc5f..a42fbbbfe1 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -78,76 +78,26 @@ class ExtractReviewSlate(openpype.api.Extractor): input_path, self.log ) # Get video metadata - for stream in streams: - input_timecode = "" - input_width = None - input_height = None - input_frame_rate = None - if "codec_type" in stream: - if stream["codec_type"] == "video": - self.log.debug("__Ffprobe Video: {}".format(stream)) - tags = stream.get("tags") or {} - input_timecode = tags.get("timecode") or "" - if "width" in stream and "height" in stream: - input_width = int(stream.get("width")) - input_height = int(stream.get("height")) - if "r_frame_rate" in stream: - # get frame rate in a form of - # x/y, like 24000/1001 for 23.976 - input_frame_rate = stream.get("r_frame_rate") - if ( - input_width - and input_height - and input_frame_rate - ): - input_frame_rate = str(input_frame_rate) - break + ( + input_width, + input_height, + input_timecode, + input_frame_rate + ) = self._get_video_metadata(streams) + # Raise exception of any stream didn't define input resolution if input_width is None: raise AssertionError(( "FFprobe couldn't read resolution from input file: \"{}\"" ).format(input_path)) - # Get audio metadata - for stream in streams: - audio_channels = None - audio_sample_rate = None - audio_channel_layout = None - input_audio = False - if stream["codec_type"] == "audio": - self.log.debug("__Ffprobe Audio: {}".format(stream)) - if stream["channels"]: - audio_channels = str(stream.get("channels")) - if stream["sample_rate"]: - audio_sample_rate = str(stream.get("sample_rate")) - if stream["channel_layout"]: - audio_channel_layout = str( - stream.get("channel_layout")) - if stream["codec_name"]: - audio_codec = str( - stream.get("codec_name")) - if ( - audio_channels - and audio_sample_rate - and audio_channel_layout - and audio_codec - ): - input_audio = str(input_audio) - audio_sample_rate = str(audio_sample_rate) - audio_channel_layout = str(audio_channel_layout) - audio_codec = str(audio_codec) - break - # Get duration of one frame in micro seconds - one_frame_duration = "40000us" - if input_frame_rate: - items = input_frame_rate.split("/") - if len(items) == 1: - one_frame_duration = float(1.0) / float(items[0]) - elif len(items) == 2: - one_frame_duration = float(items[1]) / float(items[0]) - one_frame_duration *= 1000000 - one_frame_duration = str(int(one_frame_duration)) + "us" - self.log.debug( - "One frame duration is {}".format(one_frame_duration)) + + ( + audio_codec, + audio_channels, + audio_sample_rate, + audio_channel_layout, + input_audio + ) = self._get_audio_metadata(streams) # values are set in ExtractReview if use_legacy_code: @@ -205,14 +155,16 @@ class ExtractReviewSlate(openpype.api.Extractor): else: input_args.extend(repre["outputDef"].get('input', [])) - input_args.append("-loop 1 -i {}".format( - openpype.lib.path_to_subprocess_arg(slate_path))) - input_args.extend(["-r {}".format(input_frame_rate)]) - input_args.extend(["-frames:v 1"]) + input_args.extend([ + "-loop", "1", + "-i", openpype.lib.path_to_subprocess_arg(slate_path), + "-r", str(input_frame_rate), + "-frames:v", "1", + ]) + # add timecode from source to the slate, substract one frame offset_timecode = "" if input_timecode: - offset_timecode = str(input_timecode) offset_timecode = self._tc_offset( str(input_timecode), framerate=fps, @@ -221,7 +173,8 @@ class ExtractReviewSlate(openpype.api.Extractor): self.log.debug("Slate Timecode: `{}`".format( offset_timecode )) - input_args.extend(["-timecode {}".format(offset_timecode)]) + input_args.extend(["-timecode", str(offset_timecode)]) + if use_legacy_code: format_args = [] codec_args = repre["_profile"].get('codec', []) @@ -236,10 +189,10 @@ class ExtractReviewSlate(openpype.api.Extractor): # make sure colors are correct output_args.extend([ - "-vf scale=out_color_matrix=bt709", - "-color_primaries bt709", - "-color_trc bt709", - "-colorspace bt709" + "-vf", "scale=out_color_matrix=bt709", + "-color_primaries", "bt709", + "-color_trc", "bt709", + "-colorspace", "bt709", ]) # scaling none square pixels and 1920 width @@ -275,13 +228,20 @@ class ExtractReviewSlate(openpype.api.Extractor): "__ height_half_pad: `{}`".format(height_half_pad) ) - scaling_arg = ("scale={0}x{1}:flags=lanczos," - "pad={2}:{3}:{4}:{5}:black,setsar=1").format( - width_scale, height_scale, to_width, to_height, - width_half_pad, height_half_pad + scaling_arg = ( + "scale={0}x{1}:flags=lanczos" + ",pad={2}:{3}:{4}:{5}:black" + ",setsar=1" + ",fps={6}" + ).format( + width_scale, + height_scale, + to_width, + to_height, + width_half_pad, + height_half_pad, + input_frame_rate ) - # add output frame rate as a filter, just in case - scaling_arg += ",fps={}".format(input_frame_rate) vf_back = self.add_video_filter_args(output_args, scaling_arg) # add it to output_args @@ -317,30 +277,14 @@ class ExtractReviewSlate(openpype.api.Extractor): slate_silent_path = "_silent".join( os.path.splitext(slate_v_path)) _remove_at_end.append(slate_silent_path) - - slate_silent_args = [ + self._create_silent_slate( ffmpeg_path, - "-i", slate_v_path, - "-f", "lavfi", "-i", - "anullsrc=r={}:cl={}:d={}".format( - audio_sample_rate, - audio_channel_layout, - one_frame_duration - ), - "-c:v", "copy", - "-c:a", audio_codec, - "-map", "0:v", - "-map", "1:a", - "-shortest", - "-y", - slate_silent_path - ] - # run slate generation subprocess - self.log.debug( - "Silent Slate Executing: {}".format( - " ".join(slate_silent_args))) - openpype.api.run_subprocess( - slate_silent_args, logger=self.log + slate_v_path, + slate_silent_path, + audio_codec, + audio_channels, + audio_sample_rate, + audio_channel_layout, ) # replace slate with silent slate for concat @@ -426,6 +370,129 @@ class ExtractReviewSlate(openpype.api.Extractor): self.log.debug(inst_data["representations"]) + def _get_video_metadata(self, streams): + input_timecode = "" + input_width = None + input_height = None + input_frame_rate = None + for stream in streams: + if stream.get("codec_type") != "video": + continue + self.log.debug("FFprobe Video: {}".format(stream)) + + if "width" not in stream or "height" not in stream: + continue + width = int(stream["width"]) + height = int(stream["height"]) + if not width or not height: + continue + + # Make sure that width and height are captured even if frame rate + # is not available + input_width = width + input_height = height + + tags = stream.get("tags") or {} + input_timecode = tags.get("timecode") or "" + + input_frame_rate = stream.get("r_frame_rate") + if input_frame_rate is not None: + break + return ( + input_width, + input_height, + input_timecode, + input_frame_rate + ) + + def _get_audio_metadata(self, streams): + # Get audio metadata + audio_codec = None + audio_channels = None + audio_sample_rate = None + audio_channel_layout = None + input_audio = False + + for stream in streams: + if stream.get("codec_type") != "audio": + continue + self.log.debug("__Ffprobe Audio: {}".format(stream)) + + if all( + stream.get(key) + for key in ( + "codec_name", + "channels", + "sample_rate", + "channel_layout", + ) + ): + audio_codec = stream["codec_name"] + audio_channels = stream["channels"] + audio_sample_rate = stream["sample_rate"] + audio_channel_layout = stream["channel_layout"] + input_audio = True + break + + return ( + audio_codec, + audio_channels, + audio_sample_rate, + audio_channel_layout, + input_audio, + ) + + def _create_silent_slate( + self, + ffmpeg_path, + src_path, + dst_path, + audio_codec, + audio_channels, + audio_sample_rate, + audio_channel_layout, + ): + # Get duration of one frame in micro seconds + items = audio_sample_rate.split("/") + if len(items) == 1: + one_frame_duration = 1.0 / float(items[0]) + elif len(items) == 2: + one_frame_duration = float(items[1]) / float(items[0]) + else: + one_frame_duration = None + + if one_frame_duration is None: + one_frame_duration = "40000us" + else: + one_frame_duration *= 1000000 + one_frame_duration = str(int(one_frame_duration)) + "us" + self.log.debug("One frame duration is {}".format(one_frame_duration)) + + slate_silent_args = [ + ffmpeg_path, + "-i", src_path, + "-f", "lavfi", "-i", + "anullsrc=r={}:cl={}:d={}".format( + audio_sample_rate, + audio_channel_layout, + one_frame_duration + ), + "-c:v", "copy", + "-c:a", audio_codec, + "-map", "0:v", + "-map", "1:a", + "-shortest", + "-y", + dst_path + ] + # run slate generation subprocess + self.log.debug("Silent Slate Executing: {}".format( + " ".join(slate_silent_args) + )) + openpype.api.run_subprocess( + slate_silent_args, logger=self.log + ) + def add_video_filter_args(self, args, inserting_arg): """ Fixing video filter argumets to be one long string From 4c3914c6c263efe68d466a542c26908a1e12739d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 25 May 2022 12:12:46 +0200 Subject: [PATCH 0341/1227] fix double space --- openpype/plugins/publish/extract_review_slate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index a42fbbbfe1..a2cbc1b704 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -159,7 +159,7 @@ class ExtractReviewSlate(openpype.api.Extractor): "-loop", "1", "-i", openpype.lib.path_to_subprocess_arg(slate_path), "-r", str(input_frame_rate), - "-frames:v", "1", + "-frames:v", "1", ]) # add timecode from source to the slate, substract one frame From c9b0bb0e54cbd3390f06ee8ea3135ffd5e9413e5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 25 May 2022 13:32:45 +0200 Subject: [PATCH 0342/1227] make sure chunk size is at least 1 --- openpype/modules/ftrack/lib/avalon_sync.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 124787e467..e4ba651bfd 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -143,14 +143,17 @@ def create_chunks(iterable, chunk_size=None): list: Chunked items. """ chunks = [] - if not iterable: - return chunks tupled_iterable = tuple(iterable) + if not tupled_iterable: + return chunks iterable_size = len(tupled_iterable) if chunk_size is None: chunk_size = 200 + if chunk_size < 1: + chunk_size = 1 + for idx in range(0, iterable_size, chunk_size): chunks.append(tupled_iterable[idx:idx + chunk_size]) return chunks From 0b9bdbf1e61d586b9d29799cc01fbbb8f6c78ca6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 25 May 2022 16:56:46 +0200 Subject: [PATCH 0343/1227] Nuke: abstracting functions used in plugins --- openpype/hosts/nuke/api/__init__.py | 9 ++++++- openpype/hosts/nuke/api/lib.py | 39 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/__init__.py b/openpype/hosts/nuke/api/__init__.py index b571c4098c..77fe4503d3 100644 --- a/openpype/hosts/nuke/api/__init__.py +++ b/openpype/hosts/nuke/api/__init__.py @@ -26,7 +26,11 @@ from .pipeline import ( update_container, ) from .lib import ( - maintained_selection + maintained_selection, + reset_selection, + get_view_process_node, + duplicate_node + ) from .utils import ( @@ -58,6 +62,9 @@ __all__ = ( "update_container", "maintained_selection", + "reset_selection", + "get_view_process_node", + "duplicate_node", "colorspace_exists_on_node", "get_colorspace_list" diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index ba8aa7a8db..3871381451 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -707,6 +707,20 @@ def get_imageio_input_colorspace(filename): return preset_clrsp +def get_view_process_node(): + reset_selection() + + ipn_orig = None + for v in nuke.allNodes(filter="Viewer"): + ipn = v['input_process_node'].getValue() + if "VIEWER_INPUT" not in ipn: + ipn_orig = nuke.toNode(ipn) + ipn_orig.setSelected(True) + + if ipn_orig: + return duplicate_node(ipn_orig) + + def on_script_load(): ''' Callback for ffmpeg support ''' @@ -2549,6 +2563,31 @@ class DirmapCache: return cls._sync_module +def duplicate_node(node): + reset_selection() + + # select required node for duplication + node_orig = nuke.toNode(node.name()) + node_orig.setSelected(True) + + # copy selected to clipboard + nuke.nodeCopy("%clipboard%") + + # reset selection + reset_selection() + + # paste node and selection is on it only + nuke.nodePaste("%clipboard%") + + # assign to variable + dupli_node = nuke.selectedNode() + + # reset selection + reset_selection() + + return dupli_node + + def dirmap_file_name_filter(file_name): """Nuke callback function with single full path argument. From 6477257d4241446ce06bf09f7b840b4804c4bbfe Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 25 May 2022 16:59:33 +0200 Subject: [PATCH 0344/1227] Nuke: connecting to abstracted functions --- openpype/hosts/nuke/api/plugin.py | 41 +++---------------- .../nuke/plugins/publish/extract_thumbnail.py | 34 +++------------ 2 files changed, 10 insertions(+), 65 deletions(-) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 2bad6f2c78..f2e17a4c89 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -14,11 +14,11 @@ from openpype.pipeline import ( from .lib import ( Knobby, check_subsetname_exists, - reset_selection, maintained_selection, set_avalon_knob_data, add_publish_knob, - get_nuke_imageio_settings + get_nuke_imageio_settings, + get_view_process_node ) @@ -215,37 +215,6 @@ class ExporterReview(object): self.data["representations"].append(repre) - def get_view_input_process_node(self): - """ - Will get any active view process. - - Arguments: - self (class): in object definition - - Returns: - nuke.Node: copy node of Input Process node - """ - reset_selection() - ipn_orig = None - for v in nuke.allNodes(filter="Viewer"): - ip = v["input_process"].getValue() - ipn = v["input_process_node"].getValue() - if "VIEWER_INPUT" not in ipn and ip: - ipn_orig = nuke.toNode(ipn) - ipn_orig.setSelected(True) - - if ipn_orig: - # copy selected to clipboard - nuke.nodeCopy("%clipboard%") - # reset selection - reset_selection() - # paste node and selection is on it only - nuke.nodePaste("%clipboard%") - # assign to variable - ipn = nuke.selectedNode() - - return ipn - def get_imageio_baking_profile(self): from . import lib as opnlib nuke_imageio = opnlib.get_nuke_imageio_settings() @@ -310,7 +279,7 @@ class ExporterReviewLut(ExporterReview): self._temp_nodes = [] self.log.info("Deleted nodes...") - def generate_lut(self): + def generate_lut(self, **kwargs): bake_viewer_process = kwargs["bake_viewer_process"] bake_viewer_input_process_node = kwargs[ "bake_viewer_input_process"] @@ -328,7 +297,7 @@ class ExporterReviewLut(ExporterReview): if bake_viewer_process: # Node View Process if bake_viewer_input_process_node: - ipn = self.get_view_input_process_node() + ipn = get_view_process_node() if ipn is not None: # connect ipn.setInput(0, self.previous_node) @@ -519,7 +488,7 @@ class ExporterReviewMov(ExporterReview): if bake_viewer_process: if bake_viewer_input_process_node: # View Process node - ipn = self.get_view_input_process_node() + ipn = get_view_process_node() if ipn is not None: # connect ipn.setInput(0, self.previous_node) diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index 03ae57f281..4b1c42cd12 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -3,7 +3,10 @@ import os import nuke import pyblish.api import openpype -from openpype.hosts.nuke.api.lib import maintained_selection +from openpype.hosts.nuke.api import ( + maintained_selection, + get_view_process_node +) if sys.version_info[0] >= 3: @@ -123,7 +126,7 @@ class ExtractThumbnail(openpype.api.Extractor): if bake_viewer_process: if bake_viewer_input_process_node: # get input process and connect it to baking - ipn = self.get_view_process_node() + ipn = get_view_process_node() if ipn is not None: ipn.setInput(0, previous_node) previous_node = ipn @@ -174,30 +177,3 @@ class ExtractThumbnail(openpype.api.Extractor): # Clean up for node in temporary_nodes: nuke.delete(node) - - def get_view_process_node(self): - - # Select only the target node - if nuke.selectedNodes(): - [n.setSelected(False) for n in nuke.selectedNodes()] - - ipn_orig = None - for v in [n for n in nuke.allNodes() - if "Viewer" == n.Class()]: - ip = v['input_process'].getValue() - ipn = v['input_process_node'].getValue() - if "VIEWER_INPUT" not in ipn and ip: - ipn_orig = nuke.toNode(ipn) - ipn_orig.setSelected(True) - - if ipn_orig: - nuke.nodeCopy('%clipboard%') - - # Deselect all - [n.setSelected(False) for n in nuke.selectedNodes()] - - nuke.nodePaste('%clipboard%') - - ipn = nuke.selectedNode() - - return ipn From 3b021e155e92abb8d1a0847e2dde9c409ba61d06 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 25 May 2022 17:00:05 +0200 Subject: [PATCH 0345/1227] Nuke: refactory extract slate frame to bake viewer process only to thumbnail image --- .../plugins/publish/extract_slate_frame.py | 179 +++++++++--------- 1 file changed, 89 insertions(+), 90 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index ed936abb91..576f8b3440 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -6,7 +6,11 @@ import copy import pyblish.api import openpype -from openpype.hosts.nuke.api.lib import maintained_selection +from openpype.hosts.nuke.api import ( + maintained_selection, + duplicate_node, + get_view_process_node +) class ExtractSlateFrame(openpype.api.Extractor): @@ -30,11 +34,20 @@ class ExtractSlateFrame(openpype.api.Extractor): "f_vfx_scope_of_work": [False, ""] } + # constants + SLATE_TO_SEQUENCE_DONE = None + def process(self, instance): - if hasattr(self, "viewer_lut_raw"): - self.viewer_lut_raw = self.viewer_lut_raw - else: - self.viewer_lut_raw = False + self.fpath = instance.data["path"] + self.first_frame = instance.data["frameStartHandle"] + self.last_frame = instance.data["frameEndHandle"] + + self.log.info("Creating staging dir...") + + if "representations" not in instance.data: + instance.data["representations"] = [] + + self._create_staging_dir(instance) with maintained_selection(): self.log.debug("instance: {}".format(instance)) @@ -43,7 +56,8 @@ class ExtractSlateFrame(openpype.api.Extractor): if instance.data.get("bakePresets"): for o_name, o_data in instance.data["bakePresets"].items(): - self.log.info("_ o_name: {}, o_data: {}".format(o_name, pformat(o_data))) + self.log.info("_ o_name: {}, o_data: {}".format( + o_name, pformat(o_data))) self.render_slate(instance, o_name, **o_data) else: viewer_process_swithes = { @@ -52,7 +66,21 @@ class ExtractSlateFrame(openpype.api.Extractor): } self.render_slate(instance, None, **viewer_process_swithes) + def _create_staging_dir(self, instance): + staging_dir = os.path.normpath( + os.path.dirname(self.fpath)) + + instance.data["stagingDir"] = staging_dir + + self.log.info( + "StagingDir `{0}`...".format(instance.data["stagingDir"])) + def render_slate(self, instance, output_name=None, **kwargs): + slate_node = instance.data["slateNode"] + + # fill slate node with comments + self.add_comment_slate_node(instance, slate_node) + # solve output name if any is set _output_name = output_name or "" if _output_name: @@ -62,31 +90,8 @@ class ExtractSlateFrame(openpype.api.Extractor): bake_viewer_input_process_node = kwargs[ "bake_viewer_input_process"] - node_subset_name = instance.data.get("name", None) - node = instance[0] # group node - self.log.info("Creating staging dir...") + slate_first_frame = self.first_frame - 1 - if "representations" not in instance.data: - instance.data["representations"] = [] - - staging_dir = os.path.normpath( - os.path.dirname(instance.data['path'])) - - instance.data["stagingDir"] = staging_dir - - self.log.info( - "StagingDir `{0}`...".format(instance.data["stagingDir"])) - - frame_start = instance.data["frameStart"] - frame_end = instance.data["frameEnd"] - handle_start = instance.data["handleStart"] - handle_end = instance.data["handleEnd"] - - frame_length = int( - (frame_end - frame_start + 1) + (handle_start + handle_end) - ) - - temporary_nodes = [] collection = instance.data.get("collection", None) if collection: @@ -94,51 +99,61 @@ class ExtractSlateFrame(openpype.api.Extractor): fname = os.path.basename(collection.format( "{head}{padding}{tail}")) fhead = collection.format("{head}") - - collected_frames_len = len(collection.indexes) - - # get first and last frame - first_frame = min(collection.indexes) - 1 - self.log.info('frame_length: {}'.format(frame_length)) - self.log.info( - 'len(collection.indexes): {}'.format(collected_frames_len) - ) - if ("slate" in instance.data["families"]) \ - and (frame_length != collected_frames_len): - first_frame += 1 - - last_frame = first_frame else: - fname = os.path.basename(instance.data.get("path", None)) + fname = os.path.basename(self.fpath) fhead = os.path.splitext(fname)[0] + "." - first_frame = instance.data.get("frameStartHandle", None) - 1 - last_frame = first_frame if "#" in fhead: fhead = fhead.replace("#", "")[:-1] - previous_node = node + self.log.debug("__ self.first_frame: {}".format(self.first_frame)) + self.log.debug("__ slate_first_frame: {}".format(slate_first_frame)) + + # Read node + r_node = nuke.createNode("Read") + r_node["file"].setValue(self.fpath) + r_node["first"].setValue(self.first_frame) + r_node["origfirst"].setValue(self.first_frame) + r_node["last"].setValue(self.last_frame) + r_node["origlast"].setValue(self.last_frame) + r_node["colorspace"].setValue(instance.data["colorspace"]) + previous_node = r_node + temporary_nodes = [previous_node] # only create colorspace baking if toggled on if bake_viewer_process: if bake_viewer_input_process_node: # get input process and connect it to baking - ipn = self.get_view_process_node() + ipn = get_view_process_node() if ipn is not None: ipn.setInput(0, previous_node) previous_node = ipn temporary_nodes.append(ipn) - if not self.viewer_lut_raw: - dag_node = nuke.createNode("OCIODisplay") - dag_node.setInput(0, previous_node) - previous_node = dag_node - temporary_nodes.append(dag_node) + # add duplicate slate node and connect to previous + duply_slate_node = duplicate_node(slate_node) + duply_slate_node.setInput(0, previous_node) + previous_node = duply_slate_node + temporary_nodes.append(duply_slate_node) + + # add viewer display transformation node + dag_node = nuke.createNode("OCIODisplay") + dag_node.setInput(0, previous_node) + previous_node = dag_node + temporary_nodes.append(dag_node) + + else: + # add duplicate slate node and connect to previous + duply_slate_node = duplicate_node(slate_node) + duply_slate_node.setInput(0, previous_node) + previous_node = duply_slate_node + temporary_nodes.append(duply_slate_node) # create write node write_node = nuke.createNode("Write") file = fhead[:-1] + _output_name + "_slate.png" - path = os.path.join(staging_dir, file).replace("\\", "/") + path = os.path.join( + instance.data["stagingDir"], file).replace("\\", "/") # add slate path to `slateFrames` instance data attr if not instance.data.get("slateFrames"): @@ -153,47 +168,31 @@ class ExtractSlateFrame(openpype.api.Extractor): write_node.setInput(0, previous_node) temporary_nodes.append(write_node) - # fill slate node with comments - self.add_comment_slate_node(instance) - # Render frames - nuke.execute(write_node.name(), int(first_frame), int(last_frame)) - # also render slate as sequence frame - nuke.execute(node_subset_name, int(first_frame), int(last_frame)) + nuke.execute( + write_node.name(), int(slate_first_frame), int(slate_first_frame)) - # Clean up - for node in temporary_nodes: - nuke.delete(node) + # also render image to sequence + self._render_slate_to_sequence(instance, slate_first_frame) - def get_view_process_node(self): - # Select only the target node - if nuke.selectedNodes(): - [n.setSelected(False) for n in nuke.selectedNodes()] + # # Clean up + # for node in temporary_nodes: + # nuke.delete(node) - ipn_orig = None - for v in [n for n in nuke.allNodes() - if "Viewer" in n.Class()]: - ip = v['input_process'].getValue() - ipn = v['input_process_node'].getValue() - if "VIEWER_INPUT" not in ipn and ip: - ipn_orig = nuke.toNode(ipn) - ipn_orig.setSelected(True) + def _render_slate_to_sequence(self, instance, slate_first_frame): + if not self.SLATE_TO_SEQUENCE_DONE: + node_subset_name = instance.data["name"] + # also render slate as sequence frame + nuke.execute( + node_subset_name, + int(slate_first_frame), + int(slate_first_frame) + ) - if ipn_orig: - nuke.nodeCopy('%clipboard%') + # mark as done + self.SLATE_TO_SEQUENCE_DONE = True - [n.setSelected(False) for n in nuke.selectedNodes()] # Deselect all - - nuke.nodePaste('%clipboard%') - - ipn = nuke.selectedNode() - - return ipn - - def add_comment_slate_node(self, instance): - node = instance.data.get("slateNode") - if not node: - return + def add_comment_slate_node(self, instance, node): comment = instance.context.data.get("comment") intent = instance.context.data.get("intent") From 4213f11e34ad4d917a0d48a3634f9b8a5159d1e8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 25 May 2022 17:00:56 +0200 Subject: [PATCH 0346/1227] Nuke: refactory slate frame concat to work backward compatible and with slate generator --- openpype/plugins/publish/extract_review_slate.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index af1d2b2983..01492476be 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -23,6 +23,8 @@ class ExtractReviewSlate(openpype.api.Extractor): families = ["slate", "review"] match = pyblish.api.Subset + SUFFIX = "_slate" + hosts = ["nuke", "shell"] optional = True @@ -31,8 +33,15 @@ class ExtractReviewSlate(openpype.api.Extractor): if "representations" not in inst_data: raise RuntimeError("Burnin needs already created mov to work on.") - suffix = "_slate" - slates_data = inst_data["slateFrames"] + # get slates frame from upstream + slates_data = inst_data.get("slateFrames") + if not slates_data: + # make it backward compatible and open for slates generator + # premium plugin + slates_data = { + "*": inst_data["slateFrame"] + } + self.log.info("_ slates_data: {}".format(pformat(slates_data))) ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") @@ -125,7 +134,7 @@ class ExtractReviewSlate(openpype.api.Extractor): _remove_at_end = [] ext = os.path.splitext(input_file)[1] - output_file = input_file.replace(ext, "") + suffix + ext + output_file = input_file.replace(ext, "") + self.SUFFIX + ext _remove_at_end.append(input_path) From 7513b2b2f5d2fc6990e06a2c8d8351a06a403615 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 25 May 2022 17:06:03 +0200 Subject: [PATCH 0347/1227] Deadline: making sure slate frames data are transfered to metadata.json --- openpype/modules/deadline/plugins/publish/submit_publish_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 78ab935e42..ed502f8fd2 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -145,7 +145,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # mapping of instance properties to be transfered to new instance for every # specified family instance_transfer = { - "slate": ["slateFrame"], + "slate": ["slateFrames"], "review": ["lutPath"], "render2d": ["bakingNukeScripts", "version"], "renderlayer": ["convertToScanline"] From e258bfcc26a04347089941f881758009a885c803 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 25 May 2022 17:17:29 +0200 Subject: [PATCH 0348/1227] hound --- openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py | 3 ++- openpype/plugins/publish/extract_review_slate.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py index 384ed5f2ef..5ea7c352b9 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py @@ -51,7 +51,8 @@ class ExtractReviewDataMov(openpype.api.Extractor): with maintained_selection(): generated_repres = [] for o_name, o_data in self.outputs.items(): - self.log.debug("o_name: {}, o_data: {}".format(o_name, pformat(o_data))) + self.log.debug( + "o_name: {}, o_data: {}".format(o_name, pformat(o_data))) f_families = o_data["filter"]["families"] f_task_types = o_data["filter"]["task_types"] f_subsets = o_data["filter"]["subsets"] diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 01492476be..f2aed22c0e 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -326,7 +326,6 @@ class ExtractReviewSlate(openpype.api.Extractor): return slate_path - def _get_slates_resolution(self, slate_path): slate_streams = get_ffprobe_streams(slate_path, self.log) # Try to find first stream with defined 'width' and 'height' From ce882641e7baec3c7edca957e2024331943ab9af Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 25 May 2022 17:27:40 +0200 Subject: [PATCH 0349/1227] Vendor: updating scriptmenu to 1.5.2 --- openpype/vendor/python/common/scriptsmenu/action.py | 3 ++- openpype/vendor/python/common/scriptsmenu/launchfornuke.py | 7 ++----- openpype/vendor/python/common/scriptsmenu/scriptsmenu.py | 3 +-- openpype/vendor/python/common/scriptsmenu/version.py | 2 +- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/openpype/vendor/python/common/scriptsmenu/action.py b/openpype/vendor/python/common/scriptsmenu/action.py index dc4d775f6a..5e68628406 100644 --- a/openpype/vendor/python/common/scriptsmenu/action.py +++ b/openpype/vendor/python/common/scriptsmenu/action.py @@ -119,7 +119,8 @@ module.{module_name}()""" """ # get the current application and its linked keyboard modifiers - modifiers = QtWidgets.QApplication.keyboardModifiers() + app = QtWidgets.QApplication.instance() + modifiers = app.keyboardModifiers() # If the menu has a callback registered for the current modifier # we run the callback instead of the action itself. diff --git a/openpype/vendor/python/common/scriptsmenu/launchfornuke.py b/openpype/vendor/python/common/scriptsmenu/launchfornuke.py index 23e4ed1b4d..72302a79a6 100644 --- a/openpype/vendor/python/common/scriptsmenu/launchfornuke.py +++ b/openpype/vendor/python/common/scriptsmenu/launchfornuke.py @@ -8,7 +8,7 @@ def _nuke_main_window(): if (obj.inherits('QMainWindow') and obj.metaObject().className() == 'Foundry::UI::DockMainWindow'): return obj - raise RuntimeError('Could not find Nuke MainWindow instance') + raise RuntimeError('Could not find Nuke MainWindow instance') def _nuke_main_menubar(): @@ -22,9 +22,6 @@ def _nuke_main_menubar(): def main(title="Scripts"): - # Register control + shift callback to add to shelf (Nuke behavior) - # modifiers = QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier - # menu.register_callback(modifiers, to_shelf) nuke_main_bar = _nuke_main_menubar() for nuke_bar in nuke_main_bar.children(): if isinstance(nuke_bar, scriptsmenu.ScriptsMenu): @@ -33,4 +30,4 @@ def main(title="Scripts"): return menu menu = scriptsmenu.ScriptsMenu(title=title, parent=nuke_main_bar) - return menu \ No newline at end of file + return menu diff --git a/openpype/vendor/python/common/scriptsmenu/scriptsmenu.py b/openpype/vendor/python/common/scriptsmenu/scriptsmenu.py index e2b7ff96c7..9e7c094902 100644 --- a/openpype/vendor/python/common/scriptsmenu/scriptsmenu.py +++ b/openpype/vendor/python/common/scriptsmenu/scriptsmenu.py @@ -264,8 +264,7 @@ class ScriptsMenu(QtWidgets.QMenu): action.setVisible(True) else: for action in self._script_actions: - if not action.has_tag(search.lower()): - action.setVisible(False) + action.setVisible(action.has_tag(search.lower())) # Set visibility for all submenus for action in self.actions(): diff --git a/openpype/vendor/python/common/scriptsmenu/version.py b/openpype/vendor/python/common/scriptsmenu/version.py index 73f9426c2d..52ec49c845 100644 --- a/openpype/vendor/python/common/scriptsmenu/version.py +++ b/openpype/vendor/python/common/scriptsmenu/version.py @@ -1,6 +1,6 @@ VERSION_MAJOR = 1 VERSION_MINOR = 5 -VERSION_PATCH = 1 +VERSION_PATCH = 2 version = '{}.{}.{}'.format(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH) From d17ae6d6a588be1c700be664f3e746975661034a Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Wed, 25 May 2022 19:10:32 +0200 Subject: [PATCH 0350/1227] Change icon path and add nukestudio icon. --- openpype/resources/app_icons/nukestudio.png | Bin 0 -> 46080 bytes .../defaults/system_settings/applications.json | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 openpype/resources/app_icons/nukestudio.png diff --git a/openpype/resources/app_icons/nukestudio.png b/openpype/resources/app_icons/nukestudio.png new file mode 100644 index 0000000000000000000000000000000000000000..99c95f59ff858d67fb92263f594f4b71bda87a66 GIT binary patch literal 46080 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 HIsgA3TR*cj literal 0 HcmV?d00001 diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 2b0de44fa9..29c8a1cccc 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -292,7 +292,7 @@ "nukex": { "enabled": true, "label": "Nuke X", - "icon": "{}/app_icons/nuke.png", + "icon": "{}/app_icons/nukex.png", "host_name": "nuke", "environment": { "NUKE_PATH": [ @@ -431,7 +431,7 @@ "nukestudio": { "enabled": true, "label": "Nuke Studio", - "icon": "{}/app_icons/nuke.png", + "icon": "{}/app_icons/nukestudio.png", "host_name": "hiero", "environment": { "WORKFILES_STARTUP": "0", From d9a9981fefacc42a9e9466f6af6769f1605d4396 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 25 May 2022 19:37:14 +0200 Subject: [PATCH 0351/1227] Fix - Harmony 21.1 messed up Javascript Qt API QDataStream is missing, different way to get codec used. QApplication.activeWindow() also returned null, replaced by topLevelWidgets --- openpype/hosts/harmony/api/TB_sceneOpened.js | 31 +++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/harmony/api/TB_sceneOpened.js b/openpype/hosts/harmony/api/TB_sceneOpened.js index 6a403fa65e..29473bcc93 100644 --- a/openpype/hosts/harmony/api/TB_sceneOpened.js +++ b/openpype/hosts/harmony/api/TB_sceneOpened.js @@ -279,19 +279,13 @@ function Client() { }; self._send = function(message) { - var data = new QByteArray(); - var outstr = new QDataStream(data, QIODevice.WriteOnly); - outstr.writeInt(0); - data.append('UTF-8'); - outstr.device().seek(0); - outstr.writeInt(data.size() - 4); - var codec = QTextCodec.codecForUtfText(data); - var msg = codec.fromUnicode(message); - var l = msg.size(); - var coded = new QByteArray('AH').append(self.pack(l)); - coded = coded.append(msg); - self.socket.write(new QByteArray(coded)); - self.logDebug('Sent.'); + var codec_name = new QByteArray().append("ISO-8859-1"); + var codec = QTextCodec.codecForName(codec_name); + var msg = codec.fromUnicode(message); + var l = msg.size(); + var coded = new QByteArray().append('AH').append(self.pack(l)).append(msg); + self.socket.write(new QByteArray(coded)); + self.logDebug('Sent.'); }; self.waitForLock = function() { @@ -343,6 +337,7 @@ function start() { var host = '127.0.0.1'; /** port of the server */ var port = parseInt(System.getenv('AVALON_HARMONY_PORT')); + MessageLog.trace("port " + port.toString()); // Attach the client to the QApplication to preserve. var app = QCoreApplication.instance(); @@ -351,7 +346,15 @@ function start() { app.avalonClient = new Client(); app.avalonClient.socket.connectToHost(host, port); } - var menuBar = QApplication.activeWindow().menuBar(); + var mainWindow = null; + var widgets = QApplication.topLevelWidgets(); + for (var i = 0 ; i < widgets.length; i++) { + if (widgets[i] instanceof QMainWindow){ + MessageLog.trace('(DEBUG): START Main window '); + mainWindow = widgets[i]; + } + } + var menuBar = mainWindow.menuBar(); var actions = menuBar.actions(); app.avalonMenu = null; From f213a33f130d336ca8345ab64bbc6d1105c3a379 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 25 May 2022 19:40:40 +0200 Subject: [PATCH 0352/1227] Fix - Harmony 21.1 messed up Javascript Qt API Removed missed logging --- openpype/hosts/harmony/api/TB_sceneOpened.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/harmony/api/TB_sceneOpened.js b/openpype/hosts/harmony/api/TB_sceneOpened.js index 29473bcc93..610b0a73bb 100644 --- a/openpype/hosts/harmony/api/TB_sceneOpened.js +++ b/openpype/hosts/harmony/api/TB_sceneOpened.js @@ -337,7 +337,6 @@ function start() { var host = '127.0.0.1'; /** port of the server */ var port = parseInt(System.getenv('AVALON_HARMONY_PORT')); - MessageLog.trace("port " + port.toString()); // Attach the client to the QApplication to preserve. var app = QCoreApplication.instance(); @@ -350,7 +349,6 @@ function start() { var widgets = QApplication.topLevelWidgets(); for (var i = 0 ; i < widgets.length; i++) { if (widgets[i] instanceof QMainWindow){ - MessageLog.trace('(DEBUG): START Main window '); mainWindow = widgets[i]; } } From c50cab558c80664e332d9831798d2754df1b89e1 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 26 May 2022 02:22:33 +0300 Subject: [PATCH 0353/1227] initial refactoring --- openpype/plugins/publish/extract_jpeg_exr.py | 191 +++++++++++-------- 1 file changed, 107 insertions(+), 84 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 070e9e1e08..52a678e9ef 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -8,6 +8,7 @@ from openpype.pipeline import ( from openpype.lib import ( get_ffmpeg_tool_path, get_oiio_tools_path, + is_oiio_supported, filter_profiles, run_subprocess, @@ -89,99 +90,121 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): full_input_path = os.path.join(stagingdir, input_file) self.log.info("input {}".format(full_input_path)) - - do_convert = should_convert_for_ffmpeg(full_input_path) - # If result is None the requirement of conversion can't be - # determined - if do_convert is None: - self.log.info(( - "Can't determine if representation requires conversion." - " Skipped." - )) - continue - - # Do conversion if needed - # - change staging dir of source representation - # - must be set back after output definitions processing - convert_dir = None - if do_convert: - convert_dir = get_transcode_temp_directory() - filename = os.path.basename(full_input_path) - convert_input_paths_for_ffmpeg( - [full_input_path], - convert_dir, - self.log - ) - full_input_path = os.path.join(convert_dir, filename) - - filename = os.path.splitext(input_file)[0] - if not filename.endswith('.'): - filename += "." - jpeg_file = filename + "jpg" - full_output_path = os.path.join(stagingdir, jpeg_file) - - self.log.info("output {}".format(full_output_path)) - - ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") - ffmpeg_args = self.ffmpeg_args or {} - - jpeg_items = [] - jpeg_items.append(path_to_subprocess_arg(ffmpeg_path)) - # override file if already exists - jpeg_items.append("-y") - # use same input args like with mov - jpeg_items.extend(ffmpeg_args.get("input") or []) - # input file - jpeg_items.append("-i {}".format( - path_to_subprocess_arg(full_input_path) - )) - # output arguments from presets - jpeg_items.extend(ffmpeg_args.get("output") or []) - - # If its a movie file, we just want one frame. + # If it's a movie file, use ffmpeg if repre["ext"] == "mov": + do_convert = should_convert_for_ffmpeg(full_input_path) + # If result is None the requirement of conversion can't be + # determined + if do_convert is None: + self.log.info(( + "Can't determine if representation requires conversion." + " Skipped." + )) + continue + + # Do conversion if needed + # - change staging dir of source representation + # - must be set back after output definitions processing + convert_dir = None + if do_convert: + convert_dir = get_transcode_temp_directory() + filename = os.path.basename(full_input_path) + convert_input_paths_for_ffmpeg( + [full_input_path], + convert_dir, + self.log + ) + full_input_path = os.path.join(convert_dir, filename) + + filename = os.path.splitext(input_file)[0] + if not filename.endswith('.'): + filename += "." + jpeg_file = filename + "jpg" + full_output_path = os.path.join(stagingdir, jpeg_file) + + self.log.info("output {}".format(full_output_path)) + + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") + ffmpeg_args = self.ffmpeg_args or {} + + jpeg_items = [] + jpeg_items.append(path_to_subprocess_arg(ffmpeg_path)) + # override file if already exists + jpeg_items.append("-y") + # use same input args like with mov + jpeg_items.extend(ffmpeg_args.get("input") or []) + # input file + jpeg_items.append("-i {}".format( + path_to_subprocess_arg(full_input_path) + )) + # output arguments from presets + jpeg_items.extend(ffmpeg_args.get("output") or []) + # we just want one frame from movie files jpeg_items.append("-vframes 1") - # output file - jpeg_items.append(path_to_subprocess_arg(full_output_path)) - subprocess_command = " ".join(jpeg_items) + # output file + jpeg_items.append(path_to_subprocess_arg(full_output_path)) + subprocess_command = " ".join(jpeg_items) - # run subprocess - self.log.debug("{}".format(subprocess_command)) - try: # temporary until oiiotool is supported cross platform - run_subprocess( - subprocess_command, shell=True, logger=self.log - ) - except RuntimeError as exp: - if "Compression" in str(exp): - self.log.debug( - "Unsupported compression on input files. Skipping!!!" + # run subprocess + self.log.debug("{}".format(subprocess_command)) + try: # temporary until oiiotool is supported cross platform + run_subprocess( + subprocess_command, shell=True, logger=self.log ) - return - self.log.warning("Conversion crashed", exc_info=True) - raise + except RuntimeError as exp: + if "Compression" in str(exp): + self.log.debug( + "Unsupported compression on input files. Skipping!" + ) + return + self.log.warning("Conversion crashed", exc_info=True) + raise - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) + # adding representation + self.log.debug("Adding: {}".format(new_repre)) + instance.data["representations"].append(new_repre) - # Cleanup temp folder - if convert_dir is not None and os.path.exists(convert_dir): - shutil.rmtree(convert_dir) + # Cleanup temp folder + if convert_dir is not None and os.path.exists(convert_dir): + shutil.rmtree(convert_dir) - # Create only one representation with name 'thumbnail' - # TODO maybe handle way how to decide from which representation - # will be thumbnail created - break + elif repre["ext"] == "exr": + oiio_support = is_oiio_supported() + + if oiio_support: + oiio_tool_path = get_oiio_tools_path() + args = [oiio_tool_path] + + ext = os.path.splitext(input_path)[1][1:] + if ext in self.movie_extensions: + args.extend(["--subimage", str(int(input_frame))]) + else: + args.extend(["--frames", str(int(input_frame))]) + + if ext == "exr": + args.extend(["--powc", "0.45,0.45,0.45,1.0"]) + + args.extend([input_path, "-o", output_path]) + output = openpype.api.run_subprocess(args) + + failed_output = "oiiotool produced no output." + if failed_output in output: + raise ValueError( + "oiiotool processing failed. Args: {}".format(args) + # Create only one representation with name 'thumbnail' + # TODO maybe handle way how to decide from which representation + # will be thumbnail created + break def _get_filtered_repres(self, instance): filtered_repres = [] From d992935468c488fdd4fe783cd7ce797d6f27ca50 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 26 May 2022 02:31:56 +0300 Subject: [PATCH 0354/1227] name probesize and duration to max_int --- openpype/plugins/publish/extract_jpeg_exr.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 52a678e9ef..066272fd01 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -1,4 +1,5 @@ import os +from urllib.parse import MAX_CACHE_SIZE import pyblish.api from openpype.pipeline import ( @@ -131,6 +132,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.append(path_to_subprocess_arg(ffmpeg_path)) # override file if already exists jpeg_items.append("-y") + # flag for large file sizes + max_int = 2147483647 + jpeg_items.append("-analyzeduration {}".format(max_int)) + jpeg_items.append("-probesize {}".format(max_int)) # use same input args like with mov jpeg_items.extend(ffmpeg_args.get("input") or []) # input file From cc4efe7638340e0ab64857f6e00b676cfa4bb77e Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 26 May 2022 06:02:00 +0300 Subject: [PATCH 0355/1227] style fix --- openpype/plugins/publish/extract_jpeg_exr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 066272fd01..52457ebb31 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -185,7 +185,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): elif repre["ext"] == "exr": oiio_support = is_oiio_supported() - + if oiio_support: oiio_tool_path = get_oiio_tools_path() args = [oiio_tool_path] From 70c5366134ccafb066c40097907b958496392190 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 26 May 2022 07:25:09 +0300 Subject: [PATCH 0356/1227] Additional oiiotool conversion refactoring, some style fixes --- openpype/plugins/publish/extract_jpeg_exr.py | 123 +++++++++++-------- 1 file changed, 69 insertions(+), 54 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 52457ebb31..bb3b80b7ef 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -91,38 +91,41 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): full_input_path = os.path.join(stagingdir, input_file) self.log.info("input {}".format(full_input_path)) - # If it's a movie file, use ffmpeg + + + # Presumably the following is not needed since we are being + # explicit + # TODO: Test cases, cleanup. + # do_convert = should_convert_for_ffmpeg(full_input_path) + # # If result is None the requirement of conversion can't be + # # determined + # if do_convert is None: + # self.log.info(( + # "Can't determine if representation requires conversion." # noqa + # " Skipped." + # )) + # continue + # + # # Do conversion if needed + # # - change staging dir of source representation + # # - must be set back after output definitions processing + # convert_dir = None + # if do_convert: + # convert_dir = get_transcode_temp_directory() + # filename = os.path.basename(full_input_path) + # convert_input_paths_for_ffmpeg( + # [full_input_path], + # convert_dir, + # self.log + # ) + # full_input_path = os.path.join(convert_dir, filename) + filename = os.path.splitext(input_file)[0] + if not filename.endswith('.'): + filename += "." + jpeg_file = filename + "jpg" + full_output_path = os.path.join(stagingdir, jpeg_file) + # If it's a movie file, use ffmpeg if repre["ext"] == "mov": - do_convert = should_convert_for_ffmpeg(full_input_path) - # If result is None the requirement of conversion can't be - # determined - if do_convert is None: - self.log.info(( - "Can't determine if representation requires conversion." - " Skipped." - )) - continue - - # Do conversion if needed - # - change staging dir of source representation - # - must be set back after output definitions processing - convert_dir = None - if do_convert: - convert_dir = get_transcode_temp_directory() - filename = os.path.basename(full_input_path) - convert_input_paths_for_ffmpeg( - [full_input_path], - convert_dir, - self.log - ) - full_input_path = os.path.join(convert_dir, filename) - - filename = os.path.splitext(input_file)[0] - if not filename.endswith('.'): - filename += "." - jpeg_file = filename + "jpg" - full_output_path = os.path.join(stagingdir, jpeg_file) - self.log.info("output {}".format(full_output_path)) ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") @@ -178,37 +181,49 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # adding representation self.log.debug("Adding: {}".format(new_repre)) instance.data["representations"].append(new_repre) - - # Cleanup temp folder - if convert_dir is not None and os.path.exists(convert_dir): - shutil.rmtree(convert_dir) - elif repre["ext"] == "exr": oiio_support = is_oiio_supported() - if oiio_support: + # TODO: Add resolution checking, possibly check for other + # places where things may break oiio_tool_path = get_oiio_tools_path() + full_output_path = os.path.join(stagingdir, jpeg_file) args = [oiio_tool_path] + oiio_cmd = [oiio_tool_path, + full_input_path, "-o", + full_output_path + ] + subprocess_exr = " ".join(oiio_cmd) + self.log.info(f"running: {subprocess_exr}") + run_subprocess(subprocess_exr, logger=self.log) - ext = os.path.splitext(input_path)[1][1:] - if ext in self.movie_extensions: - args.extend(["--subimage", str(int(input_frame))]) - else: - args.extend(["--frames", str(int(input_frame))]) - - if ext == "exr": - args.extend(["--powc", "0.45,0.45,0.45,1.0"]) - - args.extend([input_path, "-o", output_path]) - output = openpype.api.run_subprocess(args) - + # raise error if there is no ouptput + if not os.path.exists(full_input_path): + self.log.error( + ("File {} was not converted " + "by oiio tool!").format(full_input_path)) + raise AssertionError("OIIO tool conversion failed") failed_output = "oiiotool produced no output." + output = run_subprocess(args) if failed_output in output: - raise ValueError( - "oiiotool processing failed. Args: {}".format(args) - # Create only one representation with name 'thumbnail' - # TODO maybe handle way how to decide from which representation - # will be thumbnail created + raise ValueError( + "oiiotool processing failed. Args: {}".format(args)) + + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } + # adding representation + self.log.debug("Adding: {}".format(new_repre)) + instance.data["representations"].append(new_repre) + + # Create only one representation with name 'thumbnail' + # TODO maybe handle way how to decide from which repre + # will be thumbnail created break def _get_filtered_repres(self, instance): From 78ddd548287c00eb99aaeb99624d380314172052 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 26 May 2022 10:42:31 +0200 Subject: [PATCH 0357/1227] init file for tvpaint worker also has set path to not existing file in guidelines --- openpype/hosts/tvpaint/worker/init_file.tvpp | Bin 59333 -> 59973 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/openpype/hosts/tvpaint/worker/init_file.tvpp b/openpype/hosts/tvpaint/worker/init_file.tvpp index 572d278fdb49c619d3cf040f59238377d1062d6a..22170b45bc728d3d46193a35fa5216e5c739e8cf 100644 GIT binary patch delta 627 zcmZ`!&r94u6n-1o7WW`Zp|ppVoa$-UWMbA$V4$EB5B?|x5xnSZcA6b`HWPLdQ4j>h zOHVDi6%=}`g_aV$mEuM4=0(uIz|#H|eTnu`^ufFz-+SNt=AE7oon8#RfBgJU-PvFA zf4m&KJluxSm2XF1ptOhE7+!pzoHw4j-~)jsm|%g2xl?)qUjETIFe*;gsvbWYhFK7x zApr#g*n&FP*<97TgFJ;RY#`ZTb@2`sxrbE<>gYp z2h-361`&jaR)FL)4}MD{{hCeaH6xwNKj_^`O`pA5d@~QAPx5E!yS6drWS=k3t1J~A zmF-(*}bzUpvFib`vt6KY`^C@ER2Ikj&JHK3Mhla`6R;vxE0inyHZ ziKs)T5Rt6SC0$U7$v1J4h(c5JS;!mfaagof(A(jilGA5{5SKR*R_%2jpz5vFi};`W z*Td43?b!*s;r^1=F%9nU9TO){MXT^5Br`*cmR*T2%g@OG;2 delta 199 zcmX?lh56`t<_ThQ&(|8ahFa}$5WGIGhCv`|{i@XrV8A8p>0If=z3$r*`x>8X Date: Thu, 26 May 2022 10:42:37 +0200 Subject: [PATCH 0358/1227] OP-2787 - changed assert to PublishXmlValidationError --- .../help/submit_maya_remote_publish_deadline.xml | 16 ++++++++++++++++ .../submit_maya_remote_publish_deadline.py | 8 ++++---- 2 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 openpype/hosts/maya/plugins/publish/help/submit_maya_remote_publish_deadline.xml diff --git a/openpype/hosts/maya/plugins/publish/help/submit_maya_remote_publish_deadline.xml b/openpype/hosts/maya/plugins/publish/help/submit_maya_remote_publish_deadline.xml new file mode 100644 index 0000000000..e92320ccdc --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/help/submit_maya_remote_publish_deadline.xml @@ -0,0 +1,16 @@ + + + +Errors found + +## Publish process has errors + +At least one plugin failed before this plugin, job won't be sent to Deadline for processing before all issues are fixed. + +### How to repair? + +Check all failing plugins (should be highlighted in red) and fix issues if possible. + + + + \ No newline at end of file diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py index b11698f8e8..be8c50d7b3 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py @@ -3,7 +3,7 @@ import requests from maya import cmds -from openpype.pipeline import legacy_io +from openpype.pipeline import legacy_io, PublishXmlValidationError from openpype.settings import get_project_settings import pyblish.api @@ -39,9 +39,9 @@ class MayaSubmitRemotePublishDeadline(pyblish.api.InstancePlugin): ["ProcessSubmittedJobOnFarm"]) # Ensure no errors so far - assert (all(result["success"] - for result in instance.context.data["results"]), - ("Errors found, aborting integration..")) + if not (all(result["success"] + for result in instance.context.data["results"])): + raise PublishXmlValidationError("Publish process has errors") if not instance.data["publish"]: self.log.warning("No active instances found. " From 4ca419f0b63c2782d8955b8012674ca131a57ad9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 May 2022 11:36:49 +0200 Subject: [PATCH 0359/1227] Revert "OP-2787 - removed deadline family" This reverts commit 6b71ff19 --- .../maya/plugins/publish/collect_animation.py | 3 +++ .../maya/plugins/publish/collect_pointcache.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 openpype/hosts/maya/plugins/publish/collect_pointcache.py diff --git a/openpype/hosts/maya/plugins/publish/collect_animation.py b/openpype/hosts/maya/plugins/publish/collect_animation.py index 9b1e38fd0a..b442113fbc 100644 --- a/openpype/hosts/maya/plugins/publish/collect_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_animation.py @@ -55,3 +55,6 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): # Store data in the instance for the validator instance.data["out_hierarchy"] = hierarchy + + if instance.data.get("farm"): + instance.data["families"].append("deadline") diff --git a/openpype/hosts/maya/plugins/publish/collect_pointcache.py b/openpype/hosts/maya/plugins/publish/collect_pointcache.py new file mode 100644 index 0000000000..b55babe372 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/collect_pointcache.py @@ -0,0 +1,14 @@ +import pyblish.api + + +class CollectPointcache(pyblish.api.InstancePlugin): + """Collect pointcache data for instance.""" + + order = pyblish.api.CollectorOrder + 0.4 + families = ["pointcache"] + label = "Collect Pointcache" + hosts = ["maya"] + + def process(self, instance): + if instance.data.get("farm"): + instance.data["families"].append("deadline") \ No newline at end of file From c96ea856425550835c7c5dfc42b1965a54ca0902 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 May 2022 11:40:19 +0200 Subject: [PATCH 0360/1227] OP-2787 - change families to more generic Not only Deadline could be used for remote publish. DL plugin will be picked only if DL module is enabled. --- openpype/hosts/maya/plugins/publish/collect_animation.py | 2 +- openpype/hosts/maya/plugins/publish/collect_pointcache.py | 2 +- .../plugins/publish/submit_maya_remote_publish_deadline.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_animation.py b/openpype/hosts/maya/plugins/publish/collect_animation.py index b442113fbc..549098863f 100644 --- a/openpype/hosts/maya/plugins/publish/collect_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_animation.py @@ -57,4 +57,4 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): instance.data["out_hierarchy"] = hierarchy if instance.data.get("farm"): - instance.data["families"].append("deadline") + instance.data["families"].append("publish.farm") diff --git a/openpype/hosts/maya/plugins/publish/collect_pointcache.py b/openpype/hosts/maya/plugins/publish/collect_pointcache.py index b55babe372..a841341f72 100644 --- a/openpype/hosts/maya/plugins/publish/collect_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/collect_pointcache.py @@ -11,4 +11,4 @@ class CollectPointcache(pyblish.api.InstancePlugin): def process(self, instance): if instance.data.get("farm"): - instance.data["families"].append("deadline") \ No newline at end of file + instance.data["families"].append("publish.farm") diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py index be8c50d7b3..210fefb520 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py @@ -29,7 +29,7 @@ class MayaSubmitRemotePublishDeadline(pyblish.api.InstancePlugin): label = "Submit Scene to Deadline" order = pyblish.api.IntegratorOrder hosts = ["maya"] - families = ["deadline"] + families = ["publish.farm"] def process(self, instance): settings = get_project_settings(os.getenv("AVALON_PROJECT")) From 0d03e3e2f836476310b899a21aec5b49dbd2a7c8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 May 2022 11:40:50 +0200 Subject: [PATCH 0361/1227] OP-2787 - removed obsolete part of code Doesn't do anything. --- openpype/hosts/maya/plugins/publish/extract_animation.py | 7 +------ openpype/hosts/maya/plugins/publish/extract_pointcache.py | 7 +------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py index 87f2d35192..1ccc8f5cfe 100644 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_animation.py @@ -24,12 +24,7 @@ class ExtractAnimation(openpype.api.Extractor): def process(self, instance): if instance.data.get("farm"): - path = os.path.join( - os.path.dirname(instance.context.data["currentFile"]), - "cache", - instance.data["name"] + ".abc" - ) - instance.data["expectedFiles"] = [os.path.normpath(path)] + self.log.debug("Should be processed on farm, skipping.") return # Collect the out set nodes diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 7ad4c6dfa9..ff3d97ded1 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -26,12 +26,7 @@ class ExtractAlembic(openpype.api.Extractor): def process(self, instance): if instance.data.get("farm"): - path = os.path.join( - os.path.dirname(instance.context.data["currentFile"]), - "cache", - instance.data["name"] + ".abc" - ) - instance.data["expectedFiles"] = [os.path.normpath(path)] + self.log.debug("Should be processed on farm, skipping.") return nodes = instance[:] From de161da68b4035c4fde2e376dc8da1ab2b79d093 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 May 2022 11:45:29 +0200 Subject: [PATCH 0362/1227] OP-2787 - created explicit env var HEADLESS_PUBLISH Env var created to differentiate launch of Maya on the farm. lib.IS_HEADLESS might be triggered locally, it is not precise enough. Added same env var to all commands to standardize it a bit. --- openpype/hosts/maya/api/pipeline.py | 5 ++++- .../submit_maya_remote_publish_deadline.py | 2 ++ openpype/pype_commands.py | 18 +++++++++++++----- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index c2fe8a95a5..6fc93e864f 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -72,7 +72,10 @@ def install(): log.info(("Running in headless mode, skipping Maya " "save/open/new callback installation..")) - # Register default "local" target + return + + if os.environ.get("HEADLESS_PUBLISH"): + # Maya launched on farm, lib.IS_HEADLESS might be triggered locally too print("Registering pyblish target: remote") pyblish.api.register_target("remote") return diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py index 210fefb520..8f50878db4 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py @@ -114,6 +114,8 @@ class MayaSubmitRemotePublishDeadline(pyblish.api.InstancePlugin): environment["OPENPYPE_REMOTE_JOB"] = "1" environment["OPENPYPE_USERNAME"] = instance.context.data["user"] environment["OPENPYPE_PUBLISH_SUBSET"] = instance.data["subset"] + environment["HEADLESS_PUBLISH"] = "1" + payload["JobInfo"].update({ "EnvironmentKeyValue%d" % index: "{key}={value}".format( diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index d945a1f697..90c582a319 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -144,6 +144,7 @@ class PypeCommands: pyblish.api.register_target("farm") os.environ["OPENPYPE_PUBLISH_DATA"] = os.pathsep.join(paths) + os.environ["HEADLESS_PUBLISH"] = 'true' # to use in app lib log.info("Running publish ...") @@ -173,9 +174,11 @@ class PypeCommands: user_email, targets=None): """Opens installed variant of 'host' and run remote publish there. + Eventually should be yanked out to Webpublisher cli. + Currently implemented and tested for Photoshop where customer wants to process uploaded .psd file and publish collected layers - from there. + from there. Triggered by Webpublisher. Checks if no other batches are running (status =='in_progress). If so, it sleeps for SLEEP (this is separate process), @@ -273,7 +276,8 @@ class PypeCommands: def remotepublish(project, batch_path, user_email, targets=None): """Start headless publishing. - Used to publish rendered assets, workfiles etc. + Used to publish rendered assets, workfiles etc via Webpublisher. + Eventually should be yanked out to Webpublisher cli. Publish use json from passed paths argument. @@ -309,6 +313,7 @@ class PypeCommands: os.environ["AVALON_PROJECT"] = project os.environ["AVALON_APP"] = host_name os.environ["USER_EMAIL"] = user_email + os.environ["HEADLESS_PUBLISH"] = 'true' # to use in app lib pyblish.api.register_host(host_name) @@ -331,9 +336,12 @@ class PypeCommands: log.info("Publish finished.") @staticmethod - def extractenvironments( - output_json_path, project, asset, task, app, env_group - ): + def extractenvironments(output_json_path, project, asset, task, app, + env_group): + """Produces json file with environment based on project and app. + + Called by Deadline plugin to propagate environment into render jobs. + """ if all((project, asset, task, app)): from openpype.api import get_app_environments_for_context env = get_app_environments_for_context( From 8cda0ebbeef23ceccc7f1a9d9963eecc93219012 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 May 2022 12:09:13 +0200 Subject: [PATCH 0363/1227] OP-2787 - Hound --- .../plugins/publish/collect_publishable_instances.py | 7 ++++--- .../plugins/publish/submit_maya_remote_publish_deadline.py | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/collect_publishable_instances.py b/openpype/modules/deadline/plugins/publish/collect_publishable_instances.py index 9a467428fd..741a2a5af8 100644 --- a/openpype/modules/deadline/plugins/publish/collect_publishable_instances.py +++ b/openpype/modules/deadline/plugins/publish/collect_publishable_instances.py @@ -5,6 +5,7 @@ import os import pyblish.api +from openpype.pipeline import PublishValidationError class CollectDeadlinePublishableInstances(pyblish.api.InstancePlugin): @@ -24,9 +25,9 @@ class CollectDeadlinePublishableInstances(pyblish.api.InstancePlugin): def process(self, instance): self.log.debug("CollectDeadlinePublishableInstances") publish_inst = os.environ.get("OPENPYPE_PUBLISH_SUBSET", '') - assert (publish_inst, - "OPENPYPE_PUBLISH_SUBSET env var required for " - "remote publishing") + if not publish_inst: + raise PublishValidationError("OPENPYPE_PUBLISH_SUBSET env var " + "required for remote publishing") subset_name = instance.data["subset"] if subset_name == publish_inst: diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py index 8f50878db4..196adc5906 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py @@ -116,7 +116,6 @@ class MayaSubmitRemotePublishDeadline(pyblish.api.InstancePlugin): environment["OPENPYPE_PUBLISH_SUBSET"] = instance.data["subset"] environment["HEADLESS_PUBLISH"] = "1" - payload["JobInfo"].update({ "EnvironmentKeyValue%d" % index: "{key}={value}".format( key=key, From cd975beeec54fc6c13a808db8ae085c5cc9560b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Thu, 26 May 2022 14:00:41 +0200 Subject: [PATCH 0364/1227] Update openpype/hosts/nuke/plugins/publish/extract_slate_frame.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../hosts/nuke/plugins/publish/extract_slate_frame.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index 576f8b3440..2d20c1747c 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -75,7 +75,13 @@ class ExtractSlateFrame(openpype.api.Extractor): self.log.info( "StagingDir `{0}`...".format(instance.data["stagingDir"])) - def render_slate(self, instance, output_name=None, **kwargs): + def render_slate( + self, + instance, + bake_viewer_process, + bake_viewer_input_process, + output_name=None + ): slate_node = instance.data["slateNode"] # fill slate node with comments From 15822591793cb94ea730941358abb0a06dac211c Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 26 May 2022 14:07:30 +0200 Subject: [PATCH 0365/1227] :truck: split versions of integration plugin --- openpype/hosts/unreal/__init__.py | 10 +- .../integration/{ => UE_4.7}/.gitignore | 0 .../Content/Python/init_unreal.py | 0 .../integration/{ => UE_4.7}/OpenPype.uplugin | 0 .../unreal/integration/{ => UE_4.7}/README.md | 2 +- .../{ => UE_4.7}/Resources/openpype128.png | Bin .../{ => UE_4.7}/Resources/openpype40.png | Bin .../{ => UE_4.7}/Resources/openpype512.png | Bin .../UE_4.7/Source/OpenPype/OpenPype.Build.cs | 57 +++++++++ .../OpenPype/Private/AssetContainer.cpp | 0 .../Private/AssetContainerFactory.cpp | 0 .../Source/OpenPype/Private/OpenPype.cpp | 103 ++++++++++++++++ .../Source/OpenPype/Private/OpenPypeLib.cpp | 0 .../Private/OpenPypePublishInstance.cpp | 0 .../OpenPypePublishInstanceFactory.cpp | 0 .../OpenPype/Private/OpenPypePythonBridge.cpp | 0 .../Source/OpenPype/Private/OpenPypeStyle.cpp | 70 +++++++++++ .../Source/OpenPype/Public/AssetContainer.h | 0 .../OpenPype/Public/AssetContainerFactory.h | 0 .../UE_4.7/Source/OpenPype/Public/OpenPype.h | 21 ++++ .../Source/OpenPype/Public/OpenPypeLib.h | 0 .../OpenPype/Public/OpenPypePublishInstance.h | 0 .../Public/OpenPypePublishInstanceFactory.h | 0 .../OpenPype/Public/OpenPypePythonBridge.h | 0 .../Source/OpenPype/Public/OpenPypeStyle.h | 22 ++++ .../unreal/integration/UE_5.0/.gitignore | 35 ++++++ .../UE_5.0/Content/Python/__init__.py | 0 .../UE_5.0/Content/Python/init_unreal.py | 28 +++++ .../integration/UE_5.0/Content/__init__.py | 0 .../integration/UE_5.0/OpenPype.uplugin | 24 ++++ .../hosts/unreal/integration/UE_5.0/README.md | 11 ++ .../UE_5.0/Resources/openpype128.png | Bin 0 -> 14594 bytes .../UE_5.0/Resources/openpype40.png | Bin 0 -> 4884 bytes .../UE_5.0/Resources/openpype512.png | Bin 0 -> 85856 bytes .../Source/OpenPype/OpenPype.Build.cs | 0 .../OpenPype/Private/AssetContainer.cpp | 115 ++++++++++++++++++ .../Private/AssetContainerFactory.cpp | 20 +++ .../Source/OpenPype/Private/OpenPype.cpp | 0 .../OpenPype/Private/OpenPypeCommands.cpp | 0 .../Source/OpenPype/Private/OpenPypeLib.cpp | 48 ++++++++ .../Private/OpenPypePublishInstance.cpp | 108 ++++++++++++++++ .../OpenPypePublishInstanceFactory.cpp | 20 +++ .../OpenPype/Private/OpenPypePythonBridge.cpp | 13 ++ .../Source/OpenPype/Private/OpenPypeStyle.cpp | 0 .../Source/OpenPype/Public/AssetContainer.h | 39 ++++++ .../OpenPype/Public/AssetContainerFactory.h | 21 ++++ .../Source/OpenPype/Public/OpenPype.h | 0 .../Source/OpenPype/Public/OpenPypeCommands.h | 0 .../Source/OpenPype/Public/OpenPypeLib.h | 19 +++ .../OpenPype/Public/OpenPypePublishInstance.h | 21 ++++ .../Public/OpenPypePublishInstanceFactory.h | 19 +++ .../OpenPype/Public/OpenPypePythonBridge.h | 20 +++ .../Source/OpenPype/Public/OpenPypeStyle.h | 0 .../system_settings/applications.json | 12 +- repos/avalon-core | 1 - 55 files changed, 854 insertions(+), 5 deletions(-) rename openpype/hosts/unreal/integration/{ => UE_4.7}/.gitignore (100%) rename openpype/hosts/unreal/integration/{ => UE_4.7}/Content/Python/init_unreal.py (100%) rename openpype/hosts/unreal/integration/{ => UE_4.7}/OpenPype.uplugin (100%) rename openpype/hosts/unreal/integration/{ => UE_4.7}/README.md (91%) rename openpype/hosts/unreal/integration/{ => UE_4.7}/Resources/openpype128.png (100%) rename openpype/hosts/unreal/integration/{ => UE_4.7}/Resources/openpype40.png (100%) rename openpype/hosts/unreal/integration/{ => UE_4.7}/Resources/openpype512.png (100%) create mode 100644 openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/OpenPype.Build.cs rename openpype/hosts/unreal/integration/{ => UE_4.7}/Source/OpenPype/Private/AssetContainer.cpp (100%) rename openpype/hosts/unreal/integration/{ => UE_4.7}/Source/OpenPype/Private/AssetContainerFactory.cpp (100%) create mode 100644 openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPype.cpp rename openpype/hosts/unreal/integration/{ => UE_4.7}/Source/OpenPype/Private/OpenPypeLib.cpp (100%) rename openpype/hosts/unreal/integration/{ => UE_4.7}/Source/OpenPype/Private/OpenPypePublishInstance.cpp (100%) rename openpype/hosts/unreal/integration/{ => UE_4.7}/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp (100%) rename openpype/hosts/unreal/integration/{ => UE_4.7}/Source/OpenPype/Private/OpenPypePythonBridge.cpp (100%) create mode 100644 openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypeStyle.cpp rename openpype/hosts/unreal/integration/{ => UE_4.7}/Source/OpenPype/Public/AssetContainer.h (100%) rename openpype/hosts/unreal/integration/{ => UE_4.7}/Source/OpenPype/Public/AssetContainerFactory.h (100%) create mode 100644 openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPype.h rename openpype/hosts/unreal/integration/{ => UE_4.7}/Source/OpenPype/Public/OpenPypeLib.h (100%) rename openpype/hosts/unreal/integration/{ => UE_4.7}/Source/OpenPype/Public/OpenPypePublishInstance.h (100%) rename openpype/hosts/unreal/integration/{ => UE_4.7}/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h (100%) rename openpype/hosts/unreal/integration/{ => UE_4.7}/Source/OpenPype/Public/OpenPypePythonBridge.h (100%) create mode 100644 openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypeStyle.h create mode 100644 openpype/hosts/unreal/integration/UE_5.0/.gitignore create mode 100644 openpype/hosts/unreal/integration/UE_5.0/Content/Python/__init__.py create mode 100644 openpype/hosts/unreal/integration/UE_5.0/Content/Python/init_unreal.py create mode 100644 openpype/hosts/unreal/integration/UE_5.0/Content/__init__.py create mode 100644 openpype/hosts/unreal/integration/UE_5.0/OpenPype.uplugin create mode 100644 openpype/hosts/unreal/integration/UE_5.0/README.md create mode 100644 openpype/hosts/unreal/integration/UE_5.0/Resources/openpype128.png create mode 100644 openpype/hosts/unreal/integration/UE_5.0/Resources/openpype40.png create mode 100644 openpype/hosts/unreal/integration/UE_5.0/Resources/openpype512.png rename openpype/hosts/unreal/integration/{ => UE_5.0}/Source/OpenPype/OpenPype.Build.cs (100%) create mode 100644 openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainer.cpp create mode 100644 openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainerFactory.cpp rename openpype/hosts/unreal/integration/{ => UE_5.0}/Source/OpenPype/Private/OpenPype.cpp (100%) rename openpype/hosts/unreal/integration/{ => UE_5.0}/Source/OpenPype/Private/OpenPypeCommands.cpp (100%) create mode 100644 openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeLib.cpp create mode 100644 openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePublishInstance.cpp create mode 100644 openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp create mode 100644 openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePythonBridge.cpp rename openpype/hosts/unreal/integration/{ => UE_5.0}/Source/OpenPype/Private/OpenPypeStyle.cpp (100%) create mode 100644 openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainer.h create mode 100644 openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainerFactory.h rename openpype/hosts/unreal/integration/{ => UE_5.0}/Source/OpenPype/Public/OpenPype.h (100%) rename openpype/hosts/unreal/integration/{ => UE_5.0}/Source/OpenPype/Public/OpenPypeCommands.h (100%) create mode 100644 openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypeLib.h create mode 100644 openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePublishInstance.h create mode 100644 openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h create mode 100644 openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePythonBridge.h rename openpype/hosts/unreal/integration/{ => UE_5.0}/Source/OpenPype/Public/OpenPypeStyle.h (100%) delete mode 160000 repos/avalon-core diff --git a/openpype/hosts/unreal/__init__.py b/openpype/hosts/unreal/__init__.py index bedf5a29f7..ae9b113acd 100644 --- a/openpype/hosts/unreal/__init__.py +++ b/openpype/hosts/unreal/__init__.py @@ -1,13 +1,19 @@ import os import openpype.hosts +from openpype.lib.applications import Application -def add_implementation_envs(env, _app): +def add_implementation_envs(env: dict, _app: Application) -> None: """Modify environments to contain all required for implementation.""" # Set OPENPYPE_UNREAL_PLUGIN required for Unreal implementation + + engine_version = _app.name.split("/")[-1].replace("-", ".") + major_version = int(engine_version.split(".")[0]) + + ue_plugin = "UE_4.7" if major_version == 4 else "UE_5.0" unreal_plugin_path = os.path.join( os.path.dirname(os.path.abspath(openpype.hosts.__file__)), - "unreal", "integration" + "unreal", "integration", ue_plugin ) if not env.get("OPENPYPE_UNREAL_PLUGIN"): env["OPENPYPE_UNREAL_PLUGIN"] = unreal_plugin_path diff --git a/openpype/hosts/unreal/integration/.gitignore b/openpype/hosts/unreal/integration/UE_4.7/.gitignore similarity index 100% rename from openpype/hosts/unreal/integration/.gitignore rename to openpype/hosts/unreal/integration/UE_4.7/.gitignore diff --git a/openpype/hosts/unreal/integration/Content/Python/init_unreal.py b/openpype/hosts/unreal/integration/UE_4.7/Content/Python/init_unreal.py similarity index 100% rename from openpype/hosts/unreal/integration/Content/Python/init_unreal.py rename to openpype/hosts/unreal/integration/UE_4.7/Content/Python/init_unreal.py diff --git a/openpype/hosts/unreal/integration/OpenPype.uplugin b/openpype/hosts/unreal/integration/UE_4.7/OpenPype.uplugin similarity index 100% rename from openpype/hosts/unreal/integration/OpenPype.uplugin rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype.uplugin diff --git a/openpype/hosts/unreal/integration/README.md b/openpype/hosts/unreal/integration/UE_4.7/README.md similarity index 91% rename from openpype/hosts/unreal/integration/README.md rename to openpype/hosts/unreal/integration/UE_4.7/README.md index a32d89aab8..a08c1ada39 100644 --- a/openpype/hosts/unreal/integration/README.md +++ b/openpype/hosts/unreal/integration/UE_4.7/README.md @@ -1,4 +1,4 @@ -# OpenPype Unreal Integration plugin +# OpenPype Unreal Integration plugin - UE 4.x This is plugin for Unreal Editor, creating menu for [OpenPype](https://github.com/getavalon) tools to run. diff --git a/openpype/hosts/unreal/integration/Resources/openpype128.png b/openpype/hosts/unreal/integration/UE_4.7/Resources/openpype128.png similarity index 100% rename from openpype/hosts/unreal/integration/Resources/openpype128.png rename to openpype/hosts/unreal/integration/UE_4.7/Resources/openpype128.png diff --git a/openpype/hosts/unreal/integration/Resources/openpype40.png b/openpype/hosts/unreal/integration/UE_4.7/Resources/openpype40.png similarity index 100% rename from openpype/hosts/unreal/integration/Resources/openpype40.png rename to openpype/hosts/unreal/integration/UE_4.7/Resources/openpype40.png diff --git a/openpype/hosts/unreal/integration/Resources/openpype512.png b/openpype/hosts/unreal/integration/UE_4.7/Resources/openpype512.png similarity index 100% rename from openpype/hosts/unreal/integration/Resources/openpype512.png rename to openpype/hosts/unreal/integration/UE_4.7/Resources/openpype512.png diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/OpenPype.Build.cs b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/OpenPype.Build.cs new file mode 100644 index 0000000000..c30835b63d --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/OpenPype.Build.cs @@ -0,0 +1,57 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class OpenPype : ModuleRules +{ + public OpenPype(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Projects", + "InputCore", + "UnrealEd", + "LevelEditor", + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Private/AssetContainer.cpp b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/AssetContainer.cpp similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/Private/AssetContainer.cpp rename to openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/AssetContainer.cpp diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Private/AssetContainerFactory.cpp b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/AssetContainerFactory.cpp similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/Private/AssetContainerFactory.cpp rename to openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/AssetContainerFactory.cpp diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPype.cpp b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPype.cpp new file mode 100644 index 0000000000..15c46b3862 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPype.cpp @@ -0,0 +1,103 @@ +#include "OpenPype.h" +#include "LevelEditor.h" +#include "OpenPypePythonBridge.h" +#include "OpenPypeStyle.h" + + +static const FName OpenPypeTabName("OpenPype"); + +#define LOCTEXT_NAMESPACE "FOpenPypeModule" + +// This function is triggered when the plugin is staring up +void FOpenPypeModule::StartupModule() +{ + + FOpenPypeStyle::Initialize(); + FOpenPypeStyle::SetIcon("Logo", "openpype40"); + + // Create the Extender that will add content to the menu + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); + + TSharedPtr MenuExtender = MakeShareable(new FExtender()); + TSharedPtr ToolbarExtender = MakeShareable(new FExtender()); + + MenuExtender->AddMenuExtension( + "LevelEditor", + EExtensionHook::After, + NULL, + FMenuExtensionDelegate::CreateRaw(this, &FOpenPypeModule::AddMenuEntry) + ); + ToolbarExtender->AddToolBarExtension( + "Settings", + EExtensionHook::After, + NULL, + FToolBarExtensionDelegate::CreateRaw(this, &FOpenPypeModule::AddToobarEntry)); + + + LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender); + LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); + +} + +void FOpenPypeModule::ShutdownModule() +{ + FOpenPypeStyle::Shutdown(); +} + + +void FOpenPypeModule::AddMenuEntry(FMenuBuilder& MenuBuilder) +{ + // Create Section + MenuBuilder.BeginSection("OpenPype", TAttribute(FText::FromString("OpenPype"))); + { + // Create a Submenu inside of the Section + MenuBuilder.AddMenuEntry( + FText::FromString("Tools..."), + FText::FromString("Pipeline tools"), + FSlateIcon(FOpenPypeStyle::GetStyleSetName(), "OpenPype.Logo"), + FUIAction(FExecuteAction::CreateRaw(this, &FOpenPypeModule::MenuPopup)) + ); + + MenuBuilder.AddMenuEntry( + FText::FromString("Tools dialog..."), + FText::FromString("Pipeline tools dialog"), + FSlateIcon(FOpenPypeStyle::GetStyleSetName(), "OpenPype.Logo"), + FUIAction(FExecuteAction::CreateRaw(this, &FOpenPypeModule::MenuDialog)) + ); + + } + MenuBuilder.EndSection(); +} + +void FOpenPypeModule::AddToobarEntry(FToolBarBuilder& ToolbarBuilder) +{ + ToolbarBuilder.BeginSection(TEXT("OpenPype")); + { + ToolbarBuilder.AddToolBarButton( + FUIAction( + FExecuteAction::CreateRaw(this, &FOpenPypeModule::MenuPopup), + NULL, + FIsActionChecked() + + ), + NAME_None, + LOCTEXT("OpenPype_label", "OpenPype"), + LOCTEXT("OpenPype_tooltip", "OpenPype Tools"), + FSlateIcon(FOpenPypeStyle::GetStyleSetName(), "OpenPype.Logo") + ); + } + ToolbarBuilder.EndSection(); +} + + +void FOpenPypeModule::MenuPopup() { + UOpenPypePythonBridge* bridge = UOpenPypePythonBridge::Get(); + bridge->RunInPython_Popup(); +} + +void FOpenPypeModule::MenuDialog() { + UOpenPypePythonBridge* bridge = UOpenPypePythonBridge::Get(); + bridge->RunInPython_Dialog(); +} + +IMPLEMENT_MODULE(FOpenPypeModule, OpenPype) diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypeLib.cpp b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypeLib.cpp similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypeLib.cpp rename to openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypeLib.cpp diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypePublishInstance.cpp b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypePublishInstance.cpp similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypePublishInstance.cpp rename to openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypePublishInstance.cpp diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp rename to openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypePythonBridge.cpp b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypePythonBridge.cpp similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypePythonBridge.cpp rename to openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypePythonBridge.cpp diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypeStyle.cpp b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypeStyle.cpp new file mode 100644 index 0000000000..a51c2d6aa5 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypeStyle.cpp @@ -0,0 +1,70 @@ +#include "OpenPypeStyle.h" +#include "Framework/Application/SlateApplication.h" +#include "Styling/SlateStyle.h" +#include "Styling/SlateStyleRegistry.h" + + +TUniquePtr< FSlateStyleSet > FOpenPypeStyle::OpenPypeStyleInstance = nullptr; + +void FOpenPypeStyle::Initialize() +{ + if (!OpenPypeStyleInstance.IsValid()) + { + OpenPypeStyleInstance = Create(); + FSlateStyleRegistry::RegisterSlateStyle(*OpenPypeStyleInstance); + } +} + +void FOpenPypeStyle::Shutdown() +{ + if (OpenPypeStyleInstance.IsValid()) + { + FSlateStyleRegistry::UnRegisterSlateStyle(*OpenPypeStyleInstance); + OpenPypeStyleInstance.Reset(); + } +} + +FName FOpenPypeStyle::GetStyleSetName() +{ + static FName StyleSetName(TEXT("OpenPypeStyle")); + return StyleSetName; +} + +FName FOpenPypeStyle::GetContextName() +{ + static FName ContextName(TEXT("OpenPype")); + return ContextName; +} + +#define IMAGE_BRUSH(RelativePath, ...) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) + +const FVector2D Icon40x40(40.0f, 40.0f); + +TUniquePtr< FSlateStyleSet > FOpenPypeStyle::Create() +{ + TUniquePtr< FSlateStyleSet > Style = MakeUnique(GetStyleSetName()); + Style->SetContentRoot(FPaths::ProjectPluginsDir() / TEXT("OpenPype/Resources")); + + return Style; +} + +void FOpenPypeStyle::SetIcon(const FString& StyleName, const FString& ResourcePath) +{ + FSlateStyleSet* Style = OpenPypeStyleInstance.Get(); + + FString Name(GetContextName().ToString()); + Name = Name + "." + StyleName; + Style->Set(*Name, new FSlateImageBrush(Style->RootToContentDir(ResourcePath, TEXT(".png")), Icon40x40)); + + + FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); +} + +#undef IMAGE_BRUSH + +const ISlateStyle& FOpenPypeStyle::Get() +{ + check(OpenPypeStyleInstance); + return *OpenPypeStyleInstance; + return *OpenPypeStyleInstance; +} diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Public/AssetContainer.h b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/AssetContainer.h similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/Public/AssetContainer.h rename to openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/AssetContainer.h diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Public/AssetContainerFactory.h b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/AssetContainerFactory.h similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/Public/AssetContainerFactory.h rename to openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/AssetContainerFactory.h diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPype.h b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPype.h new file mode 100644 index 0000000000..db3f299354 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPype.h @@ -0,0 +1,21 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Engine.h" + + +class FOpenPypeModule : public IModuleInterface +{ +public: + virtual void StartupModule() override; + virtual void ShutdownModule() override; + +private: + + void AddMenuEntry(FMenuBuilder& MenuBuilder); + void AddToobarEntry(FToolBarBuilder& ToolbarBuilder); + void MenuPopup(); + void MenuDialog(); + +}; diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypeLib.h b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypeLib.h similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypeLib.h rename to openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypeLib.h diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypePublishInstance.h b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypePublishInstance.h similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypePublishInstance.h rename to openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypePublishInstance.h diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h rename to openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypePythonBridge.h b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypePythonBridge.h similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypePythonBridge.h rename to openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypePythonBridge.h diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypeStyle.h b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypeStyle.h new file mode 100644 index 0000000000..fbc8bcdd5b --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypeStyle.h @@ -0,0 +1,22 @@ +#pragma once +#include "CoreMinimal.h" + +class FSlateStyleSet; +class ISlateStyle; + + +class FOpenPypeStyle +{ +public: + static void Initialize(); + static void Shutdown(); + static const ISlateStyle& Get(); + static FName GetStyleSetName(); + static FName GetContextName(); + + static void SetIcon(const FString& StyleName, const FString& ResourcePath); + +private: + static TUniquePtr< FSlateStyleSet > Create(); + static TUniquePtr< FSlateStyleSet > OpenPypeStyleInstance; +}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/.gitignore b/openpype/hosts/unreal/integration/UE_5.0/.gitignore new file mode 100644 index 0000000000..b32a6f55e5 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/.gitignore @@ -0,0 +1,35 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +/Binaries +/Intermediate diff --git a/openpype/hosts/unreal/integration/UE_5.0/Content/Python/__init__.py b/openpype/hosts/unreal/integration/UE_5.0/Content/Python/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/hosts/unreal/integration/UE_5.0/Content/Python/init_unreal.py b/openpype/hosts/unreal/integration/UE_5.0/Content/Python/init_unreal.py new file mode 100644 index 0000000000..4bb03b07ed --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Content/Python/init_unreal.py @@ -0,0 +1,28 @@ +import unreal + +openpype_detected = True +try: + from openpype.pipeline import install_host + from openpype.hosts.unreal import api as openpype_host +except ImportError as exc: + openpype_host = None + openpype_detected = False + unreal.log_error("OpenPype: cannot load OpenPype [ {} ]".format(exc)) + +if openpype_detected: + install_host(openpype_host) + + +@unreal.uclass() +class OpenPypeIntegration(unreal.OpenPypePythonBridge): + @unreal.ufunction(override=True) + def RunInPython_Popup(self): + unreal.log_warning("OpenPype: showing tools popup") + if openpype_detected: + openpype_host.show_tools_popup() + + @unreal.ufunction(override=True) + def RunInPython_Dialog(self): + unreal.log_warning("OpenPype: showing tools dialog") + if openpype_detected: + openpype_host.show_tools_dialog() diff --git a/openpype/hosts/unreal/integration/UE_5.0/Content/__init__.py b/openpype/hosts/unreal/integration/UE_5.0/Content/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype.uplugin b/openpype/hosts/unreal/integration/UE_5.0/OpenPype.uplugin new file mode 100644 index 0000000000..4c7a74403c --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype.uplugin @@ -0,0 +1,24 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "OpenPype", + "Description": "OpenPype Integration", + "Category": "OpenPype.Integration", + "CreatedBy": "Ondrej Samohel", + "CreatedByURL": "https://openpype.io", + "DocsURL": "https://openpype.io/docs/artist_hosts_unreal", + "MarketplaceURL": "", + "SupportURL": "https://pype.club/", + "CanContainContent": true, + "IsBetaVersion": true, + "IsExperimentalVersion": false, + "Installed": false, + "Modules": [ + { + "Name": "OpenPype", + "Type": "Editor", + "LoadingPhase": "Default" + } + ] +} \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/README.md b/openpype/hosts/unreal/integration/UE_5.0/README.md new file mode 100644 index 0000000000..cf0aa622c2 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/README.md @@ -0,0 +1,11 @@ +# OpenPype Unreal Integration plugin - UE 5.x + +This is plugin for Unreal Editor, creating menu for [OpenPype](https://github.com/getavalon) tools to run. + +## How does this work + +Plugin is creating basic menu items in **Window/OpenPype** section of Unreal Editor main menu and a button +on the main toolbar with associated menu. Clicking on those menu items is calling callbacks that are +declared in C++ but needs to be implemented during Unreal Editor +startup in `Plugins/OpenPype/Content/Python/init_unreal.py` - this should be executed by Unreal Editor +automatically. diff --git a/openpype/hosts/unreal/integration/UE_5.0/Resources/openpype128.png b/openpype/hosts/unreal/integration/UE_5.0/Resources/openpype128.png new file mode 100644 index 0000000000000000000000000000000000000000..abe8a807ef40f00b75d7446d020a2437732c7583 GIT binary patch literal 14594 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 literal 0 HcmV?d00001 diff --git a/openpype/hosts/unreal/integration/UE_5.0/Resources/openpype512.png b/openpype/hosts/unreal/integration/UE_5.0/Resources/openpype512.png new file mode 100644 index 0000000000000000000000000000000000000000..97c4d4326bc16ba6dfb45d35c4362d8bc15900ae GIT binary patch literal 85856 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 bCJFu@j}?W0K?Ok;00000NkvXXu0mjf9`4Bp literal 0 HcmV?d00001 diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/OpenPype.Build.cs b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/OpenPype.Build.cs similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/OpenPype.Build.cs rename to openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/OpenPype.Build.cs diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainer.cpp b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainer.cpp new file mode 100644 index 0000000000..c766f87a8e --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainer.cpp @@ -0,0 +1,115 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "AssetContainer.h" +#include "AssetRegistryModule.h" +#include "Misc/PackageName.h" +#include "Engine.h" +#include "Containers/UnrealString.h" + +UAssetContainer::UAssetContainer(const FObjectInitializer& ObjectInitializer) +: UAssetUserData(ObjectInitializer) +{ + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + FString path = UAssetContainer::GetPathName(); + UE_LOG(LogTemp, Warning, TEXT("UAssetContainer %s"), *path); + FARFilter Filter; + Filter.PackagePaths.Add(FName(*path)); + + AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAssetContainer::OnAssetAdded); + AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAssetContainer::OnAssetRemoved); + AssetRegistryModule.Get().OnAssetRenamed().AddUObject(this, &UAssetContainer::OnAssetRenamed); +} + +void UAssetContainer::OnAssetAdded(const FAssetData& AssetData) +{ + TArray split; + + // get directory of current container + FString selfFullPath = UAssetContainer::GetPathName(); + FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); + + // get asset path and class + FString assetPath = AssetData.GetFullName(); + FString assetFName = AssetData.AssetClass.ToString(); + + // split path + assetPath.ParseIntoArray(split, TEXT(" "), true); + + FString assetDir = FPackageName::GetLongPackagePath(*split[1]); + + // take interest only in paths starting with path of current container + if (assetDir.StartsWith(*selfDir)) + { + // exclude self + if (assetFName != "AssetContainer") + { + assets.Add(assetPath); + assetsData.Add(AssetData); + UE_LOG(LogTemp, Log, TEXT("%s: asset added to %s"), *selfFullPath, *selfDir); + } + } +} + +void UAssetContainer::OnAssetRemoved(const FAssetData& AssetData) +{ + TArray split; + + // get directory of current container + FString selfFullPath = UAssetContainer::GetPathName(); + FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); + + // get asset path and class + FString assetPath = AssetData.GetFullName(); + FString assetFName = AssetData.AssetClass.ToString(); + + // split path + assetPath.ParseIntoArray(split, TEXT(" "), true); + + FString assetDir = FPackageName::GetLongPackagePath(*split[1]); + + // take interest only in paths starting with path of current container + FString path = UAssetContainer::GetPathName(); + FString lpp = FPackageName::GetLongPackagePath(*path); + + if (assetDir.StartsWith(*selfDir)) + { + // exclude self + if (assetFName != "AssetContainer") + { + // UE_LOG(LogTemp, Warning, TEXT("%s: asset removed"), *lpp); + assets.Remove(assetPath); + assetsData.Remove(AssetData); + } + } +} + +void UAssetContainer::OnAssetRenamed(const FAssetData& AssetData, const FString& str) +{ + TArray split; + + // get directory of current container + FString selfFullPath = UAssetContainer::GetPathName(); + FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); + + // get asset path and class + FString assetPath = AssetData.GetFullName(); + FString assetFName = AssetData.AssetClass.ToString(); + + // split path + assetPath.ParseIntoArray(split, TEXT(" "), true); + + FString assetDir = FPackageName::GetLongPackagePath(*split[1]); + if (assetDir.StartsWith(*selfDir)) + { + // exclude self + if (assetFName != "AssetContainer") + { + + assets.Remove(str); + assets.Add(assetPath); + assetsData.Remove(AssetData); + // UE_LOG(LogTemp, Warning, TEXT("%s: asset renamed %s"), *lpp, *str); + } + } +} + diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainerFactory.cpp b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainerFactory.cpp new file mode 100644 index 0000000000..b943150bdd --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainerFactory.cpp @@ -0,0 +1,20 @@ +#include "AssetContainerFactory.h" +#include "AssetContainer.h" + +UAssetContainerFactory::UAssetContainerFactory(const FObjectInitializer& ObjectInitializer) + : UFactory(ObjectInitializer) +{ + SupportedClass = UAssetContainer::StaticClass(); + bCreateNew = false; + bEditorImport = true; +} + +UObject* UAssetContainerFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + UAssetContainer* AssetContainer = NewObject(InParent, Class, Name, Flags); + return AssetContainer; +} + +bool UAssetContainerFactory::ShouldShowInNewMenu() const { + return false; +} diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPype.cpp b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPype.cpp similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPype.cpp rename to openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPype.cpp diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypeCommands.cpp b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeCommands.cpp similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypeCommands.cpp rename to openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeCommands.cpp diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeLib.cpp b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeLib.cpp new file mode 100644 index 0000000000..5facab7b8b --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeLib.cpp @@ -0,0 +1,48 @@ +#include "OpenPypeLib.h" +#include "Misc/Paths.h" +#include "Misc/ConfigCacheIni.h" +#include "UObject/UnrealType.h" + +/** + * Sets color on folder icon on given path + * @param InPath - path to folder + * @param InFolderColor - color of the folder + * @warning This color will appear only after Editor restart. Is there a better way? + */ + +void UOpenPypeLib::CSetFolderColor(FString FolderPath, FLinearColor FolderColor, bool bForceAdd) +{ + auto SaveColorInternal = [](FString InPath, FLinearColor InFolderColor) + { + // Saves the color of the folder to the config + if (FPaths::FileExists(GEditorPerProjectIni)) + { + GConfig->SetString(TEXT("PathColor"), *InPath, *InFolderColor.ToString(), GEditorPerProjectIni); + } + + }; + + SaveColorInternal(FolderPath, FolderColor); + +} +/** + * Returns all poperties on given object + * @param cls - class + * @return TArray of properties + */ +TArray UOpenPypeLib::GetAllProperties(UClass* cls) +{ + TArray Ret; + if (cls != nullptr) + { + for (TFieldIterator It(cls); It; ++It) + { + FProperty* Property = *It; + if (Property->HasAnyPropertyFlags(EPropertyFlags::CPF_Edit)) + { + Ret.Add(Property->GetName()); + } + } + } + return Ret; +} diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePublishInstance.cpp b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePublishInstance.cpp new file mode 100644 index 0000000000..4f1e846c0b --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePublishInstance.cpp @@ -0,0 +1,108 @@ +#pragma once + +#include "OpenPypePublishInstance.h" +#include "AssetRegistryModule.h" + + +UOpenPypePublishInstance::UOpenPypePublishInstance(const FObjectInitializer& ObjectInitializer) + : UObject(ObjectInitializer) +{ + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + FString path = UOpenPypePublishInstance::GetPathName(); + FARFilter Filter; + Filter.PackagePaths.Add(FName(*path)); + + AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UOpenPypePublishInstance::OnAssetAdded); + AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UOpenPypePublishInstance::OnAssetRemoved); + AssetRegistryModule.Get().OnAssetRenamed().AddUObject(this, &UOpenPypePublishInstance::OnAssetRenamed); +} + +void UOpenPypePublishInstance::OnAssetAdded(const FAssetData& AssetData) +{ + TArray split; + + // get directory of current container + FString selfFullPath = UOpenPypePublishInstance::GetPathName(); + FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); + + // get asset path and class + FString assetPath = AssetData.GetFullName(); + FString assetFName = AssetData.AssetClass.ToString(); + + // split path + assetPath.ParseIntoArray(split, TEXT(" "), true); + + FString assetDir = FPackageName::GetLongPackagePath(*split[1]); + + // take interest only in paths starting with path of current container + if (assetDir.StartsWith(*selfDir)) + { + // exclude self + if (assetFName != "OpenPypePublishInstance") + { + assets.Add(assetPath); + UE_LOG(LogTemp, Log, TEXT("%s: asset added to %s"), *selfFullPath, *selfDir); + } + } +} + +void UOpenPypePublishInstance::OnAssetRemoved(const FAssetData& AssetData) +{ + TArray split; + + // get directory of current container + FString selfFullPath = UOpenPypePublishInstance::GetPathName(); + FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); + + // get asset path and class + FString assetPath = AssetData.GetFullName(); + FString assetFName = AssetData.AssetClass.ToString(); + + // split path + assetPath.ParseIntoArray(split, TEXT(" "), true); + + FString assetDir = FPackageName::GetLongPackagePath(*split[1]); + + // take interest only in paths starting with path of current container + FString path = UOpenPypePublishInstance::GetPathName(); + FString lpp = FPackageName::GetLongPackagePath(*path); + + if (assetDir.StartsWith(*selfDir)) + { + // exclude self + if (assetFName != "OpenPypePublishInstance") + { + // UE_LOG(LogTemp, Warning, TEXT("%s: asset removed"), *lpp); + assets.Remove(assetPath); + } + } +} + +void UOpenPypePublishInstance::OnAssetRenamed(const FAssetData& AssetData, const FString& str) +{ + TArray split; + + // get directory of current container + FString selfFullPath = UOpenPypePublishInstance::GetPathName(); + FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); + + // get asset path and class + FString assetPath = AssetData.GetFullName(); + FString assetFName = AssetData.AssetClass.ToString(); + + // split path + assetPath.ParseIntoArray(split, TEXT(" "), true); + + FString assetDir = FPackageName::GetLongPackagePath(*split[1]); + if (assetDir.StartsWith(*selfDir)) + { + // exclude self + if (assetFName != "AssetContainer") + { + + assets.Remove(str); + assets.Add(assetPath); + // UE_LOG(LogTemp, Warning, TEXT("%s: asset renamed %s"), *lpp, *str); + } + } +} diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp new file mode 100644 index 0000000000..e61964c689 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp @@ -0,0 +1,20 @@ +#include "OpenPypePublishInstanceFactory.h" +#include "OpenPypePublishInstance.h" + +UOpenPypePublishInstanceFactory::UOpenPypePublishInstanceFactory(const FObjectInitializer& ObjectInitializer) + : UFactory(ObjectInitializer) +{ + SupportedClass = UOpenPypePublishInstance::StaticClass(); + bCreateNew = false; + bEditorImport = true; +} + +UObject* UOpenPypePublishInstanceFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + UOpenPypePublishInstance* OpenPypePublishInstance = NewObject(InParent, Class, Name, Flags); + return OpenPypePublishInstance; +} + +bool UOpenPypePublishInstanceFactory::ShouldShowInNewMenu() const { + return false; +} diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePythonBridge.cpp b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePythonBridge.cpp new file mode 100644 index 0000000000..8113231503 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePythonBridge.cpp @@ -0,0 +1,13 @@ +#include "OpenPypePythonBridge.h" + +UOpenPypePythonBridge* UOpenPypePythonBridge::Get() +{ + TArray OpenPypePythonBridgeClasses; + GetDerivedClasses(UOpenPypePythonBridge::StaticClass(), OpenPypePythonBridgeClasses); + int32 NumClasses = OpenPypePythonBridgeClasses.Num(); + if (NumClasses > 0) + { + return Cast(OpenPypePythonBridgeClasses[NumClasses - 1]->GetDefaultObject()); + } + return nullptr; +}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypeStyle.cpp b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeStyle.cpp similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/Private/OpenPypeStyle.cpp rename to openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeStyle.cpp diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainer.h b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainer.h new file mode 100644 index 0000000000..3c2a360c78 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainer.h @@ -0,0 +1,39 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "Engine/AssetUserData.h" +#include "AssetData.h" +#include "AssetContainer.generated.h" + +/** + * + */ +UCLASS(Blueprintable) +class OPENPYPE_API UAssetContainer : public UAssetUserData +{ + GENERATED_BODY() + +public: + + UAssetContainer(const FObjectInitializer& ObjectInitalizer); + // ~UAssetContainer(); + + UPROPERTY(EditAnywhere, BlueprintReadOnly) + TArray assets; + + // There seems to be no reflection option to expose array of FAssetData + /* + UPROPERTY(Transient, BlueprintReadOnly, Category = "Python", meta=(DisplayName="Assets Data")) + TArray assetsData; + */ +private: + TArray assetsData; + void OnAssetAdded(const FAssetData& AssetData); + void OnAssetRemoved(const FAssetData& AssetData); + void OnAssetRenamed(const FAssetData& AssetData, const FString& str); +}; + + diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainerFactory.h b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainerFactory.h new file mode 100644 index 0000000000..331ce6bb50 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainerFactory.h @@ -0,0 +1,21 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Factories/Factory.h" +#include "AssetContainerFactory.generated.h" + +/** + * + */ +UCLASS() +class OPENPYPE_API UAssetContainerFactory : public UFactory +{ + GENERATED_BODY() + +public: + UAssetContainerFactory(const FObjectInitializer& ObjectInitializer); + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + virtual bool ShouldShowInNewMenu() const override; +}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPype.h b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPype.h similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPype.h rename to openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPype.h diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypeCommands.h b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypeCommands.h similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypeCommands.h rename to openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypeCommands.h diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypeLib.h b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypeLib.h new file mode 100644 index 0000000000..59e9c8bd76 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypeLib.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Engine.h" +#include "OpenPypeLib.generated.h" + + +UCLASS(Blueprintable) +class OPENPYPE_API UOpenPypeLib : public UObject +{ + + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category = Python) + static void CSetFolderColor(FString FolderPath, FLinearColor FolderColor, bool bForceAdd); + + UFUNCTION(BlueprintCallable, Category = Python) + static TArray GetAllProperties(UClass* cls); +}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePublishInstance.h b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePublishInstance.h new file mode 100644 index 0000000000..0a27a078d7 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePublishInstance.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Engine.h" +#include "OpenPypePublishInstance.generated.h" + + +UCLASS(Blueprintable) +class OPENPYPE_API UOpenPypePublishInstance : public UObject +{ + GENERATED_BODY() + +public: + UOpenPypePublishInstance(const FObjectInitializer& ObjectInitalizer); + + UPROPERTY(EditAnywhere, BlueprintReadOnly) + TArray assets; +private: + void OnAssetAdded(const FAssetData& AssetData); + void OnAssetRemoved(const FAssetData& AssetData); + void OnAssetRenamed(const FAssetData& AssetData, const FString& str); +}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h new file mode 100644 index 0000000000..a2b3abe13e --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h @@ -0,0 +1,19 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Factories/Factory.h" +#include "OpenPypePublishInstanceFactory.generated.h" + +/** + * + */ +UCLASS() +class OPENPYPE_API UOpenPypePublishInstanceFactory : public UFactory +{ + GENERATED_BODY() + +public: + UOpenPypePublishInstanceFactory(const FObjectInitializer& ObjectInitializer); + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + virtual bool ShouldShowInNewMenu() const override; +}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePythonBridge.h b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePythonBridge.h new file mode 100644 index 0000000000..692aab2e5e --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePythonBridge.h @@ -0,0 +1,20 @@ +#pragma once +#include "Engine.h" +#include "OpenPypePythonBridge.generated.h" + +UCLASS(Blueprintable) +class UOpenPypePythonBridge : public UObject +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category = Python) + static UOpenPypePythonBridge* Get(); + + UFUNCTION(BlueprintImplementableEvent, Category = Python) + void RunInPython_Popup() const; + + UFUNCTION(BlueprintImplementableEvent, Category = Python) + void RunInPython_Dialog() const; + +}; diff --git a/openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypeStyle.h b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypeStyle.h similarity index 100% rename from openpype/hosts/unreal/integration/Source/OpenPype/Public/OpenPypeStyle.h rename to openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypeStyle.h diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 2b0de44fa9..d17674ea2c 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -1243,9 +1243,19 @@ "host_name": "unreal", "environment": {}, "variants": { - "4-26": { + "4-27": { "use_python_2": false, "environment": {} + }, + "5-0": { + "use_python_2": false, + "environment": { + "UE_PYTHONPATH": "{PYTHONPATH}" + } + }, + "__dynamic_keys_labels__": { + "4-27": "4.27", + "5-0": "5.0" } } }, diff --git a/repos/avalon-core b/repos/avalon-core deleted file mode 160000 index 2fa14cea6f..0000000000 --- a/repos/avalon-core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2fa14cea6f6a9d86eec70bbb96860cbe4c75c8eb From 2b1079be32264ae78d4cb70a10e9c10960db7d6e Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 26 May 2022 14:10:46 +0200 Subject: [PATCH 0366/1227] :recycle: simplify version determination --- openpype/hosts/unreal/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/__init__.py b/openpype/hosts/unreal/__init__.py index ae9b113acd..9c0768b78e 100644 --- a/openpype/hosts/unreal/__init__.py +++ b/openpype/hosts/unreal/__init__.py @@ -10,7 +10,7 @@ def add_implementation_envs(env: dict, _app: Application) -> None: engine_version = _app.name.split("/")[-1].replace("-", ".") major_version = int(engine_version.split(".")[0]) - ue_plugin = "UE_4.7" if major_version == 4 else "UE_5.0" + ue_plugin = "UE_5.0" if _app.name[:1] == "5" else "UE_4.7" unreal_plugin_path = os.path.join( os.path.dirname(os.path.abspath(openpype.hosts.__file__)), "unreal", "integration", ue_plugin From 067058f5d344a9ab940cd33ff0f3ab4436e74dbb Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 26 May 2022 14:13:25 +0200 Subject: [PATCH 0367/1227] :recycle: hound fixes --- openpype/hosts/unreal/__init__.py | 1 - openpype/hosts/unreal/lib.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/unreal/__init__.py b/openpype/hosts/unreal/__init__.py index 9c0768b78e..e0e1f0bc3d 100644 --- a/openpype/hosts/unreal/__init__.py +++ b/openpype/hosts/unreal/__init__.py @@ -8,7 +8,6 @@ def add_implementation_envs(env: dict, _app: Application) -> None: # Set OPENPYPE_UNREAL_PLUGIN required for Unreal implementation engine_version = _app.name.split("/")[-1].replace("-", ".") - major_version = int(engine_version.split(".")[0]) ue_plugin = "UE_5.0" if _app.name[:1] == "5" else "UE_4.7" unreal_plugin_path = os.path.join( diff --git a/openpype/hosts/unreal/lib.py b/openpype/hosts/unreal/lib.py index f220d8dedf..8c453b38b9 100644 --- a/openpype/hosts/unreal/lib.py +++ b/openpype/hosts/unreal/lib.py @@ -301,8 +301,8 @@ def create_unreal_project(project_name: str, raise NotImplementedError("Unsupported platform") if not python_path.exists(): raise RuntimeError(f"Unreal Python not found at {python_path}") - out = subprocess.check_call( - [python_path.as_posix(), "-m", "pip", "install", "--user", "pyside2"]) + subprocess.check_call( + [python_path.as_posix(), "-m", "pip", "install", "pyside2"]) if dev_mode or preset["dev_mode"]: _prepare_cpp_project(project_file, engine_path, ue_version) From 9a5dce42af1d1d9befc021ca1326f2e2a3a2fdc7 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 26 May 2022 14:17:41 +0200 Subject: [PATCH 0368/1227] :recycle: hound fixes #2 :dog: --- openpype/hosts/unreal/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/unreal/__init__.py b/openpype/hosts/unreal/__init__.py index e0e1f0bc3d..10e9c5100e 100644 --- a/openpype/hosts/unreal/__init__.py +++ b/openpype/hosts/unreal/__init__.py @@ -7,8 +7,6 @@ def add_implementation_envs(env: dict, _app: Application) -> None: """Modify environments to contain all required for implementation.""" # Set OPENPYPE_UNREAL_PLUGIN required for Unreal implementation - engine_version = _app.name.split("/")[-1].replace("-", ".") - ue_plugin = "UE_5.0" if _app.name[:1] == "5" else "UE_4.7" unreal_plugin_path = os.path.join( os.path.dirname(os.path.abspath(openpype.hosts.__file__)), From e4878eac8aecf8993b5b690401539fba88bb182c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 26 May 2022 14:25:51 +0200 Subject: [PATCH 0369/1227] Flame: make sure repre name is first segment from tokenizable str --- .../flame/plugins/publish/extract_subset_resources.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index eea575ea88..1bfe980a01 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -232,10 +232,14 @@ class ExtractSubsetResources(openpype.api.Extractor): opfapi.export_clip( export_dir_path, exporting_clip, preset_path, **export_kwargs) + # make sure only first segment is used if underscore in name + # HACK: `ftrackreview_withLUT` will result only in `ftrackreview` + repr_name = unique_name.split("_")[0] + # create representation data representation_data = { - "name": unique_name, - "outputName": unique_name, + "name": repr_name, + "outputName": repr_name, "ext": extension, "stagingDir": export_dir_path, "tags": repre_tags, From 4fbdefdb6c95b461cbe43e70a0e3de6c6ec7992e Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 26 May 2022 14:43:48 +0200 Subject: [PATCH 0370/1227] :recycle: fps from asset, few style changes --- .../unreal/plugins/load/load_animation.py | 25 +++++------- .../hosts/unreal/plugins/load/load_layout.py | 39 +++++++++---------- 2 files changed, 29 insertions(+), 35 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_animation.py b/openpype/hosts/unreal/plugins/load/load_animation.py index 54b43c500c..da2830bc52 100644 --- a/openpype/hosts/unreal/plugins/load/load_animation.py +++ b/openpype/hosts/unreal/plugins/load/load_animation.py @@ -14,6 +14,7 @@ from openpype.pipeline import ( ) from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline +from openpype.api import get_asset class AnimationFBXLoader(plugin.Loader): @@ -79,7 +80,7 @@ class AnimationFBXLoader(plugin.Loader): task.options.anim_sequence_import_data.set_editor_property( 'use_default_sample_rate', False) task.options.anim_sequence_import_data.set_editor_property( - 'custom_sample_rate', 25.0) # TODO: get from database + 'custom_sample_rate', get_asset()["data"].get("fps")) task.options.anim_sequence_import_data.set_editor_property( 'import_custom_attribute', True) task.options.anim_sequence_import_data.set_editor_property( @@ -141,22 +142,18 @@ class AnimationFBXLoader(plugin.Loader): root = "/Game/OpenPype" asset = context.get('asset').get('name') suffix = "_CON" - if asset: - asset_name = "{}_{}".format(asset, name) - else: - asset_name = "{}".format(name) - + asset_name = f"{asset}_{name}" if asset else f"{name}" tools = unreal.AssetToolsHelpers().get_asset_tools() asset_dir, container_name = tools.create_unique_asset_name( f"{root}/Animations/{asset}/{name}", suffix="") ar = unreal.AssetRegistryHelpers.get_asset_registry() - filter = unreal.ARFilter( + _filter = unreal.ARFilter( class_names=["World"], package_paths=[f"{root}/{hierarchy[0]}"], recursive_paths=False) - levels = ar.get_assets(filter) + levels = ar.get_assets(_filter) master_level = levels[0].get_editor_property('object_path') hierarchy_dir = root @@ -164,11 +161,11 @@ class AnimationFBXLoader(plugin.Loader): hierarchy_dir = f"{hierarchy_dir}/{h}" hierarchy_dir = f"{hierarchy_dir}/{asset}" - filter = unreal.ARFilter( + _filter = unreal.ARFilter( class_names=["World"], package_paths=[f"{hierarchy_dir}/"], recursive_paths=True) - levels = ar.get_assets(filter) + levels = ar.get_assets(_filter) level = levels[0].get_editor_property('object_path') unreal.EditorLevelLibrary.save_all_dirty_levels() @@ -235,8 +232,7 @@ class AnimationFBXLoader(plugin.Loader): "parent": context["representation"]["parent"], "family": context["representation"]["context"]["family"] } - unreal_pipeline.imprint( - "{}/{}".format(asset_dir, container_name), data) + unreal_pipeline.imprint(f"{asset_dir}/{container_name}", data) imported_content = EditorAssetLibrary.list_assets( asset_dir, recursive=True, include_folder=False) @@ -283,7 +279,7 @@ class AnimationFBXLoader(plugin.Loader): task.options.anim_sequence_import_data.set_editor_property( 'use_default_sample_rate', False) task.options.anim_sequence_import_data.set_editor_property( - 'custom_sample_rate', 25.0) # TODO: get from database + 'custom_sample_rate', get_asset()["data"].get("fps")) task.options.anim_sequence_import_data.set_editor_property( 'import_custom_attribute', True) task.options.anim_sequence_import_data.set_editor_property( @@ -300,8 +296,7 @@ class AnimationFBXLoader(plugin.Loader): # do import fbx and replace existing data unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) - container_path = "{}/{}".format(container["namespace"], - container["objectName"]) + container_path = f'{container["namespace"]}/{container["objectName"]}' # update metadata unreal_pipeline.imprint( container_path, diff --git a/openpype/hosts/unreal/plugins/load/load_layout.py b/openpype/hosts/unreal/plugins/load/load_layout.py index 49611c6c05..0632c3c0b5 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout.py +++ b/openpype/hosts/unreal/plugins/load/load_layout.py @@ -20,6 +20,7 @@ from openpype.pipeline import ( AVALON_CONTAINER_ID, legacy_io, ) +from openpype.api import get_asset from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline @@ -87,7 +88,8 @@ class LayoutLoader(plugin.Loader): return None - def _get_data(self, asset_name): + @staticmethod + def _get_data(asset_name): asset_doc = legacy_io.find_one({ "type": "asset", "name": asset_name @@ -95,8 +97,9 @@ class LayoutLoader(plugin.Loader): return asset_doc.get("data") + @staticmethod def _set_sequence_hierarchy( - self, seq_i, seq_j, max_frame_i, min_frame_j, max_frame_j, map_paths + seq_i, seq_j, max_frame_i, min_frame_j, max_frame_j, map_paths ): # Get existing sequencer tracks or create them if they don't exist tracks = seq_i.get_master_tracks() @@ -165,8 +168,9 @@ class LayoutLoader(plugin.Loader): hid_section.set_row_index(index) hid_section.set_level_names(maps) + @staticmethod def _process_family( - self, assets, class_name, transform, sequence, inst_name=None + assets, class_name, transform, sequence, inst_name=None ): ar = unreal.AssetRegistryHelpers.get_asset_registry() @@ -264,7 +268,7 @@ class LayoutLoader(plugin.Loader): task.options.anim_sequence_import_data.set_editor_property( 'use_default_sample_rate', False) task.options.anim_sequence_import_data.set_editor_property( - 'custom_sample_rate', 25.0) # TODO: get from database + 'custom_sample_rate', get_asset()["data"].get("fps")) task.options.anim_sequence_import_data.set_editor_property( 'import_custom_attribute', True) task.options.anim_sequence_import_data.set_editor_property( @@ -313,11 +317,8 @@ class LayoutLoader(plugin.Loader): for binding in bindings: tracks = binding.get_tracks() track = None - if not tracks: - track = binding.add_track( - unreal.MovieSceneSkeletalAnimationTrack) - else: - track = tracks[0] + track = tracks[0] if tracks else binding.add_track( + unreal.MovieSceneSkeletalAnimationTrack) sections = track.get_sections() section = None @@ -337,11 +338,11 @@ class LayoutLoader(plugin.Loader): curr_anim.get_path_name()).parent ).replace('\\', '/') - filter = unreal.ARFilter( + _filter = unreal.ARFilter( class_names=["AssetContainer"], package_paths=[anim_path], recursive_paths=False) - containers = ar.get_assets(filter) + containers = ar.get_assets(_filter) if len(containers) > 0: return @@ -352,6 +353,7 @@ class LayoutLoader(plugin.Loader): sec_params = section.get_editor_property('params') sec_params.set_editor_property('animation', animation) + @staticmethod def _generate_sequence(self, h, h_dir): tools = unreal.AssetToolsHelpers().get_asset_tools() @@ -585,10 +587,7 @@ class LayoutLoader(plugin.Loader): hierarchy_dir_list.append(hierarchy_dir) asset = context.get('asset').get('name') suffix = "_CON" - if asset: - asset_name = "{}_{}".format(asset, name) - else: - asset_name = "{}".format(name) + asset_name = f"{asset}_{name}" if asset else asset_name = name tools = unreal.AssetToolsHelpers().get_asset_tools() asset_dir, container_name = tools.create_unique_asset_name( @@ -802,7 +801,7 @@ class LayoutLoader(plugin.Loader): lc for lc in layout_containers if asset in lc.get('loaded_assets')] - if len(layouts) == 0: + if not layouts: EditorAssetLibrary.delete_directory(str(Path(asset).parent)) # Remove the Level Sequence from the parent. @@ -812,17 +811,17 @@ class LayoutLoader(plugin.Loader): namespace = container.get('namespace').replace(f"{root}/", "") ms_asset = namespace.split('/')[0] ar = unreal.AssetRegistryHelpers.get_asset_registry() - filter = unreal.ARFilter( + _filter = unreal.ARFilter( class_names=["LevelSequence"], package_paths=[f"{root}/{ms_asset}"], recursive_paths=False) - sequences = ar.get_assets(filter) + sequences = ar.get_assets(_filter) master_sequence = sequences[0].get_asset() - filter = unreal.ARFilter( + _filter = unreal.ARFilter( class_names=["World"], package_paths=[f"{root}/{ms_asset}"], recursive_paths=False) - levels = ar.get_assets(filter) + levels = ar.get_assets(_filter) master_level = levels[0].get_editor_property('object_path') sequences = [master_sequence] From 0c2d0bdd75961d8a685c810c6cbbf6d36591669d Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 26 May 2022 14:45:17 +0200 Subject: [PATCH 0371/1227] :bug: fix wrong assignment --- openpype/hosts/unreal/plugins/load/load_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/plugins/load/load_layout.py b/openpype/hosts/unreal/plugins/load/load_layout.py index 0632c3c0b5..c65cd25ac8 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout.py +++ b/openpype/hosts/unreal/plugins/load/load_layout.py @@ -587,7 +587,7 @@ class LayoutLoader(plugin.Loader): hierarchy_dir_list.append(hierarchy_dir) asset = context.get('asset').get('name') suffix = "_CON" - asset_name = f"{asset}_{name}" if asset else asset_name = name + asset_name = f"{asset}_{name}" if asset else name tools = unreal.AssetToolsHelpers().get_asset_tools() asset_dir, container_name = tools.create_unique_asset_name( From d3179847d266be44a96ab08e9b9b7fdffe5b3173 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 26 May 2022 14:59:08 +0200 Subject: [PATCH 0372/1227] General: editorial otio_range in collection was one frame longer --- openpype/lib/editorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/editorial.py b/openpype/lib/editorial.py index 2c877b9d0d..7b2d22f738 100644 --- a/openpype/lib/editorial.py +++ b/openpype/lib/editorial.py @@ -168,7 +168,7 @@ def make_sequence_collection(path, otio_range, metadata): first, last = otio_range_to_frame_range(otio_range) collection = clique.Collection( head=head, tail=tail, padding=metadata["padding"]) - collection.indexes.update([i for i in range(first, (last + 1))]) + collection.indexes.update([i for i in range(first, last)]) return dir_path, collection From f96318cddd5e4cee2f66fa01ca85ccfb6bddb769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Thu, 26 May 2022 15:01:56 +0200 Subject: [PATCH 0373/1227] Update openpype/hosts/flame/otio/flame_export.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/flame/otio/flame_export.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index ffb82b97c2..9756d0442e 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -94,7 +94,7 @@ def create_otio_time_range(start_frame, frame_duration, fps): def _get_metadata(item): if hasattr(item, 'metadata'): - return dict(dict(item.metadata)) if item.metadata else {} + return dict(item.metadata) if item.metadata else {} return {} From 95f836f41175128e548f9369d5e50c148e180e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Thu, 26 May 2022 15:02:17 +0200 Subject: [PATCH 0374/1227] Update openpype/hosts/flame/api/render_utils.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/flame/api/render_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/api/render_utils.py b/openpype/hosts/flame/api/render_utils.py index 9957550af9..da22f117a7 100644 --- a/openpype/hosts/flame/api/render_utils.py +++ b/openpype/hosts/flame/api/render_utils.py @@ -1,8 +1,8 @@ import os from xml.etree import ElementTree as ET -import openpype.api as openpype +import openpype.api import Logger -log = openpype.Logger.get_logger(__name__) +log = Logger.get_logger(__name__) def export_clip(export_path, clip, preset_path, **kwargs): From 2d1f7b9873022ece16f822690a4269a15fb979c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Thu, 26 May 2022 15:02:41 +0200 Subject: [PATCH 0375/1227] Update openpype/hosts/flame/otio/flame_export.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/flame/otio/flame_export.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index 9756d0442e..1e4ef866ed 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -280,9 +280,7 @@ def create_otio_clip(clip_data): segment = clip_data["PySegment"] # calculate source in - media_info = MediaInfoFile(clip_data["fpath"], **{ - "logger": log - }) + media_info = MediaInfoFile(clip_data["fpath"], logger=log) media_timecode_start = media_info.start_frame media_fps = media_info.fps From db5d85080a418ed8e78908bb72339a0a41011b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Thu, 26 May 2022 15:03:29 +0200 Subject: [PATCH 0376/1227] Update openpype/hosts/flame/plugins/publish/extract_subset_resources.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../hosts/flame/plugins/publish/extract_subset_resources.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 1bfe980a01..6319f4b041 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -425,9 +425,7 @@ class ExtractSubsetResources(openpype.api.Extractor): Import clip from path """ dir_path = os.path.dirname(path) - media_info = MediaInfoFile(path, **{ - "logger": self.log - }) + media_info = MediaInfoFile(path, logger=self.log) file_pattern = media_info.file_pattern self.log.debug("__ file_pattern: {}".format(file_pattern)) From 024874b4f653725e56223fba8a2a7f47404c30f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Thu, 26 May 2022 15:06:16 +0200 Subject: [PATCH 0377/1227] Update openpype/hosts/flame/plugins/publish/extract_subset_resources.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../hosts/flame/plugins/publish/extract_subset_resources.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 6319f4b041..9265d3517c 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -60,7 +60,8 @@ class ExtractSubsetResources(openpype.api.Extractor): export_presets_mapping = {} def process(self, instance): - self._make_representation_data(instance) + if "representations" not in instance.data: + instance.data["representations"] = [] # flame objects segment = instance.data["item"] From a2289429a850061484300e81eaed99413a87ef38 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 26 May 2022 15:57:52 +0200 Subject: [PATCH 0378/1227] :sparkles: added collector for FBX camera export --- .../plugins/publish/collect_fbx_camera.py | 20 +++++++++++++++++++ .../defaults/project_settings/maya.json | 3 +++ .../schemas/schema_maya_publish.json | 14 +++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 openpype/hosts/maya/plugins/publish/collect_fbx_camera.py diff --git a/openpype/hosts/maya/plugins/publish/collect_fbx_camera.py b/openpype/hosts/maya/plugins/publish/collect_fbx_camera.py new file mode 100644 index 0000000000..bfa5bccbb9 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/collect_fbx_camera.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from maya import cmds # noqa +import pyblish.api + + +class CollectFbxCamera(pyblish.api.InstancePlugin): + """Collect Camera for FBX export.""" + + order = pyblish.api.CollectorOrder + 0.2 + label = "Collect Camera for FBX export" + families = ["camera"] + + def process(self, instance): + if not instance.data.get("families"): + instance.data["families"] = [] + + if "fbx" not in instance.data["families"]: + instance.data["families"].append("fbx") + + instance.data["cameras"] = True diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 4cdfe1ca5d..e03bdcecc3 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -165,6 +165,9 @@ "CollectMayaRender": { "sync_workfile_version": false }, + "CollectFbxCamera": { + "enabled": false + }, "ValidateInstanceInContext": { "enabled": true, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 2e5bc64e1c..9877b5ff0d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -21,6 +21,20 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "CollectFbxCamera", + "label": "Collect Camera for FBX export", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, { "type": "splitter" }, From ab8068858cf62422b6838b7e526d723f1b81230a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 26 May 2022 16:32:25 +0200 Subject: [PATCH 0379/1227] flame: removing unneeded code --- .../flame/plugins/publish/extract_subset_resources.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 9265d3517c..b91aec15c8 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -51,7 +51,6 @@ class ExtractSubsetResources(openpype.api.Extractor): "path_regex": ".*" } } - keep_original_representation = False # hide publisher during exporting hide_ui_on_process = True @@ -329,14 +328,6 @@ class ExtractSubsetResources(openpype.api.Extractor): ): return True - def _make_representation_data(self, instance): - if ( - self.keep_original_representation - and "representations" not in instance.data - or not self.keep_original_representation - ): - instance.data["representations"] = [] - def _unfolds_nested_folders(self, stage_dir, files_list, ext): """Unfolds nested folders From 24c289a0d5088c708d9e6754dd8ebbc033e0e491 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 26 May 2022 17:18:00 +0200 Subject: [PATCH 0380/1227] nuke: use framerange used as list but it is bool --- openpype/hosts/nuke/api/lib.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index ba8aa7a8db..f40425eefc 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -373,7 +373,7 @@ def add_write_node_legacy(name, **kwarg): Returns: node (obj): nuke write node """ - frame_range = kwarg.get("use_range_limit", None) + use_range_limit = kwarg.get("use_range_limit", None) w = nuke.createNode( "Write", @@ -391,10 +391,10 @@ def add_write_node_legacy(name, **kwarg): log.debug(e) continue - if frame_range: + if use_range_limit: w["use_limit"].setValue(True) - w["first"].setValue(frame_range[0]) - w["last"].setValue(frame_range[1]) + w["first"].setValue(kwarg["frame_range"][0]) + w["last"].setValue(kwarg["frame_range"][1]) return w @@ -409,7 +409,7 @@ def add_write_node(name, file_path, knobs, **kwarg): Returns: node (obj): nuke write node """ - frame_range = kwarg.get("use_range_limit", None) + use_range_limit = kwarg.get("use_range_limit", None) w = nuke.createNode( "Write", @@ -420,10 +420,10 @@ def add_write_node(name, file_path, knobs, **kwarg): # finally add knob overrides set_node_knobs_from_settings(w, knobs, **kwarg) - if frame_range: + if use_range_limit: w["use_limit"].setValue(True) - w["first"].setValue(frame_range[0]) - w["last"].setValue(frame_range[1]) + w["first"].setValue(kwarg["frame_range"][0]) + w["last"].setValue(kwarg["frame_range"][1]) return w From 693efa272fefee7c09aaa93eb78ffa07b3c198f2 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 26 May 2022 16:23:50 +0100 Subject: [PATCH 0381/1227] Camera creates master level if layout is missing --- .../hosts/unreal/plugins/load/load_camera.py | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index b33e45b6e9..c6061bc5c1 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -5,6 +5,7 @@ from pathlib import Path import unreal from unreal import EditorAssetLibrary from unreal import EditorLevelLibrary +from unreal import EditorLevelUtils from openpype.pipeline import ( AVALON_CONTAINER_ID, @@ -84,10 +85,10 @@ class CameraLoader(plugin.Loader): hierarchy = context.get('asset').get('data').get('parents') root = "/Game/OpenPype" hierarchy_dir = root - hierarchy_list = [] + hierarchy_dir_list = [] for h in hierarchy: hierarchy_dir = f"{hierarchy_dir}/{h}" - hierarchy_list.append(hierarchy_dir) + hierarchy_dir_list.append(hierarchy_dir) asset = context.get('asset').get('name') suffix = "_CON" if asset: @@ -121,27 +122,40 @@ class CameraLoader(plugin.Loader): asset_dir, container_name = tools.create_unique_asset_name( f"{hierarchy_dir}/{asset}/{name}_{unique_number:02d}", suffix="") + asset_path = Path(asset_dir) + asset_path_parent = str(asset_path.parent.as_posix()) + container_name += suffix - current_level = EditorLevelLibrary.get_editor_world().get_full_name() + EditorAssetLibrary.make_directory(asset_dir) + + # Create map for the shot, and create hierarchy of map. If the maps + # already exist, we will use them. + h_dir = hierarchy_dir_list[0] + h_asset = hierarchy[0] + master_level = f"{h_dir}/{h_asset}_map.{h_asset}_map" + if not EditorAssetLibrary.does_asset_exist(master_level): + EditorLevelLibrary.new_level(f"{h_dir}/{h_asset}_map") + + level = f"{asset_path_parent}/{asset}_map.{asset}_map" + if not EditorAssetLibrary.does_asset_exist(level): + EditorLevelLibrary.new_level(f"{asset_path_parent}/{asset}_map") + + EditorLevelLibrary.load_level(master_level) + EditorLevelUtils.add_level_to_world( + EditorLevelLibrary.get_editor_world(), + level, + unreal.LevelStreamingDynamic + ) EditorLevelLibrary.save_all_dirty_levels() - - ar = unreal.AssetRegistryHelpers.get_asset_registry() - filter = unreal.ARFilter( - class_names=["World"], - package_paths=[f"{hierarchy_dir}/{asset}/"], - recursive_paths=True) - maps = ar.get_assets(filter) - - # There should be only one map in the list - EditorLevelLibrary.load_level(maps[0].get_full_name()) + EditorLevelLibrary.load_level(level) # Get all the sequences in the hierarchy. It will create them, if # they don't exist. sequences = [] frame_ranges = [] i = 0 - for h in hierarchy_list: + for h in hierarchy_dir_list: root_content = EditorAssetLibrary.list_assets( h, recursive=False, include_folder=False) @@ -256,7 +270,7 @@ class CameraLoader(plugin.Loader): "{}/{}".format(asset_dir, container_name), data) EditorLevelLibrary.save_all_dirty_levels() - EditorLevelLibrary.load_level(current_level) + EditorLevelLibrary.load_level(master_level) asset_content = EditorAssetLibrary.list_assets( asset_dir, recursive=True, include_folder=True From c5787b899257433f0873f61c9aa1408cd68c0bee Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 26 May 2022 17:23:51 +0200 Subject: [PATCH 0382/1227] Flame: small bugs --- openpype/hosts/flame/api/render_utils.py | 2 +- openpype/plugins/publish/collect_otio_subset_resources.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/api/render_utils.py b/openpype/hosts/flame/api/render_utils.py index da22f117a7..a29d6be695 100644 --- a/openpype/hosts/flame/api/render_utils.py +++ b/openpype/hosts/flame/api/render_utils.py @@ -1,6 +1,6 @@ import os from xml.etree import ElementTree as ET -import openpype.api import Logger +from openpype.api import Logger log = Logger.get_logger(__name__) diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index fb4cc6ee5e..40d4f35bdc 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -64,7 +64,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): a_frame_end_h = media_out + handle_end # create trimmed otio time range - trimmed_media_range_h = editorial.range_from_frames( + trimmed_media_range_h = oplib.range_from_frames( a_frame_start_h, (a_frame_end_h - a_frame_start_h) + 1, media_fps ) From ae233ce80cc9ecf549d2fdbebbc50d1ca29b8b98 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 26 May 2022 17:26:00 +0200 Subject: [PATCH 0383/1227] hiero: PR suggestion https://github.com/pypeclub/OpenPype/pull/3224#discussion_r882588237 --- openpype/hosts/hiero/api/lib.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 5a9f38bf92..999dae5488 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -941,6 +941,10 @@ def is_overlapping(ti_test, ti_original, strict=False): (ti_test.timelineIn() <= ti_original.timelineIn()) and (ti_test.timelineOut() >= ti_original.timelineOut()) ) + + if strict: + return covering_exp + inside_exp = ( (ti_test.timelineIn() >= ti_original.timelineIn()) and (ti_test.timelineOut() <= ti_original.timelineOut()) @@ -954,15 +958,12 @@ def is_overlapping(ti_test, ti_original, strict=False): and (ti_test.timelineIn() <= ti_original.timelineIn()) ) - if strict: - return covering_exp - else: - return any(( - covering_exp, - inside_exp, - overlaying_right_exp, - overlaying_left_exp - )) + return any(( + covering_exp, + inside_exp, + overlaying_right_exp, + overlaying_left_exp + )) def get_sequence_pattern_and_padding(file): From 4cd4124e0128cac24938cf3d0098de04b9e5f58c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 26 May 2022 17:40:27 +0200 Subject: [PATCH 0384/1227] flame: fixing frame range from editorial --- openpype/lib/editorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/editorial.py b/openpype/lib/editorial.py index 0de266725f..9f55d1fcb1 100644 --- a/openpype/lib/editorial.py +++ b/openpype/lib/editorial.py @@ -168,7 +168,7 @@ def make_sequence_collection(path, otio_range, metadata): first, last = otio_range_to_frame_range(otio_range) collection = clique.Collection( head=head, tail=tail, padding=metadata["padding"]) - collection.indexes.update([i for i in range(first, (last + 1))]) + collection.indexes.update(list(range(first, last))) return dir_path, collection From 6d4c057a04291da0381af2a1339ec3c68319d50f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 26 May 2022 17:40:42 +0200 Subject: [PATCH 0385/1227] flame: removing default preset --- .../plugins/publish/extract_subset_resources.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index b91aec15c8..0bad3f7cfc 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -34,21 +34,6 @@ class ExtractSubsetResources(openpype.api.Extractor): "representation_add_range": False, "representation_tags": ["thumbnail"], "path_regex": ".*" - }, - "ftrackpreview": { - "active": True, - "ext": "mov", - "xml_preset_file": "Apple iPad (1920x1080).xml", - "xml_preset_dir": "", - "export_type": "Movie", - "parsed_comment_attrs": False, - "colorspace_out": "Output - Rec.709", - "representation_add_range": True, - "representation_tags": [ - "review", - "delete" - ], - "path_regex": ".*" } } From a4c32639d7bcd1aae3735f763fadd7a334968df9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 26 May 2022 17:51:25 +0200 Subject: [PATCH 0386/1227] nuke: adding frame range to plugin --- openpype/hosts/nuke/plugins/create/create_write_prerender.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/nuke/plugins/create/create_write_prerender.py b/openpype/hosts/nuke/plugins/create/create_write_prerender.py index 32ee1fd86f..fec97167fb 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_prerender.py +++ b/openpype/hosts/nuke/plugins/create/create_write_prerender.py @@ -27,6 +27,10 @@ class CreateWritePrerender(plugin.AbstractWriteRender): # add fpath_template write_data["fpath_template"] = self.fpath_template write_data["use_range_limit"] = self.use_range_limit + write_data["frame_range"] = ( + nuke.root()["first_frame"].value(), + nuke.root()["last_frame"].value() + ) if not self.is_legacy(): return create_write_node( From b8cade1009bf4863b12c467fa0cbdef7b8d864e4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 May 2022 18:43:00 +0200 Subject: [PATCH 0387/1227] Fix - Harmony message length Harmony 21.1 doesn't have QDataStream anymore. This means we aren't able to write bytes into QByteArray so we had modify how content lenght is sent do the server. Content lenght is sent as string of 8 char convertible into integer (instead of 0x00000001[4 bytes] > "000000001"[8 bytes]) --- openpype/hosts/harmony/api/TB_sceneOpened.js | 21 ++++++++++++++++---- openpype/hosts/harmony/api/server.py | 12 +++++++---- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/harmony/api/TB_sceneOpened.js b/openpype/hosts/harmony/api/TB_sceneOpened.js index 610b0a73bb..e7cd555332 100644 --- a/openpype/hosts/harmony/api/TB_sceneOpened.js +++ b/openpype/hosts/harmony/api/TB_sceneOpened.js @@ -35,7 +35,11 @@ function Client() { self.pack = function(num) { var ascii=''; for (var i = 3; i >= 0; i--) { - ascii += String.fromCharCode((num >> (8 * i)) & 255); + var hex = ((num >> (8 * i)) & 255).toString(16); + if (hex.length < 2){ + ascii += "0"; + } + ascii += hex; } return ascii; }; @@ -279,12 +283,21 @@ function Client() { }; self._send = function(message) { - var codec_name = new QByteArray().append("ISO-8859-1"); + /** Harmony 21.1 doesn't have QDataStream anymore. + + This means we aren't able to write bytes into QByteArray so we had + modify how content lenght is sent do the server. + Content lenght is sent as string of 8 char convertible into integer + (instead of 0x00000001[4 bytes] > "000000001"[8 bytes]) */ + var codec_name = new QByteArray().append("UTF-8"); + var codec = QTextCodec.codecForName(codec_name); var msg = codec.fromUnicode(message); var l = msg.size(); - var coded = new QByteArray().append('AH').append(self.pack(l)).append(msg); - self.socket.write(new QByteArray(coded)); + var header = new QByteArray().append('AH').append(self.pack(l)); + var coded = msg.prepend(header); + + self.socket.write(coded); self.logDebug('Sent.'); }; diff --git a/openpype/hosts/harmony/api/server.py b/openpype/hosts/harmony/api/server.py index 88cfe54521..0de359ec61 100644 --- a/openpype/hosts/harmony/api/server.py +++ b/openpype/hosts/harmony/api/server.py @@ -88,21 +88,25 @@ class Server(threading.Thread): """ current_time = time.time() while True: - + self.log.info("wait ttt") # Receive the data in small chunks and retransmit it request = None - header = self.connection.recv(6) + header = self.connection.recv(10) if len(header) == 0: # null data received, socket is closing. self.log.info(f"[{self.timestamp()}] Connection closing.") break + if header[0:2] != b"AH": self.log.error("INVALID HEADER") - length = struct.unpack(">I", header[2:])[0] + content_length_str = header[2:].decode() + + length = int(content_length_str, 16) data = self.connection.recv(length) while (len(data) < length): # we didn't received everything in first try, lets wait for # all data. + self.log.info("loop") time.sleep(0.1) if self.connection is None: self.log.error(f"[{self.timestamp()}] " @@ -113,7 +117,7 @@ class Server(threading.Thread): break data += self.connection.recv(length - len(data)) - + self.log.debug("data:: {} {}".format(data, type(data))) self.received += data.decode("utf-8") pretty = self._pretty(self.received) self.log.debug( From bc3a8bbe2ff52903e0d9ab7f029c51407cd70ec3 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Thu, 26 May 2022 18:37:41 +0000 Subject: [PATCH 0388/1227] [Automated] Bump version --- CHANGELOG.md | 37 +++++++++++++++++++------------------ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dd410391a..8437540a49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [3.10.0-nightly.5](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.10.0-nightly.6](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.8...HEAD) @@ -10,14 +10,17 @@ - General: Creator plugins from addons can be registered [\#3179](https://github.com/pypeclub/OpenPype/pull/3179) - Ftrack: Single image reviewable [\#3157](https://github.com/pypeclub/OpenPype/pull/3157) - Nuke: Expose write attributes to settings [\#3123](https://github.com/pypeclub/OpenPype/pull/3123) -- Hiero: Initial frame publish support [\#3106](https://github.com/pypeclub/OpenPype/pull/3106) **🚀 Enhancements** +- Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) +- General: updating common vendor `scriptmenu` to 1.5.2 [\#3246](https://github.com/pypeclub/OpenPype/pull/3246) - Project Manager: Allow to paste Tasks into multiple assets at the same time [\#3226](https://github.com/pypeclub/OpenPype/pull/3226) - Project manager: Sped up project load [\#3216](https://github.com/pypeclub/OpenPype/pull/3216) - Loader UI: Speed issues of loader with sync server [\#3199](https://github.com/pypeclub/OpenPype/pull/3199) +- Looks: add basic support for Renderman [\#3190](https://github.com/pypeclub/OpenPype/pull/3190) - Maya: added clean\_import option to Import loader [\#3181](https://github.com/pypeclub/OpenPype/pull/3181) +- Add the scripts menu definition to nuke [\#3168](https://github.com/pypeclub/OpenPype/pull/3168) - Maya: add maya 2023 to default applications [\#3167](https://github.com/pypeclub/OpenPype/pull/3167) - Compressed bgeo publishing in SAP and Houdini loader [\#3153](https://github.com/pypeclub/OpenPype/pull/3153) - General: Add 'dataclasses' to required python modules [\#3149](https://github.com/pypeclub/OpenPype/pull/3149) @@ -28,24 +31,26 @@ - General: Simplified OP modules/addons import [\#3137](https://github.com/pypeclub/OpenPype/pull/3137) - Terminal: Tweak coloring of TrayModuleManager logging enabled states [\#3133](https://github.com/pypeclub/OpenPype/pull/3133) - General: Cleanup some Loader docstrings [\#3131](https://github.com/pypeclub/OpenPype/pull/3131) -- Nuke: render instance with subset name filtered overrides [\#3117](https://github.com/pypeclub/OpenPype/pull/3117) - Unreal: Layout and Camera update and remove functions reimplemented and improvements [\#3116](https://github.com/pypeclub/OpenPype/pull/3116) -- Settings: Remove environment groups from settings [\#3115](https://github.com/pypeclub/OpenPype/pull/3115) -- TVPaint: Match renderlayer key with other hosts [\#3110](https://github.com/pypeclub/OpenPype/pull/3110) -- Tray publisher: Simple families from settings [\#3105](https://github.com/pypeclub/OpenPype/pull/3105) **🐛 Bug fixes** +- nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) +- Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244) +- Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) - Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) +- Maya: Fix support for multiple resolutions [\#3236](https://github.com/pypeclub/OpenPype/pull/3236) - TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) -- Project Manager: Fix persistent editors on project change [\#3218](https://github.com/pypeclub/OpenPype/pull/3218) +- Hiero: debugging frame range and other 3.10 [\#3222](https://github.com/pypeclub/OpenPype/pull/3222) - Deadline: instance data overwrite fix [\#3214](https://github.com/pypeclub/OpenPype/pull/3214) - Ftrack: Push hierarchical attributes action works [\#3210](https://github.com/pypeclub/OpenPype/pull/3210) - Standalone Publisher: Always create new representation for thumbnail [\#3203](https://github.com/pypeclub/OpenPype/pull/3203) - Photoshop: skip collector when automatic testing [\#3202](https://github.com/pypeclub/OpenPype/pull/3202) - Nuke: render/workfile version sync doesn't work on farm [\#3185](https://github.com/pypeclub/OpenPype/pull/3185) - Ftrack: Review image only if there are no mp4 reviews [\#3183](https://github.com/pypeclub/OpenPype/pull/3183) +- Ftrack: Locations deepcopy issue [\#3177](https://github.com/pypeclub/OpenPype/pull/3177) - General: Avoid creating multiple thumbnails [\#3176](https://github.com/pypeclub/OpenPype/pull/3176) +- General: Avoid creating multiple thumbnails [\#3174](https://github.com/pypeclub/OpenPype/pull/3174) - General/Hiero: better clip duration calculation [\#3169](https://github.com/pypeclub/OpenPype/pull/3169) - General: Oiio conversion for ffmpeg checks for invalid characters [\#3166](https://github.com/pypeclub/OpenPype/pull/3166) - Fix for attaching render to subset [\#3164](https://github.com/pypeclub/OpenPype/pull/3164) @@ -55,19 +60,19 @@ - General: New Session schema [\#3141](https://github.com/pypeclub/OpenPype/pull/3141) - General: Missing version on headless mode crash properly [\#3136](https://github.com/pypeclub/OpenPype/pull/3136) - TVPaint: Composite layers in reversed order [\#3135](https://github.com/pypeclub/OpenPype/pull/3135) -- Nuke: fixing default settings for workfile builder loaders [\#3120](https://github.com/pypeclub/OpenPype/pull/3120) - Nuke: fix anatomy imageio regex default [\#3119](https://github.com/pypeclub/OpenPype/pull/3119) **🔀 Refactored code** -- Avalon repo removed from Jobs workflow [\#3193](https://github.com/pypeclub/OpenPype/pull/3193) - General: Remove remaining imports from avalon [\#3130](https://github.com/pypeclub/OpenPype/pull/3130) **Merged pull requests:** +- Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) +- Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249) +- Webpublish: remove publish highlight when creating subset name [\#3234](https://github.com/pypeclub/OpenPype/pull/3234) - Maya: added jpg to filter for Image Plane Loader [\#3223](https://github.com/pypeclub/OpenPype/pull/3223) - Webpublisher: replace space by underscore in subset names [\#3160](https://github.com/pypeclub/OpenPype/pull/3160) -- StandalonePublisher: removed Extract Background plugins [\#3093](https://github.com/pypeclub/OpenPype/pull/3093) ## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19) @@ -84,11 +89,13 @@ - Standalone Publisher: Always create new representation for thumbnail [\#3204](https://github.com/pypeclub/OpenPype/pull/3204) - Nuke: render/workfile version sync doesn't work on farm [\#3184](https://github.com/pypeclub/OpenPype/pull/3184) - Ftrack: Review image only if there are no mp4 reviews [\#3182](https://github.com/pypeclub/OpenPype/pull/3182) -- Ftrack: Locations deepcopy issue [\#3177](https://github.com/pypeclub/OpenPype/pull/3177) - Ftrack: Locations deepcopy issue [\#3175](https://github.com/pypeclub/OpenPype/pull/3175) -- General: Avoid creating multiple thumbnails [\#3174](https://github.com/pypeclub/OpenPype/pull/3174) - General: TemplateResult can be copied [\#3170](https://github.com/pypeclub/OpenPype/pull/3170) +**🔀 Refactored code** + +- Avalon repo removed from Jobs workflow [\#3193](https://github.com/pypeclub/OpenPype/pull/3193) + **Merged pull requests:** - hiero: otio p3 compatibility issue - metadata on effect use update [\#3194](https://github.com/pypeclub/OpenPype/pull/3194) @@ -123,19 +130,13 @@ - Nuke: render instance with subset name filtered overrides \(3.9.x\) [\#3125](https://github.com/pypeclub/OpenPype/pull/3125) -**🚀 Enhancements** - -- TVPaint: Match renderlayer key with other hosts [\#3109](https://github.com/pypeclub/OpenPype/pull/3109) - **🐛 Bug fixes** - TVPaint: Composite layers in reversed order [\#3134](https://github.com/pypeclub/OpenPype/pull/3134) -- General: Python 3 compatibility in queries [\#3111](https://github.com/pypeclub/OpenPype/pull/3111) **Merged pull requests:** - Ftrack: AssetVersion status on publish [\#3114](https://github.com/pypeclub/OpenPype/pull/3114) -- renderman support for 3.9.x [\#3107](https://github.com/pypeclub/OpenPype/pull/3107) ## [3.9.5](https://github.com/pypeclub/OpenPype/tree/3.9.5) (2022-04-25) diff --git a/openpype/version.py b/openpype/version.py index eee776fd2c..984e4ba426 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.10.0-nightly.5" +__version__ = "3.10.0-nightly.6" diff --git a/pyproject.toml b/pyproject.toml index 50cdefe1bb..1caa2a838a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.10.0-nightly.5" # OpenPype +version = "3.10.0-nightly.6" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 728079233484fdc277a00e4dbffe4cc8d6fe2529 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Thu, 26 May 2022 18:58:41 +0000 Subject: [PATCH 0389/1227] [Automated] Release --- CHANGELOG.md | 13 +++++-------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8437540a49..15659f8aa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [3.10.0-nightly.6](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.8...HEAD) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.8...3.10.0) **🆕 New features** @@ -42,6 +42,7 @@ - Maya: Fix support for multiple resolutions [\#3236](https://github.com/pypeclub/OpenPype/pull/3236) - TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) - Hiero: debugging frame range and other 3.10 [\#3222](https://github.com/pypeclub/OpenPype/pull/3222) +- Project Manager: Fix persistent editors on project change [\#3218](https://github.com/pypeclub/OpenPype/pull/3218) - Deadline: instance data overwrite fix [\#3214](https://github.com/pypeclub/OpenPype/pull/3214) - Ftrack: Push hierarchical attributes action works [\#3210](https://github.com/pypeclub/OpenPype/pull/3210) - Standalone Publisher: Always create new representation for thumbnail [\#3203](https://github.com/pypeclub/OpenPype/pull/3203) @@ -50,7 +51,6 @@ - Ftrack: Review image only if there are no mp4 reviews [\#3183](https://github.com/pypeclub/OpenPype/pull/3183) - Ftrack: Locations deepcopy issue [\#3177](https://github.com/pypeclub/OpenPype/pull/3177) - General: Avoid creating multiple thumbnails [\#3176](https://github.com/pypeclub/OpenPype/pull/3176) -- General: Avoid creating multiple thumbnails [\#3174](https://github.com/pypeclub/OpenPype/pull/3174) - General/Hiero: better clip duration calculation [\#3169](https://github.com/pypeclub/OpenPype/pull/3169) - General: Oiio conversion for ffmpeg checks for invalid characters [\#3166](https://github.com/pypeclub/OpenPype/pull/3166) - Fix for attaching render to subset [\#3164](https://github.com/pypeclub/OpenPype/pull/3164) @@ -64,13 +64,13 @@ **🔀 Refactored code** +- Avalon repo removed from Jobs workflow [\#3193](https://github.com/pypeclub/OpenPype/pull/3193) - General: Remove remaining imports from avalon [\#3130](https://github.com/pypeclub/OpenPype/pull/3130) **Merged pull requests:** - Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) - Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249) -- Webpublish: remove publish highlight when creating subset name [\#3234](https://github.com/pypeclub/OpenPype/pull/3234) - Maya: added jpg to filter for Image Plane Loader [\#3223](https://github.com/pypeclub/OpenPype/pull/3223) - Webpublisher: replace space by underscore in subset names [\#3160](https://github.com/pypeclub/OpenPype/pull/3160) @@ -90,12 +90,9 @@ - Nuke: render/workfile version sync doesn't work on farm [\#3184](https://github.com/pypeclub/OpenPype/pull/3184) - Ftrack: Review image only if there are no mp4 reviews [\#3182](https://github.com/pypeclub/OpenPype/pull/3182) - Ftrack: Locations deepcopy issue [\#3175](https://github.com/pypeclub/OpenPype/pull/3175) +- General: Avoid creating multiple thumbnails [\#3174](https://github.com/pypeclub/OpenPype/pull/3174) - General: TemplateResult can be copied [\#3170](https://github.com/pypeclub/OpenPype/pull/3170) -**🔀 Refactored code** - -- Avalon repo removed from Jobs workflow [\#3193](https://github.com/pypeclub/OpenPype/pull/3193) - **Merged pull requests:** - hiero: otio p3 compatibility issue - metadata on effect use update [\#3194](https://github.com/pypeclub/OpenPype/pull/3194) diff --git a/openpype/version.py b/openpype/version.py index 984e4ba426..31be1f2f02 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.10.0-nightly.6" +__version__ = "3.10.0" diff --git a/pyproject.toml b/pyproject.toml index 1caa2a838a..444af49273 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.10.0-nightly.6" # OpenPype +version = "3.10.0" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From c8a8831f8d7b8afb5f78257fcbeae2dc47e33c52 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 27 May 2022 10:33:46 +0200 Subject: [PATCH 0390/1227] :bug: fix getting attribute for multilayer pxr --- openpype/hosts/maya/plugins/publish/collect_look.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index d295492f9a..7d258b01fa 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -571,7 +571,7 @@ class CollectLook(pyblish.api.InstancePlugin): self.log.debug(" - got {}".format(cmds.nodeType(node))) - attribute = FILE_NODES.get(cmds.nodeType(node)) + attribute = get_attributes(FILE_NODES, cmds.nodeType(node)) source = cmds.getAttr("{}.{}".format( node, attribute From eff55dfce412ccc88666be98c207f5a9fbfe9ab3 Mon Sep 17 00:00:00 2001 From: DMO Date: Fri, 27 May 2022 17:52:33 +0900 Subject: [PATCH 0391/1227] Expanding the scope of mvLook to publish mipmapped textures too, like TDL's generated by 3delight. During collection, we check to see if the TDL's exist beside the original texture, and if they do they get added to the collection. Validation of these will depend on the publish intent and whether `expectMipMap` is True in the publish set. If mipmaps are expected and the intent is correct, the mipmaps will be published along with the original file, which will accelerate rendering, since 3dl will find that TDL and use it instead of having to generate it. --- .../plugins/create/create_multiverse_look.py | 1 + .../publish/collect_multiverse_look.py | 50 +++++++++- .../publish/validate_mvlook_contents.py | 93 +++++++++++++++++++ 3 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_look.py b/openpype/hosts/maya/plugins/create/create_multiverse_look.py index b2b9b5bb29..44977efca0 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_look.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_look.py @@ -12,3 +12,4 @@ class CreateMultiverseLook(plugin.Creator): def __init__(self, *args, **kwargs): super(CreateMultiverseLook, self).__init__(*args, **kwargs) self.data["fileFormat"] = ["usda", "usd"] + self.data["expectMipMap"] = True diff --git a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py index c5ea6a2253..6bf4cecc3a 100644 --- a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py @@ -17,6 +17,9 @@ SHAPE_ATTRS = ["castsShadows", "opposite"] SHAPE_ATTRS = set(SHAPE_ATTRS) +COLOUR_SPACES = ['sRGB'] +MIPMAP_EXTENSIONS = ['tdl'] + def get_look_attrs(node): """Returns attributes of a node that are important for the look. @@ -103,7 +106,7 @@ def seq_to_glob(path): "": "", "#": "#", "u_v": "|", - "", #noqa - copied from collect_look.py + "", # noqa - copied from collect_look.py "": "" } @@ -189,6 +192,22 @@ def get_file_node_files(node): return [] +def get_mipmap(fname): + for colour_space in COLOUR_SPACES: + for mipmap_ext in MIPMAP_EXTENSIONS: + mipmap_fname = '.'.join([fname, colour_space, mipmap_ext]) + if os.path.exists(mipmap_fname): + return mipmap_fname + return None + + +def is_mipamp(fname): + ext = os.path.splitext(fname)[1][1:] + if ext in MIPMAP_EXTENSIONS: + return True + return False + + class CollectMultiverseLookData(pyblish.api.InstancePlugin): """Collect Multiverse Look @@ -219,6 +238,7 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): files = [] sets = {} instance.data["resources"] = [] + expectMipMap = instance.data["expectMipMap"] for node in nodes: self.log.info("Getting resources for '{}'".format(node)) @@ -242,7 +262,7 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): files = cmds.ls(history, type="file", long=True) for f in files: - resources = self.collect_resource(f) + resources = self.collect_resource(f, expectMipMap) instance.data["resources"].append(resources) elif isinstance(matOver, multiverse.MaterialSourceUsdPath): @@ -255,7 +275,7 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): "relationships": sets } - def collect_resource(self, node): + def collect_resource(self, node, expectMipMap): """Collect the link to the file(s) used (resource) Args: node (str): name of the node @@ -310,6 +330,7 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): source = source.replace("\\", "/") files = get_file_node_files(node) + files = self.handle_files(files, expectMipMap) if len(files) == 0: self.log.error("No valid files found from node `%s`" % node) @@ -326,3 +347,26 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): "source": source, # required for resources "files": files, "color_space": color_space} # required for resources + + def handle_files(self, files, expectMipMap): + """This will go through all the files and make sure that they are + either already mipmapped or have a corresponding mipmap sidecar and + add that to the list.""" + if not expectMipMap: + return files + + extra_files = [] + self.log.debug("Expecting MipMaps, going to look for them.") + for fname in files: + self.log.info("Checking '{}' for mipmaps".format(fname)) + if is_mipamp(fname): + self.log.debug(" - file is already MipMap, skipping.") + continue + + mipmap = get_mipmap(fname) + if mipmap: + self.log.info(" mipmap found for '{}'".format(fname)) + extra_files.append(mipmap) + else: + self.log.warning(" no mipmap found for '{}'".format(fname)) + return files + extra_files diff --git a/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py b/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py new file mode 100644 index 0000000000..8f31cd4753 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py @@ -0,0 +1,93 @@ +import pyblish.api +import openpype.api +import openpype.hosts.maya.api.action + +import os +import pprint + +COLOUR_SPACES = ['sRGB'] +MIPMAP_EXTENSIONS = ['tdl'] + + +class ValidateMvLookContents(pyblish.api.InstancePlugin): + order = openpype.api.ValidateContentsOrder + families = ['mvLook'] + hosts = ['maya'] + label = 'Validate mvLook Data' + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] + + # Allow this validation step to be skipped when you just need to + # get things pushed through. + optional = True + + # These intents get enforced checks, other ones get warnings. + enforced_intents = ['-', 'Final'] + + def process(self, instance): + intent = instance.context.data['intent']['value'] + expectMipMap = instance.data["expectMipMap"] + enforced = True + if intent in self.enforced_intents: + self.log.info("This validation will be enforced: '{}'" + .format(intent)) + else: + enforced = False + self.log.info("This validation will NOT be enforced: '{}'" + .format(intent)) + + if not instance[:]: + raise RuntimeError("Instance is empty") + + invalid = set() + + resources = instance.data.get("resources", []) + for resource in resources: + files = resource["files"] + self.log.debug("Resouce '{}', files: [{}]".format(resource, files)) + node = resource["node"] + if len(files) == 0: + self.log.error("File node '{}' uses no or non-existing " + "files".format(node)) + invalid.add(node) + continue + for fname in files: + if not self.valid_file(fname): + self.log.error("File node '{}'/'{}' is not valid" + .format(node, fname)) + invalid.add(node) + + if expectMipMap and not self.is_or_has_mipmap(fname, files): + msg = "File node '{}'/'{}' does not have a mipmap".format( + node, fname) + if enforced: + invalid.add(node) + self.log.error(msg) + raise RuntimeError(msg) + else: + self.log.warning(msg) + + if invalid: + raise RuntimeError("'{}' has invalid look " + "content".format(instance.name)) + + def valid_file(self, fname): + self.log.debug("Checking validity of '{}'".format(fname)) + if not os.path.exists(fname): + return False + if os.path.getsize(fname) == 0: + return False + return True + + def is_or_has_mipmap(self, fname, files): + ext = os.path.splitext(fname)[1][1:] + if ext in MIPMAP_EXTENSIONS: + self.log.debug("Is a mipmap '{}'".format(fname)) + return True + + for colour_space in COLOUR_SPACES: + for mipmap_ext in MIPMAP_EXTENSIONS: + mipmap_fname = '.'.join([fname, colour_space, mipmap_ext]) + if mipmap_fname in files: + self.log.debug("Has a mipmap '{}'".format(fname)) + return True + return False From c9e4b14a1e470d1f8059b448474657414537d5e9 Mon Sep 17 00:00:00 2001 From: DMO Date: Fri, 27 May 2022 17:53:54 +0900 Subject: [PATCH 0392/1227] Removed unused pprint import. --- openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py b/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py index 8f31cd4753..34a8bc0c85 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py @@ -3,7 +3,6 @@ import openpype.api import openpype.hosts.maya.api.action import os -import pprint COLOUR_SPACES = ['sRGB'] MIPMAP_EXTENSIONS = ['tdl'] From 67fdfba49f35b585ad5e391271fb6ebe5e43f2d3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 May 2022 11:01:40 +0200 Subject: [PATCH 0393/1227] OP-2790 - added note about remote publish to documentation --- website/docs/artist_hosts_maya.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/docs/artist_hosts_maya.md b/website/docs/artist_hosts_maya.md index 73e89384e8..48e1093753 100644 --- a/website/docs/artist_hosts_maya.md +++ b/website/docs/artist_hosts_maya.md @@ -312,6 +312,10 @@ Example setup: ![Maya - Point Cache Example](assets/maya-pointcache_setup.png) +:::note Publish on farm +If your studio has Deadline configured, artists could choose to offload potentially long running export of pointache and publish it to the farm. +Only thing that is necessary is to toggle `Farm` property in created pointcache instance to True. + ### Loading Point Caches Loading point cache means creating reference to **abc** file with Go **OpenPype → Load...**. From e1deb29da8615c050358da6254c6e9e10d9f0c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 27 May 2022 11:06:08 +0200 Subject: [PATCH 0394/1227] :bug: fix multiple attributes per node --- .../maya/plugins/publish/collect_look.py | 115 +++++++++--------- 1 file changed, 59 insertions(+), 56 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 7d258b01fa..93c02ce0fb 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -28,7 +28,7 @@ SHAPE_ATTRS = set(SHAPE_ATTRS) def get_pxr_multitexture_file_attrs(node): attrs = [] for i in range(9): - if cmds.attributeQuery("filename{}".format(i), node): + if cmds.attributeQuery("filename{}".format(i), node=node, ex=True): file = cmds.getAttr("{}.filename{}".format(node, i)) if file: attrs.append("filename{}".format(i)) @@ -50,10 +50,10 @@ FILE_NODES = { } -def get_attributes(dictionary, attr): - # type: (dict, str) -> list +def get_attributes(dictionary, attr, node=None): + # type: (dict, str, str) -> list if callable(dictionary[attr]): - val = dictionary[attr]() + val = dictionary[attr](node) else: val = dictionary.get(attr, []) @@ -205,7 +205,8 @@ def get_file_node_paths(node): return [texture_pattern] try: - file_attributes = get_attributes(FILE_NODES, cmds.nodeType(node)) + file_attributes = get_attributes( + FILE_NODES, cmds.nodeType(node), node) except AttributeError: file_attributes = "fileTextureName" @@ -434,7 +435,8 @@ class CollectLook(pyblish.api.InstancePlugin): # Collect textures if any file nodes are found instance.data["resources"] = [] for n in files: - instance.data["resources"].append(self.collect_resource(n)) + for res in self.collect_resources(n): + instance.data["resources"].append(res) self.log.info("Collected resources: {}".format(instance.data["resources"])) @@ -554,7 +556,7 @@ class CollectLook(pyblish.api.InstancePlugin): return attributes - def collect_resource(self, node): + def collect_resources(self, node): """Collect the link to the file(s) used (resource) Args: node (str): name of the node @@ -571,60 +573,61 @@ class CollectLook(pyblish.api.InstancePlugin): self.log.debug(" - got {}".format(cmds.nodeType(node))) - attribute = get_attributes(FILE_NODES, cmds.nodeType(node)) - source = cmds.getAttr("{}.{}".format( - node, - attribute - )) - computed_attribute = "{}.{}".format(node, attribute) - if attribute == "fileTextureName": - computed_attribute = node + ".computedFileTextureNamePattern" + attributes = get_attributes(FILE_NODES, cmds.nodeType(node), node) + for attribute in attributes: + source = cmds.getAttr("{}.{}".format( + node, + attribute + )) + computed_attribute = "{}.{}".format(node, attribute) + if attribute == "fileTextureName": + computed_attribute = node + ".computedFileTextureNamePattern" - self.log.info(" - file source: {}".format(source)) - color_space_attr = "{}.colorSpace".format(node) - try: - color_space = cmds.getAttr(color_space_attr) - except ValueError: - # node doesn't have colorspace attribute - color_space = "Raw" - # Compare with the computed file path, e.g. the one with the - # pattern in it, to generate some logging information about this - # difference - # computed_attribute = "{}.computedFileTextureNamePattern".format(node) - computed_source = cmds.getAttr(computed_attribute) - if source != computed_source: - self.log.debug("Detected computed file pattern difference " - "from original pattern: {0} " - "({1} -> {2})".format(node, - source, - computed_source)) + self.log.info(" - file source: {}".format(source)) + color_space_attr = "{}.colorSpace".format(node) + try: + color_space = cmds.getAttr(color_space_attr) + except ValueError: + # node doesn't have colorspace attribute + color_space = "Raw" + # Compare with the computed file path, e.g. the one with the + # pattern in it, to generate some logging information about this + # difference + # computed_attribute = "{}.computedFileTextureNamePattern".format(node) + computed_source = cmds.getAttr(computed_attribute) + if source != computed_source: + self.log.debug("Detected computed file pattern difference " + "from original pattern: {0} " + "({1} -> {2})".format(node, + source, + computed_source)) - # We replace backslashes with forward slashes because V-Ray - # can't handle the UDIM files with the backslashes in the - # paths as the computed patterns - source = source.replace("\\", "/") + # We replace backslashes with forward slashes because V-Ray + # can't handle the UDIM files with the backslashes in the + # paths as the computed patterns + source = source.replace("\\", "/") - files = get_file_node_files(node) - if len(files) == 0: - self.log.error("No valid files found from node `%s`" % node) + files = get_file_node_files(node) + if len(files) == 0: + self.log.error("No valid files found from node `%s`" % node) - self.log.info("collection of resource done:") - self.log.info(" - node: {}".format(node)) - self.log.info(" - attribute: {}".format(attribute)) - self.log.info(" - source: {}".format(source)) - self.log.info(" - file: {}".format(files)) - self.log.info(" - color space: {}".format(color_space)) + self.log.info("collection of resource done:") + self.log.info(" - node: {}".format(node)) + self.log.info(" - attribute: {}".format(attribute)) + self.log.info(" - source: {}".format(source)) + self.log.info(" - file: {}".format(files)) + self.log.info(" - color space: {}".format(color_space)) - # Define the resource - return { - "node": node, - # here we are passing not only attribute, but with node again - # this should be simplified and changed extractor. - "attribute": "{}.{}".format(node, attribute), - "source": source, # required for resources - "files": files, - "color_space": color_space - } # required for resources + # Define the resource + yield { + "node": node, + # here we are passing not only attribute, but with node again + # this should be simplified and changed extractor. + "attribute": "{}.{}".format(node, attribute), + "source": source, # required for resources + "files": files, + "color_space": color_space + } # required for resources class CollectModelRenderSets(CollectLook): From 9c73a4181ed5aa5dcd5a902e8876cbaab55350e2 Mon Sep 17 00:00:00 2001 From: DMO Date: Fri, 27 May 2022 18:06:51 +0900 Subject: [PATCH 0395/1227] Fixing a typo! --- .../hosts/maya/plugins/publish/collect_multiverse_look.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py index 6bf4cecc3a..e77245bca5 100644 --- a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py @@ -201,7 +201,7 @@ def get_mipmap(fname): return None -def is_mipamp(fname): +def is_mipmap(fname): ext = os.path.splitext(fname)[1][1:] if ext in MIPMAP_EXTENSIONS: return True @@ -359,7 +359,7 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): self.log.debug("Expecting MipMaps, going to look for them.") for fname in files: self.log.info("Checking '{}' for mipmaps".format(fname)) - if is_mipamp(fname): + if is_mipmap(fname): self.log.debug(" - file is already MipMap, skipping.") continue From 20b7b5dca4c44cff55538bc7b9a13705edab7206 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 27 May 2022 12:39:39 +0300 Subject: [PATCH 0396/1227] Style fixes, remove unused imports --- openpype/plugins/publish/extract_jpeg_exr.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index bb3b80b7ef..771ffc0aed 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -1,8 +1,7 @@ import os -from urllib.parse import MAX_CACHE_SIZE import pyblish.api -from openpype.pipeline import ( +from openpype.pipeline import ( legacy_io, KnownPublishError ) @@ -14,14 +13,8 @@ from openpype.lib import ( filter_profiles, run_subprocess, path_to_subprocess_arg, - - get_transcode_temp_directory, - convert_input_paths_for_ffmpeg, - should_convert_for_ffmpeg ) -import shutil - class ExtractThumbnail(pyblish.api.InstancePlugin): """Create jpg thumbnail from sequence using ffmpeg""" @@ -91,8 +84,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): full_input_path = os.path.join(stagingdir, input_file) self.log.info("input {}".format(full_input_path)) - - # Presumably the following is not needed since we are being # explicit # TODO: Test cases, cleanup. @@ -149,7 +140,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.extend(ffmpeg_args.get("output") or []) # we just want one frame from movie files jpeg_items.append("-vframes 1") - # output file jpeg_items.append(path_to_subprocess_arg(full_output_path)) subprocess_command = " ".join(jpeg_items) @@ -207,8 +197,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): output = run_subprocess(args) if failed_output in output: raise ValueError( - "oiiotool processing failed. Args: {}".format(args)) - + "oiiotool processing failed. Args: {}".format(args)) # noqa new_repre = { "name": "thumbnail", "ext": "jpg", From c4115f61a7571ca157c41bbd6d4984073527b98d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 27 May 2022 12:02:39 +0200 Subject: [PATCH 0397/1227] Nuke: removing kwargs with more explicit attribute inputs --- .../plugins/publish/extract_slate_frame.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index 2d20c1747c..c9362329a6 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -58,13 +58,20 @@ class ExtractSlateFrame(openpype.api.Extractor): for o_name, o_data in instance.data["bakePresets"].items(): self.log.info("_ o_name: {}, o_data: {}".format( o_name, pformat(o_data))) - self.render_slate(instance, o_name, **o_data) + self.render_slate( + instance, + bake_viewer_process=o_data[ + "bake_viewer_process"], + bake_viewer_input_process=o_data[ + "bake_viewer_input_process"], + output_name=o_name + ) else: - viewer_process_swithes = { - "bake_viewer_process": True, - "bake_viewer_input_process": True - } - self.render_slate(instance, None, **viewer_process_swithes) + self.render_slate( + instance, + bake_viewer_process=True, + bake_viewer_input_process=True + ) def _create_staging_dir(self, instance): staging_dir = os.path.normpath( @@ -92,10 +99,6 @@ class ExtractSlateFrame(openpype.api.Extractor): if _output_name: _output_name = "_" + _output_name - bake_viewer_process = kwargs["bake_viewer_process"] - bake_viewer_input_process_node = kwargs[ - "bake_viewer_input_process"] - slate_first_frame = self.first_frame - 1 collection = instance.data.get("collection", None) @@ -128,7 +131,7 @@ class ExtractSlateFrame(openpype.api.Extractor): # only create colorspace baking if toggled on if bake_viewer_process: - if bake_viewer_input_process_node: + if bake_viewer_input_process: # get input process and connect it to baking ipn = get_view_process_node() if ipn is not None: From 709cf99ce0ebebafd936bd67f3c716e60c0081fa Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 27 May 2022 12:08:52 +0200 Subject: [PATCH 0398/1227] Nuke: adding docstring to slate renderer function --- .../plugins/publish/extract_slate_frame.py | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index c9362329a6..01759e8b60 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -60,18 +60,12 @@ class ExtractSlateFrame(openpype.api.Extractor): o_name, pformat(o_data))) self.render_slate( instance, - bake_viewer_process=o_data[ - "bake_viewer_process"], - bake_viewer_input_process=o_data[ - "bake_viewer_input_process"], - output_name=o_name + o_name, + o_data["bake_viewer_process"], + o_data["bake_viewer_input_process"] ) else: - self.render_slate( - instance, - bake_viewer_process=True, - bake_viewer_input_process=True - ) + self.render_slate(instance) def _create_staging_dir(self, instance): staging_dir = os.path.normpath( @@ -85,10 +79,21 @@ class ExtractSlateFrame(openpype.api.Extractor): def render_slate( self, instance, - bake_viewer_process, - bake_viewer_input_process, - output_name=None + output_name=None, + bake_viewer_process=True, + bake_viewer_input_process=True ): + """Slate frame renderer + + Args: + instance (PyblishInstance): Pyblish instance with subset data + output_name (str, optional): + Slate variation name. Defaults to None. + bake_viewer_process (bool, optional): + Switch for viewer profile baking. Defaults to True. + bake_viewer_input_process (bool, optional): + Switch for input process node baking. Defaults to True. + """ slate_node = instance.data["slateNode"] # fill slate node with comments From d2bc5d46d530ece81e77ef8187d3bcbcc026eb15 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 27 May 2022 12:36:02 +0200 Subject: [PATCH 0399/1227] Nuke: releasing plugin attributes and distributing them directly --- .../plugins/publish/extract_slate_frame.py | 75 ++++++++++--------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index 01759e8b60..87cb333ae1 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -27,22 +27,13 @@ class ExtractSlateFrame(openpype.api.Extractor): hosts = ["nuke"] # Settings values - # - can be extended by other attributes from node in the future key_value_mapping = { "f_submission_note": [True, "{comment}"], "f_submitting_for": [True, "{intent[value]}"], "f_vfx_scope_of_work": [False, ""] } - # constants - SLATE_TO_SEQUENCE_DONE = None - def process(self, instance): - self.fpath = instance.data["path"] - self.first_frame = instance.data["frameStartHandle"] - self.last_frame = instance.data["frameEndHandle"] - - self.log.info("Creating staging dir...") if "representations" not in instance.data: instance.data["representations"] = [] @@ -65,11 +56,18 @@ class ExtractSlateFrame(openpype.api.Extractor): o_data["bake_viewer_input_process"] ) else: + # backward compatibility self.render_slate(instance) + # also render image to sequence + self._render_slate_to_sequence(instance) + def _create_staging_dir(self, instance): + + self.log.info("Creating staging dir...") + staging_dir = os.path.normpath( - os.path.dirname(self.fpath)) + os.path.dirname(instance.data["path"])) instance.data["stagingDir"] = staging_dir @@ -96,6 +94,13 @@ class ExtractSlateFrame(openpype.api.Extractor): """ slate_node = instance.data["slateNode"] + # rendering path from group write node + fpath = instance.data["path"] + + # instance frame range with handles + first_frame = instance.data["frameStartHandle"] + last_frame = instance.data["frameEndHandle"] + # fill slate node with comments self.add_comment_slate_node(instance, slate_node) @@ -104,7 +109,7 @@ class ExtractSlateFrame(openpype.api.Extractor): if _output_name: _output_name = "_" + _output_name - slate_first_frame = self.first_frame - 1 + slate_first_frame = first_frame - 1 collection = instance.data.get("collection", None) @@ -114,22 +119,22 @@ class ExtractSlateFrame(openpype.api.Extractor): "{head}{padding}{tail}")) fhead = collection.format("{head}") else: - fname = os.path.basename(self.fpath) + fname = os.path.basename(fpath) fhead = os.path.splitext(fname)[0] + "." if "#" in fhead: fhead = fhead.replace("#", "")[:-1] - self.log.debug("__ self.first_frame: {}".format(self.first_frame)) + self.log.debug("__ first_frame: {}".format(first_frame)) self.log.debug("__ slate_first_frame: {}".format(slate_first_frame)) # Read node r_node = nuke.createNode("Read") - r_node["file"].setValue(self.fpath) - r_node["first"].setValue(self.first_frame) - r_node["origfirst"].setValue(self.first_frame) - r_node["last"].setValue(self.last_frame) - r_node["origlast"].setValue(self.last_frame) + r_node["file"].setValue(fpath) + r_node["first"].setValue(first_frame) + r_node["origfirst"].setValue(first_frame) + r_node["last"].setValue(last_frame) + r_node["origlast"].setValue(last_frame) r_node["colorspace"].setValue(instance.data["colorspace"]) previous_node = r_node temporary_nodes = [previous_node] @@ -186,25 +191,21 @@ class ExtractSlateFrame(openpype.api.Extractor): nuke.execute( write_node.name(), int(slate_first_frame), int(slate_first_frame)) - # also render image to sequence - self._render_slate_to_sequence(instance, slate_first_frame) + # Clean up + for node in temporary_nodes: + nuke.delete(node) - # # Clean up - # for node in temporary_nodes: - # nuke.delete(node) + def _render_slate_to_sequence(self, instance): + # set slate frame + first_frame = instance.data["frameStartHandle"] + slate_first_frame = first_frame - 1 - def _render_slate_to_sequence(self, instance, slate_first_frame): - if not self.SLATE_TO_SEQUENCE_DONE: - node_subset_name = instance.data["name"] - # also render slate as sequence frame - nuke.execute( - node_subset_name, - int(slate_first_frame), - int(slate_first_frame) - ) - - # mark as done - self.SLATE_TO_SEQUENCE_DONE = True + # render slate as sequence frame + nuke.execute( + instance.data["name"], + int(slate_first_frame), + int(slate_first_frame) + ) def add_comment_slate_node(self, instance, node): @@ -225,8 +226,8 @@ class ExtractSlateFrame(openpype.api.Extractor): "intent": intent }) - for key, value in self.key_value_mapping.items(): - enabled, template = value + for key, _values in self.key_value_mapping.items(): + enabled, template = _values if not enabled: self.log.debug("Key \"{}\" is disabled".format(key)) continue From a24896962062e4db576ccb33ce2dbafec77ac789 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 27 May 2022 16:14:17 +0200 Subject: [PATCH 0400/1227] Nuke: bake reformat was failing on string type --- openpype/hosts/nuke/api/plugin.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 2bad6f2c78..b8b56ef2b8 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -18,7 +18,8 @@ from .lib import ( maintained_selection, set_avalon_knob_data, add_publish_knob, - get_nuke_imageio_settings + get_nuke_imageio_settings, + set_node_knobs_from_settings ) @@ -497,16 +498,7 @@ class ExporterReviewMov(ExporterReview): add_tags.append("reformated") rf_node = nuke.createNode("Reformat") - for kn_conf in reformat_node_config: - _type = kn_conf["type"] - k_name = str(kn_conf["name"]) - k_value = kn_conf["value"] - - # to remove unicode as nuke doesn't like it - if _type == "string": - k_value = str(kn_conf["value"]) - - rf_node[k_name].setValue(k_value) + set_node_knobs_from_settings(rf_node, reformat_node_config) # connect rf_node.setInput(0, self.previous_node) From c3c9cca5c2d1c3b29d5b4beaddbe2c855a32b3b8 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 27 May 2022 17:00:04 +0200 Subject: [PATCH 0401/1227] :recycle: :dog: fix hound --- openpype/hosts/maya/plugins/publish/collect_look.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 93c02ce0fb..323bede761 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -590,10 +590,9 @@ class CollectLook(pyblish.api.InstancePlugin): except ValueError: # node doesn't have colorspace attribute color_space = "Raw" - # Compare with the computed file path, e.g. the one with the - # pattern in it, to generate some logging information about this - # difference - # computed_attribute = "{}.computedFileTextureNamePattern".format(node) + # Compare with the computed file path, e.g. the one with + # the pattern in it, to generate some logging information + # about this difference computed_source = cmds.getAttr(computed_attribute) if source != computed_source: self.log.debug("Detected computed file pattern difference " From f27176bed1ac255128fdbe3d4f9c1d6100942508 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 28 May 2022 03:44:32 +0000 Subject: [PATCH 0402/1227] [Automated] Bump version --- CHANGELOG.md | 23 +++++++++++++++++------ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15659f8aa4..4a5e1f1067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,24 @@ # Changelog +## [3.10.1-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...HEAD) + +**🚀 Enhancements** + +- TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250) +- Support for Unreal 5 [\#3122](https://github.com/pypeclub/OpenPype/pull/3122) + +**🐛 Bug fixes** + +- Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) +- Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240) +- Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) +- Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) + ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.8...3.10.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.10.0-nightly.6...3.10.0) **🆕 New features** @@ -31,7 +47,6 @@ - General: Simplified OP modules/addons import [\#3137](https://github.com/pypeclub/OpenPype/pull/3137) - Terminal: Tweak coloring of TrayModuleManager logging enabled states [\#3133](https://github.com/pypeclub/OpenPype/pull/3133) - General: Cleanup some Loader docstrings [\#3131](https://github.com/pypeclub/OpenPype/pull/3131) -- Unreal: Layout and Camera update and remove functions reimplemented and improvements [\#3116](https://github.com/pypeclub/OpenPype/pull/3116) **🐛 Bug fixes** @@ -131,10 +146,6 @@ - TVPaint: Composite layers in reversed order [\#3134](https://github.com/pypeclub/OpenPype/pull/3134) -**Merged pull requests:** - -- Ftrack: AssetVersion status on publish [\#3114](https://github.com/pypeclub/OpenPype/pull/3114) - ## [3.9.5](https://github.com/pypeclub/OpenPype/tree/3.9.5) (2022-04-25) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.10.0-nightly.2...3.9.5) diff --git a/openpype/version.py b/openpype/version.py index 31be1f2f02..12f25cdcea 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.10.0" +__version__ = "3.10.1-nightly.1" diff --git a/pyproject.toml b/pyproject.toml index 444af49273..47d678b5e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.10.0" # OpenPype +version = "3.10.1-nightly.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From d404fbf8a285e0e25f5af3d8695c6df547f0ceab Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 30 May 2022 11:51:19 +0200 Subject: [PATCH 0403/1227] OP-3277 - added functionality to replace root value with environment variable. Useful for remote workflows where Site Sync is being used. When Load reference is used, real root value (c:/project) is replaced with ${OPENPYPE_ROOT_WORK}. --- openpype/hosts/maya/plugins/load/load_reference.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index d65b5a2c1e..7fa7362ecc 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -8,6 +8,7 @@ from openpype.pipeline import ( legacy_create, ) import openpype.hosts.maya.api.plugin +from openpype.api import Anatomy from openpype.hosts.maya.api.lib import maintained_selection @@ -51,7 +52,9 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): with maintained_selection(): cmds.loadPlugin("AbcImport.mll", quiet=True) - nodes = cmds.file(self.fname, + anatomy = Anatomy(context["project"]["code"]) + file_url = anatomy.replace_root_with_env_key(self.fname, '${{{}}}') + nodes = cmds.file(file_url, namespace=namespace, sharedReferenceFile=False, reference=True, From 46ab3b8e3aa8aa258dd49e880e723ee48513ac0b Mon Sep 17 00:00:00 2001 From: DMO Date: Mon, 30 May 2022 20:20:40 +0900 Subject: [PATCH 0404/1227] Addressed a concern raised for how file_formats are extracted from instance. Renamed `expectMipMap` to `publishMipMap` to make it more clear. Added `linear` & `auto` to possible color spaces. --- .../maya/plugins/create/create_multiverse_look.py | 2 +- .../plugins/publish/collect_multiverse_look.py | 14 +++++++------- .../maya/plugins/publish/extract_multiverse_usd.py | 9 +++------ .../plugins/publish/extract_multiverse_usd_comp.py | 9 +++------ .../plugins/publish/extract_multiverse_usd_over.py | 9 +++------ .../plugins/publish/validate_mvlook_contents.py | 6 +++--- 6 files changed, 20 insertions(+), 29 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_look.py b/openpype/hosts/maya/plugins/create/create_multiverse_look.py index 44977efca0..3fc834a700 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_look.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_look.py @@ -12,4 +12,4 @@ class CreateMultiverseLook(plugin.Creator): def __init__(self, *args, **kwargs): super(CreateMultiverseLook, self).__init__(*args, **kwargs) self.data["fileFormat"] = ["usda", "usd"] - self.data["expectMipMap"] = True + self.data["publishMipMap"] = True diff --git a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py index e77245bca5..edf40a27a6 100644 --- a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py @@ -17,7 +17,7 @@ SHAPE_ATTRS = ["castsShadows", "opposite"] SHAPE_ATTRS = set(SHAPE_ATTRS) -COLOUR_SPACES = ['sRGB'] +COLOUR_SPACES = ['sRGB', 'linear', 'auto'] MIPMAP_EXTENSIONS = ['tdl'] @@ -238,7 +238,7 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): files = [] sets = {} instance.data["resources"] = [] - expectMipMap = instance.data["expectMipMap"] + publishMipMap = instance.data["publishMipMap"] for node in nodes: self.log.info("Getting resources for '{}'".format(node)) @@ -262,7 +262,7 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): files = cmds.ls(history, type="file", long=True) for f in files: - resources = self.collect_resource(f, expectMipMap) + resources = self.collect_resource(f, publishMipMap) instance.data["resources"].append(resources) elif isinstance(matOver, multiverse.MaterialSourceUsdPath): @@ -275,7 +275,7 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): "relationships": sets } - def collect_resource(self, node, expectMipMap): + def collect_resource(self, node, publishMipMap): """Collect the link to the file(s) used (resource) Args: node (str): name of the node @@ -330,7 +330,7 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): source = source.replace("\\", "/") files = get_file_node_files(node) - files = self.handle_files(files, expectMipMap) + files = self.handle_files(files, publishMipMap) if len(files) == 0: self.log.error("No valid files found from node `%s`" % node) @@ -348,11 +348,11 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): "files": files, "color_space": color_space} # required for resources - def handle_files(self, files, expectMipMap): + def handle_files(self, files, publishMipMap): """This will go through all the files and make sure that they are either already mipmapped or have a corresponding mipmap sidecar and add that to the list.""" - if not expectMipMap: + if not publishMipMap: return files extra_files = [] diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py index 4bc0322fa8..fe071b08a5 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py @@ -133,18 +133,15 @@ class ExtractMultiverseUsd(openpype.api.Extractor): return options - def get_file_format(self, instance): - fileFormat = instance.data["fileFormat"] - if fileFormat in range(len(self.file_formats)): - self.scene_type = self.file_formats[fileFormat] - def process(self, instance): # Load plugin first cmds.loadPlugin("MultiverseForMaya", quiet=True) # Define output file path staging_dir = self.staging_dir(instance) - self.get_file_format(instance) + file_format = instance.data.get("fileFormat", 0) + if file_format in range(len(self.file_formats)): + self.scene_type = self.file_formats[file_format] file_name = "{0}.{1}".format(instance.name, self.scene_type) file_path = os.path.join(staging_dir, file_name) file_path = file_path.replace('\\', '/') diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py index f76a24f7ff..7be6b101d9 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py @@ -75,18 +75,15 @@ class ExtractMultiverseUsdComposition(openpype.api.Extractor): return options - def get_file_format(self, instance): - fileFormat = instance.data["fileFormat"] - if fileFormat in range(len(self.file_formats)): - self.scene_type = self.file_formats[fileFormat] - def process(self, instance): # Load plugin first cmds.loadPlugin("MultiverseForMaya", quiet=True) # Define output file path staging_dir = self.staging_dir(instance) - self.get_file_format(instance) + file_format = instance.data.get("fileFormat", 0) + if file_format in range(len(self.file_formats)): + self.scene_type = self.file_formats[file_format] file_name = "{0}.{1}".format(instance.name, self.scene_type) file_path = os.path.join(staging_dir, file_name) file_path = file_path.replace('\\', '/') diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py index 3b101ab6cf..091750c32b 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py @@ -60,18 +60,15 @@ class ExtractMultiverseUsdOverride(openpype.api.Extractor): "timeSamplesSpan": 0.0 } - def get_file_format(self, instance): - fileFormat = instance.data["fileFormat"] - if fileFormat in range(len(self.file_formats)): - self.scene_type = self.file_formats[fileFormat] - def process(self, instance): # Load plugin first cmds.loadPlugin("MultiverseForMaya", quiet=True) # Define output file path staging_dir = self.staging_dir(instance) - self.get_file_format(instance) + file_format = instance.data.get("fileFormat", 0) + if file_format in range(len(self.file_formats)): + self.scene_type = self.file_formats[file_format] file_name = "{0}.{1}".format(instance.name, self.scene_type) file_path = os.path.join(staging_dir, file_name) file_path = file_path.replace("\\", "/") diff --git a/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py b/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py index 34a8bc0c85..bac2c030c8 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py @@ -4,7 +4,7 @@ import openpype.hosts.maya.api.action import os -COLOUR_SPACES = ['sRGB'] +COLOUR_SPACES = ['sRGB', 'linear', 'auto'] MIPMAP_EXTENSIONS = ['tdl'] @@ -24,7 +24,7 @@ class ValidateMvLookContents(pyblish.api.InstancePlugin): def process(self, instance): intent = instance.context.data['intent']['value'] - expectMipMap = instance.data["expectMipMap"] + publishMipMap = instance.data["publishMipMap"] enforced = True if intent in self.enforced_intents: self.log.info("This validation will be enforced: '{}'" @@ -55,7 +55,7 @@ class ValidateMvLookContents(pyblish.api.InstancePlugin): .format(node, fname)) invalid.add(node) - if expectMipMap and not self.is_or_has_mipmap(fname, files): + if publishMipMap and not self.is_or_has_mipmap(fname, files): msg = "File node '{}'/'{}' does not have a mipmap".format( node, fname) if enforced: From 277024de81843a3198c47402e7b251f937ab0817 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 30 May 2022 13:23:24 +0200 Subject: [PATCH 0405/1227] OP-3277 - extracted logic to ReferenceLoader All inheriting plugins implementing shared method. Flag 'use_env_var_as_root' set to True for testing temporarily, proper location in Setting should be decided. --- openpype/hosts/maya/api/plugin.py | 25 +++++++++++++++++++ .../maya/plugins/load/_load_animation.py | 5 ++-- openpype/hosts/maya/plugins/load/load_ass.py | 12 ++++++--- openpype/hosts/maya/plugins/load/load_look.py | 4 ++- .../hosts/maya/plugins/load/load_reference.py | 5 ++-- .../hosts/maya/plugins/load/load_yeti_rig.py | 4 ++- 6 files changed, 45 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index 3721868823..93b0793d9c 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -10,6 +10,7 @@ from openpype.pipeline import ( get_representation_path, AVALON_CONTAINER_ID, ) +from openpype.api import Anatomy from .pipeline import containerise from . import lib @@ -132,6 +133,7 @@ class ReferenceLoader(Loader): " imported representation ?" ) ] + use_env_var_as_root = True def load( self, @@ -191,6 +193,25 @@ class ReferenceLoader(Loader): """To be implemented by subclass""" raise NotImplementedError("Must be implemented by subclass") + def prepare_root_value(self, file_url, project_name): + """Replace root value with env var placeholder. + + Use ${OPENPYPE_ROOT_WORK} (or any other root) instead of proper root + value when storing referenced url into a workfile. + Useful for remote workflows with SiteSync. + + Args: + file_url (str) + project_name (dict) + Returns: + (str) + """ + if self.use_env_var_as_root: + anatomy = Anatomy(project_name) + file_url = anatomy.replace_root_with_env_key(file_url, '${{{}}}') + + return file_url + def update(self, container, representation): from maya import cmds from openpype.hosts.maya.api.lib import get_container_members @@ -230,6 +251,10 @@ class ReferenceLoader(Loader): self.log.debug("No alembic nodes found in {}".format(members)) try: + path = self.prepare_root_value(path, + representation["context"] + ["project"] + ["code"]) content = cmds.file(path, loadReference=reference_node, type=file_type, diff --git a/openpype/hosts/maya/plugins/load/_load_animation.py b/openpype/hosts/maya/plugins/load/_load_animation.py index 9c37e498ef..0010efb829 100644 --- a/openpype/hosts/maya/plugins/load/_load_animation.py +++ b/openpype/hosts/maya/plugins/load/_load_animation.py @@ -35,8 +35,9 @@ class AbcLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): # hero_001 (abc) # asset_counter{optional} - - nodes = cmds.file(self.fname, + file_url = self.prepare_root_value(self.fname, + context["project"]["code"]) + nodes = cmds.file(file_url, namespace=namespace, sharedReferenceFile=False, groupReference=True, diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_ass.py index a284b7ec1f..1f0eb88995 100644 --- a/openpype/hosts/maya/plugins/load/load_ass.py +++ b/openpype/hosts/maya/plugins/load/load_ass.py @@ -64,9 +64,11 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): path = os.path.join(publish_folder, filename) proxyPath = proxyPath_base + ".ma" - self.log.info - nodes = cmds.file(proxyPath, + file_url = self.prepare_root_value(proxyPath, + context["project"]["code"]) + + nodes = cmds.file(file_url, namespace=namespace, reference=True, returnNewNodes=True, @@ -123,7 +125,11 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): assert os.path.exists(proxyPath), "%s does not exist." % proxyPath try: - content = cmds.file(proxyPath, + file_url = self.prepare_root_value(proxyPath, + representation["context"] + ["project"] + ["code"]) + content = cmds.file(file_url, loadReference=reference_node, type="mayaAscii", returnNewNodes=True) diff --git a/openpype/hosts/maya/plugins/load/load_look.py b/openpype/hosts/maya/plugins/load/load_look.py index 80eac8e0b5..ae3a683241 100644 --- a/openpype/hosts/maya/plugins/load/load_look.py +++ b/openpype/hosts/maya/plugins/load/load_look.py @@ -31,7 +31,9 @@ class LookLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): import maya.cmds as cmds with lib.maintained_selection(): - nodes = cmds.file(self.fname, + file_url = self.prepare_root_value(self.fname, + context["project"]["code"]) + nodes = cmds.file(file_url, namespace=namespace, reference=True, returnNewNodes=True) diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index 7fa7362ecc..e4355ed3d4 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -8,7 +8,6 @@ from openpype.pipeline import ( legacy_create, ) import openpype.hosts.maya.api.plugin -from openpype.api import Anatomy from openpype.hosts.maya.api.lib import maintained_selection @@ -52,8 +51,8 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): with maintained_selection(): cmds.loadPlugin("AbcImport.mll", quiet=True) - anatomy = Anatomy(context["project"]["code"]) - file_url = anatomy.replace_root_with_env_key(self.fname, '${{{}}}') + file_url = self.prepare_root_value(self.fname, + context["project"]["code"]) nodes = cmds.file(file_url, namespace=namespace, sharedReferenceFile=False, diff --git a/openpype/hosts/maya/plugins/load/load_yeti_rig.py b/openpype/hosts/maya/plugins/load/load_yeti_rig.py index b4d31b473f..241c28467a 100644 --- a/openpype/hosts/maya/plugins/load/load_yeti_rig.py +++ b/openpype/hosts/maya/plugins/load/load_yeti_rig.py @@ -53,7 +53,9 @@ class YetiRigLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): # load rig with lib.maintained_selection(): - nodes = cmds.file(self.fname, + file_url = self.prepare_root_value(self.fname, + context["project"]["code"]) + nodes = cmds.file(file_url, namespace=namespace, reference=True, returnNewNodes=True, From 9afdf50bdc43ddbc014abd0ce4143d1755e79495 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Mon, 30 May 2022 15:18:51 +0300 Subject: [PATCH 0406/1227] initial conversion refactor --- openpype/plugins/publish/extract_jpeg_exr.py | 153 +++++++++++++------ 1 file changed, 106 insertions(+), 47 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 771ffc0aed..88fa627183 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -73,17 +73,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres = self._get_filtered_repres(instance) for repre in filtered_repres: - repre_files = repre["files"] - if not isinstance(repre_files, (list, tuple)): - input_file = repre_files - else: - file_index = int(float(len(repre_files)) * 0.5) - input_file = repre_files[file_index] - - stagingdir = os.path.normpath(repre["stagingDir"]) - - full_input_path = os.path.join(stagingdir, input_file) - self.log.info("input {}".format(full_input_path)) # Presumably the following is not needed since we are being # explicit # TODO: Test cases, cleanup. @@ -110,6 +99,17 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # self.log # ) # full_input_path = os.path.join(convert_dir, filename) + repre_files = repre["files"] + if not isinstance(repre_files, (list, tuple)): + input_file = repre_files + else: + file_index = int(float(len(repre_files)) * 0.5) + input_file = repre_files[file_index] + + stagingdir = os.path.normpath(repre["stagingDir"]) + + full_input_path = os.path.join(stagingdir, input_file) + self.log.info("input {}".format(full_input_path)) filename = os.path.splitext(input_file)[0] if not filename.endswith('.'): filename += "." @@ -178,42 +178,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # places where things may break oiio_tool_path = get_oiio_tools_path() full_output_path = os.path.join(stagingdir, jpeg_file) - args = [oiio_tool_path] - oiio_cmd = [oiio_tool_path, - full_input_path, "-o", - full_output_path - ] - subprocess_exr = " ".join(oiio_cmd) - self.log.info(f"running: {subprocess_exr}") - run_subprocess(subprocess_exr, logger=self.log) - - # raise error if there is no ouptput - if not os.path.exists(full_input_path): - self.log.error( - ("File {} was not converted " - "by oiio tool!").format(full_input_path)) - raise AssertionError("OIIO tool conversion failed") - failed_output = "oiiotool produced no output." - output = run_subprocess(args) - if failed_output in output: - raise ValueError( - "oiiotool processing failed. Args: {}".format(args)) # noqa - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) - - # Create only one representation with name 'thumbnail' - # TODO maybe handle way how to decide from which repre - # will be thumbnail created - break + def _get_filtered_repres(self, instance): filtered_repres = [] @@ -233,3 +198,97 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres.append(repre) return filtered_repres + + + def create_thumbnail_oiio(self, src_path, dst_path): + args = [oiio_tool_path] + oiio_cmd = [oiio_tool_path, + full_input_path, "-o", + full_output_path + ] + subprocess_exr = " ".join(oiio_cmd) + self.log.info(f"running: {subprocess_exr}") + run_subprocess(subprocess_exr, logger=self.log) + + # raise error if there is no ouptput + if not os.path.exists(full_input_path): + self.log.error( + ("File {} was not converted " + "by oiio tool!").format(full_input_path)) + raise AssertionError("OIIO tool conversion failed") + failed_output = "oiiotool produced no output." + output = run_subprocess(args) + if failed_output in output: + raise ValueError( + "oiiotool processing failed. Args: {}".format(args)) # noqa + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } + # adding representation + self.log.debug("Adding: {}".format(new_repre)) + instance.data["representations"].append(new_repre) + + # Create only one representation with name 'thumbnail' + # TODO maybe handle way how to decide from which repre + # will be thumbnail created + + def create_thumbnail_ffmpeg(self, src_path, dst_path): + self.log.info("output {}".format(full_output_path)) + + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") + ffmpeg_args = self.ffmpeg_args or {} + + jpeg_items = [] + jpeg_items.append(path_to_subprocess_arg(ffmpeg_path)) + # override file if already exists + jpeg_items.append("-y") + # flag for large file sizes + max_int = 2147483647 + jpeg_items.append("-analyzeduration {}".format(max_int)) + jpeg_items.append("-probesize {}".format(max_int)) + # use same input args like with mov + jpeg_items.extend(ffmpeg_args.get("input") or []) + # input file + jpeg_items.append("-i {}".format( + path_to_subprocess_arg(full_input_path) + )) + # output arguments from presets + jpeg_items.extend(ffmpeg_args.get("output") or []) + # we just want one frame from movie files + jpeg_items.append("-vframes 1") + # output file + jpeg_items.append(path_to_subprocess_arg(full_output_path)) + subprocess_command = " ".join(jpeg_items) + + # run subprocess + self.log.debug("{}".format(subprocess_command)) + try: # temporary until oiiotool is supported cross platform + run_subprocess( + subprocess_command, shell=True, logger=self.log + ) + except RuntimeError as exp: + if "Compression" in str(exp): + self.log.debug( + "Unsupported compression on input files. Skipping!" + ) + return + self.log.warning("Conversion crashed", exc_info=True) + raise + + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } + + # adding representation + self.log.debug("Adding: {}".format(new_repre)) + instance.data["representations"].append(new_repre) \ No newline at end of file From 38c8d2c8fe63c81fbc55c4f079e4f72519575c3c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 30 May 2022 15:27:15 +0200 Subject: [PATCH 0407/1227] Fix udim support for e.g. uppercase tag --- openpype/hosts/maya/plugins/publish/collect_look.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 323bede761..dc17ddc605 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -109,16 +109,18 @@ def node_uses_image_sequence(node, node_path): """ # useFrameExtension indicates an explicit image sequence - # The following tokens imply a sequence - patterns = ["", "", "", - "u_v", ""] try: use_frame_extension = cmds.getAttr('%s.useFrameExtension' % node) except ValueError: use_frame_extension = False + if use_frame_extension: + return True - return (use_frame_extension or - any(pattern in node_path for pattern in patterns)) + # The following tokens imply a sequence + patterns = ["", "", "", + "u_v", ""] + node_path_lowered = node_path.lower() + return any(pattern in node_path_lowered for pattern in patterns) def seq_to_glob(path): From 254455e786c50ef20d0f953bebd57d50af077b1e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 30 May 2022 18:05:56 +0200 Subject: [PATCH 0408/1227] OP-3277 - introduced Settings variable Configured via Maya/Maya-dirmap to use in all Loaders --- openpype/hosts/maya/api/plugin.py | 45 ++++++++++--------- .../defaults/project_settings/maya.json | 1 + .../projects_schema/schema_project_maya.json | 6 +++ 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index 93b0793d9c..f05893a7b4 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -11,7 +11,7 @@ from openpype.pipeline import ( AVALON_CONTAINER_ID, ) from openpype.api import Anatomy - +from openpype.settings import get_project_settings from .pipeline import containerise from . import lib @@ -133,7 +133,6 @@ class ReferenceLoader(Loader): " imported representation ?" ) ] - use_env_var_as_root = True def load( self, @@ -193,25 +192,6 @@ class ReferenceLoader(Loader): """To be implemented by subclass""" raise NotImplementedError("Must be implemented by subclass") - def prepare_root_value(self, file_url, project_name): - """Replace root value with env var placeholder. - - Use ${OPENPYPE_ROOT_WORK} (or any other root) instead of proper root - value when storing referenced url into a workfile. - Useful for remote workflows with SiteSync. - - Args: - file_url (str) - project_name (dict) - Returns: - (str) - """ - if self.use_env_var_as_root: - anatomy = Anatomy(project_name) - file_url = anatomy.replace_root_with_env_key(file_url, '${{{}}}') - - return file_url - def update(self, container, representation): from maya import cmds from openpype.hosts.maya.api.lib import get_container_members @@ -344,6 +324,29 @@ class ReferenceLoader(Loader): except RuntimeError: pass + def prepare_root_value(self, file_url, project_name): + """Replace root value with env var placeholder. + + Use ${OPENPYPE_ROOT_WORK} (or any other root) instead of proper root + value when storing referenced url into a workfile. + Useful for remote workflows with SiteSync. + + Args: + file_url (str) + project_name (dict) + Returns: + (str) + """ + settings = get_project_settings(project_name) + use_env_var_as_root = (settings["maya"] + ["maya-dirmap"] + ["use_env_var_as_root"]) + if use_env_var_as_root: + anatomy = Anatomy(project_name) + file_url = anatomy.replace_root_with_env_key(file_url, '${{{}}}') + + return file_url + @staticmethod def _organize_containers(nodes, container): # type: (list, str) -> None diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index e03bdcecc3..a42f889e85 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -8,6 +8,7 @@ "yetiRig": "ma" }, "maya-dirmap": { + "use_env_var_as_root": true, "enabled": false, "paths": { "source-path": [], diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index 0c7943447b..f9523b1baa 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -22,6 +22,12 @@ "label": "Maya Directory Mapping", "is_group": true, "children": [ + { + "type": "boolean", + "key": "use_env_var_as_root", + "label": "Use env var placeholder in referenced url", + "docstring": "Use ${} placeholder instead of physical value of root when storing into workfile metadata." + }, { "type": "boolean", "key": "enabled", From 99b6050cbec6c35d088e106a7ef5aebb182fbb6a Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 30 May 2022 18:47:09 +0200 Subject: [PATCH 0409/1227] Replace by last nuke familly icon --- openpype/resources/app_icons/hiero.png | Bin 46366 -> 87054 bytes openpype/resources/app_icons/nuke.png | Bin 49012 -> 86832 bytes openpype/resources/app_icons/nukestudio.png | Bin 46080 -> 101945 bytes openpype/resources/app_icons/nukex.png | Bin 44709 -> 99787 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/openpype/resources/app_icons/hiero.png b/openpype/resources/app_icons/hiero.png index 04bbf6265bb63f0615c2b98ab48891d03483f6b2..ba666c2fe0819e7c769bc2dc6d96c853b349fd80 100644 GIT binary patch literal 87054 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

BNYumfy0?yyj<=Shl->j>O&F$!;w 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

diff --git a/website/static/img/logo_normaal.png b/website/static/img/logo_normaal.png new file mode 100644 index 0000000000000000000000000000000000000000..711847c9f2f95d77d46d4ab98a9287e5f0ef771d GIT binary patch literal 13468 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 mxrUV7^Rp0{u+ Date: Fri, 22 Jul 2022 14:21:06 +0200 Subject: [PATCH 1222/1227] update Ellipse animation after re-brand --- website/static/img/ellipse_animation.svg | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 website/static/img/ellipse_animation.svg diff --git a/website/static/img/ellipse_animation.svg b/website/static/img/ellipse_animation.svg new file mode 100644 index 0000000000..c1caaa6726 --- /dev/null +++ b/website/static/img/ellipse_animation.svg @@ -0,0 +1,9 @@ + + + + + + + + + From 69246a76b4d6e494c563c828bdfb203fd0e80c44 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 22 Jul 2022 15:01:31 +0200 Subject: [PATCH 1223/1227] fixing the host condition --- openpype/plugins/publish/integrate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index cfaff4067b..1ddb694f85 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -229,7 +229,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): skip = False for item in self.skip_host_families: - if item["host"] != host_name: + if host_name not in item["host"]: continue families = set(item["families"]) From abc5c9e69b6cbdd0627d229e7e8294c159cef0e0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 22 Jul 2022 15:37:10 +0200 Subject: [PATCH 1224/1227] adding codec args for keeping continuity even wtih audio stream. --- openpype/plugins/publish/extract_review_slate.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 90dad00b97..69043ee261 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -321,6 +321,9 @@ class ExtractReviewSlate(openpype.api.Extractor): if format_args: concat_args.extend(format_args) + if codec_args: + concat_args.extend(codec_args) + # Use arguments from ffmpeg preset source_ffmpeg_cmd = repre.get("ffmpeg_cmd") if source_ffmpeg_cmd: From 4ac8da4ca047363005b1f0638c29584f847c8590 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 22 Jul 2022 18:14:26 +0200 Subject: [PATCH 1225/1227] remove hosts filter on integrator plugins --- openpype/plugins/publish/integrate.py | 4 ---- openpype/plugins/publish/integrate_legacy.py | 4 ---- 2 files changed, 8 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 1ddb694f85..8532691e61 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -104,10 +104,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): label = "Integrate Asset" order = pyblish.api.IntegratorOrder - hosts = ["aftereffects", "blender", "celaction", "flame", "harmony", - "hiero", "houdini", "nuke", "photoshop", "resolve", - "standalonepublisher", "traypublisher", "tvpaint", "unreal", - "webpublisher"] families = ["workfile", "pointcache", "camera", diff --git a/openpype/plugins/publish/integrate_legacy.py b/openpype/plugins/publish/integrate_legacy.py index 34e81a3839..b90b61f587 100644 --- a/openpype/plugins/publish/integrate_legacy.py +++ b/openpype/plugins/publish/integrate_legacy.py @@ -72,10 +72,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): label = "Integrate Asset (legacy)" # Make sure it happens after new integrator order = pyblish.api.IntegratorOrder + 0.00001 - hosts = ["aftereffects", "blender", "celaction", "flame", "harmony", - "hiero", "houdini", "nuke", "photoshop", "resolve", - "standalonepublisher", "traypublisher", "tvpaint", "unreal", - "webpublisher"] families = ["workfile", "pointcache", "camera", From 5c0f0f260365423128e410712c18c1938e83777b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Fri, 22 Jul 2022 18:22:48 +0200 Subject: [PATCH 1226/1227] :heavy_minus_sign: remove invalid submodules --- vendor/powershell/BurntToast | 1 - vendor/powershell/PSWriteColor | 1 - 2 files changed, 2 deletions(-) delete mode 160000 vendor/powershell/BurntToast delete mode 160000 vendor/powershell/PSWriteColor diff --git a/vendor/powershell/BurntToast b/vendor/powershell/BurntToast deleted file mode 160000 index ae0acdd870..0000000000 --- a/vendor/powershell/BurntToast +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ae0acdd870a2fd8d9f0d147de22dc36d6c5e399e diff --git a/vendor/powershell/PSWriteColor b/vendor/powershell/PSWriteColor deleted file mode 160000 index 12eda384eb..0000000000 --- a/vendor/powershell/PSWriteColor +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 12eda384ebd7a7954e15855e312215c009c97114 From e69d8e3ac65ee273e7d9a23c4bbd5f741baf01c9 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 23 Jul 2022 03:53:23 +0000 Subject: [PATCH 1227/1227] [Automated] Bump version --- CHANGELOG.md | 35 ++++++++++++++++++----------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8da885473..ec880b9c61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,17 @@ # Changelog -## [3.12.2-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.12.2-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.1...HEAD) +### 📖 Documentation + +- Update website with more studios [\#3554](https://github.com/pypeclub/OpenPype/pull/3554) +- Documentation: Update publishing dev docs [\#3549](https://github.com/pypeclub/OpenPype/pull/3549) + **🚀 Enhancements** +- Maya: add additional validators to Settings [\#3540](https://github.com/pypeclub/OpenPype/pull/3540) - General: Interactive console in cli [\#3526](https://github.com/pypeclub/OpenPype/pull/3526) - Ftrack: Automatic daily review session creation can define trigger hour [\#3516](https://github.com/pypeclub/OpenPype/pull/3516) - Ftrack: add source into Note [\#3509](https://github.com/pypeclub/OpenPype/pull/3509) @@ -20,8 +26,15 @@ **🐛 Bug fixes** +- Remove invalid submodules from `/vendor` [\#3557](https://github.com/pypeclub/OpenPype/pull/3557) +- General: Remove hosts filter on integrator plugins [\#3556](https://github.com/pypeclub/OpenPype/pull/3556) +- Settings: Clean default values of environments [\#3550](https://github.com/pypeclub/OpenPype/pull/3550) +- Module interfaces: Fix import error [\#3547](https://github.com/pypeclub/OpenPype/pull/3547) +- Workfiles tool: Show of tool and it's flags [\#3539](https://github.com/pypeclub/OpenPype/pull/3539) +- General: Create workfile documents works again [\#3538](https://github.com/pypeclub/OpenPype/pull/3538) - Additional fixes for powershell scripts [\#3525](https://github.com/pypeclub/OpenPype/pull/3525) - Maya: Added wrapper around cmds.setAttr [\#3523](https://github.com/pypeclub/OpenPype/pull/3523) +- Nuke: double slate [\#3521](https://github.com/pypeclub/OpenPype/pull/3521) - General: Fix hash of centos oiio archive [\#3519](https://github.com/pypeclub/OpenPype/pull/3519) - Maya: Renderman display output fix [\#3514](https://github.com/pypeclub/OpenPype/pull/3514) - TrayPublisher: Simple creation enhancements and fixes [\#3513](https://github.com/pypeclub/OpenPype/pull/3513) @@ -31,8 +44,12 @@ **🔀 Refactored code** +- Refactor Integrate Asset [\#3530](https://github.com/pypeclub/OpenPype/pull/3530) - General: Client docstrings cleanup [\#3529](https://github.com/pypeclub/OpenPype/pull/3529) +- General: Get current context document functions [\#3522](https://github.com/pypeclub/OpenPype/pull/3522) +- Kitsu: Use query function from client [\#3496](https://github.com/pypeclub/OpenPype/pull/3496) - TimersManager: Use query functions [\#3495](https://github.com/pypeclub/OpenPype/pull/3495) +- Deadline: Use query functions [\#3466](https://github.com/pypeclub/OpenPype/pull/3466) ## [3.12.1](https://github.com/pypeclub/OpenPype/tree/3.12.1) (2022-07-13) @@ -57,7 +74,6 @@ - Windows installer: Clean old files and add version subfolder [\#3445](https://github.com/pypeclub/OpenPype/pull/3445) - Blender: Bugfix - Set fps properly on open [\#3426](https://github.com/pypeclub/OpenPype/pull/3426) - Hiero: Add custom scripts menu [\#3425](https://github.com/pypeclub/OpenPype/pull/3425) -- Blender: pre pyside install for all platforms [\#3400](https://github.com/pypeclub/OpenPype/pull/3400) **🐛 Bug fixes** @@ -95,34 +111,19 @@ [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.12.0-nightly.3...3.12.0) -### 📖 Documentation - -- Fix typo in documentation: pyenv on mac [\#3417](https://github.com/pypeclub/OpenPype/pull/3417) -- Linux: update OIIO package [\#3401](https://github.com/pypeclub/OpenPype/pull/3401) - **🚀 Enhancements** - Webserver: Added CORS middleware [\#3422](https://github.com/pypeclub/OpenPype/pull/3422) -- Attribute Defs UI: Files widget show what is allowed to drop in [\#3411](https://github.com/pypeclub/OpenPype/pull/3411) **🐛 Bug fixes** - NewPublisher: Fix subset name change on change of creator plugin [\#3420](https://github.com/pypeclub/OpenPype/pull/3420) - Bug: fix invalid avalon import [\#3418](https://github.com/pypeclub/OpenPype/pull/3418) -- Nuke: Fix keyword argument in query function [\#3414](https://github.com/pypeclub/OpenPype/pull/3414) -- Houdini: fix loading and updating vbd/bgeo sequences [\#3408](https://github.com/pypeclub/OpenPype/pull/3408) -- Nuke: Collect representation files based on Write [\#3407](https://github.com/pypeclub/OpenPype/pull/3407) -- General: Filter representations before integration start [\#3398](https://github.com/pypeclub/OpenPype/pull/3398) -- Maya: look collector typo [\#3392](https://github.com/pypeclub/OpenPype/pull/3392) **🔀 Refactored code** - Unreal: Use client query functions [\#3421](https://github.com/pypeclub/OpenPype/pull/3421) - General: Move editorial lib to pipeline [\#3419](https://github.com/pypeclub/OpenPype/pull/3419) -- Kitsu: renaming to plural func sync\_all\_projects [\#3397](https://github.com/pypeclub/OpenPype/pull/3397) -- Houdini: Use client query functions [\#3395](https://github.com/pypeclub/OpenPype/pull/3395) -- Hiero: Use client query functions [\#3393](https://github.com/pypeclub/OpenPype/pull/3393) -- Nuke: Use client query functions [\#3391](https://github.com/pypeclub/OpenPype/pull/3391) ## [3.11.1](https://github.com/pypeclub/OpenPype/tree/3.11.1) (2022-06-20) diff --git a/openpype/version.py b/openpype/version.py index dd5ad97449..9dda1eacce 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.12.2-nightly.2" +__version__ = "3.12.2-nightly.3" diff --git a/pyproject.toml b/pyproject.toml index 9552242694..eebc8a5600 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.12.2-nightly.2" # OpenPype +version = "3.12.2-nightly.3" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License"