Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Letwory <nathan@letworyinteractive.com>2012-07-13 10:33:01 +0400
committerNathan Letwory <nathan@letworyinteractive.com>2012-07-13 10:33:01 +0400
commit62f10cc24dab453fd9d6a9e09f59adbdb42156ac (patch)
tree95dd92c307c8b074b607820f84528db0d399976e /render_renderfarmfi
parent65e61d8730aa64dbd8a48471c68522e6c9bc385b (diff)
Refactor the uploader code into a nicer module.
* xmlrpc usage encapsulated * utility functions grouped * UI and Operators separated * Encapsulate exceptions As with previous commit, this version now utilizes the new way of storing user credentials, making it a lot easier for users to use the uploader in subsequent usages. Note that refreshing the session list isn't done on login anymore so remember to press that button :)
Diffstat (limited to 'render_renderfarmfi')
-rw-r--r--render_renderfarmfi/__init__.py219
-rw-r--r--render_renderfarmfi/exceptions.py41
-rw-r--r--render_renderfarmfi/operators.py338
-rw-r--r--render_renderfarmfi/ore_session.py40
-rw-r--r--render_renderfarmfi/panels.py265
-rw-r--r--render_renderfarmfi/prepare.py190
-rw-r--r--render_renderfarmfi/rpc.py186
-rw-r--r--render_renderfarmfi/upload.py192
-rw-r--r--render_renderfarmfi/utils.py144
9 files changed, 1615 insertions, 0 deletions
diff --git a/render_renderfarmfi/__init__.py b/render_renderfarmfi/__init__.py
new file mode 100644
index 00000000..a1aa10e5
--- /dev/null
+++ b/render_renderfarmfi/__init__.py
@@ -0,0 +1,219 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_info = {
+ "name": "Renderfarm.fi",
+ "author": "Nathan Letwory <nathan@letworyinteractive.com>, Jesse Kaukonen <jesse.kaukonen@gmail.com>",
+ "version": (21,),
+ "blender": (2, 6, 3),
+ "location": "Render > Engine > Renderfarm.fi",
+ "description": "Send .blend as session to http://www.renderfarm.fi to render",
+ "warning": "",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Render/Renderfarm.fi",
+ "tracker_url": "https://projects.blender.org/tracker/index.php?"\
+ "func=detail&aid=22927",
+ "category": "Render"}
+
+"""
+Copyright 2009-2012 Laurea University of Applied Sciences
+Authors: Nathan Letwory, Jesse Kaukonen
+"""
+
+import bpy
+import hashlib
+import http.client
+import math
+from os.path import isabs, isfile, join, exists
+import os
+import time
+
+from bpy.props import PointerProperty, StringProperty, BoolProperty, EnumProperty, IntProperty, CollectionProperty
+
+from .panels import *
+from .operators import *
+from .rpc import RffiRpc
+
+bpy.CURRENT_VERSION = bl_info["version"][0]
+bpy.found_newer_version = False
+bpy.up_to_date = False
+bpy.download_location = 'http://www.renderfarm.fi/blender'
+
+bpy.rffi_creds_found = False
+bpy.rffi_user = ''
+bpy.rffi_hash = ''
+bpy.passwordCorrect = False
+bpy.loginInserted = False
+
+bpy.errorMessages = {
+ 'missing_desc': 'You need to enter a title, short and long description',
+ 'missing_creds': 'You haven\'t entered your credentials yet'
+}
+
+bpy.statusMessage = {
+ 'title': 'TRIA_RIGHT',
+ 'shortdesc': 'TRIA_RIGHT',
+ 'tags': 'TRIA_RIGHT',
+ 'longdesc': 'TRIA_RIGHT',
+ 'username': 'TRIA_RIGHT',
+ 'password': 'TRIA_RIGHT'
+}
+
+bpy.errors = []
+bpy.ore_sessions = []
+bpy.ore_completed_sessions = []
+bpy.ore_active_sessions = []
+bpy.ore_rejected_sessions = []
+bpy.ore_pending_sessions = []
+bpy.ore_active_session_queue = []
+bpy.ore_complete_session_queue = []
+bpy.queue_selected = -1
+bpy.errorStartTime = -1.0
+bpy.infoError = False
+bpy.cancelError = False
+bpy.texturePackError = False
+bpy.linkedFileError = False
+bpy.uploadInProgress = False
+bpy.originalFileName = bpy.data.filepath
+bpy.particleBakeWarning = False
+bpy.childParticleWarning = False
+bpy.simulationWarning = False
+bpy.file_format_warning = False
+bpy.ready = False
+
+
+def renderEngine(render_engine):
+ bpy.utils.register_class(render_engine)
+ return render_engine
+
+licenses = (
+ ('1', 'CC by-nc-nd', 'Creative Commons: Attribution Non-Commercial No Derivatives'),
+ ('2', 'CC by-nc-sa', 'Creative Commons: Attribution Non-Commercial Share Alike'),
+ ('3', 'CC by-nd', 'Creative Commons: Attribution No Derivatives'),
+ ('4', 'CC by-nc', 'Creative Commons: Attribution Non-Commercial'),
+ ('5', 'CC by-sa', 'Creative Commons: Attribution Share Alike'),
+ ('6', 'CC by', 'Creative Commons: Attribution'),
+ ('7', 'Copyright', 'Copyright, no license specified'),
+ )
+
+class ORESession(bpy.types.PropertyGroup):
+ name = StringProperty(name='Name', description='Name of the session', maxlen=128, default='[session]')
+
+class ORESettings(bpy.types.PropertyGroup):
+ username = StringProperty(name='E-mail', description='E-mail for Renderfarm.fi', maxlen=256, default='')
+ password = StringProperty(name='Password', description='Renderfarm.fi password', maxlen=256, default='')
+
+ shortdesc = StringProperty(name='Short description', description='A short description of the scene (100 characters)', maxlen=101, default='-')
+ tags = StringProperty(name='Tags', description='A list of tags that best suit the animation', maxlen=102, default='')
+ longdesc = StringProperty(name='Description', description='Description of the scene (2k)', maxlen=2048, default='')
+ title = StringProperty(name='Title', description='Title for this session (128 characters)', maxlen=128, default='')
+ url = StringProperty(name='Project URL', description='Project URL. Leave empty if not applicable', maxlen=256, default='')
+ engine = StringProperty(name='Engine', description='The rendering engine that is used for rendering', maxlen=64, default='blender')
+ samples = IntProperty(name='Samples', description='Number of samples that is used (Cycles only)', min=1, max=1000000, soft_min=1, soft_max=100000, default=100)
+ subsamples = IntProperty(name='Subsample Frames', description='Number of subsample frames that is used (Cycles only)', min=1, max=1000000, soft_min=1, soft_max=1000, default=10)
+ file_format = StringProperty(name='File format', description='File format used for the rendering', maxlen=30, default='PNG_FORMAT')
+
+ parts = IntProperty(name='Parts/Frame', description='', min=1, max=1000, soft_min=1, soft_max=64, default=1)
+ resox = IntProperty(name='Resolution X', description='X of render', min=1, max=10000, soft_min=1, soft_max=10000, default=1920)
+ resoy = IntProperty(name='Resolution Y', description='Y of render', min=1, max=10000, soft_min=1, soft_max=10000, default=1080)
+ memusage = IntProperty(name='Memory Usage', description='Estimated maximum memory usage during rendering in MB', min=1, max=6*1024, soft_min=1, soft_max=3*1024, default=256)
+ start = IntProperty(name='Start Frame', description='Start Frame', default=1)
+ end = IntProperty(name='End Frame', description='End Frame', default=250)
+ fps = IntProperty(name='FPS', description='FPS', min=1, max=120, default=25)
+
+ prepared = BoolProperty(name='Prepared', description='Set to True if preparation has been run', default=False)
+ debug = BoolProperty(name='Debug', description='Verbose output in console', default=False)
+ selected_session = IntProperty(name='Selected Session', description='The selected session', default=0)
+ hasUnsupportedSimulation = BoolProperty(name='HasSimulation', description='Set to True if therea re unsupported simulations', default=False)
+
+ inlicense = EnumProperty(items=licenses, name='Scene license', description='License speficied for the source files', default='1')
+ outlicense = EnumProperty(items=licenses, name='Product license', description='License speficied for the output files', default='1')
+ sessions = CollectionProperty(type=ORESession, name='Sessions', description='Sessions on Renderfarm.fi')
+ completed_sessions = CollectionProperty(type=ORESession, name='Completed sessions', description='Sessions that have been already rendered')
+ rejected_sessions = CollectionProperty(type=ORESession, name='Rejected sessions', description='Sessions that have been rejected')
+ pending_sessions = CollectionProperty(type=ORESession, name='Pending sessions', description='Sessions that are waiting for approval')
+ active_sessions = CollectionProperty(type=ORESession, name='Active sessions', description='Sessions that are currently rendering')
+ all_sessions = CollectionProperty(type=ORESession, name='All sessions', description='List of all of the users sessions')
+
+# session struct
+
+
+class RENDERFARM_MT_Session(bpy.types.Menu):
+ bl_label = "Show Session"
+
+ def draw(self, context):
+ layout = self.layout
+ ore = context.scene.ore_render
+
+ if (bpy.loginInserted == True):
+ layout.operator('ore.completed_sessions')
+ layout.operator('ore.accept_sessions')
+ layout.operator('ore.active_sessions')
+ layout.separator()
+ layout.operator('ore.cancelled_sessions')
+ else:
+ row = layout.row()
+ row.label(text="You must login first")
+
+
+class RenderfarmFi(bpy.types.RenderEngine):
+ bl_idname = 'RENDERFARMFI_RENDER'
+ bl_label = "Renderfarm.fi"
+
+ def render(self, scene):
+ print('Do test renders with Blender Render')
+
+def register():
+ bpy.utils.register_module(__name__)
+ bpy.types.Scene.ore_render = PointerProperty(type=ORESettings, name='ORE Render', description='ORE Render Settings')
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
+
+if __name__ == "__main__":
+ register()
+
+# all panels, except render panel
+# Example of wrapping every class 'as is'
+from bl_ui import properties_scene
+for member in dir(properties_scene):
+ subclass = getattr(properties_scene, member)
+ try: subclass.COMPAT_ENGINES.add('RENDERFARMFI_RENDER')
+ except: pass
+del properties_scene
+
+from bl_ui import properties_world
+for member in dir(properties_world):
+ subclass = getattr(properties_world, member)
+ try: subclass.COMPAT_ENGINES.add('RENDERFARMFI_RENDER')
+ except: pass
+del properties_world
+
+from bl_ui import properties_material
+for member in dir(properties_material):
+ subclass = getattr(properties_material, member)
+ try: subclass.COMPAT_ENGINES.add('RENDERFARMFI_RENDER')
+ except: pass
+del properties_material
+
+from bl_ui import properties_object
+for member in dir(properties_object):
+ subclass = getattr(properties_object, member)
+ try: subclass.COMPAT_ENGINES.add('RENDERFARMFI_RENDER')
+ except: pass
+del properties_object
diff --git a/render_renderfarmfi/exceptions.py b/render_renderfarmfi/exceptions.py
new file mode 100644
index 00000000..4d62562c
--- /dev/null
+++ b/render_renderfarmfi/exceptions.py
@@ -0,0 +1,41 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+class RenderfarmException(Exception):
+ def __init__(self, msg="no reason given"):
+ self.message = msg
+ def __str__(self):
+ return "RenderfarmException: "+self.message
+
+class LoginFailedException(Exception):
+ def __init__(self, msg="no reason given"):
+ self.message = msg
+ def __str__(self):
+ return "Login failed: "+self.message
+
+class SessionCancelFailedException(Exception):
+ def __init__(self, msg="no reason given"):
+ self.message = msg
+ def __str__(self):
+ return "Session could not be cancelled: "+self.message
+
+class GetSessionsFailedException(Exception):
+ def __init__(self, msg="no reason given"):
+ self.message = msg
+ def __str__(self):
+ return "Session List could not be fetched: "+self.message
diff --git a/render_renderfarmfi/operators.py b/render_renderfarmfi/operators.py
new file mode 100644
index 00000000..87d75167
--- /dev/null
+++ b/render_renderfarmfi/operators.py
@@ -0,0 +1,338 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import hashlib
+
+import bpy
+
+from .utils import _write_credentials, _read_credentials
+from .prepare import _prepare_scene
+from .upload import _ore_upload
+from .rpc import rffi, _do_refresh
+from .exceptions import LoginFailedException, SessionCancelFailedException
+
+class OpSwitchRenderfarm(bpy.types.Operator):
+ bl_label = "Switch to Renderfarm.fi"
+ bl_idname = "ore.switch_to_renderfarm_render"
+
+ def execute(self, context):
+ ore = bpy.context.scene.ore_render
+ rd = bpy.context.scene.render
+
+ ore.resox = rd.resolution_x
+ ore.resoy = rd.resolution_y
+ ore.fps = rd.fps
+ ore.start = bpy.context.scene.frame_start
+ ore.end = bpy.context.scene.frame_end
+ if (rd.engine == 'CYCLES'):
+ ore.samples = bpy.context.scene.cycles.samples
+ ore.engine = 'cycles'
+ else:
+ ore.engine = 'blender'
+ bpy.context.scene.render.engine = 'RENDERFARMFI_RENDER'
+ return {'FINISHED'}
+
+class OpSwitchBlenderRender(bpy.types.Operator):
+ bl_label = "Switch to local render"
+ bl_idname = "ore.switch_to_local_render"
+
+ def execute(self, context):
+ rd = bpy.context.scene.render
+ ore = bpy.context.scene.ore_render
+ rd.resolution_x = ore.resox
+ rd.resolution_y = ore.resoy
+ rd.fps = ore.fps
+ bpy.context.scene.frame_start = ore.start
+ bpy.context.scene.frame_end = ore.end
+ if (bpy.context.scene.ore_render.engine == 'cycles'):
+ rd.engine = 'CYCLES'
+ bpy.context.scene.cycles.samples = ore.samples
+ else:
+ bpy.context.scene.render.engine = 'BLENDER_RENDER'
+ return {'FINISHED'}
+
+# Copies start & end frame + others from render settings to ore settings
+class OpCopySettings(bpy.types.Operator):
+ bl_label = "Copy settings from current scene"
+ bl_idname = "ore.copy_settings"
+
+ def execute(self, context):
+ sce = bpy.context.scene
+ rd = sce.render
+ ore = sce.ore_render
+ ore.resox = rd.resolution_x
+ ore.resoy = rd.resolution_y
+ ore.start = sce.frame_start
+ ore.end = sce.frame_end
+ ore.fps = rd.fps
+ return {'FINISHED'}
+
+class ORE_RefreshOp(bpy.types.Operator):
+ bl_idname = 'ore.refresh_session_list'
+ bl_label = 'Refresh'
+
+ def execute(self, context):
+ result = _do_refresh(self)
+ if (result == 0):
+ return {'FINISHED'}
+ else:
+ return {'CANCELLED'}
+
+class ORE_OpenDownloadLocation(bpy.types.Operator):
+ bl_idname = 'ore.open_download_location'
+ bl_label = 'Download new version for your platform'
+
+ def execute(self, context):
+ import webbrowser
+ webbrowser.open(bpy.download_location)
+ return {'FINISHED'}
+
+class ORE_CancelSession(bpy.types.Operator):
+ bl_idname = 'ore.cancel_session'
+ bl_label = 'Cancel Session'
+
+ def execute(self, context):
+ sce = context.scene
+ ore = sce.ore_render
+ if len(bpy.ore_complete_session_queue)>0:
+ s = bpy.ore_complete_session_queue[ore.selected_session]
+ try:
+ rffi.cancel_session(self, s)
+ except SessionCancelFailedException as scfe:
+ print("sessioncancelfailedexception", scfe)
+
+ return {'FINISHED'}
+
+class ORE_GetCompletedSessions(bpy.types.Operator):
+ bl_idname = 'ore.completed_sessions'
+ bl_label = 'Completed sessions'
+
+ def execute(self, context):
+ sce = context.scene
+ ore = sce.ore_render
+ bpy.queue_selected = 1
+ bpy.ore_active_session_queue = bpy.ore_completed_sessions
+ update_session_list(completed_sessions, ore)
+
+ return {'FINISHED'}
+
+class ORE_GetCancelledSessions(bpy.types.Operator):
+ bl_idname = 'ore.cancelled_sessions'
+ bl_label = 'Cancelled sessions'
+
+ def execute(self, context):
+ sce = context.scene
+ ore = sce.ore_render
+ bpy.queue_selected = 4
+ bpy.ore_active_session_queue = bpy.ore_cancelled_sessions
+ update_session_list(cancelled_sessions, ore)
+
+ return {'FINISHED'}
+
+class ORE_GetActiveSessions(bpy.types.Operator):
+ bl_idname = 'ore.active_sessions'
+ bl_label = 'Rendering sessions'
+
+ def execute(self, context):
+ sce = context.scene
+ ore = sce.ore_render
+ bpy.queue_selected = 2
+ bpy.ore_active_session_queue = bpy.ore_active_sessions
+ update_session_list(active_sessions, ore)
+
+ return {'FINISHED'}
+
+class ORE_GetPendingSessions(bpy.types.Operator):
+ bl_idname = 'ore.accept_sessions' # using ORE lingo in API. acceptQueue is session waiting for admin approval
+ bl_label = 'Pending sessions'
+
+ def execute(self, context):
+ sce = context.scene
+ ore = sce.ore_render
+ bpy.queue_selected = 3
+ bpy.ore_active_session_queue = bpy.ore_pending_sessions
+ update_session_list(pending_sessions, ore)
+
+ return {'FINISHED'}
+
+class ORE_CheckUpdate(bpy.types.Operator):
+ bl_idname = 'ore.check_update'
+ bl_label = 'Check for a new version'
+
+ def execute(self, context):
+ blenderproxy = xmlrpc.client.ServerProxy(r'http://xmlrpc.renderfarm.fi/renderfarmfi/blender', verbose=bpy.RFFI_VERBOSE)
+ try:
+ self.report({'INFO'}, 'Checking for newer version on Renderfarm.fi')
+ dl_url = blenderproxy.blender.getCurrentVersion(bpy.CURRENT_VERSION)
+ if len(dl_url['url']) > 0:
+ self.report({'INFO'}, 'Found a newer version on Renderfarm.fi ' + dl_url['url'])
+ bpy.download_location = dl_url['url']
+ bpy.found_newer_version = True
+ else:
+ bpy.up_to_date = True
+ self.report({'INFO'}, 'Done checking for newer version on Renderfarm.fi')
+ except xmlrpc.client.Fault as f:
+ print('ERROR:', f)
+ self.report({'ERROR'}, 'An error occurred while checking for newer version on Renderfarm.fi: ' + f.faultString)
+ except xmlrpc.client.ProtocolError as e:
+ print('ERROR:', e)
+ self.report({'ERROR'}, 'An HTTP error occurred while checking for newer version on Renderfarm.fi: ' + str(e.errcode) + ' ' + e.errmsg)
+
+ return {'FINISHED'}
+
+class ORE_LoginOp(bpy.types.Operator):
+ bl_idname = 'ore.login'
+ bl_label = 'Login'
+
+ def execute(self, context):
+ sce = context.scene
+ ore = sce.ore_render
+
+ ore.password = ore.password.strip()
+ ore.username = ore.username.strip()
+
+ if ore.password != '' and ore.username != '':
+ print("writing new credentials")
+ _write_credentials(hashlib.md5(ore.password.encode() + ore.username.encode()).hexdigest(),ore.username)
+ _read_credentials()
+ ore.password = ''
+ ore.username = ''
+ bpy.loginInserted = False
+ bpy.passwordCorrect = False
+
+ try:
+ _do_refresh(self, True)
+
+ bpy.passwordCorrect = True
+ bpy.loginInserted = True
+
+ except LoginFailedException as v:
+ bpy.ready = False
+ bpy.loginInserted = False
+ bpy.passwordCorrect = False
+ ore.username = bpy.rffi_user
+ _write_credentials('', '')
+ _read_credentials()
+ ore.hash = ''
+ ore.password = ''
+ self.report({'WARNING'}, "Incorrect login: " + str(v))
+ print(v)
+ return {'CANCELLED'}
+
+ return {'FINISHED'}
+
+class ORE_ResetOp(bpy.types.Operator):
+ bl_idname = "ore.reset"
+ bl_label = "Reset Preparation"
+
+ def execute(self, context):
+ sce = context.scene
+ ore = sce.ore_render
+ ore.prepared = False
+ bpy.loginInserted = False
+ bpy.prepared = False
+ ore.hash = ''
+ ore.username = ''
+ ore.passowrd = ''
+ ore.longdesc = ''
+ ore.shortdesc = '-'
+ ore.tags = ''
+ ore.title = ''
+ ore.url = ''
+
+ return {'FINISHED'}
+
+class ORE_TestRenderOp(bpy.types.Operator):
+ bl_idname = "ore.test_render"
+ bl_label = "Run a test render"
+
+ def execute(self, context):
+ rd = context.scene.render
+ rd.engine = 'BLENDER_RENDER'
+ rd.threads_mode = 'AUTO'
+ rd.threads = 1
+ bpy.ops.render.render()
+ rd.threads_mode = 'FIXED'
+ rd.threads = 1
+ rd.engine = 'RENDERFARMFI_RENDER'
+ return {'FINISHED'}
+
+class ORE_UploaderOp(bpy.types.Operator):
+ bl_idname = "ore.upload"
+ bl_label = "Render on Renderfarm.fi"
+
+ def execute(self, context):
+
+ bpy.uploadInProgress = True
+ _prepare_scene()
+
+ returnValue = _ore_upload(self, context)
+ bpy.uploadInProgress = False
+ return returnValue
+
+class ORE_UseBlenderReso(bpy.types.Operator):
+ bl_idname = "ore.use_scene_settings"
+ bl_label = "Use Scene settings"
+
+ def execute(self, context):
+ sce = context.scene
+ ore = sce.ore_render
+ rd = context.scene.render
+
+ ore.resox = rd.resolution_x
+ ore.resoy = rd.resolution_y
+ ore.start = sce.frame_start
+ ore.end = sce.frame_end
+ ore.fps = rd.fps
+
+ return {'FINISHED'}
+
+class ORE_UseCyclesRender(bpy.types.Operator):
+ bl_idname = "ore.use_cycles_render"
+ bl_label = "Cycles"
+
+ def execute(self, context):
+ context.scene.ore_render.engine = 'cycles'
+ return {'FINISHED'}
+
+class ORE_UseBlenderRender(bpy.types.Operator):
+ bl_idname = "ore.use_blender_render"
+ bl_label = "Blender Internal"
+
+ def execute(self, context):
+ context.scene.ore_render.engine = 'blender'
+ return {'FINISHED'}
+
+class ORE_ChangeUser(bpy.types.Operator):
+ bl_idname = "ore.change_user"
+ bl_label = "Change user"
+
+ def execute(self, context):
+ ore = context.scene.ore_render
+ _write_credentials('', '')
+ _read_credentials()
+ ore.password = ''
+ bpy.ore_sessions = []
+ ore.hash = ''
+ bpy.rffi_user = ''
+ bpy.rffi_hash = ''
+ bpy.rffi_creds_found = False
+ bpy.passwordCorrect = False
+ bpy.loginInserted = False
+
+ return {'FINISHED'}
diff --git a/render_renderfarmfi/ore_session.py b/render_renderfarmfi/ore_session.py
new file mode 100644
index 00000000..fdbad807
--- /dev/null
+++ b/render_renderfarmfi/ore_session.py
@@ -0,0 +1,40 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+from math import floor
+
+class OreSession:
+
+ def __init__(self, id, title):
+ self.id = id
+ self.title = title
+ self.frames = 0
+ self.startframe = 0
+ self.endframe = 0
+ self.rendertime = 0
+ self.percentage = 0
+
+ def percentageComplete(self):
+ totFrames = self.endframe - self.startframe
+ done = 0
+ if totFrames != 0:
+ done = floor((self.frames / totFrames)*100)
+
+ if done > 100:
+ done = 100
+ return done
diff --git a/render_renderfarmfi/panels.py b/render_renderfarmfi/panels.py
new file mode 100644
index 00000000..6e46cfac
--- /dev/null
+++ b/render_renderfarmfi/panels.py
@@ -0,0 +1,265 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+import time
+
+from .utils import _read_credentials, check_status
+from .rpc import rffi
+from .exceptions import LoginFailedException
+
+class RenderButtonsPanel():
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "render"
+ # COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here
+
+class EngineSelectPanel(RenderButtonsPanel, bpy.types.Panel):
+ bl_idname = "OBJECT_PT_engineSelectPanel"
+ bl_label = "Choose rendering mode"
+ COMPAT_ENGINES = set(['RENDERFARMFI_RENDER'])
+
+ def draw(self, context):
+ layout = self.layout
+ rd = context.scene.render
+ row = layout.row()
+ row.operator("ore.switch_to_renderfarm_render", text="Renderfarm.fi", icon='WORLD')
+ row.operator("ore.switch_to_local_render", text="Local computer", icon='BLENDER')
+# row = layout.row()
+# if (bpy.context.scene.render.engine == 'RENDERFARMFI_RENDER'):
+# if bpy.found_newer_version == True:
+# layout.operator('ore.open_download_location')
+# else:
+# if bpy.up_to_date == True:
+# layout.label(text='You have the latest version')
+# layout.operator('ore.check_update')
+
+class LOGIN_PT_RenderfarmFi(RenderButtonsPanel, bpy.types.Panel):
+ bl_label = 'Login to Renderfarm.fi'
+ COMPAT_ENGINES = set(['RENDERFARMFI_RENDER'])
+
+ @classmethod
+ def poll(cls, context):
+ rd = context.scene.render
+ return (rd.use_game_engine==False) and (rd.engine in cls.COMPAT_ENGINES)
+
+ def draw(self, context):
+
+ # login
+ if not bpy.loginInserted:
+ if _read_credentials():
+ try:
+ if rffi.login(None, True, False):
+ bpy.passwordCorrect = True
+ bpy.loginInserted = True
+ except LoginFailedException as lfe:
+ bpy.passwordCorrect = False
+ bpy.loginInserted = False
+
+ layout = self.layout
+ ore = context.scene.ore_render
+ check_status(ore)
+
+ if bpy.passwordCorrect == False:
+ row = layout.row()
+ row.label(text="Email or password missing/incorrect", icon='ERROR')
+ col = layout.column()
+ col.prop(ore, 'username', icon=bpy.statusMessage['username'])
+ col.prop(ore, 'password', icon=bpy.statusMessage['password'])
+ layout.operator('ore.login')
+ else:
+ layout.label(text='Successfully logged in as:', icon='INFO')
+ layout.label(text=bpy.rffi_user)
+ layout.operator('ore.change_user')
+
+class SESSIONS_PT_RenderfarmFi(RenderButtonsPanel, bpy.types.Panel):
+ bl_label = 'My sessions'
+ COMPAT_ENGINES = set(['RENDERFARMFI_RENDER'])
+
+ @classmethod
+ def poll(cls, context):
+ rd = context.scene.render
+ return (rd.use_game_engine==False) and (rd.engine in cls.COMPAT_ENGINES)
+
+ def draw(self, context):
+ ore = context.scene.ore_render
+ if (bpy.passwordCorrect == True and bpy.loginInserted == True):
+ layout = self.layout
+
+ layout.template_list(ore, 'all_sessions', ore, 'selected_session', rows=5)
+ layout.operator('ore.cancel_session')
+ if (bpy.cancelError == True):
+ layout.label("This session cannot be cancelled")
+ errorTime = time.time() - bpy.errorStartTime
+ if (errorTime > 4):
+ bpy.cancelError = False
+ bpy.errorStartTime = -1
+ layout.operator('ore.refresh_session_list')
+ else:
+ layout = self.layout
+ layout.label(text="You must login first")
+
+class RENDER_PT_RenderfarmFi(RenderButtonsPanel, bpy.types.Panel):
+ bl_label = "Settings"
+ COMPAT_ENGINES = set(['RENDERFARMFI_RENDER'])
+
+ @classmethod
+ def poll(cls, context):
+ rd = context.scene.render
+ return (rd.use_game_engine==False) and (rd.engine in cls.COMPAT_ENGINES)
+
+ def draw(self, context):
+ layout = self.layout
+ sce = context.scene
+ ore = sce.ore_render
+ rd = sce.render
+
+ if (bpy.passwordCorrect == False or bpy.loginInserted == False):
+ layout.label(text='You must login first')
+ else:
+ layout.prop(ore, 'title', icon=bpy.statusMessage['title'])
+ layout.label(text="Example: Blue Skies project, scene 8")
+ # layout.prop(ore, 'shortdesc', icon=bpy.statusMessage['shortdesc'])
+ layout.prop(ore, 'longdesc', icon=bpy.statusMessage['longdesc'])
+ layout.label(text="Example: In this shot the main hero is running across a flowery field towards the castle.")
+ layout.prop(ore, 'tags', icon=bpy.statusMessage['tags'])
+ layout.label(text="Example: blue skies hero castle flowers grass particles")
+ layout.prop(ore, 'url')
+ layout.label(text="Example: www.sintel.org")
+
+ layout.label(text="Please verify your settings", icon='MODIFIER')
+ row = layout.row()
+ #row.operator('ore.copy_settings')
+ #row = layout.row()
+
+ layout.label(text="Rendering engine")
+ row = layout.row()
+ if (ore.engine == 'blender'):
+ row.operator('ore.use_blender_render', icon='FILE_TICK')
+ row.operator('ore.use_cycles_render')
+ elif (ore.engine == 'cycles' ):
+ row.operator('ore.use_blender_render')
+ row.operator('ore.use_cycles_render', icon='FILE_TICK')
+ else:
+ row.operator('ore.use_blender_render', icon='FILE_TICK')
+ row.operator('ore.use_cycles_render')
+
+ row = layout.row()
+
+ layout.separator()
+ row = layout.row()
+ row.prop(ore, 'resox')
+ row.prop(ore, 'resoy')
+ row = layout.row()
+ row.prop(ore, 'start')
+ row.prop(ore, 'end')
+ row = layout.row()
+ row.prop(ore, 'fps')
+ row = layout.row()
+ if (ore.engine == 'cycles'):
+ row.prop(ore, 'samples')
+ row.prop(ore, 'subsamples')
+ row = layout.row()
+ row.prop(ore, 'memusage')
+ #row.prop(ore, 'parts')
+ layout.separator()
+ row = layout.row()
+
+ layout.label(text="Licenses", icon='FILE_REFRESH')
+ row = layout.row()
+ row.prop(ore, 'inlicense')
+ row = layout.row()
+ row.prop(ore, 'outlicense')
+
+ check_status(ore)
+ if (len(bpy.errors) > 0):
+ bpy.ready = False
+ else:
+ bpy.ready = True
+
+class UPLOAD_PT_RenderfarmFi(RenderButtonsPanel, bpy.types.Panel):
+ bl_label = "Upload to www.renderfarm.fi"
+ COMPAT_ENGINES = set(['RENDERFARMFI_RENDER'])
+
+ @classmethod
+ def poll(cls, context):
+ rd = context.scene.render
+ return (rd.use_game_engine==False) and (rd.engine in cls.COMPAT_ENGINES)
+
+ def draw(self, context):
+ layout = self.layout
+ sce = context.scene
+ ore = sce.ore_render
+ rd = sce.render
+ if (bpy.passwordCorrect == False or bpy.loginInserted == False):
+ layout.label(text="You must login first")
+ else:
+ if (bpy.ready):
+ layout.label(text="Policies", icon='LAMP')
+ layout.label(text="- The animation must be at least 20 frames long")
+ layout.label(text="- No still renders")
+ layout.label(text="- No Python scripts")
+ layout.label(text="- Memory usage max 4GB")
+ layout.label(text="- If your render takes more than an hour / frame:")
+ layout.label(text=" * No filter type composite nodes (blur, glare etc.)")
+ layout.label(text=" * No SSS")
+ layout.label(text=" * No Motion Blur")
+
+ layout.separator()
+
+ row = layout.row()
+ if (bpy.uploadInProgress == True):
+ layout.label(text="------------------------")
+ layout.label(text="- Attempting upload... -")
+ layout.label(text="------------------------")
+ if (bpy.file_format_warning == True):
+ layout.label(text="Your output format is HDR", icon='ERROR')
+ layout.label(text="Right now we don't support this file format")
+ layout.label(text="File format will be changed to PNG")
+ if (bpy.texturePackError):
+ layout.label(text="There was an error in packing external textures", icon='ERROR')
+ layout.label(text="Make sure that all your textures exist on your computer")
+ layout.label(text="The render will still work, but won't have the missing textures")
+ layout.label(text="You may want to cancel your render above in \"My sessions\"")
+ if (bpy.linkedFileError):
+ layout.label(text="There was an error in appending linked .blend files", icon='ERROR')
+ layout.label(text="Your render might not have all the external content")
+ layout.label(text="You may want to cancel your render above in \"My sessions\"")
+ if (bpy.particleBakeWarning):
+ layout.label(text="You have a particle simulation", icon='ERROR')
+ layout.label(text="All Emitter type particles must be baked")
+ if (bpy.childParticleWarning):
+ layout.label(text="Child particle mode changed!", icon='ERROR')
+ layout.label(text="Renderfarm.fi requires that you use 'Interpolated'")
+ if (bpy.simulationWarning):
+ layout.label(text="There is a simulation!", icon='ERROR')
+ layout.label(text="- Fluid simulations aren't supported")
+ layout.label(text="- Collision simulations must be baked")
+ row = layout.row()
+ row.operator('ore.upload', icon='FILE_TICK')
+ if (bpy.infoError == True):
+ layout.label("You must fill in the scene info first", icon='ERROR')
+ errorTime = time.time() - bpy.errorStartTime
+ if (errorTime > 4):
+ bpy.infoError = False
+ bpy.errorStartTime = -1
+ layout.label(text="Warning:", icon='LAMP')
+ layout.label(text="Blender may seem frozen during the upload!")
+ row.operator('ore.reset', icon='FILE_REFRESH')
+ else:
+ layout.label(text="Fill the scene information first")
diff --git a/render_renderfarmfi/prepare.py b/render_renderfarmfi/prepare.py
new file mode 100644
index 00000000..08b61b98
--- /dev/null
+++ b/render_renderfarmfi/prepare.py
@@ -0,0 +1,190 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+import os
+
+def hasSSSMaterial():
+ for m in bpy.data.materials:
+ if m.subsurface_scattering.use:
+ return True
+ return False
+
+def tuneParticles():
+ for p in bpy.data.particles:
+ if (p.type == 'EMITTER'):
+ bpy.particleBakeWarning = True
+ if (p.type == 'HAIR'):
+ if (p.child_type == 'SIMPLE'):
+ p.child_type = 'INTERPOLATED'
+ bpy.childParticleWarning = True
+
+def hasParticleSystem():
+ if (len(bpy.data.particles) > 0):
+ print("Found particle system")
+ return True
+ return False
+
+def hasSimulation(t):
+ for o in bpy.data.objects:
+ for m in o.modifiers:
+ if isinstance(m, t):
+ print("Found simulation: " + str(t))
+ return True
+ return False
+
+def hasFluidSimulation():
+ return hasSimulation(bpy.types.FluidSimulationModifier)
+
+def hasSmokeSimulation():
+ return hasSimulation(bpy.types.SmokeModifier)
+
+def hasClothSimulation():
+ return hasSimulation(bpy.types.ClothModifier)
+
+def hasCollisionSimulation():
+ return hasSimulation(bpy.types.CollisionModifier)
+
+def hasSoftbodySimulation():
+ return hasSimulation(bpy.types.SoftBodyModifier)
+
+def hasUnsupportedSimulation():
+ return hasSoftbodySimulation() or hasCollisionSimulation() or hasClothSimulation() or hasSmokeSimulation() or hasFluidSimulation()
+
+def isFilterNode(node):
+ t = type(node)
+ return t==bpy.types.CompositorNodeBlur or t==bpy.types.CompositorNodeDBlur
+
+def changeSettings():
+
+ sce = bpy.context.scene
+ rd = sce.render
+ ore = sce.ore_render
+
+ # Necessary settings for BURP
+ rd.resolution_x = ore.resox
+ rd.resolution_y = ore.resoy
+ sce.frame_start = ore.start
+ sce.frame_end = ore.end
+ rd.fps = ore.fps
+
+ bpy.file_format_warning = False
+ bpy.simulationWarning = False
+ bpy.texturePackError = False
+ bpy.particleBakeWarning = False
+ bpy.childParticleWarning = False
+
+ if (rd.image_settings.file_format == 'HDR'):
+ rd.image_settings.file_format = 'PNG'
+ bpy.file_format_warning = True
+
+ # Convert between Blender's image format and BURP's formats
+ if (rd.image_settings.file_format == 'PNG'):
+ ore.file_format = 'PNG_FORMAT'
+ elif (rd.image_settings.file_format == 'OPEN_EXR'):
+ ore.file_format = 'EXR_FORMAT'
+ elif (rd.image_settings.file_format == 'OPEN_EXR_MULTILAYER'):
+ ore.file_format = 'EXR_MULTILAYER_FORMAT'
+ elif (rd.image_settings.file_format == 'HDR'):
+ ore.file_format = 'PNG_FORMAT'
+ else:
+ ore.file_format = 'PNG_FORMAT'
+
+ if (ore.engine == 'cycles'):
+ bpy.context.scene.cycles.samples = ore.samples
+
+ if (ore.subsamples <= 0):
+ ore.subsamples = 1
+
+ if (ore.samples / ore.subsamples < 100.0):
+ ore.subsamples = float(ore.samples) / 100.0
+
+ # Multipart support doesn' work if SSS is used
+ if ((rd.use_sss == True and hasSSSMaterial()) and ore.parts > 1):
+ ore.parts = 1;
+
+ if (hasParticleSystem()):
+ tuneParticles()
+ else:
+ bpy.particleBakeWarning = False
+ bpy.childParticleWarning = False
+
+ if (hasUnsupportedSimulation()):
+ simulationWarning = True
+ else:
+ bpy.simulationWarning = False
+
+def _prepare_scene():
+ sce = bpy.context.scene
+ rd = sce.render
+ ore = sce.ore_render
+
+ changeSettings()
+
+ print("Packing external textures...")
+ try:
+ bpy.ops.file.pack_all()
+ bpy.texturePackError = False
+ except Exception as e:
+ bpy.texturePackError = True
+ print(e)
+
+ linkedData = bpy.utils.blend_paths()
+ if (len(linkedData) > 0):
+ print("Appending linked .blend files...")
+ try:
+ bpy.ops.object.make_local(type='ALL')
+ bpy.linkedFileError = False
+ except Exception as e:
+ bpy.linkedFileError = True
+ print(e)
+ else:
+ print("No external .blends used, skipping...")
+
+ # Save with a different name
+ print("Saving into a new file...")
+ bpy.originalFileName = bpy.data.filepath
+ print("Original path is " + bpy.originalFileName)
+ try:
+ # If the filename is empty, we'll make one from the path of the user's resource folder
+ if (len(bpy.originalFileName) == 0):
+ print("No existing file path found, saving to autosave directory")
+ savePath = bpy.utils.user_resource("AUTOSAVE")
+ try:
+ os.mkdir(savePath)
+ except Exception as ex:
+ print(ex)
+ try:
+ savePath = savePath + "_renderfarm"
+ except Exception as ex:
+ print(ex)
+ try:
+ bpy.ops.wm.save_mainfile(filepath=savePath)
+ except Exception as ex:
+ print(ex)
+ else:
+ print("Saving to current .blend directory")
+ savePath = bpy.originalFileName
+ savePath = savePath + "_renderfarm.blend"
+ bpy.ops.wm.save_mainfile(filepath=savePath)
+ except Exception as e:
+ print(e)
+
+ print(".blend prepared")
+
+
diff --git a/render_renderfarmfi/rpc.py b/render_renderfarmfi/rpc.py
new file mode 100644
index 00000000..756f1ec3
--- /dev/null
+++ b/render_renderfarmfi/rpc.py
@@ -0,0 +1,186 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import xmlrpc.client
+import imp
+import traceback
+import sys
+import time
+
+import bpy
+
+from .exceptions import LoginFailedException, SessionCancelFailedException, \
+ GetSessionsFailedException
+from .utils import _read_credentials, _xmlsessions_to_oresessions, \
+ update_complete_session_list
+
+def _is_dev():
+ is_dev = False
+ pwfile = bpy.utils.user_resource('CONFIG', 'rffi', True)
+ pwmod = None
+ try:
+ pwmod = imp.find_module('rffi_dev',[pwfile])
+ try:
+ user_creds = imp.load_module('rffi_dev', pwmod[0], pwmod[1], pwmod[2])
+ if 'dev' in dir(user_creds) and user_creds.dev:
+ is_dev = True
+ except ImportError as e:
+ is_dev = False
+ finally:
+ if pwmod and pwmod[0]: pwmod[0].close()
+ except ImportError as e:
+ is_dev = False
+ finally:
+ if pwmod and pwmod[0]: pwmod[0].close()
+
+ return is_dev
+
+def _be_verbose():
+ be_verbose = False
+ pwfile = bpy.utils.user_resource('CONFIG', 'rffi', True)
+ pwmod = None
+ try:
+ pwmod = imp.find_module('rffi_dev',[pwfile])
+ try:
+ user_creds = imp.load_module('rffi_dev', pwmod[0], pwmod[1], pwmod[2])
+ if 'verbose' in dir(user_creds) and user_creds.verbose:
+ be_verbose = True
+ except ImportError as e:
+ be_verbose = False
+ finally:
+ if pwmod and pwmod[0]: pwmod[0].close()
+ except ImportError as e:
+ be_verbose = False
+ finally:
+ if pwmod and pwmod[0]: pwmod[0].close()
+
+ return be_verbose
+
+RFFI_DEV = _is_dev()
+RFFI_VERBOSE = _be_verbose()
+
+if RFFI_DEV:
+ print("DEVELOPER MODE")
+ rffi_xmlrpc_secure = r'http://renderfarm.local/burp/xmlrpc'
+ rffi_xmlrpc = r'http://renderfarm.local/burp/xmlrpc'
+ rffi_xmlrpc_upload = 'renderfarm.local'
+else:
+ rffi_xmlrpc_secure = r'https://xmlrpc.renderfarm.fi/burp/xmlrpc'
+ rffi_xmlrpc = r'http://xmlrpc.renderfarm.fi/burp/xmlrpc'
+ rffi_xmlrpc_upload = 'xmlrpc.renderfarm.fi'
+
+
+def _get_proxy():
+ proxy = xmlrpc.client.ServerProxy(rffi_xmlrpc, verbose=RFFI_VERBOSE)
+ return proxy
+
+def _get_secure_proxy():
+ proxy = xmlrpc.client.ServerProxy(rffi_xmlrpc_secure, verbose=RFFI_VERBOSE)
+ return proxy
+
+def _do_refresh(op, rethrow=False, print_errors=True):
+ sce = bpy.context.scene
+ ore = sce.ore_render
+
+ if _read_credentials():
+ try:
+ bpy.ore_sessions = []
+ bpy.ore_pending_sessions = []
+ bpy.ore_active_sessions = []
+ bpy.ore_completed_sessions = []
+ bpy.ore_cancelled_sessions = []
+ update_complete_session_list(ore)
+
+ res = rffi.login(op, True, print_errors)
+ userid = res['userID']
+
+ sessions = rffi.get_sessions(userid, 'accept', 0, 100, 'full')
+ bpy.ore_sessions = _xmlsessions_to_oresessions(sessions, stage='Pending')
+ bpy.ore_pending_sessions = bpy.ore_sessions
+
+ sessions = rffi.get_sessions(userid, 'completed', 0, 100, 'full')
+ bpy.ore_sessions = _xmlsessions_to_oresessions(sessions, stage='Completed')
+ bpy.ore_completed_sessions = bpy.ore_sessions
+
+ sessions = rffi.get_sessions(userid, 'cancelled', 0, 100, 'full')
+ bpy.ore_sessions = _xmlsessions_to_oresessions(sessions, stage='Cancelled')
+ bpy.ore_cancelled_sessions = bpy.ore_sessions
+
+ sessions = rffi.get_sessions(userid, 'render', 0, 100, 'full')
+ bpy.ore_sessions = _xmlsessions_to_oresessions(sessions, stage='Rendering')
+ bpy.ore_active_sessions = bpy.ore_sessions
+
+ update_complete_session_list(ore)
+
+ return 0
+ except LoginFailedException as lfe:
+ print("_do_refresh login failed", lfe)
+ if rethrow:
+ raise lfe
+ return 1
+ else:
+ return 1
+
+
+class RffiRpc(object):
+ def __init__(self):
+ self.proxy = _get_proxy()
+ self.sproxy = _get_secure_proxy()
+ self.res = None
+
+ def login(self, op, rethrow=False, print_errors=True):
+ self.res = None
+ try:
+ self.res = self.sproxy.auth.getSessionKey(bpy.rffi_user, bpy.rffi_hash)
+ except xmlrpc.client.Error as v:
+ if op: op.report({'WARNING'}, "Error at login : " + str(type(v)) + " -> " + str(v.faultCode) + ": " + v.faultString)
+ if print_errors: print("Error at login: ",v)
+ if rethrow:
+ raise LoginFailedException(v.faultString)
+ return None
+ except Exception as v:
+ if op: op.report({'WARNING'}, "Non XMLRPC Error at login: " + str(v))
+ if print_errors: print(v)
+ if rethrow:
+ raise LoginFailedException(str(v))
+ return None
+ return self.res
+
+ def get_sessions(self, user, queue, start, end, level):
+ try:
+ sessions = self.proxy.session.getSessions(user, queue, start, end, level)
+ except xmlrpc.client.Error as v:
+ raise GetSessionsFailedException(str(v))
+ return sessions
+
+ def cancel_session(self, op, session):
+ res = self.login(op)
+ if res:
+ try:
+ key = res['key']
+ userid = res['userId']
+ res = self.proxy.session.cancelSession(userid, key, session.id)
+ _do_refresh(op, True)
+ op.report({'INFO'}, 'Session ' + session.title + ' with id ' + str(session.id) + ' cancelled')
+ except xmlrpc.client.Error as v:
+ op.report({'ERROR'}, 'Could not cancel session ' + session.title + ' with id ' + str(session.id))
+ bpy.cancelError = True
+ bpy.errorStartTime = time.time()
+ raise SessionCancelFailedException(str(v))
+
+rffi = RffiRpc()
diff --git a/render_renderfarmfi/upload.py b/render_renderfarmfi/upload.py
new file mode 100644
index 00000000..077da943
--- /dev/null
+++ b/render_renderfarmfi/upload.py
@@ -0,0 +1,192 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import xmlrpc.client
+import http.client
+import hashlib
+from os.path import isabs, isfile, join, exists
+
+import bpy
+
+from .utils import _read_credentials
+from .rpc import rffi, _do_refresh, rffi_xmlrpc_upload, rffi_xmlrpc, RFFI_VERBOSE
+
+def _random_string(length):
+ import string
+ import random
+ return ''.join(random.choice(string.ascii_letters) for ii in range(length + 1))
+
+def _encode_multipart_data(data, files):
+ boundary = _random_string(30)
+
+ def get_content_type(filename):
+ return 'application/octet-stream' # default this
+
+ def encode_field(field_name):
+ return ('--' + boundary,
+ 'Content-Disposition: form-data; name="%s"' % field_name,
+ '', str(data[field_name]))
+
+ def encode_file(field_name):
+ filename = files [field_name]
+ fcontent = None
+ print('encoding', field_name)
+ try:
+ fcontent = str(open(filename, 'rb').read(), encoding='iso-8859-1')
+ except Exception as e:
+ print('Trouble in paradise', e)
+ return ('--' + boundary,
+ 'Content-Disposition: form-data; name="%s"; filename="%s"' % (field_name, filename),
+ 'Content-Type: %s' % get_content_type(filename),
+ '', fcontent)
+
+ lines = []
+ for name in data:
+ lines.extend(encode_field(name))
+ for name in files:
+ lines.extend(encode_file(name))
+ lines.extend(('--%s--' % boundary, ''))
+ print("joining lines into body")
+ body = '\r\n'.join(lines)
+
+ headers = {'content-type': 'multipart/form-data; boundary=' + boundary,
+ 'content-length': str(len(body))}
+
+ print("headers and body ready")
+
+ return body, headers
+
+def _send_post(data, files):
+ print("Forming connection for post")
+ connection = http.client.HTTPConnection(rffi_xmlrpc_upload)
+ print("Requesting")
+ connection.request('POST', '/burp/storage', *_encode_multipart_data(data, files)) # was /file
+ print("Getting response")
+ response = connection.getresponse()
+ print("Reading response")
+ res = response.read()
+ return res
+
+def _md5_for_file(filepath):
+ md5hash = hashlib.md5()
+ blocksize = 0x10000
+ f = open(filepath, "rb")
+ while True:
+ data = f.read(blocksize)
+ if not data:
+ break
+ md5hash.update(data)
+ return md5hash.hexdigest()
+
+def _upload_file(key, userid, sessionid, path):
+ print("Asserting absolute path")
+ assert isabs(path)
+ print("Asserting path is a file")
+ assert isfile(path)
+ data = {
+ 'userId': str(userid),
+ 'sessionKey': key,
+ 'sessionId': sessionid,
+ 'md5sum': _md5_for_file(path)
+ }
+ files = {
+ 'blenderfile': path
+ }
+ r = _send_post(data, files)
+
+ return r
+
+def _run_upload(key, userid, sessionid, path):
+ print("Starting upload");
+ r = _upload_file(key, userid, sessionid, path)
+ print("Upload finished")
+ o = xmlrpc.client.loads(r)
+ print("Loaded xmlrpc response")
+ return o[0][0]
+
+def _ore_upload(op, context):
+ sce = context.scene
+ ore = sce.ore_render
+
+ if not bpy.ready:
+ op.report({'ERROR'}, 'Your user or scene information is not complete')
+ bpy.infoError = True
+ bpy.errorStartTime = time.time()
+ bpy.context.scene.render.engine = 'RENDERFARMFI_RENDER'
+ return {'CANCELLED'}
+ try:
+ _read_credentials()
+ res = rffi.login(op, True)
+ key = res['key']
+ userid = res['userId']
+ print("Creating server proxy")
+ proxy = xmlrpc.client.ServerProxy(rffi_xmlrpc, verbose=RFFI_VERBOSE)
+ proxy._ServerProxy__transport.user_agent = 'Renderfarm.fi Uploader/%s' % (bpy.CURRENT_VERSION)
+ print("Creating a new session")
+ res = proxy.session.createSession(userid, key) # This may use an existing, non-rendered session. Prevents spamming in case the upload fails for some reason
+ sessionid = res['sessionId']
+ key = res['key']
+ print("Session id is " + str(sessionid))
+ res = _run_upload(key, userid, sessionid, bpy.data.filepath)
+ print("Getting fileid from xmlrpc response data")
+ fileid = int(res['fileId'])
+ print("Sending session details for session " + str(sessionid) + " with fileid " + str(fileid))
+ res = proxy.session.setTitle(userid, res['key'], sessionid, ore.title)
+ res = proxy.session.setLongDescription(userid, res['key'], sessionid, ore.longdesc)
+ res = proxy.session.setShortDescription(userid, res['key'], sessionid, ore.shortdesc)
+ if len(ore.url)>0:
+ res = proxy.session.setExternalURLs(userid, res['key'], sessionid, ore.url)
+ res = proxy.session.setStartFrame(userid, res['key'], sessionid, ore.start)
+ res = proxy.session.setEndFrame(userid, res['key'], sessionid, ore.end)
+ res = proxy.session.setSplit(userid, res['key'], sessionid, ore.parts)
+ res = proxy.session.setMemoryLimit(userid, res['key'], sessionid, ore.memusage)
+ res = proxy.session.setXSize(userid, res['key'], sessionid, ore.resox)
+ res = proxy.session.setYSize(userid, res['key'], sessionid, ore.resoy)
+ res = proxy.session.setFrameRate(userid, res['key'], sessionid, ore.fps)
+ res = proxy.session.setFrameFormat(userid, res['key'], sessionid, ore.file_format)
+ res = proxy.session.setRenderer(userid, res['key'], sessionid, ore.engine)
+ res = proxy.session.setSamples(userid, res['key'], sessionid, ore.samples)
+ res = proxy.session.setSubSamples(userid, res['key'], sessionid, ore.subsamples)
+ if (ore.engine == 'cycles'):
+ res = proxy.session.setReplication(userid, res['key'], sessionid, 1)
+ if ore.subsamples > 1:
+ res = proxy.session.setStitcher(userid, res['key'], sessionid, 'AVERAGE')
+ else:
+ res = proxy.session.setReplication(userid, res['key'], sessionid, 3)
+ res = proxy.session.setOutputLicense(userid, res['key'], sessionid, int(ore.outlicense))
+ res = proxy.session.setInputLicense(userid, res['key'], sessionid, int(ore.inlicense))
+ print("Setting primary input file")
+ res = proxy.session.setPrimaryInputFile(userid, res['key'], sessionid, fileid)
+ print("Submitting session")
+ res = proxy.session.submit(userid, res['key'], sessionid)
+ print("Session submitted")
+ op.report({'INFO'}, 'Submission sent to Renderfarm.fi')
+ except xmlrpc.client.Error as v:
+ bpy.context.scene.render.engine = 'RENDERFARMFI_RENDER'
+ print('ERROR:', v)
+ op.report({'ERROR'}, 'An XMLRPC error occurred while sending submission to Renderfarm.fi')
+ except Exception as e:
+ bpy.context.scene.render.engine = 'RENDERFARMFI_RENDER'
+ print('Unhandled error:', e)
+ op.report({'ERROR'}, 'A generic error occurred while sending submission to Renderfarm.fi')
+
+ bpy.context.scene.render.engine = 'RENDERFARMFI_RENDER'
+ _do_refresh(op)
+ return {'FINISHED'}
+
+
diff --git a/render_renderfarmfi/utils.py b/render_renderfarmfi/utils.py
new file mode 100644
index 00000000..14d2483d
--- /dev/null
+++ b/render_renderfarmfi/utils.py
@@ -0,0 +1,144 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import imp
+import xmlrpc.client
+import math
+import sys
+import traceback
+
+from os.path import join
+
+import bpy
+
+from .ore_session import OreSession
+from .exceptions import LoginFailedException
+
+def _write_credentials(hash, user):
+ with open(join(bpy.utils.user_resource('CONFIG', 'rffi', True), 'rffi_credentials.py'), 'w') as pwfile:
+ pwfile.write('hash=\''+hash+'\'\n')
+ pwfile.write('user=\''+user+'\'')
+
+
+def _read_credentials():
+ bpy.rffi_creds_found = False
+ bpy.rffi_user = ''
+ bpy.rffi_hash = ''
+
+ pwfile = bpy.utils.user_resource('CONFIG', 'rffi', True)
+ try:
+ pwmod = imp.find_module('rffi_credentials',[pwfile])
+ except ImportError as e:
+ _write_credentials('', '')
+ pwmod = imp.find_module('rffi_credentials',[pwfile])
+ try:
+ user_creds = imp.load_module('rffi_credentials', pwmod[0], pwmod[1], pwmod[2])
+ bpy.rffi_user = user_creds.user
+ bpy.rffi_hash = user_creds.hash
+ bpy.rffi_creds_found = True
+ except ImportError as e:
+ # doesn't exist yet, write template
+ _write_credentials('', '')
+ pwfile = bpy.utils.user_resource('CONFIG', 'rffi', True)
+ pwmod = imp.find_module('rffi_credentials',[pwfile])
+ try:
+ user_creds = imp.load_module('rffi_credentials', pwmod[0], pwmod[1], pwmod[2])
+ bpy.rffi_user = user_creds.user
+ bpy.rffi_hash = user_creds.hash
+ bpy.rffi_creds_found = True
+ except Exception as e2:
+ print("Couldn't write rffi_credentials.py", e2)
+ finally:
+ if pwmod and pwmod[0]: pwmod[0].close()
+
+ return bpy.rffi_creds_found
+
+
+def _xmlsessions_to_oresessions(sessions, stage=None):
+ output = []
+ for session in sessions:
+ s = session['title']
+ if stage:
+ s = s + ' (' + stage + ')'
+ sinfo = OreSession(session['sessionId'], s)
+ if stage in {'Completed', 'Active'}:
+ sinfo.frames = session['framesRendered']
+ sinfo.startframe = session['startFrame']
+ sinfo.endframe = session['endFrame']
+ output.append(sinfo)
+ return output
+
+
+def update_session_list(session_list, ore):
+ while(len(session_list) > 0):
+ session_list.remove(0)
+
+ for s in bpy.ore_active_session_queue:
+ session_list.add()
+ session = session_list[-1]
+ session.name = s.title + ' [' + str(s.percentageComplete()) + '% complete]'
+
+def update_complete_session_list(ore):
+ all_sessions = []
+
+ bpy.ore_active_session_queue = bpy.ore_cancelled_sessions
+ update_session_list(ore.rejected_sessions, ore)
+ bpy.ore_active_session_queue = bpy.ore_active_sessions
+ update_session_list(ore.active_sessions, ore)
+ bpy.ore_active_session_queue = bpy.ore_pending_sessions
+ update_session_list(ore.pending_sessions, ore)
+ bpy.ore_active_session_queue = bpy.ore_completed_sessions
+ update_session_list(ore.completed_sessions, ore)
+
+ bpy.ore_complete_session_queue = []
+ bpy.ore_complete_session_queue.extend(bpy.ore_pending_sessions)
+ bpy.ore_complete_session_queue.extend(bpy.ore_active_sessions)
+ bpy.ore_complete_session_queue.extend(bpy.ore_completed_sessions)
+ bpy.ore_complete_session_queue.extend(bpy.ore_cancelled_sessions)
+
+ bpy.ore_active_session_queue = bpy.ore_complete_session_queue
+ update_session_list(ore.all_sessions, ore)
+
+def check_status(ore):
+ bpy.errors = []
+
+ if bpy.rffi_creds_found == False and bpy.rffi_hash == '':
+ bpy.errors.append('missing_creds')
+
+ if '' in {ore.title, ore.longdesc, ore.shortdesc}:
+ bpy.errors.append('missing_desc')
+ bpy.infoError = True
+
+ set_status('username', bpy.rffi_hash=='' and ore.username=='')
+ set_status('password', bpy.rffi_hash=='' and ore.password=='')
+
+ set_status('title', ore.title=='')
+ set_status('longdesc', ore.longdesc=='')
+ set_status('shortdesc', ore.shortdesc=='')
+
+
+def set_status(property, status):
+ if status:
+ bpy.statusMessage[property] = 'ERROR'
+ else:
+ bpy.statusMessage[property] = 'TRIA_RIGHT'
+
+def show_status(layoutform, property, message):
+ if bpy.statusMessage[property] == 'ERROR':
+ layoutform.label(text='', icon='ERROR')
+