diff options
author | Nathan Letwory <nathan@letworyinteractive.com> | 2012-07-13 10:33:01 +0400 |
---|---|---|
committer | Nathan Letwory <nathan@letworyinteractive.com> | 2012-07-13 10:33:01 +0400 |
commit | 62f10cc24dab453fd9d6a9e09f59adbdb42156ac (patch) | |
tree | 95dd92c307c8b074b607820f84528db0d399976e /render_renderfarmfi | |
parent | 65e61d8730aa64dbd8a48471c68522e6c9bc385b (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__.py | 219 | ||||
-rw-r--r-- | render_renderfarmfi/exceptions.py | 41 | ||||
-rw-r--r-- | render_renderfarmfi/operators.py | 338 | ||||
-rw-r--r-- | render_renderfarmfi/ore_session.py | 40 | ||||
-rw-r--r-- | render_renderfarmfi/panels.py | 265 | ||||
-rw-r--r-- | render_renderfarmfi/prepare.py | 190 | ||||
-rw-r--r-- | render_renderfarmfi/rpc.py | 186 | ||||
-rw-r--r-- | render_renderfarmfi/upload.py | 192 | ||||
-rw-r--r-- | render_renderfarmfi/utils.py | 144 |
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') + |