Cleaned out repo
|
@ -0,0 +1,63 @@
|
|||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
|
@ -7,6 +7,7 @@
|
|||
.metadata
|
||||
bin/
|
||||
tmp/
|
||||
env/
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
|
@ -130,8 +131,7 @@ publish/
|
|||
*.publishproj
|
||||
|
||||
# NuGet Packages Directory
|
||||
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
|
||||
#packages/
|
||||
packages/
|
||||
|
||||
# Windows Azure Build Output
|
||||
csx
|
||||
|
@ -218,3 +218,12 @@ packages/Microsoft.Azure.Batch.Apps.Cloud.1.0.1-preview/lib/net40/Microsoft.Azur
|
|||
packages/Microsoft.Azure.Batch.Apps.Cloud.1.0.1-preview/lib/net45/Microsoft.Azure.Batch.Apps.Cloud.XML
|
||||
*.nupkg
|
||||
packages/repositories.config
|
||||
src.zip
|
||||
|
||||
packages/Newtonsoft.Json.7.0.1/tools/install.ps1
|
||||
packages/Newtonsoft.Json.7.0.1/lib/portable-net45+wp80+win8+wpa81+dnxcore50/Newtonsoft.Json.xml
|
||||
packages/Newtonsoft.Json.7.0.1/lib/portable-net40+sl5+wp80+win8+wpa81/Newtonsoft.Json.xml
|
||||
packages/Newtonsoft.Json.7.0.1/lib/net45/Newtonsoft.Json.xml
|
||||
packages/Newtonsoft.Json.7.0.1/lib/net40/Newtonsoft.Json.xml
|
||||
packages/Newtonsoft.Json.7.0.1/lib/net35/Newtonsoft.Json.xml
|
||||
packages/Newtonsoft.Json.7.0.1/lib/net20/Newtonsoft.Json.xml
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>f585989b-2df8-4241-8d02-948958187914</ProjectGuid>
|
||||
<ProjectHome>.</ProjectHome>
|
||||
<StartupFile>tests\__init__.py</StartupFile>
|
||||
<WorkingDirectory>.</WorkingDirectory>
|
||||
<OutputPath>.</OutputPath>
|
||||
<Name>Maya.Client</Name>
|
||||
<RootNamespace>Maya.Client</RootNamespace>
|
||||
<InterpreterId>{288d073e-7bc9-4fc6-9095-44d701572fd4}</InterpreterId>
|
||||
<InterpreterVersion>2.7</InterpreterVersion>
|
||||
<SearchPath>azure_batch_maya\scripts\;azure_batch_maya\scripts\ui\;tests\data\;tests\data\modules\</SearchPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="azure_batch_maya\modules\arnold_renderer.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="azure_batch_maya\modules\default.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="azure_batch_maya\modules\maya_software.py" />
|
||||
<Compile Include="azure_batch_maya\scripts\api.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="azure_batch_maya\scripts\assets.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="azure_batch_maya\scripts\config.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="azure_batch_maya\scripts\environment.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="azure_batch_maya\scripts\exception.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="azure_batch_maya\scripts\history.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="azure_batch_maya\scripts\submission.py" />
|
||||
<Compile Include="azure_batch_maya\scripts\pools.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="azure_batch_maya\scripts\shared.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="azure_batch_maya\scripts\tools\install_pip.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="azure_batch_maya\scripts\tools\job_watcher.py" />
|
||||
<Compile Include="azure_batch_maya\scripts\ui\ui_assets.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="azure_batch_maya\scripts\ui\ui_config.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="azure_batch_maya\scripts\ui\ui_environment.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="azure_batch_maya\scripts\ui\ui_history.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="azure_batch_maya\scripts\ui\ui_pools.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="azure_batch_maya\scripts\ui\ui_submission.py" />
|
||||
<Compile Include="azure_batch_maya\scripts\utils.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="azure_batch_maya\plug-in\AzureBatch.py" />
|
||||
<Compile Include="azure_batch_maya\scripts\ui\ui_shared.py" />
|
||||
<Compile Include="package.py" />
|
||||
<Compile Include="tests\test_assets.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="tests\test_config.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="tests\test_environment.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="tests\test_history.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="tests\test_jobwatcher.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="tests\test_pools.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="tests\test_shared.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="tests\test_submission.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="tests\__init__.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="azure_batch_maya\icons\" />
|
||||
<Folder Include="azure_batch_maya\mel\" />
|
||||
<Folder Include="azure_batch_maya\modules\" />
|
||||
<Folder Include="azure_batch_maya\" />
|
||||
<Folder Include="azure_batch_maya\scripts\" />
|
||||
<Folder Include="azure_batch_maya\plug-in\" />
|
||||
<Folder Include="azure_batch_maya\scripts\tools\" />
|
||||
<Folder Include="azure_batch_maya\scripts\ui\" />
|
||||
<Folder Include="azure_batch_maya\templates\" />
|
||||
<Folder Include="tests\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="azure_batch_maya\icons\btn_background.png" />
|
||||
<Content Include="azure_batch_maya\icons\btn_cancel.png" />
|
||||
<Content Include="azure_batch_maya\icons\btn_download.png" />
|
||||
<Content Include="azure_batch_maya\icons\btn_portal.png" />
|
||||
<Content Include="azure_batch_maya\icons\btn_refresh.png" />
|
||||
<Content Include="azure_batch_maya\icons\plugin.png" />
|
||||
<Content Include="azure_batch_maya\icons\portal.png" />
|
||||
<Content Include="azure_batch_maya\icons\loading_preview.png" />
|
||||
<Content Include="azure_batch_maya\icons\no_preview.png" />
|
||||
<Content Include="azure_batch_maya\mel\create_shelf.mel" />
|
||||
<Content Include="azure_batch_maya\plug-in\SLA.html" />
|
||||
<Content Include="azure_batch_maya\scripts\tools\ignored_plugins.json">
|
||||
<SubType>Code</SubType>
|
||||
</Content>
|
||||
<Content Include="azure_batch_maya\scripts\tools\supported_plugins.json">
|
||||
<SubType>Code</SubType>
|
||||
</Content>
|
||||
<Content Include="azure_batch_maya\templates\arnold-basic-windows.json" />
|
||||
<Content Include="azure_batch_maya\templates\arnold-basic-linux.json" />
|
||||
<Content Include="requirements.txt">
|
||||
<SubType>Code</SubType>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Interpreter Include="env\">
|
||||
<Id>{288d073e-7bc9-4fc6-9095-44d701572fd4}</Id>
|
||||
<BaseInterpreter>{9a7a9026-48c1-4688-9d5d-e5699d47d074}</BaseInterpreter>
|
||||
<Version>2.7</Version>
|
||||
<Description>env (Python 64-bit 2.7)</Description>
|
||||
<InterpreterPath>Scripts\python.exe</InterpreterPath>
|
||||
<WindowsInterpreterPath>Scripts\pythonw.exe</WindowsInterpreterPath>
|
||||
<LibraryPath>Lib\</LibraryPath>
|
||||
<PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable>
|
||||
<Architecture>Amd64</Architecture>
|
||||
</Interpreter>
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||
<PtvsTargetsFile>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets</PtvsTargetsFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'ArnoldSDKv4.2.3.1' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
<OutputPath>bin\ArnoldSDKv4.2.3.1\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'ArnoldSDKv4.2.4.1' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
<OutputPath>bin\ArnoldSDKv4.2.4.1\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<Import Condition="Exists($(PtvsTargetsFile))" Project="$(PtvsTargetsFile)" />
|
||||
<Import Condition="!Exists($(PtvsTargetsFile))" Project="$(MSBuildToolsPath)\Microsoft.Common.targets" />
|
||||
<!-- Uncomment the CoreCompile target to enable the Build command in
|
||||
Visual Studio and specify your pre- and post-build commands in
|
||||
the BeforeBuild and AfterBuild targets below. -->
|
||||
<!--<Target Name="CoreCompile" />-->
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
</Project>
|
|
@ -1,67 +1,22 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2013
|
||||
VisualStudioVersion = 12.0.31101.0
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.25420.1
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "Maya.Client", "MayaClient\Maya.Client.pyproj", "{F585989B-2DF8-4241-8D02-948958187914}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Maya.Cloud", "MayaCloud\Maya.Cloud.csproj", "{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C2073274-2672-4F93-9B76-7C7D51377551}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
CHANGES.txt = CHANGES.txt
|
||||
LICENSE.txt = LICENSE.txt
|
||||
README.rst = README.rst
|
||||
EndProjectSection
|
||||
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "AzureBatchMaya", "AzureBatchMaya.pyproj", "{F585989B-2DF8-4241-8D02-948958187914}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
ArnoldSDKv4.2.3.1|Any CPU = ArnoldSDKv4.2.3.1|Any CPU
|
||||
ArnoldSDKv4.2.3.1|Mixed Platforms = ArnoldSDKv4.2.3.1|Mixed Platforms
|
||||
ArnoldSDKv4.2.3.1|x64 = ArnoldSDKv4.2.3.1|x64
|
||||
ArnoldSDKv4.2.4.1|Any CPU = ArnoldSDKv4.2.4.1|Any CPU
|
||||
ArnoldSDKv4.2.4.1|Mixed Platforms = ArnoldSDKv4.2.4.1|Mixed Platforms
|
||||
ArnoldSDKv4.2.4.1|x64 = ArnoldSDKv4.2.4.1|x64
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|Mixed Platforms = Debug|Mixed Platforms
|
||||
Debug|x64 = Debug|x64
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|Mixed Platforms = Release|Mixed Platforms
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{F585989B-2DF8-4241-8D02-948958187914}.ArnoldSDKv4.2.3.1|Any CPU.ActiveCfg = ArnoldSDKv4.2.3.1|Any CPU
|
||||
{F585989B-2DF8-4241-8D02-948958187914}.ArnoldSDKv4.2.3.1|Mixed Platforms.ActiveCfg = ArnoldSDKv4.2.3.1|Any CPU
|
||||
{F585989B-2DF8-4241-8D02-948958187914}.ArnoldSDKv4.2.3.1|x64.ActiveCfg = ArnoldSDKv4.2.3.1|Any CPU
|
||||
{F585989B-2DF8-4241-8D02-948958187914}.ArnoldSDKv4.2.4.1|Any CPU.ActiveCfg = ArnoldSDKv4.2.4.1|Any CPU
|
||||
{F585989B-2DF8-4241-8D02-948958187914}.ArnoldSDKv4.2.4.1|Mixed Platforms.ActiveCfg = ArnoldSDKv4.2.4.1|Any CPU
|
||||
{F585989B-2DF8-4241-8D02-948958187914}.ArnoldSDKv4.2.4.1|x64.ActiveCfg = ArnoldSDKv4.2.4.1|Any CPU
|
||||
{F585989B-2DF8-4241-8D02-948958187914}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F585989B-2DF8-4241-8D02-948958187914}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{F585989B-2DF8-4241-8D02-948958187914}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{F585989B-2DF8-4241-8D02-948958187914}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F585989B-2DF8-4241-8D02-948958187914}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{F585989B-2DF8-4241-8D02-948958187914}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.ArnoldSDKv4.2.3.1|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.ArnoldSDKv4.2.3.1|Any CPU.Build.0 = Release|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.ArnoldSDKv4.2.3.1|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.ArnoldSDKv4.2.3.1|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.ArnoldSDKv4.2.3.1|x64.ActiveCfg = Release|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.ArnoldSDKv4.2.4.1|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.ArnoldSDKv4.2.4.1|Any CPU.Build.0 = Release|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.ArnoldSDKv4.2.4.1|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.ArnoldSDKv4.2.4.1|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.ArnoldSDKv4.2.4.1|x64.ActiveCfg = Release|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
@ -1,138 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>f585989b-2df8-4241-8d02-948958187914</ProjectGuid>
|
||||
<ProjectHome>.</ProjectHome>
|
||||
<StartupFile>tests\__init__.py</StartupFile>
|
||||
<WorkingDirectory>.</WorkingDirectory>
|
||||
<OutputPath>.</OutputPath>
|
||||
<Name>Maya.Client</Name>
|
||||
<RootNamespace>Maya.Client</RootNamespace>
|
||||
<InterpreterId>
|
||||
</InterpreterId>
|
||||
<InterpreterVersion>
|
||||
</InterpreterVersion>
|
||||
<SearchPath>
|
||||
</SearchPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="batchapps_maya\modules\default.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_maya\modules\maya_software.py" />
|
||||
<Compile Include="batchapps_maya\scripts\api.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_maya\scripts\assets.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_maya\scripts\config.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_maya\scripts\history.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_maya\scripts\submission.py" />
|
||||
<Compile Include="batchapps_maya\scripts\pools.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_maya\scripts\shared.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_maya\scripts\ui\ui_assets.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_maya\scripts\ui\ui_config.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_maya\scripts\ui\ui_history.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_maya\scripts\ui\ui_pools.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_maya\scripts\ui\ui_submission.py" />
|
||||
<Compile Include="batchapps_maya\scripts\utils.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_maya\plug-in\batchapps.py" />
|
||||
<Compile Include="batchapps_maya\scripts\ui\ui_shared.py" />
|
||||
<Compile Include="dependencies.py" />
|
||||
<Compile Include="package.py" />
|
||||
<Compile Include="tests\test_assets.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="tests\test_config.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="tests\test_history.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="tests\test_pools.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="tests\test_shared.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="tests\test_submission.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="tests\__init__.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="batchapps_maya\icons\" />
|
||||
<Folder Include="batchapps_maya\mel\" />
|
||||
<Folder Include="batchapps_maya\modules\" />
|
||||
<Folder Include="batchapps_maya\" />
|
||||
<Folder Include="batchapps_maya\scripts\" />
|
||||
<Folder Include="batchapps_maya\plug-in\" />
|
||||
<Folder Include="batchapps_maya\scripts\ui\" />
|
||||
<Folder Include="tests\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="batchapps_maya\icons\portal.png" />
|
||||
<Content Include="batchapps_maya\icons\submit.png" />
|
||||
<Content Include="batchapps_maya\icons\loading_preview.png" />
|
||||
<Content Include="batchapps_maya\icons\no_preview.png" />
|
||||
<Content Include="batchapps_maya\mel\create_shelf.mel" />
|
||||
<Content Include="requirements.txt">
|
||||
<SubType>Code</SubType>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||
<PtvsTargetsFile>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets</PtvsTargetsFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'ArnoldSDKv4.2.3.1' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
<OutputPath>bin\ArnoldSDKv4.2.3.1\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'ArnoldSDKv4.2.4.1' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
<OutputPath>bin\ArnoldSDKv4.2.4.1\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<Import Condition="Exists($(PtvsTargetsFile))" Project="$(PtvsTargetsFile)" />
|
||||
<Import Condition="!Exists($(PtvsTargetsFile))" Project="$(MSBuildToolsPath)\Microsoft.Common.targets" />
|
||||
<!-- Uncomment the CoreCompile target to enable the Build command in
|
||||
Visual Studio and specify your pre- and post-build commands in
|
||||
the BeforeBuild and AfterBuild targets below. -->
|
||||
<!--<Target Name="CoreCompile" />-->
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
</Project>
|
Двоичные данные
MayaClient/batchapps_maya/icons/portal.png
До Ширина: | Высота: | Размер: 789 B |
Двоичные данные
MayaClient/batchapps_maya/icons/submit.png
До Ширина: | Высота: | Размер: 699 B |
|
@ -1,33 +0,0 @@
|
|||
python("from shared import BatchAppsSettings");
|
||||
|
||||
$batchapps_path = `getenv "BATCHAPPS_ICONS"`;
|
||||
|
||||
global proc run_guiStarter()
|
||||
{
|
||||
python("BatchAppsSettings.starter()");
|
||||
}
|
||||
|
||||
global proc openMissionControl()
|
||||
{
|
||||
python("import webbrowser\nwebbrowser.open(\"https://manage.batchapps.windows.net\", 2, True)");
|
||||
}
|
||||
|
||||
|
||||
string $uiButton = `shelfButton
|
||||
-parent "BatchApps"
|
||||
-enable 1
|
||||
-annotation "Job Submission"
|
||||
-label "Job Submission"
|
||||
-image1 ($batchapps_path + "/submit.png")
|
||||
-style "iconOnly"
|
||||
-command "run_guiStarter()"`;
|
||||
|
||||
string $mcButton = `shelfButton
|
||||
-parent "BatchApps"
|
||||
-enable 1
|
||||
-annotation "Management Portal"
|
||||
-label "Management Portal"
|
||||
-image1 ($batchapps_path + "/portal.png")
|
||||
-style "iconOnly"
|
||||
-command "openMissionControl()"`;
|
||||
;
|
|
@ -1,271 +0,0 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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 maya import mel
|
||||
from maya import cmds
|
||||
|
||||
from maya.OpenMayaMPx import MFnPlugin
|
||||
import maya.OpenMaya as OpenMaya
|
||||
import maya.OpenMayaMPx as OpenMayaMPx
|
||||
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
|
||||
|
||||
VERSION = "0.1.0"
|
||||
|
||||
cmd_name = "BatchApps"
|
||||
fMayaExitingCB = None
|
||||
|
||||
|
||||
class BatchAppsSetup(OpenMayaMPx.MPxCommand):
|
||||
|
||||
def __init__(self):
|
||||
OpenMayaMPx.MPxCommand.__init__(self)
|
||||
|
||||
@staticmethod
|
||||
def clean(p):
|
||||
if os.sep == "\\":
|
||||
return p.replace(os.sep, "\\\\")
|
||||
return p
|
||||
|
||||
@staticmethod
|
||||
def create_modfile(p, plugin_path):
|
||||
try:
|
||||
modfile = os.path.join(p, "batchapps.mod")
|
||||
with open(modfile, 'w') as mod:
|
||||
mod.write("+ BatchApps {0} {1}\n".format(VERSION, plugin_path))
|
||||
mod.write("MAYA_PLUG_IN_PATH+={0}\n".format(os.path.join(plugin_path, "plug-in")))
|
||||
mod.write("MAYA_SCRIPT_PATH+:=mel\n")
|
||||
mod.write("BATCHAPPS_ICONS:=icons\n")
|
||||
mod.write("BATCHAPPS_SCRIPTS:=scripts\n")
|
||||
mod.write("BATCHAPPS_SCRIPTS+:=scripts/ui\n")
|
||||
mod.write("BATCHAPPS_MODULES:=modules")
|
||||
|
||||
print("Successfully created mod file at %s" % p)
|
||||
print("Setting environment variables for current session.")
|
||||
mel.eval("""putenv "BATCHAPPS_SCRIPTS" "{0};{1};" """.format(
|
||||
BatchAppsSetup.clean(os.path.join(plugin_path, "scripts")),
|
||||
BatchAppsSetup.clean(os.path.join(plugin_path, "scripts", "ui"))))
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
print("Couldn't create mod file at %s" % p)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def find_modules_locations(plugin_path):
|
||||
modulepaths = mel.eval("""getenv "MAYA_MODULE_PATH" """).split(os.pathsep)
|
||||
modulepaths.reverse()
|
||||
|
||||
for p in modulepaths:
|
||||
if not os.path.isdir(p):
|
||||
|
||||
try:
|
||||
os.makedirs(p)
|
||||
except:
|
||||
print("Module directory doesn't exist, and cannot create it: %s" % p)
|
||||
|
||||
if BatchAppsSetup.create_modfile(p, plugin_path):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def find_env_location(plugin_path):
|
||||
maya_env = os.path.join(os.path.normpath(os.environ['MAYA_APP_DIR']), "Maya.env")
|
||||
|
||||
if not os.path.isfile(maya_env):
|
||||
maya_env = os.path.normpath(os.path.join(mel.eval("about -preferences"), "Maya.env"))
|
||||
|
||||
return BatchAppsSetup.add_modulepath_to_env(plugin_path, maya_env)
|
||||
|
||||
@staticmethod
|
||||
def add_modulepath_to_env(plugin_path, env_path):
|
||||
plugin_mods = os.path.join(plugin_path, "modules")
|
||||
open_format = 'a' if os.path.exists(env_path) else 'w'
|
||||
|
||||
try:
|
||||
with open(env_path, open_format) as modfile:
|
||||
|
||||
if open_format == 'a' and modfile.tell() != 0:
|
||||
modfile.seek(-1, os.SEEK_END)
|
||||
next_char = modfile.read(1)
|
||||
|
||||
if next_char != '\n':
|
||||
modfile.write('\n')
|
||||
|
||||
if os.pathsep == ';':
|
||||
modfile.write("MAYA_MODULE_PATH=%MAYA_MODULE_PATH%;{0}".format(plugin_mods))
|
||||
else:
|
||||
modfile.write("MAYA_MODULE_PATH=$MAYA_MODULE_PATH:{0}".format(plugin_mods))
|
||||
|
||||
return BatchAppsSetup.create_modfile(plugin_mods, plugin_path)
|
||||
except Exception as exp:
|
||||
print("Couldn't create new maya env file: %s" % env_path)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def remove_environment():
|
||||
modulepaths = mel.eval("""getenv "MAYA_MODULE_PATH" """).split(os.pathsep)
|
||||
modulepaths.reverse()
|
||||
|
||||
for p in modulepaths:
|
||||
modfile = os.path.join(p, "batchapps.mod")
|
||||
|
||||
if os.path.exists(modfile):
|
||||
try:
|
||||
os.remove(modfile)
|
||||
|
||||
except:
|
||||
print("Found BatchApps mod file, but couldn't delete. ", modfile)
|
||||
|
||||
@staticmethod
|
||||
def set_environment(p):
|
||||
srcpath = os.path.join(p, "scripts")
|
||||
icnpath = os.path.join(p, "icons")
|
||||
melpath = os.path.join(p, "mel")
|
||||
modpath = os.path.join(p, "modules")
|
||||
sys.path.append(modpath)
|
||||
sys.path.append(srcpath)
|
||||
sys.path.append(os.path.join(srcpath, "ui"))
|
||||
#sys.path.append(os.path.join(srcpath, "props"))
|
||||
|
||||
script_dirs = mel.eval("""getenv "MAYA_SCRIPT_PATH" """) + os.pathsep
|
||||
mel.eval("""putenv "MAYA_SCRIPT_PATH" ("{0}" + "{1}") """.format(script_dirs, BatchAppsSetup.clean(melpath)))
|
||||
mel.eval("""putenv "BATCHAPPS_ICONS" "{0}" """.format(BatchAppsSetup.clean(icnpath)))
|
||||
mel.eval("""putenv "BATCHAPPS_MODULES" "{0}" """.format(BatchAppsSetup.clean(modpath)))
|
||||
|
||||
print("Attempting to create mod file under MAYA_MODULE_PATH")
|
||||
mods = BatchAppsSetup.find_modules_locations(p)
|
||||
|
||||
if not mods:
|
||||
print("Attempting to add custom module path to Maya.env")
|
||||
mods = BatchAppsSetup.find_env_location(p)
|
||||
|
||||
if not mods:
|
||||
print("Failed to setup BatchApps mod file")
|
||||
|
||||
return mel.eval("""getenv "MAYA_MODULE_PATH" """) + os.pathsep
|
||||
|
||||
def cmd_creator():
|
||||
return OpenMayaMPx.asMPxPtr(BatchAppsSetup())
|
||||
|
||||
def setup_module():
|
||||
current_file = inspect.getfile(inspect.currentframe())
|
||||
current_dir = os.path.dirname(os.path.abspath(current_file))
|
||||
plugin_path = os.path.split(current_dir)[0] + os.sep
|
||||
BatchAppsSetup.set_environment(plugin_path)
|
||||
|
||||
def get_usershelf_dir(filename):
|
||||
shelfDirs = mel.eval("internalVar -userShelfDir").split(os.pathsep)
|
||||
|
||||
for d in shelfDirs:
|
||||
|
||||
if (d.startswith(mel.eval("internalVar -userPrefDir")) and (d.endswith("/prefs/shelves/"))):
|
||||
melPath = os.path.join(os.path.normpath(d), filename)
|
||||
|
||||
if os.path.exists(melPath):
|
||||
return melPath
|
||||
|
||||
return os.path.normpath(os.path.join(mel.eval("about -preferences"), "prefs", "shelves", filename))
|
||||
|
||||
def remove_ui(clientData):
|
||||
try:
|
||||
try:
|
||||
mel.eval("""deleteUI -layout('BatchApps')""")
|
||||
|
||||
except:
|
||||
print("Couldn't delete shelf")
|
||||
|
||||
melPath = get_usershelf_dir("shelf_BatchApps.mel")
|
||||
|
||||
if os.path.exists("{0}.deleted".format(melPath)):
|
||||
os.remove("{0}.deleted".format(melPath))
|
||||
|
||||
os.rename(melPath, "{0}.deleted".format(melPath))
|
||||
|
||||
except Exception as e:
|
||||
print("Failed to load", (str(e)))
|
||||
|
||||
def initializePlugin(obj):
|
||||
print("Initializing Batch Apps plug-in")
|
||||
|
||||
plugin = OpenMayaMPx.MFnPlugin(obj, "me", "1.0", "Any")
|
||||
plugin.registerCommand(cmd_name, cmd_creator)
|
||||
|
||||
try:
|
||||
if (mel.eval("""shelfLayout -exists "BatchApps" """) == 0):
|
||||
mel.eval('addNewShelfTab %s' % "BatchApps")
|
||||
mel.eval("""source "create_shelf.mel" """)
|
||||
|
||||
melPath = get_usershelf_dir("shelf_BatchApps.mel")
|
||||
if os.path.exists("{0}.deleted".format(melPath)):
|
||||
os.remove("{0}.deleted".format(melPath))
|
||||
|
||||
os.rename(melPath, "{0}.deleted".format(melPath))
|
||||
|
||||
except:
|
||||
print("Couldn't add shelf")
|
||||
|
||||
# Add callback to clean up UI when Maya exits
|
||||
global fMayaExitingCB
|
||||
fMayaExitingCB = OpenMaya.MSceneMessage.addCallback(OpenMaya.MSceneMessage.kMayaExiting, remove_ui)
|
||||
|
||||
def uninitializePlugin(obj):
|
||||
print("Removing BatchApps plug-in")
|
||||
|
||||
plugin = MFnPlugin(obj)
|
||||
plugin.deregisterCommand(cmd_name)
|
||||
|
||||
try:
|
||||
mel.eval('deleteShelfTab %s' % "BatchApps")
|
||||
|
||||
except:
|
||||
print("Couldn't delete shelf")
|
||||
|
||||
global fMayaExitingCB
|
||||
if (fMayaExitingCB is not None):
|
||||
OpenMaya.MSceneMessage.removeCallback(fMayaExitingCB)
|
||||
|
||||
if cmds.window("BatchApps", exists=1):
|
||||
cmds.deleteUI("BatchApps")
|
||||
|
||||
BatchAppsSetup.remove_environment()
|
||||
print("Finished clearing up all BatchApps components")
|
||||
|
||||
try:
|
||||
sys.path.extend(os.environ["BATCHAPPS_SCRIPTS"].split(os.pathsep))
|
||||
sys.path.append(os.environ['BATCHAPPS_MODULES'])
|
||||
|
||||
except KeyError as e:
|
||||
print("Couldn't find BatchApps environment, setting up now...")
|
||||
setup_module()
|
|
@ -1,295 +0,0 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
try:
|
||||
from maya import cmds, mel, utils
|
||||
import maya.OpenMaya as om
|
||||
import maya.OpenMayaMPx as omp
|
||||
|
||||
except ImportError:
|
||||
print("No maya module found.")
|
||||
|
||||
import logging
|
||||
|
||||
class MayaAPI(object):
|
||||
|
||||
@staticmethod
|
||||
def refresh():
|
||||
cmds.refresh()
|
||||
|
||||
@staticmethod
|
||||
def mel(command):
|
||||
try:
|
||||
return mel.eval(command)
|
||||
except:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_list(**kwargs):
|
||||
try:
|
||||
return cmds.ls(**kwargs)
|
||||
except:
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def get_attr(*args):
|
||||
try:
|
||||
return cmds.getAttr(*args)
|
||||
except:
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def file(**kwargs):
|
||||
try:
|
||||
return cmds.file(**kwargs)
|
||||
except:
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def file_select(**kwargs):
|
||||
return cmds.fileDialog2(dialogStyle=2, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def error(message):
|
||||
log = logging.getLogger('BatchAppsMaya')
|
||||
log.warning(message)
|
||||
return cmds.confirmDialog(title="Error",
|
||||
message=message,
|
||||
messageAlign="left",
|
||||
button="OK",
|
||||
icon="critical")
|
||||
|
||||
@staticmethod
|
||||
def warning(message):
|
||||
log = logging.getLogger('BatchAppsMaya')
|
||||
log.warning(message)
|
||||
return cmds.confirmDialog(title="Warning",
|
||||
message=message,
|
||||
messageAlign="left",
|
||||
button="OK",
|
||||
icon="warning")
|
||||
|
||||
@staticmethod
|
||||
def dependency_nodes():
|
||||
return NodeIterator()
|
||||
|
||||
@staticmethod
|
||||
def node_iterator():
|
||||
return om.MItDependencyNodes()
|
||||
|
||||
@staticmethod
|
||||
def dependency_node(node):
|
||||
return om.MFnDependencyNode(node)
|
||||
|
||||
@staticmethod
|
||||
def contentinfo_table():
|
||||
return omp.MExternalContentInfoTable()
|
||||
|
||||
@staticmethod
|
||||
def delete_ui(*args, **kwargs):
|
||||
try:
|
||||
cmds.deleteUI(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
print("Couldn't delete:", str(exp))
|
||||
|
||||
@staticmethod
|
||||
def parent(parent='..'):
|
||||
cmds.setParent(parent)
|
||||
|
||||
@staticmethod
|
||||
def text(*args, **kwargs):
|
||||
try:
|
||||
return cmds.text(*args, **kwargs)
|
||||
except:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def text_field(*args, **kwargs):
|
||||
try:
|
||||
return cmds.textField(*args, **kwargs)
|
||||
except:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def button(*args, **kwargs):
|
||||
try:
|
||||
return cmds.button(*args, **kwargs)
|
||||
except:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def check_box(*args, **kwargs):
|
||||
try:
|
||||
return cmds.checkBox(*args, **kwargs)
|
||||
except:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def image(*args, **kwargs):
|
||||
try:
|
||||
return cmds.image(*args, **kwargs)
|
||||
except:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def row_layout(*args, **kwargs):
|
||||
try:
|
||||
return cmds.columnLayout(*args, **kwargs)
|
||||
except:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def col_layout(*args, **kwargs):
|
||||
try:
|
||||
return cmds.rowColumnLayout(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def frame_layout(*args, **kwargs):
|
||||
try:
|
||||
return cmds.frameLayout(*args, **kwargs)
|
||||
except Exception as e:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def scroll_layout(*args, **kwargs):
|
||||
try:
|
||||
return cmds.scrollLayout(*args, **kwargs)
|
||||
except:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def form_layout(*args, **kwargs):
|
||||
try:
|
||||
return cmds.formLayout(*args, **kwargs)
|
||||
except:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def tab_layout(*args, **kwargs):
|
||||
try:
|
||||
return cmds.tabLayout(*args, **kwargs)
|
||||
except:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def int_slider(*args, **kwargs):
|
||||
try:
|
||||
return cmds.intSliderGrp(*args, **kwargs)
|
||||
except:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def menu(*args, **kwargs):
|
||||
try:
|
||||
return cmds.optionMenu(*args, **kwargs)
|
||||
except:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def menu_option(*args, **kwargs):
|
||||
try:
|
||||
return cmds.menuItem(*args, **kwargs)
|
||||
except:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def radio_group(*args, **kwargs):
|
||||
try:
|
||||
return cmds.radioButtonGrp(*args, **kwargs)
|
||||
except:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def window(*args, **kwargs):
|
||||
try:
|
||||
return cmds.window(*args, **kwargs)
|
||||
except:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def show(ui):
|
||||
try:
|
||||
cmds.showWindow(ui)
|
||||
except:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def execute(*args):
|
||||
utils.executeDeferred(*args)
|
||||
|
||||
class NodeIterator(object):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
try:
|
||||
self._iter = MayaAPI.node_iterator()
|
||||
except:
|
||||
self._iter = iter([])
|
||||
|
||||
self._current = None
|
||||
|
||||
def get_references(self):
|
||||
dep_node = MayaAPI.dependency_node(self._current)
|
||||
assets = MayaReferences()
|
||||
|
||||
self._iter.next()
|
||||
return assets.get_paths(dep_node)
|
||||
|
||||
def is_done(self):
|
||||
try:
|
||||
is_complete = self._iter.isDone()
|
||||
|
||||
if not is_complete:
|
||||
self._current = self._iter.thisNode()
|
||||
|
||||
return is_complete
|
||||
|
||||
except Exception as exp:
|
||||
True
|
||||
|
||||
class MayaReferences(object):
|
||||
|
||||
def __init__(self):
|
||||
self._table = MayaAPI.contentinfo_table()
|
||||
|
||||
def get_paths(self, node):
|
||||
node.getExternalContent(self._table)
|
||||
paths = []
|
||||
|
||||
for i in range(self._table.length()):
|
||||
_path = []
|
||||
_node = ""
|
||||
_role = []
|
||||
self._table.getEntryByIndex(i, _path, _node, _role)
|
||||
if _path:
|
||||
paths.append(_path[0])
|
||||
|
||||
return paths
|
||||
|
|
@ -1,325 +0,0 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
|
||||
import pkgutil
|
||||
import inspect
|
||||
import importlib
|
||||
|
||||
from api import MayaAPI as maya
|
||||
from ui_assets import AssetsUI
|
||||
from batchapps import FileManager
|
||||
|
||||
from default import BatchAppsRenderAssets
|
||||
|
||||
class BatchAppsAssets(object):
|
||||
|
||||
def __init__(self, frame, call):
|
||||
|
||||
self._log = logging.getLogger('BatchAppsMaya')
|
||||
self._call = call
|
||||
self._session = None
|
||||
|
||||
self.manager = None
|
||||
self.scene = None
|
||||
self.assets = None
|
||||
self.modules = self.collect_modules()
|
||||
|
||||
self.ui = AssetsUI(self, frame)
|
||||
|
||||
#def start(self):
|
||||
# self._log.debug("Starting BatchAppsAssets...")
|
||||
# try:
|
||||
# self.ui.refresh()
|
||||
# return True
|
||||
|
||||
# except Exception as exp:
|
||||
# message = "Error starting Assets UI: {0}".format(exp)
|
||||
# self._log.warning(message)
|
||||
# Utils.error_dialog(message)
|
||||
# return False
|
||||
|
||||
|
||||
def configure(self, session):
|
||||
self._session = session
|
||||
self.manager = FileManager(self._session.credentials, self._session.config)
|
||||
self.scene = self.get_scene()
|
||||
self.assets = Assets()
|
||||
|
||||
def collect_modules(self):
|
||||
self._log.info("Collecting modules...")
|
||||
|
||||
render_modules = []
|
||||
module_dir = os.environ['BATCHAPPS_MODULES']
|
||||
|
||||
for importer, package_name, _ in pkgutil.iter_modules([module_dir]):
|
||||
if package_name == "default":
|
||||
continue
|
||||
|
||||
try:
|
||||
module = importlib.import_module(package_name)
|
||||
|
||||
for name, obj in inspect.getmembers(module, inspect.isclass):
|
||||
|
||||
if issubclass(obj, BatchAppsRenderAssets):
|
||||
render_modules.append(obj())
|
||||
|
||||
except ImportError as err:
|
||||
self._log.warning("Couldn't import module: {0}".format(package_name))
|
||||
|
||||
return render_modules
|
||||
|
||||
def configure_renderer(self):
|
||||
current_renderer = maya.mel("getAttr defaultRenderGlobals.currentRenderer")
|
||||
self._log.info("Current renderer: {0}".format(current_renderer))
|
||||
|
||||
for module in self.modules:
|
||||
|
||||
if not hasattr(module, 'render_engine'):
|
||||
self._log.warning("Module {0} has no render engine attribute. Skipping.".format(module))
|
||||
continue
|
||||
|
||||
if module.render_engine == str(current_renderer):
|
||||
self.renderer = module
|
||||
self._log.debug("Configured renderer to {0}".format(self.renderer.render_engine))
|
||||
return
|
||||
|
||||
self.renderer = BatchAppsRenderAssets()
|
||||
self._log.debug("Configured renderer to {0}".format(self.renderer.render_engine))
|
||||
|
||||
def refresh_assets(self):
|
||||
self.assets = Assets()
|
||||
self.scene = self.get_scene()
|
||||
self.set_assets()
|
||||
|
||||
def set_assets(self):
|
||||
self.configure_renderer()
|
||||
self.assets.gather(self.manager)
|
||||
self.assets.extend(self.renderer.renderer_assets())
|
||||
|
||||
def asset_categories(self):
|
||||
keys = self.assets.refs.keys()
|
||||
try:
|
||||
keys.remove('Additional')
|
||||
except ValueError:
|
||||
pass
|
||||
return keys
|
||||
|
||||
def collect_assets(self, job_set):
|
||||
self._log.info("Converting assets into user files...")
|
||||
collected = {}
|
||||
|
||||
if len(self.assets.refs) == 1:
|
||||
self.set_assets()
|
||||
|
||||
user_files = self.assets.collect()
|
||||
|
||||
for i in job_set:
|
||||
user_file = self.manager.file_from_path(i)
|
||||
user_files.append(user_file)
|
||||
|
||||
collected['pathmaps'] = self.assets.get_pathmaps()
|
||||
collected['assets'] = self.manager.create_file_set(user_files)
|
||||
|
||||
return collected
|
||||
|
||||
|
||||
def get_assets(self, category):
|
||||
return self.assets.refs.get(category, [])
|
||||
|
||||
def get_scene(self):
|
||||
scene = os.path.abspath(maya.file(q=True, sn=True))
|
||||
if ((scene.endswith('.mb')) or (scene.endswith('.ma'))) and (os.path.exists(scene)):
|
||||
return str(os.path.normpath(scene))
|
||||
else:
|
||||
return ''
|
||||
|
||||
def add_files(self, files, layout):
|
||||
for f in files:
|
||||
self.assets.add_asset(f, layout)
|
||||
|
||||
def add_dir(self, dirs, layout):
|
||||
for f in dirs:
|
||||
for r, d, f in os.walk(f):
|
||||
for files in f:
|
||||
self.assets.add_asset(os.path.join(r, files), layout)
|
||||
|
||||
|
||||
class Assets(object):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self._log = logging.getLogger('BatchAppsMaya')
|
||||
self.manager = None
|
||||
self.refs = {'Additional': []}
|
||||
self.pathmaps = []
|
||||
|
||||
def gather(self, manager):
|
||||
self.manager = manager
|
||||
|
||||
self.refs.update(self.get_textures())
|
||||
self.refs.update(self.get_caches())
|
||||
self.refs.update(self.get_references())
|
||||
|
||||
def extend(self, more_assets):
|
||||
assets = {}
|
||||
for key, value in more_assets.items():
|
||||
assets[key] = []
|
||||
|
||||
for f in value:
|
||||
self.pathmaps.append(os.path.dirname(f))
|
||||
|
||||
try:
|
||||
asset = Asset(self.manager.file_from_path(f), assets[key])
|
||||
|
||||
if not asset.check(assets[key]):
|
||||
assets[key].append(asset)
|
||||
except:
|
||||
continue
|
||||
|
||||
self.refs.update(assets)
|
||||
|
||||
def collect(self):
|
||||
self._log.info("Collecting assets...")
|
||||
|
||||
userfiles = []
|
||||
for key, value in self.refs.items():
|
||||
|
||||
for userfile in value:
|
||||
if userfile.included():
|
||||
userfiles.append(userfile.file)
|
||||
|
||||
self._log.debug("Found {0} external assets.".format(len(userfiles)))
|
||||
return userfiles
|
||||
|
||||
def get_textures(self):
|
||||
assets = {'Files': []}
|
||||
|
||||
iter_nodes = maya.dependency_nodes()
|
||||
while not iter_nodes.is_done():
|
||||
|
||||
references = iter_nodes.get_references()
|
||||
for filepath in references:
|
||||
try:
|
||||
self.pathmaps.append(os.path.dirname(filepath))
|
||||
asset = Asset(self.manager.file_from_path(filepath), assets['Files'])
|
||||
|
||||
if not asset.check(assets['Files']):
|
||||
assets['Files'].append(asset)
|
||||
|
||||
except Exception as exp:
|
||||
self._log.warning("Failed to extract asset file from reference: {0}".format(exp))
|
||||
continue
|
||||
|
||||
self._log.debug("Found {0} external files.".format(len(assets['Files'])))
|
||||
return assets
|
||||
|
||||
def get_references(self):
|
||||
return {}
|
||||
|
||||
def get_caches(self):
|
||||
assets = {'Caches': []}
|
||||
cacheFiles = maya.get_list(type="cacheFile")
|
||||
|
||||
for c in cacheFiles:
|
||||
|
||||
c_path = maya.get_attr(c+".cachePath")
|
||||
if c_path:
|
||||
self.pathmaps.append(os.path.dirname(c_path))
|
||||
asset = Asset(self.manager.file_from_path(c_path), assets['Caches'])
|
||||
assets['Caches'].append(asset)
|
||||
|
||||
self._log.debug("Found {0} caches.".format(len(assets['Caches'])))
|
||||
return assets
|
||||
|
||||
def add_asset(self, file, layout):
|
||||
self._log.info("Adding file: {0}".format(file))
|
||||
|
||||
self.pathmaps.append(os.path.dirname(file))
|
||||
asset = Asset(self.manager.file_from_path(file), self.refs['Additional'])
|
||||
|
||||
if not asset.check(self.refs['Additional']):
|
||||
asset.display(layout)
|
||||
self.refs['Additional'].append(asset)
|
||||
|
||||
def get_pathmaps(self):
|
||||
path_list = list(set(self.pathmaps))
|
||||
clean_list = [str(p) for p in path_list if p]
|
||||
|
||||
path_mapping = {'PathMaps':clean_list}
|
||||
return json.dumps(path_mapping)
|
||||
|
||||
class Asset(object):
|
||||
|
||||
def __init__(self, file, parent):
|
||||
test = bool(file)
|
||||
self.path = file.path
|
||||
self.label = " {0}".format(os.path.basename(self.path))
|
||||
self.file = file
|
||||
self.note = self.path if bool(self.file) else "Can't find {0}".format(self.path)
|
||||
self.parent_list = parent
|
||||
self.check_box = None
|
||||
|
||||
def display(self, layout, enable=True):
|
||||
self.check_box = maya.check_box(label=self.label,
|
||||
value=bool(self.file),
|
||||
enable=bool(self.file),
|
||||
parent=layout,
|
||||
onCommand=lambda e: self.include(),
|
||||
offCommand=lambda e: self.exclude(),
|
||||
annotation=self.note)
|
||||
|
||||
def included(self):
|
||||
if self.check_box:
|
||||
return bool(maya.check_box(self.check_box, query=True, value=True))
|
||||
else:
|
||||
return bool(self.file)
|
||||
|
||||
def include(self):
|
||||
if self not in self.parent_list:
|
||||
self.parent_list.append(self)
|
||||
|
||||
def exclude(self):
|
||||
try:
|
||||
self.parent_list.remove(self)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def delete(self):
|
||||
try:
|
||||
self.parent_list.remove(self)
|
||||
except ValueError:
|
||||
pass
|
||||
maya.delete_ui(self.check_box, control=True)
|
||||
|
||||
def check(self, files):
|
||||
return any(os.path.normcase(f.path) == os.path.normcase(self.path) for f in files)
|
|
@ -1,298 +0,0 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import webbrowser
|
||||
import logging
|
||||
|
||||
from BaseHTTPServer import HTTPServer
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
||||
from urllib import unquote
|
||||
|
||||
from ui_config import ConfigUI
|
||||
|
||||
from batchapps import (
|
||||
AzureOAuth,
|
||||
Configuration)
|
||||
from batchapps.exceptions import (
|
||||
AuthenticationException,
|
||||
InvalidConfigException)
|
||||
|
||||
TIMEOUT = 60 # 1 minute
|
||||
|
||||
LOG_LEVELS = {
|
||||
'debug':10,
|
||||
'info':20,
|
||||
'warning':30,
|
||||
'error':40
|
||||
}
|
||||
|
||||
class BatchAppsConfig(object):
|
||||
|
||||
def __init__(self, frame, start):
|
||||
|
||||
self.session = start
|
||||
|
||||
self._data_dir = os.path.join(os.path.expanduser('~'), 'BatchAppsData')
|
||||
self._ini_file = "batch_apps.ini"
|
||||
|
||||
self._cfg = self._configure_plugin()
|
||||
self._log_level = LOG_LEVELS[self._cfg.logging_level().lower()]
|
||||
self._creds = None
|
||||
|
||||
self._log = self._configure_logging()
|
||||
self._code = None
|
||||
|
||||
self.ui = ConfigUI(self, frame)
|
||||
|
||||
self._auth = self.auto_authentication()
|
||||
self._update_config_ui()
|
||||
|
||||
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
return self._cfg
|
||||
|
||||
@property
|
||||
def credentials(self):
|
||||
return self._creds
|
||||
|
||||
@property
|
||||
def auth(self):
|
||||
return self._auth
|
||||
|
||||
def _configure_plugin(self):
|
||||
cfg = None
|
||||
try:
|
||||
data_dir = os.path.split(self._data_dir)
|
||||
|
||||
cfg = Configuration(data_path=data_dir[0],
|
||||
name=self._ini_file,
|
||||
datadir=data_dir[1])
|
||||
|
||||
cfg.add_jobtype("Arnold")
|
||||
cfg.current_jobtype("Arnold")
|
||||
|
||||
except (InvalidConfigException, IndexError) as exp:
|
||||
raise Exception("Error occurred during configuration {0}".format(exp))
|
||||
|
||||
|
||||
if not os.path.isdir(self._data_dir):
|
||||
raise EnvironmentError(
|
||||
"Data directory not created at '{0}'.\n"
|
||||
"Please ensure you have adequate permissions.".format(self._data_dir))
|
||||
|
||||
return cfg
|
||||
|
||||
def _configure_logging(self):
|
||||
logger = logging.getLogger('BatchAppsMaya')
|
||||
|
||||
console_format = logging.Formatter(
|
||||
"BatchApps: [%(levelname)s] %(message)s")
|
||||
|
||||
file_format = logging.Formatter(
|
||||
"%(asctime)-15s [%(levelname)s] %(module)s: %(message)s")
|
||||
|
||||
console_logging = logging.StreamHandler()
|
||||
console_logging.setFormatter(console_format)
|
||||
logger.addHandler(console_logging)
|
||||
|
||||
logfile = os.path.join(self._data_dir, "batch_apps.log")
|
||||
|
||||
file_logging = logging.FileHandler(logfile)
|
||||
file_logging.setFormatter(file_format)
|
||||
logger.addHandler(file_logging)
|
||||
|
||||
logger.setLevel(int(self._log_level))
|
||||
return logger
|
||||
|
||||
|
||||
def _update_config_ui(self):
|
||||
self.ui.endpoint = self._cfg.endpoint()
|
||||
|
||||
auth_config = self._cfg.aad_config(validate=False)
|
||||
self.ui.account = auth_config.get("unattended_account", "")
|
||||
self.ui.key = auth_config.get("unattended_key", "")
|
||||
self.ui.client = auth_config.get("client_id", "")
|
||||
self.ui.tenant = auth_config.get("tenant", "")
|
||||
self.ui.redirect = auth_config.get("redirect_uri", "")
|
||||
|
||||
self.ui.logging = self._log_level
|
||||
|
||||
self.ui.set_authenticate(self._auth)
|
||||
|
||||
def set_logging(self, level):
|
||||
|
||||
self._log_level = int(LOG_LEVELS[level])
|
||||
self._log.setLevel(self._log_level)
|
||||
|
||||
self._cfg.logging_level(str(level))
|
||||
|
||||
def save_changes(self):
|
||||
self._cfg.aad_config(endpoint = self.ui.endpoint, account=self.ui.account, key=self.ui.key,
|
||||
client_id=self.ui.client, tenant=self.ui.tenant,
|
||||
redirect=self.ui.redirect, validate=False)
|
||||
self._cfg.save_config()
|
||||
|
||||
def auto_authentication(self):
|
||||
try:
|
||||
self._log.info("Checking for unattended session...")
|
||||
self._creds = AzureOAuth.get_unattended_session(config=self._cfg)
|
||||
self._log.info("Found!")
|
||||
self.ui.status = "Authenticated via unattended credentials"
|
||||
return True
|
||||
|
||||
except (AuthenticationException, InvalidConfigException) as exp:
|
||||
self._log.info("Could not get unattended session: {0}".format(exp))
|
||||
|
||||
try:
|
||||
self._log.info("Checking for cached session...")
|
||||
self._creds = AzureOAuth.get_session(config=self._cfg)
|
||||
self._log.info("Found!")
|
||||
self.ui.status = "Authenticated via cached credentials"
|
||||
return True
|
||||
|
||||
except (AuthenticationException, InvalidConfigException) as exp:
|
||||
self._log.info("Could not get cached session: {0}".format(exp))
|
||||
self.ui.status = "Unauthenticated"
|
||||
return False
|
||||
|
||||
def authenticate(self):
|
||||
self._cfg = self._configure_plugin()
|
||||
self._auth = self.auto_authentication()
|
||||
|
||||
if not self._auth:
|
||||
self._auth = self.web_authentication()
|
||||
|
||||
self.ui.set_authenticate(self._auth)
|
||||
self.session()
|
||||
|
||||
|
||||
def wait_for_request(self):
|
||||
self._code = None
|
||||
|
||||
redirect = self._cfg.aad_config()['redirect_uri'].split(':')
|
||||
server_address = (redirect[-2].lstrip('/'), int(redirect[-1]))
|
||||
|
||||
class OAuthRequestHandler(BaseHTTPRequestHandler):
|
||||
|
||||
def log_message(self, format, *args):
|
||||
return
|
||||
|
||||
def do_GET(s):
|
||||
self.process_response(s)
|
||||
|
||||
web_server = HTTPServer(server_address, OAuthRequestHandler)
|
||||
self._log.debug("Created web server listening at: {0}, {1}.".format(
|
||||
redirect[-2], int(redirect[-1])))
|
||||
|
||||
web_server.timeout = TIMEOUT
|
||||
web_server.handle_request()
|
||||
web_server.server_close()
|
||||
self._log.debug("Closed server.")
|
||||
|
||||
def open_websession(self):
|
||||
try:
|
||||
url, state = AzureOAuth.get_authorization_url(config=self._cfg)
|
||||
webbrowser.open(url)
|
||||
|
||||
self._log.info("Opened web browser for authentication "
|
||||
"and waiting for response.")
|
||||
|
||||
self.wait_for_request()
|
||||
|
||||
except (AuthenticationException, InvalidConfigException) as exp:
|
||||
self._log.error("Unable to open Web UI auth session: "
|
||||
"{0}".format(exp))
|
||||
|
||||
|
||||
def process_response(self, s):
|
||||
self._code = s.path
|
||||
if s.path.startswith('/?code'):
|
||||
|
||||
s.send_response(200)
|
||||
s.send_header("Content-type", "text/html")
|
||||
s.end_headers()
|
||||
|
||||
s.wfile.write(b"<html><head><title>Authentication Successful</title></head>")
|
||||
s.wfile.write(b"<body><p>Authentication successful.</p>")
|
||||
s.wfile.write(b"<p>You can now return to Maya where your log in</p>")
|
||||
s.wfile.write(b"<p>will be complete in just a moment.</p>")
|
||||
s.wfile.write(b"</body></html>")
|
||||
|
||||
else:
|
||||
|
||||
s.send_response(401)
|
||||
s.send_header("Content-type", "text/html")
|
||||
s.end_headers()
|
||||
|
||||
s.wfile.write(b"<html><head><title>Authentication Failed</title></head>")
|
||||
s.wfile.write(b"<body><p>Authentication unsuccessful.</p>")
|
||||
s.wfile.write(b"<p>Check the Maya console for details.</p>")
|
||||
s.wfile.write(b"</body></html>")
|
||||
|
||||
def decode_error(self, val):
|
||||
error_idx = self._code.find(val)
|
||||
if error_idx < 0:
|
||||
return None
|
||||
|
||||
strt_idx = error_idx + len(val)
|
||||
end_idx = self._code.find('&', strt_idx)
|
||||
error_val = self._code[strt_idx:end_idx]
|
||||
|
||||
return unquote(error_val)
|
||||
|
||||
def web_authentication(self):
|
||||
|
||||
self.open_websession()
|
||||
|
||||
if not self._code:
|
||||
self._log.warning("Log in timed out - please try again.")
|
||||
return False
|
||||
|
||||
elif '/?error=' in self._code:
|
||||
error = self.decode_error('/?error=')
|
||||
details = self.decode_error(
|
||||
'&error_description=').replace('+', ' ')
|
||||
|
||||
self._log.error("Authentication failed: {0}".format(error))
|
||||
self._log.error(details)
|
||||
return False
|
||||
|
||||
else:
|
||||
self._log.info(
|
||||
"Received valid authentication response from web browser.")
|
||||
self._log.info("Now retrieving new authentication token...")
|
||||
|
||||
self._creds = AzureOAuth.get_authorization_token(
|
||||
self._code, config=self._cfg)
|
||||
|
||||
self._log.info("Successful! Login complete.")
|
||||
return True
|
||||
|
|
@ -1,302 +0,0 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import logging
|
||||
import tempfile
|
||||
import glob
|
||||
import struct
|
||||
import random
|
||||
import string
|
||||
import shutil
|
||||
|
||||
from api import MayaAPI as maya
|
||||
from ui_history import HistoryUI
|
||||
|
||||
from batchapps import JobManager
|
||||
from batchapps.exceptions import RestCallException, FileDownloadException
|
||||
|
||||
|
||||
|
||||
class BatchAppsHistory(object):
|
||||
|
||||
def __init__(self, frame, call):
|
||||
|
||||
self._log = logging.getLogger('BatchAppsMaya')
|
||||
self._call = call
|
||||
self._session = None
|
||||
|
||||
self.manager = None
|
||||
|
||||
self.index = 0
|
||||
self.per_call = 5
|
||||
self.count = 0
|
||||
self.min = True
|
||||
self.max = False
|
||||
|
||||
self.ui = HistoryUI(self, frame)
|
||||
self.jobs = []
|
||||
self.selected_job = None
|
||||
|
||||
#def start(self):
|
||||
# self._log.debug("Starting BatchAppsHistory...")
|
||||
# self.ui.refresh()
|
||||
|
||||
def configure(self, session):
|
||||
self._session = session
|
||||
self.manager = JobManager(self._session.credentials, self._session.config)
|
||||
|
||||
def get_history(self):
|
||||
self.jobs = self._call(self.manager.get_jobs, self.index, self.per_call)
|
||||
self.count = len(self.manager)
|
||||
|
||||
self.set_num_jobs()
|
||||
self.set_min_max()
|
||||
|
||||
display_jobs = []
|
||||
for index, job in enumerate(self.jobs):
|
||||
display_jobs.append(self.ui.create_job_entry(job.name, index))
|
||||
|
||||
return display_jobs
|
||||
|
||||
def set_num_jobs(self):
|
||||
if (self.index + self.per_call) > self.count:
|
||||
extra = (self.index + self.per_call) - self.count
|
||||
new_range = ((self.index + self.per_call) - extra)
|
||||
|
||||
self.ui.num_jobs = "{0} - {1} of {2}".format(
|
||||
min((self.index + 1), new_range),
|
||||
new_range,
|
||||
self.count)
|
||||
|
||||
else:
|
||||
|
||||
self.ui.num_jobs = "{0} - {1} of {2}".format(
|
||||
min((self.index + 1), self.count),
|
||||
(self.index + self.per_call),
|
||||
self.count)
|
||||
|
||||
def set_min_max(self):
|
||||
self.min = True if self.index < 1 else False
|
||||
|
||||
if (self.count % self.per_call) == 0:
|
||||
self.max = (self.index >= (self.count - self.per_call)) or (self.per_call > self.count)
|
||||
|
||||
else:
|
||||
self.max = self.index >= (self.count - self.per_call + (self.per_call - (self.count % self.per_call)))
|
||||
|
||||
self.ui.last_page = not self.max
|
||||
self.ui.first_page = not self.min
|
||||
|
||||
def show_next_jobs(self):
|
||||
self.index = min(self.index + self.per_call, self.count)
|
||||
|
||||
def show_prev_jobs(self):
|
||||
self.index = max(self.index - self.per_call, 0)
|
||||
|
||||
def show_first_jobs(self):
|
||||
self.index = 0
|
||||
|
||||
def show_last_jobs(self):
|
||||
if (self.count % self.per_call) == 0:
|
||||
self.index = self.count - self.per_call
|
||||
else:
|
||||
self.index = self.count - self.per_call + (self.per_call - (self.count % self.per_call))
|
||||
|
||||
def job_selected(self, job_ui):
|
||||
if self.selected_job and job_ui:
|
||||
self.selected_job.collapse()
|
||||
|
||||
self.selected_job = job_ui
|
||||
|
||||
try:
|
||||
if job_ui:
|
||||
self._log.info("Collecting job info...")
|
||||
job = self.jobs[job_ui.index]
|
||||
|
||||
self.selected_job.set_label("loading...")
|
||||
loading_thumb = os.path.join(os.environ["BATCHAPPS_ICONS"], "loading_preview.png")
|
||||
self.selected_job.set_thumbnail(loading_thumb, 24)
|
||||
maya.refresh()
|
||||
|
||||
self._call(job.update)
|
||||
self.selected_job.set_status(job.status)
|
||||
self.selected_job.set_progress(job.percentage)
|
||||
self.selected_job.set_submission(job.time_submitted)
|
||||
self.selected_job.set_tasks(job.number_tasks)
|
||||
self.selected_job.set_job(job.id)
|
||||
self.selected_job.set_pool(job.pool_id)
|
||||
self.selected_job.set_label(job.name)
|
||||
|
||||
maya.refresh()
|
||||
self._log.info("Updated {0}".format(job.name))
|
||||
|
||||
except Exception as exp:
|
||||
self._log.warning("Failed to update job details {0}".format(exp))
|
||||
self.selected_job.collapse()
|
||||
|
||||
def temp_name(self, prefix, size=6, chars=string.hexdigits):
|
||||
return str(prefix)+ '.' +''.join(random.choice(chars) for x in range(size))+".png"
|
||||
|
||||
def get_thumb(self):
|
||||
try:
|
||||
job = self.jobs[self.selected_job.index]
|
||||
|
||||
except (IndexError, AttributeError):
|
||||
self._log.warning("Selected job index does not match jobs list.")
|
||||
|
||||
if not self.selected_job:
|
||||
return
|
||||
|
||||
thumb = os.path.join(os.environ["BATCHAPPS_ICONS"], "no_preview.png")
|
||||
self.selected_job.set_thumbnail(thumb, 24)
|
||||
return
|
||||
|
||||
if job.status == "Complete":
|
||||
self._log.info("Getting job thumbail...")
|
||||
self.get_job_thumb(job)
|
||||
|
||||
else:
|
||||
tasks = self._call(job.get_tasks)
|
||||
self._log.info("Job not complete. Getting task thumbnails...")
|
||||
self.get_task_thumb(job, tasks)
|
||||
|
||||
def get_task_thumb(self, job, tasks):
|
||||
thumb = os.path.join(os.environ["BATCHAPPS_ICONS"], "no_preview.png")
|
||||
|
||||
if len(tasks) < 1:
|
||||
self._log.info("No completed tasks")
|
||||
self.selected_job.set_thumbnail(thumb, self.get_image_height(thumb))
|
||||
maya.refresh()
|
||||
return
|
||||
|
||||
tempDir = tempfile.gettempdir()
|
||||
for task in reversed(tasks):
|
||||
thumb_name = "{0}.{1}.*.png".format(job.id, task.id)
|
||||
existing_thumb = glob.glob(os.path.join(tempDir, thumb_name))
|
||||
if existing_thumb:
|
||||
thumb = existing_thumb[0]
|
||||
break
|
||||
|
||||
try:
|
||||
prefix = "{0}.{1}".format(job.id, task.id)
|
||||
thumb_file = self.temp_name(prefix)
|
||||
thumb = self._call(task.get_thumbnail, tempDir, thumb_file, True)
|
||||
break
|
||||
|
||||
except FileDownloadException as exp:
|
||||
self._log.info("Couldn't retrieve thumbnail: {0}".format(exp))
|
||||
|
||||
self.selected_job.set_thumbnail(thumb, self.get_image_height(thumb))
|
||||
maya.refresh()
|
||||
|
||||
def get_job_thumb(self, job):
|
||||
tempDir = tempfile.gettempdir()
|
||||
existing_thumb = glob.glob(os.path.join(tempDir, "{0}.*.png".format(job.id)))
|
||||
thumb = os.path.join(os.environ["BATCHAPPS_ICONS"], "no_preview.png")
|
||||
|
||||
if existing_thumb:
|
||||
thumb = existing_thumb[0]
|
||||
|
||||
else:
|
||||
thumb_file = self.temp_name("{0}.job".format(job.id))
|
||||
try:
|
||||
thumb = job.get_thumbnail(tempDir, thumb_file, True)
|
||||
|
||||
except (RestCallException, FileDownloadException) as exp:
|
||||
self._log.info("Couldn't retrieve thumbnail: {0}".format(exp))
|
||||
try:
|
||||
os.remove(thumb_file)
|
||||
except:
|
||||
pass
|
||||
|
||||
self.selected_job.set_thumbnail(thumb, self.get_image_height(thumb))
|
||||
maya.refresh()
|
||||
|
||||
def cancel_job(self):
|
||||
try:
|
||||
job = self.jobs[self.selected_job.index]
|
||||
|
||||
except (IndexError, AttributeError) as exp:
|
||||
self._log.warning("Selected job index does not match jobs list.")
|
||||
return
|
||||
|
||||
resp = self._call(job.cancel)
|
||||
if not resp:
|
||||
self._log.info("Job was not able to be cancelled.")
|
||||
|
||||
def download_output(self, save_file):
|
||||
|
||||
try:
|
||||
if os.path.exists(save_file):
|
||||
try:
|
||||
os.remove(save_file)
|
||||
|
||||
except Exception as e:
|
||||
self._log.error("Unable to overwrite output: {0}".format(e))
|
||||
return
|
||||
|
||||
save_dir = tempfile.mkdtemp()
|
||||
try:
|
||||
self.selected_job.change_download_label("Downloading...")
|
||||
maya.refresh()
|
||||
|
||||
job = self.jobs[self.selected_job.index]
|
||||
saved_output = self._call(job.get_output, save_dir, overwrite=True)
|
||||
|
||||
except (IndexError, AttributeError):
|
||||
self._log.error("Selected job index does not match jobs list.")
|
||||
if not self.selected_job:
|
||||
self.ui.refresh()
|
||||
return
|
||||
|
||||
except FileDownloadException as exp:
|
||||
self._log.error("Error downloading output: {0}".format(exp))
|
||||
return
|
||||
|
||||
try:
|
||||
shutil.move(saved_output, save_file)
|
||||
|
||||
except Exception as e:
|
||||
self._log.error("Unable to write file to path {0}, {1}".format(save_file, e))
|
||||
|
||||
finally:
|
||||
shutil.rmtree(save_dir)
|
||||
|
||||
finally:
|
||||
if self.selected_job:
|
||||
self.selected_job.change_download_label("Download Output")
|
||||
maya.refresh()
|
||||
|
||||
def get_image_height(self, image):
|
||||
y = 120
|
||||
with open(image, 'rb') as im:
|
||||
im.read(12)
|
||||
if im.read(4) == 'IHDR':
|
||||
x, y = struct.unpack("!LL", im.read(8))
|
||||
return y
|
|
@ -1,150 +0,0 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from api import MayaAPI as maya
|
||||
|
||||
from ui_pools import PoolsUI
|
||||
|
||||
from batchapps import PoolManager
|
||||
from batchapps.exceptions import RestCallException, FileDownloadException
|
||||
|
||||
|
||||
|
||||
class BatchAppsPools(object):
|
||||
|
||||
def __init__(self, frame, call):
|
||||
|
||||
self._log = logging.getLogger('BatchAppsMaya')
|
||||
self._call = call
|
||||
self._session = None
|
||||
|
||||
self.manager = None
|
||||
|
||||
self.ui = PoolsUI(self, frame)
|
||||
self.pools = []
|
||||
self.selected_pool = None
|
||||
|
||||
#def start(self):
|
||||
# self._log.debug("Starting BatchAppsPools...")
|
||||
# self.ui.refresh()
|
||||
|
||||
def configure(self, session):
|
||||
self._session = session
|
||||
self.manager = PoolManager(self._session.credentials, self._session.config)
|
||||
|
||||
def get_pools(self):
|
||||
self.pools = self._call(self.manager.get_pools)
|
||||
self.count = len(self.manager)
|
||||
|
||||
display_pools = []
|
||||
for index, pool in enumerate(self.pools):
|
||||
display_pools.append(self.ui.create_pool_entry(pool.id, index))
|
||||
|
||||
return display_pools
|
||||
|
||||
def pool_selected(self, pool_ui):
|
||||
if self.selected_pool and pool_ui:
|
||||
self.selected_pool.collapse()
|
||||
|
||||
self.selected_pool = pool_ui
|
||||
if pool_ui:
|
||||
self.update_pool(pool_ui.index)
|
||||
|
||||
def update_pool(self, index):
|
||||
try:
|
||||
pool = self.pools[index]
|
||||
self.selected_pool.set_label("loading...")
|
||||
maya.refresh()
|
||||
|
||||
self._call(pool.update)
|
||||
self.selected_pool.set_label(pool.id)
|
||||
self.selected_pool.set_size(pool.current_size)
|
||||
self.selected_pool.set_target(pool.target_size)
|
||||
self.selected_pool.set_type("Auto" if pool.auto else "Provisioned")
|
||||
self.selected_pool.set_state(pool.state)
|
||||
self.selected_pool.set_tasks(pool.max_tasks)
|
||||
self.selected_pool.set_allocation(pool.allocation_state)
|
||||
self.selected_pool.set_created(pool.created)
|
||||
maya.refresh()
|
||||
|
||||
except Exception as exp:
|
||||
self._log.warning(str(exp))
|
||||
self.ui.refresh()
|
||||
|
||||
def is_auto_pool(self):
|
||||
try:
|
||||
pool = self.pools[self.selected_pool.index]
|
||||
return pool.auto
|
||||
except (IndexError, TypeError, AttributeError) as exp:
|
||||
self._log.info("Unable to retrieve selected pool {0}".format(exp))
|
||||
return False
|
||||
|
||||
def delete_pool(self):
|
||||
try:
|
||||
pool = self.pools[self.selected_pool.index]
|
||||
resp = self._call(pool.delete)
|
||||
|
||||
except (IndexError, TypeError, AttributeError) as exp:
|
||||
self._log.info("Unable to retrieve selected pool {0}".format(exp))
|
||||
return
|
||||
|
||||
if not resp:
|
||||
self._log.info("Pool was unable to be deleted.")
|
||||
self.ui.refresh()
|
||||
|
||||
def get_pool_size(self):
|
||||
try:
|
||||
pool = self.pools[self.selected_pool.index]
|
||||
return int(pool.target_size)
|
||||
|
||||
except (IndexError, TypeError, AttributeError) as exp:
|
||||
self._log.info("Failed to parse pool target size {0}".format(exp))
|
||||
return 0
|
||||
|
||||
def create_pool(self, size):
|
||||
return self._call(self.manager.create, target_size=int(size))
|
||||
|
||||
def resize_pool(self, new_size):
|
||||
self._log.info("Resizing pool...")
|
||||
try:
|
||||
self.selected_pool.change_resize_label("Resizing...")
|
||||
maya.refresh()
|
||||
|
||||
pool = self.pools[self.selected_pool.index]
|
||||
self._call(pool.resize, int(new_size))
|
||||
self.selected_pool.change_resize_label("Resize Pool")
|
||||
self.selected_pool.set_target(new_size)
|
||||
maya.refresh()
|
||||
|
||||
except Exception as exp:
|
||||
self._log.info("Failed to resize pool {0}".format(exp))
|
||||
self.ui.refresh()
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
import webbrowser
|
||||
import os
|
||||
import threading
|
||||
|
||||
from ui_shared import BatchAppsUI
|
||||
|
||||
from config import BatchAppsConfig
|
||||
from submission import BatchAppsSubmission
|
||||
from history import BatchAppsHistory
|
||||
from assets import BatchAppsAssets
|
||||
from pools import BatchAppsPools
|
||||
|
||||
from api import MayaAPI as maya
|
||||
|
||||
from batchapps.exceptions import (
|
||||
InvalidConfigException,
|
||||
RestCallException,
|
||||
SessionExpiredException,
|
||||
FileDownloadException)
|
||||
|
||||
class BatchAppsSettings(object):
|
||||
|
||||
@staticmethod
|
||||
def starter():
|
||||
BatchAppsSettings()
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self._log = logging.getLogger('BatchAppsMaya')
|
||||
|
||||
self.supported_versions = [2015]
|
||||
self.version = self.check_maya_version()
|
||||
|
||||
try:
|
||||
self.frame = BatchAppsUI(self)
|
||||
|
||||
self.config = BatchAppsConfig(self.frame, self.start)
|
||||
self.submission = BatchAppsSubmission(self.frame, self.call)
|
||||
self.assets = BatchAppsAssets(self.frame, self.call)
|
||||
self.pools = BatchAppsPools(self.frame, self.call)
|
||||
self.history = BatchAppsHistory(self.frame, self.call)
|
||||
|
||||
self.start()
|
||||
|
||||
except Exception as exp:
|
||||
if (maya.window("BatchApps", q=1, exists=1)):
|
||||
maya.delete_ui("BatchApps")
|
||||
|
||||
message = "Batch Plugin Failed to Start: {0}".format(exp)
|
||||
maya.error(message)
|
||||
|
||||
def check_maya_version(self):
|
||||
|
||||
self._log.info("Checking maya version...")
|
||||
current_version = int(maya.mel("getApplicationVersionAsFloat()"))
|
||||
checked_version = self.check_version(current_version)
|
||||
|
||||
if checked_version != current_version:
|
||||
message = """You are using a version of Maya ({0}) that BatchApps does not support.
|
||||
Your job will render with the closest available version ({1}), however
|
||||
we cannot guarantee its success.""".format(current_version, checked_version)
|
||||
|
||||
maya.warning(message)
|
||||
return checked_version
|
||||
|
||||
def check_version(self, version):
|
||||
if version not in self.supported_versions:
|
||||
|
||||
if version < self.supported_versions[0]:
|
||||
return self.supported_versions[0]
|
||||
|
||||
elif version > self.supported_versions[-1]:
|
||||
return self.supported_versions[-1]
|
||||
|
||||
else:
|
||||
return version
|
||||
|
||||
def start(self):
|
||||
try:
|
||||
self._log.debug("Starting BatchAppsShared...")
|
||||
|
||||
if self.config.auth:
|
||||
self.frame.is_logged_in()
|
||||
|
||||
self.history.configure(self.config)
|
||||
self.assets.configure(self.config)
|
||||
self.pools.configure(self.config)
|
||||
|
||||
self.submission.start(self.config, self.assets, self.pools)
|
||||
|
||||
else:
|
||||
self.frame.is_logged_out()
|
||||
|
||||
except Exception as exp:
|
||||
if (maya.window("BatchApps", q=1, exists=1)):
|
||||
maya.delete_ui("BatchApps")
|
||||
|
||||
maya.error("Batch Plugin UI failed to load:\n{0}".format(exp))
|
||||
|
||||
def call(self, command, *args, **kwargs):
|
||||
try:
|
||||
return command(*args, **kwargs)
|
||||
|
||||
except SessionExpiredException as exp:
|
||||
self.frame.is_logged_out()
|
||||
raise
|
||||
|
||||
except RestCallException as exp:
|
||||
#if (maya.window("BatchApps", q=1, exists=1)):
|
||||
# maya.delete_ui("BatchApps")
|
||||
maya.error("API call failed: {0}".format(exp))
|
||||
raise
|
||||
|
||||
except FileDownloadException as exp:
|
||||
raise
|
||||
|
||||
except Exception as exp:
|
||||
if (maya.window("BatchApps", q=1, exists=1)):
|
||||
maya.delete_ui("BatchApps")
|
||||
maya.error("Error: {0}".format(exp))
|
||||
raise
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import sys
|
||||
import math
|
||||
import pkgutil
|
||||
import inspect
|
||||
import importlib
|
||||
import logging
|
||||
|
||||
from api import MayaAPI as maya
|
||||
|
||||
from ui_submission import SubmissionUI
|
||||
|
||||
import utils
|
||||
|
||||
from batchapps import JobManager
|
||||
from batchapps.exceptions import RestCallException, SessionExpiredException
|
||||
from default import BatchAppsRenderJob
|
||||
|
||||
|
||||
class BatchAppsSubmission:
|
||||
|
||||
def __init__(self, frame, call):
|
||||
|
||||
self._log = logging.getLogger('BatchAppsMaya')
|
||||
self._call = call
|
||||
|
||||
self.ui = SubmissionUI(self, frame)
|
||||
self.modules = self.collect_modules()
|
||||
self.renderer = None
|
||||
|
||||
self.job_manager = None
|
||||
self.asset_manager = None
|
||||
self.pool_manager = None
|
||||
|
||||
|
||||
def start(self, session, assets, pools):
|
||||
self._log.debug("Starting BatchAppsSubmission...")
|
||||
|
||||
self.job_manager = JobManager(session.credentials, session.config)
|
||||
self.asset_manager = assets
|
||||
self.pool_manager = pools
|
||||
|
||||
if self.renderer:
|
||||
self.renderer.delete()
|
||||
|
||||
self.configure_renderer()
|
||||
self.renderer.display(self.ui.render_module)
|
||||
self.ui.submit_enabled(self.renderer.render_enabled())
|
||||
|
||||
self.ui.is_logged_in()
|
||||
maya.refresh()
|
||||
|
||||
def collect_modules(self):
|
||||
self._log.info("Collecting modules...")
|
||||
#TODO: Get this not to conflict with existing modules, e.g. arnold
|
||||
|
||||
render_modules = []
|
||||
module_dir = os.environ['BATCHAPPS_MODULES']
|
||||
|
||||
for importer, package_name, _ in pkgutil.iter_modules([module_dir]):
|
||||
if package_name == "default":
|
||||
continue
|
||||
|
||||
module = importlib.import_module(package_name)
|
||||
for name, obj in inspect.getmembers(module, inspect.isclass):
|
||||
|
||||
if issubclass(obj, BatchAppsRenderJob):
|
||||
render_modules.append(obj())
|
||||
self._log.debug("Appended {0} to render module list.".format(render_modules[-1].label))
|
||||
|
||||
return render_modules
|
||||
|
||||
def configure_renderer(self):
|
||||
self._log.info("Configuring renderer...")
|
||||
|
||||
current_renderer = maya.mel("getAttr defaultRenderGlobals.currentRenderer")
|
||||
self._log.debug("Current renderer: {0}".format(current_renderer))
|
||||
|
||||
for module in self.modules:
|
||||
|
||||
if not hasattr(module, 'render_engine'):
|
||||
self._log.warning("Module {0} has no render engine attribute. Skipping.".format(module))
|
||||
continue
|
||||
|
||||
if module.render_engine == str(current_renderer):
|
||||
self.renderer = module
|
||||
self._log.debug("Configured renderer to {0}".format(self.renderer.render_engine))
|
||||
return
|
||||
|
||||
self.renderer = BatchAppsRenderJob()
|
||||
self._log.debug("Configured renderer to {0}".format(self.renderer.render_engine))
|
||||
|
||||
def refresh_renderer(self, layout):
|
||||
self.renderer.delete()
|
||||
self.configure_renderer()
|
||||
self.renderer.display(layout)
|
||||
self.ui.submit_enabled(self.renderer.render_enabled())
|
||||
|
||||
def submit(self):
|
||||
self.renderer.disable(False)
|
||||
self.ui.processing(False)
|
||||
maya.refresh()
|
||||
|
||||
try:
|
||||
renderer_data = self.renderer.get_jobdata()
|
||||
file_set = self.asset_manager.collect_assets(renderer_data)
|
||||
|
||||
except Exception as exp:
|
||||
maya.error(str(exp))
|
||||
self.renderer.disable(True)
|
||||
self.ui.processing(True)
|
||||
return
|
||||
|
||||
try:
|
||||
new_job = self.job_manager.create_job(self.renderer.get_title())
|
||||
pool_spec = self.ui.get_pool()
|
||||
|
||||
if pool_spec.get(1):
|
||||
self._log.info("Using auto-pool.")
|
||||
new_job.instances = int(pool_spec[1])
|
||||
|
||||
if pool_spec.get(2):
|
||||
self._log.info("Using existing pool.")
|
||||
new_job.pool = str(pool_spec[2])
|
||||
|
||||
if pool_spec.get(3):
|
||||
self._log.info("Creating new pool.")
|
||||
new_job.pool = self.pool_manager.create_pool(int(pool_spec[3]))
|
||||
|
||||
new_job.add_file_collection(file_set.get('assets', []))
|
||||
new_job.settings = file_set.get('pathmaps', "")
|
||||
|
||||
new_job.params = self.renderer.get_params()
|
||||
|
||||
failed = self._call(new_job.required_files.upload)
|
||||
if failed:
|
||||
for (asset, exp) in failed:
|
||||
self._log.warning("File {0} failed with {1}".format(asset, exp))
|
||||
maya.error("One or more files failed to upload. Submission aborted.")
|
||||
self.renderer.disable(True)
|
||||
self.ui.processing(True)
|
||||
return
|
||||
|
||||
self._log.info("Upload complete. Submitting...")
|
||||
self._log.debug(new_job._create_job_message())
|
||||
|
||||
self._call(new_job.submit)
|
||||
|
||||
except SessionExpiredException:
|
||||
pass
|
||||
|
||||
except Exception as exp:
|
||||
maya.error(str(exp))
|
||||
|
||||
finally:
|
||||
self.renderer.disable(True)
|
||||
self.ui.processing(True)
|
||||
|
||||
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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 api import MayaAPI as maya
|
||||
|
||||
import os
|
||||
import utils
|
||||
|
||||
class AssetsUI:
|
||||
|
||||
def __init__(self, base, frame):
|
||||
|
||||
self.base = base
|
||||
self.label = " Assets "
|
||||
self.ready = False
|
||||
|
||||
with utils.RowLayout(width=360) as layout:
|
||||
self.page = layout
|
||||
|
||||
with utils.ScrollLayout(v_scrollbar=3, h_scrollbar=0, height=495, width=355):
|
||||
with utils.RowLayout(row_spacing=20, width=330) as sublayout:
|
||||
self.asset_display = sublayout
|
||||
|
||||
with utils.ColumnLayout(1, col_width=(1,355)):
|
||||
with utils.ColumnLayout(2, col_width=((1, 177),(2, 177))):
|
||||
maya.button(label="Add Files", command=self.add_asset)
|
||||
maya.button(label="Add Directory", command=self.add_dir)
|
||||
|
||||
maya.button(label="Refresh", command=self.refresh)
|
||||
|
||||
frame.add_tab(self)
|
||||
self.is_logged_out()
|
||||
|
||||
def refresh(self, *args):
|
||||
self.clear_ui()
|
||||
self.base.set_assets()
|
||||
|
||||
for cat in self.base.asset_categories():
|
||||
self.list_display(cat)
|
||||
|
||||
self.user_assets = self.list_display('Additional')
|
||||
|
||||
def list_display(self, label):
|
||||
with utils.FrameLayout(label=label, collapsable=True, width=325,
|
||||
parent=self.asset_display):
|
||||
|
||||
with utils.ScrollLayout(v_scrollbar=3, h_scrollbar=0):
|
||||
with utils.RowLayout() as layout:
|
||||
|
||||
for f in self.base.get_assets(label):
|
||||
enabled = os.path.normcase(f.path) == os.path.normcase(self.base.scene)
|
||||
f.display(layout, enable=(not enabled))
|
||||
|
||||
return layout
|
||||
|
||||
def clear_ui(self):
|
||||
children = maya.row_layout(self.asset_display,
|
||||
query=True,
|
||||
childArray=True)
|
||||
if not children:
|
||||
return
|
||||
|
||||
for child in children:
|
||||
maya.delete_ui(child, control=True)
|
||||
|
||||
def add_asset(self, *args):
|
||||
cap = "Select additional rendering assets"
|
||||
fil = "All Files (*.*)"
|
||||
okCap = "Add Files"
|
||||
new_files = maya.file_select(fileFilter=fil,
|
||||
fileMode=4,
|
||||
okCaption=okCap,
|
||||
caption=cap)
|
||||
if not new_files:
|
||||
return
|
||||
|
||||
self.base.add_files(new_files, self.user_assets)
|
||||
|
||||
|
||||
def add_dir(self, *args):
|
||||
cap = "Select directory of assets"
|
||||
okCap = "Add Folder"
|
||||
new_dir = maya.file_select(fileMode=3, okCaption=okCap, caption=cap)
|
||||
if not new_dir:
|
||||
return
|
||||
|
||||
self.base.add_dir(new_dir, self.user_assets)
|
||||
|
||||
def is_logged_in(self):
|
||||
maya.col_layout(self.page, edit=True, enable=True)
|
||||
|
||||
def is_logged_out(self):
|
||||
maya.col_layout(self.page, edit=True, enable=False)
|
||||
self.ready = False
|
||||
|
||||
def prepare(self):
|
||||
if not self.ready:
|
||||
maya.refresh()
|
||||
try:
|
||||
self.refresh()
|
||||
self.is_logged_in()
|
||||
self.ready = True
|
||||
|
||||
except Exception as exp:
|
||||
maya.error("Error starting Assets UI: {0}".format(exp))
|
||||
self.is_logged_out()
|
||||
|
||||
maya.refresh()
|
|
@ -1,232 +0,0 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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 api import MayaAPI as maya
|
||||
|
||||
import utils
|
||||
|
||||
|
||||
class ConfigUI(object):
|
||||
|
||||
def __init__(self, base, frame):
|
||||
|
||||
self.base = base
|
||||
self.label = " Config "
|
||||
self.ready = False
|
||||
|
||||
with utils.RowLayout(width=360) as layout:
|
||||
self.page = layout
|
||||
|
||||
with utils.ScrollLayout(height=520, parent=self.page) as col:
|
||||
self.heading = maya.text(label="Authenticating plug-in...",
|
||||
align="center", font="boldLabelFont")
|
||||
|
||||
with utils.ColumnLayout(2, col_width=((1, 50),(2, 280)), row_spacing=(1,5),
|
||||
row_offset=((1, "top", 15),(2, "bottom", 15))) as cols:
|
||||
|
||||
maya.text(label="Status: ", align="left")
|
||||
self.auth_status = maya.text(label="", align="left")
|
||||
maya.text(label="Service: ", align="left")
|
||||
self._endpoint = maya.text_field(width=280,
|
||||
height=25,
|
||||
enable=True,
|
||||
changeCommand=self.changes_detected)
|
||||
maya.text(label="Logging: ", align="left")
|
||||
with utils.Dropdown(self.set_logging) as log_settings:
|
||||
self._logging = log_settings
|
||||
self._logging.add_item("Debug")
|
||||
self._logging.add_item("Info")
|
||||
self._logging.add_item("Warning")
|
||||
self._logging.add_item("Error")
|
||||
|
||||
maya.text(label="")
|
||||
|
||||
box_label = "Attended Configuration Settings"
|
||||
with utils.FrameLayout(label=box_label, width=360, collapsable=True, parent=col) as box:
|
||||
|
||||
with utils.ColumnLayout(2, col_width=((1, 120),(2, 200)), row_spacing=(1,10),
|
||||
row_offset=((1, "top", 10),(3, "bottom", 15))) as cols:
|
||||
|
||||
maya.text(label="Client ID ", align="right")
|
||||
self._client = maya.text_field(width=200,
|
||||
height=25,
|
||||
enable=True,
|
||||
changeCommand=self.changes_detected)
|
||||
|
||||
maya.text(label="Tenant ", align="right")
|
||||
self._tenant = maya.text_field(width=200,
|
||||
height=25,
|
||||
enable=True,
|
||||
changeCommand=self.changes_detected)
|
||||
|
||||
maya.text(label="Redirect URI ", align="right")
|
||||
self._redirect = maya.text_field(width=200,
|
||||
height=25,
|
||||
enable=True,
|
||||
changeCommand=self.changes_detected)
|
||||
|
||||
|
||||
box_label = "Unattended Configuration Settings"
|
||||
with utils.FrameLayout(label=box_label, width=360, collapsable=True, parent=col) as box:
|
||||
|
||||
with utils.ColumnLayout(2, col_width=((1, 120),(2, 200)), row_spacing=(1,10),
|
||||
row_offset=((1, "top", 10),(2, "bottom", 15))) as cols:
|
||||
|
||||
maya.text(label="Unattended Account ", align="right")
|
||||
self._account = maya.text_field(width=200,
|
||||
height=25,
|
||||
enable=True,
|
||||
changeCommand=self.changes_detected)
|
||||
|
||||
maya.text(label="Unattended Key ", align="right")
|
||||
self._key = maya.text_field(width=200,
|
||||
height=25,
|
||||
enable=True,
|
||||
changeCommand=self.changes_detected)
|
||||
|
||||
|
||||
with utils.ColumnLayout(2, col_width=((1, 180),(2, 180)), row_spacing=(1,10),
|
||||
parent=self.page) as cols:
|
||||
|
||||
self._save_cfg = maya.button(label="Save Changes",
|
||||
command=self.save_changes,
|
||||
width=180,
|
||||
enable=False)
|
||||
|
||||
|
||||
self._authenticate = maya.button(label="Authenticate",
|
||||
command=self.authenticate,
|
||||
width=180,
|
||||
enable=True)
|
||||
|
||||
frame.add_tab(self)
|
||||
maya.row_layout(self.page, edit=True, enable=False)
|
||||
maya.refresh()
|
||||
|
||||
@property
|
||||
def endpoint(self):
|
||||
return maya.text_field(self._endpoint, query=True, text=True)
|
||||
|
||||
@endpoint.setter
|
||||
def endpoint(self, value):
|
||||
maya.text_field(self._endpoint, edit=True, text=str(value))
|
||||
|
||||
|
||||
@property
|
||||
def account(self):
|
||||
return maya.text_field(self._account, query=True, text=True)
|
||||
|
||||
@account.setter
|
||||
def account(self, value):
|
||||
maya.text_field(self._account, edit=True, text=str(value))
|
||||
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
return maya.text_field(self._key, query=True, text=True)
|
||||
|
||||
@key.setter
|
||||
def key(self, value):
|
||||
maya.text_field(self._key, edit=True, text=str(value))
|
||||
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
return maya.text_field(self._client, query=True, text=True)
|
||||
|
||||
@client.setter
|
||||
def client(self, value):
|
||||
maya.text_field(self._client, edit=True, text=str(value))
|
||||
|
||||
|
||||
@property
|
||||
def tenant(self):
|
||||
return maya.text_field(self._tenant, query=True, text=True)
|
||||
|
||||
@tenant.setter
|
||||
def tenant(self, value):
|
||||
maya.text_field(self._tenant, edit=True, text=str(value))
|
||||
|
||||
|
||||
@property
|
||||
def redirect(self):
|
||||
return maya.text_field(self._redirect, query=True, text=True)
|
||||
|
||||
@redirect.setter
|
||||
def redirect(self, value):
|
||||
maya.text_field(self._redirect, edit=True, text=str(value))
|
||||
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return maya.text(self.auth_status, query=True, label=True)[8:]
|
||||
|
||||
@status.setter
|
||||
def status(self, value):
|
||||
maya.text(self.auth_status, edit=True, label=value)
|
||||
|
||||
|
||||
@property
|
||||
def logging(self):
|
||||
return self._logging.selected() * 10
|
||||
|
||||
@logging.setter
|
||||
def logging(self, value):
|
||||
self._logging.select(int(value)/10)
|
||||
|
||||
def is_logged_in(self):
|
||||
maya.text(self.heading, edit=True, label="Authentication Configuration")
|
||||
maya.row_layout(self.page, edit=True, enable=True)
|
||||
|
||||
def is_logged_out(self):
|
||||
maya.text(self.heading, edit=True, label="Authentication Configuration")
|
||||
maya.row_layout(self.page, edit=True, enable=True)
|
||||
|
||||
def prepare(self):
|
||||
pass
|
||||
|
||||
def changes_detected(self, *args):
|
||||
maya.button(self._save_cfg, edit=True, enable=True)
|
||||
|
||||
def save_changes(self, *args):
|
||||
self.base.save_changes()
|
||||
maya.button(self._save_cfg, edit=True, enable=False)
|
||||
|
||||
def set_logging(self, level):
|
||||
self.changes_detected()
|
||||
self.base.set_logging(level.lower())
|
||||
|
||||
def set_authenticate(self, auth):
|
||||
if auth:
|
||||
maya.button(self._authenticate, edit=True, label="Refresh Authentication")
|
||||
else:
|
||||
maya.button(self._authenticate, edit=True, label="Authenticate")
|
||||
|
||||
def authenticate(self, *args):
|
||||
self.base.authenticate()
|
||||
|
|
@ -1,312 +0,0 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
|
||||
from api import MayaAPI as maya
|
||||
import utils
|
||||
|
||||
|
||||
class HistoryUI(object):
|
||||
|
||||
def __init__(self, base, frame):
|
||||
|
||||
self.base = base
|
||||
self.label = " Jobs "
|
||||
self.ready = False
|
||||
|
||||
self.jobs_displayed = []
|
||||
|
||||
with utils.RowLayout(width=360) as layout:
|
||||
self.page = layout
|
||||
|
||||
with utils.ColumnLayout(1, col_width=(1,360)) as col:
|
||||
self.total = maya.text(label="", font="boldLabelFont")
|
||||
self.paging_display()
|
||||
|
||||
with utils.ScrollLayout(v_scrollbar=3, h_scrollbar=0, width=355, height=478):
|
||||
|
||||
with utils.ColumnLayout(1, col_width=(1,345)) as col:
|
||||
if not self.jobs_displayed:
|
||||
|
||||
self.empty_jobs = maya.text(
|
||||
label="Loading job data...",
|
||||
align='left',
|
||||
font="boldLabelFont")
|
||||
|
||||
self.jobs_layout = col
|
||||
|
||||
with utils.ColumnLayout(1, col_width=(1,355)) as col:
|
||||
maya.button(label="Refresh", command=self.refresh)
|
||||
|
||||
frame.add_tab(self)
|
||||
self.is_logged_out()
|
||||
|
||||
@property
|
||||
def num_jobs(self):
|
||||
maya.text(self.total, query=True, label=True)
|
||||
|
||||
@num_jobs.setter
|
||||
def num_jobs(self, value):
|
||||
maya.text(self.total, edit=True, label=value)
|
||||
|
||||
@property
|
||||
def last_page(self):
|
||||
return maya.button(self.next_btn, query=True, enable=True)
|
||||
|
||||
@last_page.setter
|
||||
def last_page(self, value):
|
||||
maya.button(self.next_btn, edit=True, enable=value)
|
||||
maya.button(self.last_btn, edit=True, enable=value)
|
||||
|
||||
@property
|
||||
def first_page(self):
|
||||
return maya.button(self.prev_btn, query=True, enable=True)
|
||||
|
||||
@first_page.setter
|
||||
def first_page(self, value):
|
||||
maya.button(self.first_btn, edit=True, enable=value)
|
||||
maya.button(self.prev_btn, edit=True, enable=value)
|
||||
|
||||
def is_logged_in(self):
|
||||
maya.row_layout(self.page, edit=True, enable=True)
|
||||
|
||||
def is_logged_out(self):
|
||||
maya.row_layout(self.page, edit=True, enable=False)
|
||||
self.ready = False
|
||||
|
||||
def prepare(self):
|
||||
if not self.ready:
|
||||
maya.refresh()
|
||||
try:
|
||||
self.refresh()
|
||||
self.is_logged_in()
|
||||
self.ready = True
|
||||
|
||||
except Exception as exp:
|
||||
maya.error("Error starting Assets UI: {0}".format(exp))
|
||||
self.is_logged_out()
|
||||
|
||||
maya.refresh()
|
||||
|
||||
def paging_display(self):
|
||||
with utils.ColumnLayout(2, col_width=((1, 180),(2, 180))):
|
||||
|
||||
with utils.ColumnLayout(2, col_width=((1, 85),(2, 85))) as prev:
|
||||
self.first_btn = maya.button(label="<<", align="center", command=self.show_first_jobs)
|
||||
self.prev_btn = maya.button(label="<", align="center", command=self.show_prev_jobs)
|
||||
|
||||
with utils.ColumnLayout(2, col_width=((1, 85),(2, 85))) as next:
|
||||
self.next_btn = maya.button(label=">", align="center", command=self.show_next_jobs)
|
||||
self.last_btn = maya.button(label=">>", align="center", command=self.show_last_jobs)
|
||||
|
||||
|
||||
def refresh(self, *k):
|
||||
try:
|
||||
maya.delete_ui(self.empty_jobs)
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
self.base.job_selected(None)
|
||||
for i in self.jobs_displayed:
|
||||
i.remove()
|
||||
|
||||
self.jobs_displayed = self.base.get_history()
|
||||
if not self.jobs_displayed:
|
||||
self.empty_jobs = maya.text(label="No jobs to display", parent=self.jobs_layout)
|
||||
|
||||
|
||||
def create_job_entry(self, name, index):
|
||||
frame = maya.frame_layout(label=name,
|
||||
collapsable=True,
|
||||
collapse=True,
|
||||
borderStyle='in',
|
||||
width=345,
|
||||
visible=True,
|
||||
parent=self.jobs_layout)
|
||||
|
||||
return BatchAppsJobInfo(self.base, index, frame)
|
||||
|
||||
def show_next_jobs(self, *k):
|
||||
self.base.show_next_jobs()
|
||||
self.refresh()
|
||||
|
||||
def show_prev_jobs(self, *k):
|
||||
self.base.show_prev_jobs()
|
||||
self.refresh()
|
||||
|
||||
def show_first_jobs(self, *k):
|
||||
self.base.show_first_jobs()
|
||||
self.refresh()
|
||||
|
||||
def show_last_jobs(self, *k):
|
||||
self.base.show_last_jobs()
|
||||
self.refresh()
|
||||
|
||||
|
||||
class BatchAppsJobInfo:
|
||||
|
||||
def __init__(self, base, index, layout):
|
||||
|
||||
self.base = base
|
||||
self.index = index
|
||||
self.layout = layout
|
||||
|
||||
maya.frame_layout(layout,
|
||||
edit=True,
|
||||
collapseCommand=self.on_collapse,
|
||||
expandCommand=self.on_expand)
|
||||
|
||||
self.listbox = maya.col_layout(numberOfColumns=2,
|
||||
columnWidth=((1, 100),
|
||||
(2, 200)),
|
||||
rowSpacing=((1, 5),),
|
||||
rowOffset=((1, "top", 5),(1, "bottom", 5),),
|
||||
parent=self.layout)
|
||||
self.content = []
|
||||
|
||||
|
||||
def set_label(self, value):
|
||||
maya.frame_layout(self.layout, edit=True, label=value)
|
||||
|
||||
def set_status(self, value):
|
||||
maya.text(self._status, edit=True, label=" {0}".format(value))
|
||||
|
||||
def get_status(self):
|
||||
return maya.text(self._status, query=True, label=True).lstrip()
|
||||
|
||||
def set_progress(self, value):
|
||||
maya.text(self._progress, edit=True, label=" {0}%".format(value))
|
||||
|
||||
def set_submission(self, value):
|
||||
datetime = value.split('T')
|
||||
datetime[1] = datetime[1].split('.')[0]
|
||||
label = ' '.join(datetime)
|
||||
maya.text(self._submission, edit=True, label=" {0}".format(label))
|
||||
|
||||
def set_tasks(self, value):
|
||||
maya.text(self._tasks, edit=True, label=" {0}".format(value))
|
||||
|
||||
def set_job(self, value):
|
||||
maya.text(self._job, edit=True, label=" {0}".format(value))
|
||||
|
||||
def set_pool(self, value):
|
||||
maya.text(self._pool, edit=True, label=" {0}".format(value))
|
||||
|
||||
def on_expand(self):
|
||||
|
||||
self.content.append(maya.text(label=""))
|
||||
self.content.append(maya.text(label="Preview: ", parent=self.listbox, align="right"))
|
||||
|
||||
self._thumbnail = maya.image(parent=self.listbox)
|
||||
self.content.append(self._thumbnail)
|
||||
|
||||
self._status = self.display_info("Status: ")
|
||||
self._progress = self.display_info("Progress: ")
|
||||
self._submission = self.display_info("Submission time: ")
|
||||
self._tasks = self.display_info("Task Count: ")
|
||||
self._job = self.display_info("Job ID: ")
|
||||
self._pool = self.display_info("Pool: ")
|
||||
|
||||
with utils.RowLayout(row_spacing=5, col_attach=("both",20), parent=self.layout) as buttons:
|
||||
self.button_layout = buttons
|
||||
self.content.append(self.button_layout)
|
||||
|
||||
self.base.job_selected(self)
|
||||
maya.execute(self.base.get_thumb)
|
||||
|
||||
self.complete_job() if (self.get_status() == "Complete") else self.incomplete_job()
|
||||
self.content.append(maya.text(label="", parent=self.layout))
|
||||
|
||||
maya.refresh()
|
||||
|
||||
def on_collapse(self):
|
||||
self.base.job_selected(None)
|
||||
maya.parent(self.listbox)
|
||||
for element in self.content:
|
||||
maya.delete_ui(element, control=True)
|
||||
|
||||
self.content = []
|
||||
|
||||
def collapse(self):
|
||||
maya.frame_layout(self.layout, edit=True, collapse=True)
|
||||
self.on_collapse()
|
||||
maya.refresh()
|
||||
|
||||
def remove(self):
|
||||
maya.delete_ui(self.layout, control=True)
|
||||
|
||||
def complete_job(self):
|
||||
self.download_button = self.button("Download Output", self.download_output)
|
||||
self.cancel_button = None
|
||||
return [self.download_output]
|
||||
|
||||
def incomplete_job(self):
|
||||
self.download_button = None
|
||||
|
||||
if self.get_status() in ["InProgress", "NotStarted"]:
|
||||
self.cancel_button = self.button("Cancel Job", self.cancel_job)
|
||||
return [self.cancel_button]
|
||||
else:
|
||||
return []
|
||||
|
||||
def display_info(self, label):
|
||||
self.content.append(maya.text(label=label, parent=self.listbox, align="right"))
|
||||
input = maya.text(align="left", label="", parent=self.listbox)
|
||||
self.content.append(input)
|
||||
return input
|
||||
|
||||
def button(self, label, command):
|
||||
return maya.button(label=label,
|
||||
width=150,
|
||||
parent=self.button_layout,
|
||||
align="center",
|
||||
command=command)
|
||||
|
||||
def set_thumbnail(self, thumb, height):
|
||||
maya.image(self._thumbnail,
|
||||
edit=True,
|
||||
image=thumb,
|
||||
height=height)
|
||||
|
||||
def change_download_label(self, label):
|
||||
maya.button(self.download_button, edit=True, label="{0}".format(label))
|
||||
|
||||
def download_output(self, *args):
|
||||
save_file = maya.file_select(fileFilter="Zip Archive (*.zip)",
|
||||
fileMode=0,
|
||||
okCaption="Save to file",
|
||||
caption="Choose an output")
|
||||
if save_file is None:
|
||||
return
|
||||
|
||||
save_file = os.path.normpath(save_file[0])
|
||||
self.base.download_output(save_file)
|
||||
|
||||
def cancel_job(self, *args):
|
||||
self.base.cancel_job()
|
|
@ -1,243 +0,0 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
|
||||
from api import MayaAPI as maya
|
||||
|
||||
import utils
|
||||
|
||||
class PoolsUI(object):
|
||||
|
||||
def __init__(self, base, frame):
|
||||
|
||||
self.base = base
|
||||
self.label = " Pools "
|
||||
self.ready = False
|
||||
|
||||
self.pools_displayed = []
|
||||
|
||||
with utils.RowLayout(width=360) as layout:
|
||||
self.page = layout
|
||||
|
||||
with utils.ScrollLayout(v_scrollbar=3, h_scrollbar=0, width=355, height=520) as scroll:
|
||||
|
||||
with utils.RowLayout(row_spacing=20) as sublayout:
|
||||
|
||||
if not self.pools_displayed:
|
||||
self.empty_pools = maya.text(
|
||||
label="Loading pool data...",
|
||||
font="boldLabelFont",
|
||||
parent=sublayout)
|
||||
|
||||
self.pools_layout = sublayout
|
||||
|
||||
with utils.ColumnLayout(1, col_width=(1,355)) as col:
|
||||
maya.button(label="Refresh", command=self.refresh)
|
||||
|
||||
frame.add_tab(self)
|
||||
self.is_logged_out()
|
||||
|
||||
def is_logged_in(self):
|
||||
maya.row_layout(self.page, edit=True, enable=True)
|
||||
|
||||
def is_logged_out(self):
|
||||
maya.row_layout(self.page, edit=True, enable=False)
|
||||
self.ready = False
|
||||
|
||||
def prepare(self):
|
||||
if not self.ready:
|
||||
maya.refresh()
|
||||
try:
|
||||
self.refresh()
|
||||
self.is_logged_in()
|
||||
self.ready = True
|
||||
|
||||
except Exception as exp:
|
||||
maya.error("Error starting Pools UI: {0}".format(exp))
|
||||
self.is_logged_out()
|
||||
|
||||
maya.refresh()
|
||||
|
||||
def refresh(self, *k):
|
||||
try:
|
||||
maya.delete_ui(self.empty_pools)
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
self.base.pool_selected(None)
|
||||
for i in self.pools_displayed:
|
||||
i.remove()
|
||||
|
||||
self.pools_displayed = self.base.get_pools()
|
||||
if not self.pools_displayed:
|
||||
self.empty_pools = maya.text(label="No pools to display",
|
||||
parent=self.pools_layout)
|
||||
|
||||
|
||||
def create_pool_entry(self, name, index):
|
||||
frame = maya.frame_layout(label=name,
|
||||
collapsable=True,
|
||||
collapse=True,
|
||||
borderStyle='in',
|
||||
width=345,
|
||||
visible=True,
|
||||
parent=self.pools_layout)
|
||||
|
||||
return BatchAppsPoolInfo(self.base, index, frame)
|
||||
|
||||
|
||||
class BatchAppsPoolInfo:
|
||||
|
||||
def __init__(self, base, index, layout):
|
||||
|
||||
self.base = base
|
||||
self.index = index
|
||||
self.layout = layout
|
||||
|
||||
maya.frame_layout(
|
||||
layout,
|
||||
edit=True,
|
||||
collapseCommand=self.on_collapse,
|
||||
expandCommand=self.on_expand)
|
||||
|
||||
|
||||
self.listbox = maya.col_layout(
|
||||
numberOfColumns=2,
|
||||
columnWidth=((1, 100),
|
||||
(2, 200)),
|
||||
rowSpacing=((1, 5),),
|
||||
rowOffset=((1, "top", 5),(1, "bottom", 5),),
|
||||
parent=self.layout)
|
||||
|
||||
self.content = []
|
||||
|
||||
|
||||
def set_label(self, value):
|
||||
maya.frame_layout(self.layout, edit=True, label=value)
|
||||
|
||||
def set_type(self, value):
|
||||
maya.text(self._type, edit=True, label=" {0}".format(value))
|
||||
|
||||
def set_size(self, value):
|
||||
maya.text(self._size, edit=True, label=" {0}".format(value))
|
||||
|
||||
def set_target(self, value):
|
||||
maya.text(self._target, edit=True, label=" {0}".format(value))
|
||||
|
||||
def set_created(self, value):
|
||||
datetime = value.split('T')
|
||||
datetime[1] = datetime[1].split('.')[0]
|
||||
label = ' '.join(datetime)
|
||||
maya.text(self._created, edit=True, label=" {0}".format(label))
|
||||
|
||||
def set_state(self, value):
|
||||
maya.text(self._state, edit=True, label=" {0}".format(value))
|
||||
|
||||
def set_tasks(self, value):
|
||||
maya.text(self._tasks, edit=True, label=" {0}".format(value))
|
||||
|
||||
def set_allocation(self, value):
|
||||
maya.text(self._allocation, edit=True, label=" {0}".format(value))
|
||||
|
||||
def change_resize_label(self, label, enable=True):
|
||||
maya.button(self.resize_button, edit=True, label="{0}".format(label), enable=enable)
|
||||
|
||||
def on_expand(self):
|
||||
|
||||
self._type = self.display_info("Type: ")
|
||||
self._size = self.display_info("Current Size: ")
|
||||
self._target = self.display_info("Target Size: ")
|
||||
self._created = self.display_info("Created: ")
|
||||
self._state = self.display_info("State: ")
|
||||
self._tasks = self.display_info("Tasks per VM: ")
|
||||
self._allocation = self.display_info("Allocation State: ")
|
||||
|
||||
self.base.pool_selected(self)
|
||||
|
||||
auto = self.base.is_auto_pool()
|
||||
if not auto:
|
||||
|
||||
self.resize_button = self.button("Resize Pool", self.resize_pool, self.listbox)
|
||||
self.resize_int = maya.int_slider(
|
||||
value=self.base.get_pool_size(),
|
||||
minValue=1,
|
||||
maxValue=1000,
|
||||
fieldMinValue=1,
|
||||
fieldMaxValue=100,
|
||||
field=True,
|
||||
width=200,
|
||||
parent=self.listbox,
|
||||
annotation="Number of instances to work in pool.")
|
||||
self.content.extend([self.resize_button, self.resize_int])
|
||||
|
||||
self.content.append(self.button("Delete Pool", self.delete_pool, self.layout))
|
||||
|
||||
maya.refresh()
|
||||
|
||||
def on_collapse(self):
|
||||
self.base.pool_selected(None)
|
||||
maya.parent(self.listbox)
|
||||
for element in self.content:
|
||||
maya.delete_ui(element, control=True)
|
||||
|
||||
self.content = []
|
||||
|
||||
def collapse(self):
|
||||
maya.frame_layout(self.layout, edit=True, collapse=True)
|
||||
self.on_collapse()
|
||||
maya.refresh()
|
||||
|
||||
def remove(self):
|
||||
maya.delete_ui(self.layout, control=True)
|
||||
|
||||
def display_info(self, label):
|
||||
self.content.append(maya.text(label=label, parent=self.listbox, align="right"))
|
||||
input = maya.text(align="left", label="", parent=self.listbox)
|
||||
self.content.append(input)
|
||||
return input
|
||||
|
||||
def button(self, label, command, p):
|
||||
return maya.button(label=label,
|
||||
parent=p,
|
||||
align="center",
|
||||
command=command)
|
||||
|
||||
def delete_pool(self, *args):
|
||||
self.base.delete_pool()
|
||||
#self.base.refresh()
|
||||
|
||||
def resize_pool(self, *args):
|
||||
self.change_resize_label("Resizing...", enable=False)
|
||||
maya.refresh()
|
||||
|
||||
resize = maya.int_slider(self.resize_int, query=True, value=True)
|
||||
self.base.resize_pool(resize)
|
||||
self.base.update_pool(self.index)
|
||||
|
||||
self.change_resize_label("Resize Pool")
|
|
@ -1,157 +0,0 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import utils
|
||||
|
||||
from api import MayaAPI as maya
|
||||
|
||||
|
||||
class SubmissionUI(object):
|
||||
|
||||
def __init__(self, base, frame):
|
||||
|
||||
self.base = base
|
||||
self.label = " Submit "
|
||||
|
||||
with utils.RowLayout(width=360) as layout:
|
||||
self.page = layout
|
||||
|
||||
with utils.ScrollLayout(height=497, parent=self.page) as col:
|
||||
box_label = "Pool Settings"
|
||||
|
||||
with utils.FrameLayout(label=box_label, width=360, collapsable=True, parent=col) as box:
|
||||
|
||||
self.pool_settings = maya.col_layout(
|
||||
numberOfColumns=2,
|
||||
columnWidth=((1, 80),
|
||||
(2, 200)),
|
||||
parent=box,
|
||||
rowSpacing=(1, 10),
|
||||
rowOffset=((1, "top", 20),
|
||||
(2, "bottom", 20)))
|
||||
|
||||
maya.text(label="Pools: ", align="right")
|
||||
self.select_pool_type = maya.radio_group(
|
||||
labelArray3=("Auto provision a pool for this job",
|
||||
"Reuse an existing persistent pool",
|
||||
"Create a new persistent pool"),
|
||||
numberOfRadioButtons=3,
|
||||
select=1,
|
||||
vertical=True,
|
||||
onCommand1=self.set_pool_instances,
|
||||
onCommand2=self.set_pool_reuse,
|
||||
onCommand3=self.set_pool_instances)
|
||||
|
||||
self.pool_text = maya.text(label="Instances: ", align="right")
|
||||
self.control = maya.int_slider(
|
||||
field=True, value=3,
|
||||
minValue=1,
|
||||
maxValue=1000,
|
||||
fieldMinValue=1,
|
||||
fieldMaxValue=1000,
|
||||
annotation="Number of instances in pool")
|
||||
|
||||
box_label = "Render Settings"
|
||||
with utils.FrameLayout(label=box_label, width=360, collapsable=True, parent=col) as box:
|
||||
self.render_module = box
|
||||
|
||||
with utils.ColumnLayout(1, col_width=(1,355)) as col:
|
||||
self.submit_button = maya.button(label="Submit Job", command=self.submit)
|
||||
self.refresh_button = maya.button(label="Refresh", command=self.refresh)
|
||||
|
||||
frame.add_tab(self)
|
||||
|
||||
def is_logged_in(self):
|
||||
maya.row_layout(self.page, edit=True, enable=True)
|
||||
|
||||
def is_logged_out(self):
|
||||
maya.row_layout(self.page, edit=True, enable=False)
|
||||
|
||||
def prepare(self):
|
||||
pass
|
||||
|
||||
def refresh(self, *k):
|
||||
self.base.refresh_renderer(self.render_module)
|
||||
maya.refresh()
|
||||
|
||||
def processing(self, enabled):
|
||||
if enabled:
|
||||
maya.button(self.submit_button, edit=True, label="Submit Job", enable=True)
|
||||
maya.button(self.refresh_button, edit=True, enable=True)
|
||||
else:
|
||||
maya.button(self.submit_button, edit=True, label="Submitting...", enable=False)
|
||||
maya.button(self.refresh_button, edit=True, enable=False)
|
||||
|
||||
def submit(self, *k):
|
||||
self.base.submit()
|
||||
|
||||
def submit_enabled(self, enable):
|
||||
maya.button(self.submit_button, edit=True, enable=enable)
|
||||
|
||||
def get_pool(self):
|
||||
pool_type = maya.radio_group(self.select_pool_type, query=True, select=True)
|
||||
if pool_type == 2:
|
||||
details = str(maya.text_field(self.control, query=True, text=True))
|
||||
else:
|
||||
details = int(maya.int_slider(self.control, query=True, value=True))
|
||||
return {pool_type: details}
|
||||
|
||||
def set_pool_instances(self, *k):
|
||||
maya.delete_ui(self.pool_text)
|
||||
maya.delete_ui(self.control)
|
||||
|
||||
self.pool_text = maya.text(
|
||||
label="Instances: ",
|
||||
align="right",
|
||||
parent=self.pool_settings)
|
||||
|
||||
self.control = maya.int_slider(
|
||||
field=True,
|
||||
value=3,
|
||||
minValue=1,
|
||||
maxValue=1000,
|
||||
fieldMinValue=1,
|
||||
fieldMaxValue=1000,
|
||||
parent=self.pool_settings,
|
||||
annotation="Number of instances in pool")
|
||||
|
||||
def set_pool_reuse(self, *k):
|
||||
maya.delete_ui(self.pool_text)
|
||||
maya.delete_ui(self.control)
|
||||
|
||||
self.pool_text = maya.text(
|
||||
label="Pool ID: ",
|
||||
align="right",
|
||||
parent=self.pool_settings)
|
||||
|
||||
self.control = maya.text_field(
|
||||
parent=self.pool_settings,
|
||||
annotation="Use an existing persistent pool ID",
|
||||
editable=True)
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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 api import MayaAPI as maya
|
||||
|
||||
class Layout(object):
|
||||
|
||||
def __init__(self, form, **kwargs):
|
||||
|
||||
self.form = form
|
||||
|
||||
settings = {}
|
||||
|
||||
if kwargs.get("width"):
|
||||
settings["width"] = kwargs["width"]
|
||||
|
||||
if kwargs.get("height"):
|
||||
settings["height"] = kwargs["height"]
|
||||
|
||||
if kwargs.get("parent"):
|
||||
settings["parent"] = kwargs["parent"]
|
||||
|
||||
if kwargs.get("row_spacing"):
|
||||
settings["rowSpacing"] = kwargs["row_spacing"]
|
||||
|
||||
if kwargs.get("col_attach"):
|
||||
settings["columnAttach"] = kwargs["col_attach"]
|
||||
|
||||
if kwargs.get("layout"):
|
||||
settings.update(kwargs["layout"])
|
||||
|
||||
self.layout = self.form(**settings)
|
||||
|
||||
|
||||
def __enter__(self):
|
||||
return self.layout
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
#TODO: Exception handling should go in here
|
||||
maya.parent()
|
||||
|
||||
|
||||
class RowLayout(Layout):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(RowLayout, self).__init__(maya.row_layout, **kwargs)
|
||||
|
||||
|
||||
class FrameLayout(Layout):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
settings = {}
|
||||
|
||||
if kwargs.get("label"):
|
||||
settings["label"] = kwargs["label"]
|
||||
|
||||
if kwargs.get("collapsable"):
|
||||
settings["collapsable"] = kwargs["collapsable"]
|
||||
|
||||
super(FrameLayout, self).__init__(maya.frame_layout, layout=settings, **kwargs)
|
||||
|
||||
|
||||
class ColumnLayout(Layout):
|
||||
|
||||
def __init__(self, columns, **kwargs):
|
||||
|
||||
settings = {"numberOfColumns": columns}
|
||||
|
||||
if kwargs.get("col_width"):
|
||||
settings["columnWidth"] = kwargs["col_width"]
|
||||
|
||||
if kwargs.get("row_offset"):
|
||||
settings["rowOffset"] = kwargs["row_offset"]
|
||||
|
||||
if kwargs.get("row_height"):
|
||||
settings["rowHeight"] = kwargs["row_height"]
|
||||
|
||||
super(ColumnLayout, self).__init__(maya.col_layout, layout=settings, **kwargs)
|
||||
|
||||
|
||||
class ScrollLayout(Layout):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
settings = {"horizontalScrollBarThickness": 0,
|
||||
"verticalScrollBarThickness": 3}
|
||||
|
||||
super(ScrollLayout, self).__init__(maya.scroll_layout, layout=settings, **kwargs)
|
||||
|
||||
|
||||
class Dropdown(object):
|
||||
|
||||
def __init__(self, command):
|
||||
|
||||
self.menu = maya.menu(changeCommand=command)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
maya.parent()
|
||||
|
||||
def add_item(self, item):
|
||||
maya.menu_option(label=item, parent=self.menu)
|
||||
|
||||
def selected(self):
|
||||
return int(maya.menu(self.menu, query=True, select=True))
|
||||
|
||||
def select(self, value):
|
||||
maya.menu(self.menu, edit=True, select=int(value))
|
|
@ -1,146 +0,0 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plug-in
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
#
|
||||
# Maya Sample Dependency Install Script
|
||||
#
|
||||
# This script is designed to be run with Maya to download the required
|
||||
# packages to Maya's bundled python environment.
|
||||
# This script is experimental and is provided as a convenience only.
|
||||
# The script has currently only been tested under Windows OS.
|
||||
#
|
||||
# The script downloads and unpacks the following dependencies:
|
||||
# - Azure Batch Apps Python Client v0.4.0
|
||||
# - Keyring v4.0
|
||||
# - OAuthLib v0.7.2
|
||||
# - Requeses-OAuthLib v0.4.2
|
||||
# - Requests v2.2.1
|
||||
#
|
||||
# To run the script, run the following command as an administrator:
|
||||
#
|
||||
# >> mayapy.exe dependency_check.py
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import urllib
|
||||
import os
|
||||
import tarfile
|
||||
import shutil
|
||||
import sys
|
||||
import zipfile
|
||||
import warnings
|
||||
import importlib
|
||||
import tempfile
|
||||
|
||||
warnings.simplefilter('ignore')
|
||||
|
||||
TEMP_DIR = os.path.join(tempfile.gettempdir(), "batchapps_maya_install")
|
||||
INSTALL_DIR = os.path.join(sys.prefix, "lib", "site-packages")
|
||||
LIBS = [
|
||||
{"lib":"oauthlib", "ver":"0.7.2", "mod":"oauthlib", "ext":"tar.gz"},
|
||||
{"lib":"requests-oauthlib", "ver":"0.4.2", "mod":"requests_oauthlib", "ext":"tar.gz"},
|
||||
{"lib":"requests", "ver":"2.2.1", "mod":"requests", "ext":"tar.gz"},
|
||||
{"lib":"keyring", "ver":"4.0", "mod":"keyring", "ext":"zip"},
|
||||
{"lib":"azure-batch-apps", "ver":"0.3.0", "mod":"batchapps", "ext":"tar.gz"}
|
||||
]
|
||||
|
||||
def unpack_tar(lib_full, lib_dir, lib_module):
|
||||
|
||||
with tarfile.open(lib_full, 'r:gz') as tfile:
|
||||
members = tfile.getmembers()
|
||||
mod_path = "{0}/{1}/".format(lib_dir, lib_module)
|
||||
subset = [m for m in members if m.path.startswith(mod_path)]
|
||||
tfile.extractall(path=TEMP_DIR, members=subset)
|
||||
|
||||
def unpack_zip(lib_full, lib_dir, lib_module):
|
||||
|
||||
with zipfile.ZipFile(lib_full) as zfile:
|
||||
members = zfile.infolist()
|
||||
mod_path = "{0}/{1}/".format(lib_dir, lib_module)
|
||||
subset = [m for m in members if m.filename.startswith(mod_path)]
|
||||
zfile.extractall(path=TEMP_DIR, members=subset)
|
||||
|
||||
def download_lib(lib_name, lib_version, lib_module, lib_ext):
|
||||
|
||||
lib_dir = "{0}-{1}".format(lib_name, lib_version)
|
||||
lib_file = "{0}.{1}".format(lib_dir, lib_ext)
|
||||
lib_full = os.path.join(TEMP_DIR, lib_file)
|
||||
lib_url = "http://pypi.python.org/packages/source/{0}/{1}/{2}".format(lib_name[0], lib_name, lib_file)
|
||||
|
||||
lib_inst = os.path.join(INSTALL_DIR, lib_module)
|
||||
|
||||
try:
|
||||
|
||||
print(" - Creating temp dir at: {0}".format(TEMP_DIR))
|
||||
if not os.path.exists(TEMP_DIR):
|
||||
os.mkdir(TEMP_DIR)
|
||||
|
||||
print(" - Downloading package from {0}".format(lib_url))
|
||||
urllib.urlretrieve(lib_url, lib_full)
|
||||
|
||||
print(" - Download complete")
|
||||
|
||||
if lib_ext == 'zip':
|
||||
unpack_zip(lib_full, lib_dir, lib_module)
|
||||
else:
|
||||
unpack_tar(lib_full, lib_dir, lib_module)
|
||||
|
||||
|
||||
print(" - Files unpacked")
|
||||
shutil.copytree(os.path.join(TEMP_DIR, lib_dir, lib_module), lib_inst)
|
||||
print(" - Files moved to: {0}".format(lib_inst))
|
||||
print(" - Successfully installed {0}".format(lib['lib']))
|
||||
|
||||
except EnvironmentError:
|
||||
print(" - Failed to install {0}. Please ensure you have administrator"
|
||||
" access to the Maya installation directory".format(lib_name))
|
||||
|
||||
except Exception as exp:
|
||||
print(" - Failed to install {0}. Error: {1}".format(lib_name, exp))
|
||||
|
||||
finally:
|
||||
print(" - Cleaning up temp files")
|
||||
shutil.rmtree(TEMP_DIR)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
print("Checking for dependencies...")
|
||||
|
||||
for lib in LIBS:
|
||||
try:
|
||||
importlib.import_module(lib['mod'])
|
||||
print("Found {0}".format(lib['lib']))
|
||||
|
||||
except ImportError:
|
||||
print("Missing {0}".format(lib['lib']))
|
||||
print(" - Downloading version {0}".format(lib['ver']))
|
||||
download_lib(lib['lib'], lib['ver'], lib['mod'], lib['ext'])
|
||||
|
||||
print("Dependency check complete!")
|
||||
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
keyring==3.8
|
||||
requests==2.3.0
|
||||
mock==1.0.1
|
||||
oauthlib==0.6.3
|
||||
requests_oauthlib==0.4.1
|
||||
unittest2==0.5.1
|
||||
azure-batch-apps==0.4.0
|
|
@ -1,7 +0,0 @@
|
|||
from default import BatchAppsRenderAssets
|
||||
|
||||
class BatchAppsModuleCAssets(BatchAppsRenderAssets):
|
||||
render_engine = "Renderer_C"
|
||||
|
||||
def renderer_assets(self):
|
||||
return {}
|
|
@ -1,47 +0,0 @@
|
|||
///--------------------------------------------------------------------------
|
||||
///
|
||||
/// Maya Batch C# Cloud Assemblies
|
||||
///
|
||||
/// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
///
|
||||
/// MIT License
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
///--------------------------------------------------------------------------
|
||||
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Azure.Batch.Apps.Cloud;
|
||||
|
||||
namespace Maya.Cloud
|
||||
{
|
||||
public class ApplicationDefinition
|
||||
{
|
||||
public static readonly CloudApplication Application = new ParallelCloudApplication
|
||||
{
|
||||
ApplicationName = "Maya",
|
||||
JobType = "Maya",
|
||||
JobSplitterType = typeof(MayaJobSplitter),
|
||||
TaskProcessorType = typeof(MayaTaskProcessor)
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
///--------------------------------------------------------------------------
|
||||
///
|
||||
/// Maya Batch C# Cloud Assemblies
|
||||
///
|
||||
/// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
///
|
||||
/// MIT License
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
///--------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Maya.Cloud.Exceptions
|
||||
{
|
||||
[Serializable]
|
||||
public class InvalidParameterException : Exception
|
||||
{
|
||||
|
||||
public InvalidParameterException()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public InvalidParameterException(string message)
|
||||
: base(message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected InvalidParameterException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
///--------------------------------------------------------------------------
|
||||
///
|
||||
/// Maya Batch C# Cloud Assemblies
|
||||
///
|
||||
/// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
///
|
||||
/// MIT License
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
///--------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Maya.Cloud.Exceptions
|
||||
{
|
||||
[Serializable]
|
||||
public class NoOutputsFoundException : Exception
|
||||
{
|
||||
public NoOutputsFoundException()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public NoOutputsFoundException(string message)
|
||||
: base(message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected NoOutputsFoundException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
///--------------------------------------------------------------------------
|
||||
///
|
||||
/// Maya Batch C# Cloud Assemblies
|
||||
///
|
||||
/// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
///
|
||||
/// MIT License
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
///--------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Maya.Cloud.Exceptions
|
||||
{
|
||||
[Serializable]
|
||||
public class ZipException : Exception
|
||||
{
|
||||
public ZipException()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ZipException(string message)
|
||||
: base(message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ZipException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected ZipException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
///--------------------------------------------------------------------------
|
||||
///
|
||||
/// Maya Batch C# Cloud Assemblies
|
||||
///
|
||||
/// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
///
|
||||
/// MIT License
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
///--------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Maya.Cloud.Exceptions;
|
||||
using Microsoft.Azure.Batch.Apps.Cloud;
|
||||
|
||||
namespace Maya.Cloud
|
||||
{
|
||||
class MayaJobSplitter : JobSplitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Splits a job into more granular tasks to be processed in parallel.
|
||||
/// </summary>
|
||||
/// <param name="job">The job to be split.</param>
|
||||
/// <param name="settings">Contains information and services about the split request.</param>
|
||||
/// <returns>A sequence of tasks to be run on compute nodes.</returns>
|
||||
protected override IEnumerable<TaskSpecifier> Split(IJob job, JobSplitSettings settings)
|
||||
{
|
||||
var jobParameters = MayaParameters.FromJob(job);
|
||||
if (!jobParameters.Valid)
|
||||
{
|
||||
Log.Error(jobParameters.ErrorText);
|
||||
throw new InvalidParameterException(jobParameters.ErrorText);
|
||||
}
|
||||
|
||||
job.Parameters["settings"] = job.JobSettings;
|
||||
|
||||
for (var i = jobParameters.Start; i <= jobParameters.End; i++)
|
||||
{
|
||||
yield return new TaskSpecifier
|
||||
{
|
||||
TaskIndex = i,
|
||||
Parameters = job.Parameters,
|
||||
RequiredFiles = job.Files,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{CB1A970D-D7EC-4B35-A87D-7E7A8B618542}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Maya.Cloud</RootNamespace>
|
||||
<AssemblyName>Maya.Cloud</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<RunCodeAnalysis>true</RunCodeAnalysis>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<RunCodeAnalysis>true</RunCodeAnalysis>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Azure.Batch.Apps.Cloud">
|
||||
<HintPath>..\packages\Microsoft.Azure.Batch.Apps.Cloud.1.0.1-preview\lib\net45\Microsoft.Azure.Batch.Apps.Cloud.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ApplicationDefinition.cs" />
|
||||
<Compile Include="Exceptions\InvalidParameterException.cs" />
|
||||
<Compile Include="Exceptions\NoOutputsFoundException.cs" />
|
||||
<Compile Include="Exceptions\ZipException.cs" />
|
||||
<Compile Include="JobSplitter.cs" />
|
||||
<Compile Include="MayaScripts.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>MayaScripts.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="TaskParameters.cs" />
|
||||
<Compile Include="TaskProcessor.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="MayaScripts.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>MayaScripts.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -1,165 +0,0 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.34014
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Maya.Cloud {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class MayaScripts {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal MayaScripts() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Maya.Cloud.MayaScripts", typeof(MayaScripts).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?>
|
||||
///<ADLMCUSTOMENV VERSION="1.0.0.0">
|
||||
/// <PLATFORM OS="Windows">
|
||||
/// <KEY ID="ADLM_COMMON_BIN_LOCATION">
|
||||
/// <!--Path to the AdLM shared executables-->
|
||||
/// <STRING>{0}\Common Files\Autodesk Shared\Adlm\R9</STRING>
|
||||
/// </KEY>
|
||||
/// <KEY ID="ADLM_COMMON_LIB_LOCATION">
|
||||
/// <!--Path to the AdLM shared libraries-->
|
||||
/// <STRING>{0}\Common Files\Autodesk Shared\Adlm\R9</STRING>
|
||||
/// </KEY>
|
||||
/// <KEY ID="ADLM_COMMON_LOCAL [rest of string was truncated]";.
|
||||
/// </summary>
|
||||
internal static string client {
|
||||
get {
|
||||
return ResourceManager.GetString("client", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to global proc dirMap()
|
||||
///{{
|
||||
///dirmap -en true;
|
||||
///{0}
|
||||
///}}.
|
||||
/// </summary>
|
||||
internal static string dirMap {
|
||||
get {
|
||||
return ResourceManager.GetString("dirMap", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to MAYA_MODULE_PATH ={0}/Maya2015/modules;{1}/2015-x64/modules;{1};{0}/Common Files/Autodesk Shared/Modules/maya/2015
|
||||
///FBX_LOCATION = {0}/Maya2015/plug-ing/fbx/
|
||||
///MENTALRAY_LOCATION = {0}/mentalrayForMaya2015/
|
||||
///MAYA_SCRIPT_BASE = {0}/Maya2015
|
||||
///TEMP = {2}
|
||||
///MAYA_LOCATION = {0}\Maya2015
|
||||
///TMPDIR = {2}
|
||||
///MENTALRAY_BIN_LOCATION = {0}/mentalrayForMaya2015/bin
|
||||
///MAYA_PLUG_IN_PATH = {1};{0}/Maya2015/bin/plug-ins;{0}/Maya2015/plug-ins/bifrost/plug-ins;{0}/Maya2015/plug-ins/fbx/plug-ins;{0}/mentalrayForMaya2015/plug-ins;{0} [rest of string was truncated]";.
|
||||
/// </summary>
|
||||
internal static string env {
|
||||
get {
|
||||
return ResourceManager.GetString("env", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to AUTODESK_ADLM_THINCLIENT_ENV={0}\Adlm\AdlmThinClientCustomEnv.xml
|
||||
///MAYA_LICENSE=unlimited
|
||||
///MAYA_LICENSE_METHOD=network.
|
||||
/// </summary>
|
||||
internal static string lic {
|
||||
get {
|
||||
return ResourceManager.GetString("lic", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to //Maya 2015 Project Definition
|
||||
///
|
||||
///
|
||||
///
|
||||
///workspace -fr "fluidCache" ".";
|
||||
///
|
||||
///workspace -fr "images" ".";
|
||||
///
|
||||
///workspace -fr "offlineEdit" ".";
|
||||
///
|
||||
///workspace -fr "furShadowMap" ".";
|
||||
///
|
||||
///workspace -fr "iprImages" ".";
|
||||
///
|
||||
///workspace -fr "renderData" ".";
|
||||
///
|
||||
///workspace -fr "scripts" ".";
|
||||
///
|
||||
///workspace -fr "fileCache" ".";
|
||||
///
|
||||
///workspace -fr "eps" ".";
|
||||
///
|
||||
///workspace -fr "shaders" ".";
|
||||
///
|
||||
///workspace -fr "3dPaintTextures" ".";
|
||||
///
|
||||
///workspace -fr "translatorData" ".";
|
||||
///
|
||||
///workspace -fr "mel" ".";
|
||||
///
|
||||
///workspace -fr "furFiles" ".";
|
||||
///
|
||||
///workspace -fr "O [rest of string was truncated]";.
|
||||
/// </summary>
|
||||
internal static string workspace {
|
||||
get {
|
||||
return ResourceManager.GetString("workspace", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,278 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="client" xml:space="preserve">
|
||||
<value><?xml version="1.0" encoding="utf-8"?>
|
||||
<ADLMCUSTOMENV VERSION="1.0.0.0">
|
||||
<PLATFORM OS="Windows">
|
||||
<KEY ID="ADLM_COMMON_BIN_LOCATION">
|
||||
<!--Path to the AdLM shared executables-->
|
||||
<STRING>{0}\Common Files\Autodesk Shared\Adlm\R9</STRING>
|
||||
</KEY>
|
||||
<KEY ID="ADLM_COMMON_LIB_LOCATION">
|
||||
<!--Path to the AdLM shared libraries-->
|
||||
<STRING>{0}\Common Files\Autodesk Shared\Adlm\R9</STRING>
|
||||
</KEY>
|
||||
<KEY ID="ADLM_COMMON_LOCALIZED_DATA_LOCATION">
|
||||
<!--Path to the AdLM shared localized resource files (do not include the language folder)-->
|
||||
<STRING>{0}\Common Files\Autodesk Shared\Adlm\R9</STRING>
|
||||
</KEY>
|
||||
<KEY ID="ADLM_ERROR_TABLE_LOCATION">
|
||||
<!--Path to AdlmErrorCodes.xml-->
|
||||
<STRING>{0}\Adlm\R9</STRING>
|
||||
</KEY>
|
||||
<KEY ID="ADLM_PIT_FILE_LOCATION">
|
||||
<!--Path to the ProductInformation.pit file-->
|
||||
<STRING>{0}\Adlm</STRING>
|
||||
</KEY>
|
||||
<KEY ID="ADLM_CASCADE_FILE_LOCATION">
|
||||
<!--Path where the CascadeInfo.cas file will be created (this should always be on the client machine)-->
|
||||
<STRING>{0}\Adlm</STRING>
|
||||
</KEY>
|
||||
<KEY ID="ADLM_LICENSE_FILE_LOCATION">
|
||||
<!--Path to a directory containing one or more .lic files (API override)-->
|
||||
<STRING>{0}\Adlm</STRING>
|
||||
</KEY>
|
||||
<KEY ID="ADLM_APPLICATION_ADLM_RESOURCE_LOCATION">
|
||||
<!--Path to the AdlmIntRes.xml file (API override)-->
|
||||
<STRING></STRING>
|
||||
</KEY>
|
||||
<KEY ID="ADLM_APPLICATION_ERROR_LOG_FILE">
|
||||
<!--Path and filename of the application AdLM log file (API override)-->
|
||||
<STRING></STRING>
|
||||
</KEY>
|
||||
</PLATFORM>
|
||||
</ADLMCUSTOMENV></value>
|
||||
</data>
|
||||
<data name="dirMap" xml:space="preserve">
|
||||
<value>global proc dirMap()
|
||||
{{
|
||||
dirmap -en true;
|
||||
{0}
|
||||
}}</value>
|
||||
</data>
|
||||
<data name="env" xml:space="preserve">
|
||||
<value>MAYA_MODULE_PATH ={0}/Maya2015/modules;{1}/2015-x64/modules;{1};{0}/Common Files/Autodesk Shared/Modules/maya/2015
|
||||
FBX_LOCATION = {0}/Maya2015/plug-ing/fbx/
|
||||
MENTALRAY_LOCATION = {0}/mentalrayForMaya2015/
|
||||
MAYA_SCRIPT_BASE = {0}/Maya2015
|
||||
TEMP = {2}
|
||||
MAYA_LOCATION = {0}\Maya2015
|
||||
TMPDIR = {2}
|
||||
MENTALRAY_BIN_LOCATION = {0}/mentalrayForMaya2015/bin
|
||||
MAYA_PLUG_IN_PATH = {1};{0}/Maya2015/bin/plug-ins;{0}/Maya2015/plug-ins/bifrost/plug-ins;{0}/Maya2015/plug-ins/fbx/plug-ins;{0}/mentalrayForMaya2015/plug-ins;{0}/solidangle/mtoadeploy/2015/plug-ins;{0}/Maya2015/plug-ins/substance/plug-ins;{0}/Maya2015/plug-ins/xgen/plug-ins;
|
||||
PYTHONHOME = {0}/Maya2015/Python
|
||||
XGEN_LOCATION = {0}/Maya2015/plug-ins/xgen/
|
||||
XGEN_ROOT = {1}/xgen
|
||||
SUBSTANCES_LOCATION = {0}/Maya2015/plug-ins/substance/substances
|
||||
BIFROST_LOCATION = {0}/Maya2015/plug-ins/bifrost/
|
||||
TMP = {2}
|
||||
PYTHONPATH = {0}/Maya2015/plug-ins/bifrost/scripts/presets;{0}/Maya2015/plug-ins/bifrost/scripts;{0}/Maya2015/plug-ins/fbx/scripts;{0}/mentalrayForMaya2015/scripts/AETemplates;{0}/mentalrayForMaya2015/scripts/mentalray;{0}/mentalrayForMaya2015/scripts/unsupported;{0}/mentalrayForMaya2015/scripts;{0}/solidangle/mtoadeploy/2015/scripts;{0}/Maya2015/plug-ins/substance/scripts;{0}/Maya2015/plug-ins/xgen/scripts/cafm;{0}/Maya2015/plug-ins/xgen/scripts/xgenm;{0}/Maya2015/plug-ins/xgen/scripts/xgenm/ui;{0}/Maya2015/plug-ins/xgen/scripts/xgenm/xmaya;{0}/Maya2015/plug-ins/xgen/scripts/xgenm/ui/brushes;{0}/Maya2015/plug-ins/xgen/scripts/xgenm/ui/dialogs;{0}/Maya2015/plug-ins/xgen/scripts/xgenm/ui/fxmodules;{0}/Maya2015/plug-ins/xgen/scripts/xgenm/ui/tabs;{0}/Maya2015/plug-ins/xgen/scripts/xgenm/ui/util;{0}/Maya2015/plug-ins/xgen/scripts/xgenm/ui/widgets;{0}/Maya2015/plug-ins/xgen/scripts
|
||||
ILMDIR = {0}/Common Files/Autodesk Shared/Materials
|
||||
ARNOLD_PLUGIN_PATH = {0}/solidangle/mtoadeploy/2015/shaders
|
||||
MENTALRAY_SHADERS_LOCATION = {0}/mentalrayForMaya2015/shaders
|
||||
MAYA_RENDER_DESC_PATH = {0}/mentalrayForMaya2015/rendererDesc
|
||||
MENTAL_RAY_INCLUDE_LOCATION = {0}/mentalrayForMaya2015/shaders/include
|
||||
MTOA_PATH = {0}/solidangle/mtoadeploy/2015/
|
||||
MTOA_EXTENSIONS_PATH = {0}/solidangle/mtoadeploy/2015/extensions
|
||||
MAYA_SCRIPT_PATH = {1};{0}/Maya2015/scripts;{0}/Maya2015/scripts/startup;{0}/Maya2015/scripts/others;{0}/Maya2015/scripts/AETemplates;{0}/Maya2015/scripts/unsupported;{0}/Maya2015/scripts/paintEffects;{0}/Maya2015/scripts/fluidEffects;{0}/Maya2015/scripts/hair;{0}/Maya2015/scripts/cloth;{0}/Maya2015/scripts/live;{0}/Maya2015/scripts/fur;{0}/Maya2015/scripts/muscle;{0}/Maya2015/scripts/turtle;{0}/Maya2015/scripts/FBX;{0}/Maya2015/scripts/mayaHIK;{0}/Maya2015/plug-ins/bifrost/scripts/presets;{0}/Maya2015/plug-ins/bifrost/scripts;{0}/Maya2015/plug-ins/fbx/scripts;{0}/mentalrayForMaya2015/scripts/AETemplates;{0}/mentalrayForMaya2015/scripts/mentalray;{0}/mentalrayForMaya2015/scripts/unsupported;{0}/mentalrayForMaya2015/scripts;{0}/solidangle/mtoadeploy/2015/scripts
|
||||
IMF_PLUG_IN_PATH = {0}/mentalrayForMaya2015/bin/image
|
||||
MAYA_PLUGIN_RESOURCE_PATH = {1};{0}/Maya2015/plug-ins/bifrost/resources;{0}/Maya2015/plug-ins/fbx/resources;{0}/mentalrayForMaya2015/resources;{0}/solidangle/mtoadeploy/2015/resources;{0}/Maya2015/plug-ins/substance/resources;{0}/Maya2015/plug-ins/xgen/resources
|
||||
MAYA_PRESET_PATH = {0}/Maya2015/plug-ins/bifrost/presets;{0}/Maya2015/plug-ins/fbx/presets;{0}/mentalrayForMaya2015/presets/attrPresets;{0}/mentalrayForMaya2015/presets/attrPresets/maya_bifrost_liquid;{0}/mentalrayForMaya2015/presets/attrPresets/mia_material;{0}/mentalrayForMaya2015/presets/attrPresets/mia_material_x;{0}/mentalrayForMaya2015/presets/attrPresets/mia_material_x_passes;{0}/mentalrayForMaya2015/presets;{0}/solidangle/mtoadeploy/2015/presets;{0}/Maya2015/plug-ins/substance/presets;{0}/Maya2015/plug-ins/xgen/presets
|
||||
XBMLANGPATH = {0}/Maya2015/icons;{0}/Maya2015/app-defaults;{0}/Maya2015/icons/paintEffects;{0}/Maya2015/icons/fluidEffects;{0}/Maya2015/icons/hair;{0}/Maya2015/icons/cloth;{0}/Maya2015/icons/live;{0}/Maya2015/icons/fur;{0}/Maya2015/icons/muscle;{0}/Maya2015/icons/turtle;{0}/Maya2015/icons/FBX;{0}/Maya2015/icons/mayaHIK;{0}/Maya2015/plug-ins/bifrost/icons;{0}/Maya2015/plug-ins/fbx/icons;{0}/mentalrayForMaya2015/icons;{0}/solidangle/mtoadeploy/2015/icons;{0}/Maya2015/plug-ins/substance/icons;{0}/Maya2015/plug-ins/xgen/icons</value>
|
||||
</data>
|
||||
<data name="lic" xml:space="preserve">
|
||||
<value>AUTODESK_ADLM_THINCLIENT_ENV={0}\Adlm\AdlmThinClientCustomEnv.xml
|
||||
MAYA_LICENSE=unlimited
|
||||
MAYA_LICENSE_METHOD=network</value>
|
||||
</data>
|
||||
<data name="workspace" xml:space="preserve">
|
||||
<value>//Maya 2015 Project Definition
|
||||
|
||||
|
||||
|
||||
workspace -fr "fluidCache" ".";
|
||||
|
||||
workspace -fr "images" ".";
|
||||
|
||||
workspace -fr "offlineEdit" ".";
|
||||
|
||||
workspace -fr "furShadowMap" ".";
|
||||
|
||||
workspace -fr "iprImages" ".";
|
||||
|
||||
workspace -fr "renderData" ".";
|
||||
|
||||
workspace -fr "scripts" ".";
|
||||
|
||||
workspace -fr "fileCache" ".";
|
||||
|
||||
workspace -fr "eps" ".";
|
||||
|
||||
workspace -fr "shaders" ".";
|
||||
|
||||
workspace -fr "3dPaintTextures" ".";
|
||||
|
||||
workspace -fr "translatorData" ".";
|
||||
|
||||
workspace -fr "mel" ".";
|
||||
|
||||
workspace -fr "furFiles" ".";
|
||||
|
||||
workspace -fr "OBJ" ".";
|
||||
|
||||
workspace -fr "particles" ".";
|
||||
|
||||
workspace -fr "scene" ".";
|
||||
|
||||
workspace -fr "furEqualMap" ".";
|
||||
|
||||
workspace -fr "sourceImages" ".";
|
||||
|
||||
workspace -fr "furImages" ".";
|
||||
|
||||
workspace -fr "clips" ".";
|
||||
|
||||
workspace -fr "depth" ".";
|
||||
|
||||
workspace -fr "movie" ".";
|
||||
|
||||
workspace -fr "audio" ".";
|
||||
|
||||
workspace -fr "bifrostCache" ".";
|
||||
|
||||
workspace -fr "autoSave" ".";
|
||||
|
||||
workspace -fr "mayaAscii" ".";
|
||||
|
||||
workspace -fr "move" ".";
|
||||
|
||||
workspace -fr "sound" ".";
|
||||
|
||||
workspace -fr "diskCache" ".";
|
||||
|
||||
workspace -fr "illustrator" ".";
|
||||
|
||||
workspace -fr "mayaBinary" ".";
|
||||
|
||||
workspace -fr "templates" ".";
|
||||
|
||||
workspace -fr "furAttrMap" ".";</value>
|
||||
</data>
|
||||
</root>
|
|
@ -1,36 +0,0 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Maya.Cloud")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Maya.Cloud")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2015")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("dfd5212e-5daa-49e9-a92e-2355fe9baef5")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
@ -1,270 +0,0 @@
|
|||
///--------------------------------------------------------------------------
|
||||
///
|
||||
/// Maya Batch C# Cloud Assemblies
|
||||
///
|
||||
/// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
///
|
||||
/// MIT License
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
///--------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Json;
|
||||
using Microsoft.Azure.Batch.Apps.Cloud;
|
||||
using System.IO;
|
||||
|
||||
namespace Maya.Cloud
|
||||
{
|
||||
public abstract class MayaParameters
|
||||
{
|
||||
public static readonly IList<String> SupportedFormats = new List<String> { ".png", ".bmp", ".jpg", ".tga", ".exr" };
|
||||
|
||||
public abstract bool Valid { get; }
|
||||
|
||||
public abstract int Start { get; }
|
||||
|
||||
public abstract int End { get; }
|
||||
|
||||
public abstract MayaSettings Settings { get; }
|
||||
|
||||
public abstract string JobFile { get; }
|
||||
|
||||
public abstract string Renderer { get; }
|
||||
|
||||
public abstract string ErrorText { get; }
|
||||
|
||||
public static MayaParameters FromJob(IJob job)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
|
||||
int start = GetInt32Parameter(job.Parameters, "start", errors);
|
||||
int end = GetInt32Parameter(job.Parameters, "end", errors);
|
||||
|
||||
string jobfile = GetStringParameter(job.Parameters, "jobfile", errors);
|
||||
string engine = GetStringParameter(job.Parameters, "engine", errors);
|
||||
|
||||
if (errors.Any())
|
||||
{
|
||||
return new InvalidMayaParameters(string.Join(Environment.NewLine, errors.Select(e => "* " + e)));
|
||||
}
|
||||
|
||||
return new ValidMayaParameters(start, end, jobfile, engine);
|
||||
}
|
||||
public static MayaParameters FromTask(ITask task)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
|
||||
string jobfile = GetStringParameter(task.Parameters, "jobfile", errors);
|
||||
string engine = GetStringParameter(task.Parameters, "engine", errors);
|
||||
MayaSettings settings = GetSettingsParameter(task.Parameters, "settings", errors);
|
||||
|
||||
if (errors.Any())
|
||||
{
|
||||
return new InvalidMayaParameters(string.Join(Environment.NewLine, errors.Select(e => "* " + e)));
|
||||
}
|
||||
|
||||
return new ValidMayaParameters(jobfile, engine, settings);
|
||||
}
|
||||
|
||||
|
||||
private static int GetInt32Parameter(IDictionary<string, string> parameters, string parameterName, List<string> errors)
|
||||
{
|
||||
int value = 0;
|
||||
try
|
||||
{
|
||||
string text = parameters[parameterName];
|
||||
value = int.Parse(text, CultureInfo.InvariantCulture);
|
||||
if (value < 0)
|
||||
{
|
||||
errors.Add(parameterName + " parameter is not a positive integer");
|
||||
}
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
errors.Add(parameterName + " parameter not specified");
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
errors.Add(parameterName + " parameter is not a valid integer");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Add("Unexpected error reading parameter " + parameterName + ": " + ex.Message);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static string GetStringParameter(IDictionary<string, string> parameters, string parameterName, List<string> errors)
|
||||
{
|
||||
string text = "";
|
||||
try
|
||||
{
|
||||
text = parameters[parameterName];
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
errors.Add(parameterName + " parameter not specified");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Add("Unexpected error reading parameter " + parameterName + ": " + ex.Message);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private static MayaSettings GetSettingsParameter(IDictionary<string, string> parameters, string parameterName, List<string> errors)
|
||||
{
|
||||
var jsonSettings = GetStringParameter(parameters, parameterName, errors);
|
||||
var settings = new MayaSettings();
|
||||
|
||||
try
|
||||
{
|
||||
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(MayaSettings));
|
||||
MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonSettings));
|
||||
settings = (MayaSettings)ser.ReadObject(stream);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Add("Error deserializing json settings: " + ex.Message);
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
public class MayaSettings
|
||||
{
|
||||
[DataMember]
|
||||
public List<string> PathMaps { get; set; }
|
||||
}
|
||||
|
||||
|
||||
private class ValidMayaParameters : MayaParameters
|
||||
{
|
||||
private readonly int _start;
|
||||
private readonly int _end;
|
||||
private readonly string _jobfile;
|
||||
private readonly string _renderer;
|
||||
private readonly MayaSettings _settings;
|
||||
|
||||
public ValidMayaParameters(int start, int end, string jobfile, string engine)
|
||||
{
|
||||
_start = start;
|
||||
_end = end;
|
||||
_jobfile = jobfile;
|
||||
_renderer = engine;
|
||||
}
|
||||
|
||||
public ValidMayaParameters(string jobfile, string engine, MayaSettings settings)
|
||||
{
|
||||
_jobfile = jobfile;
|
||||
_renderer = engine;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public override bool Valid
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override int Start
|
||||
{
|
||||
get { return _start; }
|
||||
}
|
||||
|
||||
public override int End
|
||||
{
|
||||
get { return _end; }
|
||||
}
|
||||
|
||||
public override string JobFile
|
||||
{
|
||||
get { return _jobfile; }
|
||||
}
|
||||
|
||||
public override string Renderer
|
||||
{
|
||||
get { return _renderer; }
|
||||
}
|
||||
|
||||
public override MayaSettings Settings
|
||||
{
|
||||
get { return _settings; }
|
||||
}
|
||||
|
||||
public override string ErrorText
|
||||
{
|
||||
get { throw new InvalidOperationException("ErrorText does not apply to valid parameters"); }
|
||||
}
|
||||
}
|
||||
|
||||
private class InvalidMayaParameters : MayaParameters
|
||||
{
|
||||
private readonly string _errorText;
|
||||
|
||||
public InvalidMayaParameters(string errorText)
|
||||
{
|
||||
_errorText = errorText;
|
||||
}
|
||||
|
||||
public override bool Valid
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override int Start
|
||||
{
|
||||
get { throw new InvalidOperationException("Start does not apply to invalid parameters"); }
|
||||
}
|
||||
|
||||
public override int End
|
||||
{
|
||||
get { throw new InvalidOperationException("End does not apply to invalid parameters"); }
|
||||
}
|
||||
|
||||
public override string JobFile
|
||||
{
|
||||
get { throw new InvalidOperationException("JobFile does not apply to invalid parameters"); }
|
||||
}
|
||||
|
||||
public override string Renderer
|
||||
{
|
||||
get { throw new InvalidOperationException("Renderer does not apply to invalid parameters"); }
|
||||
}
|
||||
|
||||
public override MayaSettings Settings
|
||||
{
|
||||
get { throw new InvalidOperationException("Settings does not apply to invalid parameters"); }
|
||||
}
|
||||
|
||||
public override string ErrorText
|
||||
{
|
||||
get { return _errorText; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,374 +0,0 @@
|
|||
///--------------------------------------------------------------------------
|
||||
///
|
||||
/// Maya Batch C# Cloud Assemblies
|
||||
///
|
||||
/// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
///
|
||||
/// MIT License
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
///--------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Globalization;
|
||||
using System.IO.Compression;
|
||||
using Maya.Cloud.Exceptions;
|
||||
|
||||
using Microsoft.Azure.Batch.Apps.Cloud;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Maya.Cloud
|
||||
{
|
||||
public class MayaTaskProcessor : ParallelTaskProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Path to the Maya executable
|
||||
/// </summary>
|
||||
private string RenderPath
|
||||
{
|
||||
get { return @"Maya2015\bin\render.exe"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Args with which to run Maya
|
||||
/// </summary>
|
||||
private string RenderArgs
|
||||
{
|
||||
get { return @"-renderer {0} -log ""{1}"" -proj ""{2}"" -preRender ""dirMap"" -rd ""{3}"" -s {4} -e {4} ""{5}"""; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the external process for processing the task
|
||||
/// </summary>
|
||||
/// <param name="task">The task to be processed.</param>
|
||||
/// <param name="settings">Contains information about the processing request.</param>
|
||||
/// <returns>The result of task processing.</returns>
|
||||
protected override TaskProcessResult RunExternalTaskProcess(ITask task, TaskExecutionSettings settings)
|
||||
{
|
||||
var taskParameters = MayaParameters.FromTask(task);
|
||||
var projDir = ConfigureMayaEnv(task, LocalStoragePath, ExecutablesPath);
|
||||
CreateMappingScript(taskParameters, LocalStoragePath);
|
||||
|
||||
var initialFiles = CollectFiles(LocalStoragePath);
|
||||
|
||||
if (!taskParameters.Valid)
|
||||
{
|
||||
Log.Error(taskParameters.ErrorText);
|
||||
return new TaskProcessResult
|
||||
{
|
||||
Success = TaskProcessSuccess.PermanentFailure,
|
||||
ProcessorOutput = "Parameter error: " + taskParameters.ErrorText,
|
||||
};
|
||||
}
|
||||
|
||||
var inputFile = Path.Combine(LocalStoragePath, taskParameters.JobFile);
|
||||
var logFile = string.Format("{0}.log", task.TaskId);
|
||||
|
||||
var externalProcessPath = ExecutablePath(RenderPath);
|
||||
var externalProcessArgs = string.Format(CultureInfo.InvariantCulture, RenderArgs, taskParameters.Renderer,
|
||||
logFile, LocalStoragePath, LocalStoragePath, task.TaskIndex, inputFile);
|
||||
|
||||
Log.Info("Calling '{0}' with Args '{1}' for Task '{2}' / Job '{3}' .", RenderPath, externalProcessArgs, task.TaskId, task.JobId);
|
||||
var processResult = ExecuteProcess(externalProcessPath, externalProcessArgs);
|
||||
|
||||
if (processResult == null)
|
||||
{
|
||||
if (File.Exists(logFile))
|
||||
return new TaskProcessResult
|
||||
{
|
||||
Success = TaskProcessSuccess.PermanentFailure,
|
||||
ProcessorOutput = File.ReadAllText(logFile)
|
||||
};
|
||||
|
||||
else
|
||||
return new TaskProcessResult { Success = TaskProcessSuccess.PermanentFailure };
|
||||
}
|
||||
|
||||
var newFiles = GetNewFiles(initialFiles, LocalStoragePath);
|
||||
var result = TaskProcessResult.FromExternalProcessResult(processResult, newFiles);
|
||||
if (File.Exists(logFile))
|
||||
result.ProcessorOutput = File.ReadAllText(logFile);
|
||||
|
||||
var thumbnail = CreateThumbnail(task, newFiles);
|
||||
|
||||
if (!string.IsNullOrEmpty(thumbnail))
|
||||
{
|
||||
var taskPreview = new TaskOutputFile
|
||||
{
|
||||
FileName = thumbnail,
|
||||
Kind = TaskOutputFileKind.Preview
|
||||
};
|
||||
result.OutputFiles.Add(taskPreview);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to execute the external processing for merging the tasks output into job output
|
||||
/// </summary>
|
||||
/// <param name="mergeTask">The merge task.</param>
|
||||
/// <param name="settings">Contains information about the processing request.</param>
|
||||
/// <returns>The job outputs resulting from the merge process.</returns>
|
||||
protected override JobResult RunExternalMergeProcess(ITask mergeTask, TaskExecutionSettings settings)
|
||||
{
|
||||
var inputFiles = CollectFiles(LocalStoragePath);
|
||||
FilterFiles(inputFiles);
|
||||
|
||||
var outputFile = LocalPath("output.zip");
|
||||
var result = new JobResult();
|
||||
|
||||
if (inputFiles.Count < 1)
|
||||
{
|
||||
throw new NoOutputsFoundException("No job outputs found.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (ZipArchive outputs = ZipFile.Open(outputFile, ZipArchiveMode.Create))
|
||||
{
|
||||
foreach (var input in inputFiles)
|
||||
{
|
||||
outputs.CreateEntryFromFile(input, Path.GetFileName(input), CompressionLevel.Optimal);
|
||||
}
|
||||
}
|
||||
|
||||
result.OutputFile = outputFile;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var error = string.Format("Failed to zip outputs: {0}", ex.ToString());
|
||||
throw new ZipException(error, ex);
|
||||
}
|
||||
|
||||
result.PreviewFile = CreateThumbnail(mergeTask, inputFiles.ToArray());
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string ConfigureMayaEnv(ITask task, string cwd, string exe)
|
||||
{
|
||||
Environment.SetEnvironmentVariable("MAYA_APP_DIR", cwd);
|
||||
var sysPath = Environment.GetEnvironmentVariable("PATH");
|
||||
Environment.SetEnvironmentVariable("PATH", string.Format(@"{0};{1}\Maya2015\bin;{1}\mentalrayForMaya2015\bin", sysPath, exe));
|
||||
|
||||
var project = Path.Combine(cwd, "workspace.mel");
|
||||
if (!File.Exists(project))
|
||||
{
|
||||
using (StreamWriter workspace = new StreamWriter(project))
|
||||
{
|
||||
workspace.Write(MayaScripts.workspace);
|
||||
}
|
||||
}
|
||||
|
||||
var license = Path.Combine(exe, "Maya2015", "bin", "License.env");
|
||||
if (!File.Exists(license))
|
||||
{
|
||||
var formattedLic = string.Format(MayaScripts.lic, exe);
|
||||
using (var licFile = new StreamWriter(license))
|
||||
{
|
||||
licFile.Write(formattedLic);
|
||||
}
|
||||
}
|
||||
|
||||
var client = Path.Combine(exe, "Adlm", "AdlmThinClientCustomEnv.xml");
|
||||
if (!File.Exists(client))
|
||||
{
|
||||
var formattedClient = string.Format(MayaScripts.client, exe);
|
||||
using (var clientFile = new StreamWriter(client))
|
||||
{
|
||||
clientFile.Write(formattedClient);
|
||||
}
|
||||
}
|
||||
|
||||
var envDir = Path.Combine(cwd, "2015-x64"); //TODO: Fix this to make the version depend on vhd version
|
||||
if (!Directory.Exists(envDir))
|
||||
{
|
||||
Directory.CreateDirectory(envDir);
|
||||
}
|
||||
|
||||
var scriptDir = Path.Combine(envDir, "scripts");
|
||||
if (!Directory.Exists(scriptDir))
|
||||
{
|
||||
Directory.CreateDirectory(scriptDir);
|
||||
}
|
||||
|
||||
var envPath = Path.Combine(envDir, "Maya.env");
|
||||
if (!File.Exists(envPath))
|
||||
{
|
||||
var formattedEnv = string.Format(MayaScripts.env, exe, cwd, Path.GetTempPath());
|
||||
using (var envFile = new StreamWriter(envPath))
|
||||
{
|
||||
envFile.Write(formattedEnv);
|
||||
}
|
||||
}
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
private void CreateMappingScript(MayaParameters parameters, string localPath)
|
||||
{
|
||||
var scriptPath = Path.Combine(localPath, "2015-x64", "scripts", "dirMap.mel");
|
||||
var remappedPaths = parameters.Settings.PathMaps;
|
||||
var pathsScript = "";
|
||||
if (!File.Exists(scriptPath) && remappedPaths.Count > 0)
|
||||
{
|
||||
foreach (var p in remappedPaths)
|
||||
{
|
||||
pathsScript += string.Format("dirmap -m \"{0}\" \"{1}\";\n", p, localPath.Replace('\\', '/'));
|
||||
}
|
||||
var formattedScript = string.Format(MayaScripts.dirMap, pathsScript);
|
||||
|
||||
using (var scriptFile = new StreamWriter(scriptPath))
|
||||
{
|
||||
scriptFile.Write(formattedScript);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a list of files that currently exist in a given location, according to a
|
||||
/// given naming pattern.
|
||||
/// </summary>
|
||||
/// <param name="location">The directory to list the contents of.</param>
|
||||
/// <param name="pattern">The naming convention the returned files names adhere to.</param>
|
||||
/// <returns>A HashSet of the paths of the files in the directory.</returns>
|
||||
private static HashSet<string> CollectFiles(string location, string pattern = "*")
|
||||
{
|
||||
return new HashSet<string>(Directory.GetFiles(location, pattern, SearchOption.AllDirectories));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a difference between the current contents of a directory and a supplied file list.
|
||||
/// </summary>
|
||||
/// <param name="oldFiles">Set of file paths to compare.</param>
|
||||
/// <param name="location">Path to the directory.</param>
|
||||
/// <returns>An array of files paths in the directory that do not appear in the supplied set.</returns>
|
||||
private static string[] GetNewFiles(HashSet<string> oldFiles, string location)
|
||||
{
|
||||
var filesNow = CollectFiles(location);
|
||||
filesNow.RemoveWhere(oldFiles.Contains);
|
||||
FilterFiles(filesNow);
|
||||
|
||||
return filesNow.ToArray();
|
||||
}
|
||||
|
||||
private static void FilterFiles(HashSet<string> fileSet)
|
||||
{
|
||||
fileSet.RemoveWhere(f => f.EndsWith(".temp"));
|
||||
fileSet.RemoveWhere(f => f.EndsWith(".stdout"));
|
||||
fileSet.RemoveWhere(f => f.EndsWith(".log"));
|
||||
fileSet.RemoveWhere(f => f.EndsWith(".xml"));
|
||||
fileSet.RemoveWhere(f => f.EndsWith("mayaLog"));
|
||||
fileSet.RemoveWhere(f => f.EndsWith(".mel"));
|
||||
fileSet.RemoveWhere(f => f.EndsWith(".gbtaskcompletion"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an image thumbnail for the task. If supplied image format is incompatible, no thumb
|
||||
/// will be created and no error thrown.
|
||||
/// </summary>
|
||||
/// <param name="task">The task that needs a thumbnail.</param>
|
||||
/// <param name="inputName">The task output from which to generate the thumbnail.</param>
|
||||
/// <returns>The path to the new thumbnail if created, else an empty string.</returns>
|
||||
protected string CreateThumbnail(ITask task, string[] inputs)
|
||||
{
|
||||
var imagemagick = ExecutablePath(@"ImageMagick\convert.exe");
|
||||
if (!File.Exists(imagemagick))
|
||||
{
|
||||
Log.Info("ImageMagick not found. Skipping thumbnail creation.");
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var filtered = inputs.Where(x => MayaParameters.SupportedFormats.Contains(Path.GetExtension(x)));
|
||||
if (filtered.Count() < 1)
|
||||
{
|
||||
Log.Info("No thumbnail compatible images found.");
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var thumbInput = filtered.First();
|
||||
var thumbOutput = LocalPath(string.Format("{0}_{1}_thumbnail.png", task.JobId, task.TaskIndex));
|
||||
|
||||
var process = new ExternalProcess
|
||||
{
|
||||
CommandPath = imagemagick,
|
||||
Arguments = string.Format(@"""{0}"" -thumbnail 200x150> ""{1}""", thumbInput, thumbOutput),
|
||||
WorkingDirectory = LocalStoragePath
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
process.Run();
|
||||
}
|
||||
catch (ExternalProcessException ex)
|
||||
{
|
||||
Log.Info("No thumbnail generated: {0}", ex);
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
Log.Info("Generated thumbnail from {0} at {1}", Path.GetFileName(thumbInput), Path.GetFileName(thumbOutput));
|
||||
return thumbOutput;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run a, executable with a given set of arguments.
|
||||
/// </summary>
|
||||
/// <param name="exePath">Path the executable.</param>
|
||||
/// <param name="exeArgs">The command line arguments.</param>
|
||||
/// <returns>The ExternalProcessResult if run successfully, or null if an error was thrown.</returns>
|
||||
private ExternalProcessResult ExecuteProcess(string exePath, string exeArgs)
|
||||
{
|
||||
var process = new ExternalProcess
|
||||
{
|
||||
CommandPath = exePath,
|
||||
Arguments = exeArgs,
|
||||
WorkingDirectory = LocalStoragePath
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
return process.Run();
|
||||
}
|
||||
catch (ExternalProcessException ex)
|
||||
{
|
||||
string outputInfo = "No program output";
|
||||
if (!string.IsNullOrEmpty(ex.StandardError) || !string.IsNullOrEmpty(ex.StandardOutput))
|
||||
{
|
||||
outputInfo = Environment.NewLine + "stderr: " + ex.StandardError + Environment.NewLine + "stdout: " + ex.StandardOutput;
|
||||
}
|
||||
|
||||
Log.Error("Failed to invoke command {0} {1}: exit code was {2}. {3}", ex.CommandPath, ex.Arguments, ex.ExitCode, outputInfo);
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Error in task processor: {0}", ex.ToString());
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Azure.Batch.Apps.Cloud" version="1.0.1-preview" targetFramework="net45" />
|
||||
</packages>
|
После Ширина: | Высота: | Размер: 16 KiB |
После Ширина: | Высота: | Размер: 16 KiB |
После Ширина: | Высота: | Размер: 787 B |
После Ширина: | Высота: | Размер: 16 KiB |
После Ширина: | Высота: | Размер: 16 KiB |
После Ширина: | Высота: | Размер: 16 KiB |
До Ширина: | Высота: | Размер: 913 B После Ширина: | Высота: | Размер: 913 B |
До Ширина: | Высота: | Размер: 1.1 KiB После Ширина: | Высота: | Размер: 1.1 KiB |
После Ширина: | Высота: | Размер: 15 KiB |
После Ширина: | Высота: | Размер: 15 KiB |
|
@ -0,0 +1,33 @@
|
|||
python("from shared import AzureBatchSettings");
|
||||
|
||||
$batch_path = `getenv "AZBATCH_ICONS"`;
|
||||
|
||||
global proc run_guiStarter()
|
||||
{
|
||||
python("AzureBatchSettings.starter()");
|
||||
}
|
||||
|
||||
global proc openMissionControl()
|
||||
{
|
||||
python("import webbrowser\nwebbrowser.open(\"https://ms.portal.azure.com\", 2, True)");
|
||||
}
|
||||
|
||||
|
||||
string $uiButton = `shelfButton
|
||||
-parent "AzureBatch"
|
||||
-enable 1
|
||||
-annotation "Launch Plugin"
|
||||
-label "Launch Plugin"
|
||||
-image1 ($batch_path + "/plugin.png")
|
||||
-style "iconOnly"
|
||||
-command "run_guiStarter()"`;
|
||||
|
||||
string $mcButton = `shelfButton
|
||||
-parent "AzureBatch"
|
||||
-enable 1
|
||||
-annotation "Management Portal"
|
||||
-label "Management Portal"
|
||||
-image1 ($batch_path + "/portal.png")
|
||||
-style "iconOnly"
|
||||
-command "openMissionControl()"`;
|
||||
;
|
|
@ -0,0 +1,132 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import sys
|
||||
import gzip
|
||||
import glob
|
||||
import json
|
||||
|
||||
from maya import mel, cmds
|
||||
import maya.OpenMaya as om
|
||||
import maya.OpenMayaMPx as omp
|
||||
|
||||
from default import AzureBatchRenderJob, AzureBatchRenderAssets
|
||||
|
||||
|
||||
class ArnoldRenderJob(AzureBatchRenderJob):
|
||||
|
||||
def __init__(self):
|
||||
self._renderer = "arnold"
|
||||
self.label = "Arnold"
|
||||
|
||||
def settings(self):
|
||||
if self.scene_name == "":
|
||||
job_name = "Untitled"
|
||||
else:
|
||||
job_name = str(os.path.splitext(os.path.basename(self.scene_name))[0])
|
||||
file_prefix = mel.eval("getAttr defaultRenderGlobals.imageFilePrefix")
|
||||
if file_prefix:
|
||||
file_prefix = os.path.split(file_prefix)[1]
|
||||
else:
|
||||
file_prefix = "<Scene>"
|
||||
self.job_name = self.display_string("Job Name: ", job_name)
|
||||
self.output_name = self.display_string("Output Prefix: ", file_prefix)
|
||||
self.start = self.display_int("Start frame: ", self.start_frame, edit=True)
|
||||
self.end = self.display_int("End frame: ", self.end_frame, edit=True)
|
||||
self.step = self.display_int("Frame step: ", self.frame_step, edit=True)
|
||||
|
||||
def get_title(self):
|
||||
return str(cmds.textField(self.job_name, query=True, text=True))
|
||||
|
||||
def render_enabled(self):
|
||||
return True
|
||||
|
||||
def get_jobdata(self):
|
||||
if self.scene_name == "":
|
||||
raise ValueError("Current Maya scene has not been saved to disk.")
|
||||
|
||||
pending_changes = cmds.file(query=True, modified=True)
|
||||
if not pending_changes:
|
||||
return self.scene_name, [self.scene_name]
|
||||
options = ["Save and Continue",
|
||||
"Don't Save and Continue",
|
||||
"Cancel"]
|
||||
answer = cmds.confirmDialog(title='Unsaved Changes',
|
||||
message='There are unsaved changes. Proceed?',
|
||||
button=options,
|
||||
defaultButton=options[0],
|
||||
cancelButton=options[2],
|
||||
dismissString=options[2])
|
||||
if answer == options[2]:
|
||||
raise Exception("Submission Aborted")
|
||||
if answer == options[0]:
|
||||
cmds.SaveScene()
|
||||
return self.scene_name, [self.scene_name]
|
||||
|
||||
def get_params(self):
|
||||
params = {}
|
||||
params["frameStart"] = cmds.intField(self.start, query=True, value=True)
|
||||
params["frameEnd"] = cmds.intField(self.end, query=True, value=True)
|
||||
params["frameStep"] = cmds.intField(self.step, query=True, value=True)
|
||||
params["renderer"] = "arnold"
|
||||
return params
|
||||
|
||||
|
||||
class ArnoldRenderAssets(AzureBatchRenderAssets):
|
||||
|
||||
assets = []
|
||||
render_engine = "arnold"
|
||||
file_nodes = {
|
||||
"aiStandIn": ["dso"],
|
||||
"aiPhotometricLight": ["aiFilename"],
|
||||
"aiVolume": ["dso", "filename"],
|
||||
"aiImage": ["filename"]
|
||||
}
|
||||
|
||||
def check_path(self, path):
|
||||
if '#' in path:
|
||||
return path.replace('#', '[0-9]')
|
||||
elif '<udim>' in path:
|
||||
return path.replace('<udim>', '[0-9][0-9][0-9][0-9]')
|
||||
elif '<tile>' in path:
|
||||
return path.replace('<tile>', '_u*_v*')
|
||||
else:
|
||||
return path
|
||||
|
||||
def renderer_assets(self):
|
||||
self.assets = []
|
||||
collected = []
|
||||
for node_type, attributes in self.file_nodes.items():
|
||||
nodes = cmds.ls(type=node_type)
|
||||
for node in nodes:
|
||||
for attr in attributes:
|
||||
collected.append(cmds.getAttr(node + "." + attr))
|
||||
for path in collected:
|
||||
self.assets.append(self.check_path(path))
|
||||
return self.assets
|
|
@ -1,6 +1,6 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
|
@ -26,14 +26,14 @@
|
|||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
from maya import cmds, mel
|
||||
|
||||
import os
|
||||
|
||||
class BatchAppsRenderJob(object):
|
||||
from maya import cmds, mel
|
||||
|
||||
|
||||
class AzureBatchRenderJob(object):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self._renderer = None
|
||||
self.label = "Renderer not supported"
|
||||
self.file_format = None
|
||||
|
@ -84,7 +84,6 @@ class BatchAppsRenderJob(object):
|
|||
cmds.text(label=label, align='right')
|
||||
if edit:
|
||||
return cmds.intField(value=value)
|
||||
|
||||
else:
|
||||
return cmds.text(label=value, align='left')
|
||||
|
||||
|
@ -92,10 +91,17 @@ class BatchAppsRenderJob(object):
|
|||
cmds.text(label=label, align='right')
|
||||
if edit:
|
||||
return cmds.textField(text=value)
|
||||
|
||||
else:
|
||||
return cmds.text(label=value, align='left')
|
||||
|
||||
def display_menu(self, label, options):
|
||||
cmds.text(label=label, align='right')
|
||||
menu = cmds.optionMenu()
|
||||
for opt in options:
|
||||
cmds.menuItem(label=opt)
|
||||
cmds.setParent('..')
|
||||
return menu
|
||||
|
||||
def display_button(self, label, cmd):
|
||||
cmds.text(label="")
|
||||
return cmds.button(label=label, command=cmd, align='center')
|
||||
|
@ -147,18 +153,17 @@ class BatchAppsRenderJob(object):
|
|||
horizontalScrollBarThickness=0,
|
||||
verticalScrollBarThickness=3,
|
||||
parent=layout,
|
||||
width=360,
|
||||
height=310)
|
||||
#width=360,
|
||||
height=260)
|
||||
|
||||
self.subLayout = cmds.rowColumnLayout(
|
||||
numberOfColumns=2,
|
||||
columnWidth=((1, 80),
|
||||
columnWidth=((1, 100),
|
||||
(2, 200),),
|
||||
parent=self.module_layout,
|
||||
rowSpacing=(1, 10),
|
||||
rowOffset=((1, "top", 20),
|
||||
(12, "top", 25),))
|
||||
|
||||
self.display_string("Renderer: ", self.label, edit=False)
|
||||
self.settings()
|
||||
|
||||
|
@ -181,9 +186,9 @@ class BatchAppsRenderJob(object):
|
|||
cmds.textFieldButtonGrp(k, edit=True, text=str(output[0]))
|
||||
|
||||
|
||||
class BatchAppsRenderAssets(object):
|
||||
class AzureBatchRenderAssets(object):
|
||||
|
||||
assets = {}
|
||||
assets = []
|
||||
render_engine = ""
|
||||
|
||||
def renderer_assets(self):
|
|
@ -1,6 +1,6 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
|
@ -26,31 +26,35 @@
|
|||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
from maya import cmds, mel
|
||||
|
||||
import os
|
||||
import sys
|
||||
import gzip
|
||||
import json
|
||||
|
||||
from default import BatchAppsRenderJob, BatchAppsRenderAssets
|
||||
from maya import cmds, mel
|
||||
|
||||
from default import AzureBatchRenderJob, AzureBatchRenderAssets
|
||||
|
||||
|
||||
class BatchAppsMayaJob(BatchAppsRenderJob):
|
||||
class AzureBatchMayaJob(AzureBatchRenderJob):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self._renderer = "mayaSoftware"
|
||||
self.label = "Maya Software"
|
||||
|
||||
def settings(self):
|
||||
if self.scene_name == "":
|
||||
job_name = "Untitled"
|
||||
|
||||
else:
|
||||
job_name = str(os.path.splitext(os.path.basename(self.scene_name))[0])
|
||||
file_prefix = mel.eval("getAttr defaultRenderGlobals.imageFilePrefix")
|
||||
if file_prefix:
|
||||
file_prefix = os.path.split(file_prefix)[1]
|
||||
else:
|
||||
file_prefix = "<Scene>.<Camera>.<RenderLayer>"
|
||||
|
||||
self.job_name = self.display_string("Job Name: ", job_name)
|
||||
|
||||
self.output_name = self.display_string("Output Prefix: ", file_prefix)
|
||||
self.start = self.display_int("Start frame: ", self.start_frame, edit=True)
|
||||
self.end = self.display_int("End frame: ", self.end_frame, edit=True)
|
||||
self.step = self.display_int("Frame step: ", self.frame_step, edit=True)
|
||||
|
@ -64,22 +68,40 @@ class BatchAppsMayaJob(BatchAppsRenderJob):
|
|||
def get_jobdata(self):
|
||||
if self.scene_name == "":
|
||||
raise ValueError("Current Maya scene has not been saved to disk.")
|
||||
else:
|
||||
pending_changes = cmds.file(query=True, modified=True)
|
||||
if not pending_changes:
|
||||
return [self.scene_name]
|
||||
options = ["Save and Continue",
|
||||
"Don't Save and Continue",
|
||||
"Cancel"]
|
||||
answer = cmds.confirmDialog(title='Unsaved Changes',
|
||||
message='There are unsaved changes. Proceed?',
|
||||
button=options,
|
||||
defaultButton=options[0],
|
||||
cancelButton=options[2],
|
||||
dismissString=options[2])
|
||||
if answer == options[2]:
|
||||
raise Exception("Submission Aborted")
|
||||
if answer == options[0]:
|
||||
cmds.SaveScene()
|
||||
return [self.scene_name]
|
||||
|
||||
def get_params(self):
|
||||
params = {}
|
||||
|
||||
params["start"] = cmds.intField(self.start, query=True, value=True)
|
||||
params["end"] = cmds.intField(self.end, query=True, value=True)
|
||||
params["engine"] = "sw"
|
||||
params["jobfile"] = os.path.basename(self.scene_name)
|
||||
|
||||
params["StartFrame"] = cmds.intField(self.start, query=True, value=True)
|
||||
params["EndFrame"] = cmds.intField(self.end, query=True, value=True)
|
||||
params["Renderer"] = "sw"
|
||||
params["JobFile"] = os.path.basename(self.scene_name)
|
||||
filename = str(cmds.textField(self.output_name, query=True, text=True))
|
||||
if '/' in filename or '\\' in filename:
|
||||
raise ValueError("Subfolders not supported in output filename.")
|
||||
params["OutputName"] = filename
|
||||
return params
|
||||
|
||||
class MayaRenderAssets(BatchAppsRenderAssets):
|
||||
|
||||
assets = {}
|
||||
class MayaRenderAssets(AzureBatchRenderAssets):
|
||||
|
||||
assets = []
|
||||
render_engine = "mayaSoftware"
|
||||
|
||||
def renderer_assets(self):
|
|
@ -0,0 +1,532 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import urllib
|
||||
import os
|
||||
import tarfile
|
||||
import shutil
|
||||
import sys
|
||||
import zipfile
|
||||
import warnings
|
||||
import importlib
|
||||
import tempfile
|
||||
import inspect
|
||||
import glob
|
||||
import webbrowser
|
||||
import subprocess
|
||||
from distutils.version import StrictVersion
|
||||
|
||||
from maya import mel
|
||||
from maya import cmds
|
||||
|
||||
from maya.OpenMayaMPx import MFnPlugin
|
||||
import maya.OpenMaya as OpenMaya
|
||||
import maya.OpenMayaMPx as OpenMayaMPx
|
||||
|
||||
warnings.simplefilter('ignore')
|
||||
|
||||
INSTALL_DIR = os.path.normpath(
|
||||
os.path.join(cmds.internalVar(userScriptDir=True), 'azure-batch-libs'))
|
||||
sys.path.append(INSTALL_DIR)
|
||||
|
||||
REQUIREMENTS = [
|
||||
"pathlib==1.0.1",
|
||||
]
|
||||
|
||||
NAMESPACE_PACAKGES = [
|
||||
#"azure-mgmt-nspkg==2.0.0",
|
||||
"azure-mgmt-batch==3.0.0",
|
||||
"azure-mgmt-storage==1.0.0rc1",
|
||||
"azure-common==1.1.5",
|
||||
"azure-batch==2.0.0",
|
||||
"azure-storage==0.32.0",
|
||||
]
|
||||
|
||||
VERSION = "0.9.0"
|
||||
SLA_PREF = "AzureBatch_SLA-3"
|
||||
SHELF_FILE = "shelf_AzureBatch.mel"
|
||||
cmd_name = "AzureBatch"
|
||||
fMayaExitingCB = None
|
||||
os.environ["AZUREBATCH_VERSION"] = VERSION
|
||||
|
||||
|
||||
def sla_prompt():
|
||||
"""Open prompt for T's & C's agreement."""
|
||||
current_file = inspect.getfile(inspect.currentframe())
|
||||
current_dir = os.path.dirname(os.path.abspath(current_file))
|
||||
SLA = os.path.join(current_dir, "SLA.html")
|
||||
form = cmds.setParent(q=True)
|
||||
cmds.formLayout(form, e=True, width=500)
|
||||
heading = cmds.text(
|
||||
l='Maya Cloud Rendering Service Level Agreement', font="boldLabelFont")
|
||||
text = cmds.text(l="By loading this plug-in you are agreeing to "
|
||||
"the following terms and conditions.")
|
||||
if not os.path.exists(SLA):
|
||||
raise RuntimeError("SLA notice not found at {0}".format(SLA))
|
||||
|
||||
with open(SLA, "rb") as sla_text:
|
||||
html = sla_text.read()
|
||||
unicode = html.decode("windows-1252")
|
||||
encoded_str = unicode.encode("ascii", "xmlcharrefreplace")
|
||||
read = cmds.scrollField(editable=False, wordWrap=True, height=300,
|
||||
text=unicode, backgroundColor=(1.0,1.0,1.0))
|
||||
agree = cmds.button(l='Agree', c='maya.cmds.layoutDialog( dismiss="Agree" )' )
|
||||
disagree = cmds.button(l='Disagree', c='maya.cmds.layoutDialog( dismiss="Disagree" )' )
|
||||
cmds.formLayout(form, edit=True,
|
||||
attachForm=[(heading, 'top', 10), (heading, 'left', 10),
|
||||
(heading, 'right', 10), (read, 'left', 10),
|
||||
(read, 'right', 10), (text, 'left', 10),
|
||||
(text, 'right', 10), (agree, 'left', 10),
|
||||
(agree, 'bottom', 10), (disagree, 'right', 10),
|
||||
(disagree, 'bottom', 10)],
|
||||
attachNone=[(text, 'bottom'), (read, 'bottom')],
|
||||
attachControl=[(text, 'top', 10, heading),
|
||||
(read, 'top', 10, text),
|
||||
(agree, 'top', 50, read),
|
||||
(disagree, 'top', 50, read)],
|
||||
attachPosition=[(agree, 'right', 5, 50),
|
||||
(disagree, 'left', 5, 50)])
|
||||
|
||||
|
||||
class AzureBatchSetup(OpenMayaMPx.MPxCommand):
|
||||
"""Plug-in Setup Module."""
|
||||
|
||||
def __init__(self):
|
||||
OpenMayaMPx.MPxCommand.__init__(self)
|
||||
|
||||
@staticmethod
|
||||
def clean(path):
|
||||
"""Modify Windows paths for writing to files.
|
||||
:param str path: The path to clean.
|
||||
"""
|
||||
if os.sep == "\\":
|
||||
return path.replace(os.sep, "\\\\")
|
||||
return path
|
||||
|
||||
@staticmethod
|
||||
def create_modfile(mod_path, plugin_path):
|
||||
"""Write all environment variables to mod file at a given
|
||||
location.
|
||||
|
||||
:param str mod_path: The module directory path in which we'll attempt
|
||||
to create the mod file.
|
||||
:param str plugin_path: The directory where the plug-in files
|
||||
currently reside.
|
||||
:returns: True if the mod file was successfully created, else
|
||||
False.
|
||||
"""
|
||||
try:
|
||||
modfile = os.path.join(mod_path, "AzureBatch.mod")
|
||||
with open(modfile, 'w') as mod:
|
||||
mod.write("+ AzureBatch {0} {1}\n".format(
|
||||
VERSION, plugin_path))
|
||||
mod.write("MAYA_PLUG_IN_PATH+={0}\n".format(
|
||||
os.path.join(plugin_path, "plug-in")))
|
||||
mod.write("MAYA_SCRIPT_PATH+:=mel\n")
|
||||
mod.write("AZUREBATCH_ICONS:=icons\n")
|
||||
mod.write("AZUREBATCH_TEMPLATES:=templates\n")
|
||||
mod.write("AZUREBATCH_SCRIPTS:=scripts\n")
|
||||
mod.write("AZUREBATCH_SCRIPTS+:=scripts/ui\n")
|
||||
mod.write("AZUREBATCH_TOOLS:=scripts/tools\n")
|
||||
mod.write("AZUREBATCH_MODULES:=modules")
|
||||
|
||||
print("Successfully created mod file at %s" % mod_path)
|
||||
print("Setting environment variables for current session.")
|
||||
os.environ["AZUREBATCH_SCRIPTS"] = "{0};{1};".format(
|
||||
AzureBatchSetup.clean(os.path.join(plugin_path,
|
||||
"scripts")),
|
||||
AzureBatchSetup.clean(os.path.join(plugin_path,
|
||||
"scripts", "ui")))
|
||||
return True
|
||||
except Exception as err:
|
||||
print(str(err))
|
||||
print("Couldn't create mod file at %s" % mod_path)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def find_modules_locations(plugin_path):
|
||||
"""Iterate through paths in MAYA_MODULE_PATH to find appropriate
|
||||
directory to create mod file.
|
||||
Attempts to create a module directory at the specified path if
|
||||
it doesn't already exist.
|
||||
|
||||
:param str plugin_path: Path where the plug-in files currently
|
||||
reside.
|
||||
:returns: True if a directory is found and mod file successfully
|
||||
created there, else False.
|
||||
"""
|
||||
modulepaths = os.environ["MAYA_MODULE_PATH"].split(os.pathsep)
|
||||
modulepaths.reverse()
|
||||
for path in modulepaths:
|
||||
if not os.path.isdir(path):
|
||||
try:
|
||||
os.makedirs(p)
|
||||
except:
|
||||
print("Module directory doesn't exist, "
|
||||
"and cannot create it: %s" % path)
|
||||
continue
|
||||
if AzureBatchSetup.create_modfile(path, plugin_path):
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def find_env_location(plugin_path):
|
||||
"""Find directory of Maya.env files.
|
||||
|
||||
:param str plugin_path: Path where the plug-in files currently
|
||||
reside.
|
||||
:returns: True if a custom module path is successfully added to
|
||||
Maya.env, else False.
|
||||
"""
|
||||
maya_app_dir = os.path.normpath(os.environ.get('MAYA_APP_DIR', ""))
|
||||
maya_env = os.path.join(maya_app_dir, "Maya.env")
|
||||
if not os.path.isfile(maya_env):
|
||||
maya_env = os.path.join(mel.eval("about -preferences"), "Maya.env")
|
||||
maya_env = os.path.normpath(maya_env)
|
||||
return AzureBatchSetup.add_modulepath_to_env(plugin_path, maya_env)
|
||||
|
||||
@staticmethod
|
||||
def add_modulepath_to_env(plugin_path, env_path):
|
||||
"""Add a new entry for MAYA_MODULE_PATH to Maya.env and create mod
|
||||
file at this path.
|
||||
|
||||
:param str plugin_path: Path where the plug-in files currently
|
||||
reside.
|
||||
:param str env_path: Path to Maya.env.
|
||||
:returns: True if Maya.env configured and mod file created,
|
||||
else False.
|
||||
"""
|
||||
plugin_mods = os.path.join(plugin_path, "modules")
|
||||
open_format = 'a' if os.path.exists(env_path) else 'w'
|
||||
try:
|
||||
with open(env_path, open_format) as modfile:
|
||||
if open_format == 'a' and modfile.tell() != 0:
|
||||
modfile.seek(-1, os.SEEK_END)
|
||||
next_char = modfile.read(1)
|
||||
if next_char != '\n':
|
||||
modfile.write('\n')
|
||||
if os.pathsep == ';':
|
||||
modfile.write(
|
||||
"MAYA_MODULE_PATH=%MAYA_MODULE_PATH%;{0}".format(
|
||||
plugin_mods))
|
||||
else:
|
||||
modfile.write(
|
||||
"MAYA_MODULE_PATH=$MAYA_MODULE_PATH:{0}".format(
|
||||
plugin_mods))
|
||||
return AzureBatchSetup.create_modfile(plugin_mods, plugin_path)
|
||||
except Exception as exp:
|
||||
print("Couldn't create new maya env file: %s" % env_path)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def remove_environment():
|
||||
"""Iterate through paths in MAYA_MODULE_PATH to find and remove
|
||||
any mod files.
|
||||
"""
|
||||
modulepaths = os.environ.get("MAYA_MODULE_PATH", "").split(os.pathsep)
|
||||
modulepaths.reverse()
|
||||
for p in modulepaths:
|
||||
modfile = os.path.join(p, "AzureBatch.mod")
|
||||
if os.path.exists(modfile):
|
||||
try:
|
||||
print("Removing mod file from {0}".format(modfile))
|
||||
os.remove(modfile)
|
||||
except:
|
||||
print("Found AzureBatch mod file {0}, but "
|
||||
"couldn't delete.".format(modfile))
|
||||
message = "Remove installed Python dependencies?"
|
||||
del_python = cmds.confirmDialog(
|
||||
title='Azure Batch', message=message, button=['Yes','No'],
|
||||
defaultButton='No', dismissString='No')
|
||||
|
||||
if del_python == 'Yes':
|
||||
try:
|
||||
print("Removing Python dependencies: {0}".format(INSTALL_DIR))
|
||||
shutil.rmtree(INSTALL_DIR)
|
||||
except:
|
||||
print("Couldn't remove {0}".format(INSTALL_DIR))
|
||||
|
||||
@staticmethod
|
||||
def set_environment(plugin_path):
|
||||
"""Set environment variables for current session, if they haven't
|
||||
been set by a mod file on startup. (I.e. after first installed)
|
||||
Attempt to create mod file for future sessions.
|
||||
|
||||
:param str plugin_path: The directory where the plug-in files
|
||||
currently reside.
|
||||
"""
|
||||
srcpath = os.path.join(plugin_path, "scripts")
|
||||
icnpath = os.path.join(plugin_path, "icons")
|
||||
melpath = os.path.join(plugin_path, "mel")
|
||||
modpath = os.path.join(plugin_path, "modules")
|
||||
tplpath = os.path.join(plugin_path, "templates")
|
||||
tolpath = os.path.join(plugin_path, "scripts", "tools")
|
||||
sys.path.append(modpath)
|
||||
sys.path.append(srcpath)
|
||||
sys.path.append(os.path.join(srcpath, "ui"))
|
||||
|
||||
script_dirs = os.environ["MAYA_SCRIPT_PATH"] + os.pathsep
|
||||
os.environ["AZUREBATCH_ICONS"] = AzureBatchSetup.clean(icnpath)
|
||||
os.environ["AZUREBATCH_MODULES"] = AzureBatchSetup.clean(modpath)
|
||||
os.environ["AZUREBATCH_TEMPLATES"] = AzureBatchSetup.clean(tplpath)
|
||||
os.environ["AZUREBATCH_TOOLS"] = AzureBatchSetup.clean(tolpath)
|
||||
os.environ["MAYA_SCRIPT_PATH"] = script_dirs + \
|
||||
AzureBatchSetup.clean(melpath)
|
||||
print("Attempting to create mod file under MAYA_MODULE_PATH")
|
||||
mods = AzureBatchSetup.find_modules_locations(plugin_path)
|
||||
|
||||
if not mods:
|
||||
print("Attempting to add custom module path to Maya.env")
|
||||
mods = AzureBatchSetup.find_env_location(plugin_path)
|
||||
if not mods:
|
||||
print("Failed to setup AzureBatch mod file")
|
||||
return os.environ["MAYA_MODULE_PATH"] + os.pathsep
|
||||
|
||||
|
||||
def cmd_creator():
|
||||
"""Create set up command"""
|
||||
return OpenMayaMPx.asMPxPtr(AzureBatchSetup())
|
||||
|
||||
|
||||
def setup_module():
|
||||
"""Set up module environment"""
|
||||
current_file = inspect.getfile(inspect.currentframe())
|
||||
current_dir = os.path.dirname(os.path.abspath(current_file))
|
||||
plugin_path = os.path.split(current_dir)[0] + os.sep
|
||||
AzureBatchSetup.set_environment(plugin_path)
|
||||
|
||||
|
||||
def get_usershelf_dir():
|
||||
"""Get directory of saved shelf preferences."""
|
||||
shelf_dirs = mel.eval("internalVar -userShelfDir").split(os.pathsep)
|
||||
for dir in shelf_dirs:
|
||||
if (dir.startswith(mel.eval("internalVar -userPrefDir")) and \
|
||||
(dir.endswith("/prefs/shelves/"))):
|
||||
melPath = os.path.join(os.path.normpath(dir), SHELF_FILE)
|
||||
if os.path.exists(melPath):
|
||||
return melPath
|
||||
|
||||
prefs = mel.eval("about -preferences")
|
||||
pref_dir = os.path.join(prefs, "prefs", "shelves", SHELF_FILE)
|
||||
return os.path.normpath(pref_dir)
|
||||
|
||||
|
||||
def remove_ui(clientData):
|
||||
"""Remove shelf preferences if plug-in deselected."""
|
||||
try:
|
||||
try:
|
||||
mel.eval("""deleteUI -layout('AzureBatch')""")
|
||||
except:
|
||||
print("Couldn't delete shelf")
|
||||
melPath = get_usershelf_dir()
|
||||
if os.path.exists("{0}.deleted".format(melPath)):
|
||||
os.remove("{0}.deleted".format(melPath))
|
||||
os.rename(melPath, "{0}.deleted".format(melPath))
|
||||
except Exception as e:
|
||||
print("Failed to load", (str(e)))
|
||||
|
||||
|
||||
def dependency_installed(package):
|
||||
"""Check if the specified package is installed and up-to-date.
|
||||
:param str package: A pip-formatted package reference.
|
||||
"""
|
||||
try:
|
||||
package_ref = package.split('==')
|
||||
module = importlib.import_module(package_ref[0].replace('-', '.'))
|
||||
if hasattr(module, '__version__') and len(package_ref) > 1:
|
||||
if StrictVersion(package_ref[1]) > StrictVersion(getattr(module, '__version__')):
|
||||
raise ImportError("Installed package out of date")
|
||||
except ImportError:
|
||||
print("Unable to load {}".format(package))
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def install_pkg(package):
|
||||
"""Install the specified package by shelling out to pip.
|
||||
:param str package: A pip-formatted package reference.
|
||||
|
||||
TODO: Check if there's a better way to bypass the verification error.
|
||||
TODO: Check if this works for package upgrades
|
||||
"""
|
||||
pip_cmds = ['mayapy', os.path.join(INSTALL_DIR, 'pip'),
|
||||
'install', package,
|
||||
'--target', INSTALL_DIR,
|
||||
'--index-url', 'http://pypi.python.org/simple/',
|
||||
'--trusted-host', 'pypi.python.org']
|
||||
print(pip_cmds)
|
||||
installer = subprocess.Popen(pip_cmds)
|
||||
installer.wait()
|
||||
if installer.returncode != 0:
|
||||
raise RuntimeError("Failed to install package: {}".format(package))
|
||||
|
||||
|
||||
def install_namespace_pkg(package, namespace):
|
||||
"""Azure packages have issues installing one by one as the don't
|
||||
unpackage correctly into the namespace directory. So we have to install
|
||||
to a temp directory and move it to the right place.
|
||||
|
||||
:param str package: A pip-formatted package reference.
|
||||
:param str namespace: The package namespace to unpack to.
|
||||
"""
|
||||
temp_target = os.path.join(INSTALL_DIR, 'temp-target')
|
||||
pip_cmds = ['mayapy', os.path.join(INSTALL_DIR, 'pip'),
|
||||
'install', package,
|
||||
'--no-deps',
|
||||
'--target', temp_target,
|
||||
'--index-url', 'http://pypi.python.org/simple/',
|
||||
'--trusted-host', 'pypi.python.org']
|
||||
installer = subprocess.Popen(pip_cmds)
|
||||
installer.wait()
|
||||
if installer.returncode == 0:
|
||||
try:
|
||||
shutil.copytree(os.path.join(temp_target, namespace), os.path.join(INSTALL_DIR, namespace))
|
||||
except Exception as e:
|
||||
print(e)
|
||||
try:
|
||||
shutil.rmtree(temp_target)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
def initializePlugin(obj):
|
||||
"""Initialize Plug-in"""
|
||||
print("Initializing Azure Batch plug-in")
|
||||
existing = cmds.optionVar(exists=SLA_PREF)
|
||||
if not existing:
|
||||
agree = cmds.layoutDialog(ui=sla_prompt, title="Azure Batch Maya Client")
|
||||
if str(agree) != 'Agree':
|
||||
raise RuntimeError("Plugin initialization aborted.")
|
||||
cmds.optionVar(stringValue=(SLA_PREF, VERSION))
|
||||
else:
|
||||
agreed = cmds.optionVar(query=SLA_PREF)
|
||||
if StrictVersion(agreed) < VERSION:
|
||||
agree = cmds.layoutDialog(ui=sla_prompt, title="AzureBatch Maya Client")
|
||||
if str(agree) != 'Agree':
|
||||
raise RuntimeError("Plugin initialization aborted.")
|
||||
cmds.optionVar(stringValue=(SLA_PREF, VERSION))
|
||||
|
||||
print("Checking for dependencies...")
|
||||
missing_libs = []
|
||||
for package in REQUIREMENTS:
|
||||
if not dependency_installed(package):
|
||||
missing_libs.append(package)
|
||||
for package in NAMESPACE_PACAKGES:
|
||||
if not dependency_installed(package):
|
||||
missing_libs.append(package)
|
||||
if missing_libs:
|
||||
message = ("One or more dependencies are missing or out-of-date."
|
||||
"\nWould you like to install the following?\n\n")
|
||||
for lib in missing_libs:
|
||||
message += "{0} v{1}\n".format(*lib.split('=='))
|
||||
install = cmds.confirmDialog(
|
||||
title='Azure Batch', message=message, button=['Yes','No'],
|
||||
defaultButton='Yes', cancelButton='No', dismissString='No')
|
||||
|
||||
if install == "No":
|
||||
cmds.confirmDialog(
|
||||
message="Could not load Azure Batch plug-in", button='OK')
|
||||
raise ImportError("Failed to load Azure Batch - "
|
||||
"missing one or more dependencies")
|
||||
|
||||
print("Attempting to install dependencies via Pip.")
|
||||
try:
|
||||
os.environ['PYTHONPATH'] = INSTALL_DIR + os.pathsep + os.environ['PYTHONPATH']
|
||||
install_script = os.path.normpath(os.path.join( os.environ['AZUREBATCH_TOOLS'], 'install_pip.py'))
|
||||
installer = subprocess.Popen(["mayapy", install_script, '--target', INSTALL_DIR])
|
||||
installer.wait()
|
||||
if installer.returncode != 0:
|
||||
raise RuntimeError("Failed to install pip")
|
||||
except BaseException as exp:
|
||||
print("Failed to install Pip. Please install dependencies manually to continue.")
|
||||
raise
|
||||
try:
|
||||
print("Installing dependencies")
|
||||
for package in missing_libs:
|
||||
install_pkg(package)
|
||||
if package in NAMESPACE_PACAKGES:
|
||||
package_path = package.split('==')[0].split('-')
|
||||
install_namespace_pkg(package, os.path.join(*package_path))
|
||||
except:
|
||||
error = "Failed to install dependencies - please install manually"
|
||||
cmds.confirmDialog(message=error, button='OK')
|
||||
raise ImportError(error)
|
||||
message = ("One or more dependencies have been successfully installed."
|
||||
"\n Please restart Maya to complete installation.")
|
||||
cmds.confirmDialog(message=message, button='OK')
|
||||
raise ImportError("Please restart Maya. Azure Batch installed "
|
||||
"Python dependencies.")
|
||||
|
||||
print("Dependency check complete!")
|
||||
plugin = OpenMayaMPx.MFnPlugin(
|
||||
obj, "Microsoft Corporation", VERSION, "Any")
|
||||
plugin.registerCommand(cmd_name, cmd_creator)
|
||||
|
||||
try:
|
||||
if (mel.eval("""shelfLayout -exists "AzureBatch" """) == 0):
|
||||
mel.eval('addNewShelfTab %s' % "AzureBatch")
|
||||
mel.eval("""source "create_shelf.mel" """)
|
||||
melPath = get_usershelf_dir()
|
||||
if os.path.exists("{0}.deleted".format(melPath)):
|
||||
os.remove("{0}.deleted".format(melPath))
|
||||
os.rename(melPath, "{0}.deleted".format(melPath))
|
||||
except Exception as exp:
|
||||
print("Couldn't add shelf: {}".format(exp))
|
||||
|
||||
# Add callback to clean up UI when Maya exits
|
||||
global fMayaExitingCB
|
||||
fMayaExitingCB = OpenMaya.MSceneMessage.addCallback(
|
||||
OpenMaya.MSceneMessage.kMayaExiting, remove_ui)
|
||||
|
||||
|
||||
def uninitializePlugin(obj):
|
||||
"""Remove and uninstall plugin."""
|
||||
print("Removing AzureBatch plug-in")
|
||||
plugin = MFnPlugin(obj)
|
||||
plugin.deregisterCommand(cmd_name)
|
||||
try:
|
||||
mel.eval('deleteShelfTab %s' % "AzureBatch")
|
||||
except:
|
||||
print("Couldn't delete shelf")
|
||||
global fMayaExitingCB
|
||||
if (fMayaExitingCB is not None):
|
||||
OpenMaya.MSceneMessage.removeCallback(fMayaExitingCB)
|
||||
if cmds.window("AzureBatch", exists=1):
|
||||
cmds.deleteUI("AzureBatch")
|
||||
AzureBatchSetup.remove_environment()
|
||||
print("Finished clearing up all AzureBatch components")
|
||||
|
||||
|
||||
"""Check for environment and set up if not found."""
|
||||
try:
|
||||
sys.path.extend(os.environ["AZUREBATCH_SCRIPTS"].split(os.pathsep))
|
||||
sys.path.append(os.environ['AZUREBATCH_MODULES'])
|
||||
except KeyError as e:
|
||||
print("Couldn't find AzureBatch environment, setting up now...")
|
||||
setup_module()
|
|
@ -0,0 +1,524 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
try:
|
||||
from maya import cmds, mel, utils
|
||||
import maya.OpenMaya as om
|
||||
import maya.OpenMayaMPx as omp
|
||||
except ImportError:
|
||||
print("No maya module found.")
|
||||
|
||||
import os
|
||||
import logging
|
||||
LOG = logging.getLogger('AzureBatchMaya')
|
||||
|
||||
|
||||
class MayaAPI(object):
|
||||
|
||||
@staticmethod
|
||||
def refresh():
|
||||
cmds.refresh()
|
||||
|
||||
@staticmethod
|
||||
def mel(command):
|
||||
try:
|
||||
return mel.eval(command)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'mel': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_list(**kwargs):
|
||||
try:
|
||||
return cmds.ls(**kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'get_list': {0}".format(exp).strip())
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def get_attr(*args):
|
||||
try:
|
||||
return cmds.getAttr(*args)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'get_attr': {0}".format(exp).strip())
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def file(**kwargs):
|
||||
try:
|
||||
return cmds.file(**kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'file': {0}".format(exp).strip())
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def reference(*args, **kwargs):
|
||||
try:
|
||||
return cmds.referenceQuery(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'reference': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def animated():
|
||||
return MayaAPI.get_attr("defaultRenderGlobals.animation")
|
||||
|
||||
@staticmethod
|
||||
def start_frame():
|
||||
if MayaAPI.animated():
|
||||
return int(MayaAPI.get_attr("defaultRenderGlobals.startFrame"))
|
||||
else:
|
||||
return int(cmds.currentTime(query=True))
|
||||
|
||||
@staticmethod
|
||||
def end_frame():
|
||||
animated = mel.eval("getAttr defaultRenderGlobals.animation")
|
||||
if MayaAPI.animated():
|
||||
return int(MayaAPI.get_attr("defaultRenderGlobals.endFrame"))
|
||||
else:
|
||||
return int(cmds.currentTime(query=True))
|
||||
|
||||
@staticmethod
|
||||
def frame_step():
|
||||
return int(mel.eval("getAttr defaultRenderGlobals.byFrameStep"))
|
||||
|
||||
@staticmethod
|
||||
def file_select(**kwargs):
|
||||
return cmds.fileDialog2(dialogStyle=2, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def error(message):
|
||||
LOG.warning(message)
|
||||
return cmds.confirmDialog(title="Error",
|
||||
message=message,
|
||||
messageAlign="left",
|
||||
button="OK",
|
||||
icon="critical")
|
||||
|
||||
@staticmethod
|
||||
def warning(message):
|
||||
LOG.warning(message)
|
||||
return cmds.confirmDialog(title="Warning",
|
||||
message=message,
|
||||
messageAlign="left",
|
||||
button="OK",
|
||||
icon="warning")
|
||||
|
||||
@staticmethod
|
||||
def info(message):
|
||||
LOG.info(message)
|
||||
return cmds.confirmDialog(title="",
|
||||
message=message,
|
||||
messageAlign="left",
|
||||
button="OK",
|
||||
icon="information")
|
||||
|
||||
@staticmethod
|
||||
def confirm(message, options):
|
||||
LOG.info(message)
|
||||
return cmds.confirmDialog(title="",
|
||||
message=message,
|
||||
messageAlign="left",
|
||||
button=options,
|
||||
defaultButton=options[-1],
|
||||
cancelButton=options[-1],
|
||||
dismissString=options[-1],
|
||||
icon="information")
|
||||
|
||||
@staticmethod
|
||||
def workspace(*args, **kwargs):
|
||||
try:
|
||||
return cmds.workspace(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'workspace': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def dependency_nodes():
|
||||
return NodeIterator()
|
||||
|
||||
@staticmethod
|
||||
def node_iterator():
|
||||
return om.MItDependencyNodes()
|
||||
|
||||
@staticmethod
|
||||
def dependency_node(node):
|
||||
return om.MFnDependencyNode(node)
|
||||
|
||||
@staticmethod
|
||||
def node_attribute(node, dep_node, attr):
|
||||
attr_obj = dep_node.attribute(attr)
|
||||
return om.MPlug(node, attr_obj)
|
||||
|
||||
@staticmethod
|
||||
def contentinfo_table():
|
||||
return omp.MExternalContentInfoTable()
|
||||
|
||||
@staticmethod
|
||||
def delete_ui(*args, **kwargs):
|
||||
try:
|
||||
cmds.deleteUI(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'delete_ui': {0}".format(exp).strip())
|
||||
|
||||
@staticmethod
|
||||
def parent(parent='..'):
|
||||
cmds.setParent(parent)
|
||||
|
||||
@staticmethod
|
||||
def text(*args, **kwargs):
|
||||
try:
|
||||
return cmds.text(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'text': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def text_field(*args, **kwargs):
|
||||
try:
|
||||
return cmds.textField(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'text_field': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def button(*args, **kwargs):
|
||||
try:
|
||||
return cmds.button(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'button': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def symbol_button(*args, **kwargs):
|
||||
try:
|
||||
return cmds.symbolButton(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'symbol_button': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def icon_button(*args, **kwargs):
|
||||
try:
|
||||
return cmds.iconTextButton(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'icon_button': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def check_box(*args, **kwargs):
|
||||
try:
|
||||
return cmds.checkBox(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'check_box': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def symbol_check_box(*args, **kwargs):
|
||||
try:
|
||||
if kwargs.get('query') or kwargs.get('q'):
|
||||
return cmds.symbolCheckBox(*args, **kwargs)
|
||||
return cmds.symbolCheckBox(*args,
|
||||
onImage="precompExportChecked.png",
|
||||
offImage="precompExportUnchecked.png",
|
||||
**kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'symbol_check_box': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def image(*args, **kwargs):
|
||||
try:
|
||||
return cmds.image(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'image': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def row_layout(*args, **kwargs):
|
||||
try:
|
||||
return cmds.columnLayout(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'row_layout': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def row(*args, **kwargs):
|
||||
try:
|
||||
return cmds.rowLayout(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'row_': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def col_layout(*args, **kwargs):
|
||||
try:
|
||||
return cmds.rowColumnLayout(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'col_layout': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def frame_layout(*args, **kwargs):
|
||||
try:
|
||||
return cmds.frameLayout(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'frame_layout': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def scroll_layout(*args, **kwargs):
|
||||
try:
|
||||
return cmds.scrollLayout(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'scroll_layout': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def form_layout(*args, **kwargs):
|
||||
try:
|
||||
return cmds.formLayout(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'form_layout': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def tab_layout(*args, **kwargs):
|
||||
try:
|
||||
return cmds.tabLayout(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'tab_layout': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def grid_layout(*args, **kwargs):
|
||||
try:
|
||||
return cmds.gridLayout (*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'grid_layout': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def int_slider(*args, **kwargs):
|
||||
try:
|
||||
return cmds.intSliderGrp(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'int_slider': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def popup_menu(*args, **kwargs):
|
||||
try:
|
||||
return cmds.popupMenu(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'popup_menu': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def menu(*args, **kwargs):
|
||||
try:
|
||||
return cmds.optionMenu(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'menu': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def menu_option(*args, **kwargs):
|
||||
try:
|
||||
return cmds.menuItem(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'menu_option': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def radio_group(*args, **kwargs):
|
||||
try:
|
||||
return cmds.radioButtonGrp(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'radio_group': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def table(*args, **kwargs):
|
||||
try:
|
||||
return cmds.scriptTable(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'table': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def progress_bar(*args, **kwargs):
|
||||
try:
|
||||
return cmds.progressBar(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'progress_bar': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def window(*args, **kwargs):
|
||||
try:
|
||||
return cmds.window(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'window': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def show(ui):
|
||||
try:
|
||||
cmds.showWindow(ui)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'show': {0}".format(exp).strip())
|
||||
|
||||
@staticmethod
|
||||
def execute(*args, **kwargs):
|
||||
cmds.evalDeferred(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def execute_in_main_thread(*args, **kwargs):
|
||||
return utils.executeInMainThreadWithResult(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def plugins(*args, **kwargs):
|
||||
try:
|
||||
return cmds.pluginInfo(*args, **kwargs)
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'plugins': {0}".format(exp).strip())
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def prefs_dir():
|
||||
return cmds.internalVar(userPrefDir=True)
|
||||
|
||||
@staticmethod
|
||||
def script_dir():
|
||||
return cmds.internalVar(userScriptDir=True)
|
||||
|
||||
|
||||
class NodeIterator(object):
|
||||
|
||||
def __init__(self):
|
||||
try:
|
||||
self._iter = MayaAPI.node_iterator()
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'NodeIterator': {0}".format(exp).strip())
|
||||
self._iter = iter([])
|
||||
self._current = None
|
||||
|
||||
def get_references(self):
|
||||
assets = MayaReferences(self._current)
|
||||
self._iter.next()
|
||||
return assets.get_paths()
|
||||
|
||||
def is_done(self):
|
||||
try:
|
||||
is_complete = self._iter.isDone()
|
||||
if not is_complete:
|
||||
self._current = self._iter.thisNode()
|
||||
return is_complete
|
||||
except Exception as exp:
|
||||
LOG.debug("MayaAPI exception in 'NodeIterator': {0}".format(exp).strip())
|
||||
True
|
||||
|
||||
class MayaCallbacks(object):
|
||||
"""Add callbacks to Maya's event handlers for various scene file
|
||||
events, including saving, loading, creating etc.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def after_save(func):
|
||||
return om.MSceneMessage.addCallback(om.MSceneMessage.kAfterSave, func)
|
||||
|
||||
@staticmethod
|
||||
def after_new(func):
|
||||
return om.MSceneMessage.addCallback(om.MSceneMessage.kAfterNew, func)
|
||||
|
||||
@staticmethod
|
||||
def after_open(func):
|
||||
return om.MSceneMessage.addCallback(om.MSceneMessage.kAfterOpen, func)
|
||||
|
||||
@staticmethod
|
||||
def after_read(func):
|
||||
return om.MSceneMessage.addCallback(om.MSceneMessage.kAfterFileRead, func)
|
||||
|
||||
@staticmethod
|
||||
def remove(callback):
|
||||
om.MSceneMessage.removeCallback(callback)
|
||||
|
||||
|
||||
class MayaReferences(object):
|
||||
|
||||
def __init__(self, node):
|
||||
self._node = node
|
||||
self._dep_node = MayaAPI.dependency_node(node)
|
||||
self._table = MayaAPI.contentinfo_table()
|
||||
|
||||
def get_paths(self):
|
||||
self._dep_node.getExternalContent(self._table)
|
||||
paths = []
|
||||
for i in range(self._table.length()):
|
||||
_path = []
|
||||
_node = ""
|
||||
_role = []
|
||||
self._table.getEntryByIndex(i, _path, _node, _role)
|
||||
if _path and '.' in _path[0]:
|
||||
paths.append(_path[0])
|
||||
token_ref = self.token_paths()
|
||||
if token_ref:
|
||||
paths.append(token_ref)
|
||||
return paths
|
||||
|
||||
def token_paths(self):
|
||||
try:
|
||||
pattern = MayaAPI.node_attribute(
|
||||
self._node, self._dep_node, "computedFileTextureNamePattern")
|
||||
pattern = str(pattern.asString())
|
||||
|
||||
if "<udim>" in pattern.lower():
|
||||
LOG.debug("Found UDIM reference: {0}".format(pattern))
|
||||
pattern = pattern.replace("<UDIM>", "[0-9][0-9][0-9][0-9]")
|
||||
pattern = pattern.replace("<udim>", "[0-9][0-9][0-9][0-9]")
|
||||
return os.path.normpath(pattern)
|
||||
|
||||
elif "u<u>_v<v>" in pattern.lower():
|
||||
LOG.debug("Found UV reference: {0}".format(pattern))
|
||||
pattern = pattern.replace("<u>", "*")
|
||||
pattern = pattern.replace("<v>", "*")
|
||||
pattern = pattern.replace("<f>", "*")
|
||||
pattern = pattern.replace("<U>", "*")
|
||||
pattern = pattern.replace("<V>", "*")
|
||||
pattern = pattern.replace("<F>", "*")
|
||||
return os.path.normpath(pattern)
|
||||
|
||||
elif "<tile>" in pattern.lower():
|
||||
LOG.debug("Found tile reference: {0}".format(pattern))
|
||||
pattern = pattern.replace("<tile>", "_u*_v*")
|
||||
pattern = pattern.replace("<TILE>", "_u*_v*")
|
||||
return os.path.normpath(pattern)
|
||||
|
||||
except Exception as exp:
|
||||
return None
|
|
@ -0,0 +1,706 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
import threading
|
||||
import os
|
||||
import sys
|
||||
import glob
|
||||
import pkgutil
|
||||
import inspect
|
||||
import importlib
|
||||
import tempfile
|
||||
from Queue import Queue
|
||||
|
||||
from api import MayaAPI as maya
|
||||
from api import MayaCallbacks as callback
|
||||
|
||||
import utils
|
||||
from utils import ProgressBar
|
||||
from exception import CancellationException, FileUploadException
|
||||
|
||||
from ui_assets import AssetsUI
|
||||
from default import AzureBatchRenderAssets
|
||||
|
||||
|
||||
SYS_SEARCHPATHS = []
|
||||
USR_SEARCHPATHS = []
|
||||
UPLOAD_THREADS = 10
|
||||
|
||||
|
||||
class AzureBatchAssets(object):
|
||||
"""Handler for asset file functionality."""
|
||||
|
||||
def __init__(self, frame, call):
|
||||
"""Create new Asset Handler.
|
||||
|
||||
:param frame: The shared plug-in UI frame.
|
||||
:type frame: :class:`.AzureBatchUI`
|
||||
:param func call: The shared REST API call wrapper.
|
||||
"""
|
||||
self._log = logging.getLogger('AzureBatchMaya')
|
||||
self._call = call
|
||||
self._session = None
|
||||
self._assets = None
|
||||
|
||||
self.batch = None
|
||||
self.modules = self._collect_modules()
|
||||
self.ui = AssetsUI(self, frame)
|
||||
self.frame = frame
|
||||
|
||||
callback.after_new(self._callback_refresh)
|
||||
callback.after_read(self._callback_refresh)
|
||||
|
||||
def _callback_refresh(self, *args):
|
||||
"""Called by Maya when a new scene file is loaded, so we reset
|
||||
the asset and submission pages of the UI, as the file references
|
||||
and potentially the selected render engine will have changed.
|
||||
"""
|
||||
global USR_SEARCHPATHS
|
||||
USR_SEARCHPATHS = []
|
||||
self._set_searchpaths()
|
||||
|
||||
if self.frame.selected_tab() == 3:
|
||||
# We only want to do a full refresh if Assets is the current tab
|
||||
self.ui.refresh()
|
||||
elif self.ui.ready:
|
||||
# Otherwise we'll just reset it to be more efficient
|
||||
self.ui.ready = False
|
||||
|
||||
def _collect_modules(self):
|
||||
"""Collect the renderer-specific submission modules. This is where
|
||||
the renderer-specfic asset collection is defined.
|
||||
"""
|
||||
self._log.info("Collecting modules...")
|
||||
render_modules = []
|
||||
module_dir = os.environ['AZUREBATCH_MODULES']
|
||||
|
||||
for importer, package_name, _ in pkgutil.iter_modules([module_dir]):
|
||||
if package_name == "default":
|
||||
continue
|
||||
try:
|
||||
module = importlib.import_module(package_name)
|
||||
for name, obj in inspect.getmembers(module, inspect.isclass):
|
||||
if issubclass(obj, AzureBatchRenderAssets):
|
||||
render_modules.append(obj())
|
||||
except ImportError as err:
|
||||
self._log.warning("Couldn't import module: {0}".format(package_name))
|
||||
return render_modules
|
||||
|
||||
def _set_searchpaths(self):
|
||||
"""Set the search path collection that will be used to resolve relative or
|
||||
invalid asset reference paths. This is reset each time a new file is loaded
|
||||
as we use the current location of the scene file as a search path.
|
||||
We also use the current project directory, the sourceimages directory and the
|
||||
current working directory.
|
||||
"""
|
||||
global SYS_SEARCHPATHS
|
||||
SYS_SEARCHPATHS = []
|
||||
scene = os.path.abspath(maya.file(q=True, sn=True))
|
||||
if ((scene.endswith('.mb')) or (scene.endswith('.ma'))) and (os.path.exists(scene)):
|
||||
SYS_SEARCHPATHS.append(os.path.dirname(scene))
|
||||
proj = maya.workspace(query=True, rootDirectory=True)
|
||||
SYS_SEARCHPATHS.append(proj)
|
||||
SYS_SEARCHPATHS.append(os.path.join(proj, "sourceimages"))
|
||||
SYS_SEARCHPATHS.append(maya.workspace(query=True, directory=True))
|
||||
SYS_SEARCHPATHS.append(os.getcwd())
|
||||
SYS_SEARCHPATHS = list(set(SYS_SEARCHPATHS))
|
||||
|
||||
def _configure_renderer(self):
|
||||
"""Configure the renderer-specific asset collection according
|
||||
to the currently selected render engine.
|
||||
Only called by the set_assets function, which is called on loading and
|
||||
refreshing the assets tab.
|
||||
"""
|
||||
current_renderer = maya.get_attr("defaultRenderGlobals.currentRenderer")
|
||||
self._log.info("Current renderer: {0}".format(current_renderer))
|
||||
|
||||
for module in self.modules:
|
||||
if not hasattr(module, 'render_engine'):
|
||||
self._log.warning("Module {0} has no render engine attribute. Skipping.".format(module))
|
||||
continue
|
||||
if module.render_engine == str(current_renderer):
|
||||
self.renderer = module
|
||||
self._log.debug("Configured renderer to {0}".format(self.renderer.render_engine))
|
||||
return
|
||||
self.renderer = AzureBatchRenderAssets()
|
||||
self._log.debug("Configured renderer to {0}".format(self.renderer.render_engine))
|
||||
|
||||
def _collect_assets(self):
|
||||
"""Called on upload. If the asset tab has not yet been loaded before
|
||||
job submission is attempted, then the asset references have not yet
|
||||
been populated, so gathers the assets if they need it, otherwise return
|
||||
the current list of asset references.
|
||||
"""
|
||||
if not self.ui.ready:
|
||||
self.ui.prepare()
|
||||
return self._assets.collect()
|
||||
|
||||
def _create_path_map(self, plugins, os_flavor):
|
||||
"""Create the pre-render mel script to redirect all the asset reference
|
||||
directories for this render. Called on job submission, and the resulting
|
||||
file is uploaded as an asset to the current file group.
|
||||
|
||||
:param plugins: A list of the currently enabled plugins, so that these can
|
||||
also be enabled on the server.
|
||||
:param str os_flavor: The chosen operating system of the render nodes, used
|
||||
to determine the formatting of the path remapping.
|
||||
"""
|
||||
map_file = os.path.join(tempfile.gettempdir(), "asset_map.mel")
|
||||
pathmap = dict(self._assets.pathmaps)
|
||||
for asset in self._assets.refs:
|
||||
pathmap.update(asset.pathmap)
|
||||
with open(map_file, 'w') as handle:
|
||||
handle.write("global proc renderPrep()\n")
|
||||
handle.write("{\n")
|
||||
if plugins:
|
||||
for plugin in plugins:
|
||||
handle.write("loadPlugin \"{}\";\n".format(plugin))
|
||||
handle.write("dirmap -en true;\n")
|
||||
for local, remote in pathmap.items():
|
||||
full_remote_path = "X:\\\\" + remote(os_flavor)
|
||||
parsed_local = local.replace('\\', '\\\\')
|
||||
handle.write("dirmap -m \"{}\" \"{}\";\n".format(parsed_local, full_remote_path))
|
||||
handle.write("}")
|
||||
return map_file
|
||||
|
||||
def _upload_all(self, to_upload, progress, total, project):
|
||||
"""Upload all selected assets in 10 threads."""
|
||||
uploads_running = []
|
||||
progress_queue = Queue()
|
||||
for i in range(0, len(to_upload), UPLOAD_THREADS):
|
||||
for index, asset in enumerate(to_upload[i:i + UPLOAD_THREADS]):
|
||||
self._log.debug("Starting thread for asset: {}".format(asset.path))
|
||||
upload = threading.Thread(
|
||||
target=asset.upload, args=(index, progress, progress_queue, project))
|
||||
upload.start()
|
||||
uploads_running.append(upload)
|
||||
self._log.debug("Batch of asset uploads pending: {}".format(threading.active_count()))
|
||||
|
||||
while any(t for t in uploads_running if t.is_alive()) or not progress_queue.empty():
|
||||
uploaded = progress_queue.get()
|
||||
if isinstance(uploaded, Exception):
|
||||
raise uploaded
|
||||
elif callable(uploaded):
|
||||
uploaded()
|
||||
else:
|
||||
total = total - (uploaded/1024/1024)
|
||||
self.ui.upload_status("Uploading {0}...".format(self._format_size(total)))
|
||||
progress_queue.task_done()
|
||||
|
||||
def _format_size(self, data):
|
||||
"""Format the data size in bytes to nicely display
|
||||
for upload progress.
|
||||
"""
|
||||
if data > 1024:
|
||||
return "{:0.2f}GB".format(data/1014)
|
||||
else:
|
||||
return "{:0.2f}MB".format(data)
|
||||
|
||||
def _total_data(self, files):
|
||||
"""Format the combined size of the files to display
|
||||
for upload progress.
|
||||
"""
|
||||
data = float(0)
|
||||
for asset in files:
|
||||
data += asset.size
|
||||
return data/1024/1024
|
||||
|
||||
def configure(self, session):
|
||||
"""Populate the Batch client for the current sessions of the asset tab.
|
||||
Called on successful authentication.
|
||||
"""
|
||||
self._session = session
|
||||
self.batch = self._session.batch
|
||||
self.storage = self._session.storage
|
||||
self._set_searchpaths()
|
||||
self._assets = Assets(self.batch)
|
||||
|
||||
def set_assets(self):
|
||||
"""Gather the asset references of the scene for display in the
|
||||
asset tab. Called on loading and refreshing the asset tab.
|
||||
"""
|
||||
self._configure_renderer()
|
||||
self._assets.gather()
|
||||
self._assets.extend(self.renderer.renderer_assets())
|
||||
|
||||
def get_project(self):
|
||||
"""Get the current project name in order to use this as the asset file
|
||||
group.
|
||||
"""
|
||||
project_path = maya.workspace(query=True, fullName=True)
|
||||
return project_path.split('/')[-1]
|
||||
|
||||
def get_assets(self):
|
||||
"""Returns the current asset references for display. Called by the UI
|
||||
on loading and refresh.
|
||||
"""
|
||||
self._log.debug("Getting initial assets")
|
||||
self._log.debug(str(self._assets.refs))
|
||||
return self._assets.refs
|
||||
|
||||
def add_files(self, files, column_layout, scroll_layout):
|
||||
"""Function called by the 'Add File(s)' button in the UI for adding arbitrary
|
||||
file references to the collection that included with the next job subbmission.
|
||||
"""
|
||||
for f in files:
|
||||
self._assets.add_asset(f, self.ui, column_layout, scroll_layout)
|
||||
|
||||
def add_dir(self, dirs, column_layout, scroll_layout):
|
||||
"""Function called by the 'Add Directory' button in the UI for adding arbitrary
|
||||
file references to the collection that included with the next job subbmission.
|
||||
"""
|
||||
for folder in dirs:
|
||||
for root, _, files in os.walk(folder):
|
||||
for filename in files:
|
||||
self._assets.add_asset(
|
||||
os.path.join(root, filename), self.ui, column_layout, scroll_layout)
|
||||
|
||||
def upload(self, job_set=None, progress_bar=None, job_id=None, load_plugins=None, os_flavor=None):
|
||||
"""Upload all the selected assets. Can be initiated as a standalone process
|
||||
from the assets tab, or as part of job submission.
|
||||
:param job_set: A list of job assets, like the scene file. This is only populated
|
||||
if the upload process is part of job submission.
|
||||
:param progress_bar: The progress of the current process. This is only populated
|
||||
if the upload process is part of job submission.
|
||||
:param job_id: The ID of the job being submitted. This is only populated is the
|
||||
upload process if path of job submission.
|
||||
:param load_plugins: A list of plugins to be added to the pre-render script for
|
||||
loading on the server. Only populated if part of a job submission.
|
||||
:param os_flavor: The OS flavor of the rendering pool. Only set as part of the job
|
||||
submission process.
|
||||
"""
|
||||
try:
|
||||
if not job_set:
|
||||
progress_bar = ProgressBar(self._log)
|
||||
self.ui.disable(False)
|
||||
self.ui.upload_button.start()
|
||||
self.ui.upload_status("Checking assets...")
|
||||
|
||||
asset_refs = self._collect_assets()
|
||||
self._log.debug("Finished collecting, preparing for upload.")
|
||||
if job_set:
|
||||
self._log.debug("Preparing job specific assets")
|
||||
job_assets = [Asset(j, None, self.batch, self._log) for j in job_set]
|
||||
map_file = self._create_path_map(load_plugins, os_flavor)
|
||||
path_map = Asset(map_file, [], self.batch, self._log)
|
||||
asset_refs.extend(job_assets)
|
||||
asset_refs.append(path_map)
|
||||
|
||||
progress_bar.is_cancelled()
|
||||
progress_bar.status('Uploading files...')
|
||||
progress_bar.max(len(asset_refs))
|
||||
self.frame.select_tab(3)
|
||||
self.ui.disable(False)
|
||||
self.ui.upload_button.start()
|
||||
payload = self._total_data(asset_refs)
|
||||
self.ui.upload_status("Uploading {0}...".format(self._format_size(payload)))
|
||||
maya.refresh()
|
||||
|
||||
self._upload_all(asset_refs, progress_bar, payload, self.ui.get_project())
|
||||
map_url = None
|
||||
asset_project = self.ui.get_project()
|
||||
if job_set:
|
||||
asset_map = os.path.basename(path_map.path)
|
||||
map_url = self.batch.file.generate_sas_url(
|
||||
asset_project, asset_map, remote_path=path_map.storage_path)
|
||||
return asset_project, map_url, progress_bar
|
||||
|
||||
except CancellationException as exp:
|
||||
if job_set:
|
||||
raise
|
||||
else:
|
||||
maya.info(str(exp))
|
||||
except Exception as exp:
|
||||
if job_set:
|
||||
raise
|
||||
else:
|
||||
maya.error(str(exp))
|
||||
finally:
|
||||
# If part of job submission errors and progress bar
|
||||
# will be handled back in submission.py
|
||||
if not job_set:
|
||||
progress_bar.end()
|
||||
self.ui.upload_button.finish()
|
||||
self.ui.disable(True)
|
||||
|
||||
|
||||
class Assets(object):
|
||||
"""A collection of asset references."""
|
||||
|
||||
def __init__(self, batch):
|
||||
self._log = logging.getLogger('AzureBatchMaya')
|
||||
self.batch = batch
|
||||
self.refs = []
|
||||
self.pathmaps = {}
|
||||
|
||||
def _search_path(self, ref_path):
|
||||
"""Validate an asset path and if the file does not exist, attempt
|
||||
to resolve it against the system search paths (using the scene file and current
|
||||
project) and user specified search paths. If the asset paths contains
|
||||
a pattern - resolve this to all applicable files.
|
||||
"""
|
||||
ref_file = os.path.basename(ref_path)
|
||||
ref_dir = os.path.dirname(ref_path)
|
||||
pattern = ('*' in ref_path or '[0-9]' in ref_path)
|
||||
self._log.debug("Checking pattern asset: {0}".format(pattern))
|
||||
if pattern:
|
||||
path_matches = glob.glob(ref_path)
|
||||
if path_matches:
|
||||
self.pathmaps[ref_dir] = utils.get_remote_file_path(ref_path)
|
||||
self._log.debug("Mapping this path {} to {}".format(ref_path, self.pathmaps[ref_dir]))
|
||||
self._log.debug("Found matches: {0}".format(path_matches))
|
||||
return path_matches
|
||||
elif os.path.exists(ref_path):
|
||||
self.pathmaps[ref_dir] = utils.get_remote_file_path(ref_path)
|
||||
self._log.debug("Mapping this path {} to {}".format(ref_path, self.pathmaps[ref_dir]))
|
||||
return [ref_path]
|
||||
|
||||
for searchpath in SYS_SEARCHPATHS:
|
||||
alt_path = os.path.join(searchpath, ref_file)
|
||||
if pattern:
|
||||
path_matches = glob.glob(alt_path)
|
||||
if path_matches:
|
||||
self.pathmaps[ref_dir] = utils.get_remote_file_path(alt_path)
|
||||
self._log.debug("Mapping this path {} to {}".format(ref_path, self.pathmaps[ref_dir]))
|
||||
self._log.debug("Found matches: {0}".format(path_matches))
|
||||
return path_matches
|
||||
elif os.path.exists(alt_path):
|
||||
self.pathmaps[ref_dir] = utils.get_remote_file_path(alt_path)
|
||||
self._log.debug("Mapping this path {} to {}".format(ref_path, self.pathmaps[ref_dir]))
|
||||
return [alt_path]
|
||||
|
||||
for searchpath in USR_SEARCHPATHS:
|
||||
for _root, _dir, _file in os.walk(searchpath):
|
||||
alt_path = os.path.join(_root, ref_file)
|
||||
if pattern:
|
||||
path_matches = glob.glob(alt_path)
|
||||
self._log.debug("Found matches: {0}".format(path_matches))
|
||||
if path_matches:
|
||||
self.pathmaps[ref_dir] = utils.get_remote_file_path(alt_path)
|
||||
self._log.debug("Mapping this path {} to {}".format(ref_path, self.pathmaps[ref_dir]))
|
||||
return path_matches
|
||||
elif os.path.exists(alt_path):
|
||||
self.pathmaps[ref_dir] = utils.get_remote_file_path(alt_path)
|
||||
self._log.debug("Mapping this path {} to {}".format(ref_path, self.pathmaps[ref_dir]))
|
||||
return [alt_path]
|
||||
self._log.debug("No matches for reference: {}".format(ref_path))
|
||||
return [ref_path]
|
||||
|
||||
def _get_textures(self):
|
||||
"""Find all texture references in the scene. This generally picks up most
|
||||
asset types.
|
||||
"""
|
||||
assets = []
|
||||
iter_nodes = maya.dependency_nodes()
|
||||
while not iter_nodes.is_done():
|
||||
references = iter_nodes.get_references()
|
||||
for filepath in references:
|
||||
try:
|
||||
checked_paths = self._search_path(filepath)
|
||||
for _path in checked_paths:
|
||||
asset = Asset(_path, assets, self.batch, self._log)
|
||||
if not asset.is_duplicate(assets):
|
||||
assets.append(asset)
|
||||
except Exception as exp:
|
||||
self._log.warning("Failed to extract asset file from reference: {0}".format(exp))
|
||||
continue
|
||||
self._log.debug("Found {0} external files.".format(len(assets)))
|
||||
return assets
|
||||
|
||||
def _get_references(self):
|
||||
"""Get Maya scene file references as assets."""
|
||||
assets = []
|
||||
ref_nodes = maya.get_list(references=True)
|
||||
references = [maya.reference(r, filename=True, withoutCopyNumber=True) for r in ref_nodes]
|
||||
references = set([r for r in references if r])
|
||||
for r in references:
|
||||
asset = Asset(r, assets, self.batch, self._log)
|
||||
if (not asset.is_duplicate(self.refs)):
|
||||
assets.append(asset)
|
||||
self._log.debug("Found {0} references.".format(len(assets)))
|
||||
return assets
|
||||
|
||||
def _get_caches(self):
|
||||
"""Gather data caches as assets. TODO: This needs to be tested.
|
||||
We generally only want to gather caches relevant to the current
|
||||
render frame range or we could end up uploading way too much data.
|
||||
"""
|
||||
assets = []
|
||||
cacheFiles = maya.get_list(type="cacheFile")
|
||||
for c in cacheFiles:
|
||||
c_path = maya.get_attr(c+".cachePath")
|
||||
c_name = maya.get_attr(c+".cacheName")
|
||||
|
||||
if c_path and c_name:
|
||||
full_path = os.path.join(c_path, c_name)
|
||||
path_matches = glob.glob(full_path + "*")
|
||||
for cache_path in path_matches:
|
||||
asset = Asset(cache_path, assets, self.batch, self._log)
|
||||
assets.append(asset)
|
||||
|
||||
containers = maya.get_list(type="bifrostContainer")
|
||||
for container in containers:
|
||||
if not maya.get_attr(container + ".enableDiskCache"):
|
||||
continue
|
||||
bifrost_path = maya.get_attr(container + ".cacheDir")
|
||||
bifrost_name = maya.get_attr(container + ".cacheName")
|
||||
start = maya.start_frame()
|
||||
end = maya.end_frame()
|
||||
step = maya.frame_step()
|
||||
for frame in range(start, end+step, step):
|
||||
cache_file = os.path.join(bifrost_path, bifrost_name + "_p." + str(frame).zfill(4) + ".bif")
|
||||
if os.path.exists(cache_file):
|
||||
asset = Asset(cache_file, assets, self.batch, self._log)
|
||||
assets.append(asset)
|
||||
self._log.debug("Found {0} caches.".format(len(assets)))
|
||||
return assets
|
||||
|
||||
def gather(self):
|
||||
"""Parse scene for all asset references. Called on loading and
|
||||
refreshing the asset tab.
|
||||
"""
|
||||
self.refs = []
|
||||
self.pathmaps = {}
|
||||
self.refs.extend(self._get_textures())
|
||||
self.refs.extend(self._get_caches())
|
||||
self.refs.extend(self._get_references())
|
||||
|
||||
def add_asset(self, file, ui, column_layout, scroll_layout):
|
||||
"""Add an additional single file to the asset list."""
|
||||
self._log.info("Adding file: {0}".format(file))
|
||||
asset = Asset(file, self.refs, self.batch, self._log)
|
||||
if not asset.is_duplicate(self.refs):
|
||||
asset.display(ui, column_layout, scroll_layout)
|
||||
self.refs.append(asset)
|
||||
|
||||
def extend(self, more_assets):
|
||||
"""Add additional assets to the current collection."""
|
||||
assets = []
|
||||
for f in more_assets:
|
||||
try:
|
||||
checked_paths = self._search_path(f)
|
||||
for _path in checked_paths:
|
||||
asset = Asset(_path, assets, self.batch, self._log)
|
||||
if (not asset.is_duplicate(assets)) and (not asset.is_duplicate(self.refs)):
|
||||
assets.append(asset)
|
||||
except Exception as exp:
|
||||
self._log.debug("Failed to extend assets: {0}".format(exp))
|
||||
continue
|
||||
self.refs.extend(assets)
|
||||
|
||||
def collect(self):
|
||||
"""Compile a list of the asset references that have been selected
|
||||
to include with the current job.
|
||||
"""
|
||||
self._log.info("Collecting assets...")
|
||||
userfiles = [f for f in self.refs if f.included()]
|
||||
self._log.debug("Using {0} external assets.".format(len(userfiles)))
|
||||
return userfiles
|
||||
|
||||
|
||||
class Asset(object):
|
||||
"""Representation of a single asset, managing it's file reference,
|
||||
display listing and upload of the file.
|
||||
"""
|
||||
|
||||
def __init__(self, filepath, parent, batch, log=None):
|
||||
self.batch = batch
|
||||
self.path = os.path.normpath(filepath)
|
||||
self.label = " {0}".format(os.path.basename(self.path))
|
||||
self.exists = os.path.exists(self.path)
|
||||
self.lastmodified = datetime.fromtimestamp(os.path.getmtime(self.path)) if self.exists else None
|
||||
self.note = self.path if self.exists else "Can't find {0}".format(self.path)
|
||||
self.parent_list = parent
|
||||
self.check_box = None
|
||||
self.size = float(os.path.getsize(self.path)) if self.exists else 0
|
||||
self.display_text = None
|
||||
self.log = log
|
||||
if self.exists:
|
||||
self.pathmap = {os.path.dirname(self.path): utils.get_remote_file_path(self.path)}
|
||||
self.storage_path = utils.get_storage_file_path(self.path)
|
||||
else:
|
||||
self.pathmap = {}
|
||||
self.storage_path = None
|
||||
|
||||
def display(self, ui, layout, scroll):
|
||||
"""Create the UI elements that will display this asset depending
|
||||
on whether the file path has been resolved or not.
|
||||
"""
|
||||
self.frame = ui
|
||||
self.scroll_layout = scroll
|
||||
|
||||
if self.exists:
|
||||
self.check_box = maya.symbol_check_box(
|
||||
value=True, parent=layout,
|
||||
onCommand=lambda e: self.include(),
|
||||
offCommand=lambda e: self.exclude(),
|
||||
annotation="Click to remove asset from submission")
|
||||
else:
|
||||
self.check_box = maya.symbol_button(
|
||||
image="fpe_someBrokenPaths.png", parent=layout,
|
||||
command=lambda e: self.search(),
|
||||
height=17, annotation="Add search path")
|
||||
self.display_text = maya.text(self.label, parent=layout, enable=self.exists, annotation=self.note, align="left")
|
||||
|
||||
def included(self):
|
||||
"""Returns whether the asset has been selected for inclusion in the upload."""
|
||||
if self.check_box and self.exists:
|
||||
return bool(maya.symbol_check_box(self.check_box, query=True, value=True))
|
||||
else:
|
||||
return self.exists
|
||||
|
||||
def include(self):
|
||||
"""Include this asset in the list for files to be uploaded for submission."""
|
||||
if self not in self.parent_list:
|
||||
self.parent_list.append(self)
|
||||
maya.symbol_check_box(
|
||||
self.check_box, edit=True,
|
||||
annotation="Click to remove asset from submission")
|
||||
|
||||
def search(self):
|
||||
"""If a filepath is unresolved, we let the user add an arbitrary search
|
||||
path that we can use to attempt to find the asset.
|
||||
"""
|
||||
global USR_SEARCHPATHS
|
||||
cap = "Select directory of assets"
|
||||
okCap = "Add Search Path"
|
||||
new_dir = maya.file_select(fileMode=3, okCaption=okCap, caption=cap)
|
||||
if not new_dir:
|
||||
return
|
||||
USR_SEARCHPATHS.append(new_dir[0])
|
||||
self.frame.refresh()
|
||||
|
||||
def exclude(self):
|
||||
"""Remove this asset from the list of files to be uploaded."""
|
||||
try:
|
||||
self.parent_list.remove(self)
|
||||
maya.symbol_check_box(self.check_box, edit=True, annotation="Click to include asset in submission")
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def delete(self):
|
||||
"""Remove this asset from the UI display. This is usually done on
|
||||
a refresh of the asset tab in order to wipe the UI for repopulating.
|
||||
"""
|
||||
try:
|
||||
self.parent_list.remove(self)
|
||||
except ValueError:
|
||||
pass
|
||||
maya.delete_ui(self.check_box, control=True)
|
||||
|
||||
def is_duplicate(self, files):
|
||||
"""Check whether this file is already represented in the current
|
||||
asset list.
|
||||
"""
|
||||
return any(f.path == self.path for f in files) #TODO: Check if this is accurate
|
||||
|
||||
def restore_label(self):
|
||||
"""Restore the original UI display label after the file has been
|
||||
uploaded.
|
||||
"""
|
||||
maya.text(self.display_text, edit=True, label=self.label)
|
||||
|
||||
def make_visible(self, index):
|
||||
"""Attempt to auto-scroll the asset display list so that the progress of
|
||||
currently uploading assets remains in view.
|
||||
TODO: Thie needs some work....
|
||||
"""
|
||||
if index == 0:
|
||||
while maya.scroll_layout(self.scroll_layout, query=True, scrollAreaValue=True)[0] > 0:
|
||||
maya.scroll_layout(self.scroll_layout, edit=True, scrollPage="up")
|
||||
elif index >= 4:
|
||||
scroll_height = maya.text(self.display_text, query=True, height=True)
|
||||
maya.scroll_layout(self.scroll_layout, edit=True, scrollByPixel=("down", scroll_height))
|
||||
maya.refresh()
|
||||
|
||||
def upload(self, index, progress_bar, queue, project):
|
||||
"""Upload this asset file. This is performed outside of Maya's
|
||||
main UI thread, therefore any calls to the Maya API must be added to the
|
||||
queue to be processed by the main thread or Maya will crash (or at the
|
||||
very least behave strangely).
|
||||
"""
|
||||
self.log.debug("Starting asset upload: {}".format(self.path))
|
||||
queue.put(progress_bar.is_cancelled)
|
||||
if progress_bar.done:
|
||||
return
|
||||
name = os.path.basename(self.path)
|
||||
if self.display_text:
|
||||
if index:
|
||||
queue.put(lambda: self.make_visible(index))
|
||||
queue.put(lambda: maya.text(
|
||||
self.display_text, edit=True,
|
||||
label=" Uploading 0% {0}".format(name)))
|
||||
queue.put(maya.refresh)
|
||||
|
||||
def update(update):
|
||||
# Update the % complete in the asset label
|
||||
if self.display_text:
|
||||
maya.text(self.display_text, edit=True, label=" Uploading {0}% {1}".format(int(update), name))
|
||||
maya.refresh()
|
||||
uploader = UploadProgress(progress_bar, update, queue, self.log)
|
||||
try:
|
||||
self.batch.file.upload(
|
||||
self.path, project, self.storage_path,
|
||||
progress_callback=uploader)
|
||||
except Exception as exp:
|
||||
queue.put(FileUploadException("Upload failed for {0}: {1}".format(name, exp)))
|
||||
else:
|
||||
if self.display_text:
|
||||
queue.put(lambda: maya.text(
|
||||
self.display_text, edit=True,
|
||||
label=" Uploading 100% {0}".format(name)))
|
||||
queue.put(maya.refresh)
|
||||
self.log.debug("Finished asset upload: {}".format(self.path))
|
||||
queue.put(self.restore_label)
|
||||
queue.put(self.size)
|
||||
|
||||
|
||||
class UploadProgress(object):
|
||||
"""Upload progress callback. Updates progress bar and checks
|
||||
for cancellation.
|
||||
"""
|
||||
|
||||
def __init__(self, prog_bar, cmd, queue, log):
|
||||
self.progress = 0
|
||||
self.bar = prog_bar
|
||||
self.command = cmd
|
||||
self.queue = queue
|
||||
self.log = log
|
||||
|
||||
def __call__(self, data, total):
|
||||
self.queue.put(self.bar.is_cancelled)
|
||||
if self.bar.done:
|
||||
raise CancellationException("File upload cancelled")
|
||||
progress = float(data)/float(total)*100
|
||||
self.log.debug(str(progress) + " " + str(self.progress))
|
||||
if int(progress) > self.progress:
|
||||
self.progress = int(progress)
|
||||
self.queue.put(lambda: self.command(progress))
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import ConfigParser
|
||||
import os
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from ui_config import ConfigUI
|
||||
from api import MayaAPI as maya
|
||||
|
||||
import azure.storage.blob as storage
|
||||
import azure.batch_extensions as batch
|
||||
from azure.batch_extensions.batch_auth import SharedKeyCredentials
|
||||
|
||||
|
||||
LOG_LEVELS = {
|
||||
'debug':10,
|
||||
'info':20,
|
||||
'warning':30,
|
||||
'error':40}
|
||||
|
||||
|
||||
class AzureBatchConfig(object):
|
||||
"""Handler for authentication and configuration of the SDK clients."""
|
||||
|
||||
def __init__(self, frame, start):
|
||||
"""Create new configuration Handler.
|
||||
|
||||
:param frame: The shared plug-in UI frame.
|
||||
:type frame: :class:`.AzureBatchUI`
|
||||
:param func call: The shared REST API call wrapper.
|
||||
"""
|
||||
self.session = start
|
||||
self._data_dir = os.path.join(maya.prefs_dir(), 'AzureBatchData')
|
||||
self._ini_file = "azure_batch.ini"
|
||||
self._cfg = ConfigParser.ConfigParser()
|
||||
self._client = None
|
||||
self._log = None
|
||||
self._storage = None
|
||||
self._configure_plugin()
|
||||
self.ui = ConfigUI(self, frame)
|
||||
self._auth = self._auto_authentication()
|
||||
self._update_config_ui()
|
||||
|
||||
@property
|
||||
def batch(self):
|
||||
return self._client
|
||||
|
||||
@property
|
||||
def storage(self):
|
||||
return self._storage
|
||||
|
||||
@property
|
||||
def auth(self):
|
||||
return self._auth
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return os.path.join(self._data_dir, self._ini_file)
|
||||
|
||||
def _configure_plugin(self):
|
||||
"""Set up the the config file, authenticate the SDK clients
|
||||
and set up the log file.
|
||||
"""
|
||||
if not os.path.exists(self._data_dir):
|
||||
os.makedirs(self._data_dir)
|
||||
config_file = os.path.join(self._data_dir, self._ini_file)
|
||||
if not os.path.exists(config_file):
|
||||
self._log = self._configure_logging(10)
|
||||
return
|
||||
try:
|
||||
self._cfg.read(config_file)
|
||||
self._storage = storage.BlockBlobService(
|
||||
self._cfg.get("AzureBatch", "storage_account"),
|
||||
self._cfg.get("AzureBatch", "storage_key"),
|
||||
endpoint_suffix="core.windows.net")
|
||||
self._storage.MAX_SINGLE_PUT_SIZE = 2 * 1024 * 1024
|
||||
credentials = SharedKeyCredentials(
|
||||
self._cfg.get("AzureBatch", "batch_account"),
|
||||
self._cfg.get("AzureBatch", "batch_key"))
|
||||
self._client = batch.BatchExtensionsClient(
|
||||
credentials, base_url=self._cfg.get("AzureBatch", "batch_url"),
|
||||
storage_client=self._storage)
|
||||
self._log = self._configure_logging(
|
||||
self._cfg.get("AzureBatch", "logging"))
|
||||
except (ConfigParser.NoOptionError, ConfigParser.NoSectionError) as exp:
|
||||
print(exp) #TODO: Better error handling
|
||||
|
||||
def _configure_logging(self, log_level):
|
||||
"""Configure the logger. Setup the file output and format
|
||||
the log messages.
|
||||
|
||||
:param log_level: The specified level of logging verbosity.
|
||||
"""
|
||||
level = int(log_level)
|
||||
logger = logging.getLogger('AzureBatchMaya')
|
||||
file_format = logging.Formatter(
|
||||
"%(asctime)-15s [%(levelname)s] %(module)s: %(message)s")
|
||||
logfile = os.path.join(self._data_dir, "azure_batch.log")
|
||||
if not os.path.exists(logfile):
|
||||
with open(logfile, 'w') as handle:
|
||||
handle.write("Azure Batch Plugin Log")
|
||||
file_logging = logging.FileHandler(logfile)
|
||||
file_logging.setFormatter(file_format)
|
||||
logger.addHandler(file_logging)
|
||||
logger.setLevel(level)
|
||||
return logger
|
||||
|
||||
def _update_config_ui(self):
|
||||
"""Populate the config tab UI with the values loaded from the
|
||||
configuration file.
|
||||
"""
|
||||
try:
|
||||
self._cfg.add_section("AzureBatch")
|
||||
except ConfigParser.DuplicateSectionError:
|
||||
pass
|
||||
try:
|
||||
self.ui.endpoint = self._cfg.get("AzureBatch", "batch_url")
|
||||
except ConfigParser.NoOptionError:
|
||||
self.ui.endpoint = ""
|
||||
try:
|
||||
self.ui.account = self._cfg.get("AzureBatch", "batch_account")
|
||||
except ConfigParser.NoOptionError:
|
||||
self.ui.account = ""
|
||||
try:
|
||||
self.ui.key = self._cfg.get("AzureBatch", "batch_key")
|
||||
except ConfigParser.NoOptionError:
|
||||
self.ui.key = ""
|
||||
try:
|
||||
self.ui.storage = self._cfg.get("AzureBatch", "storage_account")
|
||||
except ConfigParser.NoOptionError:
|
||||
self.ui.storage = ""
|
||||
try:
|
||||
self.ui.storage_key = self._cfg.get("AzureBatch", "storage_key")
|
||||
except ConfigParser.NoOptionError:
|
||||
self.ui.storage_key = ""
|
||||
try:
|
||||
self.ui.logging = int(self._cfg.get("AzureBatch", "logging"))
|
||||
except ConfigParser.NoOptionError:
|
||||
self.ui.logging = 10
|
||||
self.ui.set_authenticate(self._auth)
|
||||
|
||||
def _auto_authentication(self):
|
||||
"""Test whether the clients are correctly authenticated
|
||||
by doing some quick API calls.
|
||||
"""
|
||||
try:
|
||||
filter = batch.models.PoolListOptions(max_results=1, select="id")
|
||||
self._client.pool.list(filter)
|
||||
self._storage.create_container("batch-maya-assets", fail_on_exist=False)
|
||||
return True
|
||||
except Exception as exp:
|
||||
self._log.info("Could not get authenticate session: {0}".format(exp))
|
||||
return False
|
||||
|
||||
def set_logging(self, level):
|
||||
"""Set the logging level to that specified in the UI.
|
||||
:param str level: The specified logging level.
|
||||
"""
|
||||
log_level = int(LOG_LEVELS[level])
|
||||
self._log.setLevel(log_level)
|
||||
self._cfg.set("AzureBatch", "logging", str(level))
|
||||
|
||||
def save_changes(self):
|
||||
"""Persist configuration changes to file for future sessions."""
|
||||
try:
|
||||
self._cfg.add_section("AzureBatch")
|
||||
except ConfigParser.DuplicateSectionError:
|
||||
pass
|
||||
self._cfg.set("AzureBatch", "batch_url", self.ui.endpoint)
|
||||
self._cfg.set("AzureBatch", "batch_account", self.ui.account)
|
||||
self._cfg.set("AzureBatch", "batch_key", self.ui.key)
|
||||
self._cfg.set("AzureBatch", "storage_account", self.ui.storage)
|
||||
self._cfg.set("AzureBatch", "storage_key", self.ui.storage_key)
|
||||
self._cfg.set("AzureBatch", "logging", self.ui.logging)
|
||||
config_file = os.path.join(self._data_dir, self._ini_file)
|
||||
with open(config_file, 'w') as handle:
|
||||
self._cfg.write(handle)
|
||||
|
||||
def authenticate(self):
|
||||
"""Begin authentication - initiated by the UI button."""
|
||||
self._configure_plugin()
|
||||
self._auth = self._auto_authentication()
|
||||
self.ui.set_authenticate(self._auth)
|
||||
self.session()
|
|
@ -0,0 +1,296 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from api import MayaAPI as maya
|
||||
from api import MayaCallbacks as callback
|
||||
|
||||
from ui_environment import EnvironmentUI
|
||||
|
||||
MAYA_VERSIONS = {"Maya 2016": "2015"}
|
||||
|
||||
SUPPORTED = [
|
||||
{"name": "Arnold", "plugin": "mtoa.", "license": True, "license_var": {"key":"solidangle_LICENSE", "value":"{port}@{host}"}},
|
||||
]
|
||||
|
||||
EXTENSIONS = [".mll", ".bundle", ".py"]
|
||||
|
||||
DEFAULT = [
|
||||
'AbcBullet',
|
||||
'AbcExport',
|
||||
'AbcImport',
|
||||
'animImportExport',
|
||||
'ArubaTessellator',
|
||||
'atomImportExport',
|
||||
'AutodeskPacketFile',
|
||||
'autoLoader',
|
||||
'AzureBatch',
|
||||
'bullet',
|
||||
'cgfxShader',
|
||||
'cleanPerFaceAssignment',
|
||||
'clearcoat',
|
||||
'CloudImportExport',
|
||||
'ddsFloatReader',
|
||||
'deformerEvaluator',
|
||||
'dgProfiler',
|
||||
'DirectConnect',
|
||||
'dx11Shader',
|
||||
'fltTranslator',
|
||||
'Fur',
|
||||
'gameFbxExporter',
|
||||
'GamePipeline',
|
||||
'glslShader',
|
||||
'GPUBuiltInDeformer',
|
||||
'ge2Export',
|
||||
'gpuCache',
|
||||
'hlslShader',
|
||||
'ik2Bsolver',
|
||||
'ikSpringSolver',
|
||||
'matrixNodes',
|
||||
'mayaCharacterization',
|
||||
'mayaHIK',
|
||||
'MayaMuscle',
|
||||
'melProfiler',
|
||||
'modelingToolkit',
|
||||
'nearestPointOnMesh',
|
||||
'objExport',
|
||||
'OneClick',
|
||||
'OpenEXRLoader',
|
||||
'openInventor',
|
||||
'quatNodes',
|
||||
'retargeterNodes',
|
||||
'rotateHelper',
|
||||
'rtgExport',
|
||||
'sceneAssembly',
|
||||
'shaderFXPlugin',
|
||||
'stereoCamera',
|
||||
'studioImport',
|
||||
'tiffFloatReader',
|
||||
'Turtle',
|
||||
'Unfold3D',
|
||||
'VectorRender',
|
||||
'vrml2Export',
|
||||
'BifrostMain',
|
||||
'bifrostshellnode',
|
||||
'bifrostvisplugin',
|
||||
'fbxmaya',
|
||||
'Substance',
|
||||
'xgenMR',
|
||||
'xgenToolkit']
|
||||
|
||||
class AzureBatchEnvironment(object):
|
||||
|
||||
def __init__(self, frame, call):
|
||||
|
||||
self._log = logging.getLogger('AzureBatchMaya')
|
||||
self._call = call
|
||||
self._session = None
|
||||
|
||||
self._server_plugins = []
|
||||
|
||||
self._plugins = []
|
||||
self._version = "2015"
|
||||
|
||||
self.ui = EnvironmentUI(self, frame, MAYA_VERSIONS.keys())
|
||||
self.warnings = []
|
||||
|
||||
self.refresh()
|
||||
callback.after_new(self.ui.refresh)
|
||||
callback.after_read(self.ui.refresh)
|
||||
|
||||
@property
|
||||
def plugins(self):
|
||||
return self._server_plugins
|
||||
|
||||
@property
|
||||
def environment_variables(self):
|
||||
custom_vars = self.ui.get_env_vars()
|
||||
for plugin in self._plugins:
|
||||
custom_vars.update(plugin.get_variables())
|
||||
return custom_vars
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def license(self):
|
||||
return self.ui.get_license_server()
|
||||
|
||||
def configure(self, session):
|
||||
self._session = session
|
||||
|
||||
def get_plugins(self):
|
||||
|
||||
used_plugins = maya.plugins(query=True, pluginsInUse=True)
|
||||
extra_plugins = self.search_for_plugins()
|
||||
|
||||
plugins = []
|
||||
|
||||
for plugin in extra_plugins:
|
||||
support = [p for p in SUPPORTED if plugin.startswith(p["plugin"])]
|
||||
loaded = maya.plugins(plugin, query=True, loaded=True)
|
||||
|
||||
plugin_ref = BatchPlugin(self, plugin, loaded, support)
|
||||
plugin_ref.is_used(used_plugins if used_plugins else [])
|
||||
plugins.append(plugin_ref)
|
||||
|
||||
if self.warnings:
|
||||
warning = "The following plug-ins are used in the scene, but not yet supported.\nRendering may be affected.\n"
|
||||
for plugin in self.warnings:
|
||||
warning += plugin + "\n"
|
||||
maya.warning(warning)
|
||||
|
||||
return plugins
|
||||
|
||||
def search_for_plugins(self):
|
||||
found_plugins = []
|
||||
search_locations = os.environ["MAYA_PLUG_IN_PATH"].split(os.pathsep)
|
||||
|
||||
for plugin_dir in search_locations:
|
||||
if os.path.isdir(plugin_dir):
|
||||
plugins = os.listdir(os.path.normpath(plugin_dir))
|
||||
for plugin in plugins:
|
||||
found_plugins.append(plugin)
|
||||
|
||||
found_plugins = list(set(found_plugins))
|
||||
return filter(self.is_default, found_plugins)
|
||||
|
||||
def is_default(self, plugin):
|
||||
plugin, ext = os.path.splitext(plugin)
|
||||
if plugin in DEFAULT and ext in EXTENSIONS:
|
||||
return False
|
||||
|
||||
elif ext in EXTENSIONS:
|
||||
return True
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_version(self, version):
|
||||
self._version = MAYA_VERSIONS.get(version, "2015")
|
||||
|
||||
def refresh(self):
|
||||
self._server_plugins = []
|
||||
self.warnings = []
|
||||
for plugin in self._plugins:
|
||||
plugin.delete()
|
||||
|
||||
self._plugins = self.get_plugins()
|
||||
|
||||
|
||||
class BatchPlugin(object):
|
||||
|
||||
def __init__(self, base, plugin, loaded, support):
|
||||
|
||||
self.plugin = plugin
|
||||
self.label = plugin
|
||||
self.supported = bool(support)
|
||||
self.loaded = loaded
|
||||
self.used = False
|
||||
self.base = base
|
||||
self.license = False
|
||||
self.contents = []
|
||||
|
||||
if self.supported:
|
||||
self.label = support[0]["name"]
|
||||
self.license = support[0]["license"]
|
||||
self.license_var = support[0].get("license_var", {})
|
||||
|
||||
self.create_checkbox()
|
||||
|
||||
def create_checkbox(self):
|
||||
self.checkbox = maya.check_box(label=self.label if self.supported else "Unsupported: {0}".format(self.label),
|
||||
value=False,
|
||||
onCommand=self.include,
|
||||
offCommand=self.exclude,
|
||||
parent=self.base.ui.plugin_layout,
|
||||
enable=self.supported)
|
||||
|
||||
if self.license:
|
||||
self.license_check = maya.check_box(label="Use my license", value=False, parent=self.base.ui.plugin_layout, changeCommand=self.use_license, enable=False)
|
||||
self.custom_license_endp = maya.text_field( placeholderText='License Server', enable=False, parent=self.base.ui.plugin_layout)
|
||||
self.custom_license_port = maya.text_field( placeholderText='Port', enable=False, parent=self.base.ui.plugin_layout)
|
||||
self.contents.extend([self.license_check, self.custom_license_endp, self.custom_license_port])
|
||||
else:
|
||||
self.contents.append(maya.text(label="", parent=self.base.ui.plugin_layout))
|
||||
self.contents.append(self.checkbox)
|
||||
|
||||
def is_used(self, used_plugins):
|
||||
self.used = False
|
||||
for plugin in used_plugins:
|
||||
if plugin == os.path.splitext(self.plugin)[0]:
|
||||
self.used = True
|
||||
break
|
||||
|
||||
if self.loaded and self.supported and self.used:
|
||||
maya.check_box(self.checkbox, edit=True, value=True)
|
||||
if self.license:
|
||||
maya.check_box(self.license_check, edit=True, enable=True)
|
||||
self.base.plugins.append(self.label)
|
||||
|
||||
if self.loaded and self.used and not self.supported:
|
||||
self.base.warnings.append(self.plugin)
|
||||
|
||||
def use_license(self, license):
|
||||
if self.license:
|
||||
maya.text_field(self.custom_license_endp, edit=True, enable=license)
|
||||
maya.text_field(self.custom_license_port, edit=True, enable=license)
|
||||
|
||||
def include(self, *args):
|
||||
self.base.plugins.append(self.label)
|
||||
|
||||
if self.license:
|
||||
maya.check_box(self.license_check, edit=True, enable=True)
|
||||
|
||||
def exclude(self, *args):
|
||||
if self.used:
|
||||
maya.warning("This plugin is currently in use. Excluding it may affect rendering.")
|
||||
|
||||
if self.label in self.base.plugins: self.base.plugins.remove(self.label)
|
||||
if self.license:
|
||||
maya.check_box(self.license_check, edit=True, enable=False)
|
||||
|
||||
def delete(self):
|
||||
for c in self.contents:
|
||||
maya.delete_ui(c, control=True)
|
||||
|
||||
def get_variables(self):
|
||||
vars = {}
|
||||
if self.license and maya.check_box(self.license_check, query=True, value=True):
|
||||
license_key = self.license_var.get("key")
|
||||
license_val = self.license_var.get("value")
|
||||
|
||||
host = str(maya.text_field(self.custom_license_endp, query=True, text=True))
|
||||
port = str(maya.text_field(self.custom_license_port, query=True, text=True))
|
||||
if host and port:
|
||||
vars[license_key] = license_val.format(host=host, port=port)
|
||||
|
||||
return vars
|
|
@ -0,0 +1,58 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import traceback
|
||||
import logging
|
||||
|
||||
from api import MayaAPI as maya
|
||||
|
||||
LOG = logging.getLogger('AzureBatchMaya')
|
||||
|
||||
|
||||
class BatchMayaException(Exception):
|
||||
|
||||
def __init__(self, *args):
|
||||
super(BatchMayaException, self).__init__(*args)
|
||||
|
||||
|
||||
class CancellationException(BatchMayaException):
|
||||
|
||||
def __init__(self, message, *args):
|
||||
super(CancellationException, self).__init__(message, *args)
|
||||
|
||||
|
||||
class FileUploadException(BatchMayaException):
|
||||
|
||||
def __init__(self, *args):
|
||||
super(FileUploadException, self).__init__(*args)
|
||||
|
||||
|
||||
class PoolException(BatchMayaException):
|
||||
|
||||
def __init__(self, *args):
|
||||
super(PoolException, self).__init__(*args)
|
|
@ -0,0 +1,295 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import logging
|
||||
import tempfile
|
||||
import glob
|
||||
import struct
|
||||
import random
|
||||
import string
|
||||
import shutil
|
||||
import re
|
||||
|
||||
from api import MayaAPI as maya
|
||||
from ui_history import HistoryUI
|
||||
|
||||
import azure.batch as batch
|
||||
|
||||
|
||||
class AzureBatchHistory(object):
|
||||
"""Handler for job display functionality."""
|
||||
|
||||
def __init__(self, frame, call):
|
||||
"""Create new job history Handler.
|
||||
|
||||
:param frame: The shared plug-in UI frame.
|
||||
:type frame: :class:`.AzureBatchUI`
|
||||
:param func call: The shared REST API call wrapper.
|
||||
"""
|
||||
self._log = logging.getLogger('AzureBatchMaya')
|
||||
self._call = call
|
||||
self._session = None
|
||||
self.batch = None
|
||||
self.index = 0
|
||||
self.per_call = 5
|
||||
self.count = 0
|
||||
self.min = True
|
||||
self.max = False
|
||||
self.ui = HistoryUI(self, frame)
|
||||
self.all_jobs = []
|
||||
self.jobs = []
|
||||
self.selected_job = None
|
||||
|
||||
def _get_image_height(self, image):
|
||||
"""Get the pixel height of the job thumbnail to display.
|
||||
Note: This function only works under Python 2.7
|
||||
"""
|
||||
y = 120
|
||||
with open(image, 'rb') as im:
|
||||
im.read(12)
|
||||
if im.read(4) == 'IHDR':
|
||||
x, y = struct.unpack("!LL", im.read(8))
|
||||
return y
|
||||
|
||||
def _download_thumbnail(self, job, thumbs):
|
||||
"""Download a preview thumbnail for the selected job.
|
||||
Only certain output formats are supported. If not thumbnail exists
|
||||
then we display the default 'no preview' image.
|
||||
TODO: Remove direct storage reference to use batch.download.
|
||||
|
||||
:param job: The selected job object.
|
||||
:param thumbs: A list of thumbnail images available for this job.
|
||||
"""
|
||||
thumb = os.path.join(os.environ["AZUREBATCH_ICONS"], "no_preview.png")
|
||||
if len(thumbs) < 1:
|
||||
self._log.info("No thumbnails retrieved")
|
||||
self.selected_job.set_thumbnail(thumb, self._get_image_height(thumb))
|
||||
maya.refresh()
|
||||
return
|
||||
try:
|
||||
temp_dir = os.path.join(tempfile.gettempdir(), job.id)
|
||||
if not os.path.isdir(temp_dir):
|
||||
os.mkdir(temp_dir)
|
||||
except Exception as exp:
|
||||
self._log.warning(exp)
|
||||
self.selected_job.set_thumbnail(thumb, self._get_image_height(thumb))
|
||||
maya.refresh()
|
||||
return
|
||||
thumb_path = os.path.join(temp_dir, thumbs[-1])
|
||||
try:
|
||||
if not os.path.isfile(thumb_path):
|
||||
self._log.info("Downloading task thumb: {}".format(thumbs[-1]))
|
||||
self.storage.get_blob_to_path(job.id, thumbs[-1], thumb_path)
|
||||
self._log.info(" thumbnail download successful.\n")
|
||||
except Exception as exp:
|
||||
self._log.warning(exp)
|
||||
self.selected_job.set_thumbnail(thumb, self._get_image_height(thumb))
|
||||
maya.refresh()
|
||||
return
|
||||
self.selected_job.set_thumbnail(thumb_path, self._get_image_height(thumb_path))
|
||||
maya.refresh()
|
||||
|
||||
def _set_num_jobs(self):
|
||||
"""Calculate the paging progress label, including which page
|
||||
is currently displayed out of how many.
|
||||
"""
|
||||
if (self.index + self.per_call) > self.count:
|
||||
extra = (self.index + self.per_call) - self.count
|
||||
new_range = ((self.index + self.per_call) - extra)
|
||||
self.ui.num_jobs = "{0} - {1} of {2}".format(
|
||||
min((self.index + 1), new_range), new_range, self.count)
|
||||
else:
|
||||
self.ui.num_jobs = "{0} - {1} of {2}".format(
|
||||
min((self.index + 1), self.count), (self.index + self.per_call), self.count)
|
||||
|
||||
def _set_min_max(self):
|
||||
"""Determine whether we are currently displaying the first or
|
||||
last page, for that the forward and back buttons can be disabled
|
||||
accordingly.
|
||||
"""
|
||||
self.min = True if self.index < 1 else False
|
||||
if (self.count % self.per_call) == 0:
|
||||
self.max = (self.index >= (self.count - self.per_call)) or (self.per_call > self.count)
|
||||
else:
|
||||
self.max = self.index >= (self.count - self.per_call + (self.per_call - (self.count % self.per_call)))
|
||||
self.ui.last_page = not self.max
|
||||
self.ui.first_page = not self.min
|
||||
|
||||
def configure(self, session):
|
||||
"""Populate the Batch client for the current sessions of the job history tab.
|
||||
Called on successful authentication.
|
||||
"""
|
||||
self._session = session
|
||||
self.batch = self._session.batch
|
||||
self.storage = self._session.storage
|
||||
|
||||
def selected_job_id(self):
|
||||
"""Retrieves the ID of the currently selected job."""
|
||||
return self.jobs[self.selected_job.index].id
|
||||
|
||||
def get_data_dir(self):
|
||||
"""Get the path of the plugin configuration file.
|
||||
This is used for configuring the job watcher if it's launched.
|
||||
"""
|
||||
return self._session.path
|
||||
|
||||
def get_history(self):
|
||||
"""Retrieve the jobs run in the Batch account.
|
||||
We filter for only Maya jobs.
|
||||
"""
|
||||
self.all_jobs = [j for j in self._call(self.batch.job.list) \
|
||||
if j.metadata and any([m for m in j.metadata if m.name=='JobType' and m.value.startswith('Maya')])]
|
||||
self.all_jobs.sort(key=lambda x: x.creation_time, reverse=True)
|
||||
self.count = len(self.all_jobs)
|
||||
return self.show_jobs()
|
||||
|
||||
def show_jobs(self):
|
||||
"""Display the current page of jobs."""
|
||||
self.jobs = self.all_jobs[self.index:self.index + self.per_call]
|
||||
self._set_num_jobs()
|
||||
self._set_min_max()
|
||||
display_jobs = []
|
||||
for index, job in enumerate(self.jobs):
|
||||
display_jobs.append(self.ui.create_job_entry(job.display_name, index))
|
||||
return display_jobs
|
||||
|
||||
def show_next_jobs(self):
|
||||
"""Show the next page of jobs."""
|
||||
self.index = min(self.index + self.per_call, self.count)
|
||||
|
||||
def show_prev_jobs(self):
|
||||
"""Show the previous page of jobs."""
|
||||
self.index = max(self.index - self.per_call, 0)
|
||||
|
||||
def show_first_jobs(self):
|
||||
"""Return to the first page of jobs (most recently submitted)."""
|
||||
self.index = 0
|
||||
|
||||
def show_last_jobs(self):
|
||||
"""Skip to the last page of jobs (first ones submitted)."""
|
||||
if (self.count % self.per_call) == 0:
|
||||
self.index = self.count - self.per_call
|
||||
else:
|
||||
self.index = self.count - self.per_call + \
|
||||
(self.per_call - (self.count % self.per_call))
|
||||
|
||||
def job_selected(self, job_ui):
|
||||
"""A job has been selected, so it's details need to be retrieved
|
||||
and displayed. This is also called when a job entry has been
|
||||
refreshed.
|
||||
"""
|
||||
if self.selected_job and job_ui:
|
||||
self.selected_job.collapse()
|
||||
self.selected_job = job_ui
|
||||
if job_ui:
|
||||
self.update_job(job_ui.index)
|
||||
|
||||
def update_job(self, index):
|
||||
"""Get the latest details on a specified job.
|
||||
Also attempts to find a latest thumbnail to display.
|
||||
:param int index: The index of the job displayed on the page
|
||||
that is currently selected.
|
||||
"""
|
||||
try:
|
||||
self._log.info("Collecting job info...")
|
||||
job = self.jobs[index]
|
||||
self.selected_job.set_label("loading...")
|
||||
loading_thumb = os.path.join(os.environ["AZUREBATCH_ICONS"], "loading_preview.png")
|
||||
self.selected_job.set_thumbnail(loading_thumb, 24)
|
||||
maya.refresh()
|
||||
job = self._call(self.batch.job.get, job.id)
|
||||
tasks = list(self._call(self.batch.task.list, job.id))
|
||||
completed_tasks = [t for t in tasks if t.state == batch.models.TaskState.completed]
|
||||
errored_tasks = [t for t in completed_tasks if t.execution_info.exit_code != 0]
|
||||
state = job.state.value
|
||||
if len(tasks) == 0:
|
||||
percentage = 0
|
||||
state = "Pending"
|
||||
else:
|
||||
percentage = (100 * len(completed_tasks)) / (len(tasks))
|
||||
self.selected_job.set_status(state)
|
||||
self.selected_job.set_progress(percentage)
|
||||
self.selected_job.set_submission(job.creation_time.isoformat())
|
||||
self.selected_job.set_tasks(len(tasks))
|
||||
self.selected_job.set_job(job.id)
|
||||
self.selected_job.set_pool(job.pool_info.pool_id)
|
||||
self.selected_job.set_label(job.display_name)
|
||||
maya.refresh()
|
||||
self._log.info("Updated {0}".format(job.display_name))
|
||||
except Exception as exp:
|
||||
self._log.warning("Failed to update job details {0}".format(exp))
|
||||
self.ui.refresh()
|
||||
|
||||
def get_thumbnail(self):
|
||||
"""Check job outputs of the currently selected job to find
|
||||
any available thumbnails.
|
||||
TODO: Remove direct use of storage and replace with batch.download.
|
||||
"""
|
||||
try:
|
||||
job = self.jobs[self.selected_job.index]
|
||||
except (IndexError, AttributeError):
|
||||
self._log.warning("Selected job index does not match jobs list.")
|
||||
if not self.selected_job:
|
||||
return
|
||||
thumb = os.path.join(os.environ["AZUREBATCH_ICONS"], "no_preview.png")
|
||||
self.selected_job.set_thumbnail(thumb, 24)
|
||||
return
|
||||
try:
|
||||
blobs = self.storage.list_blobs(job.id, prefix="framethumb_")
|
||||
except Exception as exp:
|
||||
self._log.warning(exp)
|
||||
blobs = []
|
||||
thumbs = sorted([b.name for b in blobs])
|
||||
self._download_thumbnail(job, thumbs)
|
||||
|
||||
def cancel_job(self):
|
||||
"""Cancel (terminate) the currently selected job."""
|
||||
try:
|
||||
job = self.jobs[self.selected_job.index]
|
||||
self._call(self.batch.job.terminate, job.id)
|
||||
self.update_job(self.selected_job.index)
|
||||
maya.execute(self.get_thumbnail)
|
||||
maya.refresh()
|
||||
except (IndexError, AttributeError) as exp:
|
||||
self._log.warning("Selected job index does not match jobs list.")
|
||||
except Exception: #TODO get real exception
|
||||
self._log.info("Job was not able to be cancelled.")
|
||||
|
||||
def delete_job(self):
|
||||
"""Delete the currently selected job.
|
||||
TODO: We should also delete the output file group from storage.
|
||||
"""
|
||||
try:
|
||||
job = self.jobs[self.selected_job.index]
|
||||
self._call(self.batch.job.delete, job.id)
|
||||
self.update_job(self.selected_job.index)
|
||||
maya.execute(self.get_thumbnail)
|
||||
maya.refresh()
|
||||
except (IndexError, AttributeError) as exp:
|
||||
self._log.warning("Selected job index does not match jobs list.")
|
|
@ -0,0 +1,216 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
from azure.batch import models
|
||||
|
||||
import utils
|
||||
from api import MayaAPI as maya
|
||||
from ui_pools import PoolsUI
|
||||
|
||||
|
||||
|
||||
class AzureBatchPools(object):
|
||||
"""Handler for pool functionality."""
|
||||
|
||||
def __init__(self, frame, call):
|
||||
"""Create new Pool Handler.
|
||||
|
||||
:param frame: The shared plug-in UI frame.
|
||||
:type frame: :class:`.AzureBatchUI`
|
||||
:param func call: The shared REST API call wrapper.
|
||||
"""
|
||||
self._log = logging.getLogger('AzureBatchMaya')
|
||||
self._call = call
|
||||
self._session = None
|
||||
|
||||
self.batch = None
|
||||
self.ui = PoolsUI(self, frame)
|
||||
self.pools = []
|
||||
self.selected_pool = None
|
||||
|
||||
def configure(self, session):
|
||||
"""Populate the Batch client for the current sessions of the pools tab.
|
||||
Called on successful authentication.
|
||||
"""
|
||||
self._session = session
|
||||
self.batch = self._session.batch
|
||||
|
||||
def list_pools(self, lazy=False):
|
||||
"""Retrieves the currently running pools. Is called on loading and
|
||||
refreshing the pools tab, also when populating the job submission
|
||||
pool selection drop down menu.
|
||||
"""
|
||||
#if lazy and self.pools:
|
||||
# return [pool.id for pool in self.pools if not pool.auto]
|
||||
self.pools = [p for p in self._call(self.batch.pool.list)]
|
||||
self.pools.sort(key=lambda x: x.creation_time, reverse=True)
|
||||
self.count = len(self.pools)
|
||||
return [pool.id for pool in self.pools if not pool.id.startswith("Maya_Auto_Pool")]
|
||||
|
||||
def get_pools(self):
|
||||
"""Retrieves the currently running pools and populates the UI
|
||||
list. Is called on loading and refreshing the pools tab.
|
||||
"""
|
||||
self.list_pools()
|
||||
display_pools = []
|
||||
for index, pool in enumerate(self.pools):
|
||||
name = pool.display_name if pool.display_name else pool.id
|
||||
display_pools.append(self.ui.create_pool_entry(name, index))
|
||||
return display_pools
|
||||
|
||||
def pool_selected(self, pool_ui):
|
||||
"""Function called when opening and closing the pool details
|
||||
expanding sections on the UI.
|
||||
"""
|
||||
if self.selected_pool and pool_ui:
|
||||
self.selected_pool.collapse()
|
||||
self.selected_pool = pool_ui
|
||||
if pool_ui:
|
||||
self.update_pool(pool_ui.index)
|
||||
|
||||
def update_pool(self, index):
|
||||
"""Update the display for the currently selected pool. This is called
|
||||
when a specific pool is selected or refreshed in the UI.
|
||||
"""
|
||||
try:
|
||||
pool = self.pools[index]
|
||||
self.selected_pool.set_label("loading...")
|
||||
maya.refresh()
|
||||
pool = self._call(self.batch.pool.get, pool.id)
|
||||
_nodes = self._call(self.batch.compute_node.list, pool.id)
|
||||
nodes = [n for n in _nodes]
|
||||
self.selected_pool.set_label(pool.display_name if pool.display_name else pool.id)
|
||||
self.selected_pool.set_size(pool.current_dedicated)
|
||||
self.selected_pool.set_target(pool.target_dedicated)
|
||||
self.selected_pool.set_type(
|
||||
"Auto" if pool.id.startswith("Maya_Auto_Pool") else "Provisioned")
|
||||
self.selected_pool.set_state(pool.state.value)
|
||||
self.selected_pool.set_tasks(pool.max_tasks_per_node)
|
||||
self.selected_pool.set_allocation(pool.allocation_state.value)
|
||||
self.selected_pool.set_created(pool.creation_time)
|
||||
maya.refresh()
|
||||
except Exception as exp:
|
||||
self._log.warning(str(exp))
|
||||
self.ui.refresh()
|
||||
|
||||
def is_auto_pool(self):
|
||||
"""Returns whether the selected pool is an auto-pool or a
|
||||
persistant pool.
|
||||
"""
|
||||
try:
|
||||
pool = self.pools[self.selected_pool.index]
|
||||
return pool.id.startswith("Maya_Auto_Pool")
|
||||
except (IndexError, TypeError, AttributeError) as exp:
|
||||
self._log.info("Unable to retrieve selected pool {0}".format(exp))
|
||||
return False
|
||||
|
||||
def delete_pool(self):
|
||||
"""Delete the currently selected pool."""
|
||||
try:
|
||||
pool = self.pools[self.selected_pool.index]
|
||||
self._log.info("Deleting pool '{}'.".format(pool.id))
|
||||
self._call(self.batch.pool.delete, pool.id)
|
||||
except (IndexError, TypeError, AttributeError) as exp:
|
||||
self._log.info("Unable to retrieve selected pool {0}".format(exp))
|
||||
return
|
||||
finally:
|
||||
self.ui.refresh()
|
||||
|
||||
def get_pool_size(self):
|
||||
"""Get the target number of VMs in the selected pool."""
|
||||
try:
|
||||
pool = self.pools[self.selected_pool.index]
|
||||
return int(pool.target_dedicated)
|
||||
except (IndexError, TypeError, AttributeError) as exp:
|
||||
self._log.info("Failed to parse pool target size {0}".format(exp))
|
||||
return 0
|
||||
|
||||
def create_pool(self, size, name):
|
||||
"""Create and deploy a new pool.
|
||||
Called on job submission by submission.py.
|
||||
TODO: Support both Windows and Linux images.
|
||||
TODO: Support auto-scale formula.
|
||||
TODO: Configure VM size in UI.
|
||||
"""
|
||||
pool_id = 'Maya_Pool_{}'.format(uuid.uuid4())
|
||||
pool_config = models.VirtualMachineConfiguration(
|
||||
image_reference=models.ImageReference(**utils.MAYA_IMAGE_WINDOWS),
|
||||
node_agent_sku_id=utils.MAYA_SKU_WINDOWS)
|
||||
self._log.info("Creating new pool '{}' with {} VMs.".format(name, size))
|
||||
new_pool = models.PoolAddParameter(
|
||||
id=pool_id,
|
||||
display_name="Maya Pool for {}".format(name),
|
||||
vm_size="Standard_D4_v2",
|
||||
virtual_machine_configuration=pool_config,
|
||||
target_dedicated=int(size),
|
||||
max_tasks_per_node=1)
|
||||
self._call(self.batch.pool.add, new_pool)
|
||||
self._log.debug("Successfully created pool.")
|
||||
return {"poolId" : pool_id}
|
||||
|
||||
def create_auto_pool(self, size, job_name):
|
||||
"""Create a JSON auto pool specification.
|
||||
Called on job submission by submission.py.
|
||||
TODO: Support both Windows and Linux images.
|
||||
"""
|
||||
pool_config = {
|
||||
'imageReference': utils.MAYA_IMAGE_WINDOWS,
|
||||
'nodeAgentSKUId': utils.MAYA_SKU_WINDOWS}
|
||||
pool_spec = {
|
||||
'vmSize': 'Standard_D4_v2',
|
||||
'displayName': "Auto Pool for {}".format(job_name),
|
||||
'virtualMachineConfiguration': pool_config,
|
||||
'maxTasksPerNode': 1,
|
||||
'targetDedicated': int(size)}
|
||||
auto_pool = {
|
||||
'autoPoolIdPrefix': "Maya_Auto_Pool_",
|
||||
'poolLifetimeOption': "job",
|
||||
'keepAlive': False,
|
||||
'pool': pool_spec}
|
||||
return {'autoPoolSpecification': auto_pool}
|
||||
|
||||
def resize_pool(self, new_size):
|
||||
"""Resize an existing pool."""
|
||||
try:
|
||||
self.selected_pool.change_resize_label("Resizing...")
|
||||
maya.refresh()
|
||||
|
||||
pool = self.pools[self.selected_pool.index]
|
||||
self._log.info("Resizing pool '{}' to {} VMs".format(pool.id, new_size))
|
||||
self._call(self.batch.pool.resize, pool.id, {'target_dedicated':int(new_size)})
|
||||
self.selected_pool.change_resize_label("Resize Pool")
|
||||
self.selected_pool.set_target(new_size)
|
||||
maya.refresh()
|
||||
except Exception as exp:
|
||||
self._log.info("Failed to resize pool {0}".format(exp))
|
||||
self.ui.refresh()
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
import webbrowser
|
||||
import os
|
||||
import threading
|
||||
|
||||
from ui_shared import AzureBatchUI
|
||||
from config import AzureBatchConfig
|
||||
from submission import AzureBatchSubmission
|
||||
from history import AzureBatchHistory
|
||||
from assets import AzureBatchAssets
|
||||
from pools import AzureBatchPools
|
||||
#from environment import AzureBatchEnvironment
|
||||
|
||||
from api import MayaAPI as maya
|
||||
from azure.batch.models import BatchErrorException
|
||||
|
||||
|
||||
ACCEPTED_ERRORS = [
|
||||
"JobNotFound",
|
||||
"PoolNotFound",
|
||||
]
|
||||
|
||||
|
||||
class AzureBatchSettings(object):
|
||||
|
||||
@staticmethod
|
||||
def starter():
|
||||
"""Called by the mel script when the shelf button is clicked."""
|
||||
AzureBatchSettings()
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize all the tabs and attempt to authenticate using cached
|
||||
credentials if available.
|
||||
"""
|
||||
self._log = logging.getLogger('AzureBatchMaya')
|
||||
try:
|
||||
self.frame = AzureBatchUI(self)
|
||||
self.config = AzureBatchConfig(self.frame, self.start)
|
||||
self.submission = AzureBatchSubmission(self.frame, self.call)
|
||||
self.assets = AzureBatchAssets(self.frame, self.call)
|
||||
self.pools = AzureBatchPools(self.frame, self.call)
|
||||
self.history = AzureBatchHistory(self.frame, self.call)
|
||||
self.start()
|
||||
except Exception as exp:
|
||||
if (maya.window("AzureBatch", q=1, exists=1)):
|
||||
maya.delete_ui("AzureBatch")
|
||||
message = "Batch Plugin Failed to Start: {0}".format(exp)
|
||||
maya.error(message)
|
||||
|
||||
def start(self):
|
||||
"""Start the plugin UI. Depending on whether auto-authentication was
|
||||
successful, the plugin will start by displaying the submission tab.
|
||||
Otherwise the UI will be disables, and the log in tab will be displayed.
|
||||
"""
|
||||
try:
|
||||
self._log.debug("Starting AzureBatchShared...")
|
||||
if self.config.auth:
|
||||
self.frame.is_logged_in()
|
||||
self.history.configure(self.config)
|
||||
self.assets.configure(self.config)
|
||||
self.pools.configure(self.config)
|
||||
self.submission.start(self.config, self.assets, self.pools)
|
||||
else:
|
||||
self.frame.is_logged_out()
|
||||
except Exception as exp:
|
||||
self._log.warning(exp)
|
||||
if (maya.window("AzureBatch", q=1, exists=1)):
|
||||
maya.delete_ui("AzureBatch")
|
||||
maya.error("Batch Plugin UI failed to load:\n{0}".format(exp))
|
||||
|
||||
def call(self, command, *args, **kwargs):
|
||||
"""Wrap all Batch and Storage API calls in order to handle errors.
|
||||
Some errors we anticipate and raise without a dialog (e.g. PoolNotFound).
|
||||
Others we raise and display to the user.
|
||||
"""
|
||||
try:
|
||||
return command(*args, **kwargs)
|
||||
except BatchErrorException as exp:
|
||||
if exp.error.code in ACCEPTED_ERRORS:
|
||||
self._log.info("Call failed: {}".format(exp.error.code))
|
||||
raise
|
||||
else:
|
||||
message = exp.error.message.value
|
||||
if exp.error.values:
|
||||
message += "Details:\n"
|
||||
for detail in exp.error.values:
|
||||
message += "{}: {}".format(details.key, detail.value)
|
||||
maya.error(message)
|
||||
raise
|
||||
except Exception as exp:
|
||||
if (maya.window("AzureBatch", q=1, exists=1)):
|
||||
maya.delete_ui("AzureBatch")
|
||||
maya.error("Error: {0}".format(exp))
|
||||
raise
|
|
@ -0,0 +1,297 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import sys
|
||||
import pkgutil
|
||||
import inspect
|
||||
import importlib
|
||||
import logging
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from api import MayaAPI as maya
|
||||
from api import MayaCallbacks as callback
|
||||
|
||||
from ui_submission import SubmissionUI
|
||||
from exception import CancellationException, PoolException
|
||||
import utils
|
||||
from default import AzureBatchRenderJob
|
||||
|
||||
|
||||
class AzureBatchSubmission(object):
|
||||
"""Handler for job submission functionality."""
|
||||
|
||||
def __init__(self, frame, call):
|
||||
"""Create new Submission Handler.
|
||||
|
||||
:param frame: The shared plug-in UI frame.
|
||||
:type frame: :class:`.AzureBatchUI`
|
||||
:param func call: The shared REST API call wrapper.
|
||||
"""
|
||||
self._log = logging.getLogger('AzureBatchMaya')
|
||||
self._call = call
|
||||
|
||||
self.ui = SubmissionUI(self, frame)
|
||||
self.modules = self._collect_modules()
|
||||
self.renderer = None
|
||||
self.frame = frame
|
||||
self.asset_manager = None
|
||||
self.pool_manager = None
|
||||
#self.env_manager = None
|
||||
self.batch = None
|
||||
|
||||
callback.after_new(self.ui.refresh)
|
||||
callback.after_open(self.ui.refresh)
|
||||
|
||||
def _collect_modules(self):
|
||||
"""Collect the renderer-specific submission modules. This is where
|
||||
the renderer-specfic job processing is defined.
|
||||
"""
|
||||
self._log.info("Collecting modules...")
|
||||
render_modules = []
|
||||
module_dir = os.environ['AZUREBATCH_MODULES']
|
||||
for importer, package_name, _ in pkgutil.iter_modules([module_dir]):
|
||||
if package_name == "default":
|
||||
continue
|
||||
module = importlib.import_module(package_name)
|
||||
for name, obj in inspect.getmembers(module, inspect.isclass):
|
||||
if issubclass(obj, AzureBatchRenderJob):
|
||||
renderer = obj()
|
||||
render_modules.append(renderer)
|
||||
self._log.debug(
|
||||
"Appended {0} to render module list.".format(renderer.label))
|
||||
return render_modules
|
||||
|
||||
def _configure_renderer(self):
|
||||
"""Configure the renderer-specific job processing according
|
||||
to the currently selected render engine.
|
||||
Called by both the start and refresh functions.
|
||||
"""
|
||||
self._log.info("Configuring renderer...")
|
||||
current_renderer = maya.get_attr("defaultRenderGlobals.currentRenderer")
|
||||
self._log.debug("Current renderer: {0}".format(current_renderer))
|
||||
|
||||
for module in self.modules:
|
||||
if not hasattr(module, 'render_engine'):
|
||||
self._log.warning(
|
||||
"Module {0} has no render engine attribute. Skipping.".format(module))
|
||||
continue
|
||||
if module.render_engine == str(current_renderer):
|
||||
self.renderer = module
|
||||
self._log.debug("Configured renderer to {0}".format(self.renderer.render_engine))
|
||||
return
|
||||
self.renderer = AzureBatchRenderJob()
|
||||
self._log.debug("Configured renderer to {0}".format(self.renderer.render_engine))
|
||||
|
||||
def _check_outputs(self):
|
||||
"""Check whether at least one of the scene cameras is marked as renderable
|
||||
and that at least one layer is a render layer. If not, there will be no
|
||||
outputs so we raise an error.
|
||||
"""
|
||||
cameras = maya.get_list(type="camera")
|
||||
render_cams = [maya.get_attr(c + ".renderable") for c in cameras]
|
||||
if not any(render_cams):
|
||||
raise ValueError("No render camera selected. Please select a render "
|
||||
"camera and save the scene before submitting.")
|
||||
layers = maya.get_list(type="renderLayer")
|
||||
render_layers = [maya.get_attr(l + ".renderable") for l in layers]
|
||||
if not any(render_layers):
|
||||
raise ValueError("No render layers enabled. Please enable a render "
|
||||
"layer and save the scene before submitting.")
|
||||
|
||||
def _check_plugins(self):
|
||||
"""Checks through all plug-ins that are currently in use (according to Maya)
|
||||
by the scene. We compile a list of those that we both support and should not
|
||||
ignore and return for inclusion in the pre-render script plug-in loading.
|
||||
|
||||
TODO: This has been temporarily disabled because some of the plug-ins were
|
||||
causing errors in Maya on the render nodes. Need to investigate and add the
|
||||
culprits to the ignore list if necessary.
|
||||
"""
|
||||
try:
|
||||
with open(os.path.join(os.environ["AZUREBATCH_TOOLS"],
|
||||
"supported_plugins.json"), 'r') as plugins:
|
||||
supported_plugins = json.load(plugins)
|
||||
with open(os.path.join(os.environ["AZUREBATCH_TOOLS"],
|
||||
"ignored_plugins.json"), 'r') as plugins:
|
||||
ignored_plugins = json.load(plugins)
|
||||
except EnvironmentError:
|
||||
self._log.warning("Unable to load supported plugins")
|
||||
return []
|
||||
loaded_plugins = maya.plugins(query=True, listPlugins=True)
|
||||
unsupported_plugins = [p for p in loaded_plugins \
|
||||
if p not in supported_plugins and p not in ignored_plugins]
|
||||
if unsupported_plugins:
|
||||
warning = ("The following plug-ins are used in the scene, but are not "
|
||||
"yet supported.\nRendering may be affected.\n")
|
||||
for plugin in unsupported_plugins:
|
||||
warning += plugin + "\n"
|
||||
options = ["Continue", "Cancel"]
|
||||
answer = maya.confirm(warning, options)
|
||||
if answer == options[-1]:
|
||||
raise CancellationException("Submission Aborted")
|
||||
return []
|
||||
#return [p for p in loaded_plugins \
|
||||
# if p in supported_plugins and p not in ignored_plugins]
|
||||
|
||||
def _configure_pool(self, job_name):
|
||||
"""Based on the selected pool option for the job, either deploy a new
|
||||
pool, create an auto-pool specification, or simply return the ID of the
|
||||
chosen existing pool.
|
||||
|
||||
:param str job_name: The name of the job being submitted. Used for creating
|
||||
useful pool names.
|
||||
"""
|
||||
pool_spec = self.ui.get_pool()
|
||||
if pool_spec.get(1):
|
||||
self._log.info("Using auto-pool.")
|
||||
return self.pool_manager.create_auto_pool(int(pool_spec[1]), job_name)
|
||||
if pool_spec.get(2):
|
||||
self._log.info("Using existing pool.")
|
||||
pool_id = str(pool_spec[2])
|
||||
if pool_id == "None":
|
||||
raise PoolException("No pool selected.")
|
||||
return {"poolId" : pool_id}
|
||||
if pool_spec.get(3):
|
||||
self._log.info("Creating new pool.")
|
||||
return self.pool_manager.create_pool(int(pool_spec[3]), job_name)
|
||||
|
||||
def start(self, session, assets, pools): #, env):
|
||||
"""Load submission tab after plug-in has been authenticated.
|
||||
|
||||
:param session: Authenticated configuration handler.
|
||||
:type session: :class:`.AzureBatchConfig`
|
||||
:param assets: Asset handler.
|
||||
:type assets: :class:`.AzureBatchAssets`
|
||||
:param pools: Pool handler.
|
||||
:type pools: :class:`.AzureBatchPools`
|
||||
|
||||
TODO:
|
||||
:param env: Render node environment handler.
|
||||
:type env: :class:`.AzureBatchEnvironment`
|
||||
"""
|
||||
self._log.debug("Starting AzureBatchSubmission...")
|
||||
self.batch = session.batch
|
||||
self.storage = session.storage
|
||||
self.asset_manager = assets
|
||||
self.pool_manager = pools
|
||||
#self.env_manager = env
|
||||
self.data_path = session.path
|
||||
if self.renderer:
|
||||
self.renderer.delete()
|
||||
self._configure_renderer()
|
||||
self.renderer.display(self.ui.render_module)
|
||||
self.ui.submit_enabled(self.renderer.render_enabled())
|
||||
self.ui.is_logged_in()
|
||||
maya.refresh()
|
||||
|
||||
def refresh_renderer(self, layout):
|
||||
"""Refresh the displayed renderer module for job submission
|
||||
settings. The module is completely wiped and re-loaded - this allows
|
||||
the user to load a new scene, or swap to a new render engine.
|
||||
"""
|
||||
self.renderer.delete()
|
||||
self._configure_renderer()
|
||||
self.renderer.display(layout)
|
||||
self.ui.submit_enabled(self.renderer.render_enabled())
|
||||
|
||||
def available_pools(self):
|
||||
"""Retrieve the currently available pools to populate
|
||||
the job submission pool selection drop down.
|
||||
"""
|
||||
pools = self.pool_manager.list_pools(lazy=True)
|
||||
return pools
|
||||
|
||||
def submit(self, watch_job=False, download_dir=None):
|
||||
"""Submit a new job.
|
||||
|
||||
:param watch_job: Whether to launch the job watcher process once
|
||||
the job has submitted.
|
||||
:param download_dir: If launching the job watcher, a download directory
|
||||
must be specified.
|
||||
"""
|
||||
POOL_OS = 'Windows' # TODO: Need to configure the VM environment.
|
||||
job_id = "maya-render-{}".format(uuid.uuid4())
|
||||
self.renderer.disable(False)
|
||||
progress = utils.ProgressBar(self._log)
|
||||
maya.refresh()
|
||||
|
||||
batch_parameters = {'id': job_id}
|
||||
batch_parameters['displayName'] = self.renderer.get_title()
|
||||
batch_parameters['metadata'] = [{"name": "JobType", "value": "Maya"}]
|
||||
template_file = os.path.join(
|
||||
os.environ['AZUREBATCH_TEMPLATES'], 'arnold-basic-{}.json'.format(POOL_OS))
|
||||
batch_parameters['applicationTemplateInfo'] = {'filePath': template_file}
|
||||
application_params = {}
|
||||
batch_parameters['applicationTemplateInfo']['parameters'] = application_params
|
||||
try:
|
||||
self._check_outputs()
|
||||
plugins = self._check_plugins()
|
||||
application_params['outputs'] = job_id
|
||||
|
||||
self.ui.submit_status("Checking assets...")
|
||||
scene_file, renderer_data = self.renderer.get_jobdata()
|
||||
print("a")
|
||||
application_params['sceneFile'] = utils.format_scene_path(scene_file, POOL_OS)
|
||||
print("b")
|
||||
file_group, map_url, progress = self.asset_manager.upload(
|
||||
renderer_data, progress, job_id, plugins, 'Windows')
|
||||
application_params['projectData'] = file_group
|
||||
application_params['assetScript'] = map_url
|
||||
self.frame.select_tab(2)
|
||||
|
||||
self.ui.submit_status("Configuring job...")
|
||||
progress.status("Configuring job...")
|
||||
job_params = self.renderer.get_params()
|
||||
application_params.update(job_params)
|
||||
|
||||
self.ui.submit_status("Setting pool...")
|
||||
progress.status("Setting pool...")
|
||||
pool = self._configure_pool(self.renderer.get_title())
|
||||
batch_parameters['poolInfo'] = pool
|
||||
|
||||
self._log.debug(json.dumps(batch_parameters))
|
||||
new_job = self.batch.job.jobparameter_from_json(batch_parameters)
|
||||
progress.is_cancelled()
|
||||
self.ui.submit_status("Submitting...")
|
||||
progress.status("Submitting...")
|
||||
self._call(self.batch.job.add, new_job)
|
||||
maya.info("Job submitted successfully")
|
||||
|
||||
if watch_job:
|
||||
utils.JobWatcher(new_job.id, self.data_path, download_dir)
|
||||
except CancellationException:
|
||||
maya.info("Job submission cancelled")
|
||||
except Exception as exp:
|
||||
print(exp)
|
||||
maya.error(str(exp))
|
||||
finally:
|
||||
progress.end()
|
||||
self.frame.select_tab(2)
|
||||
self.renderer.disable(True)
|
|
@ -0,0 +1,7 @@
|
|||
[
|
||||
"AzureBatch",
|
||||
"retargeterNodes",
|
||||
"GamePipeline",
|
||||
"CloudImportExport",
|
||||
"OneClick"
|
||||
]
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
import os
|
||||
import tempfile
|
||||
import sys
|
||||
import urllib2
|
||||
|
||||
|
||||
_GET_PIP = "https://bootstrap.pypa.io/get-pip.py"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
temp_dir = os.path.join(tempfile.gettempdir(), 'azure-batch-maya')
|
||||
if not os.path.isdir(temp_dir):
|
||||
os.makedirs(temp_dir)
|
||||
sys.path.append(temp_dir)
|
||||
pip_script = os.path.join(temp_dir, 'getpip.py')
|
||||
if not os.path.exists(pip_script):
|
||||
with open(pip_script, 'w') as script:
|
||||
data = urllib2.urlopen(_GET_PIP)
|
||||
script.write(data.read())
|
||||
import getpip
|
||||
try:
|
||||
getpip.main()
|
||||
except BaseException as e:
|
||||
import pip
|
||||
sys.exit(0)
|
|
@ -0,0 +1,222 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import webbrowser
|
||||
import ConfigParser
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
batch_client = None
|
||||
storage_client = None
|
||||
is_thumnail = re.compile("framethumb_[\d\-]+.png")
|
||||
is_log = re.compile("frame_[\d\-]+.log")
|
||||
|
||||
|
||||
def header(header):
|
||||
header_chars = len(header)
|
||||
total_len = 50
|
||||
dashes = total_len - header_chars
|
||||
mult = int(dashes/2)
|
||||
padded = "\n\n" + mult*"-" + header + mult*"-"
|
||||
if dashes % 2 > 0:
|
||||
padded += "-"
|
||||
return padded
|
||||
|
||||
|
||||
def _check_valid_dir(directory):
|
||||
try:
|
||||
log_dir = os.path.join(directory, "logs")
|
||||
if not os.path.isdir(log_dir):
|
||||
os.makedirs(log_dir)
|
||||
return directory
|
||||
|
||||
except (TypeError, EnvironmentError) as exp:
|
||||
raise RuntimeError(exp)
|
||||
|
||||
def _download_output(container, blob_name, output_path, size):
|
||||
def progress(data, total):
|
||||
try:
|
||||
percent = float(data)*100/float(size)
|
||||
sys.stdout.write(' Downloading... {0}%\r'.format(int(percent)))
|
||||
except:
|
||||
sys.stdout.write(' Downloading... %\r')
|
||||
finally:
|
||||
sys.stdout.flush()
|
||||
|
||||
print("Downloading task output: {}".format(blob_name))
|
||||
storage_client.get_blob_to_path(container, blob_name, output_path, progress_callback=progress)
|
||||
print(" Output download successful.\n")
|
||||
|
||||
def _track_completed_tasks(container, dwnld_dir):
|
||||
try:
|
||||
job_outputs = storage_client.list_blobs(container)
|
||||
for output in job_outputs:
|
||||
if is_log.match(output.name):
|
||||
output_file = os.path.join(dwnld_dir, "logs", output.name)
|
||||
elif is_thumnail.match(output.name) or output.name == "Parameters.json":
|
||||
continue
|
||||
else:
|
||||
output_file = os.path.join(dwnld_dir, output.name)
|
||||
|
||||
if not os.path.isfile(output_file):
|
||||
_download_output(container, output.name, output_file, output.properties.content_length)
|
||||
|
||||
except (TypeError, AttributeError, KeyError) as exp:
|
||||
raise RuntimeError("Failed {0}".format(exp))
|
||||
|
||||
|
||||
def _check_job_stopped(job):
|
||||
"""
|
||||
Checks job for failure or completion.
|
||||
|
||||
:Args:
|
||||
- job (:class:`batchapps.SubmittedJob`): an instance of the current
|
||||
SubmittedJob object.
|
||||
|
||||
:Returns:
|
||||
- A boolean indicating True if the job completed, or False if still in
|
||||
progress.
|
||||
:Raises:
|
||||
- RuntimeError if the job has failed, or been cancelled.
|
||||
"""
|
||||
|
||||
stopped_status = [
|
||||
batch.models.JobState.disabling,
|
||||
batch.models.JobState.disabled,
|
||||
batch.models.JobState.terminating,
|
||||
batch.models.JobState.deleting
|
||||
]
|
||||
running_status = [
|
||||
batch.models.JobState.active,
|
||||
batch.models.JobState.enabling
|
||||
]
|
||||
|
||||
try:
|
||||
if job.state in stopped_status:
|
||||
print(header("Job has stopped"))
|
||||
print("Job status: {0}".format(job.state))
|
||||
raise RuntimeError("Job is no longer running. Status: {0}".format(job.state))
|
||||
|
||||
elif job.state == batch.models.JobState.completed:
|
||||
print(header("Job has completed"))
|
||||
return True
|
||||
|
||||
elif job.state in running_status:
|
||||
return False
|
||||
|
||||
except AttributeError as exp:
|
||||
raise RuntimeError(exp)
|
||||
|
||||
def track_job_progress(id, container, dwnld_dir):
|
||||
print("Tracking job with ID: {0}".format(id))
|
||||
try:
|
||||
job = batch_client.job.get(id)
|
||||
tasks = [t for t in batch_client.task.list(id)]
|
||||
|
||||
while True:
|
||||
completed_tasks = [t for t in tasks if t.state == batch.models.TaskState.completed]
|
||||
errored_tasks = [t for t in completed_tasks if t.execution_info.exit_code != 0]
|
||||
if len(tasks) == 0:
|
||||
percentage = 0
|
||||
else:
|
||||
percentage = (100 * len(completed_tasks)) / len(tasks)
|
||||
print("Running - {}%".format(percentage))
|
||||
if errored_tasks:
|
||||
print(" - Warning: some tasks have completed with a non-zero exit code.")
|
||||
|
||||
_track_completed_tasks(container, dwnld_dir)
|
||||
|
||||
if _check_job_stopped(job):
|
||||
return # Job complete
|
||||
|
||||
time.sleep(10)
|
||||
job = batch_client.job.get(id)
|
||||
print(job.job_preparation_task.command_line)
|
||||
for r in job.job_preparation_task.resource_files:
|
||||
print(r.blob_source)
|
||||
tasks = [t for t in batch_client.task.list(id)]
|
||||
|
||||
except (TypeError, AttributeError) as exp:
|
||||
raise RuntimeError("Error occured: {0}".format(exp))
|
||||
|
||||
except KeyboardInterrupt:
|
||||
raise RuntimeError("Monitoring aborted.")
|
||||
|
||||
def _authenticate(cfg_path):
|
||||
global batch_client, storage_client
|
||||
cfg = ConfigParser.ConfigParser()
|
||||
try:
|
||||
cfg.read(cfg_path)
|
||||
credentials = SharedKeyCredentials(
|
||||
cfg.get("AzureBatch", "batch_account"),
|
||||
cfg.get("AzureBatch", "batch_key"))
|
||||
batch_client = batch.BatchServiceClient(
|
||||
credentials, base_url=cfg.get("AzureBatch", "batch_url"))
|
||||
storage_client = storage.BlockBlobService(
|
||||
cfg.get("AzureBatch", "storage_account"),
|
||||
cfg.get("AzureBatch", "storage_key"),
|
||||
endpoint_suffix="core.windows.net")
|
||||
except (EnvironmentError, ConfigParser.NoOptionError, ConfigParser.NoSectionError) as exp:
|
||||
raise ValueError("Failed to authenticate using Maya configuration {0}".format(cfg_path))
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
sys.path.append(sys.argv[5])
|
||||
print("Appending path {0}".format(sys.argv[5]))
|
||||
|
||||
import azure.storage.blob as storage
|
||||
import azure.batch as batch
|
||||
from azure.batch.batch_auth import SharedKeyCredentials
|
||||
data_path = sys.argv[1]
|
||||
job_id = sys.argv[2]
|
||||
download_dir = sys.argv[3]
|
||||
container = sys.argv[4]
|
||||
|
||||
_check_valid_dir(download_dir)
|
||||
_authenticate(data_path)
|
||||
|
||||
EXIT_STRING = ""
|
||||
track_job_progress(job_id, container, download_dir)
|
||||
|
||||
except (RuntimeError, ValueError) as exp:
|
||||
EXIT_STRING = exp
|
||||
|
||||
except Exception as exp:
|
||||
EXIT_STRING = "An unexpected exception occurred: {0}".format(exp)
|
||||
|
||||
finally:
|
||||
try:
|
||||
input = raw_input
|
||||
except NameError:
|
||||
pass
|
||||
print('\n' + str(EXIT_STRING))
|
||||
if input(header("Press 'enter' to exit")):
|
||||
sys.exit()
|
|
@ -0,0 +1,66 @@
|
|||
[
|
||||
"AbcBullet",
|
||||
"AbcExport",
|
||||
"AbcImport",
|
||||
"animImportExport",
|
||||
"ArubaTessellator",
|
||||
"atomImportExport",
|
||||
"AutodeskPacketFile",
|
||||
"autoLoader",
|
||||
"bullet",
|
||||
"cacheEvaluator",
|
||||
"cgfxShader",
|
||||
"cleanPerFaceAssignment",
|
||||
"clearcoat",
|
||||
"curveWarp",
|
||||
"ddsFloatReader",
|
||||
"deformerEvaluator",
|
||||
"dgProfiler",
|
||||
"dx11Shader",
|
||||
"fltTranslator",
|
||||
"freeze",
|
||||
"Fur",
|
||||
"gameFbxExporter",
|
||||
"glslShader",
|
||||
"GPUBuiltInDeformer",
|
||||
"gpuCache",
|
||||
"hairPhysicalShader",
|
||||
"Iges",
|
||||
"ik2Bsolver",
|
||||
"ikSpringSolver",
|
||||
"lookdevKit",
|
||||
"matrixNodes",
|
||||
"mayaCharacterization",
|
||||
"mayaHIK",
|
||||
"MayaMuscle",
|
||||
"melProfiler",
|
||||
"meshReorder",
|
||||
"modelingToolkit",
|
||||
"nearestPointOnMesh",
|
||||
"objExport",
|
||||
"OpenEXRLoader",
|
||||
"quatNodes",
|
||||
"renderSetup",
|
||||
"rotateHelper",
|
||||
"sceneAssembly",
|
||||
"shaderFXPlugin",
|
||||
"shotCamera",
|
||||
"stereoCamera",
|
||||
"studioImport",
|
||||
"tiffFloatReader",
|
||||
"Turtle",
|
||||
"Type",
|
||||
"Unfold3D",
|
||||
"VectorRender",
|
||||
"ATFPlugin",
|
||||
"bifrostshellnode",
|
||||
"bifrostvisplugin",
|
||||
"Boss",
|
||||
"MASH",
|
||||
"fbxmaya",
|
||||
"invertShape",
|
||||
"poseInterpolator",
|
||||
"mtoa",
|
||||
"Substance",
|
||||
"xgenToolkit"
|
||||
]
|
|
@ -0,0 +1,195 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import utils
|
||||
|
||||
from api import MayaAPI as maya
|
||||
|
||||
|
||||
class AssetsUI(object):
|
||||
"""Class to create the 'Assets' tab in the plug-in UI"""
|
||||
|
||||
def __init__(self, base, frame):
|
||||
"""Create 'Assets' tab and add to UI frame.
|
||||
|
||||
:param base: The base class for handling asset-related functionality.
|
||||
:type base: :class:`.AzureBatchAssets`
|
||||
:param frame: The shared plug-in UI frame.
|
||||
:type frame: :class:`.AzureBatchUI`
|
||||
"""
|
||||
self.base = base
|
||||
self.label = "Assets"
|
||||
self.ready = False
|
||||
self.page = maya.form_layout(enableBackground=True)
|
||||
with utils.Row(2, 2, (70,260), ("left","left")) as proj:
|
||||
maya.text(label="Project: ", align="left")
|
||||
self._asset_group = maya.text_field(height=25, enable=True)
|
||||
|
||||
with utils.ScrollLayout(
|
||||
v_scrollbar=3, h_scrollbar=0, height=450) as scroll:
|
||||
self.scroll_layout = scroll
|
||||
with utils.ColumnLayout(2) as sublayout:
|
||||
self.asset_display = sublayout
|
||||
f_btn = maya.button(label="Add Files", command=self.add_asset)
|
||||
d_btn = maya.button(label="Add Directory", command=self.add_dir)
|
||||
|
||||
with utils.Row(1, 1, 355) as u_btn:
|
||||
self.upload_button = utils.ProcButton(
|
||||
"Upload", "Uploading...", self.upload)
|
||||
|
||||
with utils.Row(1, 1, 355, "center", (1,"bottom",0)) as r_btn:
|
||||
self.refresh_button = utils.ProcButton(
|
||||
"Refresh", "Refreshing...", self.refresh)
|
||||
|
||||
maya.form_layout(
|
||||
self.page, edit=True,
|
||||
attachForm=[(proj, 'left', 5), (proj, 'right', 5),
|
||||
(proj,'top', 5),
|
||||
(scroll, 'left', 5), (scroll, 'right', 5),
|
||||
(f_btn, 'left', 5),(d_btn,'right',5),
|
||||
(u_btn, 'left', 0),(u_btn,'right',0),
|
||||
(r_btn, 'bottom', 5),
|
||||
(r_btn, 'left', 0),(r_btn,'right',0)],
|
||||
attachControl=[(proj, "bottom", 5, scroll),
|
||||
(proj, "bottom", 5, scroll),
|
||||
(scroll, "bottom", 5, f_btn),
|
||||
(scroll, "bottom", 5, d_btn),
|
||||
(f_btn, "bottom" , 5, u_btn),
|
||||
(d_btn, "bottom", 5, u_btn),
|
||||
(u_btn, "bottom",5,r_btn)],
|
||||
attachPosition=[(f_btn, 'right', 5, 50),
|
||||
(d_btn, 'left', 5, 50)])
|
||||
frame.add_tab(self)
|
||||
self.is_logged_out()
|
||||
|
||||
def refresh(self, *args):
|
||||
"""Refresh Assets tab. Command for refresh_button.
|
||||
Remove all existing UI elements and gathered
|
||||
assets and re-build from scratch. This is also called to populate
|
||||
the tab for the first time.
|
||||
"""
|
||||
self.refresh_button.start()
|
||||
self.clear_ui()
|
||||
maya.refresh()
|
||||
project_name = self.base.get_project()
|
||||
maya.text_field(self._asset_group, edit=True, text=project_name)
|
||||
self.base.set_assets()
|
||||
for f in self.base.get_assets():
|
||||
f.display(self, self.asset_display, self.scroll_layout)
|
||||
self.refresh_button.finish()
|
||||
|
||||
def upload(self, *args):
|
||||
"""Upload gathered assets. Command for upload_button.
|
||||
Calls the base upload function.
|
||||
"""
|
||||
self.base.upload()
|
||||
|
||||
def upload_status(self, status):
|
||||
"""Report upload status in UI. Called from base class.
|
||||
Displays status in the upload_button label.
|
||||
:param str status: The status string to display.
|
||||
"""
|
||||
self.upload_button.update(status)
|
||||
|
||||
def disable(self, enabled):
|
||||
"""Disable the tab from user interaction. Used during long running
|
||||
processes like upload, and when plug-in is unauthenticated.
|
||||
:param bool enabled: Whether to enable the display. False will
|
||||
disable the display.
|
||||
"""
|
||||
maya.form_layout(self.page, edit=True, enable=enabled)
|
||||
|
||||
def clear_ui(self):
|
||||
"""Wipe all UI elements in the Assets tab."""
|
||||
children = maya.col_layout(self.asset_display,
|
||||
query=True,
|
||||
childArray=True)
|
||||
if not children:
|
||||
return
|
||||
for child in children:
|
||||
maya.delete_ui(child, control=True)
|
||||
|
||||
def add_asset(self, *args):
|
||||
"""Add one or more individual asset files to the collection.
|
||||
Command for the 'Add Files' button.
|
||||
"""
|
||||
cap = "Select additional rendering assets"
|
||||
fil = "All Files (*.*)"
|
||||
okCap = "Add Files"
|
||||
new_files = maya.file_select(fileFilter=fil,
|
||||
fileMode=4,
|
||||
okCaption=okCap,
|
||||
caption=cap)
|
||||
if not new_files:
|
||||
return
|
||||
self.base.add_files(new_files, self.asset_display, self.scroll_layout)
|
||||
|
||||
|
||||
def add_dir(self, *args):
|
||||
"""Add all the files from a specified directory as assets to the
|
||||
collection. Command for the 'Add Directory' button.
|
||||
"""
|
||||
cap = "Select directory of assets"
|
||||
okCap = "Add Folder"
|
||||
new_dir = maya.file_select(fileMode=3, okCaption=okCap, caption=cap)
|
||||
if not new_dir:
|
||||
return
|
||||
self.base.add_dir(new_dir, self.asset_display, self.scroll_layout)
|
||||
|
||||
def is_logged_in(self):
|
||||
"""Called when the plug-in is authenticated. Enables UI."""
|
||||
maya.form_layout(self.page, edit=True, enable=True)
|
||||
|
||||
def is_logged_out(self):
|
||||
"""Called when the plug-in is logged out. Disables UI and resets
|
||||
whether that tab has been loaded for the first time.
|
||||
"""
|
||||
maya.form_layout(self.page, edit=True, enable=False)
|
||||
self.ready = False
|
||||
|
||||
def get_project(self):
|
||||
return maya.text_field(self._asset_group, query=True, text=True)
|
||||
|
||||
def prepare(self):
|
||||
"""Called when the tab is loaded (clicked into) for the first time.
|
||||
Initiates the gathering of assets and preparing of UI elements.
|
||||
Once loaded, remains so for the rest of the plug-in session unless
|
||||
logged out or manually refreshed.
|
||||
If loading the UI fails, the tab returns to a logged-out state.
|
||||
"""
|
||||
if not self.ready:
|
||||
maya.refresh()
|
||||
try:
|
||||
self.refresh()
|
||||
self.is_logged_in()
|
||||
self.ready = True
|
||||
except Exception as exp:
|
||||
maya.error("Error starting Assets UI: {0}".format(exp))
|
||||
self.is_logged_out()
|
||||
maya.refresh()
|
|
@ -0,0 +1,220 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import utils
|
||||
|
||||
from api import MayaAPI as maya
|
||||
|
||||
|
||||
class ConfigUI(object):
|
||||
"""Class to create the 'Config' tab in the plug-in UI"""
|
||||
|
||||
def __init__(self, base, frame):
|
||||
"""Create 'Config' tab and add to UI frame.
|
||||
|
||||
:param base: The base class for handling configuration and
|
||||
auth-related functionality.
|
||||
:type base: :class:`.AzureBatchConfig`
|
||||
:param frame: The shared plug-in UI frame.
|
||||
:type frame: :class:`.AzureBatchUI`
|
||||
"""
|
||||
self.base = base
|
||||
self.label = "Config"
|
||||
self.ready = False
|
||||
self.page = maya.form_layout()
|
||||
|
||||
with utils.ScrollLayout(height=520, parent=self.page) as scroll:
|
||||
self.heading = maya.text(label="Authenticating plug-in...",
|
||||
align="center", font="boldLabelFont")
|
||||
with utils.Row(2, 2, (70,260), ("left","left"),
|
||||
[(1, "top", 15),(2,"top",15)]):
|
||||
maya.text(label="Status: ", align="left")
|
||||
self.auth_status = maya.text(label="", align="left")
|
||||
|
||||
with utils.Row(2, 2, (70,260), ("left","left")):
|
||||
maya.text(label="Service: ", align="left")
|
||||
self._endpoint = maya.text_field(height=25, enable=True)
|
||||
|
||||
with utils.Row(2, 2, (70,260), ("left","center"),
|
||||
[(1, "bottom", 20),(2,"bottom",15)]):
|
||||
maya.text(label="Logging: ", align="left")
|
||||
with utils.Dropdown(self.set_logging) as log_settings:
|
||||
self._logging = log_settings
|
||||
self._logging.add_item("Debug")
|
||||
self._logging.add_item("Info")
|
||||
self._logging.add_item("Warning")
|
||||
self._logging.add_item("Error")
|
||||
|
||||
box_label = "Azure Authentication Settings"
|
||||
with utils.FrameLayout(label=box_label, collapsable=True):
|
||||
with utils.Row(2, 2, (140, 180), ("right","center"),
|
||||
[(1, "top", 20),(2, "top", 15)]):
|
||||
maya.text(label="Batch Account: ", align="right")
|
||||
self._account = maya.text_field(height=25, enable=True)
|
||||
|
||||
with utils.Row(2, 2, (140, 180), ("right","center"),
|
||||
[(1, "bottom", 20),(2,"bottom",15)]):
|
||||
maya.text(label="Batch Key: ", align="right")
|
||||
self._key = maya.text_field(height=25, enable=True)
|
||||
|
||||
with utils.Row(2, 2, (140, 180), ("right","center"),
|
||||
[(1, "bottom", 20),(2,"bottom",15)]):
|
||||
maya.text(label="Storage Account: ", align="right")
|
||||
self._storage = maya.text_field(height=25, enable=True)
|
||||
|
||||
with utils.Row(2, 2, (140, 180), ("right","center"),
|
||||
[(1, "bottom", 20),(2,"bottom",15)]):
|
||||
maya.text(label="Storage Key: ", align="right")
|
||||
self._storage_key = maya.text_field(height=25, enable=True)
|
||||
|
||||
with utils.Row(1, 1, 355, "center", (1, "bottom",0)) as btn:
|
||||
self._authenticate = maya.button(label="Authenticate",
|
||||
command=self.authenticate,
|
||||
enable=True)
|
||||
maya.form_layout(self.page, edit=True,
|
||||
attachForm=[(scroll, 'top', 5),
|
||||
(scroll, 'left', 5), (scroll, 'right', 5),
|
||||
(btn, 'bottom', 5),
|
||||
(btn, 'left', 0), (btn, 'right', 0)],
|
||||
attachControl=(scroll, "bottom", 5, btn))
|
||||
frame.add_tab(self)
|
||||
maya.form_layout(self.page, edit=True, enable=False)
|
||||
maya.refresh()
|
||||
|
||||
@property
|
||||
def endpoint(self):
|
||||
"""AzureBatch Service Endpoint. Retrieves contents of text field."""
|
||||
return maya.text_field(self._endpoint, query=True, text=True)
|
||||
|
||||
@endpoint.setter
|
||||
def endpoint(self, value):
|
||||
"""AzureBatch Service Endpoint. Sets contents of text field."""
|
||||
maya.text_field(self._endpoint, edit=True, text=str(value))
|
||||
|
||||
@property
|
||||
def account(self):
|
||||
"""AzureBatch Unattended Account ID. Retrieves contents of text field."""
|
||||
return maya.text_field(self._account, query=True, text=True)
|
||||
|
||||
@account.setter
|
||||
def account(self, value):
|
||||
"""AzureBatch Unattended Account ID. Sets contents of text field."""
|
||||
maya.text_field(self._account, edit=True, text=str(value))
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
"""AzureBatch Unattended Account Key. Retrieves contents of text field."""
|
||||
return maya.text_field(self._key, query=True, text=True)
|
||||
|
||||
@key.setter
|
||||
def key(self, value):
|
||||
"""AzureBatch Unattended Account Key. Sets contents of text field."""
|
||||
maya.text_field(self._key, edit=True, text=str(value))
|
||||
|
||||
@property
|
||||
def storage(self):
|
||||
"""AzureBatch AAD Client ID. Retrieves contents of text field."""
|
||||
return maya.text_field(self._storage, query=True, text=True)
|
||||
|
||||
@storage.setter
|
||||
def storage(self, value):
|
||||
"""AzureBatch AAD Client ID. Sets contents of text field."""
|
||||
maya.text_field(self._storage, edit=True, text=str(value))
|
||||
|
||||
@property
|
||||
def storage_key(self):
|
||||
"""AzureBatch AAD Tenant. Retrieves contents of text field."""
|
||||
return maya.text_field(self._storage_key, query=True, text=True)
|
||||
|
||||
@storage_key.setter
|
||||
def storage_key(self, value):
|
||||
"""AzureBatch AAD Tenant. Sets contents of text field."""
|
||||
maya.text_field(self._storage_key, edit=True, text=str(value))
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""Plug-in authentication status. Sets contents of label."""
|
||||
return maya.text(self.auth_status, query=True, label=True)[8:]
|
||||
|
||||
@status.setter
|
||||
def status(self, value):
|
||||
"""Plug-in authentication status. Sets contents of label."""
|
||||
maya.text(self.auth_status, edit=True, label=value)
|
||||
|
||||
@property
|
||||
def logging(self):
|
||||
"""Plug-in logging level. Retrieves selected level from dropdown."""
|
||||
return self._logging.selected() * 10
|
||||
|
||||
@logging.setter
|
||||
def logging(self, value):
|
||||
"""Plug-in logging level. Sets selected level from dropdown."""
|
||||
self._logging.select(int(value)/10)
|
||||
|
||||
def is_logged_in(self):
|
||||
"""Called when the plug-in is authenticated. Sets heading text."""
|
||||
maya.text(
|
||||
self.heading, edit=True, label="Authentication Configuration")
|
||||
print("x")
|
||||
maya.form_layout(self.page, edit=True, enable=True)
|
||||
|
||||
def is_logged_out(self):
|
||||
"""Called when the plug-in is logged out. Sets heading text."""
|
||||
maya.text(
|
||||
self.heading, edit=True, label="Authentication Configuration")
|
||||
maya.form_layout(self.page, edit=True, enable=True)
|
||||
|
||||
def prepare(self):
|
||||
"""Prepare Config tab contents - nothing needs to be done here as all
|
||||
loaded on plug-in start up.
|
||||
"""
|
||||
pass
|
||||
|
||||
def set_logging(self, level):
|
||||
"""Set logging level. Command for logging dropdown selection.
|
||||
:param str level: The selected logging level, e.g. ``debug``.
|
||||
"""
|
||||
self.base.set_logging(level.lower())
|
||||
|
||||
def set_authenticate(self, auth):
|
||||
"""Set label of authentication button depending on auth status.
|
||||
:param bool auth: Whether plug-in is authenticated.
|
||||
"""
|
||||
if auth:
|
||||
maya.button(
|
||||
self._authenticate, edit=True, label="Refresh Authentication")
|
||||
else:
|
||||
maya.button(self._authenticate, edit=True, label="Authenticate")
|
||||
|
||||
def authenticate(self, *args):
|
||||
"""Initiate plug-in authentication, and save updated credentials
|
||||
to the config file.
|
||||
"""
|
||||
self.base.save_changes()
|
||||
self.base.authenticate()
|
||||
|
|
@ -0,0 +1,295 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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 api import MayaAPI as maya
|
||||
|
||||
import os
|
||||
import utils
|
||||
|
||||
def edit_cell(*args):
|
||||
return 1
|
||||
|
||||
class EnvironmentUI(object):
|
||||
"""
|
||||
Class to create the 'Env' tab in the plug-in UI
|
||||
"""
|
||||
|
||||
def __init__(self, base, frame, versions):
|
||||
"""
|
||||
Create 'Env' tab and add to UI frame.
|
||||
|
||||
:Args:
|
||||
- base (:class:`.AzureBatchEnvironemtn`): The base class for handling
|
||||
Maya and plugin-related functionality.
|
||||
- frame (:class:`.AzureBatchUI`): The shared plug-in UI frame.
|
||||
"""
|
||||
|
||||
self.base = base
|
||||
self.label = " Env "
|
||||
self.ready = False
|
||||
|
||||
self.page = maya.form_layout(enableBackground=True)
|
||||
|
||||
with utils.ScrollLayout(
|
||||
v_scrollbar=3, h_scrollbar=0, height=520) as scroll:
|
||||
|
||||
with utils.RowLayout(row_spacing=20) as sublayout:
|
||||
|
||||
with utils.FrameLayout(
|
||||
label="Maya Version", collapsable=True, width=325):
|
||||
|
||||
with utils.ColumnLayout(
|
||||
2, col_width=((1,160),(2,160)), row_spacing=(1,5),
|
||||
row_offset=((1, "top", 15),(3, "bottom", 15))):
|
||||
|
||||
maya.text(
|
||||
label="Use Maya version: ", align='right')
|
||||
|
||||
with utils.Dropdown(self.set_version) as v_settings:
|
||||
self._maya_version = v_settings
|
||||
for v in versions:
|
||||
self._maya_version.add_item(v)
|
||||
|
||||
maya.text(
|
||||
label="Use my license server: ", align='right')
|
||||
|
||||
self.use_license = maya.check_box(
|
||||
label="", value=False,
|
||||
changeCommand=self.user_license_server)
|
||||
|
||||
self.custom_license_endp = maya.text_field(
|
||||
placeholderText='License Server', enable=False)
|
||||
|
||||
self.custom_license_port = maya.text_field(
|
||||
placeholderText='Port', enable=False )
|
||||
|
||||
with utils.FrameLayout(
|
||||
label="Environment Variables", collapsable=True,
|
||||
width=325, collapse=True):
|
||||
|
||||
with utils.Row(1,1,325):
|
||||
self.env_vars = maya.table(
|
||||
rows=0, columns=2, columnWidth=[(1,155), (2,155)],
|
||||
label=[(1,"Setting"), (2,"Value")], rowHeight=15,
|
||||
editable=False, selectionBehavior=1,
|
||||
getCellCmd=self.populate_row)
|
||||
|
||||
with utils.ColumnLayout(2, col_width=((1,160),(2,160))):
|
||||
|
||||
self.custom_env_var = maya.text_field(
|
||||
placeholderText='Env Variable' )
|
||||
|
||||
self.custom_env_val = maya.text_field(
|
||||
placeholderText='Value' )
|
||||
|
||||
with utils.ClickMenu(
|
||||
self.insert_path, parent=self.custom_env_val,
|
||||
button=3) as menu:
|
||||
|
||||
menu.add_item('<storage>')
|
||||
menu.add_item('<maya_root>')
|
||||
menu.add_item('<user_scripts>')
|
||||
menu.add_item('<user_modules>')
|
||||
menu.add_item('<temp_dir>')
|
||||
|
||||
maya.button(
|
||||
label="Add", command=self.add_row)
|
||||
|
||||
maya.button(
|
||||
label="Delete", command=self.delete_row)
|
||||
|
||||
with utils.FrameLayout(
|
||||
label="Plugins", collapsable=True,
|
||||
width=325, collapse=True):
|
||||
|
||||
with utils.ColumnLayout(
|
||||
2, col_width=((1,180),(2,140))) as plugin_layout:
|
||||
|
||||
self.plugin_layout = plugin_layout
|
||||
|
||||
with utils.Row(1, 1, 355, "center", (1,"bottom",0)) as btn:
|
||||
|
||||
self.refresh_button = utils.ProcButton(
|
||||
"Refresh", "Refreshing...", self.refresh)
|
||||
|
||||
maya.form_layout(self.page, edit=True,
|
||||
attachForm=[(scroll, 'top', 5), (scroll, 'left', 5),
|
||||
(scroll, 'right', 5), (btn, 'bottom', 5),
|
||||
(btn, 'left', 5), (btn, 'right', 5)],
|
||||
attachControl=(scroll, "bottom", 5, btn))
|
||||
|
||||
frame.add_tab(self)
|
||||
self.is_logged_out()
|
||||
|
||||
def delete_row(self, *args):
|
||||
"""
|
||||
Remove selected row from user environment variables table.
|
||||
"""
|
||||
selected_row = maya.table(self.env_vars, query=True, selectedRow=True)
|
||||
maya.table(self.env_vars, edit=True, deleteRow=selected_row)
|
||||
|
||||
def add_row(self, *args):
|
||||
"""
|
||||
Add new user environment variable from contents of custom_env text
|
||||
fields.
|
||||
"""
|
||||
env_var = maya.text_field(self.custom_env_var, query=True, text=True)
|
||||
env_val = maya.text_field(self.custom_env_val, query=True, text=True)
|
||||
if env_var and env_val:
|
||||
maya.table(self.env_vars, edit=True, insertRow=1)
|
||||
|
||||
def populate_row(self, row, column):
|
||||
"""
|
||||
Add data to table cell. Command called by table getCell automatically
|
||||
when new row is added.
|
||||
|
||||
:Args:
|
||||
- row (int): Selected row index.
|
||||
- column (int): Selected column index.
|
||||
"""
|
||||
if column == 1:
|
||||
env_var = maya.text_field(
|
||||
self.custom_env_var, query=True, text=True)
|
||||
|
||||
maya.text_field(self.custom_env_var, edit=True, text="")
|
||||
return env_var
|
||||
|
||||
if column == 2:
|
||||
env_val = maya.text_field(
|
||||
self.custom_env_val, query=True, text=True)
|
||||
|
||||
maya.text_field(self.custom_env_val, edit=True, text="")
|
||||
return env_val
|
||||
|
||||
def set_version(self, version):
|
||||
"""
|
||||
Set Maya version to run on server. Command for version dropdown
|
||||
selection.
|
||||
|
||||
:Args:
|
||||
- version (str): The selected version string, e.g. ``Maya Beta``.
|
||||
"""
|
||||
self.base.set_version(version)
|
||||
|
||||
def insert_path(self, path):
|
||||
"""
|
||||
Insert a path place holder into an environment variable value.
|
||||
Command for text field right-click menu selection.
|
||||
|
||||
:Args:
|
||||
- path (string): One of the place holder values from the menu,
|
||||
e.g. ``<storage>, <maya_root>``.
|
||||
"""
|
||||
maya.text_field(self.custom_env_val, edit=True, insertText=path)
|
||||
|
||||
def get_env_vars(self):
|
||||
"""
|
||||
Retrieve all user environment variables.
|
||||
|
||||
:Returns:
|
||||
- Environment variables as a dictionary.
|
||||
"""
|
||||
vars = {}
|
||||
rows = maya.table(self.env_vars, query=True, rows=True)
|
||||
for row in range(1, rows):
|
||||
|
||||
row_key = maya.table(
|
||||
self.env_vars, cellIndex=(row, 1), query=True, cellValue=True)
|
||||
|
||||
row_val = maya.table(
|
||||
self.env_vars, cellIndex=(row, 2), query=True, cellValue=True)
|
||||
|
||||
vars[str(row_key[0])] = str(row_val[0])
|
||||
|
||||
return vars
|
||||
|
||||
def user_license_server(self, enabled):
|
||||
"""
|
||||
Enable use of custom Maya license server. Command for use_license
|
||||
check box.
|
||||
|
||||
:Args:
|
||||
- enabled (bool): Whether to use custom license server.
|
||||
"""
|
||||
maya.text_field(self.custom_license_endp, edit=True, enable=enabled)
|
||||
maya.text_field(self.custom_license_port, edit=True, enable=enabled)
|
||||
|
||||
def get_license_server(self):
|
||||
license = {"LicenseServer":"", "LicensePort":""}
|
||||
enabled = maya.check_box(self.use_license, query=True, value=True)
|
||||
|
||||
if enabled:
|
||||
license["LicenseServer"] = str(maya.text_field(
|
||||
self.custom_license_endp, query=True, text=True))
|
||||
|
||||
license["LicensePort"] = str(maya.text_field(
|
||||
self.custom_license_port, query=True, text=True))
|
||||
|
||||
return license
|
||||
|
||||
def refresh(self, *args):
|
||||
"""
|
||||
Clear any data and customization. Command for refresh_button.
|
||||
"""
|
||||
maya.table(self.env_vars, edit=True, clearTable=True, rows=0)
|
||||
self.base.refresh()
|
||||
maya.refresh()
|
||||
|
||||
def is_logged_in(self):
|
||||
"""
|
||||
Called when the plug-in is authenticated. Enables UI.
|
||||
"""
|
||||
maya.form_layout(self.page, edit=True, enable=True)
|
||||
|
||||
def is_logged_out(self):
|
||||
"""
|
||||
Called when the plug-in is logged out. Disables UI and resets
|
||||
whether that tab has been loaded for the first time.
|
||||
"""
|
||||
maya.form_layout(self.page, edit=True, enable=False)
|
||||
self.ready = False
|
||||
|
||||
def prepare(self):
|
||||
"""
|
||||
Called when the tab is loaded (clicked into) for the first time.
|
||||
Once loaded, remains so for the rest of the plug-in session unless
|
||||
logged out or manually refreshed.
|
||||
|
||||
If loading the UI fails, the tab returns to a logged-out state.
|
||||
"""
|
||||
if not self.ready:
|
||||
maya.refresh()
|
||||
try:
|
||||
self.is_logged_in()
|
||||
self.ready = True
|
||||
|
||||
except Exception as exp:
|
||||
maya.error("Error starting Environment UI: {0}".format(exp))
|
||||
self.is_logged_out()
|
||||
|
||||
maya.refresh()
|
|
@ -0,0 +1,538 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import webbrowser
|
||||
|
||||
from api import MayaAPI as maya
|
||||
import utils
|
||||
|
||||
|
||||
class HistoryUI(object):
|
||||
"""Class to create the 'Jobs' tab in the plug-in UI"""
|
||||
|
||||
def __init__(self, base, frame):
|
||||
"""Create 'Jobs' tab and add to UI frame.
|
||||
|
||||
:param base: The base class for handling jobs monitoring functionality.
|
||||
:type base: :class:`.AzureBatchHistory`
|
||||
:param frame: The shared plug-in UI frame.
|
||||
:type frame: :class:`.AzureBatchUI`
|
||||
"""
|
||||
self.base = base
|
||||
self.label = " Jobs "
|
||||
self.ready = False
|
||||
self.jobs_displayed = []
|
||||
self.page = maya.form_layout(enableBackground=True)
|
||||
|
||||
with utils.Row(1, 1, 360, "center") as lbl:
|
||||
self.total = maya.text(label="", font="boldLabelFont")
|
||||
self.paging_display()
|
||||
with utils.ScrollLayout(
|
||||
v_scrollbar=3, h_scrollbar=0, height=478) as scroll:
|
||||
with utils.RowLayout(row_spacing=20) as sublayout:
|
||||
if not self.jobs_displayed:
|
||||
self.empty_jobs = maya.text(
|
||||
label="Loading job data...",
|
||||
align='left',
|
||||
font="boldLabelFont")
|
||||
self.jobs_layout = sublayout
|
||||
|
||||
with utils.Row(1, 1, 355, "center", (1,"bottom",0)) as btn:
|
||||
self.refresh_button = utils.ProcButton(
|
||||
"Refresh", "Refreshing...", self.refresh)
|
||||
maya.form_layout(
|
||||
self.page, edit=True,
|
||||
attachForm=[(lbl, 'top', 0), (lbl, 'left', 5),
|
||||
(lbl, 'right', 5),
|
||||
(self.first_btn, 'left', 5),
|
||||
(self.last_btn, 'right', 5),
|
||||
(scroll, 'left', 5), (scroll, 'right', 5),
|
||||
(btn, 'bottom', 5), (btn, 'left', 0),
|
||||
(btn, 'right', 0)],
|
||||
attachControl=[(self.first_btn,"top",5, lbl),
|
||||
(self.prev_btn,"top",5, lbl),
|
||||
(self.last_btn,"top",5, lbl),
|
||||
(self.next_btn,"top",5, lbl),
|
||||
(scroll,"top",5, self.first_btn),
|
||||
(scroll,"top",5, self.next_btn),
|
||||
(scroll,"top",5, self.prev_btn),
|
||||
(scroll,"top",5, self.last_btn),
|
||||
(scroll, "bottom", 5, btn)],
|
||||
attachPosition=[(lbl, 'top', 5, 0),
|
||||
(self.first_btn, 'right', 5, 25),
|
||||
(self.prev_btn, 'left', 5, 25),
|
||||
(self.prev_btn, 'right', 5, 50),
|
||||
(self.next_btn, 'right', 5, 75),
|
||||
(self.next_btn, 'left', 5, 50),
|
||||
(self.last_btn, 'left', 5, 75),])
|
||||
frame.add_tab(self)
|
||||
self.is_logged_out()
|
||||
|
||||
@property
|
||||
def num_jobs(self):
|
||||
"""Total number of jobs. Retrieves contents of label."""
|
||||
return maya.text(self.total, query=True, label=True)
|
||||
|
||||
@num_jobs.setter
|
||||
def num_jobs(self, value):
|
||||
"""Total number of jobs. Sets contents of label."""
|
||||
maya.text(self.total, edit=True, label=value)
|
||||
|
||||
@property
|
||||
def last_page(self):
|
||||
"""Whether the tab is displaying the last page of jobs.
|
||||
Retrieves status of paging buttons.
|
||||
"""
|
||||
return maya.button(self.next_btn, query=True, enable=True)
|
||||
|
||||
@last_page.setter
|
||||
def last_page(self, value):
|
||||
"""Whether the tab is displaying the last page of jobs.
|
||||
Sets status of paging buttons.
|
||||
"""
|
||||
maya.button(self.next_btn, edit=True, enable=value)
|
||||
maya.button(self.last_btn, edit=True, enable=value)
|
||||
|
||||
@property
|
||||
def first_page(self):
|
||||
"""Whether the tab is displaying the first page of jobs.
|
||||
Retrieves status of paging buttons.
|
||||
"""
|
||||
return maya.button(self.prev_btn, query=True, enable=True)
|
||||
|
||||
@first_page.setter
|
||||
def first_page(self, value):
|
||||
"""Whether the tab is displaying the first page of jobs.
|
||||
Sets status of paging buttons.
|
||||
"""
|
||||
maya.button(self.first_btn, edit=True, enable=value)
|
||||
maya.button(self.prev_btn, edit=True, enable=value)
|
||||
|
||||
def is_logged_in(self):
|
||||
"""Called when the plug-in is authenticated. Enables UI."""
|
||||
maya.form_layout(self.page, edit=True, enable=True)
|
||||
|
||||
def is_logged_out(self):
|
||||
"""Called when the plug-in is logged out. Disables UI and resets
|
||||
whether that tab has been loaded for the first time.
|
||||
"""
|
||||
maya.form_layout(self.page, edit=True, enable=False)
|
||||
self.ready = False
|
||||
|
||||
def prepare(self):
|
||||
"""Called when the tab is loaded (clicked into) for the first time.
|
||||
Initiates the downloading of job details based on the selected page.
|
||||
Once loaded, remains so for the rest of the plug-in session unless
|
||||
logged out or manually refreshed.
|
||||
|
||||
If loading the UI fails, the tab returns to a logged-out state.
|
||||
"""
|
||||
if not self.ready:
|
||||
maya.refresh()
|
||||
try:
|
||||
self.refresh()
|
||||
self.is_logged_in()
|
||||
self.ready = True
|
||||
|
||||
except Exception as exp:
|
||||
maya.error("Error starting Assets UI: {0}".format(exp))
|
||||
self.is_logged_out()
|
||||
maya.refresh()
|
||||
|
||||
def paging_display(self):
|
||||
"""Display the buttons for controlling the job paging."""
|
||||
self.first_btn = maya.button(
|
||||
label="<<", align="center", command=self.show_first_jobs)
|
||||
self.prev_btn = maya.button(
|
||||
label="<", align="center", command=self.show_prev_jobs)
|
||||
self.next_btn = maya.button(
|
||||
label=">", align="center", command=self.show_next_jobs)
|
||||
self.last_btn = maya.button(
|
||||
label=">>", align="center", command=self.show_last_jobs)
|
||||
|
||||
def reload(self, *args):
|
||||
"""Refresh Jobs tab. Command for refresh_button.
|
||||
Remove all existing UI elements and job details and re-build
|
||||
from scratch. This is also called to populate
|
||||
the tab for the first time.
|
||||
"""
|
||||
self.refresh_button.start()
|
||||
maya.delete_ui(self.empty_jobs)
|
||||
self.base.job_selected(None)
|
||||
for i in self.jobs_displayed:
|
||||
i.remove()
|
||||
self.jobs_displayed = self.base.show_jobs()
|
||||
if not self.jobs_displayed:
|
||||
self.empty_jobs = maya.text(
|
||||
label="No jobs to display", parent=self.jobs_layout)
|
||||
self.refresh_button.finish()
|
||||
|
||||
def refresh(self, *args):
|
||||
"""Refresh Jobs tab. Command for refresh_button.
|
||||
Remove all existing UI elements and job details and re-build
|
||||
from scratch. This is also called to populate
|
||||
the tab for the first time.
|
||||
"""
|
||||
self.refresh_button.start()
|
||||
maya.delete_ui(self.empty_jobs)
|
||||
|
||||
self.base.job_selected(None)
|
||||
for i in self.jobs_displayed:
|
||||
i.remove()
|
||||
self.jobs_displayed = self.base.get_history()
|
||||
if not self.jobs_displayed:
|
||||
self.empty_jobs = maya.text(
|
||||
label="No jobs to display", parent=self.jobs_layout)
|
||||
self.refresh_button.finish()
|
||||
|
||||
def disable(self, enabled):
|
||||
"""Disable the tab from user interaction. Used during long running
|
||||
processes like refreshing the job data, and when plug-in is
|
||||
unauthenticated.
|
||||
|
||||
:param bool enabled: Whether to enable the display. False will
|
||||
disable the display.
|
||||
"""
|
||||
maya.form_layout(self.page, edit=True, enable=enabled)
|
||||
|
||||
def create_job_entry(self, name, index):
|
||||
"""Create new dropdown frame to represent a job entry.
|
||||
:returns: A :class:`.AzureBatchJobInfo` object.
|
||||
"""
|
||||
frame = maya.frame_layout(label=name,
|
||||
collapsable=True,
|
||||
collapse=True,
|
||||
visible=True,
|
||||
parent=self.jobs_layout)
|
||||
return AzureBatchJobInfo(self.base, index, frame)
|
||||
|
||||
def show_next_jobs(self, *args):
|
||||
"""Display next set of jobs. Command for next_btn."""
|
||||
self.base.show_next_jobs()
|
||||
self.reload()
|
||||
|
||||
def show_prev_jobs(self, *args):
|
||||
"""Display previous set of jobs. Command for prev_btn."""
|
||||
self.base.show_prev_jobs()
|
||||
self.reload()
|
||||
|
||||
def show_first_jobs(self, *args):
|
||||
"""Display first jobs. Command for first_btn."""
|
||||
self.base.show_first_jobs()
|
||||
self.reload()
|
||||
|
||||
def show_last_jobs(self, *args):
|
||||
"""Display last jobs. Command for last_btn."""
|
||||
self.base.show_last_jobs()
|
||||
self.reload()
|
||||
|
||||
|
||||
class AzureBatchJobInfo(object):
|
||||
"""Class to represent a single job reference."""
|
||||
|
||||
def __init__(self, base, index, layout):
|
||||
"""Create a new job reference.
|
||||
|
||||
:param base: The base class for handling jobs monitoring functionality.
|
||||
:type base: :class:`.AzureBatchHistory`
|
||||
:param int index: The index of where this reference is displayed on
|
||||
the current page.
|
||||
:param layout: The layout on which the job details will be displayed.
|
||||
:type layout: :class:`.utils.FrameLayout`
|
||||
"""
|
||||
self.base = base
|
||||
self.index = index
|
||||
self.layout = layout
|
||||
self.data_path = self.base.get_data_dir()
|
||||
self.selected_dir = utils.get_default_output_path()
|
||||
maya.frame_layout(
|
||||
layout,
|
||||
edit=True,
|
||||
collapseCommand=self.on_collapse,
|
||||
expandCommand=self.on_expand)
|
||||
self.job_details = maya.form_layout(parent=self.layout)
|
||||
maya.parent()
|
||||
self.content = []
|
||||
self.url = ""
|
||||
|
||||
def set_label(self, value):
|
||||
"""Set the label for the job frame layout.
|
||||
:param str value: The string to display as label.
|
||||
"""
|
||||
maya.frame_layout(self.layout, edit=True, label=value)
|
||||
|
||||
def set_status(self, value):
|
||||
"""Set the buttons availability depending on the job status.
|
||||
:param str value: The status of the job.
|
||||
"""
|
||||
maya.text(self._status, edit=True, label=" {0}".format(value))
|
||||
maya.icon_button(self.refresh_button, edit=True, enable=True)
|
||||
maya.icon_button(self.view_button, edit=True, enable=True)
|
||||
maya.icon_button(self.watch_button, edit=True, enable=True)
|
||||
maya.icon_button(self.cancel_button, edit=True, enable=(
|
||||
value in ["active", "enabling"]))
|
||||
maya.icon_button(self.delete_button, edit=True, enable=(
|
||||
value=='completed'))
|
||||
|
||||
def get_status(self):
|
||||
"""Get the status of the job."""
|
||||
return maya.text(self._status, query=True, label=True).lstrip()
|
||||
|
||||
def set_progress(self, value):
|
||||
"""Set the label for progress complete.
|
||||
:param int value: The percent complete.
|
||||
"""
|
||||
maya.text(self._progress, edit=True, label=" {0}%".format(value))
|
||||
|
||||
def set_submission(self, value):
|
||||
"""Set the label for date/time submitted.
|
||||
:param str value: The datetime string to format.
|
||||
"""
|
||||
datetime = value.split('T')
|
||||
datetime[1] = datetime[1].split('.')[0]
|
||||
label = ' '.join(datetime)
|
||||
maya.text(self._submission, edit=True, label=" {0}".format(label))
|
||||
|
||||
def set_tasks(self, value):
|
||||
"""Set the label for number of tasks in the job.
|
||||
:param int value: The number of tasks.
|
||||
"""
|
||||
maya.text(self._tasks, edit=True, label=" {0}".format(value))
|
||||
|
||||
def set_job(self, value):
|
||||
"""Set the label for the job ID, and format the portal URL reference
|
||||
for the job with this ID.
|
||||
:param str value: The job ID.
|
||||
"""
|
||||
maya.text_field(self._job, edit=True, text=value)
|
||||
self.url = "https://ms.portal.azure.com"
|
||||
|
||||
def set_pool(self, value):
|
||||
"""Set the label for the pool ID that this job ran on.
|
||||
:param str value: The pool ID.
|
||||
"""
|
||||
maya.text_field(self._pool, edit=True, text=value)
|
||||
|
||||
def open_portal(self, *args):
|
||||
"""Open the portal in a web browser to the details page
|
||||
for this job. Command for view_button
|
||||
"""
|
||||
webbrowser.open(self.url, 2, True)
|
||||
|
||||
def on_expand(self):
|
||||
"""Command for the expanding of the job reference frame layout.
|
||||
Loads latest details for the specified job and populates UI.
|
||||
"""
|
||||
with utils.Row(2, 2, (100,220), parent=self.job_details) as row:
|
||||
self.content.append(row)
|
||||
self.content.append(maya.text(label=""))
|
||||
self._thumbnail = maya.image()
|
||||
self.content.append(self._thumbnail)
|
||||
self._status, st_row = self.display_info("Status: ")
|
||||
self._progress, pr_row = self.display_info("Progress: ")
|
||||
self._submission, sb_row = self.display_info("Submission time: ")
|
||||
self._tasks, tk_row = self.display_info("Task Count: ")
|
||||
self._job, jb_row = self.display_data("Job ID: ")
|
||||
self._pool, pl_row = self.display_data("Pool: ")
|
||||
self._dir, dr_row = self.display_watcher()
|
||||
self.refresh_button = self.display_button(
|
||||
"btn_refresh.png", self.refresh, "Refresh")
|
||||
self.view_button = self.display_button(
|
||||
"btn_portal.png", self.open_portal,
|
||||
"View this job in the portal")
|
||||
self.watch_button = self.display_button(
|
||||
"btn_background.png", self.start_watcher,
|
||||
"Watch this job in the background")
|
||||
self.cancel_button = self.display_button(
|
||||
"btn_cancel.png", self.cancel_job, "Cancel this job")
|
||||
self.delete_button = self.display_button(
|
||||
"btn_delete.png",self.delete_job, "Delete job")
|
||||
maya.form_layout(
|
||||
self.job_details, edit=True,
|
||||
attachForm=[(row,"top",5), (row,"left",5), (row,"right",5),
|
||||
(st_row,"left",5), (st_row,"right",5),
|
||||
(pr_row,"left",5), (pr_row,"right",5),
|
||||
(sb_row,"left",5), (sb_row,"right",5),
|
||||
(tk_row,"left",5), (tk_row,"right",5),
|
||||
(jb_row,"left",5), (jb_row,"right",5),
|
||||
(pl_row,"left",5), (pl_row,"right",5),
|
||||
(dr_row,"left",5), (dr_row,"right",5),
|
||||
(self.refresh_button,"left",5),
|
||||
(self.delete_button,"right",5),
|
||||
(self.refresh_button,"bottom",5),
|
||||
(self.view_button,"bottom",5),
|
||||
(self.watch_button,"bottom",5),
|
||||
(self.cancel_button,"bottom",5),
|
||||
(self.delete_button,"bottom",5)],
|
||||
attachControl=[(st_row,"top",5, row),
|
||||
(pr_row,"top",5, st_row),
|
||||
(sb_row,"top",5, pr_row),
|
||||
(tk_row,"top",5, sb_row),
|
||||
(jb_row,"top",5, tk_row),
|
||||
(pl_row,"top",5, jb_row),
|
||||
(dr_row,"top",5, pl_row),
|
||||
(self.refresh_button,"top",5, dr_row),
|
||||
(self.view_button,"top",5, dr_row),
|
||||
(self.watch_button,"top",5, dr_row),
|
||||
(self.cancel_button,"top",5, dr_row),
|
||||
(self.delete_button, "top", 5, dr_row)],
|
||||
attachPosition=[(self.refresh_button, 'right', 5, 20),
|
||||
(self.view_button, 'left', 5, 20),
|
||||
(self.view_button, 'right', 5, 40),
|
||||
(self.watch_button, 'right', 5, 60),
|
||||
(self.watch_button, 'left', 5, 40),
|
||||
(self.cancel_button, 'left', 5, 60),
|
||||
(self.cancel_button, 'right', 5, 80),
|
||||
(self.delete_button, 'left', 5, 80)])
|
||||
self.base.job_selected(self)
|
||||
maya.execute(self.base.get_thumbnail)
|
||||
maya.refresh()
|
||||
|
||||
def on_collapse(self):
|
||||
"""Command for the collapsing of the job reference frame layout.
|
||||
Deletes all UI elements and resets currently selected job.
|
||||
This is called automatically when the user collapses the UI layout,
|
||||
or programmatically from the :func:`collapse` function.
|
||||
"""
|
||||
self.base.job_selected(None)
|
||||
maya.parent(self.job_details)
|
||||
for element in self.content:
|
||||
maya.delete_ui(element, control=True)
|
||||
self.content = []
|
||||
|
||||
def collapse(self):
|
||||
"""Collapse the job frame. Initiates the on_collapse sequence."""
|
||||
maya.frame_layout(self.layout, edit=True, collapse=True)
|
||||
self.on_collapse()
|
||||
maya.refresh()
|
||||
|
||||
def remove(self):
|
||||
"""Delete the job reference frame layout."""
|
||||
maya.delete_ui(self.layout, control=True)
|
||||
|
||||
def refresh(self):
|
||||
"""Refresh the details of the specified job, and update the UI."""
|
||||
self.base.update_job(self.index)
|
||||
maya.execute(self.base.get_thumbnail)
|
||||
self.selected_dir = utils.get_default_output_path()
|
||||
maya.text_field(self._dir, edit=True, text=self.selected_dir)
|
||||
maya.refresh()
|
||||
|
||||
def start_watcher(self, *args):
|
||||
"""Start the background job watcher. Command for watch_button."""
|
||||
utils.JobWatcher(
|
||||
self.base.selected_job_id(), self.data_path, self.selected_dir)
|
||||
|
||||
def select_dir(self, *args):
|
||||
"""Selected directory for job watcher to download outputs to.
|
||||
Command for directory select symbol button.
|
||||
"""
|
||||
cap = "Select target directory for job downloads."
|
||||
okCap = "Select Folder"
|
||||
new_dir = maya.file_select(fileMode=3, okCaption=okCap, caption=cap)
|
||||
if new_dir:
|
||||
self.selected_dir = new_dir[0]
|
||||
maya.text_field(self._dir, edit=True, text=self.selected_dir)
|
||||
|
||||
|
||||
def display_info(self, label):
|
||||
"""Display text data as a label with a heading.
|
||||
:param str label: The text for the data heading.
|
||||
"""
|
||||
with utils.Row(2, 2, (100,220), ("right","left"),
|
||||
parent=self.job_details) as row:
|
||||
self.content.append(row)
|
||||
self.content.append(maya.text(label=label, align="right"))
|
||||
input = maya.text(align="left", label="")
|
||||
self.content.append(input)
|
||||
return input, row
|
||||
|
||||
def display_data(self, label):
|
||||
"""Display text data as a non-editable text field with heading.
|
||||
:param str label: The text for the data heading.
|
||||
"""
|
||||
with utils.Row(2, 2, (100,220), ("right","left"),
|
||||
parent=self.job_details) as row:
|
||||
self.content.append(row)
|
||||
self.content.append(maya.text(label=label, align="right"))
|
||||
input = maya.text_field(text="", editable=False)
|
||||
self.content.append(input)
|
||||
return input, row
|
||||
|
||||
def display_button(self, icon, cmd, note):
|
||||
"""Display a job function button.
|
||||
|
||||
:param str icon: The name of an icon to display on the button.
|
||||
:param func cmd: The command to execute when the button is clicked.
|
||||
:param str note: The tool tip text for the button.
|
||||
:returns: The button object.
|
||||
"""
|
||||
img = os.path.join(os.environ["AZUREBATCH_ICONS"], icon)
|
||||
btn = maya.icon_button(style='iconOnly', image=img, flat=0,
|
||||
height=30,
|
||||
command=cmd,
|
||||
parent=self.job_details,
|
||||
enable=False, annotation=note)
|
||||
self.content.append(btn)
|
||||
return btn
|
||||
|
||||
def display_watcher(self):
|
||||
"""Display output download directory selection elements for
|
||||
job watcher.
|
||||
:returns: Text field object for directory path.
|
||||
"""
|
||||
with utils.Row(3, 2, (100,190,40), ("right","center","center"),
|
||||
parent=self.job_details) as row:
|
||||
self.content.append(row)
|
||||
self.content.append(maya.text(label="Outputs: ", align="right"))
|
||||
input = maya.text_field(text=self.selected_dir, editable=False)
|
||||
self.content.append(maya.symbol_button(
|
||||
image="SP_DirOpenIcon.png",
|
||||
command=self.select_dir,
|
||||
annotation="Select Download Directory"))
|
||||
return input, row
|
||||
|
||||
def set_thumbnail(self, thumb, height):
|
||||
"""Set thumbnail image.
|
||||
|
||||
:param str thumb: The path to the thumbnail image.
|
||||
:param int height: The height of the image in pixels.
|
||||
"""
|
||||
maya.image(self._thumbnail,
|
||||
edit=True,
|
||||
image=thumb,
|
||||
height=height)
|
||||
|
||||
def cancel_job(self, *args):
|
||||
"""Cancel the specified job. Command for cancel_button."""
|
||||
self.base.cancel_job()
|
||||
|
||||
def delete_job(self, *args):
|
||||
"""Cancel the specified job. Command for cancel_button."""
|
||||
self.base.delete_job()
|
|
@ -0,0 +1,290 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
|
||||
from api import MayaAPI as maya
|
||||
|
||||
import utils
|
||||
|
||||
class PoolsUI(object):
|
||||
"""Class to create the 'Pools' tab in the plug-in UI"""
|
||||
|
||||
def __init__(self, base, frame):
|
||||
"""Create 'Pools' tab and add to UI frame.
|
||||
|
||||
:param base: The base class for handling pools monitoring functionality.
|
||||
:type base: :class:`.AzureBatchPools`
|
||||
:param frame: The shared plug-in UI frame.
|
||||
:type frame: :class:`.AzureBatchUI`
|
||||
"""
|
||||
self.base = base
|
||||
self.label = "Pools "
|
||||
self.ready = False
|
||||
self.pools_displayed = []
|
||||
self.page = maya.form_layout(enableBackground=True)
|
||||
with utils.ScrollLayout(
|
||||
v_scrollbar=3, h_scrollbar=0, height=520) as scroll:
|
||||
with utils.RowLayout(row_spacing=20) as sublayout:
|
||||
if not self.pools_displayed:
|
||||
self.empty_pools = maya.text(
|
||||
label="Loading pool data...",
|
||||
font="boldLabelFont",
|
||||
parent=sublayout)
|
||||
self.pools_layout = sublayout
|
||||
with utils.Row(1, 1, 355, "center", (1,"bottom",0)) as btn:
|
||||
self.refresh_button = utils.ProcButton(
|
||||
"Refresh", "Refreshing...", self.refresh)
|
||||
maya.form_layout(self.page, edit=True,
|
||||
attachForm=[(scroll, 'top', 5),
|
||||
(scroll, 'left', 5), (scroll, 'right', 5),
|
||||
(btn, 'bottom', 5),
|
||||
(btn, 'left', 0), (btn, 'right', 0)],
|
||||
attachControl=(scroll, "bottom", 5, btn))
|
||||
frame.add_tab(self)
|
||||
self.is_logged_out()
|
||||
|
||||
def is_logged_in(self):
|
||||
"""Called when the plug-in is authenticated. Enables UI."""
|
||||
maya.form_layout(self.page, edit=True, enable=True)
|
||||
|
||||
def is_logged_out(self):
|
||||
"""Called when the plug-in is logged out. Disables UI and resets
|
||||
whether that tab has been loaded for the first time.
|
||||
"""
|
||||
maya.form_layout(self.page, edit=True, enable=False)
|
||||
self.ready = False
|
||||
|
||||
def prepare(self):
|
||||
"""Called when the tab is loaded (clicked into) for the first time.
|
||||
Initiates the downloading of pools details.
|
||||
Once loaded, remains so for the rest of the plug-in session unless
|
||||
logged out or manually refreshed.
|
||||
|
||||
If loading the UI fails, the tab returns to a logged-out state.
|
||||
"""
|
||||
if not self.ready:
|
||||
maya.refresh()
|
||||
try:
|
||||
self.refresh()
|
||||
self.is_logged_in()
|
||||
self.ready = True
|
||||
except Exception as exp:
|
||||
maya.error("Error starting Pools UI: {0}".format(exp))
|
||||
self.is_logged_out()
|
||||
maya.refresh()
|
||||
|
||||
def refresh(self, *args):
|
||||
"""Refresh Pools tab. Command for refresh_button.
|
||||
Remove all existing UI elements and pool details and re-build
|
||||
from scratch. This is also called to populate
|
||||
the tab for the first time.
|
||||
"""
|
||||
self.refresh_button.start()
|
||||
maya.delete_ui(self.empty_pools)
|
||||
self.base.pool_selected(None)
|
||||
for i in self.pools_displayed:
|
||||
i.remove()
|
||||
self.pools_displayed = self.base.get_pools()
|
||||
if not self.pools_displayed:
|
||||
self.empty_pools = maya.text(label="No pools to display",
|
||||
parent=self.pools_layout)
|
||||
self.refresh_button.finish()
|
||||
|
||||
def create_pool_entry(self, name, index):
|
||||
"""Create new dropdown frame to represent a pool entry.
|
||||
:returns: A :class:`.AzureBatchPoolInfo` object.
|
||||
"""
|
||||
frame = maya.frame_layout(label=name,
|
||||
collapsable=True,
|
||||
collapse=True,
|
||||
width=345,
|
||||
visible=True,
|
||||
parent=self.pools_layout)
|
||||
return AzureBatchPoolInfo(self.base, index, frame)
|
||||
|
||||
|
||||
class AzureBatchPoolInfo(object):
|
||||
"""Class to represent a single pool reference."""
|
||||
|
||||
def __init__(self, base, index, layout):
|
||||
"""Create a new pool reference.
|
||||
|
||||
:param base: The base class for handling pools monitoring functionality.
|
||||
:type base: :class:`.AzureBatchPools`
|
||||
:param int index: The index of where this reference is displayed on
|
||||
the current page.
|
||||
:param layout: The layout on which the pool details will be displayed.
|
||||
:type layout: :class:`.utils.FrameLayout`
|
||||
"""
|
||||
self.base = base
|
||||
self.index = index
|
||||
self.layout = layout
|
||||
maya.frame_layout(
|
||||
layout,
|
||||
edit=True,
|
||||
collapseCommand=self.on_collapse,
|
||||
expandCommand=self.on_expand)
|
||||
self.listbox = maya.col_layout(
|
||||
numberOfColumns=2,
|
||||
columnWidth=((1, 100), (2, 200)),
|
||||
rowSpacing=((1, 5),),
|
||||
rowOffset=((1, "top", 5),(1, "bottom", 5),),
|
||||
parent=self.layout)
|
||||
self.content = []
|
||||
|
||||
def set_label(self, value):
|
||||
"""Set the label for the pool frame layout.
|
||||
:param str value: The string to display as label.
|
||||
"""
|
||||
maya.frame_layout(self.layout, edit=True, label=value)
|
||||
|
||||
def set_type(self, value):
|
||||
"""Set the type of the pool - auto or provisioned.
|
||||
:param str value: The pool type.
|
||||
"""
|
||||
maya.text(self._type, edit=True, label=" {0}".format(value))
|
||||
|
||||
def set_size(self, value):
|
||||
"""Set the number of instances in the pool.
|
||||
:param int value: Size of the pool.
|
||||
"""
|
||||
maya.text(self._size, edit=True, label=" {0}".format(value))
|
||||
|
||||
def set_target(self, value):
|
||||
"""Set the target number of instances in the pool.
|
||||
:param int value: The target size of the pool.
|
||||
"""
|
||||
maya.text(self._target, edit=True, label=" {0}".format(value))
|
||||
|
||||
def set_created(self, value):
|
||||
"""Set the date/time the pool was created.
|
||||
:param str value: The datetime string of the pool creation.
|
||||
"""
|
||||
#datetime = value.split('T')
|
||||
#datetime[1] = datetime[1].split('.')[0]
|
||||
label = str(value)#' '.join(datetime)
|
||||
maya.text(self._created, edit=True, label=" {0}".format(label))
|
||||
|
||||
def set_state(self, value):
|
||||
"""Set the state of the pool.
|
||||
:param str value: The pool state.
|
||||
"""
|
||||
maya.text(self._state, edit=True, label=" {0}".format(value))
|
||||
|
||||
def set_tasks(self, value):
|
||||
"""Set the tasks per TVM allowed in the pool.
|
||||
:param int value: Tasks per TVM.
|
||||
"""
|
||||
maya.text(self._tasks, edit=True, label=" {0}".format(value))
|
||||
|
||||
def set_allocation(self, value):
|
||||
"""Set the allocation state of the pool.
|
||||
:param str value: The pool allocation state.
|
||||
"""
|
||||
maya.text(self._allocation, edit=True, label=" {0}".format(value))
|
||||
|
||||
def on_expand(self):
|
||||
"""Command for the expanding of the pool reference frame layout.
|
||||
Loads latest details for the specified pool and populates UI.
|
||||
"""
|
||||
self._type = self.display_info("Type: ")
|
||||
self._size = self.display_info("Current Size: ")
|
||||
self._target = self.display_info("Target Size: ")
|
||||
self._created = self.display_info("Created: ")
|
||||
self._state = self.display_info("State: ")
|
||||
self._tasks = self.display_info("Tasks per VM: ")
|
||||
self._allocation = self.display_info("Allocation State: ")
|
||||
self.base.pool_selected(self)
|
||||
auto = self.base.is_auto_pool()
|
||||
if not auto:
|
||||
self.resize_button = utils.ProcButton(
|
||||
"Resize Pool", "Resizing...", self.resize_pool,
|
||||
parent=self.listbox, align="center")
|
||||
self.resize_int = maya.int_slider(
|
||||
value=self.base.get_pool_size(),
|
||||
minValue=0,
|
||||
maxValue=1000,
|
||||
fieldMinValue=0,
|
||||
fieldMaxValue=100,
|
||||
field=True,
|
||||
width=230,
|
||||
parent=self.listbox,
|
||||
annotation="Number of instances to work in pool.")
|
||||
self.content.append(self.resize_button.display)
|
||||
self.content.append(self.resize_int)
|
||||
self.delete_button = utils.ProcButton("Delete Pool", "Deleting...",
|
||||
self.delete_pool, parent=self.layout, align="center")
|
||||
self.content.append(self.delete_button.display)
|
||||
maya.refresh()
|
||||
|
||||
def on_collapse(self):
|
||||
"""Command for the collapsing of the pool reference frame layout.
|
||||
Deletes all UI elements and resets currently selected pool.
|
||||
This is called automatically when the user collapses the UI layout,
|
||||
or programmatically from the :func:`collapse` function.
|
||||
"""
|
||||
self.base.pool_selected(None)
|
||||
maya.parent(self.listbox)
|
||||
for element in self.content:
|
||||
maya.delete_ui(element, control=True)
|
||||
self.content = []
|
||||
|
||||
def collapse(self):
|
||||
"""Collapse the pool frame. Initiates the on_collapse sequence."""
|
||||
maya.frame_layout(self.layout, edit=True, collapse=True)
|
||||
self.on_collapse()
|
||||
maya.refresh()
|
||||
|
||||
def remove(self):
|
||||
"""Delete the pool reference frame layout."""
|
||||
maya.delete_ui(self.layout, control=True)
|
||||
|
||||
def display_info(self, label):
|
||||
"""Display text data as a label with a heading.
|
||||
:param str label: The text for the data heading.
|
||||
"""
|
||||
self.content.append(
|
||||
maya.text(label=label, parent=self.listbox, align="right"))
|
||||
input = maya.text(align="left", label="", parent=self.listbox)
|
||||
self.content.append(input)
|
||||
return input
|
||||
|
||||
def delete_pool(self, *args):
|
||||
"""Delete the specified pool."""
|
||||
self.delete_button.start()
|
||||
self.base.delete_pool()
|
||||
self.delete_button.finish()
|
||||
|
||||
def resize_pool(self, *args):
|
||||
"""Resize the specified pool."""
|
||||
self.resize_button.start()
|
||||
resize = maya.int_slider(self.resize_int, query=True, value=True)
|
||||
self.base.resize_pool(resize)
|
||||
self.base.update_pool(self.index)
|
||||
self.resize_button.finish()
|
|
@ -1,6 +1,6 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
|
@ -26,19 +26,24 @@
|
|||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
|
||||
from api import MayaAPI as maya
|
||||
|
||||
|
||||
class BatchAppsUI():
|
||||
class AzureBatchUI(object):
|
||||
"""Class to create the plug-in UI frame into which all the tabs
|
||||
will be loaded.
|
||||
"""
|
||||
|
||||
def __init__(self, base):
|
||||
|
||||
"""Create and open plug-in UI."""
|
||||
self.base = base
|
||||
if (maya.window("BatchApps", q=1, exists=1)):
|
||||
maya.delete_ui("BatchApps")
|
||||
|
||||
self.ui = maya.window("BatchApps",
|
||||
title="BatchApps Maya Client",
|
||||
if (maya.window("AzureBatch", q=1, exists=1)):
|
||||
maya.delete_ui("AzureBatch")
|
||||
self.ui = maya.window("AzureBatch",
|
||||
title="Azure Batch Maya Client v{0}".format(
|
||||
os.environ["AZUREBATCH_VERSION"]),
|
||||
sizeable=True,
|
||||
height=450,
|
||||
resizeToFitChildren=True)
|
||||
|
@ -46,49 +51,78 @@ class BatchAppsUI():
|
|||
self.tab_display = maya.tab_layout(innerMarginWidth=5,
|
||||
innerMarginHeight=5,
|
||||
preventOverride=False,
|
||||
selectCommand=self.change_tab)
|
||||
selectCommand=self.change_tab,
|
||||
childResizable=True)
|
||||
maya.form_layout(self.form,
|
||||
edit=True,
|
||||
attachForm=((self.tab_display, 'top', 0),
|
||||
(self.tab_display, 'left', 0),
|
||||
(self.tab_display, 'bottom', 0),
|
||||
(self.tab_display, 'right', 0)))
|
||||
|
||||
|
||||
self.tabs = []
|
||||
|
||||
maya.window(self.ui,
|
||||
edit=True,
|
||||
width=365,
|
||||
width=375,
|
||||
height=575)
|
||||
maya.show(self.ui)
|
||||
maya.refresh()
|
||||
|
||||
|
||||
def is_logged_out(self):
|
||||
"""Called when plug-in is logged out. Iterates through all UI tabs
|
||||
and sets them to logged-out state, and resets whether they have
|
||||
been loaded.
|
||||
Sets config/authentication tab as the display tab.
|
||||
"""
|
||||
maya.tab_layout(self.tab_display, edit=True, selectTabIndex=1)
|
||||
|
||||
for page in self.tabs:
|
||||
page.ready = False
|
||||
page.is_logged_out()
|
||||
|
||||
def is_logged_in(self):
|
||||
"""Called when the plug-in is authenticated. Sets the Submit tab as
|
||||
the display tab.
|
||||
"""
|
||||
maya.tab_layout(self.tab_display, edit=True, selectTabIndex=2)
|
||||
self.tabs[0].is_logged_in()
|
||||
|
||||
def change_tab(self, *args):
|
||||
"""Called when a user clicks on a tab to display it.
|
||||
Initiates the loading of that tabs contents.
|
||||
"""
|
||||
if self.base.config.auth:
|
||||
selected = maya.tab_layout(self.tab_display, query=True, selectTabIndex=True)
|
||||
selected = maya.tab_layout(
|
||||
self.tab_display, query=True, selectTabIndex=True)
|
||||
selected_tab = self.tabs[selected-1]
|
||||
selected_tab.prepare()
|
||||
|
||||
def add_tab(self, content):
|
||||
maya.row_layout(content.page, edit=True, parent=self.tab_display)
|
||||
self.tabs.append(content)
|
||||
def select_tab(self, tab):
|
||||
"""Select a specific tab for display.
|
||||
:param int tab: The index of the tab to display.
|
||||
"""
|
||||
maya.tab_layout(self.tab_display, edit=True, selectTabIndex=tab)
|
||||
|
||||
def selected_tab(self):
|
||||
"""Get the index of the currently selected tab.
|
||||
:returns: The index (int) of the currently selected tab.
|
||||
"""
|
||||
return maya.tab_layout(
|
||||
self.tab_display, query=True, selectTabIndex=True)
|
||||
|
||||
def add_tab(self, content):
|
||||
"""Add a new tab to the display layout.
|
||||
Content must have a ``page`` attribute as it's primary layout.
|
||||
|
||||
:param content: This is called by any of the tab UI classes to
|
||||
add themselves to the plug-in UI.
|
||||
:type content: :class:`AzureBatchUI`
|
||||
|
||||
|
||||
"""
|
||||
maya.form_layout(content.page, edit=True, parent=self.tab_display)
|
||||
self.tabs.append(content)
|
||||
new_layout = []
|
||||
for t in self.tabs:
|
||||
new_layout.append((str(t.page), t.label))
|
||||
|
||||
new_layout = tuple(new_layout)
|
||||
maya.tab_layout(self.tab_display, edit=True, tabLabel=new_layout)
|
|
@ -0,0 +1,281 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import utils
|
||||
|
||||
from api import MayaAPI as maya
|
||||
|
||||
|
||||
class SubmissionUI(object):
|
||||
"""Class to create the 'Submit' tab in the plug-in UI"""
|
||||
|
||||
def __init__(self, base, frame):
|
||||
"""Create 'Submit' tab and add to UI frame.
|
||||
|
||||
:param base: The base class for handling jobs submission functionality.
|
||||
:type base: :class:`.AzureBatchSubmission`
|
||||
:param frame: The shared plug-in UI frame.
|
||||
:type frame: :class:`.AzureBatchUI`
|
||||
"""
|
||||
self.base = base
|
||||
self.label = "Submit"
|
||||
self.page = maya.form_layout(enableBackground=True)
|
||||
self.select_pool_type = 1
|
||||
self.select_instances = 1
|
||||
|
||||
with utils.ScrollLayout(height=475, parent=self.page) as scroll:
|
||||
box_label = "Pool Settings"
|
||||
with utils.FrameLayout(label=box_label, collapsable=True):
|
||||
self.pool_settings = maya.col_layout(
|
||||
numberOfColumns=2,
|
||||
columnWidth=((1, 100), (2, 200)),
|
||||
rowSpacing=(1, 10),
|
||||
rowOffset=((1, "top", 20), (2, "bottom", 20)))
|
||||
maya.text(label="Pools: ", align="right")
|
||||
maya.radio_group(
|
||||
labelArray3=("Auto provision a pool for this job",
|
||||
"Reuse an existing persistent pool",
|
||||
"Create a new persistent pool"),
|
||||
numberOfRadioButtons=3,
|
||||
select=self.select_pool_type,
|
||||
vertical=True,
|
||||
onCommand1=self.set_pool_auto,
|
||||
onCommand2=self.set_pool_reuse,
|
||||
onCommand3=self.set_pool_new)
|
||||
self.pool_text = maya.text(
|
||||
label="Instances: ", align="right")
|
||||
self.control = maya.int_slider(
|
||||
field=True, value=self.select_instances,
|
||||
minValue=1,
|
||||
maxValue=1000,
|
||||
fieldMinValue=1,
|
||||
fieldMaxValue=1000,
|
||||
changeCommand=self.set_pool_instances,
|
||||
annotation="Number of instances in pool")
|
||||
maya.parent()
|
||||
|
||||
box_label = "Render Settings"
|
||||
with utils.FrameLayout(label=box_label, collapsable=True) as box:
|
||||
self.render_module = box
|
||||
|
||||
with utils.Row(3, 2, (120,200,35)) as watch:
|
||||
self.job_watcher_ui()
|
||||
|
||||
with utils.Row(1, 1, 355, "center") as s_btn:
|
||||
self.submit_button = utils.ProcButton(
|
||||
"Submit Job", "Submitting...", self.submit)
|
||||
|
||||
with utils.Row(1, 1, 355, "center", (1,"bottom",0)) as r_btn:
|
||||
self.refresh_button = utils.ProcButton(
|
||||
"Refresh", "Refreshing...", self.refresh)
|
||||
|
||||
maya.form_layout(
|
||||
self.page, edit=True,
|
||||
attachForm=[(scroll, 'top', 5),
|
||||
(scroll, 'left', 5), (scroll, 'right', 5),
|
||||
(watch, 'left', 0), (watch, 'right', 0),
|
||||
(s_btn, 'left', 0), (s_btn, 'right', 0),
|
||||
(r_btn, 'bottom', 5),
|
||||
(r_btn, 'left', 0), (r_btn, 'right', 0)],
|
||||
attachControl=[(scroll, "bottom", 5, watch),
|
||||
(watch, "bottom" ,5, s_btn),
|
||||
(s_btn, "bottom", 5, r_btn)])
|
||||
frame.add_tab(self)
|
||||
|
||||
def job_watcher_ui(self):
|
||||
"""Create UI elements for enabling and configuring job watcher."""
|
||||
self.watch_job = maya.check_box(
|
||||
label="Watch this job",
|
||||
onCommand=lambda e: self.enable_watcher(True),
|
||||
offCommand=lambda e: self.enable_watcher(False),
|
||||
annotation="Watching a job will download tasks as they complete.")
|
||||
self.selected_dir = utils.get_default_output_path()
|
||||
self.dir = maya.text_field(text=self.selected_dir, editable=False)
|
||||
self.dir_button = maya.symbol_button(
|
||||
image="SP_DirOpenIcon.png",
|
||||
command=self.select_dir,
|
||||
annotation="Select Download Directory")
|
||||
|
||||
def select_dir(self, *args):
|
||||
"""Selected directory for job watcher to download outputs to.
|
||||
Command for directory select symbol button.
|
||||
"""
|
||||
cap = "Select target directory for job downloads."
|
||||
okCap = "Select Folder"
|
||||
new_dir = maya.file_select(fileMode=3, okCaption=okCap, caption=cap)
|
||||
if new_dir:
|
||||
self.selected_dir = new_dir[0]
|
||||
maya.text_field(self.dir, edit=True, text=self.selected_dir)
|
||||
|
||||
def enable_watcher(self, opt):
|
||||
"""Enable whether job watcher will be launched after submission.
|
||||
Command for watch_job check box.
|
||||
:param bool opt: True if watcher enabled.
|
||||
"""
|
||||
maya.symbol_button(self.dir_button, edit=True, enable=opt)
|
||||
maya.text_field(
|
||||
self.dir, edit=True, enable=opt, text=self.selected_dir)
|
||||
|
||||
def is_logged_in(self):
|
||||
"""Called when the plug-in is authenticated. Enables UI."""
|
||||
maya.form_layout(self.page, edit=True, enable=True)
|
||||
|
||||
def is_logged_out(self):
|
||||
"""Called when the plug-in is logged out. Disables UI and resets
|
||||
whether that tab has been loaded for the first time.
|
||||
"""
|
||||
maya.form_layout(self.page, edit=True, enable=False)
|
||||
|
||||
def prepare(self):
|
||||
"""Prepare Submit tab contents - nothing needs to be done here as all
|
||||
loaded on plug-in start up.
|
||||
"""
|
||||
pass
|
||||
|
||||
def refresh(self, *args):
|
||||
"""Refresh Submit tab. Command for refresh_button.
|
||||
Remove all existing UI elements and renderer details and re-build
|
||||
from scratch.
|
||||
"""
|
||||
self.refresh_button.start()
|
||||
self.base.refresh_renderer(self.render_module)
|
||||
self.selected_dir = utils.get_default_output_path()
|
||||
maya.text_field(self.dir, edit=True, text=self.selected_dir)
|
||||
self.refresh_button.finish()
|
||||
|
||||
def submit_status(self, status):
|
||||
"""Report submission status in UI. Called from base class.
|
||||
Displays status in the submit_button label.
|
||||
:param str status: The status string to display.
|
||||
"""
|
||||
self.submit_button.update(status)
|
||||
|
||||
def submit(self, *args):
|
||||
"""Submit new job. Command for submit_button."""
|
||||
self.submit_button.start()
|
||||
self.refresh_button.enable(False)
|
||||
self.enable_watcher(False)
|
||||
maya.check_box(self.watch_job, edit=True, enable=False)
|
||||
watcher = maya.check_box(self.watch_job, query=True, value=True)
|
||||
if watcher and not self.selected_dir:
|
||||
maya.warning("You must select a download directory "
|
||||
"if you wish to watch this job.")
|
||||
else:
|
||||
self.base.submit(watcher, self.selected_dir)
|
||||
maya.check_box(self.watch_job, edit=True, enable=True)
|
||||
self.enable_watcher(True)
|
||||
self.submit_button.finish()
|
||||
self.refresh_button.enable(True)
|
||||
|
||||
def submit_enabled(self, enable):
|
||||
"""Enable or disable users ability to submit a job based on renderer
|
||||
conditions.
|
||||
:param bool enable: Whether to enable to submission.
|
||||
"""
|
||||
maya.check_box(self.watch_job, edit=True, enable=enable)
|
||||
maya.button(self.dir_button, edit=True, enable=False)
|
||||
maya.button(self.submit_button.display, edit=True, enable=enable)
|
||||
|
||||
def get_pool(self):
|
||||
"""Get selected pool configuration.
|
||||
:returns: A dictionary with selected pool type as key and pool
|
||||
specification as value.
|
||||
"""
|
||||
if self.select_pool_type == 2:
|
||||
details = str(maya.menu(self.control, query=True, value=True))
|
||||
else:
|
||||
details = self.select_instances
|
||||
return {self.select_pool_type: details}
|
||||
|
||||
def set_pool_instances(self, instances):
|
||||
"""Update the number of requested instances in a pool
|
||||
based on the instance slider.
|
||||
"""
|
||||
self.select_instances = instances
|
||||
|
||||
def set_pool_new(self, *args):
|
||||
"""Set selected pool type to be new pool of given size.
|
||||
Displays the pool size UI control.
|
||||
Command for select_pool_type radio buttons.
|
||||
"""
|
||||
self.select_pool_type = 3
|
||||
maya.delete_ui(self.control)
|
||||
maya.text(self.pool_text, edit=True, label="Instances: ")
|
||||
self.control = maya.int_slider(
|
||||
field=True,
|
||||
value=self.select_instances,
|
||||
minValue=1,
|
||||
maxValue=1000,
|
||||
fieldMinValue=1,
|
||||
fieldMaxValue=1000,
|
||||
parent=self.pool_settings,
|
||||
changeCommand=self.set_pool_instances,
|
||||
annotation="Number of instances in pool")
|
||||
|
||||
def set_pool_auto(self, *args):
|
||||
"""Set selected pool type to be new pool of given size.
|
||||
Displays the pool size UI control.
|
||||
Command for select_pool_type radio buttons.
|
||||
"""
|
||||
self.select_pool_type = 1
|
||||
maya.delete_ui(self.control)
|
||||
maya.text(self.pool_text, edit=True, label="Instances: ")
|
||||
self.control = maya.int_slider(
|
||||
field=True,
|
||||
value=self.select_instances,
|
||||
minValue=1,
|
||||
maxValue=1000,
|
||||
fieldMinValue=1,
|
||||
fieldMaxValue=1000,
|
||||
parent=self.pool_settings,
|
||||
changeCommand=self.set_pool_instances,
|
||||
annotation="Number of instances in pool")
|
||||
|
||||
def set_pool_reuse(self, *args):
|
||||
"""Set selected pool type to be an existing pool with given ID.
|
||||
Loads the currently available pools and displays the pool IDs
|
||||
in a dropdown menu.
|
||||
Command for select_pool_type radio buttons.
|
||||
"""
|
||||
self.select_pool_type = 2
|
||||
maya.delete_ui(self.control)
|
||||
maya.text(self.pool_text, edit=True, label="loading...")
|
||||
maya.refresh()
|
||||
pool_options = self.base.available_pools()
|
||||
maya.text(self.pool_text, edit=True, label="Pool ID: ")
|
||||
self.control = maya.menu(
|
||||
parent=self.pool_settings,
|
||||
annotation="Use an existing persistent pool ID")
|
||||
for pool_id in pool_options:
|
||||
maya.menu_option(pool_id)
|
||||
#with utils.Dropdown(None, parent=self.pool_settings, annotation="Use an existing persistent pool ID") as pools:
|
||||
# self.control = pools
|
||||
# for pool_id in pool_options:
|
||||
# self.control.add_item(pool_id)
|
||||
|
|
@ -0,0 +1,513 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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 api import MayaAPI as maya
|
||||
import os
|
||||
import logging
|
||||
import platform
|
||||
import pathlib
|
||||
|
||||
from azure.batch_extensions import _file_utils as file_utils
|
||||
from exception import CancellationException, FileUploadException
|
||||
|
||||
|
||||
MAX_LOCAL_PATH_LENGTH = 150
|
||||
MAYA_SKU_WINDOWS = 'batch.node.windows amd64'
|
||||
MAYA_IMAGE_WINDOWS = {
|
||||
'publisher': 'batch',
|
||||
'offer': 'mayaimage-preview-preview',
|
||||
'sku': 'mayaimage-preview',
|
||||
'version': 'latest'}
|
||||
|
||||
|
||||
def shorten_path(path, filename):
|
||||
"""Iteratively remove directories from the end of a file path
|
||||
until it meets the Windows max path length requirements when
|
||||
if is downloaded to the render node.
|
||||
"""
|
||||
while len(os.path.join(path, filename)) > MAX_LOCAL_PATH_LENGTH:
|
||||
path = os.path.dirname(path)
|
||||
return path
|
||||
|
||||
|
||||
def get_storage_file_path(fullpath):
|
||||
"""Generate the virtual directory path of the asset in
|
||||
Azure storage.
|
||||
"""
|
||||
path = shorten_path(*os.path.split(fullpath))
|
||||
if ':' in path:
|
||||
drive_letter, path = path.split(':', 1)
|
||||
path = drive_letter + '/' + path[1:]
|
||||
return path.replace('\\', '/')
|
||||
|
||||
|
||||
def get_remote_file_path(assetpath):
|
||||
"""Generate remote asset path. Returns a function
|
||||
to allow for delayed generation based on the selected
|
||||
pool os flavor at job submission time.
|
||||
"""
|
||||
#TODO: Check the mapping according to pool OS flavor
|
||||
def generate_path(os_flavor, fullpath=assetpath):
|
||||
print("generating path: ", fullpath)
|
||||
local_sep = os.sep
|
||||
remote_sep = '\\' if os_flavor == 'Windows' else '/'
|
||||
path = shorten_path(*os.path.split(fullpath))
|
||||
if ':' in path:
|
||||
drive_letter, path = path.split(':', 1)
|
||||
path = drive_letter + local_sep + path[1:]
|
||||
path = path.replace('/', remote_sep).replace('\\', remote_sep)
|
||||
print("done")
|
||||
return path.strip('\\/').replace('\\', '\\\\')
|
||||
return generate_path
|
||||
|
||||
|
||||
def format_scene_path(scene_file, os_flavor):
|
||||
"""Format the Maya scene file path according to where it will
|
||||
be on the render node.
|
||||
"""
|
||||
scene_path = get_remote_file_path(scene_file)(os_flavor)
|
||||
if os_flavor == 'Windows':
|
||||
return "X:\\\\" + scene_path + '\\\\' + os.path.basename(scene_file)
|
||||
else:
|
||||
return "/X/" + scene_path + '/' + os.path.basename(scene_file)
|
||||
|
||||
|
||||
def get_default_output_path():
|
||||
"""Get the render output directory as specified in the current
|
||||
Maya project.
|
||||
|
||||
:returns: The output directory (str).
|
||||
"""
|
||||
path = maya.workspace("images", q=True, fre=True)
|
||||
output_path = maya.workspace(en=path)
|
||||
return output_path
|
||||
|
||||
|
||||
def get_os():
|
||||
"""Get the current operating system.
|
||||
:returns: The OS platform (str).
|
||||
"""
|
||||
return platform.system()
|
||||
|
||||
|
||||
class Row(object):
|
||||
"""UI row class."""
|
||||
|
||||
def __init__(self, columns, adjust, width, align=None, row=None, parent=None):
|
||||
kwargs = {}
|
||||
kwargs["numberOfColumns"] = int(columns)
|
||||
kwargs["columnWidth{0}".format(columns)] = width
|
||||
kwargs["adjustableColumn"] = adjust
|
||||
kwargs["columnAttach{0}".format(columns)] = columns*["both"] if columns>1 else "both"
|
||||
kwargs["columnOffset{0}".format(columns)] = columns*[5] if columns>1 else 5
|
||||
if row:
|
||||
kwargs["rowAttach"] = row
|
||||
if align:
|
||||
kwargs["columnAlign{0}".format(columns)] = align
|
||||
if parent:
|
||||
kwargs["parent"] = parent
|
||||
self._row = maya.row(**kwargs)
|
||||
|
||||
def __enter__(self):
|
||||
return self._row
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
maya.parent()
|
||||
|
||||
class Layout(object):
|
||||
"""Parent layout class."""
|
||||
|
||||
def __init__(self, form, **kwargs):
|
||||
"""Create a new layout based on the supplied layout form and args.
|
||||
|
||||
:param form: The layout object to be created.
|
||||
:type form: :class:`maya.cmds.Layout`
|
||||
:param kwargs: Any properties to apply to the layout.
|
||||
"""
|
||||
self.form = form
|
||||
settings = {}
|
||||
if kwargs.get("width"):
|
||||
settings["width"] = kwargs["width"]
|
||||
if kwargs.get("height"):
|
||||
settings["height"] = kwargs["height"]
|
||||
if kwargs.get("parent"):
|
||||
settings["parent"] = kwargs["parent"]
|
||||
if kwargs.get("row_spacing"):
|
||||
settings["rowSpacing"] = kwargs["row_spacing"]
|
||||
if kwargs.get("col_attach"):
|
||||
settings["columnAttach"] = kwargs["col_attach"]
|
||||
if kwargs.get("row_attach"):
|
||||
settings["rowAttach"] = kwargs["row_attach"]
|
||||
if kwargs.get("adjust"):
|
||||
settings["adjustableColumn"] = kwargs["adjust"]
|
||||
if kwargs.get("layout"):
|
||||
settings.update(kwargs["layout"])
|
||||
self.layout = self.form(**settings)
|
||||
|
||||
def __enter__(self):
|
||||
return self.layout
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
#TODO: Exception handling should go in here
|
||||
maya.parent()
|
||||
|
||||
|
||||
class RowLayout(Layout):
|
||||
"""Wrapper class for :class:`maya.cmds.rowLayout`."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
kwargs["adjust"] = True
|
||||
super(RowLayout, self).__init__(maya.row_layout, **kwargs)
|
||||
|
||||
|
||||
class FrameLayout(Layout):
|
||||
"""Wrapper class for :class:`maya.cmds.frameLayout`."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
settings = {}
|
||||
if kwargs.get("label"):
|
||||
settings["label"] = kwargs["label"]
|
||||
if kwargs.get("collapsable"):
|
||||
settings["collapsable"] = kwargs["collapsable"]
|
||||
super(FrameLayout, self).__init__(
|
||||
maya.frame_layout, layout=settings, **kwargs)
|
||||
|
||||
class GridLayout(Layout):
|
||||
"""Wrapper class for :class:`maya.cmds.gridLayout`."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
settings = {}
|
||||
if kwargs.get("rows"):
|
||||
settings["numberOfRows"] = kwargs["rows"]
|
||||
if kwargs.get("cols"):
|
||||
settings["numberOfColumns"] = kwargs["cols"]
|
||||
if kwargs.get("width"):
|
||||
settings["cellWidth"] = kwargs["width"]
|
||||
super(GridLayout, self).__init__(
|
||||
maya.grid_layout, layout=settings, **kwargs)
|
||||
|
||||
|
||||
class ColumnLayout(Layout):
|
||||
"""Wrapper class for :class:`maya.cmds.columnLayout`."""
|
||||
|
||||
def __init__(self, columns, **kwargs):
|
||||
settings = {"numberOfColumns": columns}
|
||||
if kwargs.get("col_width"):
|
||||
settings["columnWidth"] = kwargs["col_width"]
|
||||
if kwargs.get("row_offset"):
|
||||
settings["rowOffset"] = kwargs["row_offset"]
|
||||
if kwargs.get("row_height"):
|
||||
settings["rowHeight"] = kwargs["row_height"]
|
||||
if kwargs.get("col_align"):
|
||||
settings["columnAlign"] = kwargs["col_align"]
|
||||
super(ColumnLayout, self).__init__(
|
||||
maya.col_layout, layout=settings, **kwargs)
|
||||
|
||||
|
||||
class ScrollLayout(Layout):
|
||||
"""Wrapper class for :class:`maya.cmds.scrollLayout`."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
settings = {"horizontalScrollBarThickness": 0,
|
||||
"verticalScrollBarThickness": 3,
|
||||
"childResizable":True}
|
||||
super(ScrollLayout, self).__init__(
|
||||
maya.scroll_layout, layout=settings, **kwargs)
|
||||
|
||||
|
||||
class ClickMenu(object):
|
||||
"""Wrapper class for :class:`maya.cmds.popupMenu`."""
|
||||
|
||||
def __init__(self, command, **kwargs):
|
||||
"""Create new right-click menu.
|
||||
|
||||
:param func command: The function to call when a menu item
|
||||
is selected.
|
||||
"""
|
||||
self.menu = maya.popup_menu(**kwargs)
|
||||
self.command = command
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
pass
|
||||
|
||||
def add_item(self, item):
|
||||
"""Add menu item. Item will be passed into self.command.
|
||||
:param str item: The item to add.
|
||||
"""
|
||||
maya.menu_option(
|
||||
label=item, parent=self.menu, command=lambda a: self.command(item))
|
||||
|
||||
class Dropdown(object):
|
||||
"""Wrapper class for :class:`maya.cmds.menu`."""
|
||||
|
||||
def __init__(self, command, **kwargs):
|
||||
"""Create new dropdown menu.
|
||||
:param func command: Function to be called when a menu item
|
||||
is selected.
|
||||
"""
|
||||
|
||||
self.menu = maya.menu(changeCommand=command, **kwargs)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
pass
|
||||
#maya.parent()
|
||||
|
||||
def add_item(self, item):
|
||||
"""Add item to the menu
|
||||
:param str item: Menu option to add.
|
||||
"""
|
||||
maya.menu_option(label=item, parent=self.menu)
|
||||
|
||||
def selected(self):
|
||||
"""Get selected option.
|
||||
:returns: The index (int) of the selected option.
|
||||
"""
|
||||
return int(maya.menu(self.menu, query=True, select=True))
|
||||
|
||||
def value(self):
|
||||
"""Get the selected option.
|
||||
:returns: The value (str) of the selected option.
|
||||
"""
|
||||
return str(maya.menu(self.menu, query=True, value=True))
|
||||
|
||||
def select(self, value):
|
||||
"""Select a specific option in the menu.
|
||||
:param int value: The index of the option to select.
|
||||
"""
|
||||
maya.menu(self.menu, edit=True, select=int(value))
|
||||
|
||||
|
||||
class ProgressBar(object):
|
||||
"""Wrapper class for :class:`maya.cmds.mainProgressBar`."""
|
||||
|
||||
def __init__(self, log):
|
||||
"""Create and start new progress bar."""
|
||||
self.done = False
|
||||
self._log = log
|
||||
self._progress = maya.mel('$tmp = $gMainProgressBar')
|
||||
maya.progress_bar(self._progress, edit=True,
|
||||
beginProgress=True, isInterruptable=True)
|
||||
|
||||
def end(self):
|
||||
"""End the progress bar."""
|
||||
maya.progress_bar(self._progress, edit=True, endProgress=True)
|
||||
|
||||
def is_cancelled(self):
|
||||
"""Check whether process has been cancelled.
|
||||
:returns: True if cancelled else False.
|
||||
"""
|
||||
if maya.progress_bar(self._progress, query=True, isCancelled=True):
|
||||
self.done = True
|
||||
self.end()
|
||||
raise CancellationException("File upload cancelled")
|
||||
self._log.debug("not cancelled")
|
||||
|
||||
def step(self):
|
||||
"""Step progress bar forward by one."""
|
||||
maya.progress_bar(self._progress, edit=True, step=1)
|
||||
|
||||
def status(self, status):
|
||||
"""Update status label of progress bar.
|
||||
:param str status: The updated status label.
|
||||
"""
|
||||
self._log.debug("Progress status: {}".format(status))
|
||||
maya.progress_bar(self._progress, edit=True, status=str(status))
|
||||
|
||||
def max(self, max_value):
|
||||
"""Set the max number of steps in the progress bar.
|
||||
:param int max_value: Number of intervals.
|
||||
"""
|
||||
maya.progress_bar(self._progress, edit=True, maxValue=int(max_value))
|
||||
|
||||
|
||||
class HoldButton(object):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._button = maya.button(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def display(self):
|
||||
"""Returns the :class:`maya.cmds.button` object."""
|
||||
return self._button
|
||||
|
||||
def hold(self):
|
||||
"""Place the button in a processing state."""
|
||||
maya.button(self._button, edit=True, enable=False)
|
||||
maya.refresh()
|
||||
|
||||
def release(self):
|
||||
"""Finish the buttons processing state."""
|
||||
maya.button(self._button, edit=True, enable=True)
|
||||
maya.refresh()
|
||||
|
||||
|
||||
class ProcButton(object):
|
||||
"""Wrapper class for :class:`maya.cmds.button` for long running
|
||||
processes.
|
||||
"""
|
||||
|
||||
def __init__(self, label, proc_label, command, *args, **kwargs):
|
||||
"""Create new process button.
|
||||
|
||||
:param str label: The label for the button.
|
||||
:param str proc_label: The label for the button while in a
|
||||
processing state.
|
||||
:param func command: The function the button executes.
|
||||
"""
|
||||
self.label = label
|
||||
self.prob_label = proc_label
|
||||
self._button = maya.button(
|
||||
label=self.label, command=command, *args, **kwargs)
|
||||
|
||||
@property
|
||||
def display(self):
|
||||
"""Returns the :class:`maya.cmds.button` object."""
|
||||
return self._button
|
||||
|
||||
def start(self):
|
||||
"""Place the button in a processing state."""
|
||||
maya.button(
|
||||
self._button, edit=True, enable=False, label=self.prob_label)
|
||||
maya.refresh()
|
||||
|
||||
def update(self, update):
|
||||
"""Update the button label with process status.
|
||||
:param str update: The status to display on the label.
|
||||
"""
|
||||
update = "{0} [Press ESC to cancel]".format(update)
|
||||
maya.button(self._button, edit=True, enable=False, label=update)
|
||||
maya.refresh()
|
||||
|
||||
def enable(self, enabled):
|
||||
"""Enable or disable the button.
|
||||
:param bool enabled: Whether to enable the button.
|
||||
"""
|
||||
maya.button(self._button, edit=True, enable=enabled)
|
||||
maya.refresh()
|
||||
|
||||
def finish(self):
|
||||
"""Finish the buttons processing state."""
|
||||
maya.button(self._button, edit=True, enable=True, label=self.label)
|
||||
maya.refresh()
|
||||
|
||||
|
||||
class JobWatcher(object):
|
||||
"""Class for background job watcher."""
|
||||
|
||||
def __init__(self, id, data_path, dir):
|
||||
"""Create a new job watcher.
|
||||
|
||||
:param str id: The ID of the job to watch.
|
||||
:param str data_path: The path of the AzureBatch config dir.
|
||||
:param str dir: The path of directory where outputs will be
|
||||
downloaded.
|
||||
"""
|
||||
self.job_id = id
|
||||
self.data_path = data_path
|
||||
self.selected_dir = dir
|
||||
self._log = logging.getLogger('AzureBatchMaya')
|
||||
self.job_watcher = os.path.join(
|
||||
os.path.dirname(__file__), "tools", "job_watcher.py")
|
||||
platform = get_os()
|
||||
if platform == "Windows":
|
||||
self.proc_cmd = 'system("WMIC PROCESS where (Name=\'mayapy.exe\') get Commandline")'
|
||||
self.start_cmd = 'system("start mayapy {0}")'
|
||||
self.quotes = '\\"'
|
||||
self.splitter = 'mayapy'
|
||||
elif platform == "Darwin":
|
||||
self.proc_cmd = 'system("ps -ef")'
|
||||
self.start_cmd = 'system("osascript -e \'tell application \\"Terminal\\" to do script \\"python {0}\\"\'")'
|
||||
self.quotes = '\\\\\\"'
|
||||
self.splitter = '\n'
|
||||
else:
|
||||
maya.warning("Cannot launch job watcher: OS not supported.")
|
||||
return
|
||||
self.start_job_watcher()
|
||||
|
||||
def start_job_watcher(self, *args):
|
||||
"""Launch job watcher process using mayapy."""
|
||||
try:
|
||||
if not self.check_existing_process():
|
||||
args = self.prepare_args()
|
||||
command = self.start_cmd.format(" ".join(args))
|
||||
self._log.debug("Running command: {0}".format(command))
|
||||
maya.mel(command)
|
||||
self._log.info("Job watching for job with id {0}"
|
||||
" has started.".format(args[2]))
|
||||
else:
|
||||
maya.warning("Existing process running with current job ID. "
|
||||
"Job watching already in action.")
|
||||
except Exception as e:
|
||||
maya.warning(e)
|
||||
|
||||
def check_existing_process(self):
|
||||
"""Check whether a job watcher for the specified job is already running.
|
||||
:returns: True if a process already exists else False.
|
||||
"""
|
||||
self._log.info("Checking that a job watching process is not "
|
||||
"already running for this job.")
|
||||
processes = maya.mel(self.proc_cmd)
|
||||
processes = processes.split(self.splitter)
|
||||
running = [proc for proc in processes if proc.find(self.job_id) >= 0]
|
||||
if running:
|
||||
return True
|
||||
return False
|
||||
|
||||
def prepare_args(self):
|
||||
"""Prepare the command args to execute with mayapy.
|
||||
:returns: A list of clean args (str).
|
||||
"""
|
||||
args = [self.job_watcher,
|
||||
self.data_path,
|
||||
self.job_id,
|
||||
self.selected_dir,
|
||||
file_utils._get_container_name(self.job_id),
|
||||
os.path.join(maya.script_dir(), 'azure-batch-libs')] # TODO: Configure somewhere
|
||||
self._log.debug("Preparing commandline arguments...")
|
||||
return self.cleanup_args(args)
|
||||
|
||||
def cleanup_args(self, args):
|
||||
"""Clean up path command line args to double back-slashes and quote
|
||||
strings for successful mel execution.
|
||||
|
||||
:param list args: List of str args to be cleaned.
|
||||
:returns: List of cleaned string args.
|
||||
"""
|
||||
prepared_args = []
|
||||
for arg in args:
|
||||
arg = os.path.normpath(arg).replace('\\', '\\\\')
|
||||
prepared_args.append(self.quotes + str(arg) + self.quotes)
|
||||
self._log.debug("Cleaned up commandline arguments: {0}, {1}, "
|
||||
"{2}, {3}, {4}".format(*prepared_args))
|
||||
return prepared_args
|
|
@ -0,0 +1,141 @@
|
|||
{
|
||||
"templateMetadata": {
|
||||
"description": "Sample application template for working with Blender."
|
||||
},
|
||||
"parameters": {
|
||||
"sceneFile": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "The Maya scene file to be rendered"
|
||||
}
|
||||
},
|
||||
"renderer": {
|
||||
"type": "string",
|
||||
"defaultValue": "file",
|
||||
"metadata": {
|
||||
"description": "The Maya renderer to be used for the render"
|
||||
},
|
||||
"allowedValues": [
|
||||
"arnold",
|
||||
"default",
|
||||
"sw",
|
||||
"turtlebake",
|
||||
"turtle",
|
||||
"vr"
|
||||
]
|
||||
},
|
||||
"projectData": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "The file group where the input data is stored"
|
||||
}
|
||||
},
|
||||
"assetScript": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "The SAS URL to a pre-render asset path redirection script"
|
||||
}
|
||||
},
|
||||
"frameStart": {
|
||||
"type": "int",
|
||||
"metadata": {
|
||||
"description": "Index of the first frame to render"
|
||||
}
|
||||
},
|
||||
"frameStep": {
|
||||
"type": "int",
|
||||
"metadata": {
|
||||
"description": "Incremental step in frame sequeunce"
|
||||
}
|
||||
},
|
||||
"frameEnd": {
|
||||
"type": "int",
|
||||
"metadata": {
|
||||
"description": "Index of the last frame to render"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "The file group where outputs will be stored"
|
||||
}
|
||||
}
|
||||
},
|
||||
"jobPreparationTask": {
|
||||
"resourceFiles": [
|
||||
{
|
||||
"source": {
|
||||
"fileGroup": "[parameters('projectData')]"
|
||||
},
|
||||
"filePath": "assets/"
|
||||
},
|
||||
{
|
||||
"blobSource": "[parameters('assetScript')]",
|
||||
"filePath": "scripts/renderPrep.mel"
|
||||
}
|
||||
],
|
||||
"commandLine": "dir"
|
||||
},
|
||||
"taskFactory": {
|
||||
"type": "parametricSweep",
|
||||
"parameterSets": [
|
||||
{
|
||||
"start": "[parameters('frameStart')]",
|
||||
"end": "[parameters('frameEnd')]",
|
||||
"step": "[parameters('frameStep')]"
|
||||
}
|
||||
],
|
||||
"repeatTask": {
|
||||
"displayName": "Frame {0}",
|
||||
"commandLine": "mkdir /X;mount -rbind $AZ_BATCH_JOB_PREP_WORKING_DIR/assets /X;render -renderer \"[parameters('renderer')]\" -verb -preRender \"renderPrep\" -rd \"$AZ_BATCH_TASK_WORKING_DIR/images\" -s {0} -e {0} \"[parameters('sceneFile')]\"",
|
||||
"environmentSettings": [
|
||||
{
|
||||
"name": "MAYA_SCRIPT_PATH",
|
||||
"value": "$AZ_BATCH_JOB_PREP_WORKING_DIR/scripts"
|
||||
},
|
||||
{
|
||||
"name": "FLEXLM_TIMEOUT",
|
||||
"value": "5000000"
|
||||
}
|
||||
],
|
||||
"outputFiles": [
|
||||
{
|
||||
"filePattern": "images/**/*",
|
||||
"destination": {
|
||||
"autoStorage": {
|
||||
"fileGroup": "[parameters('outputs')]"
|
||||
}
|
||||
},
|
||||
"uploadDetails": {
|
||||
"taskStatus": "TaskSuccess"
|
||||
}
|
||||
},
|
||||
{
|
||||
"filePattern": "../stdout.txt",
|
||||
"destination": {
|
||||
"autoStorage": {
|
||||
"fileGroup": "[parameters('outputs')]",
|
||||
"path": "logs/frame_{0}.log"
|
||||
}
|
||||
},
|
||||
"uploadDetails": {
|
||||
"taskStatus": "TaskCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"filePattern": "../stderr.txt",
|
||||
"destination": {
|
||||
"autoStorage": {
|
||||
"fileGroup": "[parameters('outputs')]",
|
||||
"path": "logs/frame_{0}_error.log"
|
||||
}
|
||||
},
|
||||
"uploadDetails": {
|
||||
"taskStatus": "TaskCompletion"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"onAllTasksComplete": "terminateJob"
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
{
|
||||
"templateMetadata" : {
|
||||
"description" : "Sample application template for working with Blender."
|
||||
},
|
||||
"parameters": {
|
||||
"sceneFile": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "The Maya scene file to be rendered"
|
||||
}
|
||||
},
|
||||
"renderer": {
|
||||
"type": "string",
|
||||
"defaultValue": "file",
|
||||
"metadata": {
|
||||
"description": "The Maya renderer to be used for the render"
|
||||
},
|
||||
"allowedValues": [
|
||||
"arnold",
|
||||
"default",
|
||||
"sw",
|
||||
"turtlebake",
|
||||
"turtle",
|
||||
"vr"
|
||||
]
|
||||
},
|
||||
"projectData": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "The file group where the input data is stored"
|
||||
}
|
||||
},
|
||||
"assetScript": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "The SAS URL to a pre-render asset path redirection script"
|
||||
}
|
||||
},
|
||||
"frameStart": {
|
||||
"type": "int",
|
||||
"metadata": {
|
||||
"description": "Index of the first frame to render"
|
||||
}
|
||||
},
|
||||
"frameStep": {
|
||||
"type": "int",
|
||||
"metadata": {
|
||||
"description": "Incremental step in frame sequeunce"
|
||||
}
|
||||
},
|
||||
"frameEnd": {
|
||||
"type": "int",
|
||||
"metadata": {
|
||||
"description": "Index of the last frame to render"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"type": "string",
|
||||
"metadata": {
|
||||
"description": "The file group where outputs will be stored"
|
||||
}
|
||||
}
|
||||
},
|
||||
"jobPreparationTask":{
|
||||
"resourceFiles": [
|
||||
{
|
||||
"source": {
|
||||
"fileGroup": "[parameters('projectData')]"
|
||||
},
|
||||
"filePath": "assets\\"
|
||||
},
|
||||
{
|
||||
"blobSource": "[parameters('assetScript')]",
|
||||
"filePath": "scripts\\renderPrep.mel"
|
||||
}
|
||||
],
|
||||
"commandLine": "dir"
|
||||
},
|
||||
"taskFactory": {
|
||||
"type": "parametricSweep",
|
||||
"parameterSets": [
|
||||
{
|
||||
"start": "[parameters('frameStart')]",
|
||||
"end": "[parameters('frameEnd')]",
|
||||
"step": "[parameters('frameStep')]"
|
||||
}
|
||||
],
|
||||
"repeatTask": {
|
||||
"displayName": "Frame {0}",
|
||||
"commandLine": "subst X: %AZ_BATCH_JOB_PREP_WORKING_DIR%\\assets & render -renderer \"[parameters('renderer')]\" -verb -preRender \"renderPrep\" -rd \"%AZ_BATCH_TASK_WORKING_DIR%\\images\" -s {0} -e {0} \"[parameters('sceneFile')]\"",
|
||||
"environmentSettings": [
|
||||
{
|
||||
"name": "MAYA_SCRIPT_PATH",
|
||||
"value": "%AZ_BATCH_JOB_PREP_WORKING_DIR%\\scripts"
|
||||
},
|
||||
{
|
||||
"name": "FLEXLM_TIMEOUT",
|
||||
"value": "5000000"
|
||||
}
|
||||
],
|
||||
"outputFiles": [
|
||||
{
|
||||
"filePattern": "images/**/*",
|
||||
"destination": {
|
||||
"autoStorage": {
|
||||
"fileGroup": "[parameters('outputs')]"
|
||||
}
|
||||
},
|
||||
"uploadDetails": {
|
||||
"taskStatus": "TaskSuccess"
|
||||
}
|
||||
},
|
||||
{
|
||||
"filePattern": "../stdout.txt",
|
||||
"destination": {
|
||||
"autoStorage": {
|
||||
"fileGroup": "[parameters('outputs')]",
|
||||
"path": "logs/frame_{0}.log"
|
||||
}
|
||||
},
|
||||
"uploadDetails": {
|
||||
"taskStatus": "TaskCompletion"
|
||||
}
|
||||
},
|
||||
{
|
||||
"filePattern": "../stderr.txt",
|
||||
"destination": {
|
||||
"autoStorage": {
|
||||
"fileGroup": "[parameters('outputs')]",
|
||||
"path": "logs/frame_{0}_error.log"
|
||||
}
|
||||
},
|
||||
"uploadDetails": {
|
||||
"taskStatus": "TaskCompletion"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"onAllTasksComplete": "terminateJob"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plug-in
|
||||
# Azure Batch Maya Plug-in
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
|
@ -32,7 +32,7 @@ import subprocess
|
|||
import shutil
|
||||
import zipfile
|
||||
|
||||
VERSION = "0.1.0"
|
||||
VERSION = "0.8.1"
|
||||
|
||||
def main():
|
||||
"""Build Maya Plug-in package"""
|
||||
|
@ -56,7 +56,7 @@ def main():
|
|||
continue
|
||||
|
||||
for file in files:
|
||||
if os.path.splitext(file)[1] in ['.png', '.mel', '.py']:
|
||||
if os.path.splitext(file)[1] in ['.png', '.mel', '.py', '.html']:
|
||||
maya_zip.write(os.path.relpath(os.path.join(root, file)))
|
||||
|
||||
print("Package complete!")
|
|
@ -0,0 +1,3 @@
|
|||
azure-batch
|
||||
azure-storage
|
||||
pathlib
|
|
@ -1,4 +1,4 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
#
|
||||
|
@ -50,12 +50,11 @@ if sys.version_info[:2] >= (3, 3, ):
|
|||
else:
|
||||
try:
|
||||
import mock
|
||||
|
||||
except ImportError:
|
||||
print("The Batch Maya Plugin test suite requires "
|
||||
"the mock package to run on Python 3.2 and below.\n"
|
||||
"Please install this package to continue.")
|
||||
sys.exit()
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -64,13 +63,14 @@ if __name__ == '__main__':
|
|||
|
||||
test_dir = os.path.dirname(__file__)
|
||||
top_dir = os.path.dirname(test_dir)
|
||||
src_dir = os.path.join(top_dir, 'batchapps_maya', 'scripts')
|
||||
src_dir = os.path.join(top_dir, 'azure_batch_maya', 'scripts')
|
||||
mod_dir = os.path.join(test_dir, 'data', 'modules')
|
||||
ui_dir = os.path.join(src_dir, 'ui')
|
||||
os.environ["BATCHAPPS_ICONS"] = os.path.join(top_dir, 'batchapps_maya', 'icons')
|
||||
os.environ["BATCHAPPS_MODULES"] = mod_dir
|
||||
os.environ["BATCHAPPS_SCRIPTS"] = "{0};{1}".format(src_dir, ui_dir)
|
||||
sys.path.extend([src_dir, ui_dir, mod_dir])
|
||||
tools_dir = os.path.join(src_dir, 'tools')
|
||||
os.environ["AZUREBATCH_ICONS"] = os.path.join(top_dir, 'azure_batch_maya', 'icons')
|
||||
os.environ["AZUREBATCH_MODULES"] = mod_dir
|
||||
os.environ["AZUREBATCH_SCRIPTS"] = "{0};{1};{2}".format(src_dir, ui_dir, tools_dir)
|
||||
sys.path.extend([src_dir, ui_dir, tools_dir, mod_dir])
|
||||
|
||||
test_loader = TestLoader()
|
||||
suite = test_loader.discover(test_dir,
|
|
@ -1,4 +1,4 @@
|
|||
class BatchAppsRenderJob(object):
|
||||
class AzureBatchRenderJob(object):
|
||||
render_engine = "Renderer_Default"
|
||||
|
||||
def __init__(self):
|
||||
|
@ -26,7 +26,7 @@ class BatchAppsRenderJob(object):
|
|||
def disable(self, disabled):
|
||||
pass
|
||||
|
||||
class BatchAppsRenderAssets(object):
|
||||
class AzureBatchRenderAssets(object):
|
||||
render_engine = "Renderer_Default"
|
||||
|
||||
def renderer_assets(self):
|
|
@ -1,13 +1,13 @@
|
|||
from default import BatchAppsRenderJob, BatchAppsRenderAssets
|
||||
from default import AzureBatchRenderJob, AzureBatchRenderAssets
|
||||
|
||||
class BatchAppsModuleAJob(BatchAppsRenderJob):
|
||||
class AzureBatchModuleAJob(AzureBatchRenderJob):
|
||||
render_engine = "Renderer_A"
|
||||
|
||||
def __init__(self):
|
||||
self._renderer = "ModuleA"
|
||||
self.label = "Module A"
|
||||
|
||||
class BatchAppsModuleAAssets(BatchAppsRenderAssets):
|
||||
class AzureBatchAModuleAAssets(AzureBatchRenderAssets):
|
||||
render_engine = "Renderer_A"
|
||||
|
||||
def renderer_assets(self):
|
|
@ -1,6 +1,6 @@
|
|||
from default import BatchAppsRenderJob
|
||||
from default import AzureBatchRenderJob
|
||||
|
||||
class BatchAppsModuleBJob(BatchAppsRenderJob):
|
||||
class AzureBatchModuleBJob(AzureBatchRenderJob):
|
||||
render_engine = "Renderer_B"
|
||||
|
||||
def __init__(self):
|
|
@ -0,0 +1,7 @@
|
|||
from default import AzureBatchRenderAssets
|
||||
|
||||
class AzureBatchModuleCAssets(AzureBatchRenderAssets):
|
||||
render_engine = "Renderer_C"
|
||||
|
||||
def renderer_assets(self):
|
||||
return {}
|
До Ширина: | Высота: | Размер: 4.0 KiB После Ширина: | Высота: | Размер: 4.0 KiB |
|
@ -30,21 +30,21 @@
|
|||
import sys
|
||||
import os
|
||||
import logging
|
||||
import json
|
||||
|
||||
try:
|
||||
import unittest2 as unittest
|
||||
except ImportError:
|
||||
import unittest
|
||||
|
||||
try:
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
import mock
|
||||
|
||||
from ui_assets import AssetsUI
|
||||
from assets import Asset, Assets, BatchAppsAssets
|
||||
from batchapps import FileManager, Configuration, Credentials
|
||||
from batchapps.files import UserFile, FileCollection
|
||||
from assets import Asset, Assets, AzureBatchAssets
|
||||
from exception import FileUploadException
|
||||
from utils import ProgressBar, ProcButton
|
||||
|
||||
class TestAsset(unittest.TestCase):
|
||||
|
||||
|
@ -74,16 +74,24 @@ class TestAsset(unittest.TestCase):
|
|||
self.mock_self.file = self.mock_file
|
||||
self.mock_self.label = "label"
|
||||
self.mock_self.note = "note"
|
||||
#self.mock_file.__bool__ = True
|
||||
|
||||
Asset.display(self.mock_self, "layout")
|
||||
mock_api.check_box.assert_called_with(label="label",
|
||||
value=False,
|
||||
enable=False,
|
||||
Asset.display(self.mock_self, "ui", "layout", "scroll")
|
||||
mock_api.symbol_button.assert_called_with(image="fpe_someBrokenPaths.png",
|
||||
parent="layout",
|
||||
onCommand=mock.ANY,
|
||||
offCommand=mock.ANY,
|
||||
annotation="note")
|
||||
command=mock.ANY,
|
||||
height=17,
|
||||
annotation="Add search path")
|
||||
mock_api.text.assert_called_with("label", parent="layout", enable=False, annotation="note", align="left")
|
||||
self.assertEqual(self.mock_self.scroll_layout, "scroll")
|
||||
self.assertEqual(self.mock_self.frame, "ui")
|
||||
|
||||
self.mock_self.file = None
|
||||
Asset.display(self.mock_self, "ui", "layout", "scroll")
|
||||
mock_api.symbol_button.assert_called_with(image='fpe_someBrokenPaths.png',
|
||||
parent="layout",
|
||||
command=mock.ANY,
|
||||
height=17,
|
||||
annotation="Add search path")
|
||||
|
||||
@mock.patch("assets.maya")
|
||||
def test_included(self, mock_api):
|
||||
|
@ -96,30 +104,63 @@ class TestAsset(unittest.TestCase):
|
|||
|
||||
self.mock_self.check_box = 1
|
||||
val = Asset.included(self.mock_self)
|
||||
mock_api.check_box.assert_called_with(1, query=True, value=True)
|
||||
self.assertFalse(val)
|
||||
|
||||
def test_include(self):
|
||||
self.mock_self.file = "True"
|
||||
val = Asset.included(self.mock_self)
|
||||
mock_api.symbol_check_box.assert_called_with(1, query=True, value=True)
|
||||
|
||||
@mock.patch("assets.maya")
|
||||
def test_include(self, mock_api):
|
||||
|
||||
self.mock_self.check_box = 1
|
||||
self.mock_self.parent_list = []
|
||||
Asset.include(self.mock_self)
|
||||
self.assertEqual(self.mock_self.parent_list, [self.mock_self])
|
||||
mock_api.symbol_check_box.assert_called_with(1, edit=True, annotation="Click to remove asset from submission")
|
||||
|
||||
Asset.include(self.mock_self)
|
||||
self.assertEqual(self.mock_self.parent_list, [self.mock_self])
|
||||
mock_api.symbol_check_box.assert_called_with(1, edit=True, annotation="Click to remove asset from submission")
|
||||
|
||||
def test_exclude(self):
|
||||
@mock.patch("assets.maya")
|
||||
def test_search(self, mock_api):
|
||||
from assets import USR_SEARCHPATHS
|
||||
|
||||
self.mock_self.frame = mock.create_autospec(AssetsUI)
|
||||
mock_api.file_select.return_value = None
|
||||
Asset.search(self.mock_self)
|
||||
self.assertEqual(USR_SEARCHPATHS, [])
|
||||
self.assertEqual(self.mock_self.frame.refresh.call_count, 0)
|
||||
|
||||
mock_api.file_select.return_value = []
|
||||
Asset.search(self.mock_self)
|
||||
self.assertEqual(USR_SEARCHPATHS, [])
|
||||
self.assertEqual(self.mock_self.frame.refresh.call_count, 0)
|
||||
|
||||
mock_api.file_select.return_value = ["selected_path"]
|
||||
Asset.search(self.mock_self)
|
||||
self.assertEqual(USR_SEARCHPATHS, ["selected_path"])
|
||||
self.mock_self.frame.refresh.assert_called_with()
|
||||
|
||||
@mock.patch("assets.maya")
|
||||
def test_exclude(self, mock_api):
|
||||
|
||||
self.mock_self.check_box = 1
|
||||
self.mock_self.parent_list = []
|
||||
Asset.exclude(self.mock_self)
|
||||
self.assertEqual(self.mock_self.parent_list, [])
|
||||
self.assertEqual(mock_api.symbol_check_box.call_count, 0)
|
||||
|
||||
self.mock_self.parent_list = ["test"]
|
||||
Asset.exclude(self.mock_self)
|
||||
self.assertEqual(self.mock_self.parent_list, ["test"])
|
||||
self.assertEqual(mock_api.symbol_check_box.call_count, 0)
|
||||
|
||||
self.mock_self.parent_list = [self.mock_self]
|
||||
Asset.exclude(self.mock_self)
|
||||
self.assertEqual(self.mock_self.parent_list, [])
|
||||
mock_api.symbol_check_box.assert_called_with(1, edit=True, annotation="Click to include asset in submission")
|
||||
|
||||
@mock.patch("assets.maya")
|
||||
def test_delete(self, mock_api):
|
||||
|
@ -142,13 +183,71 @@ class TestAsset(unittest.TestCase):
|
|||
new_file.path = "C:\\TEST_file\\WeiRD_path"
|
||||
self.mock_self.path = "c:\\test_file\\Weird_path"
|
||||
|
||||
check = Asset.check(self.mock_self, [new_file])
|
||||
check = Asset.is_duplicate(self.mock_self, [new_file])
|
||||
self.assertTrue(check)
|
||||
|
||||
new_file.path = "C:\\TEST_file\\WeiRD_path\\different"
|
||||
check = Asset.check(self.mock_self, [new_file])
|
||||
check = Asset.is_duplicate(self.mock_self, [new_file])
|
||||
self.assertFalse(check)
|
||||
|
||||
@mock.patch("assets.maya")
|
||||
def test_make_visible(self, mock_maya):
|
||||
|
||||
mock_maya.text.return_value = 17
|
||||
self.mock_self.scroll_layout = "layout"
|
||||
self.mock_self.display_text = "text"
|
||||
self.called = 0
|
||||
|
||||
def scroll(*args, **kwargs):
|
||||
self.called += 1
|
||||
if kwargs.get("query") and self.mock_self.scroll_layout == "layout":
|
||||
return [4,0]
|
||||
|
||||
elif kwargs.get("query"):
|
||||
return [0,0]
|
||||
|
||||
else:
|
||||
self.assertEqual(kwargs.get("scrollPage"), "up")
|
||||
self.mock_self.scroll_layout = "scrolled"
|
||||
|
||||
mock_maya.scroll_layout = scroll
|
||||
Asset.make_visible(self.mock_self, 0)
|
||||
self.assertEqual(self.called, 3)
|
||||
|
||||
def scroll(*args, **kwargs):
|
||||
self.called += 1
|
||||
self.assertEqual(kwargs.get("scrollByPixel"), ("down",17))
|
||||
|
||||
mock_maya.scroll_layout = scroll
|
||||
Asset.make_visible(self.mock_self, 5)
|
||||
self.assertEqual(self.called, 4)
|
||||
|
||||
@mock.patch("assets.maya")
|
||||
def test_upload(self, mock_maya):
|
||||
|
||||
self.mock_self.path = "/my/test/path/file.txt"
|
||||
self.mock_self.display_text = "display"
|
||||
self.mock_self.file = mock.create_autospec(UserFile)
|
||||
self.mock_self.file.upload.return_value = mock.Mock(success=True)
|
||||
self.mock_self.included.return_value = False
|
||||
prog = mock.create_autospec(ProgressBar)
|
||||
|
||||
Asset.upload(self.mock_self, 0, prog, True)
|
||||
self.mock_self.file.upload.assert_called_with(force=True, callback=mock.ANY, block=100000)
|
||||
self.assertEqual(mock_maya.text.call_count, 0)
|
||||
|
||||
self.mock_self.file.upload.return_value = mock.Mock(success=False)
|
||||
with self.assertRaises(FileUploadException):
|
||||
Asset.upload(self.mock_self, 0, prog, True)
|
||||
|
||||
Asset.upload(self.mock_self, 0, prog, False)
|
||||
mock_maya.text.assert_called_with("display", edit=True, label=" Skipped file.txt")
|
||||
|
||||
self.mock_self.included.return_value = True
|
||||
Asset.upload(self.mock_self, 0, prog, False)
|
||||
mock_maya.text.assert_called_with("display", edit=True, label=" Already uploaded file.txt")
|
||||
|
||||
|
||||
class TestAssets(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
@ -162,6 +261,40 @@ class TestAssets(unittest.TestCase):
|
|||
self.assertIsNone(test_assets.manager)
|
||||
self.assertEqual(test_assets.refs, {'Additional':[]})
|
||||
|
||||
@mock.patch("assets.SYS_SEARCHPATHS")
|
||||
@mock.patch("assets.USR_SEARCHPATHS")
|
||||
@mock.patch("assets.glob")
|
||||
@mock.patch("assets.os.path.exists")
|
||||
def test_search_path(self, mock_exists, mock_glob, mock_sys, mock_usr):
|
||||
|
||||
mock_sys = []
|
||||
mock_usr = []
|
||||
self.mock_self.pathmaps = []
|
||||
mock_exists.return_value = True
|
||||
|
||||
path = Assets.search_path(self.mock_self, "testpath\\testfile")
|
||||
self.assertEqual(path, ["testpath\\testfile"])
|
||||
mock_exists.assert_called_once_with("testpath\\testfile")
|
||||
mock_exists.call_count = 0
|
||||
self.assertEqual(mock_glob.glob.call_count, 0)
|
||||
|
||||
mock_exists.return_value = False
|
||||
path = Assets.search_path(self.mock_self, "testpath\\testfile")
|
||||
self.assertEqual(path, ["testpath\\testfile"])
|
||||
mock_exists.assert_called_once_with("testpath\\testfile")
|
||||
self.assertEqual(mock_glob.glob.call_count, 0)
|
||||
|
||||
mock_glob.glob.return_value = [1,2,3]
|
||||
path = Assets.search_path(self.mock_self, "testpath\\*\\testfile")
|
||||
mock_glob.glob.assert_called_with("testpath\\*\\testfile")
|
||||
self.assertEqual(path, [1,2,3])
|
||||
|
||||
mock_glob.glob.return_value = []
|
||||
path = Assets.search_path(self.mock_self, "testpath\\[0-9]testfile")
|
||||
mock_glob.glob.assert_any_call("testpath\\[0-9]testfile")
|
||||
self.assertEqual(path, ["testpath\\[0-9]testfile"])
|
||||
|
||||
|
||||
def test_gather(self):
|
||||
|
||||
self.mock_self.refs = {}
|
||||
|
@ -170,7 +303,7 @@ class TestAssets(unittest.TestCase):
|
|||
self.mock_self.get_references.return_value = {'c':3}
|
||||
|
||||
Assets.gather(self.mock_self, "manager")
|
||||
self.assertEqual(self.mock_self.refs, {'a':1, 'b':2, 'c':3})
|
||||
self.assertEqual(self.mock_self.refs, {'a':1, 'b':2, 'c':3, 'Additional':[]})
|
||||
self.assertEqual(self.mock_self.manager, "manager")
|
||||
|
||||
self.assertEqual(self.mock_self.get_textures.call_count, 1)
|
||||
|
@ -180,19 +313,19 @@ class TestAssets(unittest.TestCase):
|
|||
@mock.patch("assets.Asset")
|
||||
def test_extend(self, mock_asset):
|
||||
|
||||
self.mock_self.pathmaps = []
|
||||
self.mock_self.refs = {}
|
||||
|
||||
self.mock_self.manager = mock.create_autospec(FileManager)
|
||||
mock_asset.return_value = mock.create_autospec(Asset)
|
||||
mock_asset.return_value.check.return_value = True
|
||||
mock_asset.return_value.is_duplicate.return_value = True
|
||||
self.mock_self.manager.file_from_path.return_value = "UserFile"
|
||||
self.mock_self.search_path.return_value = ["a"]
|
||||
|
||||
Assets.extend(self.mock_self, {"new_path":["/test_path/test_file"]})
|
||||
self.assertEqual(self.mock_self.pathmaps, ["/test_path"])
|
||||
mock_asset.assert_called_with("UserFile", [])
|
||||
self.assertEqual(self.mock_self.refs, {"new_path": []})
|
||||
|
||||
mock_asset.return_value.check.return_value = False
|
||||
mock_asset.return_value.is_duplicate.return_value = False
|
||||
Assets.extend(self.mock_self, {"new_path":["/test_path/test_file"]})
|
||||
self.assertEqual(self.mock_self.refs, {"new_path": [mock.ANY]})
|
||||
|
||||
|
@ -217,11 +350,11 @@ class TestAssets(unittest.TestCase):
|
|||
@mock.patch("assets.maya")
|
||||
def test_get_textures(self, mock_maya, mock_asset):
|
||||
|
||||
self.mock_self.pathmaps = []
|
||||
self.mock_self.manager = mock.create_autospec(FileManager)
|
||||
mock_asset.return_value = mock.create_autospec(Asset)
|
||||
mock_asset.return_value.check.return_value = True
|
||||
mock_asset.return_value.is_duplicate.return_value = True
|
||||
self.mock_self.manager.file_from_path.return_value = "UserFile"
|
||||
self.mock_self.search_path.return_value = ["a"]
|
||||
|
||||
class TestIter(object):
|
||||
|
||||
|
@ -242,29 +375,55 @@ class TestAssets(unittest.TestCase):
|
|||
mock_maya.dependency_nodes.return_value = TestIter()
|
||||
tex = Assets.get_textures(self.mock_self)
|
||||
self.assertEqual(tex, {'Files': []})
|
||||
self.assertEqual(set(self.mock_self.pathmaps), set(["dir1", "dir2", "dir3"]))
|
||||
self.assertEqual(mock_asset.call_count, 15)
|
||||
mock_asset.assert_called_with("UserFile", [])
|
||||
|
||||
mock_asset.return_value.check.return_value = False
|
||||
mock_asset.return_value.is_duplicate.return_value = False
|
||||
mock_maya.dependency_nodes.return_value = TestIter()
|
||||
tex = Assets.get_textures(self.mock_self)
|
||||
self.assertEqual(len(tex['Files']), 15)
|
||||
|
||||
def test_get_references(self):
|
||||
@mock.patch("assets.Asset")
|
||||
@mock.patch("assets.maya")
|
||||
def test_get_references(self, mock_maya, mock_asset):
|
||||
|
||||
self.mock_self.manager = mock.create_autospec(FileManager)
|
||||
mock_asset.return_value = mock.create_autospec(Asset)
|
||||
mock_asset.return_value.is_duplicate.return_value = True
|
||||
self.mock_self.manager.file_from_path.return_value = "UserFile"
|
||||
self.mock_self.pathmaps = []
|
||||
self.mock_self.refs = {'Files':[]}
|
||||
|
||||
refs = Assets.get_references(self.mock_self)
|
||||
self.assertEqual(refs, {})
|
||||
self.assertEqual(refs, {'References':[]})
|
||||
|
||||
mock_maya.get_list.return_value = ["1", "2", "3"]
|
||||
mock_maya.reference.return_value = "c:\\file\\ref"
|
||||
refs = Assets.get_references(self.mock_self)
|
||||
self.assertEqual(refs, {'References':[]})
|
||||
self.assertEqual(set(self.mock_self.pathmaps), set(["c:\\file"]))
|
||||
|
||||
mock_asset.return_value.is_duplicate.return_value = False
|
||||
refs = Assets.get_references(self.mock_self)
|
||||
self.assertEqual(refs, {'References':[mock.ANY]})
|
||||
|
||||
@mock.patch("assets.Asset")
|
||||
@mock.patch("assets.maya")
|
||||
def test_get_caches(self, mock_maya, mock_asset):
|
||||
@mock.patch("assets.glob")
|
||||
def test_get_caches(self, mock_glob, mock_maya, mock_asset):
|
||||
|
||||
def get_attr(node):
|
||||
if node.endswith("cachePath"):
|
||||
return "/test_path"
|
||||
else:
|
||||
return "test_file"
|
||||
|
||||
self.mock_self.pathmaps = []
|
||||
self.mock_self.manager = mock.create_autospec(FileManager)
|
||||
mock_asset.return_value = mock.create_autospec(Asset)
|
||||
mock_maya.get_list.return_value = ["1", "2", "3"]
|
||||
mock_maya.get_attr.return_value = "/test_path/test_file"
|
||||
mock_maya.get_attr = get_attr
|
||||
mock_glob.glob.return_value = ["pathA"]
|
||||
self.mock_self.manager.file_from_path.return_value = "UserFile"
|
||||
|
||||
caches = Assets.get_caches(self.mock_self)
|
||||
|
@ -278,29 +437,29 @@ class TestAssets(unittest.TestCase):
|
|||
self.mock_self.refs = {'Additional':[]}
|
||||
self.mock_self.manager = mock.create_autospec(FileManager)
|
||||
mock_asset.return_value = mock.create_autospec(Asset)
|
||||
mock_asset.return_value.check.return_value = True
|
||||
mock_asset.return_value.is_duplicate.return_value = True
|
||||
self.mock_self.manager.file_from_path.return_value = "UserFile"
|
||||
|
||||
Assets.add_asset(self.mock_self, "/test_path/my_asset", "layout")
|
||||
Assets.add_asset(self.mock_self, "/test_path/my_asset", "ui", ("layout", "scroll"))
|
||||
self.assertEqual(self.mock_self.pathmaps, ["/test_path"])
|
||||
self.assertEqual(self.mock_self.refs, {'Additional':[]})
|
||||
mock_asset.return_value.check.assert_called_once_with([])
|
||||
mock_asset.return_value.is_duplicate.assert_called_once_with([])
|
||||
mock_asset.assert_called_once_with("UserFile", [])
|
||||
|
||||
mock_asset.return_value.check.return_value = False
|
||||
Assets.add_asset(self.mock_self, "/test_path/my_asset", "layout")
|
||||
mock_asset.return_value.is_duplicate.return_value = False
|
||||
Assets.add_asset(self.mock_self, "/test_path/my_asset", "ui", ("layout", "scroll"))
|
||||
self.assertEqual(self.mock_self.refs, {'Additional':[mock.ANY]})
|
||||
mock_asset.return_value.display.assert_called_with("layout")
|
||||
mock_asset.return_value.display.assert_called_with("ui", "layout", "scroll")
|
||||
|
||||
def test_get_pathmaps(self):
|
||||
|
||||
self.mock_self.pathmaps = []
|
||||
maps = Assets.get_pathmaps(self.mock_self)
|
||||
self.assertEqual(maps, '{"PathMaps": []}')
|
||||
self.assertEqual(maps, {"PathMaps": []})
|
||||
|
||||
self.mock_self.pathmaps = ["test", "", "test", None, 0, 5, "", "test"]
|
||||
maps = Assets.get_pathmaps(self.mock_self)
|
||||
self.assertEqual(maps, '{"PathMaps": ["test", "5"]}')
|
||||
self.assertEqual(maps, {"PathMaps": ["test", "5"]})
|
||||
|
||||
|
||||
class TestBatchAppsAssets(unittest.TestCase):
|
||||
|
@ -312,39 +471,46 @@ class TestBatchAppsAssets(unittest.TestCase):
|
|||
|
||||
|
||||
@mock.patch.object(BatchAppsAssets, "collect_modules")
|
||||
@mock.patch("assets.callback")
|
||||
@mock.patch("assets.AssetsUI")
|
||||
def test_create_batchappsassets(self, mock_ui, mock_collect):
|
||||
def test_create_batchappsassets(self, mock_ui, mock_call, mock_collect):
|
||||
|
||||
assets = BatchAppsAssets("frame", "call")
|
||||
mock_ui.assert_called_with(assets, "frame")
|
||||
mock_collect.assert_called_with()
|
||||
mock_call.after_new.assert_called_with(assets.callback_refresh)
|
||||
mock_call.after_read.assert_called_with(assets.callback_refresh)
|
||||
|
||||
#@mock.patch("assets.Utils")
|
||||
#def test_start(self, mock_utils):
|
||||
def test_callback_refresh(self):
|
||||
|
||||
# self.mock_self.ui = mock.create_autospec(AssetsUI)
|
||||
# started = BatchAppsAssets.start(self.mock_self)
|
||||
self.mock_self.ui = mock.create_autospec(AssetsUI)
|
||||
self.mock_self.frame = mock.Mock()
|
||||
self.mock_self.frame.selected_tab = lambda: 1
|
||||
self.mock_self.ui.ready = False
|
||||
|
||||
# self.assertTrue(started)
|
||||
# self.mock_self.ui.refresh.assert_called_with()
|
||||
BatchAppsAssets.callback_refresh(self.mock_self)
|
||||
self.assertEqual(self.mock_self.ui.refresh.call_count, 0)
|
||||
self.assertFalse(self.mock_self.ui.ready)
|
||||
|
||||
# self.mock_self.ui.refresh.side_effect = Exception("woops!")
|
||||
# started = BatchAppsAssets.start(self.mock_self)
|
||||
self.mock_self.ui.ready = True
|
||||
BatchAppsAssets.callback_refresh(self.mock_self)
|
||||
self.assertEqual(self.mock_self.ui.refresh.call_count, 0)
|
||||
self.assertFalse(self.mock_self.ui.ready)
|
||||
|
||||
# self.assertFalse(started)
|
||||
# mock_utils.error_dialog.assert_called_with("Error starting Assets UI: woops!")
|
||||
self.mock_self.frame.selected_tab = lambda: 3
|
||||
BatchAppsAssets.callback_refresh(self.mock_self)
|
||||
self.assertEqual(self.mock_self.ui.refresh.call_count, 1)
|
||||
|
||||
@mock.patch("assets.Assets")
|
||||
@mock.patch("assets.FileManager")
|
||||
def test_configure(self, mock_mgr, mock_assets):
|
||||
|
||||
session = mock.Mock(credentials="creds", config="conf")
|
||||
self.mock_self.get_scene.return_value = "scene_name"
|
||||
BatchAppsAssets.configure(self.mock_self, session)
|
||||
|
||||
mock_mgr.assert_called_with("creds", "conf")
|
||||
mock_assets.assert_called_with()
|
||||
self.assertEqual(self.mock_self.scene, "scene_name")
|
||||
self.assertEqual(self.mock_self.set_searchpaths.call_count, 1)
|
||||
|
||||
def test_collect_modules(self):
|
||||
|
||||
|
@ -370,14 +536,6 @@ class TestBatchAppsAssets(unittest.TestCase):
|
|||
BatchAppsAssets.configure_renderer(self.mock_self)
|
||||
self.assertEqual(self.mock_self.renderer, renderer)
|
||||
|
||||
@mock.patch("assets.Assets")
|
||||
def test_refresh_asssets(self, mock_assets):
|
||||
|
||||
BatchAppsAssets.refresh_assets(self.mock_self)
|
||||
mock_assets.assert_called_with()
|
||||
self.mock_self.get_scene.assert_called_with()
|
||||
self.mock_self.set_assets.assert_called_with()
|
||||
|
||||
def test_set_assets(self):
|
||||
|
||||
self.mock_self.manager = "manager"
|
||||
|
@ -401,6 +559,8 @@ class TestBatchAppsAssets(unittest.TestCase):
|
|||
|
||||
def test_collect_assets(self):
|
||||
|
||||
self.mock_self.ui = mock.create_autospec(AssetsUI)
|
||||
self.mock_self.ui.ready = True
|
||||
self.mock_self.assets = mock.create_autospec(Assets)
|
||||
self.mock_self.manager = mock.create_autospec(FileManager)
|
||||
|
||||
|
@ -412,7 +572,12 @@ class TestBatchAppsAssets(unittest.TestCase):
|
|||
collection = BatchAppsAssets.collect_assets(self.mock_self, ['/test_path/file1', "c:\\test_path\\file2"])
|
||||
self.assertTrue('pathmaps' in collection)
|
||||
self.assertTrue('assets' in collection)
|
||||
self.mock_self.set_assets.assert_called_with()
|
||||
self.mock_self.assets.collect.assert_called_with()
|
||||
self.assertEqual(self.mock_self.ui.prepare.call_count, 0)
|
||||
|
||||
self.mock_self.ui.ready = False
|
||||
collection = BatchAppsAssets.collect_assets(self.mock_self, ['/test_path/file1', "c:\\test_path\\file2"])
|
||||
self.mock_self.ui.prepare.assert_called_with()
|
||||
|
||||
def test_get_assets(self):
|
||||
|
||||
|
@ -425,25 +590,66 @@ class TestBatchAppsAssets(unittest.TestCase):
|
|||
assets = BatchAppsAssets.get_assets(self.mock_self, "Test")
|
||||
self.assertEqual(assets, ["file1", "file2"])
|
||||
|
||||
@mock.patch("assets.SYS_SEARCHPATHS")
|
||||
@mock.patch("assets.maya")
|
||||
def test_set_searchpaths(self, mock_maya, mock_syspaths):
|
||||
|
||||
mock_maya.file.return_value = "testscene.mb"
|
||||
mock_maya.workspace.return_value = "/test/directory"
|
||||
mock_syspaths = ["a", "b", "c"]
|
||||
|
||||
paths = BatchAppsAssets.set_searchpaths(self.mock_self)
|
||||
self.assertEqual(sorted(paths), ["/test/directory", "/test/directory\\sourceimages", os.getcwd()])
|
||||
|
||||
|
||||
def test_add_files(self):
|
||||
|
||||
self.mock_self.ui = mock.Mock()
|
||||
self.mock_self.assets = mock.create_autospec(Assets)
|
||||
BatchAppsAssets.add_files(self.mock_self, ["a", "b"], "layout")
|
||||
self.mock_self.assets.add_asset.assert_any_call("a", "layout")
|
||||
self.mock_self.assets.add_asset.assert_any_call("b", "layout")
|
||||
self.mock_self.assets.add_asset.assert_any_call("a", self.mock_self.ui, "layout")
|
||||
self.mock_self.assets.add_asset.assert_any_call("b", self.mock_self.ui, "layout")
|
||||
|
||||
def test_add_dir(self):
|
||||
|
||||
test_dir = os.path.join(os.path.dirname(__file__), "data")
|
||||
self.mock_self.assets = mock.create_autospec(Assets)
|
||||
self.mock_self.ui = mock.Mock()
|
||||
|
||||
BatchAppsAssets.add_dir(self.mock_self, [test_dir], "layout")
|
||||
self.mock_self.assets.add_asset.assert_any_call(os.path.join(test_dir, "modules", "default.py"), "layout")
|
||||
self.mock_self.assets.add_asset.assert_any_call(os.path.join(test_dir, "modules", "default.py"), self.mock_self.ui, "layout")
|
||||
self.assertTrue(self.mock_self.assets.add_asset.call_count >= 4)
|
||||
|
||||
def test_upload_items(self):
|
||||
|
||||
mock_file1 = mock.Mock()
|
||||
mock_file2 = mock.Mock()
|
||||
to_upload = [mock_file1]
|
||||
self.mock_self.assets = mock.create_autospec(Assets)
|
||||
self.mock_self.assets.refs = {'Test':[mock_file1, mock_file2]}
|
||||
prog = mock.create_autospec(ProgressBar)
|
||||
prog.is_cancelled.return_value = False
|
||||
|
||||
c = BatchAppsAssets.upload_items(self.mock_self, to_upload, prog, 1234)
|
||||
self.assertFalse(c)
|
||||
|
||||
prog.is_cancelled.return_value = True
|
||||
c = BatchAppsAssets.upload_items(self.mock_self, to_upload, prog, 1234)
|
||||
self.assertTrue(c)
|
||||
|
||||
@mock.patch("assets.ProgressBar")
|
||||
@mock.patch("assets.maya")
|
||||
def test_upload(self, mock_maya, mock_prog):
|
||||
|
||||
self.mock_self.ui = mock.create_autospec(AssetsUI)
|
||||
self.mock_self.ui.upload_button = mock.create_autospec(ProcButton)
|
||||
self.mock_self.assets = mock.create_autospec(Assets)
|
||||
BatchAppsAssets.upload(self.mock_self)
|
||||
|
||||
|
||||
class TestAssetsCombined(unittest.TestCase):
|
||||
|
||||
@mock.patch("assets.callback")
|
||||
@mock.patch("ui_assets.utils")
|
||||
@mock.patch("ui_assets.maya")
|
||||
@mock.patch("assets.maya")
|
||||
|
@ -451,6 +657,7 @@ class TestAssetsCombined(unittest.TestCase):
|
|||
|
||||
mock_maya = args[0]
|
||||
mock_maya.file.return_value = "/test_path/test_scene.mb"
|
||||
mock_maya.workspace.return_value = "/test/project"
|
||||
|
||||
mock_uimaya = args[1]
|
||||
mock_uimaya.file_select.return_value = [os.path.join(os.path.dirname(__file__), "data", "star.png")]
|
||||
|
@ -472,17 +679,18 @@ class TestAssetsCombined(unittest.TestCase):
|
|||
session = mock.Mock(credentials=creds, config=conf)
|
||||
|
||||
assets.configure(session)
|
||||
self.assertEqual(assets.scene, "")
|
||||
#self.assertEqual(assets.scene, "")
|
||||
|
||||
mock_maya.file.return_value = os.path.join(os.path.dirname(__file__), "data", "empty.mb")
|
||||
assets.configure(session)
|
||||
self.assertTrue(assets.scene.endswith("empty.mb"))
|
||||
#self.assertTrue(assets.scene.endswith("empty.mb"))
|
||||
|
||||
assets.ui.prepare()
|
||||
self.assertTrue(assets.ui.ready)
|
||||
self.assertEqual(assets.assets.refs, {'Additional':[],
|
||||
'Caches':[],
|
||||
'Files':[]})
|
||||
'Files':[],
|
||||
'References':[]})
|
||||
|
||||
files = assets.get_assets("Caches")
|
||||
self.assertEqual(files, [])
|
||||
|
@ -498,38 +706,31 @@ class TestAssetsCombined(unittest.TestCase):
|
|||
self.assertFalse(asset in asset.parent_list)
|
||||
|
||||
check_path = mock.Mock(path=mock_uimaya.file_select.return_value[0])
|
||||
self.assertTrue(asset.check([check_path]))
|
||||
self.assertTrue(asset.is_duplicate([check_path]))
|
||||
check_path.path = "/test_file"
|
||||
self.assertFalse(asset.check([check_path]))
|
||||
self.assertFalse(asset.is_duplicate([check_path]))
|
||||
|
||||
asset.delete()
|
||||
|
||||
files = assets.get_assets("Additional")
|
||||
self.assertEqual(len(files), 0)
|
||||
self.assertEqual(len(assets.assets.pathmaps), 1)
|
||||
|
||||
assets.ui.add_asset()
|
||||
files = assets.get_assets("Additional")
|
||||
self.assertEqual(len(files), 1)
|
||||
self.assertEqual(len(assets.assets.pathmaps), 2)
|
||||
|
||||
collected = assets.collect_assets([])
|
||||
self.assertTrue(collected.get('assets'))
|
||||
self.assertTrue(collected.get('pathmaps'))
|
||||
decoded = collected.get('pathmaps')['PathMaps']
|
||||
self.assertEqual(len(decoded), 1)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
assets.ui.refresh()
|
||||
files = assets.get_assets("Additional")
|
||||
self.assertEqual(len(files), 0)
|
||||
self.assertEqual(len(assets.assets.pathmaps), 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
|
@ -0,0 +1,337 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
import json
|
||||
|
||||
try:
|
||||
import unittest2 as unittest
|
||||
except ImportError:
|
||||
import unittest
|
||||
|
||||
try:
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
import mock
|
||||
|
||||
from ui_environment import EnvironmentUI
|
||||
from environment import BatchAppsEnvironment, BatchPlugin
|
||||
|
||||
|
||||
class TestPlugin(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.mock_self = mock.create_autospec(BatchPlugin)
|
||||
return super(TestPlugin, self).setUp()
|
||||
|
||||
@mock.patch.object(BatchPlugin, "create_checkbox")
|
||||
def test_create(self, mock_checkbox):
|
||||
|
||||
plugin = BatchPlugin("base", "plugin", "False", [])
|
||||
mock_checkbox.assert_called_with()
|
||||
|
||||
with self.assertRaises(AttributeError):
|
||||
v = plugin.license_var
|
||||
|
||||
self.assertEqual(plugin.label, "plugin")
|
||||
self.assertFalse(plugin.license)
|
||||
self.assertFalse(plugin.supported)
|
||||
|
||||
plugin = BatchPlugin("base", "plugin", "False", [{"name":"My Plugin", "license":False}])
|
||||
self.assertEqual(plugin.label, "My Plugin")
|
||||
self.assertFalse(plugin.license)
|
||||
self.assertTrue(plugin.supported)
|
||||
self.assertEqual(plugin.license_var, {})
|
||||
|
||||
@mock.patch("environment.maya")
|
||||
def test_create_checkbox(self, mock_maya):
|
||||
self.mock_self.license = False
|
||||
self.mock_self.base = mock.create_autospec(BatchAppsEnvironment)
|
||||
self.mock_self.base.ui = mock.create_autospec(EnvironmentUI)
|
||||
self.mock_self.base.ui.plugin_layout = "layout"
|
||||
self.mock_self.supported = False
|
||||
self.mock_self.label = "plugin"
|
||||
self.mock_self.contents = []
|
||||
|
||||
BatchPlugin.create_checkbox(self.mock_self)
|
||||
self.assertEqual(len(self.mock_self.contents), 2)
|
||||
mock_maya.check_box.assert_called_with(label="Unsupported: plugin", value=False, onCommand=self.mock_self.include,
|
||||
offCommand=self.mock_self.exclude, parent="layout", enable=False)
|
||||
mock_maya.text.assert_called_with(label="", parent="layout")
|
||||
|
||||
self.mock_self.license = True
|
||||
self.mock_self.contents = []
|
||||
BatchPlugin.create_checkbox(self.mock_self)
|
||||
self.assertEqual(len(self.mock_self.contents), 4)
|
||||
|
||||
@mock.patch("environment.maya")
|
||||
def test_is_used(self, mock_maya):
|
||||
|
||||
used = ["plugin_A", "plugin_B"]
|
||||
self.mock_self.base = mock.create_autospec(BatchAppsEnvironment)
|
||||
self.mock_self.base.plugins = []
|
||||
self.mock_self.base.warnings = []
|
||||
self.mock_self.plugin = "plugin_C.py"
|
||||
self.mock_self.label = "plugin"
|
||||
self.mock_self.supported = False
|
||||
self.mock_self.loaded = False
|
||||
self.mock_self.license = False
|
||||
self.mock_self.checkbox = "checkbox"
|
||||
self.mock_self.license_check = "license"
|
||||
BatchPlugin.is_used(self.mock_self, used)
|
||||
self.assertFalse(self.mock_self.used)
|
||||
self.assertEqual(self.mock_self.base.plugins, [])
|
||||
self.assertEqual(self.mock_self.base.warnings, [])
|
||||
|
||||
self.mock_self.loaded = True
|
||||
BatchPlugin.is_used(self.mock_self, used)
|
||||
self.assertFalse(self.mock_self.used)
|
||||
self.assertEqual(self.mock_self.base.plugins, [])
|
||||
self.assertEqual(self.mock_self.base.warnings, [])
|
||||
|
||||
self.mock_self.plugin = "plugin_A.py"
|
||||
BatchPlugin.is_used(self.mock_self, used)
|
||||
self.assertTrue(self.mock_self.used)
|
||||
self.assertEqual(mock_maya.check_box.call_count, 0)
|
||||
self.assertEqual(self.mock_self.base.plugins, [])
|
||||
self.assertEqual(self.mock_self.base.warnings, ["plugin_A.py"])
|
||||
|
||||
self.mock_self.supported = True
|
||||
self.mock_self.base.warnings = []
|
||||
BatchPlugin.is_used(self.mock_self, used)
|
||||
self.assertTrue(self.mock_self.used)
|
||||
mock_maya.check_box.assert_called_with("checkbox", edit=True, value=True)
|
||||
self.assertEqual(self.mock_self.base.plugins, ["plugin"])
|
||||
self.assertEqual(self.mock_self.base.warnings, [])
|
||||
|
||||
self.mock_self.license = True
|
||||
BatchPlugin.is_used(self.mock_self, used)
|
||||
mock_maya.check_box.assert_called_with("license", edit=True, enable=True)
|
||||
|
||||
@mock.patch("environment.maya")
|
||||
def test_use_license(self, mock_maya):
|
||||
|
||||
self.mock_self.license = False
|
||||
self.mock_self.custom_license_endp = "endp"
|
||||
self.mock_self.custom_license_port = "port"
|
||||
BatchPlugin.use_license(self.mock_self, True)
|
||||
self.assertEqual(mock_maya.text_field.call_count, 0)
|
||||
|
||||
self.mock_self.license = True
|
||||
BatchPlugin.use_license(self.mock_self, True)
|
||||
self.assertEqual(mock_maya.text_field.call_count, 2)
|
||||
mock_maya.text_field.assert_called_with("port", edit=True, enable=True)
|
||||
|
||||
@mock.patch("environment.maya")
|
||||
def test_include(self, mock_maya):
|
||||
|
||||
self.mock_self.base = mock.create_autospec(BatchAppsEnvironment)
|
||||
self.mock_self.base.plugins = []
|
||||
self.mock_self.label = "My Plugin"
|
||||
self.mock_self.license = False
|
||||
self.mock_self.license_check = "check"
|
||||
BatchPlugin.include(self.mock_self)
|
||||
self.assertEqual(self.mock_self.base.plugins, ["My Plugin"])
|
||||
self.assertEqual(mock_maya.check_box.call_count, 0)
|
||||
|
||||
self.mock_self.license = True
|
||||
BatchPlugin.include(self.mock_self)
|
||||
self.assertEqual(self.mock_self.base.plugins, ["My Plugin", "My Plugin"])
|
||||
mock_maya.check_box.assert_called_with("check", edit=True, enable=True)
|
||||
|
||||
@mock.patch("environment.maya")
|
||||
def test_exclude(self, mock_maya):
|
||||
|
||||
self.mock_self.base = mock.create_autospec(BatchAppsEnvironment)
|
||||
self.mock_self.base.plugins = ["My Plugin"]
|
||||
self.mock_self.label = "My Plugin"
|
||||
self.mock_self.license = False
|
||||
self.mock_self.license_check = "check"
|
||||
self.mock_self.used = False
|
||||
|
||||
BatchPlugin.exclude(self.mock_self)
|
||||
self.assertEqual(mock_maya.warning.call_count, 0)
|
||||
self.assertEqual(mock_maya.check_box.call_count, 0)
|
||||
self.assertEqual(self.mock_self.base.plugins, [])
|
||||
|
||||
self.mock_self.license = True
|
||||
BatchPlugin.exclude(self.mock_self)
|
||||
self.assertEqual(mock_maya.warning.call_count, 0)
|
||||
mock_maya.check_box.assert_called_with("check", edit=True, enable=False)
|
||||
self.assertEqual(self.mock_self.base.plugins, [])
|
||||
|
||||
self.mock_self.used = True
|
||||
BatchPlugin.exclude(self.mock_self)
|
||||
mock_maya.warning.assert_called_with(mock.ANY)
|
||||
|
||||
@mock.patch("environment.maya")
|
||||
def test_delete(self, mock_maya):
|
||||
|
||||
self.mock_self.contents = ["a"]
|
||||
BatchPlugin.delete(self.mock_self)
|
||||
mock_maya.delete_ui.assert_called_with("a", control=True)
|
||||
|
||||
@mock.patch("environment.maya")
|
||||
def test_get_vaiables(self, mock_maya):
|
||||
|
||||
mock_maya.check_box.return_value = True
|
||||
self.mock_self.license = False
|
||||
self.mock_self.license_check = "check"
|
||||
self.mock_self.license_var = {"key":"solidangle_LICENSE", "value":"{port}@{host}"}
|
||||
self.mock_self.custom_license_endp = "host"
|
||||
self.mock_self.custom_license_port = "port"
|
||||
vars = BatchPlugin.get_variables(self.mock_self)
|
||||
self.assertEqual(vars, {})
|
||||
self.assertEqual(mock_maya.text_field.call_count, 0)
|
||||
|
||||
self.mock_self.license = True
|
||||
mock_maya.text_field.return_value = "blah"
|
||||
vars = BatchPlugin.get_variables(self.mock_self)
|
||||
self.assertEqual(vars, {"solidangle_LICENSE":"blah@blah"})
|
||||
self.assertEqual(mock_maya.text_field.call_count, 2)
|
||||
|
||||
|
||||
class TestBatchAppsEnvironment(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.mock_self = mock.create_autospec(BatchAppsEnvironment)
|
||||
self.mock_self._log = logging.getLogger("TestEnvironment")
|
||||
self.mock_self._server_plugins = []
|
||||
self.mock_self.warnings = []
|
||||
self.mock_self._plugins = []
|
||||
self.mock_self._version = "2017"
|
||||
return super(TestBatchAppsEnvironment, self).setUp()
|
||||
|
||||
@mock.patch.object(BatchAppsEnvironment, "refresh")
|
||||
@mock.patch("environment.EnvironmentUI")
|
||||
@mock.patch("environment.callback")
|
||||
def test_create(self, mock_call, mock_ui, mock_refresh):
|
||||
|
||||
env = BatchAppsEnvironment("frame", "call")
|
||||
mock_ui.assert_called_with(env, "frame", ["Maya I/O PR55"])
|
||||
mock_call.after_new.assert_called_with(mock.ANY)
|
||||
mock_call.after_read.assert_called_with(mock.ANY)
|
||||
mock_refresh.assert_called_with()
|
||||
|
||||
self.assertEqual(env.plugins, [])
|
||||
self.assertEqual(env.version, "2017")
|
||||
lic = env.license
|
||||
env.ui.get_license_server.assert_called_with()
|
||||
vars = env.environment_variables
|
||||
env.ui.get_env_vars.assert_called_with()
|
||||
|
||||
def test_configure(self):
|
||||
|
||||
BatchAppsEnvironment.configure(self.mock_self, "session")
|
||||
self.assertEqual(self.mock_self._session, "session")
|
||||
|
||||
@mock.patch("environment.BatchPlugin")
|
||||
@mock.patch("environment.maya")
|
||||
def test_get_plugins(self, mock_maya, mock_plugin):
|
||||
|
||||
mock_maya.plugins.return_value = ["mtoa.mll", "b", "c"]
|
||||
self.mock_self.search_for_plugins.return_value = ["Mayatomr.mll", "mtoa.mll", "c", "d", "e"]
|
||||
mock_plugin.return_value = mock.create_autospec(BatchPlugin)
|
||||
plugins = BatchAppsEnvironment.get_plugins(self.mock_self)
|
||||
|
||||
mock_maya.plugins.assert_any_call('e', query=True, loaded=True)
|
||||
self.mock_self.search_for_plugins.assert_called_with()
|
||||
|
||||
mock_plugin.assert_any_call(mock.ANY, "mtoa.mll", ["mtoa.mll", "b", "c"],
|
||||
[{"name": "Arnold", "plugin": "mtoa.", "license": True, "license_var": {"key":"solidangle_LICENSE", "value":"{port}@{host}"}}])
|
||||
mock_plugin.assert_any_call(mock.ANY, "e", ["mtoa.mll", "b", "c"], [])
|
||||
mock_plugin.assert_any_call(mock.ANY, "Mayatomr.mll", ["mtoa.mll", "b", "c"],
|
||||
[{"name": "MentalRay", "plugin": "Mayatomr.", "license": False}])
|
||||
mock_plugin.return_value.is_used.assert_called_with(["mtoa.mll", "b", "c"])
|
||||
self.assertEqual(len(plugins), 5)
|
||||
|
||||
self.mock_self.warnings = ["a","b","c"]
|
||||
plugins = BatchAppsEnvironment.get_plugins(self.mock_self)
|
||||
mock_maya.warning.assert_called_with("The following plug-ins are used in the scene, but not yet supported.\nRendering may be affected.\na\nb\nc\n")
|
||||
|
||||
@mock.patch("environment.os")
|
||||
def test_search_for_plugins(self, mock_os):
|
||||
|
||||
self.mock_self.is_default = lambda a: BatchAppsEnvironment.is_default(self.mock_self, a)
|
||||
mock_os.path.splitext = os.path.splitext
|
||||
mock_os.environ = {"MAYA_PLUG_IN_PATH":"dir1;dir2;dir3"}
|
||||
mock_os.pathsep = ';'
|
||||
mock_os.path.isdir.return_value = True
|
||||
mock_os.listdir.return_value = ["a","b","test.mll","test.mll","plugin.py", "xgenMR.py", "fbxmaya.mll"]
|
||||
plugins = BatchAppsEnvironment.search_for_plugins(self.mock_self)
|
||||
self.assertEqual(sorted(plugins), sorted(["test.mll", "plugin.py"]))
|
||||
|
||||
def test_set_version(self):
|
||||
|
||||
BatchAppsEnvironment.set_version(self.mock_self, "Maya 2015")
|
||||
self.assertEqual(self.mock_self._version, "2017")
|
||||
BatchAppsEnvironment.set_version(self.mock_self, "test")
|
||||
self.assertEqual(self.mock_self._version, "2017")
|
||||
|
||||
def test_refresh(self):
|
||||
self.mock_self._server_plugins = [1,2,3]
|
||||
self.mock_self.warnings = [4,5,6]
|
||||
mock_plugin = mock.create_autospec(BatchPlugin)
|
||||
self.mock_self._plugins = [mock_plugin]
|
||||
|
||||
BatchAppsEnvironment.refresh(self.mock_self)
|
||||
self.assertEqual(self.mock_self._server_plugins, [])
|
||||
self.assertEqual(self.mock_self.warnings, [])
|
||||
self.mock_self.get_plugins.assert_called_with()
|
||||
mock_plugin.delete.assert_called_with()
|
||||
|
||||
|
||||
class TestEnvironmentCombined(unittest.TestCase):
|
||||
|
||||
@mock.patch("ui_environment.utils")
|
||||
@mock.patch("ui_environment.maya")
|
||||
@mock.patch("environment.callback")
|
||||
@mock.patch("environment.maya")
|
||||
def test_environment(self, *args):
|
||||
|
||||
os.environ["MAYA_PLUG_IN_PATH"] = os.path.join(os.path.dirname(__file__), "data", "modules")
|
||||
|
||||
def add_tab(tab):
|
||||
self.assertFalse(tab.ready)
|
||||
|
||||
def call(func, *args, **kwargs):
|
||||
self.assertTrue(hasattr(func, '__call__'))
|
||||
return func(*args, **kwargs)
|
||||
|
||||
layout = mock.Mock(add_tab=add_tab)
|
||||
env = BatchAppsEnvironment(layout, call)
|
||||
|
||||
env.configure("session")
|
||||
self.assertEqual(env.plugins, [])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -47,6 +47,7 @@ from history import BatchAppsHistory
|
|||
from batchapps import JobManager, Configuration, Credentials
|
||||
from batchapps.job import SubmittedJob, JobSubmission, Task
|
||||
from batchapps.exceptions import FileDownloadException
|
||||
from utils import ProcButton
|
||||
|
||||
class TestBatchAppsHistory(unittest.TestCase):
|
||||
|
||||
|
@ -188,8 +189,28 @@ class TestBatchAppsHistory(unittest.TestCase):
|
|||
BatchAppsHistory.show_last_jobs(self.mock_self)
|
||||
self.assertEqual(self.mock_self.index, 10)
|
||||
|
||||
def test_job_selected(self):
|
||||
|
||||
ui_job = mock.create_autospec(BatchAppsJobInfo)
|
||||
ui_job.index = 0
|
||||
|
||||
self.mock_self.selected_job = None
|
||||
BatchAppsHistory.job_selected(self.mock_self, None)
|
||||
|
||||
BatchAppsHistory.job_selected(self.mock_self, ui_job)
|
||||
self.assertEqual(self.mock_self.selected_job, ui_job)
|
||||
self.mock_self.update_job.assert_called_with(0)
|
||||
|
||||
BatchAppsHistory.job_selected(self.mock_self, ui_job)
|
||||
ui_job.collapse.assert_called_with()
|
||||
|
||||
self.mock_self.selected_job = None
|
||||
self.mock_self.jobs = []
|
||||
BatchAppsHistory.job_selected(self.mock_self, ui_job)
|
||||
ui_job.collapse.assert_called_with()
|
||||
|
||||
@mock.patch("history.maya")
|
||||
def test_job_selected(self, mock_maya):
|
||||
def test_update_job(self, mock_maya):
|
||||
|
||||
job = mock.create_autospec(SubmittedJob)
|
||||
job.status = "Running"
|
||||
|
@ -203,28 +224,18 @@ class TestBatchAppsHistory(unittest.TestCase):
|
|||
|
||||
ui_job = mock.create_autospec(BatchAppsJobInfo)
|
||||
ui_job.index = 0
|
||||
self.mock_self.selected_job = ui_job
|
||||
|
||||
def call(func):
|
||||
self.assertTrue(hasattr(func, '__call__'))
|
||||
|
||||
self.mock_self._call = call
|
||||
self.mock_self.selected_job = None
|
||||
BatchAppsHistory.job_selected(self.mock_self, None)
|
||||
|
||||
BatchAppsHistory.job_selected(self.mock_self, ui_job)
|
||||
self.assertEqual(self.mock_self.selected_job, ui_job)
|
||||
BatchAppsHistory.update_job(self.mock_self, 0)
|
||||
ui_job.set_thumbnail.assert_called_with(os.path.join(
|
||||
os.environ["BATCHAPPS_ICONS"], "loading_preview.png"), 24)
|
||||
ui_job.set_status.assert_called_with("Running")
|
||||
|
||||
BatchAppsHistory.job_selected(self.mock_self, ui_job)
|
||||
ui_job.collapse.assert_called_with()
|
||||
|
||||
self.mock_self.selected_job = None
|
||||
self.mock_self.jobs = []
|
||||
BatchAppsHistory.job_selected(self.mock_self, ui_job)
|
||||
ui_job.collapse.assert_called_with()
|
||||
|
||||
def test_get_thumb(self):
|
||||
|
||||
job = mock.create_autospec(SubmittedJob)
|
||||
|
@ -376,18 +387,19 @@ class TestBatchAppsHistory(unittest.TestCase):
|
|||
func(dir, overwrite)
|
||||
return os.path.join(data_dir, "output.zip")
|
||||
|
||||
self.mock_self.ui = mock.create_autospec(HistoryUI)
|
||||
self.mock_self.ui.refresh_button = mock.create_autospec(ProcButton)
|
||||
self.mock_self.jobs = [job]
|
||||
self.mock_self._call = call
|
||||
self.mock_self.selected_job = mock.create_autospec(BatchAppsJobInfo)
|
||||
self.mock_self.selected_job.index = 0
|
||||
|
||||
BatchAppsHistory.download_output(self.mock_self, selected_file)
|
||||
job.get_output.assert_called_with(data_dir, True)
|
||||
job.get_output.assert_called_with(data_dir, overwrite=True, callback=mock.ANY, block=100000)
|
||||
|
||||
job.get_output.side_effect = FileDownloadException("Failed!")
|
||||
BatchAppsHistory.download_output(self.mock_self, selected_file)
|
||||
self.mock_self.selected_job.change_download_label.assert_called_with(
|
||||
"Download Output")
|
||||
self.mock_self.ui.refresh_button.finish.assert_called_with()
|
||||
|
||||
job.get_output.call_count = 0
|
||||
job.get_output.side_effect = None
|
||||
|
@ -400,14 +412,12 @@ class TestBatchAppsHistory(unittest.TestCase):
|
|||
|
||||
mock_os.remove.side_effect = EnvironmentError("Couldn't delete...")
|
||||
BatchAppsHistory.download_output(self.mock_self, selected_file)
|
||||
self.mock_self.selected_job.change_download_label.assert_called_with(
|
||||
"Download Output")
|
||||
self.mock_self.ui.refresh_button.finish.assert_called_with()
|
||||
self.assertEqual(job.get_output.call_count, 1)
|
||||
|
||||
mock_util.rmtree.side_effect = Exception("Couldn't move to new location")
|
||||
BatchAppsHistory.download_output(self.mock_self, selected_file)
|
||||
self.mock_self.selected_job.change_download_label.assert_called_with(
|
||||
"Download Output")
|
||||
self.mock_self.ui.refresh_button.finish.assert_called_with()
|
||||
mock_util.rmtree.assert_called_with(data_dir)
|
||||
|
||||
def test_image_height(self):
|
|
@ -0,0 +1,230 @@
|
|||
try:
|
||||
import unittest2 as unittest
|
||||
except ImportError:
|
||||
import unittest
|
||||
|
||||
try:
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
import mock
|
||||
|
||||
import os, sys
|
||||
|
||||
from batchapps import (
|
||||
JobManager,
|
||||
Configuration)
|
||||
|
||||
from batchapps.job import (
|
||||
SubmittedJob,
|
||||
Task,
|
||||
JobSubmission)
|
||||
|
||||
from batchapps.exceptions import (
|
||||
RestCallException,
|
||||
AuthenticationException,
|
||||
InvalidConfigException)
|
||||
|
||||
with mock.patch.object(sys, "argv"):
|
||||
import job_watcher as client
|
||||
|
||||
class TestJobWatcher(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.cwd = os.path.dirname(os.path.abspath(__file__))
|
||||
return super(TestJobWatcher, self).setUp()
|
||||
|
||||
def test_check_valid_dir(self):
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
client._check_valid_dir(None)
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
client._check_valid_dir("test")
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
client._check_valid_dir(1)
|
||||
|
||||
self.assertEqual(client._check_valid_dir(self.cwd), self.cwd)
|
||||
|
||||
@mock.patch.object(client, "_check_valid_dir")
|
||||
@mock.patch('job_watcher.os')
|
||||
def test_download_tasks_outputs(self, mock_os, mock_check_valid_dir):
|
||||
|
||||
client._download_task_outputs(None, [], None)
|
||||
self.assertFalse(mock_os.path.join.called, "Output list is empty")
|
||||
|
||||
mock_task = mock.create_autospec(Task)
|
||||
mock_outputs = [{'type': 'TaskOutput', 'name': 'test.png'}]
|
||||
mock_check_valid_dir.return_value = "test_dir"
|
||||
mock_os.path.isfile.return_value = False
|
||||
|
||||
client._download_task_outputs(mock_task, mock_outputs, "test_dir")
|
||||
mock_os.path.join.assert_called_with("test_dir", "test.png")
|
||||
mock_task.get_output.assert_called_with({'type': 'TaskOutput', 'name': 'test.png'}, "test_dir",
|
||||
callback=mock.ANY, block=100000)
|
||||
|
||||
@mock.patch.object(client, '_download_task_outputs')
|
||||
def test_track_completed_tasks(self, mock_download_task_outputs):
|
||||
|
||||
mock_job = mock.create_autospec(SubmittedJob)
|
||||
|
||||
mock_job.get_tasks.side_effect = RestCallException("RestCallException", "RestCallExceptionRaised", None)
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
client._track_completed_tasks(mock_job, "test_dir")
|
||||
|
||||
mock_job.get_tasks.side_effect = None
|
||||
mock_job.get_tasks.return_value = [1, 2, 3]
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
client._track_completed_tasks(mock_job, "test_dir")
|
||||
|
||||
mock_task1 = mock.create_autospec(Task)
|
||||
mock_task1.status = "Complete"
|
||||
mock_task1.id = 1
|
||||
mock_task1.outputs = []
|
||||
mock_task2 = mock.create_autospec(Task)
|
||||
mock_task2.status = "Complete"
|
||||
mock_task2.id = 2
|
||||
mock_task2.outputs = []
|
||||
|
||||
mock_job.number_tasks = 3
|
||||
|
||||
mock_job.get_tasks.return_value = [mock_task1, mock_task2]
|
||||
|
||||
client._track_completed_tasks(mock_job, "test_dir")
|
||||
|
||||
def test_retrieve_logs(self):
|
||||
|
||||
mock_job = mock.create_autospec(SubmittedJob)
|
||||
|
||||
client._retrieve_logs(mock_job)
|
||||
|
||||
mock_job.get_logs.side_effect = RestCallException("RestCallException", "RestCalleExceptionRaised", None)
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
client._retrieve_logs(mock_job)
|
||||
|
||||
mock_job.get_logs.side_effect = None
|
||||
mock_job.get_logs.return_value = {}
|
||||
|
||||
self.assertIsNone(client._retrieve_logs(mock_job))
|
||||
|
||||
mock_job.get_logs.return_value = {'upTo': None}
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
client._retrieve_logs(mock_job)
|
||||
|
||||
mock_job.get_logs.return_value = {'upTo': None,
|
||||
'messages': [{'timestamp': '1800-09-23bla',
|
||||
'text': 'This is a test message',
|
||||
'taskId': 2}]}
|
||||
self.assertTrue(mock_job.get_logs()['messages'])
|
||||
|
||||
@mock.patch.object(client, "_retrieve_logs")
|
||||
def test_check_job_stopped(self, mock_retrieve_logs):
|
||||
|
||||
mock_job = mock.create_autospec(SubmittedJob)
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
client._check_job_stopped(mock_job)
|
||||
|
||||
mock_job.status = "test"
|
||||
client._check_job_stopped(mock_job)
|
||||
|
||||
mock_job.status = "Error"
|
||||
with self.assertRaises(RuntimeError):
|
||||
client._check_job_stopped(mock_job)
|
||||
self.assertTrue(mock_retrieve_logs.called)
|
||||
|
||||
mock_job.status = "OnHold"
|
||||
with self.assertRaises(RuntimeError):
|
||||
client._check_job_stopped(mock_job)
|
||||
|
||||
mock_job.status = "Complete"
|
||||
self.assertTrue(client._check_job_stopped(mock_job))
|
||||
|
||||
mock_job.status = "NotStarted"
|
||||
self.assertFalse(client._check_job_stopped(mock_job))
|
||||
|
||||
mock_job.status = "InProgress"
|
||||
self.assertFalse(client._check_job_stopped(mock_job))
|
||||
|
||||
@mock.patch.object(client, "_check_job_stopped")
|
||||
@mock.patch.object(client, "_track_completed_tasks")
|
||||
def test_track_job_progress(self, mock_track_completed_tasks, mock_check_job_stopped):
|
||||
|
||||
mock_job_manager = mock.create_autospec(JobManager)
|
||||
mock_id = "test_id"
|
||||
mock_dwnld_dir = "test_dir"
|
||||
mock_job = mock.create_autospec(SubmittedJob)
|
||||
mock_job.status = "test"
|
||||
mock_job_manager.get_job.return_value = mock_job
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
client.track_job_progress(mock_job_manager, mock_id, mock_dwnld_dir)
|
||||
|
||||
mock_job.status = "Error"
|
||||
mock_job.percentage = "30"
|
||||
mock_check_job_stopped.side_effect = RuntimeError("RuntimeError", "RuntimeError raised", None)
|
||||
with self.assertRaises(RuntimeError):
|
||||
client.track_job_progress(mock_job_manager, mock_id, mock_dwnld_dir)
|
||||
|
||||
mock_job.status = "InProgress"
|
||||
mock_check_job_stopped.side_effect = [False, True]
|
||||
self.assertFalse(client.track_job_progress(mock_job_manager, mock_id, mock_dwnld_dir))
|
||||
self.assertEqual(mock_job.update.call_count, 1)
|
||||
|
||||
mock_job.status = "Complete"
|
||||
mock_check_job_stopped.side_effect = None
|
||||
self.assertIsNone(client.track_job_progress(mock_job_manager, mock_id, mock_dwnld_dir), "track_job_progress returned None unexpectedly.")
|
||||
|
||||
@mock.patch("job_watcher.webbrowser")
|
||||
@mock.patch("job_watcher.AzureOAuth")
|
||||
def test_authentication(self, mock_azureoauth, mock_webbrowser):
|
||||
|
||||
|
||||
mock_azureoauth.get_unattended_session.return_value = "Auth"
|
||||
auth = client.authentication("test")
|
||||
self.assertEqual("Auth", auth)
|
||||
self.assertFalse(mock_azureoauth.get_session.called)
|
||||
|
||||
mock_azureoauth.get_unattended_session.side_effect = InvalidConfigException("InvalidConfigException", "InvalidConfigException raised", None)
|
||||
|
||||
client.authentication("test")
|
||||
self.assertTrue(mock_azureoauth.get_session.called)
|
||||
|
||||
mock_azureoauth.get_session.return_value = "Done!"
|
||||
auth = client.authentication("test")
|
||||
self.assertEqual("Done!", auth)
|
||||
mock_azureoauth.get_session.called_with(config="test")
|
||||
|
||||
mock_azureoauth.get_session.side_effect = InvalidConfigException("InvalidConfigException", "InvalidConfigException raised", None)
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
client.authentication("test")
|
||||
|
||||
mock_azureoauth.get_authorization_token.side_effect = InvalidConfigException("InvalidConfigException", "InvalidConfigExceptio Raised", None)
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
client.authentication("test")
|
||||
|
||||
mock_azureoauth.get_session.side_effect = AuthenticationException("AuthenticationException", "AuthenticationException raised", None)
|
||||
|
||||
@mock.patch("job_watcher.Configuration")
|
||||
def test_generate_config(self, mock_cfg):
|
||||
|
||||
mock_data_path = "test"
|
||||
with self.assertRaises(EnvironmentError):
|
||||
client.generate_config(mock_data_path)
|
||||
|
||||
mock_cfg.side_effect = InvalidConfigException("InvalidConfigException", "InvalidConfigException Raised", None)
|
||||
|
||||
mock_data_path = os.path.dirname(os.path.normpath(__file__))
|
||||
with self.assertRaises(ValueError):
|
||||
client.generate_config(mock_data_path)
|
||||
|
||||
mock_cfg.side_effect = None
|
||||
self.assertEqual(client.generate_config(mock_data_path), mock_cfg())
|
||||
|
||||
|
|
@ -70,18 +70,38 @@ class TestBatchAppsPools(unittest.TestCase):
|
|||
mock_mgr.assert_called_with("creds", "conf")
|
||||
self.assertEqual(session, self.mock_self._session)
|
||||
|
||||
def test_get_pools(self):
|
||||
def test_list_pools(self):
|
||||
|
||||
mgr = mock.create_autospec(PoolManager)
|
||||
mgr.get_pools.return_value = [mock.Mock(id="1234")]
|
||||
|
||||
pool1 = mock.create_autospec(Pool)
|
||||
pool1.auto = False
|
||||
pool1.id = "12345"
|
||||
|
||||
pool2 = mock.create_autospec(Pool)
|
||||
pool2.id = "67890"
|
||||
pool2.auto = True
|
||||
|
||||
mgr.get_pools.return_value = [pool1, pool2]
|
||||
mgr.__len__.return_value = 1
|
||||
self.mock_self.manager = mgr
|
||||
|
||||
def call(func):
|
||||
self.assertEqual(func, mgr.get_pools)
|
||||
return func()
|
||||
|
||||
self.mock_self.manager = mgr
|
||||
self.mock_self._call = call
|
||||
pool_list = BatchAppsPools.list_pools(self.mock_self)
|
||||
self.assertEqual(pool_list, ["12345"])
|
||||
self.assertEqual(len(self.mock_self.pools), 2)
|
||||
|
||||
def test_get_pools(self):
|
||||
|
||||
def list_pools():
|
||||
self.mock_self.pools = [mock.Mock(id="1234")]
|
||||
self.mock_self.count = 1
|
||||
|
||||
self.mock_self.list_pools = list_pools
|
||||
self.mock_self.ui = mock.create_autospec(PoolsUI)
|
||||
self.mock_self.ui.create_pool_entry.return_value = "pool_entry"
|
||||
|
||||
|
@ -272,6 +292,7 @@ class TestPoolsCombined(unittest.TestCase):
|
|||
|
||||
pool2 = mock.create_autospec(Pool)
|
||||
pool2.id = "67890"
|
||||
pool2.auto = True
|
||||
|
||||
pools.manager.get_pools.return_value = []
|
||||
pools.manager.__len__.return_value = 0
|
|
@ -54,24 +54,24 @@ class TestBatchAppsShared(unittest.TestCase):
|
|||
|
||||
return super(TestBatchAppsShared, self).setUp()
|
||||
|
||||
@mock.patch("shared.maya")
|
||||
def test_check_version(self, mock_maya):
|
||||
#@mock.patch("shared.maya")
|
||||
#def test_check_version(self, mock_maya):
|
||||
|
||||
self.mock_self.supported_versions = [2015]
|
||||
mock_maya.mel.return_value = 2015
|
||||
self.mock_self.check_version = lambda a: BatchAppsSettings.check_version(self.mock_self, a)
|
||||
# self.mock_self.supported_versions = [2015]
|
||||
# mock_maya.mel.return_value = 2015
|
||||
# self.mock_self.check_version = lambda a: BatchAppsSettings.check_version(self.mock_self, a)
|
||||
|
||||
ver = BatchAppsSettings.check_maya_version(self.mock_self)
|
||||
self.assertEqual(ver, 2015)
|
||||
self.assertFalse(mock_maya.warning.call_count)
|
||||
# ver = BatchAppsSettings.check_maya_version(self.mock_self)
|
||||
# self.assertEqual(ver, 2015)
|
||||
# self.assertFalse(mock_maya.warning.call_count)
|
||||
|
||||
mock_maya.mel.return_value = 2016
|
||||
ver = BatchAppsSettings.check_maya_version(self.mock_self)
|
||||
self.assertEqual(ver, 2015)
|
||||
self.assertTrue(mock_maya.warning.call_count)
|
||||
# mock_maya.mel.return_value = 2016
|
||||
# ver = BatchAppsSettings.check_maya_version(self.mock_self)
|
||||
# self.assertEqual(ver, 2015)
|
||||
# self.assertTrue(mock_maya.warning.call_count)
|
||||
|
||||
mock_maya.mel.return_value = 2014
|
||||
ver = BatchAppsSettings.check_maya_version(self.mock_self)
|
||||
self.assertEqual(ver, 2015)
|
||||
self.assertEqual(mock_maya.warning.call_count, 2)
|
||||
# mock_maya.mel.return_value = 2014
|
||||
# ver = BatchAppsSettings.check_maya_version(self.mock_self)
|
||||
# self.assertEqual(ver, 2015)
|
||||
# self.assertEqual(mock_maya.warning.call_count, 2)
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Batch Apps Maya Plugin
|
||||
# Azure Batch Maya Plugin
|
||||
#
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
#
|
||||
|
@ -26,181 +26,213 @@
|
|||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
try:
|
||||
import unittest2 as unittest
|
||||
except ImportError:
|
||||
import unittest
|
||||
|
||||
try:
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
import mock
|
||||
|
||||
from utils import ProgressBar
|
||||
from ui_submission import SubmissionUI
|
||||
from submission import BatchAppsSubmission, BatchAppsRenderJob
|
||||
from batchapps import JobManager, Configuration, Credentials
|
||||
from batchapps.job import SubmittedJob, JobSubmission, Task
|
||||
from batchapps.exceptions import SessionExpiredException
|
||||
from ui_shared import AzureBatchUI
|
||||
from submission import AzureBatchSubmission, AzureBatchRenderJob
|
||||
from assets import AzureBatchAssets
|
||||
from pools import AzureBatchPools
|
||||
#from environment import AzureBatchEnvironment
|
||||
|
||||
from assets import BatchAppsAssets
|
||||
from pools import BatchAppsPools
|
||||
from azure.batch_extensions import BatchExtensionsClient
|
||||
from azure.batch_extensions import operations
|
||||
from azure.batch_extensions import models
|
||||
|
||||
class TestBatchAppsSubmission(unittest.TestCase):
|
||||
def print_status(status):
|
||||
print(status)
|
||||
|
||||
class TestBatchSubmission(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.mock_self = mock.create_autospec(BatchAppsSubmission)
|
||||
test_dir = os.path.dirname(__file__)
|
||||
top_dir = os.path.dirname(test_dir)
|
||||
src_dir = os.path.join(top_dir, 'azure_batch_maya', 'scripts')
|
||||
mod_dir = os.path.join(test_dir, 'data', 'modules')
|
||||
ui_dir = os.path.join(src_dir, 'ui')
|
||||
tools_dir = os.path.join(src_dir, 'tools')
|
||||
os.environ["AZUREBATCH_ICONS"] = os.path.join(top_dir, 'azure_batch_maya', 'icons')
|
||||
os.environ["AZUREBATCH_TEMPLATES"] = os.path.join(top_dir, 'azure_batch_maya', 'templates')
|
||||
os.environ["AZUREBATCH_MODULES"] = mod_dir
|
||||
os.environ["AZUREBATCH_SCRIPTS"] = "{0};{1};{2}".format(src_dir, ui_dir, tools_dir)
|
||||
self.mock_self = mock.create_autospec(AzureBatchSubmission)
|
||||
self.mock_self.batch = mock.create_autospec(BatchExtensionsClient)
|
||||
self.mock_self.batch.job = mock.create_autospec(operations.ExtendedJobOperations)
|
||||
self.mock_self.batch.job.jobparameter_from_json.return_value = \
|
||||
mock.create_autospec(models.ExtendedJobParameter)
|
||||
self.mock_self.ui = mock.create_autospec(SubmissionUI)
|
||||
self.mock_self.ui.submit_status = print_status
|
||||
self.mock_self.pool_manager = mock.create_autospec(AzureBatchPools)
|
||||
self.mock_self.asset_manager = mock.create_autospec(AzureBatchAssets)
|
||||
self.mock_self._log = logging.getLogger("TestSubmission")
|
||||
self.mock_self.renderer = None
|
||||
self.mock_self.frame = mock.create_autospec(AzureBatchUI)
|
||||
return super(TestBatchSubmission, self).setUp()
|
||||
|
||||
return super(TestBatchAppsSubmission, self).setUp()
|
||||
|
||||
@mock.patch.object(BatchAppsSubmission, "collect_modules")
|
||||
@mock.patch.object(AzureBatchSubmission, "_collect_modules")
|
||||
@mock.patch("submission.callback")
|
||||
@mock.patch("submission.SubmissionUI")
|
||||
def test_create_batchappssubmission(self, mock_ui, mock_mods):
|
||||
|
||||
submission = BatchAppsSubmission("frame", "call")
|
||||
def test_submission_create(self, mock_ui, mock_call, mock_mods):
|
||||
submission = AzureBatchSubmission("frame", "call")
|
||||
mock_mods.assert_called_with()
|
||||
mock_ui.assert_called_with(submission, "frame")
|
||||
mock_call.after_new.assert_called_with(mock.ANY)
|
||||
mock_call.after_open.assert_called_with(mock.ANY)
|
||||
|
||||
@mock.patch("submission.JobManager")
|
||||
@mock.patch("submission.maya")
|
||||
def test_start(self, mock_maya, mock_mgr):
|
||||
|
||||
session = mock.Mock(credentials="creds", config="conf")
|
||||
def test_submission_start(self, mock_maya):
|
||||
session = mock.Mock(batch="batch_client", storage="storage_client")
|
||||
self.mock_self.ui = mock.create_autospec(SubmissionUI)
|
||||
self.mock_self.ui.render_module = "module"
|
||||
self.mock_self.renderer = mock.Mock()
|
||||
|
||||
BatchAppsSubmission.start(self.mock_self, session, "assets", "pools")
|
||||
mock_mgr.assert_called_with("creds", "conf")
|
||||
AzureBatchSubmission.start(self.mock_self, session, "assets", "pools")
|
||||
self.mock_self.renderer.delete.assert_called_with()
|
||||
self.mock_self.configure_renderer.assert_called_with()
|
||||
self.mock_self._configure_renderer.assert_called_with()
|
||||
self.mock_self.renderer.display.assert_called_with("module")
|
||||
self.mock_self.ui.is_logged_in.assert_called_with()
|
||||
|
||||
def test_collect_modules(self):
|
||||
|
||||
mods = BatchAppsSubmission.collect_modules(self.mock_self)
|
||||
def test_submission_collect_modules(self):
|
||||
mods = AzureBatchSubmission._collect_modules(self.mock_self)
|
||||
self.assertEqual(len(mods), 4)
|
||||
|
||||
@mock.patch("submission.BatchAppsRenderJob")
|
||||
@mock.patch("submission.AzureBatchRenderJob")
|
||||
@mock.patch("submission.maya")
|
||||
def test_configure_renderer(self, mock_maya, mock_default):
|
||||
|
||||
def test_submission_configure_renderer(self, mock_maya, mock_default):
|
||||
mock_default.return_value = mock.Mock(render_engine = "default")
|
||||
mock_maya.mel.return_value = "test_renderer"
|
||||
mock_maya.get_attr.return_value = "test_renderer"
|
||||
|
||||
renderer = mock.Mock(render_engine = "my_renderer")
|
||||
self.mock_self.modules = [renderer, "test", None]
|
||||
|
||||
BatchAppsSubmission.configure_renderer(self.mock_self)
|
||||
self.assertEqual(self.mock_self.renderer, mock_default.return_value)
|
||||
AzureBatchSubmission._configure_renderer(self.mock_self)
|
||||
self.assertEqual(self.mock_self.renderer.render_engine, "default")
|
||||
|
||||
renderer = mock.Mock(render_engine = "test_renderer")
|
||||
self.mock_self.modules.append(renderer)
|
||||
|
||||
BatchAppsSubmission.configure_renderer(self.mock_self)
|
||||
self.assertEqual(self.mock_self.renderer, renderer)
|
||||
|
||||
def test_refresh_renderer(self):
|
||||
AzureBatchSubmission._configure_renderer(self.mock_self)
|
||||
self.assertEqual(self.mock_self.renderer.render_engine, "test_renderer")
|
||||
|
||||
def test_submission_refresh_renderer(self):
|
||||
self.mock_self.ui = mock.create_autospec(SubmissionUI)
|
||||
self.mock_self.renderer = mock.Mock()
|
||||
|
||||
BatchAppsSubmission.refresh_renderer(self.mock_self, "layout")
|
||||
AzureBatchSubmission.refresh_renderer(self.mock_self, "layout")
|
||||
self.mock_self.renderer.delete.assert_called_with()
|
||||
self.mock_self.renderer.display.assert_called_with("layout")
|
||||
|
||||
@mock.patch("submission.maya")
|
||||
def test_submit(self, mock_maya):
|
||||
def test_submission_available_pools(self):
|
||||
def list_pools(**kwargs):
|
||||
self.assertTrue(kwargs.get("lazy"))
|
||||
return ["pool1", "pool2"]
|
||||
self.mock_self.pool_manager = mock.Mock(list_pools=list_pools)
|
||||
pools = AzureBatchSubmission.available_pools(self.mock_self)
|
||||
self.assertEqual(pools, ["pool1", "pool2"])
|
||||
|
||||
@mock.patch("submission.utils")
|
||||
@mock.patch("submission.maya")
|
||||
def test_submission_submit(self, mock_maya, mock_utils):
|
||||
def call(func):
|
||||
self.assertTrue(hasattr(func, '__call__'))
|
||||
return func()
|
||||
|
||||
self.mock_self.job_manager = mock.create_autospec(JobManager)
|
||||
job = mock.create_autospec(JobSubmission)
|
||||
job.required_files = mock.Mock()
|
||||
job.required_files.upload.return_value = None
|
||||
|
||||
self.mock_self.job_manager.create_job.return_value = job
|
||||
self.mock_self.ui = mock.create_autospec(SubmissionUI)
|
||||
self.mock_self.pool_manager = mock.Mock()
|
||||
self.mock_self.asset_manager = mock.Mock()
|
||||
mock_prog = mock.create_autospec(ProgressBar)
|
||||
mock_prog.is_cancelled.return_value = False
|
||||
mock_utils.ProgressBar.return_value = mock_prog
|
||||
self.mock_self._configure_pool = AzureBatchSubmission._configure_pool
|
||||
self.mock_self._check_outputs = AzureBatchSubmission._check_outputs
|
||||
self.mock_self._check_plugins.return_value = []
|
||||
self.mock_self.asset_manager.upload.return_value = ("a", "b", mock_prog)
|
||||
self.mock_self.pool_manager.create_auto_pool.return_value = {'autoPool': 'auto-pool'}
|
||||
self.mock_self.pool_manager.create_pool.return_value = {'poolId': 'new-pool'}
|
||||
self.mock_self.renderer = mock.Mock()
|
||||
self.mock_self.renderer.get_jobdata.return_value = ("a", "b")
|
||||
self.mock_self.renderer.get_params.return_value = {"foo": "bar"}
|
||||
self.mock_self.renderer.get_title.return_value = "job name"
|
||||
self.mock_self._call = call
|
||||
|
||||
self.mock_self.ui.get_pool.return_value = {1:"pool"}
|
||||
self.mock_self.asset_manager.collect_assets.return_value = {}
|
||||
self.mock_self.asset_manager.upload.return_value = ("files", "maps", mock_prog)
|
||||
|
||||
BatchAppsSubmission.submit(self.mock_self)
|
||||
AzureBatchSubmission.submit(self.mock_self)
|
||||
self.assertEqual(mock_maya.error.call_count, 1)
|
||||
self.mock_self.renderer.disable.assert_called_with(True)
|
||||
self.mock_self.ui.processing.assert_called_with(True)
|
||||
|
||||
self.mock_self.ui.get_pool.return_value = {1:4}
|
||||
|
||||
BatchAppsSubmission.submit(self.mock_self)
|
||||
AzureBatchSubmission.submit(self.mock_self)
|
||||
self.assertEqual(mock_maya.error.call_count, 1)
|
||||
self.mock_self.renderer.disable.assert_called_with(True)
|
||||
self.mock_self.ui.processing.assert_called_with(True)
|
||||
|
||||
job.submit.assert_called_with()
|
||||
self.assertEqual(job.instances, 4)
|
||||
|
||||
self.mock_self.ui.get_pool.return_value = {2:4}
|
||||
BatchAppsSubmission.submit(self.mock_self)
|
||||
AzureBatchSubmission.submit(self.mock_self)
|
||||
self.assertEqual(mock_maya.error.call_count, 1)
|
||||
self.mock_self.renderer.disable.assert_called_with(True)
|
||||
self.mock_self.ui.processing.assert_called_with(True)
|
||||
|
||||
job.submit.assert_called_with()
|
||||
self.assertEqual(job.pool, '4')
|
||||
|
||||
self.mock_self.check_outputs.side_effect = ValueError("No camera")
|
||||
AzureBatchSubmission.submit(self.mock_self)
|
||||
self.assertEqual(mock_maya.error.call_count, 2)
|
||||
self.mock_self.check_outputs.side_effect = None
|
||||
|
||||
self.mock_self.ui.get_pool.return_value = {3: 4}
|
||||
BatchAppsSubmission.submit(self.mock_self)
|
||||
self.assertEqual(mock_maya.error.call_count, 1)
|
||||
AzureBatchSubmission.submit(self.mock_self)
|
||||
self.assertEqual(mock_maya.error.call_count, 2)
|
||||
self.mock_self.renderer.disable.assert_called_with(True)
|
||||
self.mock_self.ui.processing.assert_called_with(True)
|
||||
|
||||
job.submit.assert_called_with()
|
||||
job.submit.call_count = 0
|
||||
self.mock_self.pool_manager.create_pool.assert_called_with(4)
|
||||
|
||||
self.mock_self.asset_manager.collect_assets.return_value = {1,2,3}
|
||||
BatchAppsSubmission.submit(self.mock_self)
|
||||
self.assertEqual(mock_maya.error.call_count, 2)
|
||||
mock_prog.is_cancelled.return_value = True
|
||||
AzureBatchSubmission.submit(self.mock_self)
|
||||
self.assertEqual(mock_maya.info.call_count, 4)
|
||||
|
||||
self.mock_self.renderer.disable.assert_called_with(True)
|
||||
self.mock_self.ui.processing.assert_called_with(True)
|
||||
self.assertEqual(job.submit.call_count, 0)
|
||||
|
||||
self.mock_self.pool_manager.create_pool.side_effect = SessionExpiredException("Logged out!")
|
||||
BatchAppsSubmission.submit(self.mock_self)
|
||||
self.assertEqual(mock_maya.error.call_count, 2)
|
||||
mock_prog.is_cancelled.return_value = False
|
||||
self.mock_self.pool_manager.create_pool.side_effect = Exception("Logged out!")
|
||||
AzureBatchSubmission.submit(self.mock_self)
|
||||
self.assertEqual(mock_maya.error.call_count, 3)
|
||||
self.mock_self.renderer.disable.assert_called_with(True)
|
||||
self.mock_self.ui.processing.assert_called_with(True)
|
||||
self.assertEqual(job.submit.call_count, 0)
|
||||
|
||||
|
||||
class TestSubmissionCombined(unittest.TestCase):
|
||||
|
||||
@mock.patch("submission.utils")
|
||||
@mock.patch("ui_submission.utils")
|
||||
@mock.patch("ui_submission.maya")
|
||||
@mock.patch("submission.callback")
|
||||
@mock.patch("submission.maya")
|
||||
def test_submission(self, *args):
|
||||
|
||||
mock_callback = args[1]
|
||||
mock_maya = args[0]
|
||||
mock_maya.mel.return_value = "Renderer"
|
||||
mock_maya.get_list.return_value = ["1","2","3"]
|
||||
mock_maya.get_attr.return_value = True
|
||||
|
||||
ui_maya = args[1]
|
||||
ui_maya = args[2]
|
||||
ui_maya.radio_group.return_value = 2
|
||||
ui_maya.text_field.return_value = "12345"
|
||||
ui_maya.menu.return_value = "12345"
|
||||
ui_maya.int_slider.return_value = 6
|
||||
|
||||
def add_tab(tab):
|
||||
|
@ -210,21 +242,33 @@ class TestSubmissionCombined(unittest.TestCase):
|
|||
self.assertTrue(hasattr(func, '__call__'))
|
||||
return func()
|
||||
|
||||
mock_utils = args[4]
|
||||
mock_prog = mock.create_autospec(ProgressBar)
|
||||
mock_prog.is_cancelled.return_value = False
|
||||
mock_utils.ProgressBar.return_value = mock_prog
|
||||
|
||||
layout = mock.Mock(add_tab=add_tab)
|
||||
sub = BatchAppsSubmission(layout, call)
|
||||
sub = AzureBatchSubmission(layout, call)
|
||||
self.assertEqual(len(sub.modules), 4)
|
||||
self.assertTrue(mock_callback.after_new.called)
|
||||
self.assertTrue(mock_callback.after_open.called)
|
||||
|
||||
creds = mock.create_autospec(Credentials)
|
||||
conf = mock.create_autospec(Configuration)
|
||||
session = mock.Mock(credentials=creds, config=conf)
|
||||
assets = mock.create_autospec(BatchAppsAssets)
|
||||
pools = mock.create_autospec(BatchAppsPools)
|
||||
env = mock.create_autospec(BatchAppsEnvironment)
|
||||
env.license = {"license":"test"}
|
||||
env.environment_variables = {"env":"val"}
|
||||
env.plugins = {"plugins":"test"}
|
||||
|
||||
sub.start(session, assets, pools)
|
||||
sub.start(session, assets, pools, env)
|
||||
|
||||
mock_maya.mel.return_value = "Renderer_A"
|
||||
sub.ui.refresh()
|
||||
|
||||
sub.asset_manager.upload.return_value = (["abc"], {}, mock_prog)
|
||||
sub.asset_manager.collect_assets.return_value = {'assets':['abc'],
|
||||
'pathmaps':'mapping'}
|
||||
sub.job_manager = mock.create_autospec(JobManager)
|
||||
|
@ -241,6 +285,17 @@ class TestSubmissionCombined(unittest.TestCase):
|
|||
job.add_file_collection.assert_called_with(["abc"])
|
||||
self.assertEqual(job.pool, "12345")
|
||||
|
||||
mock_maya.get_attr.return_value = False
|
||||
sub.submit()
|
||||
mock_maya.error.assert_called_with(mock.ANY)
|
||||
mock_maya.error.call_count = 0
|
||||
mock_maya.get_attr.return_value = True
|
||||
|
||||
ui_maya.menu.return_value = None
|
||||
sub.submit()
|
||||
mock_maya.error.assert_called_with("No pool selected.")
|
||||
mock_maya.error.call_count = 0
|
||||
|
||||
ui_maya.radio_group.return_value = 1
|
||||
sub.submit()
|
||||
self.assertEqual(mock_maya.error.call_count, 0)
|
||||
|
@ -254,6 +309,6 @@ class TestSubmissionCombined(unittest.TestCase):
|
|||
sub.pool_manager.create_pool.assert_called_with(6)
|
||||
job.submit.assert_called_with()
|
||||
|
||||
job.required_files.upload.return_value = [("failed", Exception("boom"))]
|
||||
sub.asset_manager.upload.return_value = [("failed", Exception("boom"))]
|
||||
sub.submit()
|
||||
self.assertEqual(mock_maya.error.call_count, 1)
|