This commit is contained in:
annatisch 2017-04-25 14:27:21 -07:00
Родитель 1887856997
Коммит f87a1c54df
92 изменённых файлов: 7817 добавлений и 5358 удалений

63
.gitattributes поставляемый Normal file
Просмотреть файл

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

13
.gitignore поставляемый
Просмотреть файл

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

185
AzureBatchMaya.pyproj Normal file
Просмотреть файл

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

165
MayaCloud/MayaScripts.Designer.cs сгенерированный
Просмотреть файл

@ -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 &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
///&lt;ADLMCUSTOMENV VERSION=&quot;1.0.0.0&quot;&gt;
/// &lt;PLATFORM OS=&quot;Windows&quot;&gt;
/// &lt;KEY ID=&quot;ADLM_COMMON_BIN_LOCATION&quot;&gt;
/// &lt;!--Path to the AdLM shared executables--&gt;
/// &lt;STRING&gt;{0}\Common Files\Autodesk Shared\Adlm\R9&lt;/STRING&gt;
/// &lt;/KEY&gt;
/// &lt;KEY ID=&quot;ADLM_COMMON_LIB_LOCATION&quot;&gt;
/// &lt;!--Path to the AdLM shared libraries--&gt;
/// &lt;STRING&gt;{0}\Common Files\Autodesk Shared\Adlm\R9&lt;/STRING&gt;
/// &lt;/KEY&gt;
/// &lt;KEY ID=&quot;ADLM_COMMON_LOCAL [rest of string was truncated]&quot;;.
/// </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]&quot;;.
/// </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 &quot;fluidCache&quot; &quot;.&quot;;
///
///workspace -fr &quot;images&quot; &quot;.&quot;;
///
///workspace -fr &quot;offlineEdit&quot; &quot;.&quot;;
///
///workspace -fr &quot;furShadowMap&quot; &quot;.&quot;;
///
///workspace -fr &quot;iprImages&quot; &quot;.&quot;;
///
///workspace -fr &quot;renderData&quot; &quot;.&quot;;
///
///workspace -fr &quot;scripts&quot; &quot;.&quot;;
///
///workspace -fr &quot;fileCache&quot; &quot;.&quot;;
///
///workspace -fr &quot;eps&quot; &quot;.&quot;;
///
///workspace -fr &quot;shaders&quot; &quot;.&quot;;
///
///workspace -fr &quot;3dPaintTextures&quot; &quot;.&quot;;
///
///workspace -fr &quot;translatorData&quot; &quot;.&quot;;
///
///workspace -fr &quot;mel&quot; &quot;.&quot;;
///
///workspace -fr &quot;furFiles&quot; &quot;.&quot;;
///
///workspace -fr &quot;O [rest of string was truncated]&quot;;.
/// </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>&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;ADLMCUSTOMENV VERSION="1.0.0.0"&gt;
&lt;PLATFORM OS="Windows"&gt;
&lt;KEY ID="ADLM_COMMON_BIN_LOCATION"&gt;
&lt;!--Path to the AdLM shared executables--&gt;
&lt;STRING&gt;{0}\Common Files\Autodesk Shared\Adlm\R9&lt;/STRING&gt;
&lt;/KEY&gt;
&lt;KEY ID="ADLM_COMMON_LIB_LOCATION"&gt;
&lt;!--Path to the AdLM shared libraries--&gt;
&lt;STRING&gt;{0}\Common Files\Autodesk Shared\Adlm\R9&lt;/STRING&gt;
&lt;/KEY&gt;
&lt;KEY ID="ADLM_COMMON_LOCALIZED_DATA_LOCATION"&gt;
&lt;!--Path to the AdLM shared localized resource files (do not include the language folder)--&gt;
&lt;STRING&gt;{0}\Common Files\Autodesk Shared\Adlm\R9&lt;/STRING&gt;
&lt;/KEY&gt;
&lt;KEY ID="ADLM_ERROR_TABLE_LOCATION"&gt;
&lt;!--Path to AdlmErrorCodes.xml--&gt;
&lt;STRING&gt;{0}\Adlm\R9&lt;/STRING&gt;
&lt;/KEY&gt;
&lt;KEY ID="ADLM_PIT_FILE_LOCATION"&gt;
&lt;!--Path to the ProductInformation.pit file--&gt;
&lt;STRING&gt;{0}\Adlm&lt;/STRING&gt;
&lt;/KEY&gt;
&lt;KEY ID="ADLM_CASCADE_FILE_LOCATION"&gt;
&lt;!--Path where the CascadeInfo.cas file will be created (this should always be on the client machine)--&gt;
&lt;STRING&gt;{0}\Adlm&lt;/STRING&gt;
&lt;/KEY&gt;
&lt;KEY ID="ADLM_LICENSE_FILE_LOCATION"&gt;
&lt;!--Path to a directory containing one or more .lic files (API override)--&gt;
&lt;STRING&gt;{0}\Adlm&lt;/STRING&gt;
&lt;/KEY&gt;
&lt;KEY ID="ADLM_APPLICATION_ADLM_RESOURCE_LOCATION"&gt;
&lt;!--Path to the AdlmIntRes.xml file (API override)--&gt;
&lt;STRING&gt;&lt;/STRING&gt;
&lt;/KEY&gt;
&lt;KEY ID="ADLM_APPLICATION_ERROR_LOG_FILE"&gt;
&lt;!--Path and filename of the application AdLM log file (API override)--&gt;
&lt;STRING&gt;&lt;/STRING&gt;
&lt;/KEY&gt;
&lt;/PLATFORM&gt;
&lt;/ADLMCUSTOMENV&gt;</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>

Двоичные данные
azure_batch_maya/icons/btn_background.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 16 KiB

Двоичные данные
azure_batch_maya/icons/btn_cancel.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 16 KiB

Двоичные данные
azure_batch_maya/icons/btn_delete.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 787 B

Двоичные данные
azure_batch_maya/icons/btn_download.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 16 KiB

Двоичные данные
azure_batch_maya/icons/btn_portal.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 16 KiB

Двоичные данные
azure_batch_maya/icons/btn_refresh.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 16 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 913 B

После

Ширина:  |  Высота:  |  Размер: 913 B

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 1.1 KiB

После

Ширина:  |  Высота:  |  Размер: 1.1 KiB

Двоичные данные
azure_batch_maya/icons/plugin.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 15 KiB

Двоичные данные
azure_batch_maya/icons/portal.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 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),
(2, 200),),
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!")

3
requirements.txt Normal file
Просмотреть файл

@ -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__':

Просмотреть файл

337
tests/test_environment.py Normal file
Просмотреть файл

@ -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):

230
tests/test_jobwatcher.py Normal file
Просмотреть файл

@ -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")
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_submit(self, mock_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)
self.assertEqual(mock_maya.error.call_count, 1)