# ##### 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 , Jesse Kaukonen ", "version": (8,), "blender": (2, 5, 7), "api": 36487, "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-2011 Laurea University of Applied Sciences Authors: Nathan Letwory, Jesse Kaukonen """ import bpy import hashlib import http.client import xmlrpc.client import math from os.path import isabs, isfile from bpy.props import PointerProperty, StringProperty, BoolProperty, EnumProperty, IntProperty, CollectionProperty 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.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', 'longdesc': 'TRIA_RIGHT', 'username': 'TRIA_RIGHT', 'password': 'TRIA_RIGHT' } bpy.errors = [] bpy.ore_sessions = [] bpy.queue_selected = -1 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='') hash = StringProperty(name='Hash', description='hash calculated out of credentials', maxlen=33, default='') shortdesc = StringProperty(name='Short description', description='A short description of the scene (100 characters)', maxlen=101, default='') longdesc = StringProperty(name='Long description', description='A more elaborate 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='') 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=256, 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='source license', description='license speficied for the source files', default='1') outlicense = EnumProperty(items=licenses, name='output license', description='license speficied for the output files', default='1') sessions = CollectionProperty(type=ORESession, name='Sessions', description='Sessions on Renderfarm.fi') # session struct # 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 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 SUMMARY_PT_RenderfarmFi(RenderButtonsPanel, bpy.types.Panel): # Prints a summary to the panel before uploading. If scene settings differ from ore settings, then display a warning icon bl_label = 'Summary' bl_options = {'DEFAULT_CLOSED'} 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): sce = context.scene rd = context.scene.render ore = context.scene.ore_render layout = self.layout problems = False # Check if correct resolution is set if rd.resolution_x != ore.resox: layout.label(text='Resolution X: ' + str(ore.resox), icon='ERROR') problems = True else: layout.label(text='Resolution X: ' + str(ore.resox), icon='FILE_TICK') if rd.resolution_y != ore.resoy: layout.label(text='Resolution Y: ' + str(ore.resoy), icon='ERROR') problems = True else: layout.label(text='Resolution Y: ' + str(ore.resoy), icon='FILE_TICK') # Check if correct number of frames is specified if (sce.frame_start != ore.start) and not ( (sce.frame_start == ore.start and sce.frame_end == ore.end) and sce.frame_start == sce.frame_end): layout.label(text='Start frame: ' + str(ore.start), icon='ERROR') layout.label(text='.blend Start frame is different to the one specified in the uploader script. Please verify!') problems = True else: layout.label(text='Start frame: ' + str(ore.start), icon='FILE_TICK') if (sce.frame_end != ore.end) and not ( (sce.frame_start == ore.start and sce.frame_end == ore.end) and sce.frame_start == sce.frame_end): layout.label(text='End frame: ' + str(ore.end), icon='ERROR') layout.label(text='.blend End frame is different to the one specified in the uploader script. Please verify!') problems = True else: layout.label(text='End frame: ' + str(ore.end), icon='FILE_TICK') # Check if more than 1 frame is specified if (sce.frame_start == ore.start and sce.frame_end == ore.end) and (sce.frame_start == sce.frame_end): layout.label(text='Only one frame specified to be rendered!') layout.label(text='This is highly ineffective when using distributed rendering') problems = True if rd.resolution_percentage != 100: layout.label(text='Resolution percentage: ' + str(rd.resolution_percentage), icon='ERROR') problems = True else: layout.label(text='Resolution percentage: ' + str(rd.resolution_percentage), icon='FILE_TICK') if rd.file_format != 'PNG': layout.label(text='Output format: ' + rd.file_format, icon='ERROR') layout.label(text='Output format must be set to PNG') problems = True else: layout.label(text='Output format: ' + rd.file_format, icon='FILE_TICK') if ore.parts > 1 and rd.use_sss == True: layout.label(text='Subsurface Scattering: ' + str(rd.use_sss), icon='ERROR') layout.label(text='If you want to use SSS, parts must be set to 1') problems = True else: layout.label(text='Subsurface Scattering: ' + str(rd.use_sss), icon='FILE_TICK') if rd.use_compositing == False: layout.label(text='Composite nodes: ' + str(rd.use_compositing), icon='ERROR') layout.label(text='Composite nodes are disabled.') layout.label(text='The script automatically disables them if: ') layout.label(text='- Filter type nodes are used and parts are more than 1') layout.label(text='- There is an output node') problems = True else: layout.label(text='Composite nodes: ' + str(rd.use_compositing), icon='FILE_TICK') if rd.use_save_buffers: layout.label(text='Save buffers: ' + str(rd.use_save_buffers), icon='ERROR') layout.label(text='Save buffers must be disabled') layout.label(text='Can only disabled if Full Sample is turned off') problems = True else: layout.label(text='Save buffers: ' + str(rd.use_save_buffers), icon='FILE_TICK') if rd.use_border: layout.label(text='Border render: ' + str(rd.use_border), icon='ERROR') layout.label(text='Border render must be disabled') else: layout.label(text='Border render: ' + str(rd.use_border), icon='FILE_TICK') if rd.threads_mode != 'FIXED' or rd.threads > 1: layout.label(text='Threads: ' + rd.threads_mode + ' ' + str(rd.threads), icon='ERROR') layout.label(text='Threads must be set to Fixed, 1') problems = True else: layout.label(text='Threads: ' + rd.threads_mode + ' ' + str(rd.threads), icon='FILE_TICK') if ore.hasUnsupportedSimulation == True: layout.label(text='There is an unsupported simulation', icon='ERROR') layout.label(text='Fluid/smoke/cloth/collision/softbody simulations are not supported') problems = True else: layout.label(text='No unsupported simulations found', icon='FILE_TICK') if ore.prepared == False: layout.label(text='The script reports "not ready".', icon='ERROR') layout.label(text='Please review the settings above') layout.label(text='If everything is in order, click Check Scene again') layout.label(text='The script automatically changes settings, so make sure they are correct') else: layout.label(text='The script reports "All settings ok!"', icon='FILE_TICK') layout.label(text='Please render one frame using Blender Render first') row = layout.row() row.operator('ore.test_render') layout.label(text='If you are sure that the render works, click Render on Renderfarm.fi') class RENDERFARM_MT_Session(bpy.types.Menu): bl_label = "Show Session" def draw(self, context): layout = self.layout layout.operator('ore.completed_sessions') layout.operator('ore.accept_sessions') layout.operator('ore.active_sessions') layout.separator() layout.operator('ore.cancelled_sessions') 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): layout = self.layout # XXX layout.operator('ore.check_update') ore = context.scene.ore_render checkStatus(ore) if ore.hash=='': col = layout.column() if ore.hash=='': col.prop(ore, 'username', icon=bpy.statusMessage['username']) col.prop(ore, 'password', icon=bpy.statusMessage['password']) layout.operator('ore.login') else: layout.label(text='E-mail and password entered.', icon='INFO') layout.operator('ore.change_user') class CHECK_PT_RenderfarmFi(RenderButtonsPanel, bpy.types.Panel): bl_label = 'Check for updates' 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 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 SESSIONS_PT_RenderfarmFi(RenderButtonsPanel, bpy.types.Panel): bl_label = '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): layout = self.layout ore = context.scene.ore_render layout.menu("RENDERFARM_MT_Session") if bpy.queue_selected == 1: layout.label(text='Completed Sessions') elif bpy.queue_selected == 2: layout.label(text='Rendering Sessions') elif bpy.queue_selected == 3: layout.label(text='Pending Sessions') elif bpy.queue_selected == 4: layout.label(text='Cancelled and Rejected Sessions') layout.template_list(ore, 'sessions', ore, 'selected_session', rows=2) if bpy.queue_selected == 3: layout.operator('ore.cancel_session') class CONDITIONS_PT_RenderfarmFi(RenderButtonsPanel, bpy.types.Panel): bl_label = "IMPORTANT: Rendering on 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 layout.label(text='- The render must take more than 50 seconds / frame') layout.label(text='- The animation must be at least 20 frames long') layout.label(text='- No still renders') layout.label(text='- All external data must be included:') layout.label(text=' * Linked files: L in object mode') layout.label(text=' * Textures: File menu -> External Data') layout.label(text='- No Python scripts') layout.label(text='- Memory usage max 3GB') 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') class RENDER_PT_RenderfarmFi(RenderButtonsPanel, bpy.types.Panel): bl_label = "Scene 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 if ore.prepared and ore.hash!='': layout.prop(ore, 'memusage') layout.separator() row = layout.row() layout.separator() row = layout.row() row.prop(ore, 'inlicense') row.prop(ore, 'outlicense') layout.separator() row = layout.row() row.operator('ore.upload') layout.label(text='Blender may seem frozen during the upload!') row.operator('ore.reset', icon='FILE_REFRESH') else: layout.prop(ore, 'title', icon=bpy.statusMessage['title']) layout.prop(ore, 'shortdesc', icon=bpy.statusMessage['shortdesc']) layout.prop(ore, 'longdesc', icon=bpy.statusMessage['longdesc']) layout.prop(ore, 'url') layout.separator() layout.operator('ore.use_scene_settings', icon='HAND') row = layout.row() row.prop(ore, 'resox') row.prop(ore, 'resoy') layout.separator() layout.prop(ore, 'parts') row = layout.row() row.prop(ore, 'start') row.prop(ore, 'end') layout.prop(ore, 'fps') layout.separator() layout.operator('ore.prepare', icon='INFO') 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] return ('--' + boundary, 'Content-Disposition: form-data; name="%s"; filename="%s"' % (field_name, filename), 'Content-Type: %s' % get_content_type(filename), '', str(open(filename, 'rb').read(), encoding='iso-8859-1')) lines = [] for name in data: lines.extend(encode_field(name)) for name in files: lines.extend(encode_file(name)) lines.extend(('--%s--' % boundary, '')) body = '\r\n'.join(lines) headers = {'content-type': 'multipart/form-data; boundary=' + boundary, 'content-length': str(len(body))} return body, headers def send_post(url, data, files): connection = http.client.HTTPConnection('xmlrpc.renderfarm.fi') connection.request('POST', '/file', *encode_multipart_data(data, files)) response = connection.getresponse() 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, server, path): assert isabs(path) assert isfile(path) data = { 'userId': str(userid), 'sessionKey': key, 'sessionId': sessionid, 'md5sum': md5_for_file(path) } files = { 'blenderfile': path } r = send_post(server, data, files) #print 'Uploaded %r' % (path) return r def run_upload(key, userid, sessionid, path): #print('Upload', path) r = upload_file(key, userid, sessionid, r'http://xmlrpc.renderfarm.fi/file', path) o = xmlrpc.client.loads(r) #print('Done!') return o[0][0] def ore_upload(op, context): sce = context.scene ore = sce.ore_render if not ore.prepared: op.report(set(['ERROR']), 'Your user or scene information is not complete') return {'CANCELLED'} try: authproxy = xmlrpc.client.ServerProxy(r'https://xmlrpc.renderfarm.fi/auth') res = authproxy.auth.getSessionKey(ore.username, ore.hash) key = res['key'] userid = res['userId'] proxy = xmlrpc.client.ServerProxy(r'http://xmlrpc.renderfarm.fi/session') proxy._ServerProxy__transport.user_agent = 'Renderfarm.fi Uploader/%s' % (bpy.CURRENT_VERSION) res = proxy.session.createSession(userid, key) sessionid = res['sessionId'] key = res['key'] res = run_upload(key, userid, sessionid, bpy.data.filepath) fileid = int(res['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.setOutputLicense(userid, res['key'], sessionid, int(ore.outlicense)) res = proxy.session.setInputLicense(userid, res['key'], sessionid, int(ore.inlicense)) res = proxy.session.setPrimaryInputFile(userid, res['key'], sessionid, fileid) res = proxy.session.submit(userid, res['key'], sessionid) op.report(set(['INFO']), 'Submission sent to Renderfarm.fi') except xmlrpc.client.Error as v: print('ERROR:', v) op.report(set(['ERROR']), 'An error occurred while sending submission to Renderfarm.fi') except Exception as e: print('Unhandled error:', e) op.report(set(['ERROR']), 'An error occurred while sending submission to Renderfarm.fi') return {'FINISHED'} def setStatus(property, status): if status: bpy.statusMessage[property] = 'ERROR' else: bpy.statusMessage[property] = 'TRIA_RIGHT' def showStatus(layoutform, property, message): if bpy.statusMessage[property] == 'ERROR': layoutform.label(text='', icon='ERROR') def checkStatus(ore): bpy.errors = [] if ore.hash=='' and (ore.username=='' or ore.password==''): bpy.errors.append('missing_creds') if '' in (ore.title, ore.longdesc, ore.shortdesc): bpy.errors.append('missing_desc') setStatus('username', ore.hash=='' and ore.username=='') setStatus('password', ore.hash=='' and ore.password=='') setStatus('title', ore.title=='') setStatus('longdesc', ore.longdesc=='') setStatus('shortdesc', ore.shortdesc=='') 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 if totFrames != 0: done = math.floor((self.frames / totFrames)*100) else: done = math.floor((self.frames / (totFrames+0.01))*100) if done > 100: done = 100 return done def xmlSessionsToOreSessions(sessions, queue): bpy.ore_sessions = [] completed = sessions[queue] for sid in completed: s = completed[sid]['title'] # t = completed[sid]['timestamps'] # UNUSED sinfo = OreSession(sid, s) if queue in ('completed', 'active'): sinfo.frames = completed[sid]['framesRendered'] sinfo.startframe = completed[sid]['startFrame'] sinfo.endframe = completed[sid]['endFrame'] bpy.ore_sessions.append(sinfo) def updateSessionList(ore): while(len(ore.sessions) > 0): ore.sessions.remove(0) for s in bpy.ore_sessions: ore.sessions.add() session = ore.sessions[-1] session.name = s.title + ' [' + str(s.percentageComplete()) + '% complete]' 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 userproxy = xmlrpc.client.ServerProxy(r'https://xmlrpc.renderfarm.fi/user') if len(bpy.ore_sessions)>0: s = bpy.ore_sessions[ore.selected_session] try: userproxy.user.cancelSession(ore.username, ore.hash, int(s.id)) self.report(set(['INFO']), 'Session ' + s.title + ' with id ' + s.id + ' cancelled') except: self.report(set(['ERROR']), 'Could not cancel session ' + s.title + ' with id ' + s.id) 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 userproxy = xmlrpc.client.ServerProxy(r'https://xmlrpc.renderfarm.fi/user') sessions = userproxy.user.getAllSessions(ore.username, ore.hash, 'completed') xmlSessionsToOreSessions(sessions, 'completed') updateSessionList(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 userproxy = xmlrpc.client.ServerProxy(r'https://xmlrpc.renderfarm.fi/user') sessions = userproxy.user.getAllSessions(ore.username, ore.hash, 'completed') xmlSessionsToOreSessions(sessions, 'canceled') updateSessionList(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 userproxy = xmlrpc.client.ServerProxy(r'https://xmlrpc.renderfarm.fi/user') sessions = userproxy.user.getAllSessions(ore.username, ore.hash, 'active') xmlSessionsToOreSessions(sessions, 'active') updateSessionList(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 userproxy = xmlrpc.client.ServerProxy(r'https://xmlrpc.renderfarm.fi/user') sessions = userproxy.user.getAllSessions(ore.username, ore.hash, 'accept') xmlSessionsToOreSessions(sessions, 'accept') updateSessionList(ore) return {'FINISHED'} class ORE_CheckUpdate(bpy.types.Operator): bl_idname = 'ore.check_update' bl_label = 'Check for new version' def execute(self, context): blenderproxy = xmlrpc.client.ServerProxy(r'http://xmlrpc.renderfarm.fi/blender') try: self.report(set(['INFO']), 'Checking for newer version on Renderfarm.fi') dl_url = blenderproxy.blender.getCurrentVersion(bpy.CURRENT_VERSION) if len(dl_url['url']) > 0: self.report(set(['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(set(['INFO']), 'Done checking for newer version on Renderfarm.fi') except xmlrpc.client.Fault as f: print('ERROR:', f) self.report(set(['ERROR']), 'An error occurred while checking for newer version on Renderfarm.fi') return {'FINISHED'} class ORE_LoginOp(bpy.types.Operator): bl_idname = 'ore.login' bl_label = 'Confirm credentials' def execute(self, context): sce = context.scene ore = sce.ore_render if ore.hash=='': if ore.password != '' and ore.username != '': ore.hash = hashlib.md5(ore.password.encode() + ore.username.encode()).hexdigest() ore.password = '' checkStatus(ore) if len(bpy.errors) > 0: ore.prepared = False return {'CANCELLED'} return {'FINISHED'} class ORE_PrepareOp(bpy.types.Operator): '''Checking the scene will also save to the current file when successful!''' bl_idname = 'ore.prepare' bl_label = 'Check scene' def execute(self, context): def hasSSSMaterial(): for m in bpy.data.materials: if m.subsurface_scattering.use: return True return False def hasParticleSystem(): if len(bpy.data.particles) > 0: self.report({'WARNING'}, "Found particle system") 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): self.report({'WARNING'}, "Found simulation: " + str(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 hasCompositingErrors(use_nodes, nodetree, parts): if not use_nodes: # no nodes in use, ignore check return False for node in nodetree.nodes: # output file absolutely forbidden if type(node)==bpy.types.CompositorNodeOutputFile: self.report({'ERROR'}, 'File output node is disallowed, remove them from your compositing nodetrees.') return True # blur et al are problematic when rendering ore.parts>1 if isFilterNode(node) and parts>1: self.report({'WARNING'}, 'A filtering node found and parts > 1. This combination will give bad output.') return True return False sce = context.scene ore = sce.ore_render errors = False checkStatus(ore) if len(bpy.errors) > 0: ore.prepared = False return {'CANCELLED'} rd = sce.render bpy.ops.file.pack_all() print("=============================================") rd.threads_mode = 'FIXED' rd.threads = 1 rd.resolution_x = ore.resox rd.resolution_y = ore.resoy if (rd.resolution_percentage != 100): print("Resolution percentage is not 100. Changing to 100%") self.report({'WARNING'}, "Resolution percentage is not 100. Changing to 100%") rd.resolution_percentage = 100 if rd.file_format != 'PNG': print("Renderfarm.fi always uses PNG for output. Changing to PNG.") self.report({'WARNING'}, "Renderfarm.fi always uses PNG for output. Changing to PNG.") rd.file_format = 'PNG' if (rd.use_sss == True or hasSSSMaterial()) and ore.parts > 1: print("Subsurface Scattering is not supported when rendering with parts > 1. Disabling") self.report({'WARNING'}, "Subsurface Scattering is not supported when rendering with parts > 1. Disabling") rd.use_sss = False # disabling because ore.parts > 1. It's ok to use SSS with 1part/frame if hasUnsupportedSimulation() == True: print("An unsupported simulation was detected. Please check your settings and remove them") self.report({'WARNING'}, "An unsupported simulation was detected. Please check your settings and remove them") ore.hasUnsupportedSimulation = True errors = True else: ore.hasUnsupportedSimulation = False if (rd.use_full_sample == True and rd.use_save_buffers == True): print("Save Buffers is not supported. As you also have Full Sample on, I'm turning both of the settings off") self.report({'WARNING'}, "Save Buffers is not supported. As you also have Full Sample on, I'm turning both of the settings off") if (rd.use_full_sample == False and rd.use_save_buffers == True): print("Save buffers needs to be turned off. Changing to off") self.report({'WARNING'}, "Save buffers needs to be turned off. Changing to off") rd.use_full_sample = False rd.use_save_buffers = False rd.use_free_image_textures = True if (rd.use_border == True): print("Border render is not supported. Turning it off") self.report({'WARNING'}, "Border render is not supported. Turning it off") rd.use_border = False if rd.use_compositing: if hasCompositingErrors(sce.use_nodes, sce.node_tree, ore.parts): print("Found disallowed nodes or problematic setup") rd.use_compositing = False self.report({'WARNING'}, "Found disallowed nodes or problematic setup") print("Done checking the scene. Now do a test render") self.report({'INFO'}, "Done checking the scene. Now do a test render") print("=============================================") # if errors found, don't allow to upload, instead have user # go through this until everything is ok # Errors is only True if there is a setting that could not be changed to the correct setting # In short, unsupported simulations if errors: self.report({'WARNING'}, "Some issues found. Check console and do a test render to make sure everything works.") ore.prepared = False else: ore.prepared = True rd.engine = 'BLENDER_RENDER' bpy.ops.wm.save_mainfile() rd.engine = 'RENDERFARMFI_RENDER' return {'FINISHED'} class ORE_ResetOp(bpy.types.Operator): bl_idname = "ore.reset" bl_label = "Reset Preparation" def execute(self, context): sce = context.scene sce.ore_render.prepared = False sce.render.threads_mode = 'AUTO' 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): rd = context.scene.render rd.engine = 'BLENDER_RENDER' bpy.ops.wm.save_mainfile() return ore_upload(self, context) 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_ChangeUser(bpy.types.Operator): bl_idname = "ore.change_user" bl_label = "Change user" def execute(self, context): ore = context.scene.ore_render ore.password = '' ore.hash = '' return {'FINISHED'} 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 menu_export(self, context): #~ import os #~ default_path = os.path.splitext(bpy.data.filepath)[0] + ".py" #~ self.layout.operator(RenderfarmFi.bl_idname, text=RenderfarmFi.bl_label) def register(): bpy.utils.register_module(__name__) bpy.types.Scene.ore_render = PointerProperty(type=ORESettings, name='ORE Render', description='ORE Render Settings') #~ bpy.types.INFO_MT_render.append(menu_export) def unregister(): bpy.utils.unregister_module(__name__) #~ bpy.types.INFO_MT_render.remove(menu_export) if __name__ == "__main__": register()