Initial release
This commit is contained in:
Коммит
7bac13bb72
|
@ -0,0 +1,17 @@
|
|||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.cs diff=csharp
|
||||
|
||||
# Standard to msysgit
|
||||
*.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
|
|
@ -0,0 +1,216 @@
|
|||
#################
|
||||
## Eclipse
|
||||
#################
|
||||
|
||||
*.pydevproject
|
||||
.project
|
||||
.metadata
|
||||
bin/
|
||||
tmp/
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.classpath
|
||||
.settings/
|
||||
.loadpath
|
||||
|
||||
# External tool builders
|
||||
.externalToolBuilders/
|
||||
|
||||
# Locally stored "Eclipse launch configurations"
|
||||
*.launch
|
||||
|
||||
# CDT-specific
|
||||
.cproject
|
||||
|
||||
# PDT-specific
|
||||
.buildpath
|
||||
|
||||
|
||||
#################
|
||||
## Visual Studio
|
||||
#################
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.docstates
|
||||
|
||||
# Build results
|
||||
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
x64/
|
||||
build/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.log
|
||||
*.scc
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# NCrunch
|
||||
*.ncrunch*
|
||||
.*crunch*.local.xml
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.Publish.xml
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# NuGet Packages Directory
|
||||
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
|
||||
#packages/
|
||||
|
||||
# Windows Azure Build Output
|
||||
csx
|
||||
*.build.csdef
|
||||
|
||||
# Windows Store app package directory
|
||||
AppPackages/
|
||||
|
||||
# Others
|
||||
sql/
|
||||
*.Cache
|
||||
ClientBin/
|
||||
[Ss]tyle[Cc]op.*
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.[Pp]ublish.xml
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file to a newer
|
||||
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
App_Data/*.mdf
|
||||
App_Data/*.ldf
|
||||
|
||||
#############
|
||||
## Windows detritus
|
||||
#############
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Mac crap
|
||||
.DS_Store
|
||||
|
||||
|
||||
#############
|
||||
## Python
|
||||
#############
|
||||
|
||||
*.py[cod]
|
||||
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
dist/
|
||||
build/
|
||||
eggs/
|
||||
parts/
|
||||
var/
|
||||
sdist/
|
||||
develop-eggs/
|
||||
.installed.cfg
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.tox
|
||||
|
||||
#Translations
|
||||
*.mo
|
||||
|
||||
#Mr Developer
|
||||
.mr.developer.cfg
|
|
@ -0,0 +1,33 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2013
|
||||
VisualStudioVersion = 12.0.21005.1
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blender.Cloud", "Blender.Cloud\Blender.Cloud.csproj", "{5C019C8B-B5E5-4B98-9D75-8B5D7E6C83D0}"
|
||||
EndProject
|
||||
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "Blender.Client", "Blender.Client\Blender.Client.pyproj", "{16D9E706-0005-4AC4-832C-D0403F6B8008}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{465620A1-3D6E-431A-A116-F7260C8D87F9}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
CHANGES.txt = CHANGES.txt
|
||||
LICENSE.txt = LICENSE.txt
|
||||
README.rst = README.rst
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{5C019C8B-B5E5-4B98-9D75-8B5D7E6C83D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5C019C8B-B5E5-4B98-9D75-8B5D7E6C83D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5C019C8B-B5E5-4B98-9D75-8B5D7E6C83D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5C019C8B-B5E5-4B98-9D75-8B5D7E6C83D0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{16D9E706-0005-4AC4-832C-D0403F6B8008}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{16D9E706-0005-4AC4-832C-D0403F6B8008}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,111 @@
|
|||
<?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>16d9e706-0005-4ac4-832c-d0403f6b8008</ProjectGuid>
|
||||
<ProjectHome>.</ProjectHome>
|
||||
<StartupFile>package.py</StartupFile>
|
||||
<SearchPath>
|
||||
</SearchPath>
|
||||
<WorkingDirectory>.</WorkingDirectory>
|
||||
<OutputPath>.</OutputPath>
|
||||
<Name>Blender.Client</Name>
|
||||
<RootNamespace>Blender.Client</RootNamespace>
|
||||
<InterpreterId>
|
||||
</InterpreterId>
|
||||
<InterpreterVersion>
|
||||
</InterpreterVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||
<PtvsTargetsFile>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets</PtvsTargetsFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="batchapps_blender\assets.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\auth.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\history.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\pools.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\props\props_pools.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\submission.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\props\props_assets.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\props\props_auth.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\props\props_history.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\props\props_submission.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\props\props_shared.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\draw.py" />
|
||||
<Compile Include="batchapps_blender\shared.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\ui\ui_assets.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\ui\ui_auth.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\ui\ui_history.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\ui\ui_pools.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\ui\ui_shared.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\ui\ui_submission.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\utils.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="batchapps_blender\__init__.py" />
|
||||
<Compile Include="package.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="batchapps_blender\" />
|
||||
<Folder Include="batchapps_blender\ui\" />
|
||||
<Folder Include="batchapps_blender\props\" />
|
||||
</ItemGroup>
|
||||
<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>
|
|
@ -0,0 +1,102 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
bl_info = {
|
||||
"name": "Batch Apps Blender",
|
||||
"author": "Microsoft Corp. <bigcompute@microsoft.com>",
|
||||
"version": (0, 1, 0),
|
||||
"blender": (2, 7, 2),
|
||||
"location": "Render Properties",
|
||||
"description": ("Export Blender files to be rendered externally by "
|
||||
"Microsoft Batch Apps."),
|
||||
"category": "Render"}
|
||||
|
||||
import bpy
|
||||
|
||||
from batchapps import credentials
|
||||
from batchapps import config as logging
|
||||
|
||||
from batchapps_blender.props.props_shared import BatchAppsPreferences
|
||||
from batchapps_blender.shared import BatchAppsSettings
|
||||
from batchapps_blender.draw import *
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def start_session(self):
|
||||
"""
|
||||
Instantiate the Batch Apps session and register all the property
|
||||
classes to the Blender context.
|
||||
This is handled in an event to allow it to run under the full
|
||||
Blender context rather than the limited loading scope.
|
||||
|
||||
Once the session has started (or reported an error), this function
|
||||
is removed from the event handlers.
|
||||
"""
|
||||
try:
|
||||
session = BatchAppsSettings()
|
||||
|
||||
def get_session(self):
|
||||
return session
|
||||
bpy.types.Scene.batchapps_session = property(get_session)
|
||||
|
||||
except Exception as e:
|
||||
print("BatchApps Addon failed to load.")
|
||||
print ("Error: {0}".format(e))
|
||||
|
||||
finally:
|
||||
bpy.app.handlers.scene_update_post.remove(start_session)
|
||||
|
||||
|
||||
def register():
|
||||
"""
|
||||
Register module and applicable classes.
|
||||
This method also sets some BatchApps globals. In particular, the
|
||||
python module Requests that is packaged with blender does not allow
|
||||
for certificates to be verified, so we have to either turn this off,
|
||||
or replace the included Requests module (recommended).
|
||||
|
||||
Here we also register the User Preferences for the Addon, so it can
|
||||
be configured in the Blender User Preferences window.
|
||||
"""
|
||||
credentials.VERIFY = False
|
||||
logging.STREAM_LOG = False
|
||||
|
||||
bpy.app.handlers.scene_update_post.append(start_session)
|
||||
|
||||
bpy.utils.register_class(BatchAppsPreferences)
|
||||
bpy.utils.register_module(__name__)
|
||||
|
||||
|
||||
def unregister():
|
||||
"""
|
||||
Unregister the addon if deselected from the User Preferences window.
|
||||
"""
|
||||
bpy.utils.unregister_module(__name__)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
|
@ -0,0 +1,464 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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 bpy
|
||||
import logging
|
||||
import tempfile
|
||||
import os
|
||||
import re
|
||||
import string
|
||||
import random
|
||||
|
||||
from batchapps_blender.ui import ui_assets
|
||||
from batchapps_blender.props import props_assets
|
||||
from batchapps_blender.utils import BatchAppsOps
|
||||
|
||||
|
||||
class BatchAppsAssets(object):
|
||||
"""
|
||||
Manager for all external file handling and displaying of assets.
|
||||
"""
|
||||
|
||||
pages = ["ASSETS"]
|
||||
|
||||
def __init__(self, manager):
|
||||
|
||||
self.batchapps = manager
|
||||
self.ops = self._register_ops()
|
||||
self.props = self._register_props()
|
||||
self.ui = self._register_ui()
|
||||
|
||||
def display(self, ui, layout):
|
||||
"""
|
||||
Invokes the corresponding ui function depending on the session's
|
||||
current page.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
:Returns:
|
||||
- Runs the display function for the applicable page.
|
||||
"""
|
||||
return self.ui[bpy.context.scene.batchapps_session.page](ui, layout)
|
||||
|
||||
def _register_props(self):
|
||||
"""
|
||||
Registers and retrieves the asset property objects.
|
||||
These properties are assigned to the scene.batchapps_assets context.
|
||||
|
||||
It also resets the current job filepath, to prevent this being
|
||||
persisted between different Blender scenes.
|
||||
|
||||
:Returns:
|
||||
- :class:`.AssetProps`
|
||||
"""
|
||||
|
||||
props = props_assets.register_props()
|
||||
props.path = ""
|
||||
return props
|
||||
|
||||
def _register_ops(self):
|
||||
"""
|
||||
Registers each asset operator with a batchapps_assets prefix.
|
||||
|
||||
:Returns:
|
||||
- A list of the names (str) of the registered asset operators.
|
||||
"""
|
||||
ops = []
|
||||
ops.append(BatchAppsOps.register("assets.page",
|
||||
"Scene assets",
|
||||
self._assets))
|
||||
ops.append(BatchAppsOps.register("assets.refresh",
|
||||
"Refresh assets",
|
||||
self._refresh))
|
||||
ops.append(BatchAppsOps.register("assets.upload",
|
||||
"Upload selected assets",
|
||||
self._upload))
|
||||
ops.append(BatchAppsOps.register("assets.remove",
|
||||
"Remove asset",
|
||||
invoke=self._remove))
|
||||
ops.append(BatchAppsOps.register("assets.add", "Add asset",
|
||||
self._add_execute,
|
||||
invoke=self._add_invoke,
|
||||
filepath=bpy.props.StringProperty(
|
||||
subtype="FILE_PATH")))
|
||||
return ops
|
||||
|
||||
def _register_ui(self):
|
||||
"""
|
||||
Matches the assets page with its corresponding ui function.
|
||||
|
||||
:Returns:
|
||||
- A dictionary mapping the page name to its corresponding
|
||||
ui function.
|
||||
"""
|
||||
def get_asset_ui(name):
|
||||
name = name.lower()
|
||||
return getattr(ui_assets, name)
|
||||
|
||||
page_func = map(get_asset_ui, self.pages)
|
||||
|
||||
return dict(zip(self.pages, page_func))
|
||||
|
||||
def _assets(self, op, context):
|
||||
"""
|
||||
The execute method for the assets.page operator.
|
||||
Sets the page and initiates asset collection.
|
||||
Also checks whether assets have already been initialized for the
|
||||
current scene file.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
session = context.scene.batchapps_session
|
||||
session.page = "ASSETS"
|
||||
self.props = context.scene.batchapps_assets
|
||||
|
||||
new_path = self.get_jobpath()
|
||||
|
||||
if new_path != self.props.path:
|
||||
session.log.debug("New scene, gathering assets.")
|
||||
|
||||
self.props.path = new_path
|
||||
|
||||
if not self.props.temp:
|
||||
session.log.debug("Not temp file - saving.")
|
||||
bpy.ops.wm.save_mainfile()
|
||||
|
||||
self.generate_collection()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def _refresh(self, op, context):
|
||||
"""
|
||||
The execute method for the assets.refresh operator.
|
||||
Re-initiates asset collection from scratch, regardless of whether it
|
||||
has been done before for this scene. This means any additional assets
|
||||
added, or any existing assets removed will be restored to their initial
|
||||
state.
|
||||
This also updates the current job file path if it has changed.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
session = context.scene.batchapps_session
|
||||
self.props = context.scene.batchapps_assets
|
||||
new_path = self.get_jobpath()
|
||||
|
||||
if new_path != self.props.path:
|
||||
session.log.debug("New scene, resetting path.")
|
||||
self.props.path = new_path
|
||||
|
||||
self.generate_collection()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def _upload(self, op, context):
|
||||
"""
|
||||
The execute method for the assets.upload operator.
|
||||
Identifies assets that have been selected for uploaded.
|
||||
Iterates through uploaded each sequentially, and updates
|
||||
the UI uploaded checkbox accordingly.
|
||||
|
||||
If one asset fails to upload, the operator will continue to
|
||||
attempt to upload the remaining.
|
||||
Any other error that occurs will be raised to be handled by
|
||||
:func:`.BatchAppsOps.session`.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
session = context.scene.batchapps_session
|
||||
upload = self.pending_upload()
|
||||
|
||||
session.log.info("{0} assets to be uploaded".format(len(upload)))
|
||||
|
||||
for index in upload:
|
||||
asset = self.props.collection[index]
|
||||
display = self.props.assets[index]
|
||||
|
||||
try:
|
||||
session.log.debug("Uploading {0}".format(asset.name))
|
||||
asset.upload(force=True)
|
||||
display.upload_check = True
|
||||
session.log.debug("Upload complete")
|
||||
|
||||
except Exception as exp:
|
||||
print('Failed to upload: {0}'.format(exp))
|
||||
display.upload_check = False
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def _add_execute(self, op, context):
|
||||
"""
|
||||
The execute method for the assets.add operator.
|
||||
Adds an asset selected via a file browser.
|
||||
The file will only be added if it exists and has not already been
|
||||
added. If the file is already present, it will be skipped and the
|
||||
operator will finished without error.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
session = context.scene.batchapps_session
|
||||
session.log.debug("Selected file {0}".format(op.filepath))
|
||||
|
||||
user_file = self.batchapps.file_from_path(op.filepath)
|
||||
if user_file and user_file not in self.props.collection:
|
||||
self.props.add_asset(user_file)
|
||||
|
||||
else:
|
||||
session.log.warning("File {0} either duplicate or does not "
|
||||
"exist.".format(user_file.name))
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def _add_invoke(self, op, context, event):
|
||||
"""
|
||||
The invoke method for the assets.add operator.
|
||||
Invokes the file selector window from the WindowManager.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
- event (:class:`bpy.types.Event`): The blender invocation event.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'RUNNING_MODAL'} to indicate the operator
|
||||
will continue to process after the completion of this function.
|
||||
"""
|
||||
context.window_manager.fileselect_add(op)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
|
||||
def _remove(self, op, context, event):
|
||||
'''
|
||||
The invoke method for the assets.remove operator.
|
||||
Removes currently selected assets from both the display assets
|
||||
and the UserFile collection.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
- event (:class:`bpy.types.Event`): The blender invocation event.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
'''
|
||||
if not self.props.assets:
|
||||
return {'FINISHED'}
|
||||
|
||||
self.props.remove_selected()
|
||||
return {'FINISHED'}
|
||||
|
||||
def collect_assets(self):
|
||||
"""
|
||||
Generates a list of the external files referenced by the current
|
||||
blend file. After collection, the paths are made absolute and
|
||||
normalized.
|
||||
This currently includes files from:
|
||||
- bpy.data.sounds
|
||||
- bpy.data.fonts
|
||||
- bpy.data.textures.image
|
||||
- bpy.data.images
|
||||
- bpy.data.libraries
|
||||
|
||||
:Returns:
|
||||
- A list of file paths as strings.
|
||||
"""
|
||||
asset_list = []
|
||||
|
||||
bpy.context.scene.batchapps_session.log.info(
|
||||
"Collecting external assets.")
|
||||
|
||||
for s in bpy.data.sounds:
|
||||
new_path = os.path.realpath(bpy.path.abspath(s.filepath))
|
||||
asset_list.append(new_path)
|
||||
|
||||
for f in bpy.data.fonts:
|
||||
if f.filepath != "<builtin>":
|
||||
new_path = os.path.realpath(bpy.path.abspath(f.filepath))
|
||||
asset_list.append(new_path)
|
||||
|
||||
for t in bpy.data.textures:
|
||||
if hasattr(t, 'image'):
|
||||
if t.image:
|
||||
new_path = bpy.path.abspath(t.image.filepath)
|
||||
new_path = os.path.normpath(os.path.realpath(new_path))
|
||||
asset_list.append(new_path)
|
||||
|
||||
for i in bpy.data.images:
|
||||
new_path = os.path.realpath(bpy.path.abspath(i.filepath))
|
||||
asset_list.append(os.path.normpath(new_path))
|
||||
|
||||
for l in bpy.data.libraries:
|
||||
new_path = os.path.realpath(bpy.path.abspath(l.filepath))
|
||||
asset_list.append(os.path.normpath(new_path))
|
||||
|
||||
bpy.context.scene.batchapps_session.log.info(
|
||||
"Found %d asset files." % (len(asset_list)))
|
||||
|
||||
return asset_list
|
||||
|
||||
def name_generator(self, size=8, chars=string.hexdigits):
|
||||
"""
|
||||
Generates a random blend filename for a temporary blend file.
|
||||
|
||||
:Kwargs:
|
||||
- size (int): The number of random chars to use. Default is 8.
|
||||
- chars (string): The chars from which random chars will be
|
||||
selected. Default is '0123456789abcdefABCDEF'
|
||||
|
||||
:Returns:
|
||||
- A file name (str) with the prefix ``BATCHAPPSTMP_`` and
|
||||
suffix ".blend".
|
||||
"""
|
||||
return "BATCHAPPSTMP_"+''.join(random.choice(chars) for x in range(size))+".blend"
|
||||
|
||||
def get_jobpath(self):
|
||||
"""
|
||||
Gets the filepath to the job blend file. If currently using a saved
|
||||
blend file, this path will be returned.
|
||||
If the current blend file has never been saved (i.e. it has no path),
|
||||
a temporary path will be generated in the Blender User Preferences
|
||||
temporary directory.
|
||||
This temp filepath will not be saved to until submission.
|
||||
|
||||
:Returns:
|
||||
- The file path (str) to the .blend file.
|
||||
"""
|
||||
#TODO: Test relative vs. absolute paths.
|
||||
session = bpy.context.scene.batchapps_session
|
||||
|
||||
if bpy.data.filepath == '' and self.props.temp:
|
||||
session.log.debug(
|
||||
"Blend path: Using current temp {0}".format(self.props.path))
|
||||
return self.props.path
|
||||
|
||||
elif bpy.data.filepath == '':
|
||||
temp_dir = bpy.context.user_preferences.filepaths.temporary_directory
|
||||
temp_path = os.path.join(temp_dir, self.name_generator())
|
||||
self.props.temp = True
|
||||
|
||||
session.log.debug(
|
||||
"Blend path: Using new temp {0}".format(temp_path))
|
||||
return temp_path
|
||||
|
||||
else:
|
||||
self.props.temp = False
|
||||
|
||||
session.log.debug(
|
||||
"Blend path: Using saved {0}".format(bpy.data.filepath))
|
||||
return bpy.data.filepath
|
||||
|
||||
def generate_collection(self):
|
||||
"""
|
||||
Runs :func:`.collect_assets` and converts the result path list into
|
||||
BatchApps UserFile objects, which the props class then adds to
|
||||
the display assets list as well as the UserFile collection.
|
||||
|
||||
"""
|
||||
session = bpy.context.scene.batchapps_session
|
||||
|
||||
self.props.reset()
|
||||
assets = self.collect_assets()
|
||||
|
||||
for asset in assets:
|
||||
session.log.debug("Discovered asset {0}.".format(asset))
|
||||
user_file = self.batchapps.file_from_path(asset)
|
||||
|
||||
if user_file and user_file not in self.props.collection:
|
||||
self.props.add_asset(user_file)
|
||||
|
||||
else:
|
||||
session.log.warning("File {0} either duplicate or does not "
|
||||
"exist.".format(user_file.name))
|
||||
|
||||
if not self.props.temp:
|
||||
session.log.debug("Adding blend file as asset.")
|
||||
jobfile = self.batchapps.file_from_path(self.props.path)
|
||||
|
||||
if jobfile and jobfile not in self.props.collection:
|
||||
self.props.add_asset(jobfile)
|
||||
|
||||
def pending_upload(self):
|
||||
"""
|
||||
Get a list of the assets that have selected for upload.
|
||||
|
||||
:Returns:
|
||||
- A list of the indexes (int) of the items in the display
|
||||
assets list that have been selected for upload.
|
||||
"""
|
||||
|
||||
upload_me = []
|
||||
|
||||
for index, asset in enumerate(self.props.assets):
|
||||
if asset.upload_checkbox:
|
||||
upload_me.append(index)
|
||||
|
||||
return upload_me
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,402 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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 bpy
|
||||
|
||||
import webbrowser
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from http.server import HTTPServer
|
||||
from urllib.parse import unquote
|
||||
|
||||
from batchapps_blender.utils import (
|
||||
BatchAppsOps,
|
||||
OAuthRequestHandler)
|
||||
|
||||
from batchapps_blender.ui import ui_auth
|
||||
from batchapps_blender.props import props_auth
|
||||
|
||||
from batchapps import AzureOAuth
|
||||
from batchapps.exceptions import (
|
||||
AuthenticationException,
|
||||
InvalidConfigException)
|
||||
|
||||
|
||||
TIMEOUT = 60 # 1 minute
|
||||
|
||||
class BatchAppsAuth(object):
|
||||
"""
|
||||
Managers authentication of the session for the BatchApps Blender Addon.
|
||||
Will attempt to sign in automatically based on data available in the
|
||||
Batch Apps configuration. If unsuccessful, will prompt use to sign in
|
||||
via a web browser.
|
||||
"""
|
||||
|
||||
pages = ["LOGIN", "REDIRECT"]
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.ops = self._register_ops()
|
||||
self.props = self._register_props()
|
||||
self.ui = self._register_ui()
|
||||
|
||||
|
||||
def display(self, ui, layout):
|
||||
"""
|
||||
Invokes the corresponding ui function depending on the session's
|
||||
current page.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
:Returns:
|
||||
- Runs the display function for the applicable page.
|
||||
"""
|
||||
return self.ui[bpy.context.scene.batchapps_session.page](ui, layout)
|
||||
|
||||
|
||||
def _register_props(self):
|
||||
"""
|
||||
Registers and retrieves the auth property objects.
|
||||
These properties are assigned to the scene.batchapps_auth context.
|
||||
|
||||
:Returns:
|
||||
- :class:`.AuthProps`
|
||||
"""
|
||||
props = props_auth.register_props()
|
||||
return props
|
||||
|
||||
|
||||
def _register_ops(self):
|
||||
"""
|
||||
Registers each auth operator with a batchapps_auth prefix.
|
||||
|
||||
:Returns:
|
||||
- A list of the names (str) of the registered auth operators.
|
||||
"""
|
||||
ops = []
|
||||
ops.append(BatchAppsOps.register("auth.login",
|
||||
"Login",
|
||||
self._login))
|
||||
ops.append(BatchAppsOps.register("auth.logout",
|
||||
"Logout",
|
||||
self._logout))
|
||||
ops.append(BatchAppsOps.register("auth.redirect",
|
||||
"Redirecting authentication",
|
||||
modal=self._redirect_modal,
|
||||
invoke=self._redirect_invoke,
|
||||
_timer=None))
|
||||
return ops
|
||||
|
||||
|
||||
def _register_ui(self):
|
||||
"""
|
||||
Matches the login and redirection pages with their corresponding
|
||||
ui functions.
|
||||
|
||||
:Returns:
|
||||
- A dictionary mapping the page name to its corresponding
|
||||
ui function.
|
||||
"""
|
||||
def get_auth_ui(name):
|
||||
name = name.lower()
|
||||
return getattr(ui_auth, name)
|
||||
|
||||
page_func = map(get_auth_ui, self.pages)
|
||||
return dict(zip(self.pages, page_func))
|
||||
|
||||
def _redirect_modal(self, op, context, event):
|
||||
"""
|
||||
The modal method for the auth.redirect operator to handle running
|
||||
the authentication redirection server in a separate thread to
|
||||
prevent the blocking of the Blender UI.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
- event (:class:`bpy.types.Event`): The blender invocation event.
|
||||
|
||||
:Returns:
|
||||
- If the thread has completed, the Blender-specific value
|
||||
{'FINISHED'} to indicate the operator has completed its action.
|
||||
- Otherwise the Blender-specific value {'RUNNING_MODAL'} to
|
||||
indicate the operator wil continue to process after the
|
||||
completion of this function.
|
||||
"""
|
||||
if event.type == 'TIMER':
|
||||
context.scene.batchapps_session.log.debug("AuthThread complete.")
|
||||
if not self.props.thread.is_alive():
|
||||
context.window_manager.event_timer_remove(op._timer)
|
||||
return {'FINISHED'}
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def _redirect_invoke(self, op, context, event):
|
||||
"""
|
||||
The invoke method for the auth.redirect operator.
|
||||
Starts the authentication redirect thread.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
- event (:class:`bpy.types.Event`): The blender invocation event.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'RUNNING_MODAL'} to indicate the operator
|
||||
wil continue to process after the completion of this function.
|
||||
"""
|
||||
self.props.thread.start()
|
||||
context.scene.batchapps_session.log.debug("AuthThread initiated.")
|
||||
|
||||
context.window_manager.modal_handler_add(op)
|
||||
op._timer = context.window_manager.event_timer_add(1, context.window)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def _login(self, op, context, *args):
|
||||
"""
|
||||
The execute method for the auth.login operator.
|
||||
Sets the functions to per performed by the authentication thread and
|
||||
updates the session page to "REDIRECT" while the thread executes.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
auth_thread = lambda: BatchAppsOps.session(self.web_authentication)
|
||||
self.props.thread = threading.Thread(name="AuthThread",
|
||||
target=auth_thread)
|
||||
|
||||
bpy.ops.batchapps_auth.redirect('INVOKE_DEFAULT')
|
||||
|
||||
if context.scene.batchapps_session.page == "LOGIN":
|
||||
context.scene.batchapps_session.page = "REDIRECT"
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def _logout(self, op, context, *args):
|
||||
"""
|
||||
The execute method for the auth.logout operator.
|
||||
Clears any cached credentials and resets the session page back to the
|
||||
Login screen.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
if self.props.credentials:
|
||||
self.props.credentials.clear_auth()
|
||||
|
||||
self.props.credentials = None
|
||||
bpy.context.scene.batchapps_session.page = "LOGIN"
|
||||
bpy.context.scene.batchapps_session.log.info(
|
||||
"Logged out. Cached sessions cleared.")
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def auto_authentication(self, cfg, log):
|
||||
"""
|
||||
Attempts to authenticate automatically by first searching the Batch Apps
|
||||
configuration for an unattended session, then a cached session.
|
||||
|
||||
:Args:
|
||||
- cfg (:class:`batchapps.Configuration`): An instance of the Batch
|
||||
Apps Configuration class, read from the file set in the addon
|
||||
User Preferences.
|
||||
- log (:class:`batchapps.log.PickleLog`): A logger object as set in
|
||||
BatchAppsSettings.
|
||||
|
||||
:Returns:
|
||||
- ``True`` if the addon was successfully authenticated,
|
||||
else ``False``
|
||||
"""
|
||||
try:
|
||||
log.info("Checking for unattended session...")
|
||||
self.props.credentials = AzureOAuth.get_unattended_session(config=cfg)
|
||||
log.info("Found!")
|
||||
return True
|
||||
|
||||
except (AuthenticationException, InvalidConfigException) as exp:
|
||||
log.info("Could not get unattended session: {0}".format(exp))
|
||||
|
||||
try:
|
||||
log.info("Checking for cached session...")
|
||||
self.props.credentials = AzureOAuth.get_session(config=cfg)
|
||||
log.info("Found!")
|
||||
return True
|
||||
|
||||
except (AuthenticationException, InvalidConfigException) as exp:
|
||||
log.info("Could not get cached session: {0}".format(exp))
|
||||
return False
|
||||
|
||||
def wait_for_request(self):
|
||||
"""
|
||||
Once the user has been prompted to authenticate in a web browser
|
||||
session, start a basic HTTPServer to intercept the AAD redirect call
|
||||
to collect the server response to the auth request.
|
||||
|
||||
The localhost redirect URL will depend on how the client is set up in
|
||||
the AAD portal.
|
||||
|
||||
The server has a timeout of 1 minute.
|
||||
"""
|
||||
session = bpy.context.scene.batchapps_session
|
||||
self.props.code=None
|
||||
|
||||
config = bpy.context.scene.batchapps_session.cfg
|
||||
|
||||
redirect = config.aad_config()['redirect_uri'].split(':')
|
||||
server_address = (redirect[0], int(redirect[1]))
|
||||
|
||||
web_server = HTTPServer(server_address, OAuthRequestHandler)
|
||||
session.log.debug("Created web server listening at: {0}, {1}.".format(
|
||||
redirect[0], int(redirect[1])))
|
||||
|
||||
web_server.timeout = TIMEOUT
|
||||
web_server.handle_request()
|
||||
web_server.server_close()
|
||||
session.log.debug("Closed server.")
|
||||
|
||||
def open_websession(self):
|
||||
"""
|
||||
Open a web browser session to prompt the user to authenticate via their
|
||||
AAD credentials.
|
||||
This method of authentication is the 'last resort' after
|
||||
auto-authentication and unattended authentication have failed.
|
||||
|
||||
:Raises:
|
||||
- :class:`RuntimeError` if authentication fails, which will fail
|
||||
the loading of the addon as all auth routes have failed. This
|
||||
could be due to either an
|
||||
:class:`batchapps.exceptions.AuthenticationException` of a
|
||||
:class:`batchapps.exceptions.InvalidConfigException`.
|
||||
"""
|
||||
session = bpy.context.scene.batchapps_session
|
||||
|
||||
try:
|
||||
url, state = AzureOAuth.get_authorization_url(config=session.cfg)
|
||||
webbrowser.open(url)
|
||||
|
||||
session.log.info("Opened web browser for authentication "
|
||||
"and waiting for response.")
|
||||
|
||||
self.wait_for_request()
|
||||
|
||||
except (AuthenticationException, InvalidConfigException) as exp:
|
||||
session.log.error("Unable to open Web UI auth session: "
|
||||
"{0}".format(exp))
|
||||
|
||||
raise RuntimeError("Failed to authorize addon")
|
||||
|
||||
def decode_error(self, val):
|
||||
"""
|
||||
Format the auth redirect URL to extract the auth code.
|
||||
|
||||
:Args:
|
||||
- val (str): The redirect URL.
|
||||
|
||||
:Returns:
|
||||
- The auth code (str).
|
||||
"""
|
||||
error_idx = self.props.code.find(val)
|
||||
if error_idx < 0:
|
||||
return None
|
||||
|
||||
strt_idx = error_idx + len(val)
|
||||
end_idx = self.props.code.find('&', strt_idx)
|
||||
error_val = self.props.code[strt_idx:end_idx]
|
||||
|
||||
return unquote(error_val)
|
||||
|
||||
def web_authentication(self):
|
||||
"""
|
||||
Prompts user to authenticate via a web browser session, after
|
||||
auto-authentication and unattended authentication have failed.
|
||||
|
||||
If web authentication is successful, the session (i.e. refresh token)
|
||||
will be cached to enable auto-authentication next time the addon is
|
||||
used. Session page will be set to the HOME page.
|
||||
|
||||
If unsuccessful, the addon will failed to load and the ERROR page will
|
||||
be display. Error details will be logged to the console.
|
||||
|
||||
"""
|
||||
session = bpy.context.scene.batchapps_session
|
||||
|
||||
if self.auto_authentication(session.cfg, session.log):
|
||||
session.start(self.props.credentials)
|
||||
session.page = "HOME"
|
||||
session.redraw()
|
||||
return
|
||||
|
||||
self.open_websession()
|
||||
|
||||
if not self.props.code:
|
||||
session.log.warning("Log in timed out - please try again.")
|
||||
session.page = "LOGIN"
|
||||
|
||||
elif '/?error=' in self.props.code:
|
||||
error = self.decode_error('/?error=')
|
||||
details = self.decode_error(
|
||||
'&error_description=').replace('+', ' ')
|
||||
|
||||
session.log.error("Authentication failed: {0}".format(error))
|
||||
session.log.error(details)
|
||||
session.page = "ERROR"
|
||||
|
||||
else:
|
||||
session.log.info(
|
||||
"Received valid authentication response from web browser.")
|
||||
session.log.info("Now retrieving new authentication token...")
|
||||
|
||||
self.props.credentials = AzureOAuth.get_authorization_token(
|
||||
self.props.code, config=session.cfg)
|
||||
|
||||
session.start(self.props.credentials)
|
||||
session.log.info("Successful! Login complete.")
|
||||
|
||||
session.redraw()
|
|
@ -0,0 +1,185 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# Azure Batch Apps Blender Sample
|
||||
#
|
||||
# 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 bpy
|
||||
|
||||
from batchapps.exceptions import SessionExpiredException
|
||||
|
||||
|
||||
class Interface(bpy.types.Panel):
|
||||
"""
|
||||
Global Batch Apps Blender Interface. Handles the separate UI
|
||||
definitions of all the submodules based on the session page.
|
||||
Also provides custom functions for display props, ops and labels.
|
||||
"""
|
||||
|
||||
bl_label = "BatchApps Rendering"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "render"
|
||||
|
||||
COMPAT_ENGINES = ['BLENDER_RENDER', 'CYCLES']
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
"""
|
||||
Only displays the addon when a compatible render engine is
|
||||
selected. Currently: Blender Internal and Cycles only.
|
||||
"""
|
||||
render = context.scene.render
|
||||
return (render.engine in self.COMPAT_ENGINES)
|
||||
|
||||
def label(self, label, row, align=None, icon=None, active=True):
|
||||
"""
|
||||
Display a text label.
|
||||
|
||||
:Args:
|
||||
- label (str): The text to display.
|
||||
- row (:class:`bpy.types.UILayout`): The layout component to
|
||||
add the label to.
|
||||
|
||||
:Kwargs:
|
||||
- align (str): If set, will align the label according to the
|
||||
Blender enum: {'LEFT', 'RIGHT', 'CENTER', 'EXPAND'}
|
||||
- icon: (str): If set, will display an icon with the label, from
|
||||
the Blender icon list.
|
||||
- active (bool): If true, the UI label will be enabled, else it
|
||||
will be greyed out. Note that due to how this works in Blender,
|
||||
this attribute will be applied to the whole UILayout component
|
||||
not just this label.
|
||||
"""
|
||||
if align:
|
||||
row.alignment = align
|
||||
|
||||
if icon:
|
||||
row.label(text=label, icon=icon)
|
||||
|
||||
else:
|
||||
row.label(text=label)
|
||||
row.enabled = active
|
||||
|
||||
def prop(self, data, prop, row, label="",align=None, active=True,
|
||||
**kwargs):
|
||||
"""
|
||||
Display a Blender property.
|
||||
|
||||
:Args:
|
||||
- data (Blender context): The context of which the property is
|
||||
an attribute.
|
||||
- prop (str): The property (attribute name) to be displayed.
|
||||
- row (:class:`bpy.types.UILayout`): The layout component to
|
||||
dispaly the property on.
|
||||
|
||||
:Kwargs:
|
||||
- label (str): Any test to accompany the property. Default is "".
|
||||
- align (str): If set, will align the label according to the
|
||||
Blender enum: {'LEFT', 'RIGHT', 'CENTER', 'EXPAND'}
|
||||
- active (bool): If true, the UI prop will be enabled, else it
|
||||
will be greyed out. Note that due to how this works in Blender,
|
||||
this attribute will be applied to the whole UILayout component
|
||||
not just this property.
|
||||
"""
|
||||
if align:
|
||||
row.alignment = align
|
||||
|
||||
if label is not None:
|
||||
row.prop(data, prop, text=label, **kwargs)
|
||||
|
||||
else:
|
||||
row.prop(data, prop, **kwargs)
|
||||
|
||||
row.enabled = active
|
||||
|
||||
def operator(self, op, label, row, icon="NONE", align=None, active=True):
|
||||
"""
|
||||
Dispaly a registered Blender operator as a button.
|
||||
|
||||
:Args:
|
||||
- op (bpy.types.Operator): A Blender operator to display a
|
||||
button for.
|
||||
- label (str): The text to display on the button.
|
||||
- row (:class:`bpy.types.UILayout`): The layout component to
|
||||
dispaly the button on.
|
||||
|
||||
:Kwargs:
|
||||
- icon: (str): If set, will display an icon on the button, from
|
||||
the Blender icon list.
|
||||
- align (str): If set, will align the button according to the
|
||||
Blender enum: {'LEFT', 'RIGHT', 'CENTER', 'EXPAND'}
|
||||
- active (bool): If true, the UI button will be enabled, else it
|
||||
will be greyed out. Note that due to how this works in Blender,
|
||||
this attribute will be applied to the whole UILayout component
|
||||
not just this button.
|
||||
"""
|
||||
if align:
|
||||
row.alignment = align
|
||||
|
||||
row.operator("batchapps_" + op, text=label, icon=icon)
|
||||
row.enabled = active
|
||||
|
||||
def draw(self, context):
|
||||
"""
|
||||
The global draw method. This is called every time Blender's UI
|
||||
is refreshed.
|
||||
Which page of the addon is display is set by the Batch Apps session
|
||||
context.
|
||||
If the requested page is not recognized, the error page is display.
|
||||
|
||||
:Args:
|
||||
- context (bpy.types.Context): The current Blender runtime context.
|
||||
"""
|
||||
if not hasattr(context.scene, 'batchapps_session'):
|
||||
return None
|
||||
|
||||
layout = self.layout
|
||||
session = context.scene.batchapps_session
|
||||
|
||||
if session.page in session.pages:
|
||||
session.display(self, layout)
|
||||
|
||||
elif session.page in session.auth.pages:
|
||||
session.auth.display(self, layout)
|
||||
|
||||
elif session.page in session.submission.pages:
|
||||
session.submission.display(self, layout)
|
||||
|
||||
elif session.page in session.assets.pages:
|
||||
session.assets.display(self, layout)
|
||||
|
||||
elif session.page in session.pools.pages:
|
||||
session.pools.display(self, layout)
|
||||
|
||||
elif session.page in session.history.pages:
|
||||
session.history.display(self, layout)
|
||||
|
||||
else:
|
||||
session.log.error("Cant load page: {0}. "
|
||||
"No definition found.".format(session.page))
|
||||
|
||||
session.page = "ERROR"
|
||||
session.display(self, layout)
|
||||
|
|
@ -0,0 +1,435 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# Azure Batch Apps Blender Sample
|
||||
#
|
||||
# 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 bpy
|
||||
import os
|
||||
|
||||
import threading
|
||||
|
||||
from batchapps_blender.utils import BatchAppsOps
|
||||
from batchapps_blender.ui import ui_history
|
||||
from batchapps_blender.props import props_history
|
||||
|
||||
from batchapps.exceptions import RestCallException
|
||||
|
||||
class BatchAppsHistory(object):
|
||||
"""
|
||||
Manger for the retrival and display of the users job history.
|
||||
"""
|
||||
|
||||
pages = ["HISTORY", "LOADING"]
|
||||
|
||||
def __init__(self, manager):
|
||||
|
||||
self.batchapps = manager
|
||||
self.ops = self._register_ops()
|
||||
self.props = self._register_props()
|
||||
self.ui = self._register_ui()
|
||||
|
||||
def display(self, ui, layout):
|
||||
"""
|
||||
Invokes the corresponding ui function depending on the session's
|
||||
current page.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
:Returns:
|
||||
- Runs the display function for the applicable page.
|
||||
"""
|
||||
return self.ui[bpy.context.scene.batchapps_session.page](ui, layout)
|
||||
|
||||
def _register_props(self):
|
||||
"""
|
||||
Registers and retrieves the history property objects.
|
||||
The dispaly properties are defined in a subclass which is assigned
|
||||
to the scene.batchapps_history context.
|
||||
|
||||
:Returns:
|
||||
- :class:`.HistoryProps`
|
||||
"""
|
||||
props = props_history.register_props()
|
||||
return props
|
||||
|
||||
def _register_ops(self):
|
||||
"""
|
||||
Registers each job history operator with a batchapps_history prefix.
|
||||
|
||||
:Returns:
|
||||
- A list of the names (str) of the registered job history
|
||||
operators.
|
||||
"""
|
||||
ops = []
|
||||
ops.append(BatchAppsOps.register("history.page",
|
||||
"Job history",
|
||||
self._history))
|
||||
ops.append(BatchAppsOps.register("history.first",
|
||||
"Beginning",
|
||||
self._first))
|
||||
ops.append(BatchAppsOps.register("history.last",
|
||||
"End",
|
||||
self._last))
|
||||
ops.append(BatchAppsOps.register("history.more",
|
||||
"Next",
|
||||
self._more))
|
||||
ops.append(BatchAppsOps.register("history.less",
|
||||
"Previous",
|
||||
self._less))
|
||||
ops.append(BatchAppsOps.register("history.refresh",
|
||||
"Refresh",
|
||||
self._refresh))
|
||||
ops.append(BatchAppsOps.register("history.cancel",
|
||||
"Cancel job",
|
||||
self._cancel))
|
||||
ops.append(BatchAppsOps.register("history.loading",
|
||||
"Loading job history",
|
||||
modal=self._loading_modal,
|
||||
invoke=self._loading_invoke,
|
||||
_timer=None))
|
||||
return ops
|
||||
|
||||
def _register_ui(self):
|
||||
"""
|
||||
Matches the history and loading pages with their corresponding
|
||||
ui functions.
|
||||
|
||||
:Returns:
|
||||
- A dictionary mapping the page name to its corresponding
|
||||
ui function.
|
||||
"""
|
||||
def get_history_ui(name):
|
||||
name = name.lower()
|
||||
return getattr(ui_history, name)
|
||||
|
||||
page_func = map(get_history_ui, self.pages)
|
||||
return dict(zip(self.pages, page_func))
|
||||
|
||||
def _loading_modal(self, op, context, event):
|
||||
"""
|
||||
The modal method for the history.loading operator to handle running
|
||||
the downloading of the job history data in a separate thread to
|
||||
prevent the blocking of the Blender UI.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
- event (:class:`bpy.types.Event`): The blender invocation event.
|
||||
|
||||
:Returns:
|
||||
- If the thread has completed, the Blender-specific value
|
||||
{'FINISHED'} to indicate the operator has completed its action.
|
||||
- Otherwise the Blender-specific value {'RUNNING_MODAL'} to
|
||||
indicate the operator wil continue to process after the
|
||||
completion of this function.
|
||||
"""
|
||||
if event.type == 'TIMER':
|
||||
context.scene.batchapps_session.log.debug("HistoryThread complete.")
|
||||
if not self.props.thread.is_alive():
|
||||
context.window_manager.event_timer_remove(op._timer)
|
||||
return {'FINISHED'}
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def _loading_invoke(self, op, context, event):
|
||||
"""
|
||||
The invoke method for the history.loading operator.
|
||||
Starts the job data retrieval thread.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
- event (:class:`bpy.types.Event`): The blender invocation event.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'RUNNING_MODAL'} to indicate the operator
|
||||
wil continue to process after the completion of this function.
|
||||
"""
|
||||
self.props.thread.start()
|
||||
context.scene.batchapps_session.log.debug("HistoryThread initiated.")
|
||||
|
||||
context.window_manager.modal_handler_add(op)
|
||||
op._timer = context.window_manager.event_timer_add(1, context.window)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def _history(self, op, context, *args):
|
||||
"""
|
||||
The execute method for the history.page operator.
|
||||
Sets the functions to be performed by the job data retrieval thread
|
||||
and updates the session page to "LOADING" while the thread executes.
|
||||
|
||||
Also resets the job display paging controls.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
self.props.display = context.scene.batchapps_history
|
||||
self.props.display.selected = -1
|
||||
self.props.display.index = 0
|
||||
self.props.display.total_count = 0
|
||||
|
||||
history_thread = lambda: BatchAppsOps.session(self.get_job_list)
|
||||
self.props.thread = threading.Thread(name="HistoryThread",
|
||||
target=history_thread)
|
||||
|
||||
bpy.ops.batchapps_history.loading('INVOKE_DEFAULT')
|
||||
|
||||
if context.scene.batchapps_session.page == "HOME":
|
||||
context.scene.batchapps_session.page = "LOADING"
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def _first(self, op, context, *args):
|
||||
"""
|
||||
The execute method for the history.first operator.
|
||||
Resets the job display paging index to 0 (to display the first jobs
|
||||
in the list) and re-loads the accompanying job data.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
self.props.display.index = 0
|
||||
self.get_job_list()
|
||||
return {'FINISHED'}
|
||||
|
||||
def _last(self, op, context, *args):
|
||||
"""
|
||||
The execute method for the history.last operator.
|
||||
Resets the job display paging controls to display the last jobs
|
||||
in the list and re-loads the accompanying job data.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
settings = self.props.display
|
||||
|
||||
if (settings.total_count % settings.per_call) == 0:
|
||||
settings.index = settings.total_count - settings.per_call
|
||||
|
||||
else:
|
||||
div = settings.per_call - (settings.total_count % settings.per_call)
|
||||
settings.index = settings.total_count - settings.per_call + div
|
||||
|
||||
self.get_job_list()
|
||||
return {'FINISHED'}
|
||||
|
||||
def _more(self, op, context, *args):
|
||||
"""
|
||||
The execute method for the history.more operator.
|
||||
Resets the job display paging controls to display the subsequent jobs
|
||||
in the list and re-loads the accompanying job data.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
self.props.display.index = self.props.display.index + self.props.display.per_call
|
||||
self.get_job_list()
|
||||
return {'FINISHED'}
|
||||
|
||||
def _less(self, op, context, *args):
|
||||
"""
|
||||
The execute method for the history.less operator.
|
||||
Resets the job display paging controls to display the previous jobs
|
||||
in the list and re-loads the accompanying job data.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
self.props.display.index = self.props.display.index - self.props.display.per_call
|
||||
self.get_job_list()
|
||||
return {'FINISHED'}
|
||||
|
||||
def _refresh(self, op, context, *args):
|
||||
"""
|
||||
The execute method for the history.refresh operator.
|
||||
Re-loads the current job data.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
self.get_job_list()
|
||||
return {'FINISHED'}
|
||||
|
||||
def _cancel(self, op, context, *args):
|
||||
"""
|
||||
The execute method for the history.cancel operator.
|
||||
Cancels the currently selected job.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
job = self.get_selected_job()
|
||||
context.scene.batchapps_session.log.debug(
|
||||
"Selected job {0}".format(job.id))
|
||||
|
||||
job.cancel()
|
||||
job.update()
|
||||
context.scene.batchapps_session.log.info(
|
||||
"Cancelled with ID: {0}".format(job.id))
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def get_selected_job(self):
|
||||
"""
|
||||
Retrieves the job object for the job currently selected in
|
||||
the dispaly.
|
||||
|
||||
:Returns:
|
||||
- A :class:`batchapps.jobs.SubmittedJob` object.
|
||||
"""
|
||||
return self.props.job_list[self.props.display.selected]
|
||||
|
||||
def get_job_list(self):
|
||||
"""
|
||||
Downlaods a set of job data based on index and default per call parameter,
|
||||
assigns it to the property job_list and redraws the HISTORY page to
|
||||
display the new data.
|
||||
|
||||
Each job is also registered as an operator class.
|
||||
#TODO: Unregister previous job classes?
|
||||
"""
|
||||
self.props.job_list = []
|
||||
self.props.display.jobs.clear()
|
||||
|
||||
bpy.context.scene.batchapps_session.log.debug(
|
||||
"Getting job data: index {0}, total {1}, percall {2}".format(
|
||||
self.props.display.index,
|
||||
self.props.display.total_count,
|
||||
self.props.display.per_call))
|
||||
|
||||
|
||||
latest_jobs = self.batchapps.get_jobs(
|
||||
index=self.props.display.index,
|
||||
per_call=self.props.display.per_call)
|
||||
|
||||
for job in latest_jobs:
|
||||
self.props.job_list.append(job)
|
||||
self.props.display.add_job(job)
|
||||
|
||||
self.props.display.total_count = len(self.batchapps)
|
||||
for index, job in enumerate(self.props.display.jobs):
|
||||
self.register_job(job, index)
|
||||
|
||||
bpy.context.scene.batchapps_session.log.info(
|
||||
"Retrieved {0} of {1} job "
|
||||
"listings.".format(len(latest_jobs),
|
||||
self.props.display.total_count))
|
||||
|
||||
bpy.context.scene.batchapps_session.page = "HISTORY"
|
||||
bpy.context.scene.batchapps_session.redraw()
|
||||
|
||||
|
||||
def register_job(self, job, index):
|
||||
"""
|
||||
Register a job as an operator class for dispaly in the UI.
|
||||
|
||||
:Args:
|
||||
- job (:class:`batchapps.jobs.SubmittedJob`): The job to register.
|
||||
- index (int): The index of the job in list currently displayed.
|
||||
|
||||
:Returns:
|
||||
- The newly registered operator name (str).
|
||||
"""
|
||||
name = "history.{0}".format(job.id.replace("-", "_"))
|
||||
label = "Job: {0}".format(job.name)
|
||||
index_prop = bpy.props.IntProperty(default=index)
|
||||
|
||||
def execute(self):
|
||||
session = bpy.context.scene.batchapps_history
|
||||
bpy.context.scene.batchapps_session.log.debug(
|
||||
"Job details opened: {0}, selected: {1}, index {2}".format(
|
||||
self.enabled,
|
||||
session.selected,
|
||||
self.ui_index))
|
||||
|
||||
if self.enabled and session.selected == self.ui_index:
|
||||
session.selected = -1
|
||||
|
||||
else:
|
||||
session.selected = self.ui_index
|
||||
|
||||
bpy.context.scene.batchapps_session.log.debug(
|
||||
"Registering {0}".format(name))
|
||||
|
||||
return BatchAppsOps.register_expanding(name, label, execute,
|
||||
ui_index=index_prop)
|
|
@ -0,0 +1,273 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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 bpy
|
||||
import os
|
||||
|
||||
import threading
|
||||
|
||||
from batchapps_blender.utils import BatchAppsOps
|
||||
from batchapps_blender.ui import ui_pools
|
||||
from batchapps_blender.props import props_pools
|
||||
|
||||
from batchapps.exceptions import RestCallException
|
||||
|
||||
class BatchAppsPools(object):
|
||||
"""
|
||||
Manager for the display and creation of Batch Apps instance pools.
|
||||
"""
|
||||
|
||||
pages = ["POOLS", "CREATE"]
|
||||
|
||||
def __init__(self, manager):
|
||||
|
||||
self.batchapps = manager
|
||||
|
||||
self.ops = self._register_ops()
|
||||
self.props = self._register_props()
|
||||
self.ui = self._register_ui()
|
||||
|
||||
def display(self, ui, layout):
|
||||
"""
|
||||
Invokes the corresponding ui function depending on the session's
|
||||
current page.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
:Returns:
|
||||
- Runs the display function for the applicable page.
|
||||
"""
|
||||
return self.ui[bpy.context.scene.batchapps_session.page](ui, layout)
|
||||
|
||||
def _register_props(self):
|
||||
"""
|
||||
Registers and retrieves the pools property objects.
|
||||
The dispaly properties are defined in a subclass which is assigned
|
||||
to the scene.batchapps_pools context.
|
||||
|
||||
:Returns:
|
||||
- :class:`.PoolsProps`
|
||||
"""
|
||||
props = props_pools.register_props()
|
||||
return props
|
||||
|
||||
def _register_ops(self):
|
||||
"""
|
||||
Registers each pool operator with a batchapps_pools prefix.
|
||||
|
||||
:Returns:
|
||||
- A list of the names (str) of the registered pool operators.
|
||||
"""
|
||||
ops = []
|
||||
ops.append(BatchAppsOps.register("pools.page",
|
||||
"Running pools",
|
||||
self._pools))
|
||||
ops.append(BatchAppsOps.register("pools.start",
|
||||
"Start new pool",
|
||||
self._start))
|
||||
ops.append(BatchAppsOps.register("pools.delete",
|
||||
"Delete pool",
|
||||
self._delete))
|
||||
ops.append(BatchAppsOps.register_expanding("pools.create",
|
||||
"Create pool",
|
||||
self._create))
|
||||
return ops
|
||||
|
||||
def _register_ui(self):
|
||||
"""
|
||||
Matches the pools and create pool pages with their corresponding
|
||||
ui functions.
|
||||
|
||||
:Returns:
|
||||
- A dictionary mapping the page name to its corresponding
|
||||
ui function.
|
||||
"""
|
||||
def get_pools_ui(name):
|
||||
name = name.lower()
|
||||
return getattr(ui_pools, name)
|
||||
|
||||
page_func = map(get_pools_ui, self.pages)
|
||||
return dict(zip(self.pages, page_func))
|
||||
|
||||
def _pools(self, op, context):
|
||||
"""
|
||||
The execute method for the pools.page operator.
|
||||
Downloads the data on the pools currently running in the service and
|
||||
registers each as an operator for display in the UI.
|
||||
|
||||
Sets the page to POOLS.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
self.props.display = bpy.context.scene.batchapps_pools
|
||||
|
||||
self.props.display.pools.clear()
|
||||
self.props.display.selected = -1
|
||||
|
||||
context.scene.batchapps_session.log.debug("Getting pool data.")
|
||||
|
||||
self.props.pools = self.batchapps.get_pools()
|
||||
context.scene.batchapps_session.log.info(
|
||||
"Retrieved {0} pool references.".format(len(self.props.pools)))
|
||||
|
||||
for pool in self.props.pools:
|
||||
self.props.display.add_pool(pool)
|
||||
|
||||
for index, pool in enumerate(self.props.display.pools):
|
||||
self.register_pool(pool, index)
|
||||
|
||||
context.scene.batchapps_session.page = "POOLS"
|
||||
return {'FINISHED'}
|
||||
|
||||
def _start(self, op, context):
|
||||
"""
|
||||
The execute method for the pools.start operator.
|
||||
Starts a newly created pool, then calls the pools.page operator
|
||||
to refresh the pool list in the display and return to the POOLS page.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
new_pool = self.batchapps.create(
|
||||
target_size=self.props.display.pool_size)
|
||||
|
||||
context.scene.batchapps_session.log.info(
|
||||
"Started new pool with ID: {0}".format(new_pool.id))
|
||||
|
||||
return bpy.ops.batchapps_pools.page()
|
||||
|
||||
def _delete(self, op, context):
|
||||
"""
|
||||
The execute method for the pools.delete operator.
|
||||
Delete the currently selected pool, then calls the pools.page
|
||||
operator to refresh the pool list in the display.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
pool = self.get_selected_pool()
|
||||
context.scene.batchapps_session.log.debug(
|
||||
"Selected pool {0}".format(pool.id))
|
||||
|
||||
pool.delete()
|
||||
context.scene.batchapps_session.log.info(
|
||||
"Deleted pool with ID: {0}".format(pool.id))
|
||||
|
||||
return bpy.ops.batchapps_pools.page()
|
||||
|
||||
def _create(self, op):
|
||||
"""
|
||||
The execute method for the pools.create operator.
|
||||
Display the UI components to create a new pool by setting the page
|
||||
to CREATE.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
session = bpy.context.scene.batchapps_session
|
||||
session.page = "POOLS" if op.enabled else "CREATE"
|
||||
return {'FINISHED'}
|
||||
|
||||
def get_selected_pool(self):
|
||||
"""
|
||||
Retrieves the pool object for the pool currently selected in
|
||||
the dispaly.
|
||||
|
||||
:Returns:
|
||||
- A :class:`batchapps.pools.Pool` object.
|
||||
"""
|
||||
return self.props.pools[self.props.display.selected]
|
||||
|
||||
def register_pool(self, pool, index):
|
||||
"""
|
||||
Register a pool as an operator class for dispaly in the UI.
|
||||
|
||||
:Args:
|
||||
- pool (:class:`batchapps.jobs.SubmittedJob`): The pool to
|
||||
register.
|
||||
- index (int): The index of the job in list currently displayed.
|
||||
|
||||
:Returns:
|
||||
- The newly registered operator name (str).
|
||||
"""
|
||||
name = "pools.{0}".format(pool.id.replace("-", "_"))
|
||||
label = "Pool: {0}".format(pool.id)
|
||||
index_prop = bpy.props.IntProperty(default=index)
|
||||
|
||||
def execute(self):
|
||||
session = bpy.context.scene.batchapps_pools
|
||||
bpy.context.scene.batchapps_session.log.debug(
|
||||
"Pool details opened: {0}, selected: {1}, index {2}".format(
|
||||
self.enabled,
|
||||
session.selected,
|
||||
self.ui_index))
|
||||
|
||||
if self.enabled and session.selected == self.ui_index:
|
||||
session.selected = -1
|
||||
|
||||
else:
|
||||
session.selected = self.ui_index
|
||||
|
||||
bpy.context.scene.batchapps_session.log.debug(
|
||||
"Registering {0}".format(name))
|
||||
|
||||
return BatchAppsOps.register_expanding(name, label, execute,
|
||||
ui_index=index_prop)
|
|
@ -0,0 +1,193 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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 bpy
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def on_load(*args):
|
||||
"""
|
||||
Handler to ensure assets list is reset inbetween .blend files.
|
||||
Also resets the job file path inbetween blend files.
|
||||
|
||||
Run on blend file load.
|
||||
|
||||
"""
|
||||
bpy.context.scene.batchapps_assets.path = ""
|
||||
if bpy.context.scene.batchapps_session.page == "ASSETS":
|
||||
bpy.ops.batchapps_assets.refresh()
|
||||
|
||||
def format_date(asset):
|
||||
"""
|
||||
Format an assets last modified date for the UI.
|
||||
|
||||
:Args:
|
||||
- asset (:class:`batchapps.files.UserFile`): Asset whos date we
|
||||
want to format.
|
||||
|
||||
:Returns:
|
||||
- The last modified date as a string. If formatting fails,
|
||||
an empty string.
|
||||
"""
|
||||
try:
|
||||
datelist = asset.get_last_modified().split('T')
|
||||
datelist[1] = datelist[1].split('.')[0]
|
||||
return ' '.join(datelist)
|
||||
|
||||
except:
|
||||
bpy.context.scene.batchapps_session.log.debug(
|
||||
"Couldn't format date {0}.".format(asset.get_last_modified()))
|
||||
return ""
|
||||
|
||||
class AssetDisplayProps(bpy.types.PropertyGroup):
|
||||
"""
|
||||
A display object representing an asset.
|
||||
Displayed by :class:`.ui_assets.AssetListUI`.
|
||||
"""
|
||||
|
||||
name = bpy.props.StringProperty(
|
||||
description="Asset filename")
|
||||
|
||||
fullpath = bpy.props.StringProperty(
|
||||
description="Asset full path")
|
||||
|
||||
upload_checkbox = bpy.props.BoolProperty(
|
||||
description = "Check to upload asset",
|
||||
default = False)
|
||||
|
||||
upload_check = bpy.props.BoolProperty(
|
||||
description="Selected for upload",
|
||||
default=False)
|
||||
|
||||
timestamp = bpy.props.StringProperty(
|
||||
description="Asset last modified timestamp",
|
||||
default="")
|
||||
|
||||
class AssetProps(bpy.types.PropertyGroup):
|
||||
"""
|
||||
Asset Properties,
|
||||
Once instantiated, this class is set to both the Blender context, and
|
||||
assigned to assets.BatchAppsAssets.props.
|
||||
"""
|
||||
|
||||
collection = []
|
||||
|
||||
path = bpy.props.StringProperty(
|
||||
description="Blend file path to be rendered")
|
||||
|
||||
temp = bpy.props.BoolProperty(
|
||||
description="Whether we're using a temp blend file",
|
||||
default=False)
|
||||
|
||||
assets = bpy.props.CollectionProperty(
|
||||
type=AssetDisplayProps,
|
||||
description="Asset display list")
|
||||
|
||||
index = bpy.props.IntProperty(
|
||||
description="Selected asset index")
|
||||
|
||||
def add_asset(self, asset):
|
||||
"""
|
||||
Add an asset to both the display and object lists.
|
||||
|
||||
"""
|
||||
log = bpy.context.scene.batchapps_session.log
|
||||
log.debug("Adding asset to ui list {0}.".format(asset.name))
|
||||
|
||||
uploaded = asset.is_uploaded()
|
||||
|
||||
self.collection.append(asset)
|
||||
self.assets.add()
|
||||
entry = self.assets[-1]
|
||||
entry.name = asset.name
|
||||
entry.timestamp = format_date(asset)
|
||||
entry.fullpath = asset.path
|
||||
entry.upload_check = False if uploaded is None else True
|
||||
|
||||
log.debug("Total assets now {0}.".format(len(self.assets)))
|
||||
|
||||
def remove_selected(self):
|
||||
"""
|
||||
Remove selected asset from both display and object lists.
|
||||
|
||||
"""
|
||||
bpy.context.scene.batchapps_session.log.debug(
|
||||
"Removing index {0}.".format(self.index))
|
||||
|
||||
self.collection.pop(self.index)
|
||||
self.assets.remove(self.index)
|
||||
self.index = max(self.index - 1, 0)
|
||||
|
||||
def get_jobfile(self):
|
||||
"""
|
||||
Get the asset object whos path is the job file path.
|
||||
|
||||
"""
|
||||
log = bpy.context.scene.batchapps_session.log
|
||||
|
||||
for asset in self.collection:
|
||||
if asset.path == self.path:
|
||||
log.debug("Found job asset at {0}".format(self.path))
|
||||
return asset
|
||||
else:
|
||||
log.debug("Found no job asset, using {0}".format(self.path))
|
||||
return self.path
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Clear both asset display and object lists.
|
||||
|
||||
"""
|
||||
self.collection.clear()
|
||||
self.assets.clear()
|
||||
self.index = 0
|
||||
|
||||
bpy.context.scene.batchapps_session.log.debug("Reset asset lists.")
|
||||
|
||||
def set_uploaded(self):
|
||||
"""
|
||||
Mark all assets as having been uploaded.
|
||||
|
||||
"""
|
||||
for asset in self.assets:
|
||||
asset.upload_check = True
|
||||
|
||||
|
||||
def register_props():
|
||||
"""
|
||||
Register the asset property classes and assign to the blender
|
||||
context under "batchapps_assets".
|
||||
|
||||
:Returns:
|
||||
- A :class:`.AssetProps` object
|
||||
"""
|
||||
|
||||
bpy.types.Scene.batchapps_assets = \
|
||||
bpy.props.PointerProperty(type=AssetProps)
|
||||
|
||||
bpy.app.handlers.load_post.append(on_load)
|
||||
|
||||
return bpy.context.scene.batchapps_assets
|
|
@ -0,0 +1,57 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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 bpy
|
||||
|
||||
class AuthProps(object):
|
||||
"""
|
||||
Auth Properties.
|
||||
Once instantiated, this class is set to both the Blender context, and
|
||||
assigned to auth.BatchAppsAuth.props.
|
||||
"""
|
||||
|
||||
thread = None
|
||||
code = None
|
||||
credentials = None
|
||||
|
||||
|
||||
def register_props():
|
||||
"""
|
||||
Register the auth property class and assign to the blender
|
||||
context under "batchapps_auth".
|
||||
|
||||
:Returns:
|
||||
- A :class:`.AuthProps` object
|
||||
|
||||
"""
|
||||
props_obj = AuthProps()
|
||||
|
||||
def get_props(self):
|
||||
return props_obj
|
||||
|
||||
bpy.types.Scene.batchapps_auth = property(get_props)
|
||||
return props_obj
|
|
@ -0,0 +1,176 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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 bpy
|
||||
|
||||
def format_date(job):
|
||||
"""
|
||||
Format a job submitted date for the UI.
|
||||
|
||||
:Args:
|
||||
- jon (:class:`batchapps.job.SubmittedJob`): Job whos date we want to
|
||||
format.
|
||||
|
||||
:Returns:
|
||||
- The submitted date as a string. If formatting fails,
|
||||
an empty string.
|
||||
"""
|
||||
try:
|
||||
datelist = job.time_submitted.split('T')
|
||||
datelist[1] = datelist[1].split('.')[0]
|
||||
return ' '.join(datelist)
|
||||
|
||||
except:
|
||||
bpy.context.scene.batchapps_session.log.debug(
|
||||
"Couldn't format date {0}.".format(job.time_submitted))
|
||||
return ""
|
||||
|
||||
|
||||
class HistoryDetails(bpy.types.PropertyGroup):
|
||||
"""A display object representing a job."""
|
||||
|
||||
id = bpy.props.StringProperty(
|
||||
description="Job ID",
|
||||
default="")
|
||||
|
||||
name = bpy.props.StringProperty(
|
||||
description="Job Name",
|
||||
default="")
|
||||
|
||||
type = bpy.props.StringProperty(
|
||||
description="Job Type",
|
||||
default="")
|
||||
|
||||
status = bpy.props.StringProperty(
|
||||
description="Job Status",
|
||||
default="")
|
||||
|
||||
timestamp = bpy.props.StringProperty(
|
||||
description="Time Submitted",
|
||||
default="")
|
||||
|
||||
percent = bpy.props.IntProperty(
|
||||
description="Percent Complete",
|
||||
default=0)
|
||||
|
||||
pool_id = bpy.props.StringProperty(
|
||||
description="Job Pool ID",
|
||||
default="Unknown")
|
||||
|
||||
tasks = bpy.props.IntProperty(
|
||||
description="Number of Tasks",
|
||||
default=0)
|
||||
|
||||
|
||||
class HistoryDisplayProps(bpy.types.PropertyGroup):
|
||||
"""
|
||||
Display object representing a job list.
|
||||
This class is added to the Blender context.
|
||||
"""
|
||||
|
||||
selected = bpy.props.IntProperty(
|
||||
description="Selected Job",
|
||||
default=-1)
|
||||
|
||||
jobs = bpy.props.CollectionProperty(
|
||||
type=HistoryDetails,
|
||||
description="Job History")
|
||||
|
||||
per_call = bpy.props.IntProperty(
|
||||
description="Jobs Per Call",
|
||||
default=10,
|
||||
min=1,
|
||||
soft_min=1,
|
||||
max=50,
|
||||
soft_max=50)
|
||||
|
||||
total_count = bpy.props.IntProperty(
|
||||
description="Total Job Count",
|
||||
default=0)
|
||||
|
||||
index = bpy.props.IntProperty(
|
||||
description="Job display index",
|
||||
default=0)
|
||||
|
||||
icons = {
|
||||
'inprogress': 'PREVIEW_RANGE',
|
||||
'complete': 'FILE_TICK',
|
||||
'cancelled': 'CANCEL',
|
||||
'error': 'ERROR',
|
||||
'notstarted': 'TIME'
|
||||
}
|
||||
|
||||
def add_job(self, job):
|
||||
"""
|
||||
Add a job to the job display list.
|
||||
|
||||
"""
|
||||
log = bpy.context.scene.batchapps_session.log
|
||||
log.debug("Adding job to ui list {0}".format(job.id))
|
||||
|
||||
self.jobs.add()
|
||||
entry = self.jobs[-1]
|
||||
entry.id = job.id
|
||||
entry.name = job.name
|
||||
entry.type = job.type
|
||||
entry.status = job.status
|
||||
entry.tasks = job.number_tasks
|
||||
entry.percent = job.percentage if job.percentage else 0
|
||||
entry.timestamp = format_date(job)
|
||||
|
||||
if job.pool_id:
|
||||
entry.pool_id = job.pool_id
|
||||
|
||||
|
||||
class HistoryProps(object):
|
||||
"""
|
||||
History properties.
|
||||
Once instantiated, this class is assigned to assets.BatchAppsAssets.props
|
||||
but is not added to the Blender context.
|
||||
"""
|
||||
|
||||
job_list = []
|
||||
display = None
|
||||
thread = None
|
||||
|
||||
|
||||
def register_props():
|
||||
"""
|
||||
Register the history property classes and assign to the blender
|
||||
context under "batchapps_history".
|
||||
|
||||
:Returns:
|
||||
- A :class:`.HistoryProps` object
|
||||
|
||||
"""
|
||||
props_obj = HistoryProps()
|
||||
|
||||
bpy.types.Scene.batchapps_history = \
|
||||
bpy.props.PointerProperty(type=HistoryDisplayProps)
|
||||
props_obj.display = bpy.context.scene.batchapps_history
|
||||
|
||||
return props_obj
|
|
@ -0,0 +1,159 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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 bpy
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def on_load(*args):
|
||||
"""
|
||||
Event handler to refresh pools when a new blender scene is opened.
|
||||
|
||||
Run on blend file load when page is POOLS or CREATE.
|
||||
"""
|
||||
if bpy.context.scene.batchapps_session.page in ["POOLS", "CREATE"]:
|
||||
bpy.ops.batchapps_pools.page()
|
||||
|
||||
def format_date(pool):
|
||||
"""
|
||||
Format a pool created date for the UI.
|
||||
|
||||
:Args:
|
||||
- pool (:class:`batchapps.pool.Pool`): Pool whos date we want to
|
||||
format.
|
||||
|
||||
:Returns:
|
||||
- The created date as a string. If formatting fails,
|
||||
an empty string.
|
||||
"""
|
||||
try:
|
||||
datelist = pool.created.split('T')
|
||||
datelist[1] = datelist[1].split('.')[0]
|
||||
return ' '.join(datelist)
|
||||
|
||||
except:
|
||||
bpy.context.scene.batchapps_session.log.debug(
|
||||
"Couldn't format date {0}.".format(pool.created))
|
||||
return ""
|
||||
|
||||
class PoolDetails(bpy.types.PropertyGroup):
|
||||
"""
|
||||
A display object representing a pool.
|
||||
his class is added to the Blender context.
|
||||
"""
|
||||
|
||||
id = bpy.props.StringProperty(
|
||||
description="Pool ID",
|
||||
default="")
|
||||
|
||||
auto = bpy.props.BoolProperty(
|
||||
description="Auto Pool or manually provisioned",
|
||||
default=True)
|
||||
|
||||
created = bpy.props.StringProperty(
|
||||
description="When pool was created",
|
||||
default="")
|
||||
|
||||
target = bpy.props.IntProperty(
|
||||
description="Pool target size",
|
||||
default=0)
|
||||
|
||||
current = bpy.props.IntProperty(
|
||||
description="Pool current size",
|
||||
default=0)
|
||||
|
||||
state = bpy.props.StringProperty(
|
||||
description="Pool State",
|
||||
default="")
|
||||
|
||||
queue = bpy.props.IntProperty(
|
||||
description="Pool Queue",
|
||||
default=0)
|
||||
|
||||
class PoolDisplayProps(bpy.types.PropertyGroup):
|
||||
"""Display object representing a pool list"""
|
||||
|
||||
selected = bpy.props.IntProperty(
|
||||
description="Selected pool",
|
||||
default=-1)
|
||||
|
||||
pool_size = bpy.props.IntProperty(
|
||||
description="Number of instances in new pool",
|
||||
default=3,
|
||||
min=3,
|
||||
max=20)
|
||||
|
||||
pools = bpy.props.CollectionProperty(
|
||||
type=PoolDetails,
|
||||
description="Pools currently running")
|
||||
|
||||
def add_pool(self, pool):
|
||||
"""
|
||||
Add a pool reference to the pool display list.
|
||||
|
||||
"""
|
||||
log = bpy.context.scene.batchapps_session.log
|
||||
log.debug("Adding pool to ui list {0}".format(pool.id))
|
||||
|
||||
self.pools.add()
|
||||
entry = self.pools[-1]
|
||||
entry.id = pool.id
|
||||
entry.auto = pool.auto
|
||||
entry.created = format_date(pool)
|
||||
entry.target = pool.target_size
|
||||
entry.current = pool.current_size
|
||||
entry.state = pool.state
|
||||
entry.queue = len(pool.jobs)
|
||||
|
||||
class PoolsProps(object):
|
||||
"""
|
||||
Pools Properties.
|
||||
Once instantiated, this class is assigned to pools.BatchAppsPools.props
|
||||
but is not added to the Blender context.
|
||||
"""
|
||||
|
||||
pools = []
|
||||
display = None
|
||||
thread = None
|
||||
|
||||
def register_props():
|
||||
"""
|
||||
Register the pool property classes and assign to the blender
|
||||
context under "batchapps_pools".
|
||||
Also registers pool event handlers.
|
||||
|
||||
:Returns:
|
||||
- A :class:`.PoolsProps` object
|
||||
|
||||
"""
|
||||
props_obj = PoolsProps()
|
||||
bpy.types.Scene.batchapps_pools = \
|
||||
bpy.props.PointerProperty(type=PoolDisplayProps)
|
||||
|
||||
props_obj.display = bpy.context.scene.batchapps_pools
|
||||
bpy.app.handlers.load_post.append(on_load)
|
||||
|
||||
return props_obj
|
|
@ -0,0 +1,115 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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 bpy
|
||||
import os
|
||||
|
||||
|
||||
class BatchAppsPreferences(bpy.types.AddonPreferences):
|
||||
"""BatchApps Blender addon user preferences."""
|
||||
|
||||
bl_idname = __package__.split('.')[0]
|
||||
|
||||
ini_file = bpy.props.StringProperty(
|
||||
name="Configuration file",
|
||||
description="BatchApps config file",
|
||||
subtype='FILE_NAME',
|
||||
default="batch_apps.ini")
|
||||
|
||||
data_dir = bpy.props.StringProperty(
|
||||
name="Data directory",
|
||||
description="Location of config file and log file",
|
||||
subtype='DIR_PATH',
|
||||
default=os.path.join(os.path.expanduser('~'), 'BatchAppsData'))
|
||||
|
||||
log_level = bpy.props.EnumProperty(items=(('10', 'Debug', ''),
|
||||
('20', 'Info', ''),
|
||||
('30', 'Warning', ''),
|
||||
('40', 'Error', ''),
|
||||
('50', 'Critical', '')),
|
||||
name="Logging level",
|
||||
description="Level of logging detail",
|
||||
default="30")
|
||||
|
||||
account = bpy.props.StringProperty(
|
||||
name="Unattended Account",
|
||||
description="Batch Apps Unattended Account",
|
||||
default="")
|
||||
|
||||
key = bpy.props.StringProperty(
|
||||
name="Unattended Key",
|
||||
description="Batch Apps Unattended Account key",
|
||||
default="")
|
||||
|
||||
endpoint = bpy.props.StringProperty(
|
||||
name="Service URL",
|
||||
description="Batch Apps service endpoint",
|
||||
default="")
|
||||
|
||||
client_id = bpy.props.StringProperty(
|
||||
name="Client ID",
|
||||
description="AAD Client ID",
|
||||
default="")
|
||||
|
||||
tenant = bpy.props.StringProperty(
|
||||
name="Tenant",
|
||||
description="AAD auth tenant",
|
||||
default="")
|
||||
|
||||
redirect = bpy.props.StringProperty(
|
||||
name="Redirect URI",
|
||||
description="AAD auth redirect URI",
|
||||
default="")
|
||||
|
||||
def draw(self, context):
|
||||
"""
|
||||
Draw the display for the settings in the User Preferences
|
||||
with next to the Addon entry.
|
||||
|
||||
:Args:
|
||||
- context (bpy.types.Context): Blenders current runtime
|
||||
context.
|
||||
|
||||
"""
|
||||
layout = self.layout
|
||||
layout.label(text="Blender will need to be restarted "
|
||||
"for changes to take effect.")
|
||||
|
||||
layout.prop(self, "data_dir")
|
||||
layout.prop(self, "ini_file")
|
||||
layout.prop(self, "log_level")
|
||||
|
||||
layout.label(text="")
|
||||
layout.label(text="Service Authentication configuration. "
|
||||
"These settings will override those in the config file.")
|
||||
|
||||
layout.prop(self, "endpoint")
|
||||
layout.prop(self, "account")
|
||||
layout.prop(self, "key")
|
||||
layout.prop(self, "client_id")
|
||||
layout.prop(self, "tenant")
|
||||
layout.prop(self, "redirect")
|
|
@ -0,0 +1,206 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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 bpy
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def framecheck(*args):
|
||||
"""
|
||||
Event handler to detect when global frame range has been changed
|
||||
and updates addon UI accordingly.
|
||||
Also issues a warning if the selected frame range falls outside the
|
||||
global frame range.
|
||||
|
||||
Run when scene is updated and page is SUBMIT.
|
||||
|
||||
"""
|
||||
submission = bpy.context.scene.batchapps_submission
|
||||
session = bpy.context.scene.batchapps_session
|
||||
|
||||
if bpy.context.scene.batchapps_session.page not in ["SUBMIT"]:
|
||||
return
|
||||
|
||||
if submission.end_f < submission.start_f:
|
||||
session.log.warning("Start frame can't be greater than end frame.")
|
||||
submission.end_f = submission.start_f
|
||||
|
||||
if ((submission.start_f < bpy.context.scene.frame_start) or
|
||||
(submission.end_f > bpy.context.scene.frame_end)):
|
||||
session.log.warning("Selected frame range falls outside global range.")
|
||||
submission.valid_range = False
|
||||
|
||||
else:
|
||||
submission.valid_range = True
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def formatcheck(*args):
|
||||
"""
|
||||
Event handler to detect when the global render output format has been
|
||||
changed and update UI accordingly.
|
||||
Also issues a warning if the selected output format is not supported.
|
||||
|
||||
Run when scene is updated and page is SUBMIT.
|
||||
|
||||
"""
|
||||
submission = bpy.context.scene.batchapps_submission
|
||||
session = bpy.context.scene.batchapps_session
|
||||
|
||||
if session.page not in ["SUBMIT"]:
|
||||
return
|
||||
|
||||
format = bpy.context.scene.render.image_settings.file_format
|
||||
if format not in submission.supported_formats:
|
||||
session.log.warning("Invalid output format - using PNG instead.")
|
||||
submission.valid_format = False
|
||||
submission.image_format = 'PNG'
|
||||
|
||||
else:
|
||||
submission.valid_format = True
|
||||
submission.image_format = format
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def on_load(*args):
|
||||
"""
|
||||
Event handler to update the frame range when a new blender scene
|
||||
has been opened.
|
||||
|
||||
Run on blend file load.
|
||||
|
||||
"""
|
||||
submission = bpy.context.scene.batchapps_submission
|
||||
|
||||
submission.start_f = bpy.context.scene.frame_start
|
||||
submission.end_f = bpy.context.scene.frame_end
|
||||
|
||||
|
||||
class SubmissionDisplayProps(bpy.types.PropertyGroup):
|
||||
"""
|
||||
A display object representing a new job submission.
|
||||
This class is added to the Blender context.
|
||||
"""
|
||||
|
||||
title = bpy.props.StringProperty(
|
||||
description="Job Title",
|
||||
maxlen=64,
|
||||
default="")
|
||||
|
||||
start_f = bpy.props.IntProperty(
|
||||
description="Start Frame",
|
||||
default=1,
|
||||
min=0,
|
||||
soft_min=0)
|
||||
|
||||
end_f = bpy.props.IntProperty(
|
||||
description="End Frame",
|
||||
default=1,
|
||||
min=0,
|
||||
soft_min=0)
|
||||
|
||||
image_format = bpy.props.StringProperty(
|
||||
description="Image Format",
|
||||
default='PNG')
|
||||
|
||||
supported_formats = {
|
||||
'PNG': 'PNG',
|
||||
'JPEG': 'JPEG',
|
||||
'BMP': 'BMP',
|
||||
'CINEON': 'CINEON',
|
||||
'DPX': 'DPX',
|
||||
'HDR': 'HDR',
|
||||
'IRIS': 'IRIS',
|
||||
'OPEN_EXR': 'EXR',
|
||||
'TARGA': 'TGA',
|
||||
'TIFF': 'TIFF',
|
||||
'OPEN_EXR_MULTILAYER': 'MULTILAYER',
|
||||
'TARGA_RAW': 'RAWTGA'}
|
||||
|
||||
number_cores = bpy.props.IntProperty(
|
||||
description="Number of instances in pool",
|
||||
default=3,
|
||||
min=3,
|
||||
max=20)
|
||||
|
||||
valid_range = bpy.props.BoolProperty(
|
||||
description="Valid frame range",
|
||||
default=True)
|
||||
|
||||
valid_format = bpy.props.BoolProperty(
|
||||
description="Valid image format",
|
||||
default=True)
|
||||
|
||||
pool = bpy.props.EnumProperty(
|
||||
items=[("new", "Auto Pool", "Auto provision a pool for this job"),
|
||||
("reuse", "Use Pool ID", "Reuse an existing persistent pool"),
|
||||
("create", "Create Pool", "Create a new persistent pool")],
|
||||
description="Pool on which job will run",
|
||||
options={'ENUM_FLAG'},
|
||||
default={"new"})
|
||||
|
||||
pool_id = bpy.props.StringProperty(
|
||||
description="Existing Pool ID",
|
||||
default="")
|
||||
|
||||
|
||||
class SubmissionProps(object):
|
||||
"""
|
||||
Submission Properties.
|
||||
Once instantiated, this class is assigned to submission.BatchAppsSubmission.props
|
||||
but is not added to the Blender context.
|
||||
"""
|
||||
|
||||
thread = None
|
||||
display = None
|
||||
|
||||
def register_handlers(self):
|
||||
"""
|
||||
Register submission event handlers.
|
||||
|
||||
"""
|
||||
bpy.app.handlers.load_post.append(on_load)
|
||||
bpy.app.handlers.scene_update_post.append(framecheck)
|
||||
bpy.app.handlers.scene_update_post.append(formatcheck)
|
||||
on_load(None)
|
||||
|
||||
|
||||
def register_props():
|
||||
"""
|
||||
Register the submission property classes and assign to the blender
|
||||
context under "batchapps_submission".
|
||||
Also registers submission event handlers.
|
||||
|
||||
:Returns:
|
||||
- A :class:`.SubmissionProps` object
|
||||
|
||||
"""
|
||||
props_obj = SubmissionProps()
|
||||
bpy.types.Scene.batchapps_submission = \
|
||||
bpy.props.PointerProperty(type=SubmissionDisplayProps)
|
||||
|
||||
props_obj.display = bpy.context.scene.batchapps_submission
|
||||
props_obj.register_handlers()
|
||||
|
||||
return props_obj
|
|
@ -0,0 +1,304 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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 bpy
|
||||
import logging
|
||||
import webbrowser
|
||||
import os
|
||||
|
||||
from batchapps_blender.ui import ui_shared
|
||||
|
||||
from batchapps_blender.auth import BatchAppsAuth
|
||||
from batchapps_blender.submission import BatchAppsSubmission
|
||||
from batchapps_blender.assets import BatchAppsAssets
|
||||
from batchapps_blender.history import BatchAppsHistory
|
||||
from batchapps_blender.pools import BatchAppsPools
|
||||
from batchapps_blender.utils import BatchAppsOps
|
||||
|
||||
from batchapps import (
|
||||
JobManager,
|
||||
FileManager,
|
||||
PoolManager,
|
||||
Configuration)
|
||||
|
||||
from batchapps.exceptions import InvalidConfigException
|
||||
|
||||
|
||||
def override_config(cfg, **kwargs):
|
||||
"""
|
||||
Override a particular AAD setting in the config file, if it has been
|
||||
set in the Blender User Preferences.
|
||||
|
||||
:Args:
|
||||
- cfg (:class:`batchapps.Configuration`): The configuration to be
|
||||
updated.
|
||||
|
||||
:Kwargs:
|
||||
- The auth setting in the config to be changed, with it's updated
|
||||
value.
|
||||
|
||||
:Returns:
|
||||
- The updated config (:class:`batchapps.Configuration`).
|
||||
"""
|
||||
try:
|
||||
cfg.aad_config(**kwargs)
|
||||
|
||||
except InvalidConfigException:
|
||||
pass
|
||||
|
||||
finally:
|
||||
return cfg
|
||||
|
||||
class BatchAppsSettings(object):
|
||||
"""
|
||||
Initializes and manages the Batch Apps addon session.
|
||||
Registers all classes and handles all sub-pages.
|
||||
Defines the display of the global HOME and ERROR pages.
|
||||
Also configures logging and User Preferences.
|
||||
"""
|
||||
|
||||
pages = ["HOME", "ERROR"]
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.ops = self._register_ops()
|
||||
self.ui = self._register_ui()
|
||||
self.props = self._register_props()
|
||||
|
||||
self.log = self._configure_logging()
|
||||
self.cfg = self._configure_addon()
|
||||
self.page = "LOGIN"
|
||||
|
||||
self.auth = BatchAppsAuth()
|
||||
self.submission = None
|
||||
self.history = None
|
||||
self.assets = None
|
||||
self.pools = None
|
||||
|
||||
if self.auth.auto_authentication(self.cfg, self.log):
|
||||
self.start(self.auth.props.credentials)
|
||||
|
||||
def _configure_addon(self):
|
||||
"""
|
||||
Configures the addon based on the current User Preferences
|
||||
and the supplied Batch Apps ini file configuration.
|
||||
|
||||
:Returns:
|
||||
- A :class:`.batchapps.Configuration` object.
|
||||
"""
|
||||
try:
|
||||
data_dir = os.path.split(self.props.data_dir)
|
||||
|
||||
cfg = Configuration(jobtype='Blender',
|
||||
data_path=data_dir[0],
|
||||
log_level=int(self.props.log_level),
|
||||
name=self.props.ini_file,
|
||||
datadir=data_dir[1])
|
||||
|
||||
except (InvalidConfigException, IndexError) as exp:
|
||||
self.log.warning("Warning failed to load config file, "
|
||||
"creating new default config.")
|
||||
|
||||
self.log.warning(str(exp))
|
||||
cfg = Configuration(jobtype='Blender', log_level='warning')
|
||||
|
||||
finally:
|
||||
if self.props.endpoint:
|
||||
cfg = override_config(cfg, endpoint=self.props.endpoint)
|
||||
if self.props.account:
|
||||
cfg = override_config(cfg, account=self.props.account)
|
||||
if self.props.key:
|
||||
cfg = override_config(cfg, key=self.props.key)
|
||||
if self.props.client_id:
|
||||
cfg = override_config(cfg, client_id=self.props.client_id)
|
||||
if self.props.tenant:
|
||||
cfg = override_config(cfg, tenant=self.props.tenant)
|
||||
if self.props.redirect:
|
||||
cfg = override_config(cfg, redirect=self.props.redirect)
|
||||
|
||||
cfg.save_config()
|
||||
return cfg
|
||||
|
||||
def _configure_logging(self):
|
||||
"""
|
||||
Configures the logger for the addon based on the User Preferences.
|
||||
Sets up a stream handler to log to Blenders console and a file
|
||||
handler to log to the Batch Apps log file.
|
||||
|
||||
:Returns:
|
||||
- A :class:`batchapps.log.PickleLog` object.
|
||||
"""
|
||||
logger = logging.getLogger('BatchAppsBlender')
|
||||
|
||||
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.props.data_dir, "batch_apps.log")
|
||||
|
||||
file_logging = logging.FileHandler(logfile)
|
||||
file_logging.setFormatter(file_format)
|
||||
logger.addHandler(file_logging)
|
||||
|
||||
logger.setLevel(int(self.props.log_level))
|
||||
return logger
|
||||
|
||||
def _register_ops(self):
|
||||
"""
|
||||
Registers the shared operators with a batchapps_shared prefix.
|
||||
|
||||
:Returns:
|
||||
- A list of the names (str) of the registered operators.
|
||||
"""
|
||||
ops = []
|
||||
ops.append(BatchAppsOps.register("shared.home",
|
||||
"Home",
|
||||
self._home))
|
||||
ops.append(BatchAppsOps.register("shared.management_portal",
|
||||
"Management Portal",
|
||||
self._management_portal))
|
||||
return ops
|
||||
|
||||
def _register_props(self):
|
||||
"""
|
||||
Retrieves the shared addon properties - in this case the User
|
||||
Preferences.
|
||||
|
||||
:Returns:
|
||||
- :class:`.BatchAppsPreferences`
|
||||
"""
|
||||
return bpy.context.user_preferences.addons[__package__].preferences
|
||||
|
||||
def _register_ui(self):
|
||||
"""
|
||||
Matches the HOME and ERROR pages with their corresponding
|
||||
ui functions.
|
||||
|
||||
:Returns:
|
||||
- A dictionary mapping the page name to its corresponding
|
||||
ui function.
|
||||
"""
|
||||
def get_shared_ui(name):
|
||||
name = name.lower()
|
||||
return getattr(ui_shared, name)
|
||||
|
||||
page_func = map(get_shared_ui, self.pages)
|
||||
return dict(zip(self.pages, page_func))
|
||||
|
||||
def _home(self, op, context):
|
||||
"""
|
||||
The execute method for the shared.home operator.
|
||||
Sets the session page to HOME.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
self.page = "HOME"
|
||||
return {'FINISHED'}
|
||||
|
||||
def _management_portal(self, op, context):
|
||||
"""
|
||||
The execute method for the shared.management_portal operator.
|
||||
Opens the Management Portal in a web browser.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
webbrowser.open("https://manage.batchapps.windows.net", 2, True)
|
||||
return {'FINISHED'}
|
||||
|
||||
def display(self, ui, layout):
|
||||
"""
|
||||
Invokes the corresponding ui function depending on the session's
|
||||
current page.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
:Returns:
|
||||
- Runs the display function for the applicable page.
|
||||
"""
|
||||
return self.ui[self.page](ui, layout)
|
||||
|
||||
def start(self, creds):
|
||||
"""
|
||||
Initialize all the addon subpages after authentication is
|
||||
complete.
|
||||
Sets page to HOME.
|
||||
|
||||
:Args:
|
||||
- creds (:class:`batchapps.Credentials`): Authorised credentials
|
||||
with which API calls will be made.
|
||||
"""
|
||||
job_mgr = JobManager(creds, cfg=self.cfg)
|
||||
asset_mgr = FileManager(creds, cfg=self.cfg)
|
||||
pool_mgr = PoolManager(creds, cfg=self.cfg)
|
||||
|
||||
self.submission = BatchAppsSubmission(job_mgr, asset_mgr, pool_mgr)
|
||||
self.log.debug("Initialised submission module")
|
||||
|
||||
self.assets = BatchAppsAssets(asset_mgr)
|
||||
self.log.debug("Initialised assets module")
|
||||
|
||||
self.history = BatchAppsHistory(job_mgr)
|
||||
self.log.debug("Initialised history module")
|
||||
|
||||
self.pools = BatchAppsPools(pool_mgr)
|
||||
self.log.debug("Initialised pool module")
|
||||
|
||||
self.page = "HOME"
|
||||
|
||||
def redraw(self):
|
||||
"""
|
||||
Somewhat hacky way to force Blender to redraw the UI.
|
||||
"""
|
||||
bpy.context.scene.objects.active = bpy.context.scene.objects.active
|
||||
|
|
@ -0,0 +1,373 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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 bpy
|
||||
|
||||
import logging
|
||||
import random
|
||||
import os
|
||||
import string
|
||||
import threading
|
||||
import time
|
||||
|
||||
from batchapps_blender.ui import ui_submission
|
||||
from batchapps_blender.props import props_submission
|
||||
from batchapps_blender.utils import BatchAppsOps
|
||||
|
||||
from batchapps.exceptions import (
|
||||
AuthenticationException,
|
||||
InvalidConfigException)
|
||||
|
||||
|
||||
class BatchAppsSubmission(object):
|
||||
"""
|
||||
Manages the creation and submission of a new job.
|
||||
"""
|
||||
|
||||
pages = ['SUBMIT', 'PROCESSING', 'SUBMITTED']
|
||||
|
||||
def __init__(self, job_mgr, file_mgr, pool_mgr):
|
||||
|
||||
self.batchapps_job = job_mgr
|
||||
self.batchapps_files = file_mgr
|
||||
self.batchapps_pool = pool_mgr
|
||||
|
||||
self.ops = self._register_ops()
|
||||
self.props = self._register_props()
|
||||
self.ui = self._register_ui()
|
||||
|
||||
def display(self, ui, layout):
|
||||
"""
|
||||
Invokes the corresponding ui function depending on the session's
|
||||
current page.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
:Returns:
|
||||
- Runs the display function for the applicable page.
|
||||
"""
|
||||
return self.ui[bpy.context.scene.batchapps_session.page](ui, layout)
|
||||
|
||||
def _register_props(self):
|
||||
"""
|
||||
Registers and retrieves the submission property objects.page
|
||||
The dispaly properties are defined in a subclass which is assigned
|
||||
to the scene.batchapps_submission context.
|
||||
|
||||
:Returns:
|
||||
- :class:`.SubmissionProps`
|
||||
"""
|
||||
props = props_submission.register_props()
|
||||
return props
|
||||
|
||||
def _register_ops(self):
|
||||
"""
|
||||
Registers each job submission operator with a batchapps_submission
|
||||
prefix.
|
||||
|
||||
:Returns:
|
||||
- A list of the names (str) of the registered job submission
|
||||
operators.
|
||||
"""
|
||||
ops = []
|
||||
ops.append(BatchAppsOps.register("submission.page",
|
||||
"Create new job",
|
||||
self._submission))
|
||||
ops.append(BatchAppsOps.register("submission.start",
|
||||
"Submit job",
|
||||
self._start))
|
||||
ops.append(BatchAppsOps.register("submission.processing",
|
||||
"Submitting new job",
|
||||
modal=self._processing_modal,
|
||||
invoke=self._processing_invoke,
|
||||
_timer=None))
|
||||
return ops
|
||||
|
||||
def _register_ui(self):
|
||||
"""
|
||||
Matches the submit, processing and completed pages with their
|
||||
corresponding ui functions.
|
||||
|
||||
:Returns:
|
||||
- A dictionary mapping the page name to its corresponding
|
||||
ui function.
|
||||
"""
|
||||
def get_ui(name):
|
||||
name = name.lower()
|
||||
return getattr(ui_submission, name)
|
||||
|
||||
page_func = map(get_ui, self.pages)
|
||||
return dict(zip(self.pages, page_func))
|
||||
|
||||
def _processing_modal(self, op, context, event):
|
||||
"""
|
||||
The modal method for the submission.processing operator to handle
|
||||
running the submission of a job in a separate thread to prevent
|
||||
the blocking of the Blender UI.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
- event (:class:`bpy.types.Event`): The blender invocation event.
|
||||
|
||||
:Returns:
|
||||
- If the thread has completed, the Blender-specific value
|
||||
{'FINISHED'} to indicate the operator has completed its action.
|
||||
- Otherwise the Blender-specific value {'RUNNING_MODAL'} to
|
||||
indicate the operator wil continue to process after the
|
||||
completion of this function.
|
||||
"""
|
||||
if event.type == 'TIMER':
|
||||
context.scene.batchapps_session.log.debug("SubmitThread complete.")
|
||||
if not self.props.thread.is_alive():
|
||||
context.window_manager.event_timer_remove(op._timer)
|
||||
return {'FINISHED'}
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def _processing_invoke(self, op, context, event):
|
||||
"""
|
||||
The invoke method for the submission.processing operator.
|
||||
Starts the job submission thread.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
- event (:class:`bpy.types.Event`): The blender invocation event.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'RUNNING_MODAL'} to indicate the operator
|
||||
wil continue to process after the completion of this function.
|
||||
"""
|
||||
self.props.thread.start()
|
||||
context.scene.batchapps_session.log.debug("SubmitThread initiated.")
|
||||
|
||||
context.window_manager.modal_handler_add(op)
|
||||
op._timer = context.window_manager.event_timer_add(1, context.window)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def _start(self, op, context, *args):
|
||||
"""
|
||||
The execute method for the submission.start operator.
|
||||
Sets the functions to be performed by the job submission thread
|
||||
and updates the session page to "PROCESSING" while the thread
|
||||
executes.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
submit_thread = lambda: BatchAppsOps.session(self.submit_job)
|
||||
self.props.thread = threading.Thread(name="SubmitThread",
|
||||
target=submit_thread)
|
||||
|
||||
bpy.ops.batchapps_submission.processing('INVOKE_DEFAULT')
|
||||
|
||||
if context.scene.batchapps_session.page == "SUBMIT":
|
||||
context.scene.batchapps_session.page = "PROCESSING"
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def _submission(self, op, context, *args):
|
||||
"""
|
||||
The execute method for the submission.page operator.
|
||||
Sets the page to SUBMIT.
|
||||
|
||||
:Args:
|
||||
- op (:class:`bpy.types.Operator`): An instance of the current
|
||||
operator class.
|
||||
- context (:class:`bpy.types.Context`): The current blender
|
||||
context.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
"""
|
||||
context.scene.batchapps_session.page = "SUBMIT"
|
||||
return {'FINISHED'}
|
||||
|
||||
def gather_parameters(self):
|
||||
"""
|
||||
Gathers the operating parameters for the job.
|
||||
|
||||
:Returns:
|
||||
- A dictionary of parameters (stR).
|
||||
"""
|
||||
session = bpy.context.scene.batchapps_session
|
||||
params = {}
|
||||
|
||||
params["output"] = bpy.path.clean_name(self.props.display.title)
|
||||
params["start"] = str(self.props.display.start_f)
|
||||
params["end"] = str(self.props.display.end_f)
|
||||
params["format"] = self.props.display.supported_formats[
|
||||
self.props.display.image_format]
|
||||
|
||||
return params
|
||||
|
||||
def get_pool(self):
|
||||
"""
|
||||
Retrieve the pool id to be used for the job, or create an auto
|
||||
pool if necessary.
|
||||
|
||||
:Returns:
|
||||
- The pool id (string).
|
||||
"""
|
||||
session = bpy.context.scene.batchapps_session
|
||||
pools = bpy.context.scene.batchapps_pools
|
||||
pool = None
|
||||
|
||||
if self.props.display.pool == {"reuse"} and self.props.display.pool_id:
|
||||
pool = self.props.display.pool_id
|
||||
session.log.info("Using existing pool with ID: {0}".format(pool))
|
||||
return pool
|
||||
|
||||
elif self.props.display.pool == {"create"}:
|
||||
session.log.info("Creating new pool.")
|
||||
|
||||
pool = self.batchapps_pool.create(target_size=pools.pool_size)
|
||||
session.log.info("Created pool with ID: {0}".format(pool.id))
|
||||
|
||||
self.props.display.pool = {"reuse"}
|
||||
self.props.display.pool_id = pool.id
|
||||
return pool
|
||||
|
||||
elif self.props.display.pool == {"new"}:
|
||||
return pool
|
||||
|
||||
else:
|
||||
raise ValueError("Invalid pool settings.")
|
||||
|
||||
def get_title(self):
|
||||
"""
|
||||
Retrieve the job title if specified, or set to "Untitled_Job".
|
||||
"""
|
||||
if self.props.display.title == "":
|
||||
self.props.display.title = "Untitled_Job"
|
||||
|
||||
else:
|
||||
self.props.display.title = bpy.path.clean_name(
|
||||
self.props.display.title)
|
||||
|
||||
return self.props.display.title
|
||||
|
||||
def upload_assets(self, new_job):
|
||||
"""
|
||||
Upload all assets required by the job.
|
||||
|
||||
:Args:
|
||||
- new_job (:class:`JobSubmission`): The job for which all assets
|
||||
will be uploaded.
|
||||
|
||||
:Raises:
|
||||
- ValueError if one or more assets fails to upload.
|
||||
"""
|
||||
session = bpy.context.scene.batchapps_session
|
||||
session.log.info("Uploading any required files.")
|
||||
|
||||
failed = new_job.required_files.upload()
|
||||
if failed:
|
||||
[session.log.error("{0}: {1}".format(f[0], f[1])) for f in failed]
|
||||
raise ValueError("Some required assets failed to upload.")
|
||||
|
||||
def configure_assets(self, new_job):
|
||||
"""
|
||||
Gather the assets required for the job and allocate the job file from
|
||||
which the rendering will be run.
|
||||
"""
|
||||
session = bpy.context.scene.batchapps_session
|
||||
assets = bpy.context.scene.batchapps_assets
|
||||
|
||||
if assets.path == '':
|
||||
session.log.info("No assets referenced yet. Checking now.")
|
||||
bpy.ops.batchapps_assets.refresh()
|
||||
|
||||
file_set = self.batchapps_files.create_file_set(assets.collection)
|
||||
new_job.add_file_collection(file_set)
|
||||
|
||||
if bpy.context.scene.batchapps_assets.temp:
|
||||
session.log.debug("Using temp blend file {0}".format(assets.path))
|
||||
bpy.ops.wm.save_as_mainfile(filepath=assets.path,
|
||||
check_existing=False,
|
||||
copy=True)
|
||||
jobfile = self.batchapps_files.file_from_path(assets.path)
|
||||
|
||||
new_job.add_file(jobfile)
|
||||
new_job.set_job_file(-1)
|
||||
|
||||
else:
|
||||
session.log.debug("Using saved blend file {0}".format(assets.path))
|
||||
jobfile = bpy.context.scene.batchapps_assets.get_jobfile()
|
||||
new_job.set_job_file(jobfile)
|
||||
|
||||
self.upload_assets(new_job)
|
||||
|
||||
def submit_job(self):
|
||||
"""
|
||||
The job submission process including the uploading of any required
|
||||
assets and the instantiation of an auto-pool if necessary.
|
||||
|
||||
Sets the page to COMPLETE if successful.
|
||||
"""
|
||||
self.props.display = bpy.context.scene.batchapps_submission
|
||||
session = bpy.context.scene.batchapps_session
|
||||
assets = bpy.context.scene.batchapps_assets
|
||||
session.log.info("Starting new job submission.")
|
||||
|
||||
new_job = self.batchapps_job.create_job(self.get_title())
|
||||
self.configure_assets(new_job)
|
||||
|
||||
new_job.pool = self.get_pool()
|
||||
new_job.instances = self.props.display.number_cores
|
||||
new_job.params = self.gather_parameters()
|
||||
new_job.params['jobfile'] = new_job.source
|
||||
|
||||
session.log.info("Preparation complete, submitting job.")
|
||||
session.log.debug("Submission details: {0}".format(
|
||||
new_job._create_job_message()))
|
||||
|
||||
submission = new_job.submit()
|
||||
session.log.info(
|
||||
"New job submitted with ID: {0}".format(submission['id']))
|
||||
|
||||
session.page = "SUBMITTED"
|
||||
assets.set_uploaded()
|
||||
session.redraw()
|
|
@ -0,0 +1,170 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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 bpy
|
||||
|
||||
class AssetListUI(bpy.types.UIList):
|
||||
"""Ui List element for display assets"""
|
||||
|
||||
def draw_item(self, context, layout, data, item, icon, active_data,
|
||||
active_propname, index, flt_flag):
|
||||
"""Draw UI List"""
|
||||
|
||||
asset = item
|
||||
|
||||
if self.layout_type in {'DEFAULT', 'COMPACT'}:
|
||||
layout.label(asset.name)
|
||||
|
||||
if not asset.upload_check:
|
||||
col = layout.column()
|
||||
col.prop(asset,
|
||||
"upload_checkbox",
|
||||
text="",
|
||||
index=index)
|
||||
|
||||
else:
|
||||
col = layout.column()
|
||||
col.label("", icon="FILE_TICK")
|
||||
|
||||
elif self.layout_type in {'GRID'}:
|
||||
layout.alignment = 'CENTER'
|
||||
|
||||
if flt_flag:
|
||||
layout.enabled = False
|
||||
layout.label(text="", icon_value=icon)
|
||||
|
||||
else:
|
||||
layout.label(text="",
|
||||
translate=False,
|
||||
icon_value=icon)
|
||||
|
||||
|
||||
def uilist_controls(ui, layout):
|
||||
"""
|
||||
Displays the buttons and labels for the UI List.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
"""
|
||||
batchapps_assets = bpy.context.scene.batchapps_assets
|
||||
|
||||
num_assets = len(batchapps_assets.assets)
|
||||
num_display = "Displaying {0} assets".format(num_assets)
|
||||
|
||||
ui.label(num_display, layout.row(align=True), align='CENTER')
|
||||
row = layout.row(align=True)
|
||||
|
||||
div = row.split()
|
||||
ui.operator("assets.refresh", "Reset", div, "FILE_REFRESH")
|
||||
|
||||
div = row.split()
|
||||
active = any(a.upload_checkbox for a in batchapps_assets.assets)
|
||||
ui.operator('assets.upload', "Upload", div, "MOVE_UP_VEC", active=active)
|
||||
|
||||
div = row.split()
|
||||
ui.operator("assets.add", "", div, "ZOOMIN")
|
||||
|
||||
div = row.split()
|
||||
active = (num_assets > 0)
|
||||
ui.operator('assets.remove', "", div, "ZOOMOUT", active=active)
|
||||
|
||||
|
||||
def display_uilist(ui, layout):
|
||||
"""
|
||||
Displays the UI List that will show all collected assets.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
"""
|
||||
|
||||
batchapps_assets = bpy.context.scene.batchapps_assets
|
||||
outerBox = layout.box()
|
||||
row = outerBox.row()
|
||||
|
||||
ui.label("Filename", row)
|
||||
ui.label("Uploaded", row, "RIGHT")
|
||||
|
||||
outerBox.template_list("AssetListUI",
|
||||
"",
|
||||
batchapps_assets,
|
||||
"assets",
|
||||
batchapps_assets,
|
||||
"index")
|
||||
|
||||
if len(batchapps_assets.assets) > 0:
|
||||
display_details(ui, outerBox)
|
||||
|
||||
|
||||
def display_details(ui, outerBox):
|
||||
"""
|
||||
Displays the details of the asset selected in the UI List.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- outerBox (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components. In this case the box the details are listed within.
|
||||
"""
|
||||
batchapps_assets = bpy.context.scene.batchapps_assets
|
||||
col = outerBox.column(align=True)
|
||||
|
||||
if batchapps_assets.index < len(batchapps_assets.assets):
|
||||
|
||||
selected = batchapps_assets.assets[batchapps_assets.index]
|
||||
uploaded = "Uploaded" if selected.upload_check else "Not Uploaded"
|
||||
|
||||
ui.label("Asset: {0}".format(selected.name), col)
|
||||
ui.label("Status: {0}".format(uploaded), col)
|
||||
ui.label("Date Modified: {0}".format(selected.timestamp), col)
|
||||
ui.label("Full Path: {0}".format(selected.fullpath), col)
|
||||
|
||||
def assets(ui, layout):
|
||||
"""
|
||||
Display asset management page.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
"""
|
||||
uilist_controls(ui, layout)
|
||||
display_uilist(ui, layout)
|
||||
|
||||
ui.operator("shared.home", "Return Home", layout)
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
def pre_login(ui, layout):
|
||||
"""
|
||||
Display login operations.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
"""
|
||||
ui.label("", layout)
|
||||
ui.operator("auth.login", "Sign In", layout, "TRIA_RIGHT")
|
||||
ui.label("", layout)
|
||||
|
||||
def post_login(ui, layout):
|
||||
"""
|
||||
Display pending authentication message.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
"""
|
||||
ui.label("Waiting for authentication", layout.row(), "CENTER")
|
||||
ui.label("Timeout: 1 minute", layout.row(), "CENTER")
|
||||
ui.label("", layout)
|
||||
|
||||
def login(ui, layout):
|
||||
"""
|
||||
Display login page.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
"""
|
||||
sublayout = layout.box()
|
||||
ui.label("Sign in below to start", sublayout.row(align=True), "CENTER")
|
||||
ui.label("", sublayout)
|
||||
|
||||
pre_login(ui, sublayout)
|
||||
|
||||
def redirect(ui, layout):
|
||||
"""
|
||||
Display auth redirect pending page.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
"""
|
||||
sublayout = layout.box()
|
||||
ui.label("Sign in below to start", sublayout.row(align=True), "CENTER")
|
||||
ui.label("", sublayout)
|
||||
|
||||
post_login(ui, sublayout)
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# Azure Batch Apps Blender Sample
|
||||
#
|
||||
# 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 bpy
|
||||
|
||||
|
||||
def status_icon(job):
|
||||
"""
|
||||
Get the appropriate operator icon based on a jobs status.
|
||||
|
||||
:Args:
|
||||
- job (:class:`.HistoryDetails`): The selected job to display.
|
||||
|
||||
:Returns:
|
||||
- The required icon name (str).
|
||||
|
||||
"""
|
||||
status = job.status.lower()
|
||||
icons = bpy.context.scene.batchapps_history.icons
|
||||
return icons.get(status, "")
|
||||
|
||||
def details(ui, layout, job):
|
||||
"""
|
||||
Display the detailed information on an indivual selected job.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
- job (:class:`.HistoryDetails`): The selected job to display.
|
||||
|
||||
"""
|
||||
if job.status in ["InProgress", "Error", "Cancelled"]:
|
||||
status = """Status: {0} - {1}% complete""".format(
|
||||
job.status, job.percent)
|
||||
|
||||
else:
|
||||
status = "Status: {0}".format(job.status)
|
||||
|
||||
ui.label(status, layout)
|
||||
ui.label("Submitted: {0}".format(job.timestamp), layout)
|
||||
ui.label("ID: {0}".format(job.id), layout)
|
||||
ui.label("Type: {0}".format(job.type), layout)
|
||||
ui.label("Number of Tasks: {0}".format(job.tasks), layout)
|
||||
ui.label("Pool: {0}".format(job.pool_id), layout)
|
||||
|
||||
if job.status.lower() in ["notstarted", "inprogress"]:
|
||||
ui.operator("history.cancel", "Cancel Job", layout)
|
||||
|
||||
def page_controls(ui, layout, num_jobs):
|
||||
"""
|
||||
Display the job history list paging controls.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
- num_jobs (int): The total number of jobs in the users history.
|
||||
|
||||
"""
|
||||
history = bpy.context.scene.batchapps_history
|
||||
|
||||
if history.total_count == 0:
|
||||
start = 0
|
||||
end = 0
|
||||
|
||||
elif history.total_count != 0 and history.index == 0:
|
||||
start = 1
|
||||
end = num_jobs
|
||||
|
||||
else:
|
||||
start = history.index + 1
|
||||
end = (start+num_jobs)-1
|
||||
|
||||
job_display = "Displaying jobs {start}-{end} of {total}".format(
|
||||
start=start, end=end, total=history.total_count)
|
||||
|
||||
ui.label(job_display, layout.row(align=True), "CENTER")
|
||||
|
||||
split = layout.split(percentage=0.333)
|
||||
row = split.row(align=True)
|
||||
ui.operator("history.first", "", row, "REW", "LEFT", (history.index != 0))
|
||||
ui.operator("history.less", "", row, "PREV_KEYFRAME",
|
||||
"LEFT", (history.index != 0))
|
||||
|
||||
row = split.row(align=True)
|
||||
ui.operator("history.refresh", "Refresh", row, "FILE_REFRESH", "CENTER")
|
||||
|
||||
row = split.row(align=True)
|
||||
enabled = (history.index + num_jobs) != history.total_count
|
||||
ui.operator("history.more", "", row, "NEXT_KEYFRAME", "RIGHT", enabled)
|
||||
ui.operator("history.last", "", row, "FF", "RIGHT", enabled)
|
||||
|
||||
|
||||
def history(ui, layout):
|
||||
"""
|
||||
Display job history page.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
"""
|
||||
jobs = bpy.context.scene.batchapps_history.jobs
|
||||
|
||||
page_controls(ui, layout, len(jobs))
|
||||
outer_box = layout.box()
|
||||
|
||||
if not jobs:
|
||||
ui.label("No jobs to display.", outer_box.row(), "CENTER")
|
||||
|
||||
else:
|
||||
for index, job in enumerate(jobs):
|
||||
|
||||
if index == bpy.context.scene.batchapps_history.selected:
|
||||
inner_box = outer_box.box()
|
||||
|
||||
ui.operator("history."+job.id.replace("-", "_"), (" "+job.name),
|
||||
inner_box, status_icon(job))
|
||||
details(ui, inner_box, job)
|
||||
|
||||
else:
|
||||
ui.operator("history."+job.id.replace("-", "_"), (" "+job.name),
|
||||
outer_box, status_icon(job))
|
||||
|
||||
ui.label("", layout)
|
||||
ui.operator("shared.home", "Return Home", layout)
|
||||
|
||||
def loading(ui, layout):
|
||||
"""
|
||||
Display job history loading page.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
"""
|
||||
page_controls(ui, layout, 0)
|
||||
outer_box = layout.box()
|
||||
ui.label("Loading...", outer_box.row(align=True), "CENTER")
|
||||
|
||||
ui.label("", layout)
|
||||
ui.operator("shared.home", "Return Home", layout, active=False)
|
|
@ -0,0 +1,153 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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 bpy
|
||||
|
||||
def details(ui, layout, pool):
|
||||
"""
|
||||
Display details on an individual selected pool.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
- pool (:class:`.PoolDetails`): The selected pool to display.
|
||||
|
||||
"""
|
||||
types = {True:'Auto Provisioned',
|
||||
False: 'Persistent Pool'}
|
||||
if not pool.auto:
|
||||
split = layout.split(percentage=0.1)
|
||||
ui.label("ID: ", split.row(align=True))
|
||||
proprow = split.row(align=True)
|
||||
proprow.active=False
|
||||
ui.prop(pool, 'id', proprow)
|
||||
|
||||
else: ui.label("ID: {0}".format(pool.id), layout)
|
||||
|
||||
ui.label("Type: {0}".format(types[pool.auto]), layout)
|
||||
ui.label("State: {0}".format(pool.state), layout)
|
||||
ui.label("Currently running: {0} jobs".format(pool.queue), layout)
|
||||
ui.label("", layout)
|
||||
|
||||
ui.label("Created: {0}".format(pool.created), layout)
|
||||
split = layout.split(percentage=0.5)
|
||||
ui.label("Target Size: {0}".format(pool.target), split.row(align=True))
|
||||
ui.label("Current Size: {0}".format(pool.current), split.row(align=True))
|
||||
|
||||
|
||||
row = layout.row(align=True)
|
||||
if pool.queue > 0:
|
||||
row.alert=True
|
||||
ui.operator("pools.delete", "Delete Pool", row)
|
||||
|
||||
def display_pools(ui, layout):
|
||||
"""
|
||||
Display pool list.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
"""
|
||||
batchapps_pools = bpy.context.scene.batchapps_pools
|
||||
icons_right = {True: 'DISCLOSURE_TRI_RIGHT_VEC', False: 'TRIA_RIGHT'}
|
||||
icons_down = {True: 'DISCLOSURE_TRI_DOWN_VEC', False: 'TRIA_DOWN'}
|
||||
|
||||
if not batchapps_pools.pools:
|
||||
ui.label("No pools found", layout)
|
||||
|
||||
else:
|
||||
for index, pool in enumerate(batchapps_pools.pools):
|
||||
|
||||
if index == batchapps_pools.selected:
|
||||
|
||||
inner_box = layout.box()
|
||||
ui.operator("pools."+pool.id.replace("-", "_"), "Hide details",
|
||||
inner_box, icons_down[pool.auto])
|
||||
|
||||
details(ui, inner_box, pool)
|
||||
|
||||
else:
|
||||
ui.operator("pools."+pool.id.replace("-", "_"), (' '+pool.id),
|
||||
layout, icons_right[pool.auto])
|
||||
|
||||
def pools(ui, layout):
|
||||
"""
|
||||
Display pools page.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
"""
|
||||
batchapps_pools = bpy.context.scene.batchapps_pools
|
||||
ui.operator("pools.create", "Create New Pool", layout)
|
||||
ui.label("", layout)
|
||||
|
||||
display_pools(ui, layout)
|
||||
|
||||
ui.label("", layout)
|
||||
ui.operator("pools.page", "Refresh Pools", layout)
|
||||
ui.operator("shared.home", "Return Home", layout)
|
||||
|
||||
def create(ui, layout):
|
||||
"""
|
||||
Display create new pool panel in pools page.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
"""
|
||||
batchapps_pools = bpy.context.scene.batchapps_pools
|
||||
ui.operator("pools.create", "Create New Pool", layout.row())
|
||||
|
||||
box = layout.box()
|
||||
ui.label("New Pool", box)
|
||||
ui.prop(batchapps_pools, "pool_size", box, "Pool Size")
|
||||
ui.operator("pools.start", "Start Pool", box)
|
||||
|
||||
ui.label("", layout)
|
||||
|
||||
display_pools(ui, layout)
|
||||
|
||||
ui.label("", layout)
|
||||
ui.operator("pools.page", "Refresh Pools", layout)
|
||||
ui.operator("shared.home", "Return Home", layout)
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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 bpy
|
||||
|
||||
def home(ui, layout):
|
||||
"""
|
||||
Display home page.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
"""
|
||||
col = layout.column()
|
||||
ui.operator("submission.page", "Submit New Job", col)
|
||||
ui.operator("history.page", "Jobs", col)
|
||||
ui.operator("assets.page", "Assets", col)
|
||||
ui.operator("pools.page", "Batch Apps Pools", col)
|
||||
ui.operator("shared.management_portal", "Management Portal", col)
|
||||
ui.operator("auth.logout", "Logout", col)
|
||||
ui.label("", layout)
|
||||
|
||||
def error(ui, layout):
|
||||
"""
|
||||
Display error page.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
"""
|
||||
sublayout = layout.box()
|
||||
ui.label("An error occurred", sublayout.row(align=True), "CENTER")
|
||||
ui.label("Please see console for details.", sublayout.row(align=True), "CENTER")
|
||||
|
||||
if bpy.context.scene.batchapps_auth.credentials:
|
||||
ui.operator("shared.home", "Return Home", sublayout)
|
||||
|
||||
else:
|
||||
ui.operator("auth.logout", "Return to Login", sublayout)
|
||||
|
||||
ui.label("", sublayout)
|
|
@ -0,0 +1,218 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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 bpy
|
||||
|
||||
|
||||
def static(ui, layout, active):
|
||||
"""
|
||||
Display static job details reflecting global render settings.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
- active (bool): Whether UI components are enabled.
|
||||
|
||||
"""
|
||||
width = int(bpy.context.scene.render.resolution_x*\
|
||||
(bpy.context.scene.render.resolution_percentage/100))
|
||||
height = int(bpy.context.scene.render.resolution_y*\
|
||||
(bpy.context.scene.render.resolution_percentage/100))
|
||||
output = bpy.context.scene.batchapps_submission.image_format
|
||||
|
||||
ui.label("Width: {0}".format(width), layout.row(), active=active)
|
||||
ui.label("Height: {0}".format(height), layout.row(), active=active)
|
||||
ui.label("Output: {0}".format(output), layout.row(), active=active)
|
||||
|
||||
|
||||
def variable(ui, layout, active):
|
||||
"""
|
||||
Display frame selection controls.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
- active (bool): Whether UI components are enabled.
|
||||
|
||||
"""
|
||||
ui.prop(bpy.context.scene.batchapps_submission, "start_f", layout.row(),
|
||||
label="Start Frame ", active=active)
|
||||
ui.prop(bpy.context.scene.batchapps_submission, "end_f", layout.row(),
|
||||
label="End Frame ", active=active)
|
||||
|
||||
|
||||
def pool_select(ui, layout, active):
|
||||
"""
|
||||
Display pool selection controls.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
- active (bool): Whether UI components are enabled.
|
||||
|
||||
"""
|
||||
ui.label("", layout)
|
||||
ui.prop(bpy.context.scene.batchapps_submission, "pool", layout.row(),
|
||||
label=None, expand=True, active=active)
|
||||
|
||||
if bpy.context.scene.batchapps_submission.pool == {"reuse"}:
|
||||
ui.label("Use an existing persistant pool by ID", layout.row(), active=active)
|
||||
ui.prop(bpy.context.scene.batchapps_submission, "pool_id",
|
||||
layout.row(), active=active)
|
||||
|
||||
elif bpy.context.scene.batchapps_submission.pool == {"create"}:
|
||||
ui.label("Create a new persistant pool", layout.row(), active=active)
|
||||
ui.prop(bpy.context.scene.batchapps_pools, "pool_size",
|
||||
layout.row(), "Number of instances:", active=active)
|
||||
|
||||
else:
|
||||
ui.label("Auto provision a pool for this job", layout.row(),
|
||||
active=active)
|
||||
ui.prop(bpy.context.scene.batchapps_submission, "number_cores",
|
||||
layout.row(), "Number of instances:", active=active)
|
||||
|
||||
def pre_submission(ui, layout):
|
||||
"""
|
||||
Display any warnings before job submission.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
"""
|
||||
if not bpy.context.scene.batchapps_submission.valid_format:
|
||||
ui.label("Warning: Output format {0}".format(
|
||||
bpy.context.scene.render.image_settings.file_format), layout)
|
||||
|
||||
ui.label("not supported. Using PNG instead", layout)
|
||||
row = layout.row(align=True)
|
||||
row.alert=True
|
||||
ui.operator("submission.start", "Submit Job", row)
|
||||
|
||||
elif not bpy.context.scene.batchapps_submission.valid_range:
|
||||
ui.label("Warning: Selected frame range falls", layout)
|
||||
ui.label("outside global render range", layout)
|
||||
row = layout.row(align=True)
|
||||
row.alert=True
|
||||
ui.operator("submission.start", "Submit Job", row)
|
||||
|
||||
else:
|
||||
ui.label("", layout)
|
||||
ui.label("", layout)
|
||||
ui.operator("submission.start", "Submit Job", layout)
|
||||
|
||||
ui.operator("shared.home", "Return Home", layout)
|
||||
|
||||
|
||||
def post_submission(ui, layout):
|
||||
"""
|
||||
Display the job processing message.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
"""
|
||||
ui.label("Submission now processing.", layout.row(align=True), "CENTER")
|
||||
ui.label("See console for progress.", layout.row(align=True), "CENTER")
|
||||
ui.label("Please don't close blender.", layout.row(align=True), "CENTER")
|
||||
|
||||
|
||||
def submit(ui, layout):
|
||||
"""
|
||||
Display new job submission page.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
"""
|
||||
ui.prop(bpy.context.scene.batchapps_submission, "title", layout,
|
||||
label="Job name ", active=True)
|
||||
|
||||
static(ui, layout, True)
|
||||
variable(ui, layout, True)
|
||||
pool_select(ui, layout, True)
|
||||
pre_submission(ui, layout)
|
||||
|
||||
def processing(ui, layout):
|
||||
"""
|
||||
Display job submission processing page.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
"""
|
||||
ui.prop(bpy.context.scene.batchapps_submission, "title", layout.row(),
|
||||
label="Job name ", active=False)
|
||||
|
||||
static(ui, layout, False)
|
||||
variable(ui, layout, False)
|
||||
pool_select(ui, layout, False)
|
||||
post_submission(ui, layout)
|
||||
|
||||
ui.label("", layout)
|
||||
|
||||
|
||||
def submitted(ui, layout):
|
||||
"""
|
||||
Display job submitted page.
|
||||
|
||||
:Args:
|
||||
- ui (blender :class:`.Interface`): The instance of the Interface
|
||||
panel class.
|
||||
- layout (blender :class:`bpy.types.UILayout`): The layout object,
|
||||
derived from the Interface panel. Used for creating ui
|
||||
components.
|
||||
|
||||
"""
|
||||
sublayout = layout.box()
|
||||
ui.label("Submission Successfull!", sublayout.row(align=True), "CENTER")
|
||||
ui.label("", sublayout, "CENTER")
|
||||
ui.operator("shared.home", "Return Home", layout)
|
|
@ -0,0 +1,207 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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 bpy
|
||||
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
from urllib.parse import unquote
|
||||
|
||||
import sys
|
||||
import socket
|
||||
|
||||
from batchapps.exceptions import SessionExpiredException
|
||||
|
||||
class BatchAppsOps(object):
|
||||
"""
|
||||
Static class for registering operators and executing them in a
|
||||
error-safe way.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def session(func, *args, **kwargs):
|
||||
"""
|
||||
Execute an operator function.
|
||||
Can be invoke, execute, modal or a thread function.
|
||||
|
||||
:Args:
|
||||
- func (function): The function to be executed along with
|
||||
any args and kwargs.
|
||||
|
||||
:Returns:
|
||||
- Blender-specific value {'FINISHED'} to indicate the operator has
|
||||
completed its action.
|
||||
- If a :class:`batchapps.exceptions.SessionExpiredException` is
|
||||
raised, returns {'CANCELLED'}.
|
||||
"""
|
||||
session = bpy.context.scene.batchapps_session
|
||||
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
|
||||
except SessionExpiredException:
|
||||
session.log.error(
|
||||
"Warning: Session Expired - please log back in again.")
|
||||
|
||||
session.page = "LOGIN"
|
||||
session.redraw()
|
||||
return {'CANCELLED'}
|
||||
|
||||
except Exception as exp:
|
||||
session.page = "ERROR"
|
||||
session.log.error("Error occurred: {0}".format(exp))
|
||||
session.redraw()
|
||||
return {'CANCELLED'}
|
||||
|
||||
@staticmethod
|
||||
def register(name, label, execute=None, modal=None, invoke=None, **kwargs):
|
||||
"""
|
||||
Register a custom operator.
|
||||
|
||||
:Args:
|
||||
- name (str): The id name of the operator (bl_idname).
|
||||
- label (str): The description of the operator (bl_label).
|
||||
|
||||
:Kwargs:
|
||||
- execute (func): The execute function if applicable.
|
||||
- modal (func): The modal function if applicable.
|
||||
- invoke (func): The invoke function if applicable.
|
||||
- Any additional attributes or functions to be added to the class.
|
||||
|
||||
:Returns:
|
||||
- The ID name of the registered operator with the
|
||||
prefix ``batchapps_``.
|
||||
|
||||
"""
|
||||
name = "batchapps_" + str(name)
|
||||
op_spec = {"bl_idname": name, "bl_label": label}
|
||||
|
||||
if execute:
|
||||
def op_execute(self, context):
|
||||
return BatchAppsOps.session(execute, self, context)
|
||||
|
||||
op_spec["execute"] = op_execute
|
||||
|
||||
if modal:
|
||||
def op_modal(self, context, event):
|
||||
return BatchAppsOps.session(modal, self, context, event)
|
||||
|
||||
op_spec["modal"] = op_modal
|
||||
|
||||
if invoke:
|
||||
def op_invoke(self, context, event):
|
||||
return BatchAppsOps.session(invoke, self, context, event)
|
||||
|
||||
op_spec["invoke"] = op_invoke
|
||||
|
||||
op_spec.update(kwargs)
|
||||
|
||||
new_op = type("BatchAppsOp",
|
||||
(bpy.types.Operator, ),
|
||||
op_spec)
|
||||
|
||||
bpy.utils.register_class(new_op)
|
||||
return name
|
||||
|
||||
@staticmethod
|
||||
def register_expanding(name, label, execute, modal=None,
|
||||
invoke=None, **kwargs):
|
||||
"""
|
||||
Register an operator that can be used as an expanding UI
|
||||
component (for example a job in the history page).
|
||||
|
||||
:Args:
|
||||
- name (str): The id name of the operator (bl_idname).
|
||||
- label (str): The description of the operator (bl_label).
|
||||
- execute (func): The execute function.
|
||||
|
||||
:Kwargs:
|
||||
- modal (func): The modal function if applicable.
|
||||
- invoke (func): The invoke function if applicable.
|
||||
- Any additional attributes or functions to be added to the class.
|
||||
|
||||
:Returns:
|
||||
- The ID name of the registered operator with the
|
||||
prefix ``batchapps_``.
|
||||
|
||||
"""
|
||||
kwargs.update({'enabled':bpy.props.BoolProperty(default=False)})
|
||||
|
||||
def op_execute(self, context):
|
||||
execute(self)
|
||||
self.enabled = not self.enabled
|
||||
return {'FINISHED'}
|
||||
|
||||
return BatchAppsOps.register(name, label, op_execute, modal,
|
||||
invoke, **kwargs)
|
||||
|
||||
class OAuthRequestHandler(BaseHTTPRequestHandler):
|
||||
"""
|
||||
A custom HTTP server request handler to handler the AAD redirects
|
||||
for authentication.
|
||||
"""
|
||||
|
||||
def log_message(self, format, *args):
|
||||
"""
|
||||
Override logging to silence messages from base class.
|
||||
"""
|
||||
return
|
||||
|
||||
def do_GET(s):
|
||||
"""
|
||||
Handle a GET request from the AAD server. If a code is returned,
|
||||
assigns to the batchapps_auth.code property and returns status 200
|
||||
along with a simple HTML message.
|
||||
If an error is returned, assigns to properties for logging the
|
||||
returns status 401 and an HTML message.
|
||||
"""
|
||||
session = bpy.context.scene.batchapps_session
|
||||
session.log.debug("Received AAD request {0}".format(s.path))
|
||||
|
||||
if s.path.startswith('/?code'):
|
||||
bpy.context.scene.batchapps_auth.code = s.path
|
||||
|
||||
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 Blender 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:
|
||||
bpy.context.scene.batchapps_auth.code = s.path
|
||||
|
||||
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 Blender console for details.</p>")
|
||||
s.wfile.write(b"</body></html>")
|
|
@ -0,0 +1,69 @@
|
|||
#-------------------------------------------------------------------------
|
||||
# The Blender Batch Apps Sample
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#--------------------------------------------------------------------------
|
||||
"""Zip up Blender Addon"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import shutil
|
||||
import zipfile
|
||||
|
||||
VERSION = "0.1.0"
|
||||
|
||||
def main():
|
||||
"""Build Blender Addon package"""
|
||||
|
||||
python_exe = os.path.join(sys.prefix, "python.exe")
|
||||
if not os.path.exists(python_exe):
|
||||
print("Cannot find python.exe at path: {0}".format(python_exe))
|
||||
return
|
||||
|
||||
print("Building package...")
|
||||
|
||||
package_dir = os.path.abspath("build")
|
||||
if not os.path.isdir(package_dir):
|
||||
try:
|
||||
os.mkdir(package_dir)
|
||||
except:
|
||||
print("Cannot create build dir at path: {0}".format(package_dir))
|
||||
return
|
||||
|
||||
package = os.path.join(package_dir, "batchapps-blender-{0}.zip".format(VERSION))
|
||||
source = os.path.abspath("batchapps_blender")
|
||||
|
||||
with zipfile.ZipFile(package, mode='w') as blend_zip:
|
||||
for root, dirs, files in os.walk(source):
|
||||
if root.endswith("__pycache__"):
|
||||
continue
|
||||
|
||||
for file in files:
|
||||
blend_zip.write(os.path.relpath(os.path.join(root, file)))
|
||||
|
||||
print("Package complete!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,45 @@
|
|||
///-------------------------------------------------------------------------
|
||||
/// The Blender Batch Apps Sample
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
///--------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Azure.Batch.Apps.Cloud;
|
||||
|
||||
namespace Blender.Cloud
|
||||
{
|
||||
public class ApplicationDefinition
|
||||
{
|
||||
public static readonly CloudApplication Application = new ParallelCloudApplication
|
||||
{
|
||||
ApplicationName = "Blender",
|
||||
JobType = "Blender",
|
||||
JobSplitterType = typeof(BlenderJobSplitter),
|
||||
TaskProcessorType = typeof(BlenderTaskProcessor)
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<?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>{5C019C8B-B5E5-4B98-9D75-8B5D7E6C83D0}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Blender.Cloud</RootNamespace>
|
||||
<AssemblyName>Blender.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>
|
||||
</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>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Azure.Batch.Apps.Cloud">
|
||||
<HintPath>..\packages\Microsoft.Azure.Batch.Apps.Cloud.1.0.0\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.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="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="BlenderParameters.cs" />
|
||||
<Compile Include="TaskProcessor.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
</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>
|
|
@ -0,0 +1,242 @@
|
|||
///-------------------------------------------------------------------------
|
||||
/// The Blender Batch Apps Sample
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
///--------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Batch.Apps.Cloud;
|
||||
|
||||
namespace Blender.Cloud
|
||||
{
|
||||
public abstract class BlenderParameters
|
||||
{
|
||||
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 string JobFile { get; }
|
||||
|
||||
public abstract string Prefix { get; }
|
||||
|
||||
public abstract string Format { get; }
|
||||
|
||||
public abstract string ErrorText { get; }
|
||||
|
||||
public static BlenderParameters 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 prefix = GetStringParameter(job.Parameters, "output", errors);
|
||||
string format = GetStringParameter(job.Parameters, "format", errors);
|
||||
|
||||
if (errors.Any())
|
||||
{
|
||||
return new InvalidBlenderParameters(string.Join(Environment.NewLine, errors.Select(e => "* " + e)));
|
||||
}
|
||||
|
||||
return new ValidBlenderParameters(start, end, jobfile, prefix, format);
|
||||
}
|
||||
public static BlenderParameters FromTask(ITask task)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
|
||||
string jobfile = GetStringParameter(task.Parameters, "jobfile", errors);
|
||||
string prefix = GetStringParameter(task.Parameters, "output", errors);
|
||||
string format = GetStringParameter(task.Parameters, "format", errors);
|
||||
|
||||
if (errors.Any())
|
||||
{
|
||||
return new InvalidBlenderParameters(string.Join(Environment.NewLine, errors.Select(e => "* " + e)));
|
||||
}
|
||||
|
||||
return new ValidBlenderParameters(jobfile, prefix, format);
|
||||
}
|
||||
|
||||
|
||||
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 class ValidBlenderParameters : BlenderParameters
|
||||
{
|
||||
private readonly int _start;
|
||||
private readonly int _end;
|
||||
private readonly string _jobfile;
|
||||
private readonly string _prefix;
|
||||
private readonly string _format;
|
||||
|
||||
public ValidBlenderParameters(int start, int end, string jobfile, string prefix, string format)
|
||||
{
|
||||
_start = start;
|
||||
_end = end;
|
||||
_jobfile = jobfile;
|
||||
_prefix = prefix;
|
||||
_format = format;
|
||||
}
|
||||
|
||||
public ValidBlenderParameters(string jobfile, string prefix, string format)
|
||||
{
|
||||
_jobfile = jobfile;
|
||||
_prefix = prefix;
|
||||
_format = format;
|
||||
}
|
||||
|
||||
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 Prefix
|
||||
{
|
||||
get { return _prefix; }
|
||||
}
|
||||
|
||||
public override string Format
|
||||
{
|
||||
get { return _format; }
|
||||
}
|
||||
|
||||
public override string ErrorText
|
||||
{
|
||||
get { throw new InvalidOperationException("ErrorText does not apply to valid parameters"); }
|
||||
}
|
||||
}
|
||||
|
||||
private class InvalidBlenderParameters : BlenderParameters
|
||||
{
|
||||
private readonly string _errorText;
|
||||
|
||||
public InvalidBlenderParameters(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 Prefix
|
||||
{
|
||||
get { throw new InvalidOperationException("Prefix does not apply to invalid parameters"); }
|
||||
}
|
||||
|
||||
public override string Format
|
||||
{
|
||||
get { throw new InvalidOperationException("Format does not apply to invalid parameters"); }
|
||||
}
|
||||
|
||||
public override string ErrorText
|
||||
{
|
||||
get { return _errorText; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Blender.Cloud.Exceptions
|
||||
{
|
||||
[Serializable]
|
||||
public class InvalidParameterException : Exception
|
||||
{
|
||||
|
||||
public InvalidParameterException()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public InvalidParameterException(string message)
|
||||
: base(message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected InvalidParameterException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Blender.Cloud.Exceptions
|
||||
{
|
||||
[Serializable]
|
||||
public class NoOutputsFoundException : Exception
|
||||
{
|
||||
public NoOutputsFoundException()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public NoOutputsFoundException(string message)
|
||||
: base(message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected NoOutputsFoundException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Blender.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)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
///-------------------------------------------------------------------------
|
||||
/// The Blender Batch Apps Sample
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
///--------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using Blender.Cloud.Exceptions;
|
||||
using Microsoft.Azure.Batch.Apps.Cloud;
|
||||
|
||||
namespace Blender.Cloud
|
||||
{
|
||||
public class BlenderJobSplitter : 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 = BlenderParameters.FromJob(job);
|
||||
if (!jobParameters.Valid)
|
||||
{
|
||||
Log.Error(jobParameters.ErrorText);
|
||||
throw new InvalidParameterException(jobParameters.ErrorText);
|
||||
}
|
||||
|
||||
for (var i = jobParameters.Start; i <= jobParameters.End; i++)
|
||||
{
|
||||
yield return new TaskSpecifier
|
||||
{
|
||||
TaskIndex = i,
|
||||
Parameters = job.Parameters,
|
||||
RequiredFiles = job.Files,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
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("Blender.Cloud")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Blender.Cloud")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2014")]
|
||||
[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("240340f5-790c-471e-b643-9b50744129b6")]
|
||||
|
||||
// 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")]
|
|
@ -0,0 +1,277 @@
|
|||
///-------------------------------------------------------------------------
|
||||
/// The Blender Batch Apps Sample
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
///--------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Globalization;
|
||||
using System.IO.Compression;
|
||||
using System.Threading.Tasks;
|
||||
using System.Diagnostics;
|
||||
using Blender.Cloud.Exceptions;
|
||||
using Microsoft.Azure.Batch.Apps.Cloud;
|
||||
|
||||
namespace Blender.Cloud
|
||||
{
|
||||
public class BlenderTaskProcessor : ParallelTaskProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Path to the Blender executable
|
||||
/// </summary>
|
||||
private string RenderPath
|
||||
{
|
||||
get { return @"Blender\blender.exe"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Args with which to run Blender
|
||||
/// </summary>
|
||||
private string RenderArgs
|
||||
{
|
||||
get { return @"-b ""{0}"" -o ""{1}_####"" -F {2} -f {3} -t 0"; }
|
||||
}
|
||||
|
||||
|
||||
/// <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 = BlenderParameters.FromTask(task);
|
||||
var initialFiles = CollectFiles(LocalStoragePath);
|
||||
|
||||
if (!taskParameters.Valid)
|
||||
{
|
||||
Log.Error(taskParameters.ErrorText);
|
||||
return new TaskProcessResult
|
||||
{
|
||||
Success = TaskProcessSuccess.PermanentFailure,
|
||||
ProcessorOutput = "Parameter error: " + taskParameters.ErrorText,
|
||||
};
|
||||
}
|
||||
|
||||
var inputFile = LocalPath(taskParameters.JobFile);
|
||||
var outputFile = LocalPath(taskParameters.Prefix);
|
||||
|
||||
string externalProcessPath = ExecutablePath(RenderPath);
|
||||
string externalProcessArgs = string.Format(CultureInfo.InvariantCulture, RenderArgs, inputFile, outputFile, taskParameters.Format, task.TaskIndex);
|
||||
|
||||
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)
|
||||
{
|
||||
return new TaskProcessResult { Success = TaskProcessSuccess.RetryableFailure };
|
||||
}
|
||||
|
||||
var newFiles = GetNewFiles(initialFiles, LocalStoragePath);
|
||||
var result = TaskProcessResult.FromExternalProcessResult(processResult, newFiles);
|
||||
|
||||
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 taskParameters = BlenderParameters.FromTask(mergeTask);
|
||||
if (!taskParameters.Valid)
|
||||
{
|
||||
Log.Error(taskParameters.ErrorText);
|
||||
throw new InvalidParameterException(taskParameters.ErrorText);
|
||||
}
|
||||
|
||||
var inputFilter = string.Format( CultureInfo.InvariantCulture, "{0}*", taskParameters.Prefix);
|
||||
var inputFiles = CollectFiles(LocalStoragePath, inputFilter);
|
||||
|
||||
|
||||
|
||||
var outputFile = LocalPath("output.zip");
|
||||
var result = ZipOutputs(inputFiles, outputFile);
|
||||
result.PreviewFile = CreateThumbnail(mergeTask, inputFiles.ToArray());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a list of files that currently exist in a given location, accoding 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));
|
||||
}
|
||||
|
||||
/// <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);
|
||||
filesNow.RemoveWhere(f => f.EndsWith(".temp"));
|
||||
filesNow.RemoveWhere(f => f.EndsWith(".stdout"));
|
||||
filesNow.RemoveWhere(f => f.EndsWith(".log"));
|
||||
filesNow.RemoveWhere(f => f.EndsWith(".xml"));
|
||||
|
||||
return filesNow.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Zips up a supplied list of output files.
|
||||
/// </summary>
|
||||
/// <param name="inputs">List of files to include in the zip.</param>
|
||||
/// <param name="output">The output zip file path.</param>
|
||||
/// <returns>A JobResult with the new zip assigned as OutputFile.</returns>
|
||||
private static JobResult ZipOutputs(HashSet<string> inputs, string output)
|
||||
{
|
||||
if (inputs.Count < 1)
|
||||
{
|
||||
throw new NoOutputsFoundException("No job outputs found.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (ZipArchive outputs = ZipFile.Open(output, ZipArchiveMode.Create))
|
||||
{
|
||||
foreach (var input in inputs)
|
||||
{
|
||||
outputs.CreateEntryFromFile(input, Path.GetFileName(input), CompressionLevel.Optimal);
|
||||
}
|
||||
}
|
||||
|
||||
return new JobResult { OutputFile = output };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var error = string.Format("Failed to zip outputs: {0}", ex.ToString());
|
||||
throw new ZipException(error, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 filtered = inputs.Where(x => BlenderParameters.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 thumbnailerPath = ExecutablePath(@"ImageMagick\convert.exe");
|
||||
var thumbnailerArgs = string.Format(@"""{0}"" -thumbnail 200x150> ""{1}""", thumbInput, thumbOutput);
|
||||
|
||||
var processResult = ExecuteProcess(thumbnailerPath, thumbnailerArgs);
|
||||
|
||||
if (processResult != null)
|
||||
{
|
||||
Log.Info("Generated thumbnail from {0} at {1}", thumbInput, thumbOutput);
|
||||
return thumbOutput;
|
||||
}
|
||||
|
||||
Log.Info("No thumbnail generated");
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run a, executable with a given set of arguments.
|
||||
/// </summary>
|
||||
/// <param name="exePath">Path the executable.</param>
|
||||
/// <param name="exeArgs">The commandline 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Azure.Batch.Apps.Cloud" version="1.0.0" targetFramework="net45" />
|
||||
</packages>
|
|
@ -0,0 +1 @@
|
|||
v0.1.0, 2014-12-xx - Beta Release.
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
The Blender Batch Apps Sample ver. 0.1.0
|
||||
|
||||
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.
|
|
@ -0,0 +1,221 @@
|
|||
===============================
|
||||
Azure Batch Apps Blender Sample
|
||||
===============================
|
||||
|
||||
Microsoft Azure Batch Apps is an Azure service offering on-demand capacity for compute-intensive workloads.
|
||||
This is a sample project utilizing the Azure Batch Apps SDK and the Azure Batch Apps Python client to show how
|
||||
one could set up a cloud-based rendering platform using Blender.
|
||||
|
||||
The sample involves two parts, the cloud assembly project, and the Blender client project for job submission.
|
||||
For more information on Batch Apps concepts, terms, and project structure `check out this article <http://azure.microsoft.com/en-us/documentation/articles/batch-dotnet-get-started/#tutorial2>`_.
|
||||
|
||||
The client project is a python 'Addon' for Blender, to allow for a seamless user experience for submitting render
|
||||
jobs to the cloud from within Blender.
|
||||
|
||||
|
||||
License
|
||||
========
|
||||
|
||||
This project is licensed under the MIT License.
|
||||
For details see LICENSE.txt or visit `<http://opensource.org/licenses/MIT>`_.
|
||||
|
||||
Blender
|
||||
========
|
||||
|
||||
Blender is a free and open source 3D animation suite.
|
||||
It supports the entirety of the 3D pipeline—modeling, rigging, animation, simulation, rendering, compositing and motion tracking, even video editing and game creation.
|
||||
Advanced users employ Blender’s API for Python scripting to customize the application and write specialized tools; often these are included in Blender’s future releases.
|
||||
Blender is well suited to individuals and small studios who benefit from its unified pipeline and responsive development process.
|
||||
|
||||
For more information and to download Blender, visit `<http://www.blender.org>`_.
|
||||
|
||||
|
||||
Set up
|
||||
======
|
||||
|
||||
In order to build the projects you will need to have the following tools:
|
||||
|
||||
- `Microsoft Azure Batch Apps Cloud SDK <http://www.nuget.org/packages/Microsoft.Azure.Batch.Apps.Cloud/>`_
|
||||
- `Python Tools for Visual Studio <http://pytools.codeplex.com/>`_
|
||||
- `Azure Batch Apps Python Client and it's required packages <https://github.com/Azure/azure-batch-apps-python>`_
|
||||
|
||||
|
||||
|
||||
Part 1. Blender.Cloud
|
||||
======================
|
||||
|
||||
This project builds a cloud assembly for running rendering jobs using Blender.
|
||||
A "cloud assembly" is a zip file containing an application-specific DLL with logic for splitting
|
||||
jobs into tasks, and for executing each of those tasks. In this sample, we split the job into
|
||||
a task for each frame to be rendered, and execute each task by running the blender.exe program.
|
||||
The cloud assembly goes hand in hand with an "application image," a zip file
|
||||
containing the program or programs to be executed. In this sample, we have used Blender and
|
||||
ImageMagick in the application image.
|
||||
|
||||
|
||||
Building the Cloud Assembly
|
||||
---------------------------
|
||||
|
||||
To build the cloud assembly zip file:
|
||||
1. Build the Blender.Cloud project.
|
||||
2. Open the output folder of the Blender.Cloud project.
|
||||
3. Select all the DLLs (and optionally PDB files) in the output folder.
|
||||
4. Right-click and choose Send To > Compressed Folder.
|
||||
|
||||
|
||||
Building the Application Image
|
||||
-------------------------------
|
||||
|
||||
The application image contains the following applications:
|
||||
* `Blender <http://www.blender.org/download/>`_ - The application we want to cloud-enable.
|
||||
It is easiest in this scenario to download as a zip rather than the installer.
|
||||
* `ImageMagick <http://www.imagemagick.org/script/binary-releases.php#windows>`_- (Optional) Tool for creating preview thumbnails
|
||||
of the rendered frames. Locate the `portable Win32 static build <http://www.imagemagick.org/download/binaries/ImageMagick-6.9.0-2-Q16-x86-windows.zip>`_.
|
||||
|
||||
To build the application image zip file:
|
||||
1. Open http://www.blender.org/download/.
|
||||
2. Locate the 64bit zip download for the latest Blender release. `Direct download of 2.72b here <http://mirror.cs.umn.edu/blender.org/release/Blender2.72/blender-2.72b-windows64.zip>`_.
|
||||
3. Extract the subfolder (blender-2.7x-windows64) to a location of choice and rename it 'Blender'.
|
||||
|
||||
4. Open http://www.imagemagick.org/script/binary-releases.php#windows
|
||||
5. Locate the portable Win32 static build. It is important to use the portable build! `Direct
|
||||
download of 6.9.0-3 here <http://www.imagemagick.org/download/binaries/ImageMagick-6.9.0-2-Q16-x86-windows.zip>`_.
|
||||
6. Extract the subfolder (ImageMagick-6.x.x) next to the earlier 'Blender' directory, and rename it to 'ImageMagick'.
|
||||
|
||||
7. Select both the 'Blender' and 'ImageMagick' directories, right-click and choose Send To > Compressed Folder.
|
||||
8. Rename the resulting zip file to Blender.zip
|
||||
|
||||
The final application image zip file should have the following structure:
|
||||
|
||||
Blender.zip
|
||||
|
|
||||
| -- Blender
|
||||
| |
|
||||
| | -- blender.exe
|
||||
| | -- 2.7x directory
|
||||
| | -- other Blender components
|
||||
|
|
||||
| -- ImageMagick
|
||||
|
|
||||
| -- convert.exe
|
||||
| -- other ImageMagick components
|
||||
|
||||
|
||||
Uploading the Application to Your Batch Apps Service
|
||||
-----------------------------------------------------
|
||||
|
||||
1. Open the Azure management portal (manage.windowsazure.com).
|
||||
2. Select Batch Services in the left-hand menu.
|
||||
3. Select your service in the list and click "Manage Batch Apps." This opens the Batch Apps management
|
||||
portal.
|
||||
4. Select Services in the left-hand menu.
|
||||
5. Select your service in the list and click View Details.
|
||||
6. Choose the Manage Applications tab.
|
||||
7. Click New Application.
|
||||
8. Under "Select and upload a cloud assembly," choose your cloud assembly zip file and click Upload.
|
||||
9. Under "Select and upload an application image," choose your application image zip file and click Upload.
|
||||
(Be sure to leave the version as "default".)
|
||||
10. Click Done.
|
||||
|
||||
|
||||
|
||||
Part 2. Blender.Client
|
||||
=======================
|
||||
|
||||
No that the Blender rendering service is configured in Batch Apps, we need a way to submit Blender files
|
||||
to be rendered.
|
||||
The sample client is an Addon for Blender written in Python, that can be used on multiple platforms.
|
||||
|
||||
Python Setup
|
||||
-------------
|
||||
|
||||
The Addon requires some additional Python packages in order to run.
|
||||
By default, Blender is shipped with it's own Python environment, so it's into this environment that these
|
||||
packages will need to be installed.
|
||||
There are several approaches one could take:
|
||||
|
||||
- If there is already an installation of Python 3.4 on the machine, one can use pip to install the required
|
||||
packages, then copy them into the Blender bundled Python environment.
|
||||
Source: ~\Python34\Lib\site-packages -> Destination: ~\Blender Foundation\blender\2.7x\python\lib\site-packages
|
||||
|
||||
- Download the packages directly from `<pypi.python.org>`_, and copy them into the Blender bundled Python environment.
|
||||
Destination: ~\Blender Foundation\blender\2.7x\python\lib\site-packages
|
||||
|
||||
The required packages are the following:
|
||||
|
||||
- `Batch Apps Python Client <https://pypi.python.org/pypi/azure-batch-apps>`_
|
||||
- `Keyring <https://pypi.python.org/pypi/keyring>`_
|
||||
- `OAuthLib <https://pypi.python.org/pypi/oauthlib>`_
|
||||
- `Requests-OAuthLib <https://pypi.python.org/pypi/requests-oauthlib>`_
|
||||
- Note: additional package `Requests <https://pypi.python.org/pypi/requests>`_ already comes bundled with Blender.
|
||||
|
||||
|
||||
Building and Installing the Addon
|
||||
----------------------------------
|
||||
|
||||
Set Blender.Client as the 'Start-up Project' in Visual Studio.
|
||||
By running the Blender.Client project, the Addon will be packaged into a Zip located in Blender.Client\build
|
||||
To install the Addon:
|
||||
|
||||
1. Run Blender
|
||||
2. Open File -> User Preferences
|
||||
3. Navigate to the Addons tab
|
||||
4. Click 'Install from File...' at the bottom of the dialog window.
|
||||
5. Navigate to and select the packaged client zip.
|
||||
6. The Addon 'Batch Apps Blender' will now be registered under the 'Render' category. Once located, select the
|
||||
check box to activate the Addon.
|
||||
7. Once activated, the Addon UI will appear in the 'Render Properties' panel - by default, in the lower right corner
|
||||
of the screen.
|
||||
|
||||
|
||||
Addon Logging and Configuration
|
||||
--------------------------------
|
||||
|
||||
The sample addon logs to both Blenders stdout and to file.
|
||||
By default this log file will be saved to $HOME/BatchAppsData. This directory is also the location of the Addon
|
||||
configuration file.
|
||||
|
||||
This directory, the config file to use, and the level of logging detail are all configurable within the Blender UI.
|
||||
The authentication configuration settings of the file can also be overridden in the Blender UI.
|
||||
|
||||
1. Run Blender
|
||||
2. Open File -> User Preferences
|
||||
3. Navigate to the Addons tab
|
||||
4. Either search for 'Batch Apps Blender', or navigate to the Addon under the 'Render' category.
|
||||
5. Select the arrow next to the Addon to open the details drop down - here you will find info on the version and installation directory.
|
||||
6. Listed here you will also find the configuration preferences. If modified, click 'Save User Settings' at the bottom
|
||||
of the dialog window (Note: this will also cause the Batch Apps Blender Addon to be activated on Blender start-up).
|
||||
7. Once saved, restart Blender for the changes to take effect.
|
||||
|
||||
|
||||
Authentication
|
||||
---------------
|
||||
|
||||
To run this addon you will need:
|
||||
- Your Batch Apps service URL
|
||||
- Unattended account credentials for your Batch Apps service
|
||||
|
||||
1. Open the Azure management portal (manage.windowsazure.com).
|
||||
2. Select Batch Services in the left-hand menu.
|
||||
3. Select your service in the list and click "Manage Batch Apps." This opens the Batch Apps management
|
||||
portal.
|
||||
4. Select Services in the left-hand menu.
|
||||
5. Select your service in the list and click View Details.
|
||||
6. Copy the service URL from the page and paste it into the 'Service URL' field in the Blender User Preferences.
|
||||
7. Click the Unattended Account button at the bottom of the page.
|
||||
8. Copy the Account ID from the page and paste it into the 'Unattended Account' field in the Blender User Preferences.
|
||||
9. Below the Account Keys list, select "3 months" and click the Add Key button.
|
||||
Copy the generated key and paste it into the project App.config UnattendedAccountKey field.
|
||||
NOTE: the generated key will be shown only once! If you accidentally close the page
|
||||
before copying the key, just reopen it and add a new key.
|
||||
|
||||
|
||||
Addon Documentation
|
||||
--------------------
|
||||
|
||||
The Addon User Guide can be found `here <PENDING_URL>`_.
|
||||
Auto generated Sphinx documentation for the Addon code can be found `here <PENDING_URL>`_.
|
||||
|
||||
|
||||
|
||||
|
Двоичный файл не отображается.
Загрузка…
Ссылка в новой задаче