diff options
author | Martin Poirier <theeth@yahoo.com> | 2011-03-22 04:40:22 +0300 |
---|---|---|
committer | Martin Poirier <theeth@yahoo.com> | 2011-03-22 04:40:22 +0300 |
commit | a503713d6ca238119a57d0c86d79480aeccf5642 (patch) | |
tree | 4bc04bf3af3e786f4a50ecacb6960cd9406f6d66 /release/scripts | |
parent | f3686b58856adc6929ef38d14c03197127bab017 (diff) |
Moving netrender to addons
Diffstat (limited to 'release/scripts')
-rw-r--r-- | release/scripts/io/netrender/__init__.py | 73 | ||||
-rw-r--r-- | release/scripts/io/netrender/balancing.py | 195 | ||||
-rw-r--r-- | release/scripts/io/netrender/client.py | 372 | ||||
-rw-r--r-- | release/scripts/io/netrender/master.py | 1079 | ||||
-rw-r--r-- | release/scripts/io/netrender/master_html.py | 315 | ||||
-rw-r--r-- | release/scripts/io/netrender/model.py | 360 | ||||
-rw-r--r-- | release/scripts/io/netrender/netrender.css | 88 | ||||
-rw-r--r-- | release/scripts/io/netrender/netrender.js | 146 | ||||
-rw-r--r-- | release/scripts/io/netrender/operators.py | 564 | ||||
-rw-r--r-- | release/scripts/io/netrender/repath.py | 150 | ||||
-rw-r--r-- | release/scripts/io/netrender/slave.py | 349 | ||||
-rw-r--r-- | release/scripts/io/netrender/thumbnail.py | 81 | ||||
-rw-r--r-- | release/scripts/io/netrender/ui.py | 543 | ||||
-rw-r--r-- | release/scripts/io/netrender/utils.py | 313 | ||||
-rw-r--r-- | release/scripts/io/netrender/versioning.py | 72 |
15 files changed, 0 insertions, 4700 deletions
diff --git a/release/scripts/io/netrender/__init__.py b/release/scripts/io/netrender/__init__.py deleted file mode 100644 index c77894d4751..00000000000 --- a/release/scripts/io/netrender/__init__.py +++ /dev/null @@ -1,73 +0,0 @@ -# ##### 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 ##### - -# This directory is a Python package. - -# To support reload properly, try to access a package var, if it's there, reload everything -if "init_data" in locals(): - import imp - imp.reload(model) - imp.reload(operators) - imp.reload(client) - imp.reload(slave) - imp.reload(master) - imp.reload(master_html) - imp.reload(utils) - imp.reload(balancing) - imp.reload(ui) - imp.reload(repath) - imp.reload(versioning) -else: - from . import model - from . import operators - from . import client - from . import slave - from . import master - from . import master_html - from . import utils - from . import balancing - from . import ui - from . import repath - from . import versioning - -jobs = [] -slaves = [] -blacklist = [] - -init_file = "" -valid_address = False -init_data = True - - -def register(): - ui.addProperties() - - import bpy - scene = bpy.context.scene - if scene: - netsettings = scene.network_render - ui.init_data(netsettings) - - bpy.utils.register_module(__name__) - - -def unregister(): - import bpy - del bpy.types.Scene.network_render - - bpy.utils.unregister_module(__name__) diff --git a/release/scripts/io/netrender/balancing.py b/release/scripts/io/netrender/balancing.py deleted file mode 100644 index dde3ad53084..00000000000 --- a/release/scripts/io/netrender/balancing.py +++ /dev/null @@ -1,195 +0,0 @@ -# ##### 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 time - -from netrender.utils import * -import netrender.model - -class RatingRule: - def __init__(self): - self.enabled = True - - def id(self): - return str(id(self)) - - def rate(self, job): - return 0 - -class ExclusionRule: - def __init__(self): - self.enabled = True - - def id(self): - return str(id(self)) - - def test(self, job): - return False - -class PriorityRule: - def __init__(self): - self.enabled = True - - def id(self): - return str(id(self)) - - def test(self, job): - return False - -class Balancer: - def __init__(self): - self.rules = [] - self.priorities = [] - self.exceptions = [] - - def ruleByID(self, rule_id): - for rule in self.rules: - if rule.id() == rule_id: - return rule - for rule in self.priorities: - if rule.id() == rule_id: - return rule - for rule in self.exceptions: - if rule.id() == rule_id: - return rule - - return None - - def addRule(self, rule): - self.rules.append(rule) - - def addPriority(self, priority): - self.priorities.append(priority) - - def addException(self, exception): - self.exceptions.append(exception) - - def applyRules(self, job): - return sum((rule.rate(job) for rule in self.rules if rule.enabled)) - - def applyPriorities(self, job): - for priority in self.priorities: - if priority.enabled and priority.test(job): - return True # priorities are first - - return False - - def applyExceptions(self, job): - for exception in self.exceptions: - if exception.enabled and exception.test(job): - return True # exceptions are last - - return False - - def sortKey(self, job): - return (1 if self.applyExceptions(job) else 0, # exceptions after - 0 if self.applyPriorities(job) else 1, # priorities first - self.applyRules(job)) - - def balance(self, jobs): - if jobs: - # use inline copy to make sure the list is still accessible while sorting - jobs[:] = sorted(jobs, key=self.sortKey) - return jobs[0] - else: - return None - -# ========================== - -class RatingUsage(RatingRule): - def __str__(self): - return "Usage per job" - - def rate(self, job): - # less usage is better - return job.usage / job.priority - -class RatingUsageByCategory(RatingRule): - def __init__(self, get_jobs): - super().__init__() - self.getJobs = get_jobs - - def __str__(self): - return "Usage per category" - - def rate(self, job): - total_category_usage = sum([j.usage for j in self.getJobs() if j.category == job.category]) - maximum_priority = max([j.priority for j in self.getJobs() if j.category == job.category]) - - # less usage is better - return total_category_usage / maximum_priority - -class NewJobPriority(PriorityRule): - def __init__(self, limit = 1): - super().__init__() - self.limit = limit - - def setLimit(self, value): - self.limit = int(value) - - def str_limit(self): - return "less than %i frame%s done" % (self.limit, "s" if self.limit > 1 else "") - - def __str__(self): - return "Priority to new jobs" - - def test(self, job): - return job.countFrames(status = DONE) < self.limit - -class MinimumTimeBetweenDispatchPriority(PriorityRule): - def __init__(self, limit = 10): - super().__init__() - self.limit = limit - - def setLimit(self, value): - self.limit = int(value) - - def str_limit(self): - return "more than %i minute%s since last" % (self.limit, "s" if self.limit > 1 else "") - - def __str__(self): - return "Priority to jobs that haven't been dispatched recently" - - def test(self, job): - return job.countFrames(status = DISPATCHED) == 0 and (time.time() - job.last_dispatched) / 60 > self.limit - -class ExcludeQueuedEmptyJob(ExclusionRule): - def __str__(self): - return "Exclude non queued or empty jobs" - - def test(self, job): - return job.status != JOB_QUEUED or job.countFrames(status = QUEUED) == 0 - -class ExcludeSlavesLimit(ExclusionRule): - def __init__(self, count_jobs, count_slaves, limit = 0.75): - super().__init__() - self.count_jobs = count_jobs - self.count_slaves = count_slaves - self.limit = limit - - def setLimit(self, value): - self.limit = float(value) - - def str_limit(self): - return "more than %.0f%% of all slaves" % (self.limit * 100) - - def __str__(self): - return "Exclude jobs that would use too many slaves" - - def test(self, job): - return not ( self.count_jobs() == 1 or self.count_slaves() <= 1 or float(job.countSlaves() + 1) / self.count_slaves() <= self.limit ) diff --git a/release/scripts/io/netrender/client.py b/release/scripts/io/netrender/client.py deleted file mode 100644 index bc43b8cfbb7..00000000000 --- a/release/scripts/io/netrender/client.py +++ /dev/null @@ -1,372 +0,0 @@ -# ##### 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 sys, os, re -import http, http.client, http.server, urllib -import subprocess, shutil, time, hashlib -import json - -import netrender -import netrender.model -import netrender.slave as slave -import netrender.master as master -from netrender.utils import * - -def addFluidFiles(job, path): - if os.path.exists(path): - pattern = re.compile("fluidsurface_(final|preview)_([0-9]+)\.(bobj|bvel)\.gz") - - for fluid_file in sorted(os.listdir(path)): - match = pattern.match(fluid_file) - - if match: - # fluid frames starts at 0, which explains the +1 - # This is stupid - current_frame = int(match.groups()[1]) + 1 - job.addFile(path + fluid_file, current_frame, current_frame) - -def addPointCache(job, ob, point_cache, default_path): - if not point_cache.use_disk_cache: - return - - - name = point_cache.name - if name == "": - name = "".join(["%02X" % ord(c) for c in ob.name]) - - cache_path = bpy.path.abspath(point_cache.filepath) if point_cache.use_external else default_path - - index = "%02i" % point_cache.index - - if os.path.exists(cache_path): - pattern = re.compile(name + "_([0-9]+)_" + index + "\.bphys") - - cache_files = [] - - for cache_file in sorted(os.listdir(cache_path)): - match = pattern.match(cache_file) - - if match: - cache_frame = int(match.groups()[0]) - cache_files.append((cache_frame, cache_file)) - - cache_files.sort() - - if len(cache_files) == 1: - cache_frame, cache_file = cache_files[0] - job.addFile(cache_path + cache_file, cache_frame, cache_frame) - else: - for i in range(len(cache_files)): - current_item = cache_files[i] - next_item = cache_files[i+1] if i + 1 < len(cache_files) else None - previous_item = cache_files[i - 1] if i > 0 else None - - current_frame, current_file = current_item - - if not next_item and not previous_item: - job.addFile(cache_path + current_file, current_frame, current_frame) - elif next_item and not previous_item: - next_frame = next_item[0] - job.addFile(cache_path + current_file, current_frame, next_frame - 1) - elif not next_item and previous_item: - previous_frame = previous_item[0] - job.addFile(cache_path + current_file, previous_frame + 1, current_frame) - else: - next_frame = next_item[0] - previous_frame = previous_item[0] - job.addFile(cache_path + current_file, previous_frame + 1, next_frame - 1) - -def fillCommonJobSettings(job, job_name, netsettings): - job.name = job_name - job.category = netsettings.job_category - - for slave in netrender.blacklist: - job.blacklist.append(slave.id) - - job.chunks = netsettings.chunks - job.priority = netsettings.priority - - if netsettings.job_type == "JOB_BLENDER": - job.type = netrender.model.JOB_BLENDER - elif netsettings.job_type == "JOB_PROCESS": - job.type = netrender.model.JOB_PROCESS - elif netsettings.job_type == "JOB_VCS": - job.type = netrender.model.JOB_VCS - -def clientSendJob(conn, scene, anim = False): - netsettings = scene.network_render - if netsettings.job_type == "JOB_BLENDER": - return clientSendJobBlender(conn, scene, anim) - elif netsettings.job_type == "JOB_VCS": - return clientSendJobVCS(conn, scene, anim) - -def clientSendJobVCS(conn, scene, anim = False): - netsettings = scene.network_render - job = netrender.model.RenderJob() - - if anim: - for f in range(scene.frame_start, scene.frame_end + 1): - job.addFrame(f) - else: - job.addFrame(scene.frame_current) - - filename = bpy.data.filepath - - if not filename.startswith(netsettings.vcs_wpath): - # this is an error, need better way to handle this - return - - filename = filename[len(netsettings.vcs_wpath):] - - if filename[0] in (os.sep, os.altsep): - filename = filename[1:] - - print("CREATING VCS JOB", filename) - - job.addFile(filename, signed=False) - - job_name = netsettings.job_name - path, name = os.path.split(filename) - if job_name == "[default]": - job_name = name - - - fillCommonJobSettings(job, job_name, netsettings) - - # VCS Specific code - job.version_info = netrender.model.VersioningInfo() - job.version_info.system = netsettings.vcs_system - job.version_info.wpath = netsettings.vcs_wpath - job.version_info.rpath = netsettings.vcs_rpath - job.version_info.revision = netsettings.vcs_revision - - # try to send path first - conn.request("POST", "/job", json.dumps(job.serialize())) - response = conn.getresponse() - response.read() - - job_id = response.getheader("job-id") - - # a VCS job is always good right now, need error handling - - return job_id - -def clientSendJobBlender(conn, scene, anim = False): - netsettings = scene.network_render - job = netrender.model.RenderJob() - - if anim: - for f in range(scene.frame_start, scene.frame_end + 1): - job.addFrame(f) - else: - job.addFrame(scene.frame_current) - - filename = bpy.data.filepath - job.addFile(filename) - - job_name = netsettings.job_name - path, name = os.path.split(filename) - if job_name == "[default]": - job_name = name - - ########################### - # LIBRARIES - ########################### - for lib in bpy.data.libraries: - file_path = bpy.path.abspath(lib.filepath) - if os.path.exists(file_path): - job.addFile(file_path) - - ########################### - # IMAGES - ########################### - for image in bpy.data.images: - if image.source == "FILE" and not image.packed_file: - file_path = bpy.path.abspath(image.filepath) - if os.path.exists(file_path): - job.addFile(file_path) - - tex_path = os.path.splitext(file_path)[0] + ".tex" - if os.path.exists(tex_path): - job.addFile(tex_path) - - ########################### - # FLUID + POINT CACHE - ########################### - root, ext = os.path.splitext(name) - default_path = path + os.sep + "blendcache_" + root + os.sep # need an API call for that - - for object in bpy.data.objects: - for modifier in object.modifiers: - if modifier.type == 'FLUID_SIMULATION' and modifier.settings.type == "DOMAIN": - addFluidFiles(job, bpy.path.abspath(modifier.settings.filepath)) - elif modifier.type == "CLOTH": - addPointCache(job, object, modifier.point_cache, default_path) - elif modifier.type == "SOFT_BODY": - addPointCache(job, object, modifier.point_cache, default_path) - elif modifier.type == "SMOKE" and modifier.smoke_type == "TYPE_DOMAIN": - addPointCache(job, object, modifier.domain_settings.point_cache, default_path) - elif modifier.type == "MULTIRES" and modifier.is_external: - file_path = bpy.path.abspath(modifier.filepath) - job.addFile(file_path) - - # particles modifier are stupid and don't contain data - # we have to go through the object property - for psys in object.particle_systems: - addPointCache(job, object, psys.point_cache, default_path) - - #print(job.files) - - fillCommonJobSettings(job, job_name, netsettings) - - # try to send path first - conn.request("POST", "/job", json.dumps(job.serialize())) - response = conn.getresponse() - response.read() - - job_id = response.getheader("job-id") - - # if not ACCEPTED (but not processed), send files - if response.status == http.client.ACCEPTED: - for rfile in job.files: - f = open(rfile.filepath, "rb") - conn.request("PUT", fileURL(job_id, rfile.index), f) - f.close() - response = conn.getresponse() - response.read() - - # server will reply with ACCEPTED until all files are found - - return job_id - -def requestResult(conn, job_id, frame): - conn.request("GET", renderURL(job_id, frame)) - -class NetworkRenderEngine(bpy.types.RenderEngine): - bl_idname = 'NET_RENDER' - bl_label = "Network Render" - bl_use_postprocess = False - def render(self, scene): - if scene.network_render.mode == "RENDER_CLIENT": - self.render_client(scene) - elif scene.network_render.mode == "RENDER_SLAVE": - self.render_slave(scene) - elif scene.network_render.mode == "RENDER_MASTER": - self.render_master(scene) - else: - print("UNKNOWN OPERATION MODE") - - def render_master(self, scene): - netsettings = scene.network_render - - address = "" if netsettings.server_address == "[default]" else netsettings.server_address - - master.runMaster((address, netsettings.server_port), netsettings.use_master_broadcast, netsettings.use_master_clear, bpy.path.abspath(netsettings.path), self.update_stats, self.test_break) - - - def render_slave(self, scene): - slave.render_slave(self, scene.network_render, scene.render.threads) - - def render_client(self, scene): - netsettings = scene.network_render - self.update_stats("", "Network render client initiation") - - - conn = clientConnection(netsettings.server_address, netsettings.server_port) - - if conn: - # Sending file - - self.update_stats("", "Network render exporting") - - new_job = False - - job_id = netsettings.job_id - - # reading back result - - self.update_stats("", "Network render waiting for results") - - - requestResult(conn, job_id, scene.frame_current) - response = conn.getresponse() - buf = response.read() - - if response.status == http.client.NO_CONTENT: - new_job = True - netsettings.job_id = clientSendJob(conn, scene) - job_id = netsettings.job_id - - requestResult(conn, job_id, scene.frame_current) - response = conn.getresponse() - buf = response.read() - - while response.status == http.client.ACCEPTED and not self.test_break(): - time.sleep(1) - requestResult(conn, job_id, scene.frame_current) - response = conn.getresponse() - buf = response.read() - - # cancel new jobs (animate on network) on break - if self.test_break() and new_job: - conn.request("POST", cancelURL(job_id)) - response = conn.getresponse() - response.read() - print( response.status, response.reason ) - netsettings.job_id = 0 - - if response.status != http.client.OK: - conn.close() - return - - r = scene.render - x= int(r.resolution_x*r.resolution_percentage*0.01) - y= int(r.resolution_y*r.resolution_percentage*0.01) - - result_path = os.path.join(bpy.path.abspath(netsettings.path), "output.exr") - - folder = os.path.split(result_path)[0] - - if not os.path.exists(folder): - os.mkdir(folder) - - f = open(result_path, "wb") - - f.write(buf) - - f.close() - - result = self.begin_result(0, 0, x, y) - result.load_from_file(result_path) - self.end_result(result) - - conn.close() - -def compatible(module): - module = __import__(module) - for subclass in module.__dict__.values(): - try: subclass.COMPAT_ENGINES.add('NET_RENDER') - except: pass - del module - -compatible("properties_world") -compatible("properties_material") -compatible("properties_data_mesh") -compatible("properties_data_camera") -compatible("properties_texture") diff --git a/release/scripts/io/netrender/master.py b/release/scripts/io/netrender/master.py deleted file mode 100644 index 793e3bb51bf..00000000000 --- a/release/scripts/io/netrender/master.py +++ /dev/null @@ -1,1079 +0,0 @@ -# ##### 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 sys, os -import http, http.client, http.server, urllib, socket, socketserver, threading -import subprocess, shutil, time, hashlib -import pickle -import select # for select.error -import json - -from netrender.utils import * -import netrender.model -import netrender.balancing -import netrender.master_html -import netrender.thumbnail as thumbnail - -class MRenderFile(netrender.model.RenderFile): - def __init__(self, filepath, index, start, end, signature): - super().__init__(filepath, index, start, end, signature) - self.found = False - - def test(self): - self.found = os.path.exists(self.filepath) - if self.found and self.signature != None: - found_signature = hashFile(self.filepath) - self.found = self.signature == found_signature - - return self.found - - -class MRenderSlave(netrender.model.RenderSlave): - def __init__(self, name, address, stats): - super().__init__() - self.id = hashlib.md5(bytes(repr(name) + repr(address), encoding='utf8')).hexdigest() - self.name = name - self.address = address - self.stats = stats - self.last_seen = time.time() - - self.job = None - self.job_frames = [] - - netrender.model.RenderSlave._slave_map[self.id] = self - - def seen(self): - self.last_seen = time.time() - - def finishedFrame(self, frame_number): - self.job_frames.remove(frame_number) - if not self.job_frames: - self.job = None - -class MRenderJob(netrender.model.RenderJob): - def __init__(self, job_id, job_info): - super().__init__(job_info) - self.id = job_id - self.last_dispatched = time.time() - - # force one chunk for process jobs - if self.type == netrender.model.JOB_PROCESS: - self.chunks = 1 - - # Force WAITING status on creation - self.status = JOB_WAITING - - # special server properties - self.last_update = 0 - self.save_path = "" - self.files = [MRenderFile(rfile.filepath, rfile.index, rfile.start, rfile.end, rfile.signature) for rfile in job_info.files] - - def initInfo(self): - if not self.resolution: - self.resolution = tuple(getFileInfo(self.files[0].filepath, ["bpy.context.scene.render.resolution_x", "bpy.context.scene.render.resolution_y", "bpy.context.scene.render.resolution_percentage"])) - - def save(self): - if self.save_path: - f = open(os.path.join(self.save_path, "job.txt"), "w") - f.write(json.dumps(self.serialize())) - f.close() - - def edit(self, info_map): - if "status" in info_map: - self.status = info_map["status"] - - if "priority" in info_map: - self.priority = info_map["priority"] - - if "chunks" in info_map: - self.chunks = info_map["chunks"] - - def testStart(self): - # Don't test files for versionned jobs - if not self.version_info: - for f in self.files: - if not f.test(): - return False - - self.start() - self.initInfo() - return True - - def testFinished(self): - for f in self.frames: - if f.status == QUEUED or f.status == DISPATCHED: - break - else: - self.status = JOB_FINISHED - - def pause(self, status = None): - if self.status not in {JOB_PAUSED, JOB_QUEUED}: - return - - if status is None: - self.status = JOB_PAUSED if self.status == JOB_QUEUED else JOB_QUEUED - elif status: - self.status = JOB_QUEUED - else: - self.status = JOB_PAUSED - - def start(self): - self.status = JOB_QUEUED - - def addLog(self, frames): - log_name = "_".join(("%06d" % f for f in frames)) + ".log" - log_path = os.path.join(self.save_path, log_name) - - for number in frames: - frame = self[number] - if frame: - frame.log_path = log_path - - def addFrame(self, frame_number, command): - frame = MRenderFrame(frame_number, command) - self.frames.append(frame) - return frame - - def reset(self, all): - for f in self.frames: - f.reset(all) - - def getFrames(self): - frames = [] - for f in self.frames: - if f.status == QUEUED: - self.last_dispatched = time.time() - frames.append(f) - if len(frames) >= self.chunks: - break - - return frames - -class MRenderFrame(netrender.model.RenderFrame): - def __init__(self, frame, command): - super().__init__() - self.number = frame - self.slave = None - self.time = 0 - self.status = QUEUED - self.command = command - - self.log_path = None - - def reset(self, all): - if all or self.status == ERROR: - self.log_path = None - self.slave = None - self.time = 0 - self.status = QUEUED - - -# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= -# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= -# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -file_pattern = re.compile("/file_([a-zA-Z0-9]+)_([0-9]+)") -render_pattern = re.compile("/render_([a-zA-Z0-9]+)_([0-9]+).exr") -thumb_pattern = re.compile("/thumb_([a-zA-Z0-9]+)_([0-9]+).jpg") -log_pattern = re.compile("/log_([a-zA-Z0-9]+)_([0-9]+).log") -reset_pattern = re.compile("/reset(all|)_([a-zA-Z0-9]+)_([0-9]+)") -cancel_pattern = re.compile("/cancel_([a-zA-Z0-9]+)") -pause_pattern = re.compile("/pause_([a-zA-Z0-9]+)") -edit_pattern = re.compile("/edit_([a-zA-Z0-9]+)") - -class RenderHandler(http.server.BaseHTTPRequestHandler): - def log_message(self, format, *args): - # override because the original calls self.address_string(), which - # is extremely slow due to some timeout.. - sys.stderr.write("[%s] %s\n" % (self.log_date_time_string(), format%args)) - - def getInfoMap(self): - length = int(self.headers['content-length']) - - if length > 0: - msg = str(self.rfile.read(length), encoding='utf8') - return json.loads(msg) - else: - return {} - - def send_head(self, code = http.client.OK, headers = {}, content = "application/octet-stream"): - self.send_response(code) - self.send_header("Content-type", content) - - for key, value in headers.items(): - self.send_header(key, value) - - self.end_headers() - - def do_HEAD(self): - - if self.path == "/status": - job_id = self.headers.get('job-id', "") - job_frame = int(self.headers.get('job-frame', -1)) - - job = self.server.getJobID(job_id) - if job: - frame = job[job_frame] - - - if frame: - self.send_head(http.client.OK) - else: - # no such frame - self.send_head(http.client.NO_CONTENT) - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - - def do_GET(self): - - if self.path == "/version": - self.send_head() - self.server.stats("", "Version check") - self.wfile.write(VERSION) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/render"): - match = render_pattern.match(self.path) - - if match: - job_id = match.groups()[0] - frame_number = int(match.groups()[1]) - - job = self.server.getJobID(job_id) - - if job: - frame = job[frame_number] - - if frame: - if frame.status in (QUEUED, DISPATCHED): - self.send_head(http.client.ACCEPTED) - elif frame.status == DONE: - self.server.stats("", "Sending result to client") - - filename = os.path.join(job.save_path, "%06d.exr" % frame_number) - - f = open(filename, 'rb') - self.send_head(content = "image/x-exr") - shutil.copyfileobj(f, self.wfile) - f.close() - elif frame.status == ERROR: - self.send_head(http.client.PARTIAL_CONTENT) - else: - # no such frame - self.send_head(http.client.NO_CONTENT) - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: - # invalid url - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/thumb"): - match = thumb_pattern.match(self.path) - - if match: - job_id = match.groups()[0] - frame_number = int(match.groups()[1]) - - job = self.server.getJobID(job_id) - - if job: - frame = job[frame_number] - - if frame: - if frame.status in (QUEUED, DISPATCHED): - self.send_head(http.client.ACCEPTED) - elif frame.status == DONE: - filename = os.path.join(job.save_path, "%06d.exr" % frame_number) - - thumbname = thumbnail.generate(filename) - - if thumbname: - f = open(thumbname, 'rb') - self.send_head(content = "image/jpeg") - shutil.copyfileobj(f, self.wfile) - f.close() - else: # thumbnail couldn't be generated - self.send_head(http.client.PARTIAL_CONTENT) - return - elif frame.status == ERROR: - self.send_head(http.client.PARTIAL_CONTENT) - else: - # no such frame - self.send_head(http.client.NO_CONTENT) - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: - # invalid url - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/log"): - match = log_pattern.match(self.path) - - if match: - job_id = match.groups()[0] - frame_number = int(match.groups()[1]) - - job = self.server.getJobID(job_id) - - if job: - frame = job[frame_number] - - if frame: - if not frame.log_path or frame.status in (QUEUED, DISPATCHED): - self.send_head(http.client.PROCESSING) - else: - self.server.stats("", "Sending log to client") - f = open(frame.log_path, 'rb') - - self.send_head(content = "text/plain") - - shutil.copyfileobj(f, self.wfile) - - f.close() - else: - # no such frame - self.send_head(http.client.NO_CONTENT) - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: - # invalid URL - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/status": - job_id = self.headers.get('job-id', "") - job_frame = int(self.headers.get('job-frame', -1)) - - if job_id: - - job = self.server.getJobID(job_id) - if job: - if job_frame != -1: - frame = job[frame] - - if frame: - message = frame.serialize() - else: - # no such frame - self.send_heat(http.client.NO_CONTENT) - return - else: - message = job.serialize() - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - return - else: # status of all jobs - message = [] - - for job in self.server: - message.append(job.serialize()) - - - self.server.stats("", "Sending status") - self.send_head() - self.wfile.write(bytes(json.dumps(message), encoding='utf8')) - - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/job": - self.server.balance() - - slave_id = self.headers['slave-id'] - - slave = self.server.getSeenSlave(slave_id) - - if slave: # only if slave id is valid - job, frames = self.server.newDispatch(slave_id) - - if job and frames: - for f in frames: - print("dispatch", f.number) - f.status = DISPATCHED - f.slave = slave - - slave.job = job - slave.job_frames = [f.number for f in frames] - - self.send_head(headers={"job-id": job.id}) - - message = job.serialize(frames) - - self.wfile.write(bytes(json.dumps(message), encoding='utf8')) - - self.server.stats("", "Sending job to slave") - else: - # no job available, return error code - slave.job = None - slave.job_frames = [] - - self.send_head(http.client.ACCEPTED) - else: # invalid slave id - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/file"): - match = file_pattern.match(self.path) - - if match: - slave_id = self.headers['slave-id'] - slave = self.server.getSeenSlave(slave_id) - - if not slave: - # invalid slave id - print("invalid slave id") - - job_id = match.groups()[0] - file_index = int(match.groups()[1]) - - job = self.server.getJobID(job_id) - - if job: - render_file = job.files[file_index] - - if render_file: - self.server.stats("", "Sending file to slave") - f = open(render_file.filepath, 'rb') - - self.send_head() - shutil.copyfileobj(f, self.wfile) - - f.close() - else: - # no such file - self.send_head(http.client.NO_CONTENT) - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: # invalid url - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/slaves": - message = [] - - self.server.stats("", "Sending slaves status") - - for slave in self.server.slaves: - message.append(slave.serialize()) - - self.send_head() - - self.wfile.write(bytes(json.dumps(message), encoding='utf8')) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - else: - # hand over the rest to the html section - netrender.master_html.get(self) - - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - def do_POST(self): - - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - if self.path == "/job": - - length = int(self.headers['content-length']) - - job_info = netrender.model.RenderJob.materialize(json.loads(str(self.rfile.read(length), encoding='utf8'))) - - job_id = self.server.nextJobID() - - job = MRenderJob(job_id, job_info) - - for frame in job_info.frames: - frame = job.addFrame(frame.number, frame.command) - - self.server.addJob(job) - - headers={"job-id": job_id} - - if job.testStart(): - self.server.stats("", "New job, started") - self.send_head(headers=headers) - else: - self.server.stats("", "New job, missing files (%i total)" % len(job.files)) - self.send_head(http.client.ACCEPTED, headers=headers) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/edit"): - match = edit_pattern.match(self.path) - - if match: - job_id = match.groups()[0] - - job = self.server.getJobID(job_id) - - if job: - info_map = self.getInfoMap() - - job.edit(info_map) - self.send_head() - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: - # invalid url - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/balance_limit": - info_map = self.getInfoMap() - for rule_id, limit in info_map.items(): - try: - rule = self.server.balancer.ruleByID(rule_id) - if rule: - rule.setLimit(limit) - except: - pass # invalid type - - self.send_head() - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/balance_enable": - info_map = self.getInfoMap() - for rule_id, enabled in info_map.items(): - rule = self.server.balancer.ruleByID(rule_id) - if rule: - rule.enabled = enabled - - self.send_head() - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/cancel"): - match = cancel_pattern.match(self.path) - - if match: - info_map = self.getInfoMap() - clear = info_map.get("clear", False) - - job_id = match.groups()[0] - - job = self.server.getJobID(job_id) - - if job: - self.server.stats("", "Cancelling job") - self.server.removeJob(job, clear) - self.send_head() - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: - # invalid url - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/pause"): - match = pause_pattern.match(self.path) - - if match: - info_map = self.getInfoMap() - status = info_map.get("status", None) - - job_id = match.groups()[0] - - job = self.server.getJobID(job_id) - - if job: - self.server.stats("", "Pausing job") - job.pause(status) - self.send_head() - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: - # invalid url - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/clear": - # cancel all jobs - info_map = self.getInfoMap() - clear = info_map.get("clear", False) - - self.server.stats("", "Clearing jobs") - self.server.clear(clear) - - self.send_head() - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/reset"): - match = reset_pattern.match(self.path) - - if match: - all = match.groups()[0] == 'all' - job_id = match.groups()[1] - job_frame = int(match.groups()[2]) - - job = self.server.getJobID(job_id) - - if job: - if job_frame != 0: - - frame = job[job_frame] - if frame: - self.server.stats("", "Reset job frame") - frame.reset(all) - self.send_head() - else: - # no such frame - self.send_head(http.client.NO_CONTENT) - - else: - self.server.stats("", "Reset job") - job.reset(all) - self.send_head() - - else: # job not found - self.send_head(http.client.NO_CONTENT) - else: # invalid url - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/slave": - length = int(self.headers['content-length']) - job_frame_string = self.headers['job-frame'] - - self.server.stats("", "New slave connected") - - slave_info = netrender.model.RenderSlave.materialize(json.loads(str(self.rfile.read(length), encoding='utf8')), cache = False) - - slave_id = self.server.addSlave(slave_info.name, self.client_address, slave_info.stats) - - self.send_head(headers = {"slave-id": slave_id}) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/log": - length = int(self.headers['content-length']) - - log_info = netrender.model.LogFile.materialize(json.loads(str(self.rfile.read(length), encoding='utf8'))) - - slave_id = log_info.slave_id - - slave = self.server.getSeenSlave(slave_id) - - if slave: # only if slave id is valid - job = self.server.getJobID(log_info.job_id) - - if job: - self.server.stats("", "Log announcement") - job.addLog(log_info.frames) - self.send_head(http.client.OK) - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: # invalid slave id - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - def do_PUT(self): - - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - if self.path.startswith("/file"): - match = file_pattern.match(self.path) - - if match: - self.server.stats("", "Receiving job") - - length = int(self.headers['content-length']) - job_id = match.groups()[0] - file_index = int(match.groups()[1]) - - job = self.server.getJobID(job_id) - - if job: - - render_file = job.files[file_index] - - if render_file: - main_file = job.files[0].filepath # filename of the first file - - main_path, main_name = os.path.split(main_file) - - if file_index > 0: - file_path = prefixPath(job.save_path, render_file.filepath, main_path) - else: - file_path = os.path.join(job.save_path, main_name) - - buf = self.rfile.read(length) - - # add same temp file + renames as slave - - f = open(file_path, "wb") - f.write(buf) - f.close() - del buf - - render_file.filepath = file_path # set the new path - - if job.testStart(): - self.server.stats("", "File upload, starting job") - self.send_head(http.client.OK) - else: - self.server.stats("", "File upload, file missings") - self.send_head(http.client.ACCEPTED) - else: # invalid file - print("file not found", job_id, file_index) - self.send_head(http.client.NO_CONTENT) - else: # job not found - print("job not found", job_id, file_index) - self.send_head(http.client.NO_CONTENT) - else: # invalid url - print("no match") - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/render": - self.server.stats("", "Receiving render result") - - # need some message content here or the slave doesn't like it - self.wfile.write(bytes("foo", encoding='utf8')) - - slave_id = self.headers['slave-id'] - - slave = self.server.getSeenSlave(slave_id) - - if slave: # only if slave id is valid - job_id = self.headers['job-id'] - - job = self.server.getJobID(job_id) - - if job: - job_frame = int(self.headers['job-frame']) - job_result = int(self.headers['job-result']) - job_time = float(self.headers['job-time']) - - frame = job[job_frame] - - if frame: - if job.hasRenderResult(): - if job_result == DONE: - length = int(self.headers['content-length']) - buf = self.rfile.read(length) - f = open(os.path.join(job.save_path, "%06d.exr" % job_frame), 'wb') - f.write(buf) - f.close() - - del buf - elif job_result == ERROR: - # blacklist slave on this job on error - # slaves might already be in blacklist if errors on the whole chunk - if not slave.id in job.blacklist: - job.blacklist.append(slave.id) - - slave.finishedFrame(job_frame) - - frame.status = job_result - frame.time = job_time - - job.testFinished() - - self.send_head() - else: # frame not found - self.send_head(http.client.NO_CONTENT) - else: # job not found - self.send_head(http.client.NO_CONTENT) - else: # invalid slave id - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/thumb": - self.server.stats("", "Receiving thumbnail result") - - # need some message content here or the slave doesn't like it - self.wfile.write(bytes("foo", encoding='utf8')) - - slave_id = self.headers['slave-id'] - - slave = self.server.getSeenSlave(slave_id) - - if slave: # only if slave id is valid - job_id = self.headers['job-id'] - - job = self.server.getJobID(job_id) - - if job: - job_frame = int(self.headers['job-frame']) - - frame = job[job_frame] - - if frame: - if job.hasRenderResult(): - length = int(self.headers['content-length']) - buf = self.rfile.read(length) - f = open(os.path.join(job.save_path, "%06d.jpg" % job_frame), 'wb') - f.write(buf) - f.close() - - del buf - - else: # frame not found - self.send_head(http.client.NO_CONTENT) - else: # job not found - self.send_head(http.client.NO_CONTENT) - else: # invalid slave id - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/log"): - self.server.stats("", "Receiving log file") - - match = log_pattern.match(self.path) - - if match: - job_id = match.groups()[0] - - job = self.server.getJobID(job_id) - - if job: - job_frame = int(match.groups()[1]) - - frame = job[job_frame] - - if frame and frame.log_path: - length = int(self.headers['content-length']) - buf = self.rfile.read(length) - f = open(frame.log_path, 'ab') - f.write(buf) - f.close() - - del buf - - self.server.getSeenSlave(self.headers['slave-id']) - - self.send_head() - else: # frame not found - self.send_head(http.client.NO_CONTENT) - else: # job not found - self.send_head(http.client.NO_CONTENT) - else: # invalid url - self.send_head(http.client.NO_CONTENT) - -class RenderMasterServer(socketserver.ThreadingMixIn, http.server.HTTPServer): - def __init__(self, address, handler_class, path, subdir=True): - super().__init__(address, handler_class) - self.jobs = [] - self.jobs_map = {} - self.slaves = [] - self.slaves_map = {} - self.job_id = 0 - - if subdir: - self.path = os.path.join(path, "master_" + str(os.getpid())) - else: - self.path = path - - self.slave_timeout = 5 # 5 mins: need a parameter for that - - self.balancer = netrender.balancing.Balancer() - self.balancer.addRule(netrender.balancing.RatingUsageByCategory(self.getJobs)) - self.balancer.addRule(netrender.balancing.RatingUsage()) - self.balancer.addException(netrender.balancing.ExcludeQueuedEmptyJob()) - self.balancer.addException(netrender.balancing.ExcludeSlavesLimit(self.countJobs, self.countSlaves, limit = 0.9)) - self.balancer.addPriority(netrender.balancing.NewJobPriority()) - self.balancer.addPriority(netrender.balancing.MinimumTimeBetweenDispatchPriority(limit = 2)) - - if not os.path.exists(self.path): - os.mkdir(self.path) - - def restore(self, jobs, slaves, balancer = None): - self.jobs = jobs - self.jobs_map = {} - - for job in self.jobs: - self.jobs_map[job.id] = job - self.job_id = max(self.job_id, int(job.id)) - - self.slaves = slaves - for slave in self.slaves: - self.slaves_map[slave.id] = slave - - if balancer: - self.balancer = balancer - - - def nextJobID(self): - self.job_id += 1 - return str(self.job_id) - - def addSlave(self, name, address, stats): - slave = MRenderSlave(name, address, stats) - self.slaves.append(slave) - self.slaves_map[slave.id] = slave - - return slave.id - - def removeSlave(self, slave): - self.slaves.remove(slave) - self.slaves_map.pop(slave.id) - - def getSlave(self, slave_id): - return self.slaves_map.get(slave_id) - - def getSeenSlave(self, slave_id): - slave = self.getSlave(slave_id) - if slave: - slave.seen() - - return slave - - def timeoutSlaves(self): - removed = [] - - t = time.time() - - for slave in self.slaves: - if (t - slave.last_seen) / 60 > self.slave_timeout: - removed.append(slave) - - if slave.job: - for f in slave.job_frames: - slave.job[f].status = ERROR - - for slave in removed: - self.removeSlave(slave) - - def updateUsage(self): - blend = 0.5 - for job in self.jobs: - job.usage *= (1 - blend) - - if self.slaves: - slave_usage = blend / self.countSlaves() - - for slave in self.slaves: - if slave.job: - slave.job.usage += slave_usage - - - def clear(self, clear_files = False): - removed = self.jobs[:] - - for job in removed: - self.removeJob(job, clear_files) - - def balance(self): - self.balancer.balance(self.jobs) - - def getJobs(self): - return self.jobs - - def countJobs(self, status = JOB_QUEUED): - total = 0 - for j in self.jobs: - if j.status == status: - total += 1 - - return total - - def countSlaves(self): - return len(self.slaves) - - def removeJob(self, job, clear_files = False): - self.jobs.remove(job) - self.jobs_map.pop(job.id) - - if clear_files: - shutil.rmtree(job.save_path) - - for slave in self.slaves: - if slave.job == job: - slave.job = None - slave.job_frames = [] - - def addJob(self, job): - self.jobs.append(job) - self.jobs_map[job.id] = job - - # create job directory - job.save_path = os.path.join(self.path, "job_" + job.id) - if not os.path.exists(job.save_path): - os.mkdir(job.save_path) - - job.save() - - def getJobID(self, id): - return self.jobs_map.get(id) - - def __iter__(self): - for job in self.jobs: - yield job - - def newDispatch(self, slave_id): - if self.jobs: - for job in self.jobs: - if not self.balancer.applyExceptions(job) and slave_id not in job.blacklist: - return job, job.getFrames() - - return None, None - -def clearMaster(path): - shutil.rmtree(path) - -def createMaster(address, clear, path): - filepath = os.path.join(path, "blender_master.data") - - if not clear and os.path.exists(filepath): - print("loading saved master:", filepath) - with open(filepath, 'rb') as f: - path, jobs, slaves = pickle.load(f) - - httpd = RenderMasterServer(address, RenderHandler, path, subdir=False) - httpd.restore(jobs, slaves) - - return httpd - - return RenderMasterServer(address, RenderHandler, path) - -def saveMaster(path, httpd): - filepath = os.path.join(path, "blender_master.data") - - with open(filepath, 'wb') as f: - pickle.dump((httpd.path, httpd.jobs, httpd.slaves), f, pickle.HIGHEST_PROTOCOL) - -def runMaster(address, broadcast, clear, path, update_stats, test_break): - httpd = createMaster(address, clear, path) - httpd.timeout = 1 - httpd.stats = update_stats - - if broadcast: - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - - start_time = time.time() - 2 - - while not test_break(): - try: - httpd.handle_request() - except select.error: - pass - - if time.time() - start_time >= 2: # need constant here - httpd.timeoutSlaves() - - httpd.updateUsage() - - if broadcast: - print("broadcasting address") - s.sendto(bytes("%i" % address[1], encoding='utf8'), 0, ('<broadcast>', 8000)) - start_time = time.time() - - httpd.server_close() - if clear: - clearMaster(httpd.path) - else: - saveMaster(path, httpd) - diff --git a/release/scripts/io/netrender/master_html.py b/release/scripts/io/netrender/master_html.py deleted file mode 100644 index 877273207a8..00000000000 --- a/release/scripts/io/netrender/master_html.py +++ /dev/null @@ -1,315 +0,0 @@ -# ##### 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 os -import re -import shutil -from netrender.utils import * -import netrender.model - -src_folder = os.path.split(__file__)[0] - -def get(handler): - def output(text): - handler.wfile.write(bytes(text, encoding='utf8')) - - def head(title, refresh = False): - output("<html><head>") - if refresh: - output("<meta http-equiv='refresh' content=5>") - output("<script src='/html/netrender.js' type='text/javascript'></script>") -# output("<script src='/html/json2.js' type='text/javascript'></script>") - output("<title>") - output(title) - output("</title></head><body>") - output("<link rel='stylesheet' href='/html/netrender.css' type='text/css'>") - - - def link(text, url, script=""): - return "<a href='%s' %s>%s</a>" % (url, script, text) - - def tag(name, text, attr=""): - return "<%s %s>%s</%s>" % (name, attr, text, name) - - def startTable(border=1, class_style = None, caption = None): - output("<table border='%i'" % border) - - if class_style: - output(" class='%s'" % class_style) - - output(">") - - if caption: - output("<caption>%s</caption>" % caption) - - def headerTable(*headers): - output("<thead><tr>") - - for c in headers: - output("<td>" + c + "</td>") - - output("</tr></thead>") - - def rowTable(*data, id = None, class_style = None, extra = None): - output("<tr") - - if id: - output(" id='%s'" % id) - - if class_style: - output(" class='%s'" % class_style) - - if extra: - output(" %s" % extra) - - output(">") - - for c in data: - output("<td>" + str(c) + "</td>") - - output("</tr>") - - def endTable(): - output("</table>") - - def checkbox(title, value, script=""): - return """<input type="checkbox" title="%s" %s %s>""" % (title, "checked" if value else "", ("onclick=\"%s\"" % script) if script else "") - - if handler.path == "/html/netrender.js": - f = open(os.path.join(src_folder, "netrender.js"), 'rb') - - handler.send_head(content = "text/javascript") - shutil.copyfileobj(f, handler.wfile) - - f.close() - elif handler.path == "/html/netrender.css": - f = open(os.path.join(src_folder, "netrender.css"), 'rb') - - handler.send_head(content = "text/css") - shutil.copyfileobj(f, handler.wfile) - - f.close() - elif handler.path == "/html" or handler.path == "/": - handler.send_head(content = "text/html") - head("NetRender", refresh = True) - - output("<h2>Jobs</h2>") - - startTable() - headerTable( - " ", - "id", - "name", - "category", - "type", - "chunks", - "priority", - "usage", - "wait", - "status", - "length", - "done", - "dispatched", - "error", - "priority", - "exception" - ) - - handler.server.balance() - - for job in handler.server.jobs: - results = job.framesStatus() - rowTable( - """<button title="cancel job" onclick="cancel_job('%s');">X</button>""" % job.id + - """<button title="pause job" onclick="request('/pause_%s', null);">P</button>""" % job.id + - """<button title="reset all frames" onclick="request('/resetall_%s_0', null);">R</button>""" % job.id, - job.id, - link(job.name, "/html/job" + job.id), - job.category if job.category else "<i>None</i>", - netrender.model.JOB_TYPES[job.type], - str(job.chunks) + - """<button title="increase chunks size" onclick="request('/edit_%s', "{'chunks': %i}");">+</button>""" % (job.id, job.chunks + 1) + - """<button title="decrease chunks size" onclick="request('/edit_%s', "{'chunks': %i}");" %s>-</button>""" % (job.id, job.chunks - 1, "disabled=True" if job.chunks == 1 else ""), - str(job.priority) + - """<button title="increase priority" onclick="request('/edit_%s', "{'priority': %i}");">+</button>""" % (job.id, job.priority + 1) + - """<button title="decrease priority" onclick="request('/edit_%s', "{'priority': %i}");" %s>-</button>""" % (job.id, job.priority - 1, "disabled=True" if job.priority == 1 else ""), - "%0.1f%%" % (job.usage * 100), - "%is" % int(time.time() - job.last_dispatched), - job.statusText(), - len(job), - results[DONE], - results[DISPATCHED], - str(results[ERROR]) + - """<button title="reset error frames" onclick="request('/reset_%s_0', null);" %s>R</button>""" % (job.id, "disabled=True" if not results[ERROR] else ""), - "yes" if handler.server.balancer.applyPriorities(job) else "no", - "yes" if handler.server.balancer.applyExceptions(job) else "no" - ) - - endTable() - - output("<h2>Slaves</h2>") - - startTable() - headerTable("name", "address", "last seen", "stats", "job") - - for slave in handler.server.slaves: - rowTable(slave.name, slave.address[0], time.ctime(slave.last_seen), slave.stats, link(slave.job.name, "/html/job" + slave.job.id) if slave.job else "None") - - endTable() - - output("<h2>Configuration</h2>") - - output("""<button title="remove all jobs" onclick="clear_jobs();">CLEAR JOB LIST</button>""") - - startTable(caption = "Rules", class_style = "rules") - - headerTable("type", "enabled", "description", "limit") - - for rule in handler.server.balancer.rules: - rowTable( - "rating", - checkbox("", rule.enabled, "balance_enable('%s', '%s')" % (rule.id(), str(not rule.enabled).lower())), - rule, - rule.str_limit() + - """<button title="edit limit" onclick="balance_edit('%s', '%s');">edit</button>""" % (rule.id(), str(rule.limit)) if hasattr(rule, "limit") else " " - ) - - for rule in handler.server.balancer.priorities: - rowTable( - "priority", - checkbox("", rule.enabled, "balance_enable('%s', '%s')" % (rule.id(), str(not rule.enabled).lower())), - rule, - rule.str_limit() + - """<button title="edit limit" onclick="balance_edit('%s', '%s');">edit</button>""" % (rule.id(), str(rule.limit)) if hasattr(rule, "limit") else " " - ) - - for rule in handler.server.balancer.exceptions: - rowTable( - "exception", - checkbox("", rule.enabled, "balance_enable('%s', '%s')" % (rule.id(), str(not rule.enabled).lower())), - rule, - rule.str_limit() + - """<button title="edit limit" onclick="balance_edit('%s', '%s');">edit</button>""" % (rule.id(), str(rule.limit)) if hasattr(rule, "limit") else " " - ) - - endTable() - - output("</body></html>") - - elif handler.path.startswith("/html/job"): - handler.send_head(content = "text/html") - job_id = handler.path[9:] - - head("NetRender") - - job = handler.server.getJobID(job_id) - - if job: - output("<h2>Render Information</h2>") - - job.initInfo() - - startTable() - - rowTable("resolution", "%ix%i at %i%%" % job.resolution) - - endTable() - - - if job.type == netrender.model.JOB_BLENDER: - output("<h2>Files</h2>") - - startTable() - headerTable("path") - - tot_cache = 0 - tot_fluid = 0 - - rowTable(job.files[0].filepath) - rowTable("Other Files", class_style = "toggle", extra = "onclick='toggleDisplay(".other", "none", "table-row")'") - - for file in job.files: - if file.filepath.endswith(".bphys"): - tot_cache += 1 - elif file.filepath.endswith(".bobj.gz") or file.filepath.endswith(".bvel.gz"): - tot_fluid += 1 - else: - if file != job.files[0]: - rowTable(file.filepath, class_style = "other") - - if tot_cache > 0: - rowTable("%i physic cache files" % tot_cache, class_style = "toggle", extra = "onclick='toggleDisplay(".cache", "none", "table-row")'") - for file in job.files: - if file.filepath.endswith(".bphys"): - rowTable(os.path.split(file.filepath)[1], class_style = "cache") - - if tot_fluid > 0: - rowTable("%i fluid bake files" % tot_fluid, class_style = "toggle", extra = "onclick='toggleDisplay(".fluid", "none", "table-row")'") - for file in job.files: - if file.filepath.endswith(".bobj.gz") or file.filepath.endswith(".bvel.gz"): - rowTable(os.path.split(file.filepath)[1], class_style = "fluid") - - endTable() - elif job.type == netrender.model.JOB_VCS: - output("<h2>Versioning</h2>") - - startTable() - - rowTable("System", job.version_info.system.name) - rowTable("Remote Path", job.version_info.rpath) - rowTable("Working Path", job.version_info.wpath) - rowTable("Revision", job.version_info.revision) - rowTable("Render File", job.files[0].filepath) - - endTable() - - if job.blacklist: - output("<h2>Blacklist</h2>") - - startTable() - headerTable("name", "address") - - for slave_id in job.blacklist: - slave = handler.server.slaves_map[slave_id] - rowTable(slave.name, slave.address[0]) - - endTable() - - output("<h2>Frames</h2>") - - startTable() - headerTable("no", "status", "render time", "slave", "log", "result", "") - - for frame in job.frames: - rowTable( - frame.number, - frame.statusText(), - "%.1fs" % frame.time, - frame.slave.name if frame.slave else " ", - link("view log", logURL(job_id, frame.number)) if frame.log_path else " ", - link("view result", renderURL(job_id, frame.number)) + " [" + - tag("span", "show", attr="class='thumb' onclick='showThumb(%s, %i)'" % (job.id, frame.number)) + "]" if frame.status == DONE else " ", - "<img name='thumb%i' title='hide thumbnails' src='' class='thumb' onclick='showThumb(%s, %i)'>" % (frame.number, job.id, frame.number) - ) - - endTable() - else: - output("no such job") - - output("</body></html>") - diff --git a/release/scripts/io/netrender/model.py b/release/scripts/io/netrender/model.py deleted file mode 100644 index 5fc0bc2a0bb..00000000000 --- a/release/scripts/io/netrender/model.py +++ /dev/null @@ -1,360 +0,0 @@ -# ##### 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 sys, os -import http, http.client, http.server, urllib -import subprocess, shutil, time, hashlib - -import netrender.versioning as versioning -from netrender.utils import * - -class LogFile: - def __init__(self, job_id = 0, slave_id = 0, frames = []): - self.job_id = job_id - self.slave_id = slave_id - self.frames = frames - - def serialize(self): - return { - "job_id": self.job_id, - "slave_id": self.slave_id, - "frames": self.frames - } - - @staticmethod - def materialize(data): - if not data: - return None - - logfile = LogFile() - logfile.job_id = data["job_id"] - logfile.slave_id = data["slave_id"] - logfile.frames = data["frames"] - - return logfile - -class RenderSlave: - _slave_map = {} - - def __init__(self): - self.id = "" - self.name = "" - self.address = ("",0) - self.stats = "" - self.total_done = 0 - self.total_error = 0 - self.last_seen = 0.0 - - def serialize(self): - return { - "id": self.id, - "name": self.name, - "address": self.address, - "stats": self.stats, - "total_done": self.total_done, - "total_error": self.total_error, - "last_seen": self.last_seen - } - - @staticmethod - def materialize(data, cache = True): - if not data: - return None - - slave_id = data["id"] - - if cache and slave_id in RenderSlave._slave_map: - return RenderSlave._slave_map[slave_id] - - slave = RenderSlave() - slave.id = slave_id - slave.name = data["name"] - slave.address = data["address"] - slave.stats = data["stats"] - slave.total_done = data["total_done"] - slave.total_error = data["total_error"] - slave.last_seen = data["last_seen"] - - if cache: - RenderSlave._slave_map[slave_id] = slave - - return slave - -JOB_BLENDER = 1 -JOB_PROCESS = 2 -JOB_VCS = 3 - -JOB_TYPES = { - JOB_BLENDER: "Blender", - JOB_PROCESS: "Process", - JOB_VCS: "Versioned", - } - -class VersioningInfo: - def __init__(self, info = None): - self._system = None - self.wpath = "" - self.rpath = "" - self.revision = "" - - @property - def system(self): - return self._system - - @system.setter - def system(self, value): - self._system = versioning.SYSTEMS[value] - - def update(self): - self.system.update(self) - - def serialize(self): - return { - "wpath": self.wpath, - "rpath": self.rpath, - "revision": self.revision, - "system": self.system.name - } - - @staticmethod - def generate(system, path): - vs = VersioningInfo() - vs.wpath = path - vs.system = system - - vs.rpath = vs.system.path(path) - vs.revision = vs.system.revision(path) - - return vs - - - @staticmethod - def materialize(data): - if not data: - return None - - vs = VersioningInfo() - vs.wpath = data["wpath"] - vs.rpath = data["rpath"] - vs.revision = data["revision"] - vs.system = data["system"] - - return vs - - -class RenderFile: - def __init__(self, filepath = "", index = 0, start = -1, end = -1, signature=0): - self.filepath = filepath - self.original_path = filepath - self.signature = signature - self.index = index - self.start = start - self.end = end - - def serialize(self): - return { - "filepath": self.filepath, - "original_path": self.original_path, - "index": self.index, - "start": self.start, - "end": self.end, - "signature": self.signature - } - - @staticmethod - def materialize(data): - if not data: - return None - - rfile = RenderFile(data["filepath"], data["index"], data["start"], data["end"], data["signature"]) - rfile.original_path = data["original_path"] - - return rfile - -class RenderJob: - def __init__(self, job_info = None): - self.id = "" - self.type = JOB_BLENDER - self.name = "" - self.category = "None" - self.status = JOB_WAITING - self.files = [] - self.chunks = 0 - self.priority = 0 - self.blacklist = [] - - self.version_info = None - - self.resolution = None - - self.usage = 0.0 - self.last_dispatched = 0.0 - self.frames = [] - - if job_info: - self.type = job_info.type - self.name = job_info.name - self.category = job_info.category - self.status = job_info.status - self.files = job_info.files - self.chunks = job_info.chunks - self.priority = job_info.priority - self.blacklist = job_info.blacklist - self.version_info = job_info.version_info - - def hasRenderResult(self): - return self.type in (JOB_BLENDER, JOB_VCS) - - def rendersWithBlender(self): - return self.type in (JOB_BLENDER, JOB_VCS) - - def addFile(self, file_path, start=-1, end=-1, signed=True): - if signed: - signature = hashFile(file_path) - else: - signature = None - self.files.append(RenderFile(file_path, len(self.files), start, end, signature)) - - def addFrame(self, frame_number, command = ""): - frame = RenderFrame(frame_number, command) - self.frames.append(frame) - return frame - - def __len__(self): - return len(self.frames) - - def countFrames(self, status=QUEUED): - total = 0 - for f in self.frames: - if f.status == status: - total += 1 - - return total - - def countSlaves(self): - return len(set((frame.slave for frame in self.frames if frame.status == DISPATCHED))) - - def statusText(self): - return JOB_STATUS_TEXT[self.status] - - def framesStatus(self): - results = { - QUEUED: 0, - DISPATCHED: 0, - DONE: 0, - ERROR: 0 - } - - for frame in self.frames: - results[frame.status] += 1 - - return results - - def __contains__(self, frame_number): - for f in self.frames: - if f.number == frame_number: - return True - else: - return False - - def __getitem__(self, frame_number): - for f in self.frames: - if f.number == frame_number: - return f - else: - return None - - def serialize(self, frames = None): - min_frame = min((f.number for f in frames)) if frames else -1 - max_frame = max((f.number for f in frames)) if frames else -1 - return { - "id": self.id, - "type": self.type, - "name": self.name, - "category": self.category, - "status": self.status, - "files": [f.serialize() for f in self.files if f.start == -1 or not frames or (f.start <= max_frame and f.end >= min_frame)], - "frames": [f.serialize() for f in self.frames if not frames or f in frames], - "chunks": self.chunks, - "priority": self.priority, - "usage": self.usage, - "blacklist": self.blacklist, - "last_dispatched": self.last_dispatched, - "version_info": self.version_info.serialize() if self.version_info else None, - "resolution": self.resolution - } - - @staticmethod - def materialize(data): - if not data: - return None - - job = RenderJob() - job.id = data["id"] - job.type = data["type"] - job.name = data["name"] - job.category = data["category"] - job.status = data["status"] - job.files = [RenderFile.materialize(f) for f in data["files"]] - job.frames = [RenderFrame.materialize(f) for f in data["frames"]] - job.chunks = data["chunks"] - job.priority = data["priority"] - job.usage = data["usage"] - job.blacklist = data["blacklist"] - job.last_dispatched = data["last_dispatched"] - job.resolution = data["resolution"] - - version_info = data.get("version_info", None) - if version_info: - job.version_info = VersioningInfo.materialize(version_info) - - return job - -class RenderFrame: - def __init__(self, number = 0, command = ""): - self.number = number - self.time = 0 - self.status = QUEUED - self.slave = None - self.command = command - - def statusText(self): - return FRAME_STATUS_TEXT[self.status] - - def serialize(self): - return { - "number": self.number, - "time": self.time, - "status": self.status, - "slave": None if not self.slave else self.slave.serialize(), - "command": self.command - } - - @staticmethod - def materialize(data): - if not data: - return None - - frame = RenderFrame() - frame.number = data["number"] - frame.time = data["time"] - frame.status = data["status"] - frame.slave = RenderSlave.materialize(data["slave"]) - frame.command = data["command"] - - return frame diff --git a/release/scripts/io/netrender/netrender.css b/release/scripts/io/netrender/netrender.css deleted file mode 100644 index 0c54690e002..00000000000 --- a/release/scripts/io/netrender/netrender.css +++ /dev/null @@ -1,88 +0,0 @@ -body { - background-color:#eee; - font-size:12px; - font-family: "Lucida Sans","Lucida Sans Unicode","Lucida Grande",Lucida,sans-serif; - -} -a { - /*text-decoration:none;*/ - color:#666; -} -a:hover { - color:#000; -} -h2 { - background-color:#ddd; - font-size:120%; - padding:5px; -} - -h2 { - background-color:#ddd; - font-size:110%; - padding:5px; -} - -table { - text-align:center; - border:0; - background-color:#ddd; - padding: 0px; - margin: 0px; -} -thead{ - font-size:90%; - color:#555; - background-color:#ccc; -} -td { - border:0; - padding:2px; - padding-left:10px; - padding-right:10px; - margin-left:20px; - background-color:#ddd; -} -td:hover { - background-color:#ccc; -} -tr { - border:0; -} -button { - color: #111; - width: auto; - height: auto; -} - -.toggle { - text-decoration: underline; - cursor: pointer; -} - -.cache { - display: none; -} - -.fluid { - display: none; -} - -.other { - display: none; -} - -.rules { - width: 60em; - text-align: left; -} - -img.thumb { - display: none; - cursor: pointer; -} - -span.thumb { - text-decoration: underline; - cursor: pointer; -} diff --git a/release/scripts/io/netrender/netrender.js b/release/scripts/io/netrender/netrender.js deleted file mode 100644 index 1024a169571..00000000000 --- a/release/scripts/io/netrender/netrender.js +++ /dev/null @@ -1,146 +0,0 @@ -lastFrame = -1 -maxFrame = -1 -minFrame = -1 - -function request(url, data) -{ - xmlhttp = new XMLHttpRequest(); - xmlhttp.open("POST", url, false); - xmlhttp.send(data); - window.location.reload() -} - -function edit(id, info) -{ - request("/edit_" + id, info) -} - -function clear_jobs() -{ - var r=confirm("Also delete files on master?"); - - if (r==true) { - request('/clear', '{"clear":true}'); - } else { - request('/clear', '{"clear":false}'); - } -} - -function cancel_job(id) -{ - var r=confirm("Also delete files on master?"); - - if (r==true) { - request('/cancel_' + id, '{"clear":true}'); - } else { - request('/cancel_' + id, '{"clear":false}'); - } -} - -function balance_edit(id, old_value) -{ - var new_value = prompt("New limit", old_value); - if (new_value != null && new_value != "") { - request("/balance_limit", '{"' + id + '":"' + new_value + '"}'); - } -} - -function balance_enable(id, value) -{ - request("/balance_enable", '{"' + id + '":' + value + "}"); -} - -function showThumb(job, frame) -{ - if (lastFrame != -1) { - if (maxFrame != -1 && minFrame != -1) { - if (frame >= minFrame && frame <= maxFrame) { - for(i = minFrame; i <= maxFrame; i=i+1) { - toggleThumb(job, i); - } - minFrame = -1; - maxFrame = -1; - lastFrame = -1; - } else if (frame > maxFrame) { - for(i = maxFrame+1; i <= frame; i=i+1) { - toggleThumb(job, i); - } - maxFrame = frame; - lastFrame = frame; - } else { - for(i = frame; i <= minFrame-1; i=i+1) { - toggleThumb(job, i); - } - minFrame = frame; - lastFrame = frame; - } - } else if (frame == lastFrame) { - toggleThumb(job, frame); - } else if (frame < lastFrame) { - minFrame = frame; - maxFrame = lastFrame; - - for(i = minFrame; i <= maxFrame-1; i=i+1) { - toggleThumb(job, i); - } - lastFrame = frame; - } else { - minFrame = lastFrame; - maxFrame = frame; - - for(i = minFrame+1; i <= maxFrame; i=i+1) { - toggleThumb(job, i); - } - lastFrame = frame; - } - } else { - toggleThumb(job, frame); - } -} - -function toggleThumb(job, frame) -{ - img = document.images["thumb" + frame]; - url = "/thumb_" + job + "_" + frame + ".jpg" - - if (img.style.display == "block") { - img.style.display = "none"; - img.src = ""; - lastFrame = -1; - } else { - img.src = url; - img.style.display = "block"; - lastFrame = frame; - } -} - -function returnObjById( id ) -{ - if (document.getElementById) - var returnVar = document.getElementById(id); - else if (document.all) - var returnVar = document.all[id]; - else if (document.layers) - var returnVar = document.layers[id]; - return returnVar; -} - -function toggleDisplay( className, value1, value2 ) -{ - style = getStyle(className) - - if (style.style["display"] == value1) { - style.style["display"] = value2; - } else { - style.style["display"] = value1; - } -} - -function getStyle(className) { - var classes = document.styleSheets[0].rules || document.styleSheets[0].cssRules - for(var x=0;x<classes.length;x++) { - if(classes[x].selectorText==className) { - return classes[x]; - } - } -}
\ No newline at end of file diff --git a/release/scripts/io/netrender/operators.py b/release/scripts/io/netrender/operators.py deleted file mode 100644 index f2c2fda7bde..00000000000 --- a/release/scripts/io/netrender/operators.py +++ /dev/null @@ -1,564 +0,0 @@ -# ##### 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 sys, os -import http, http.client, http.server, urllib, socket -import webbrowser -import json - -import netrender -from netrender.utils import * -import netrender.client as client -import netrender.model -import netrender.versioning as versioning - -class RENDER_OT_netslave_bake(bpy.types.Operator): - '''NEED DESCRIPTION''' - bl_idname = "render.netslavebake" - bl_label = "Bake all in file" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - scene = context.scene - netsettings = scene.network_render - - filename = bpy.data.filepath - path, name = os.path.split(filename) - root, ext = os.path.splitext(name) - default_path = path + os.sep + "blendcache_" + root + os.sep # need an API call for that - relative_path = os.sep + os.sep + "blendcache_" + root + os.sep - - # Force all point cache next to the blend file - for object in bpy.data.objects: - for modifier in object.modifiers: - if modifier.type == 'FLUID_SIMULATION' and modifier.settings.type == "DOMAIN": - modifier.settings.path = relative_path - bpy.ops.fluid.bake({"active_object": object, "scene": scene}) - elif modifier.type == "CLOTH": - modifier.point_cache.frame_step = 1 - modifier.point_cache.use_disk_cache = True - modifier.point_cache.use_external = False - elif modifier.type == "SOFT_BODY": - modifier.point_cache.frame_step = 1 - modifier.point_cache.use_disk_cache = True - modifier.point_cache.use_external = False - elif modifier.type == "SMOKE" and modifier.smoke_type == "TYPE_DOMAIN": - modifier.domain_settings.point_cache.use_step = 1 - modifier.domain_settings.point_cache.use_disk_cache = True - modifier.domain_settings.point_cache.use_external = False - - # particles modifier are stupid and don't contain data - # we have to go through the object property - for psys in object.particle_systems: - psys.point_cache.use_step = 1 - psys.point_cache.use_disk_cache = True - psys.point_cache.use_external = False - psys.point_cache.filepath = relative_path - - bpy.ops.ptcache.bake_all() - - #bpy.ops.wm.save_mainfile(filepath = path + os.sep + root + "_baked.blend") - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class RENDER_OT_netclientanim(bpy.types.Operator): - '''Start rendering an animation on network''' - bl_idname = "render.netclientanim" - bl_label = "Animation on network" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - scene = context.scene - netsettings = scene.network_render - - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - # Sending file - scene.network_render.job_id = client.clientSendJob(conn, scene, True) - conn.close() - - bpy.ops.render.render('INVOKE_AREA', animation=True) - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class RENDER_OT_netclientrun(bpy.types.Operator): - '''Start network rendering service''' - bl_idname = "render.netclientstart" - bl_label = "Start Service" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - bpy.ops.render.render('INVOKE_AREA', animation=True) - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class RENDER_OT_netclientsend(bpy.types.Operator): - '''Send Render Job to the Network''' - bl_idname = "render.netclientsend" - bl_label = "Send job" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - scene = context.scene - netsettings = scene.network_render - - try: - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - # Sending file - scene.network_render.job_id = client.clientSendJob(conn, scene, True) - conn.close() - self.report('INFO', "Job sent to master") - except Exception as err: - self.report('ERROR', str(err)) - - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class RENDER_OT_netclientsendframe(bpy.types.Operator): - '''Send Render Job with current frame to the Network''' - bl_idname = "render.netclientsendframe" - bl_label = "Send current frame job" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - scene = context.scene - netsettings = scene.network_render - - try: - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - # Sending file - scene.network_render.job_id = client.clientSendJob(conn, scene, False) - conn.close() - self.report('INFO', "Job sent to master") - except Exception as err: - self.report('ERROR', str(err)) - - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class RENDER_OT_netclientstatus(bpy.types.Operator): - '''Refresh the status of the current jobs''' - bl_idname = "render.netclientstatus" - bl_label = "Client Status" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - netsettings = context.scene.network_render - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - conn.request("GET", "/status") - - response = conn.getresponse() - content = response.read() - print( response.status, response.reason ) - - jobs = (netrender.model.RenderJob.materialize(j) for j in json.loads(str(content, encoding='utf8'))) - - while(len(netsettings.jobs) > 0): - netsettings.jobs.remove(0) - - netrender.jobs = [] - - for j in jobs: - netrender.jobs.append(j) - netsettings.jobs.add() - job = netsettings.jobs[-1] - - j.results = j.framesStatus() # cache frame status - - job.name = j.name - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class RENDER_OT_netclientblacklistslave(bpy.types.Operator): - '''Operator documentation text, will be used for the operator tooltip and python docs.''' - bl_idname = "render.netclientblacklistslave" - bl_label = "Client Blacklist Slave" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - netsettings = context.scene.network_render - - if netsettings.active_slave_index >= 0: - - # deal with data - slave = netrender.slaves.pop(netsettings.active_slave_index) - netrender.blacklist.append(slave) - - # deal with rna - netsettings.slaves_blacklist.add() - netsettings.slaves_blacklist[-1].name = slave.name - - netsettings.slaves.remove(netsettings.active_slave_index) - netsettings.active_slave_index = -1 - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class RENDER_OT_netclientwhitelistslave(bpy.types.Operator): - '''Operator documentation text, will be used for the operator tooltip and python docs.''' - bl_idname = "render.netclientwhitelistslave" - bl_label = "Client Whitelist Slave" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - netsettings = context.scene.network_render - - if netsettings.active_blacklisted_slave_index >= 0: - - # deal with data - slave = netrender.blacklist.pop(netsettings.active_blacklisted_slave_index) - netrender.slaves.append(slave) - - # deal with rna - netsettings.slaves.add() - netsettings.slaves[-1].name = slave.name - - netsettings.slaves_blacklist.remove(netsettings.active_blacklisted_slave_index) - netsettings.active_blacklisted_slave_index = -1 - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - - -class RENDER_OT_netclientslaves(bpy.types.Operator): - '''Refresh status about available Render slaves''' - bl_idname = "render.netclientslaves" - bl_label = "Client Slaves" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - netsettings = context.scene.network_render - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - conn.request("GET", "/slaves") - - response = conn.getresponse() - content = response.read() - print( response.status, response.reason ) - - slaves = (netrender.model.RenderSlave.materialize(s) for s in json.loads(str(content, encoding='utf8'))) - - while(len(netsettings.slaves) > 0): - netsettings.slaves.remove(0) - - netrender.slaves = [] - - for s in slaves: - for i in range(len(netrender.blacklist)): - slave = netrender.blacklist[i] - if slave.id == s.id: - netrender.blacklist[i] = s - netsettings.slaves_blacklist[i].name = s.name - break - else: - netrender.slaves.append(s) - - netsettings.slaves.add() - slave = netsettings.slaves[-1] - slave.name = s.name - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class RENDER_OT_netclientcancel(bpy.types.Operator): - '''Cancel the selected network rendering job.''' - bl_idname = "render.netclientcancel" - bl_label = "Client Cancel" - - @classmethod - def poll(cls, context): - netsettings = context.scene.network_render - return netsettings.active_job_index >= 0 and len(netsettings.jobs) > 0 - - def execute(self, context): - netsettings = context.scene.network_render - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - job = netrender.jobs[netsettings.active_job_index] - - conn.request("POST", cancelURL(job.id), json.dumps({'clear':False})) - - response = conn.getresponse() - response.read() - print( response.status, response.reason ) - - netsettings.jobs.remove(netsettings.active_job_index) - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class RENDER_OT_netclientcancelall(bpy.types.Operator): - '''Cancel all running network rendering jobs.''' - bl_idname = "render.netclientcancelall" - bl_label = "Client Cancel All" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - netsettings = context.scene.network_render - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - conn.request("POST", "/clear", json.dumps({'clear':False})) - - response = conn.getresponse() - response.read() - print( response.status, response.reason ) - - while(len(netsettings.jobs) > 0): - netsettings.jobs.remove(0) - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class netclientdownload(bpy.types.Operator): - '''Download render results from the network''' - bl_idname = "render.netclientdownload" - bl_label = "Client Download" - - @classmethod - def poll(cls, context): - netsettings = context.scene.network_render - return netsettings.active_job_index >= 0 and len(netsettings.jobs) > netsettings.active_job_index - - def execute(self, context): - netsettings = context.scene.network_render - rd = context.scene.render - - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - job_id = netrender.jobs[netsettings.active_job_index].id - - conn.request("GET", "/status", headers={"job-id":job_id}) - - response = conn.getresponse() - - if response.status != http.client.OK: - self.report('ERROR', "Job ID %i not defined on master" % job_id) - return {'ERROR'} - - content = response.read() - - job = netrender.model.RenderJob.materialize(json.loads(str(content, encoding='utf8'))) - - conn.close() - - finished_frames = [] - - nb_error = 0 - nb_missing = 0 - - for frame in job.frames: - if frame.status == DONE: - finished_frames.append(frame.number) - elif frame.status == ERROR: - nb_error += 1 - else: - nb_missing += 1 - - if not finished_frames: - return - - frame_ranges = [] - - first = None - last = None - - for i in range(len(finished_frames)): - current = finished_frames[i] - - if not first: - first = current - last = current - elif last + 1 == current: - last = current - - if last + 1 < current or i + 1 == len(finished_frames): - if first < last: - frame_ranges.append((first, last)) - else: - frame_ranges.append((first,)) - - first = current - last = current - - getResults(netsettings.server_address, netsettings.server_port, job_id, job.resolution[0], job.resolution[1], job.resolution[2], frame_ranges) - - if nb_error and nb_missing: - self.report('ERROR', "Results downloaded but skipped %i frames with errors and %i unfinished frames" % (nb_error, nb_missing)) - elif nb_error: - self.report('ERROR', "Results downloaded but skipped %i frames with errors" % nb_error) - elif nb_missing: - self.report('WARNING', "Results downloaded but skipped %i unfinished frames" % nb_missing) - else: - self.report('INFO', "All results downloaded") - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class netclientscan(bpy.types.Operator): - '''Listen on network for master server broadcasting its address and port.''' - bl_idname = "render.netclientscan" - bl_label = "Client Scan" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - address, port = clientScan(self.report) - - if address: - scene = context.scene - netsettings = scene.network_render - netsettings.server_address = address - netsettings.server_port = port - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class netclientvcsguess(bpy.types.Operator): - '''Guess VCS setting for the current file''' - bl_idname = "render.netclientvcsguess" - bl_label = "VCS Guess" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - netsettings = context.scene.network_render - - system = versioning.SYSTEMS.get(netsettings.vcs_system, None) - - if system: - wpath, name = os.path.split(os.path.abspath(bpy.data.filepath)) - - rpath = system.path(wpath) - revision = system.revision(wpath) - - netsettings.vcs_wpath = wpath - netsettings.vcs_rpath = rpath - netsettings.vcs_revision = revision - - - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - - -class netclientweb(bpy.types.Operator): - '''Open new window with information about running rendering jobs''' - bl_idname = "render.netclientweb" - bl_label = "Open Master Monitor" - - @classmethod - def poll(cls, context): - netsettings = context.scene.network_render - return netsettings.server_address != "[default]" - - def execute(self, context): - netsettings = context.scene.network_render - - - # open connection to make sure server exists - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - conn.close() - - webbrowser.open("http://%s:%i" % (netsettings.server_address, netsettings.server_port)) - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) diff --git a/release/scripts/io/netrender/repath.py b/release/scripts/io/netrender/repath.py deleted file mode 100644 index 3ac9636b628..00000000000 --- a/release/scripts/io/netrender/repath.py +++ /dev/null @@ -1,150 +0,0 @@ -# ##### 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 sys, os -import subprocess - -import bpy - -from netrender.utils import * -import netrender.model - -BLENDER_PATH = sys.argv[0] - -def reset(job): - main_file = job.files[0] - - job_full_path = main_file.filepath - - if os.path.exists(job_full_path + ".bak"): - os.remove(job_full_path) # repathed file - os.renames(job_full_path + ".bak", job_full_path) - -def update(job): - paths = [] - - main_file = job.files[0] - - job_full_path = main_file.filepath - - - path, ext = os.path.splitext(job_full_path) - - new_path = path + ".remap" + ext - - # Disable for now. Partial repath should work anyway - #all = main_file.filepath != main_file.original_path - all = False - - for rfile in job.files[1:]: - if all or rfile.original_path != rfile.filepath: - paths.append(rfile.original_path) - paths.append(rfile.filepath) - - # Only update if needed - if paths: - process = subprocess.Popen([BLENDER_PATH, "-b", "-noaudio", job_full_path, "-P", __file__, "--", new_path] + paths, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - process.wait() - - os.renames(job_full_path, job_full_path + ".bak") - os.renames(new_path, job_full_path) - -def process(paths): - def processPointCache(point_cache): - point_cache.use_external = False - - def processFluid(fluid): - new_path = path_map.get(fluid.filepath, None) - if new_path: - fluid.path = new_path - - path_map = {} - for i in range(0, len(paths), 2): - # special case for point cache - if paths[i].endswith(".bphys"): - pass # Don't need them in the map, they all use the default external path - # NOTE: This is probably not correct all the time, need to be fixed. - # special case for fluids - elif paths[i].endswith(".bobj.gz"): - path_map[os.path.split(paths[i])[0]] = os.path.split(paths[i+1])[0] - else: - path_map[os.path.split(paths[i])[1]] = paths[i+1] - - # TODO original paths aren't really the orignal path (they are the normalized path - # so we repath using the filenames only. - - ########################### - # LIBRARIES - ########################### - for lib in bpy.data.libraries: - file_path = bpy.path.abspath(lib.filepath) - new_path = path_map.get(os.path.split(file_path)[1], None) - if new_path: - lib.filepath = new_path - - ########################### - # IMAGES - ########################### - for image in bpy.data.images: - if image.source == "FILE" and not image.packed_file: - file_path = bpy.path.abspath(image.filepath) - new_path = path_map.get(os.path.split(file_path)[1], None) - if new_path: - image.filepath = new_path - - - ########################### - # FLUID + POINT CACHE - ########################### - for object in bpy.data.objects: - for modifier in object.modifiers: - if modifier.type == 'FLUID_SIMULATION' and modifier.settings.type == "DOMAIN": - processFluid(settings) - elif modifier.type == "CLOTH": - processPointCache(modifier.point_cache) - elif modifier.type == "SOFT_BODY": - processPointCache(modifier.point_cache) - elif modifier.type == "SMOKE" and modifier.smoke_type == "TYPE_DOMAIN": - processPointCache(modifier.domain_settings.point_cache_low) - if modifier.domain_settings.use_high_resolution: - processPointCache(modifier.domain_settings.point_cache_high) - elif modifier.type == "MULTIRES" and modifier.is_external: - file_path = bpy.path.abspath(modifier.filepath) - new_path = path_map.get(file_path, None) - if new_path: - modifier.filepath = new_path - - # particles modifier are stupid and don't contain data - # we have to go through the object property - for psys in object.particle_systems: - processPointCache(psys.point_cache) - - -if __name__ == "__main__": - try: - i = sys.argv.index("--") - except: - i = 0 - - if i: - new_path = sys.argv[i+1] - args = sys.argv[i+2:] - - process(args) - - bpy.ops.wm.save_as_mainfile(filepath=new_path, check_existing=False) diff --git a/release/scripts/io/netrender/slave.py b/release/scripts/io/netrender/slave.py deleted file mode 100644 index b05de0afeb9..00000000000 --- a/release/scripts/io/netrender/slave.py +++ /dev/null @@ -1,349 +0,0 @@ -# ##### 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 sys, os, platform, shutil -import http, http.client, http.server, urllib -import subprocess, time -import json - -import bpy - -from netrender.utils import * -import netrender.model -import netrender.repath -import netrender.thumbnail as thumbnail - -BLENDER_PATH = sys.argv[0] - -CANCEL_POLL_SPEED = 2 -MAX_TIMEOUT = 10 -INCREMENT_TIMEOUT = 1 -MAX_CONNECT_TRY = 10 -try: - system = platform.system() -except UnicodeDecodeError: - import sys - system = sys.platform - -if system in ('Windows', 'win32') and platform.version() >= '5': # Error mode is only available on Win2k or higher, that's version 5 - import ctypes - def SetErrorMode(): - val = ctypes.windll.kernel32.SetErrorMode(0x0002) - ctypes.windll.kernel32.SetErrorMode(val | 0x0002) - return val - - def RestoreErrorMode(val): - ctypes.windll.kernel32.SetErrorMode(val) -else: - def SetErrorMode(): - return 0 - - def RestoreErrorMode(val): - pass - -def clearSlave(path): - shutil.rmtree(path) - -def slave_Info(): - sysname, nodename, release, version, machine, processor = platform.uname() - slave = netrender.model.RenderSlave() - slave.name = nodename - slave.stats = sysname + " " + release + " " + machine + " " + processor - return slave - -def testCancel(conn, job_id, frame_number): - conn.request("HEAD", "/status", headers={"job-id":job_id, "job-frame": str(frame_number)}) - - # canceled if job isn't found anymore - if responseStatus(conn) == http.client.NO_CONTENT: - return True - else: - return False - -def testFile(conn, job_id, slave_id, rfile, JOB_PREFIX, main_path = None): - job_full_path = prefixPath(JOB_PREFIX, rfile.filepath, main_path) - - found = os.path.exists(job_full_path) - - if found and rfile.signature != None: - found_signature = hashFile(job_full_path) - found = found_signature == rfile.signature - - if not found: - print("Found file %s at %s but signature mismatch!" % (rfile.filepath, job_full_path)) - job_full_path = prefixPath(JOB_PREFIX, rfile.filepath, main_path, force = True) - - if not found: - # Force prefix path if not found - job_full_path = prefixPath(JOB_PREFIX, rfile.filepath, main_path, force = True) - temp_path = os.path.join(JOB_PREFIX, "slave.temp") - conn.request("GET", fileURL(job_id, rfile.index), headers={"slave-id":slave_id}) - response = conn.getresponse() - - if response.status != http.client.OK: - return None # file for job not returned by server, need to return an error code to server - - f = open(temp_path, "wb") - buf = response.read(1024) - - while buf: - f.write(buf) - buf = response.read(1024) - - f.close() - - os.renames(temp_path, job_full_path) - - rfile.filepath = job_full_path - - return job_full_path - -def breakable_timeout(timeout): - for i in range(timeout): - time.sleep(1) - if engine.test_break(): - break - -def render_slave(engine, netsettings, threads): - timeout = 1 - - bisleep = BreakableIncrementedSleep(INCREMENT_TIMEOUT, 1, MAX_TIMEOUT, engine.test_break) - - engine.update_stats("", "Network render node initiation") - - conn = clientConnection(netsettings.server_address, netsettings.server_port) - - if not conn: - timeout = 1 - print("Connection failed, will try connecting again at most %i times" % MAX_CONNECT_TRY) - bisleep.reset() - - for i in range(MAX_CONNECT_TRY): - bisleep.sleep() - - conn = clientConnection(netsettings.server_address, netsettings.server_port) - - if conn or engine.test_break(): - break - - print("Retry %i failed, waiting %is before retrying" % (i + 1, bisleep.current)) - - if conn: - conn.request("POST", "/slave", json.dumps(slave_Info().serialize())) - response = conn.getresponse() - response.read() - - slave_id = response.getheader("slave-id") - - NODE_PREFIX = os.path.join(bpy.path.abspath(netsettings.path), "slave_" + slave_id) - if not os.path.exists(NODE_PREFIX): - os.mkdir(NODE_PREFIX) - - engine.update_stats("", "Network render connected to master, waiting for jobs") - - while not engine.test_break(): - conn.request("GET", "/job", headers={"slave-id":slave_id}) - response = conn.getresponse() - - if response.status == http.client.OK: - bisleep.reset() - - job = netrender.model.RenderJob.materialize(json.loads(str(response.read(), encoding='utf8'))) - engine.update_stats("", "Network render processing job from master") - - JOB_PREFIX = os.path.join(NODE_PREFIX, "job_" + job.id) - if not os.path.exists(JOB_PREFIX): - os.mkdir(JOB_PREFIX) - - # set tempdir for fsaa temp files - # have to set environ var because render is done in a subprocess and that's the easiest way to propagate the setting - os.environ["TMP"] = JOB_PREFIX - - - if job.type == netrender.model.JOB_BLENDER: - job_path = job.files[0].filepath # path of main file - main_path, main_file = os.path.split(job_path) - - job_full_path = testFile(conn, job.id, slave_id, job.files[0], JOB_PREFIX) - print("Fullpath", job_full_path) - print("File:", main_file, "and %i other files" % (len(job.files) - 1,)) - - for rfile in job.files[1:]: - testFile(conn, job.id, slave_id, rfile, JOB_PREFIX, main_path) - print("\t", rfile.filepath) - - netrender.repath.update(job) - - engine.update_stats("", "Render File "+ main_file+ " for job "+ job.id) - elif job.type == netrender.model.JOB_VCS: - if not job.version_info: - # Need to return an error to server, incorrect job type - pass - - job_path = job.files[0].filepath # path of main file - main_path, main_file = os.path.split(job_path) - - job.version_info.update() - - # For VCS jobs, file path is relative to the working copy path - job_full_path = os.path.join(job.version_info.wpath, job_path) - - engine.update_stats("", "Render File "+ main_file+ " for job "+ job.id) - - # announce log to master - logfile = netrender.model.LogFile(job.id, slave_id, [frame.number for frame in job.frames]) - conn.request("POST", "/log", bytes(json.dumps(logfile.serialize()), encoding='utf8')) - response = conn.getresponse() - response.read() - - - first_frame = job.frames[0].number - - # start render - start_t = time.time() - - if job.rendersWithBlender(): - frame_args = [] - - for frame in job.frames: - print("frame", frame.number) - frame_args += ["-f", str(frame.number)] - - val = SetErrorMode() - process = subprocess.Popen([BLENDER_PATH, "-b", "-noaudio", job_full_path, "-t", str(threads), "-o", os.path.join(JOB_PREFIX, "######"), "-E", "BLENDER_RENDER", "-F", "MULTILAYER"] + frame_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - RestoreErrorMode(val) - elif job.type == netrender.model.JOB_PROCESS: - command = job.frames[0].command - val = SetErrorMode() - process = subprocess.Popen(command.split(" "), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - RestoreErrorMode(val) - - headers = {"slave-id":slave_id} - - cancelled = False - stdout = bytes() - run_t = time.time() - while not cancelled and process.poll() is None: - stdout += process.stdout.read(1024) - current_t = time.time() - cancelled = engine.test_break() - if current_t - run_t > CANCEL_POLL_SPEED: - - # update logs if needed - if stdout: - # (only need to update on one frame, they are linked - conn.request("PUT", logURL(job.id, first_frame), stdout, headers=headers) - response = conn.getresponse() - response.read() - - # Also output on console - if netsettings.use_slave_output_log: - print(str(stdout, encoding='utf8'), end="") - - stdout = bytes() - - run_t = current_t - if testCancel(conn, job.id, first_frame): - cancelled = True - - if job.type == netrender.model.JOB_BLENDER: - netrender.repath.reset(job) - - # read leftovers if needed - stdout += process.stdout.read() - - if cancelled: - # kill process if needed - if process.poll() is None: - try: - process.terminate() - except OSError: - pass - continue # to next frame - - # flush the rest of the logs - if stdout: - # Also output on console - if netsettings.use_slave_thumb: - print(str(stdout, encoding='utf8'), end="") - - # (only need to update on one frame, they are linked - conn.request("PUT", logURL(job.id, first_frame), stdout, headers=headers) - if responseStatus(conn) == http.client.NO_CONTENT: - continue - - total_t = time.time() - start_t - - avg_t = total_t / len(job.frames) - - status = process.returncode - - print("status", status) - - headers = {"job-id":job.id, "slave-id":slave_id, "job-time":str(avg_t)} - - - if status == 0: # non zero status is error - headers["job-result"] = str(DONE) - for frame in job.frames: - headers["job-frame"] = str(frame.number) - if job.hasRenderResult(): - # send image back to server - - filename = os.path.join(JOB_PREFIX, "%06d.exr" % frame.number) - - # thumbnail first - if netsettings.use_slave_thumb: - thumbname = thumbnail.generate(filename) - - if thumbname: - f = open(thumbname, 'rb') - conn.request("PUT", "/thumb", f, headers=headers) - f.close() - responseStatus(conn) - - f = open(filename, 'rb') - conn.request("PUT", "/render", f, headers=headers) - f.close() - if responseStatus(conn) == http.client.NO_CONTENT: - continue - - elif job.type == netrender.model.JOB_PROCESS: - conn.request("PUT", "/render", headers=headers) - if responseStatus(conn) == http.client.NO_CONTENT: - continue - else: - headers["job-result"] = str(ERROR) - for frame in job.frames: - headers["job-frame"] = str(frame.number) - # send error result back to server - conn.request("PUT", "/render", headers=headers) - if responseStatus(conn) == http.client.NO_CONTENT: - continue - - engine.update_stats("", "Network render connected to master, waiting for jobs") - else: - bisleep.sleep() - - conn.close() - - if netsettings.use_slave_clear: - clearSlave(NODE_PREFIX) - -if __name__ == "__main__": - pass diff --git a/release/scripts/io/netrender/thumbnail.py b/release/scripts/io/netrender/thumbnail.py deleted file mode 100644 index 2ead6e82745..00000000000 --- a/release/scripts/io/netrender/thumbnail.py +++ /dev/null @@ -1,81 +0,0 @@ -# ##### 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 sys, os -import subprocess - -import bpy - -def generate(filename, external=True): - if external: - process = subprocess.Popen([sys.argv[0], "-b", "-noaudio", "-P", __file__, "--", filename], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - while process.poll() is None: - process.stdout.read(1024) # empty buffer to be sure - process.stdout.read() - - return _thumbname(filename) - else: - return _internal(filename) - -def _thumbname(filename): - root = os.path.splitext(filename)[0] - return root + ".jpg" - -def _internal(filename): - imagename = os.path.split(filename)[1] - thumbname = _thumbname(filename) - - if os.path.exists(thumbname): - return thumbname - - if bpy: - scene = bpy.data.scenes[0] # FIXME, this is dodgy! - scene.render.file_format = "JPEG" - scene.render.file_quality = 90 - - # remove existing image, if there's a leftover (otherwise open changes the name) - if imagename in bpy.data.images: - img = bpy.data.images[imagename] - bpy.data.images.remove(img) - - bpy.ops.image.open(filepath=filename) - img = bpy.data.images[imagename] - - img.save_render(thumbname, scene=scene) - - img.user_clear() - bpy.data.images.remove(img) - - try: - process = subprocess.Popen(["convert", thumbname, "-resize", "300x300", thumbname]) - process.wait() - return thumbname - except Exception as exp: - print("Error while generating thumbnail") - print(exp) - - return None - -if __name__ == "__main__": - import bpy - try: - start = sys.argv.index("--") + 1 - except ValueError: - start = 0 - for filename in sys.argv[start:]: - generate(filename, external=False) diff --git a/release/scripts/io/netrender/ui.py b/release/scripts/io/netrender/ui.py deleted file mode 100644 index ebc7b5807e8..00000000000 --- a/release/scripts/io/netrender/ui.py +++ /dev/null @@ -1,543 +0,0 @@ -# ##### 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 sys, os -import http, http.client, http.server, urllib -import subprocess, shutil, time, hashlib - -import netrender -import netrender.slave as slave -import netrender.master as master - -from netrender.utils import * - -VERSION = b"0.3" - -PATH_PREFIX = "/tmp/" - -QUEUED = 0 -DISPATCHED = 1 -DONE = 2 -ERROR = 3 - -LAST_ADDRESS_TEST = 0 - -def base_poll(cls, context): - rd = context.scene.render - return (rd.use_game_engine==False) and (rd.engine in cls.COMPAT_ENGINES) - - -def init_file(): - if netrender.init_file != bpy.data.filepath: - netrender.init_file = bpy.data.filepath - netrender.init_data = True - netrender.valid_address = False - -def init_data(netsettings): - init_file() - - if netrender.init_data: - netrender.init_data = False - - netsettings.active_slave_index = 0 - while(len(netsettings.slaves) > 0): - netsettings.slaves.remove(0) - - netsettings.active_blacklisted_slave_index = 0 - while(len(netsettings.slaves_blacklist) > 0): - netsettings.slaves_blacklist.remove(0) - - netsettings.active_job_index = 0 - while(len(netsettings.jobs) > 0): - netsettings.jobs.remove(0) - -def verify_address(netsettings): - global LAST_ADDRESS_TEST - init_file() - - if LAST_ADDRESS_TEST + 30 < time.time(): - LAST_ADDRESS_TEST = time.time() - - try: - conn = clientConnection(netsettings.server_address, netsettings.server_port, scan = False, timeout = 1) - except: - conn = None - - if conn: - netrender.valid_address = True - conn.close() - else: - netrender.valid_address = False - - return netrender.valid_address - -class NeedValidAddress(): - @classmethod - def poll(cls, context): - return super().poll(context) and verify_address(context.scene.network_render) - -class NetRenderButtonsPanel(): - 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 - - @classmethod - def poll(cls, context): - rd = context.scene.render - return rd.engine == 'NET_RENDER' and rd.use_game_engine == False - -# Setting panel, use in the scene for now. -class RENDER_PT_network_settings(NetRenderButtonsPanel, bpy.types.Panel): - bl_label = "Network Settings" - COMPAT_ENGINES = {'NET_RENDER'} - - @classmethod - def poll(cls, context): - return super().poll(context) - - def draw(self, context): - layout = self.layout - - netsettings = context.scene.network_render - - verify_address(netsettings) - - layout.prop(netsettings, "mode", expand=True) - - if netsettings.mode in ("RENDER_MASTER", "RENDER_SLAVE"): - layout.operator("render.netclientstart", icon='PLAY') - - layout.prop(netsettings, "path") - - split = layout.split(percentage=0.7) - - col = split.column() - col.label(text="Server Address:") - col.prop(netsettings, "server_address", text="") - - col = split.column() - col.label(text="Port:") - col.prop(netsettings, "server_port", text="") - - if netsettings.mode != "RENDER_MASTER": - layout.operator("render.netclientscan", icon='FILE_REFRESH', text="") - - if not netrender.valid_address: - layout.label(text="No master at specified address") - - layout.operator("render.netclientweb", icon='QUESTION') - -class RENDER_PT_network_slave_settings(NetRenderButtonsPanel, bpy.types.Panel): - bl_label = "Slave Settings" - COMPAT_ENGINES = {'NET_RENDER'} - - @classmethod - def poll(cls, context): - scene = context.scene - return super().poll(context) and scene.network_render.mode == "RENDER_SLAVE" - - def draw(self, context): - layout = self.layout - - rd = context.scene.render - netsettings = context.scene.network_render - - layout.prop(netsettings, "use_slave_clear") - layout.prop(netsettings, "use_slave_thumb") - layout.prop(netsettings, "use_slave_output_log") - layout.label(text="Threads:") - layout.prop(rd, "threads_mode", expand=True) - - col = layout.column() - col.enabled = rd.threads_mode == 'FIXED' - col.prop(rd, "threads") - -class RENDER_PT_network_master_settings(NetRenderButtonsPanel, bpy.types.Panel): - bl_label = "Master Settings" - COMPAT_ENGINES = {'NET_RENDER'} - - @classmethod - def poll(cls, context): - scene = context.scene - return super().poll(context) and scene.network_render.mode == "RENDER_MASTER" - - def draw(self, context): - layout = self.layout - - netsettings = context.scene.network_render - - layout.prop(netsettings, "use_master_broadcast") - layout.prop(netsettings, "use_master_clear") - -class RENDER_PT_network_job(NetRenderButtonsPanel, bpy.types.Panel): - bl_label = "Job Settings" - COMPAT_ENGINES = {'NET_RENDER'} - - @classmethod - def poll(cls, context): - scene = context.scene - return super().poll(context) and scene.network_render.mode == "RENDER_CLIENT" - - def draw(self, context): - layout = self.layout - - netsettings = context.scene.network_render - - verify_address(netsettings) - - if netsettings.server_address != "[default]": - layout.operator("render.netclientanim", icon='RENDER_ANIMATION') - layout.operator("render.netclientsend", icon='FILE_BLEND') - layout.operator("render.netclientsendframe", icon='RENDER_STILL') - if netsettings.job_id: - row = layout.row() - row.operator("render.render", text="Get Image", icon='RENDER_STILL') - row.operator("render.render", text="Get Animation", icon='RENDER_ANIMATION').animation = True - - split = layout.split(percentage=0.3) - - col = split.column() - col.label(text="Type:") - col.label(text="Name:") - col.label(text="Category:") - - col = split.column() - col.prop(netsettings, "job_type", text="") - col.prop(netsettings, "job_name", text="") - col.prop(netsettings, "job_category", text="") - - row = layout.row() - row.prop(netsettings, "priority") - row.prop(netsettings, "chunks") - -class RENDER_PT_network_job_vcs(NetRenderButtonsPanel, bpy.types.Panel): - bl_label = "VCS Job Settings" - COMPAT_ENGINES = {'NET_RENDER'} - - @classmethod - def poll(cls, context): - scene = context.scene - return (super().poll(context) - and scene.network_render.mode == "RENDER_CLIENT" - and scene.network_render.job_type == "JOB_VCS") - - def draw(self, context): - layout = self.layout - - netsettings = context.scene.network_render - - layout.operator("render.netclientvcsguess", icon='FILE_REFRESH', text="") - - layout.prop(netsettings, "vcs_system") - layout.prop(netsettings, "vcs_revision") - layout.prop(netsettings, "vcs_rpath") - layout.prop(netsettings, "vcs_wpath") - -class RENDER_PT_network_slaves(NeedValidAddress, NetRenderButtonsPanel, bpy.types.Panel): - bl_label = "Slaves Status" - COMPAT_ENGINES = {'NET_RENDER'} - - @classmethod - def poll(cls, context): - netsettings = context.scene.network_render - return super().poll(context) and netsettings.mode == "RENDER_CLIENT" - - def draw(self, context): - layout = self.layout - - netsettings = context.scene.network_render - - row = layout.row() - row.template_list(netsettings, "slaves", netsettings, "active_slave_index", rows=2) - - sub = row.column(align=True) - sub.operator("render.netclientslaves", icon='FILE_REFRESH', text="") - sub.operator("render.netclientblacklistslave", icon='ZOOMOUT', text="") - - if len(netrender.slaves) > netsettings.active_slave_index >= 0: - layout.separator() - - slave = netrender.slaves[netsettings.active_slave_index] - - layout.label(text="Name: " + slave.name) - layout.label(text="Address: " + slave.address[0]) - layout.label(text="Seen: " + time.ctime(slave.last_seen)) - layout.label(text="Stats: " + slave.stats) - -class RENDER_PT_network_slaves_blacklist(NeedValidAddress, NetRenderButtonsPanel, bpy.types.Panel): - bl_label = "Slaves Blacklist" - COMPAT_ENGINES = {'NET_RENDER'} - - @classmethod - def poll(cls, context): - netsettings = context.scene.network_render - return super().poll(context) and netsettings.mode == "RENDER_CLIENT" - - def draw(self, context): - layout = self.layout - - netsettings = context.scene.network_render - - row = layout.row() - row.template_list(netsettings, "slaves_blacklist", netsettings, "active_blacklisted_slave_index", rows=2) - - sub = row.column(align=True) - sub.operator("render.netclientwhitelistslave", icon='ZOOMOUT', text="") - - if len(netrender.blacklist) > netsettings.active_blacklisted_slave_index >= 0: - layout.separator() - - slave = netrender.blacklist[netsettings.active_blacklisted_slave_index] - - layout.label(text="Name: " + slave.name) - layout.label(text="Address: " + slave.address[0]) - layout.label(text="Seen: " + time.ctime(slave.last_seen)) - layout.label(text="Stats: " + slave.stats) - -class RENDER_PT_network_jobs(NeedValidAddress, NetRenderButtonsPanel, bpy.types.Panel): - bl_label = "Jobs" - COMPAT_ENGINES = {'NET_RENDER'} - - @classmethod - def poll(cls, context): - netsettings = context.scene.network_render - return super().poll(context) and netsettings.mode == "RENDER_CLIENT" - - def draw(self, context): - layout = self.layout - - netsettings = context.scene.network_render - - row = layout.row() - row.template_list(netsettings, "jobs", netsettings, "active_job_index", rows=2) - - sub = row.column(align=True) - sub.operator("render.netclientstatus", icon='FILE_REFRESH', text="") - sub.operator("render.netclientcancel", icon='ZOOMOUT', text="") - sub.operator("render.netclientcancelall", icon='PANEL_CLOSE', text="") - sub.operator("render.netclientdownload", icon='RENDER_ANIMATION', text="") - - if len(netrender.jobs) > netsettings.active_job_index >= 0: - layout.separator() - - job = netrender.jobs[netsettings.active_job_index] - - layout.label(text="Name: %s" % job.name) - layout.label(text="Length: %04i" % len(job)) - layout.label(text="Done: %04i" % job.results[DONE]) - layout.label(text="Error: %04i" % job.results[ERROR]) - -import properties_render -class RENDER_PT_network_output(NeedValidAddress, NetRenderButtonsPanel, bpy.types.Panel): - bl_label = "Output" - COMPAT_ENGINES = {'NET_RENDER'} - - @classmethod - def poll(cls, context): - netsettings = context.scene.network_render - return super().poll(context) and netsettings.mode == "RENDER_CLIENT" - - draw = properties_render.RENDER_PT_output.draw - - -def addProperties(): - class NetRenderSettings(bpy.types.PropertyGroup): - pass - - class NetRenderSlave(bpy.types.PropertyGroup): - pass - - class NetRenderJob(bpy.types.PropertyGroup): - pass - - bpy.utils.register_class(NetRenderSettings) - bpy.utils.register_class(NetRenderSlave) - bpy.utils.register_class(NetRenderJob) - - from bpy.props import PointerProperty, StringProperty, BoolProperty, EnumProperty, IntProperty, CollectionProperty - bpy.types.Scene.network_render = PointerProperty(type=NetRenderSettings, name="Network Render", description="Network Render Settings") - - NetRenderSettings.server_address = StringProperty( - name="Server address", - description="IP or name of the master render server", - maxlen = 128, - default = "[default]") - - NetRenderSettings.server_port = IntProperty( - name="Server port", - description="port of the master render server", - default = 8000, - min=1, - max=65535) - - NetRenderSettings.use_master_broadcast = BoolProperty( - name="Broadcast", - description="broadcast master server address on local network", - default = True) - - NetRenderSettings.use_slave_clear = BoolProperty( - name="Clear on exit", - description="delete downloaded files on exit", - default = True) - - NetRenderSettings.use_slave_thumb = BoolProperty( - name="Generate thumbnails", - description="Generate thumbnails on slaves instead of master", - default = False) - - NetRenderSettings.use_slave_output_log = BoolProperty( - name="Output render log on console", - description="Output render text log to console as well as sending it to the master", - default = True) - - NetRenderSettings.use_master_clear = BoolProperty( - name="Clear on exit", - description="delete saved files on exit", - default = False) - - default_path = os.environ.get("TEMP") - - if not default_path: - if os.name == 'nt': - default_path = "c:/tmp/" - else: - default_path = "/tmp/" - elif not default_path.endswith(os.sep): - default_path += os.sep - - NetRenderSettings.path = StringProperty( - name="Path", - description="Path for temporary files", - maxlen = 128, - default = default_path, - subtype='FILE_PATH') - - NetRenderSettings.job_type = EnumProperty( - items=( - ("JOB_BLENDER", "Blender", "Standard Blender Job"), - ("JOB_PROCESS", "Process", "Custom Process Job"), - ("JOB_VCS", "VCS", "Version Control System Managed Job"), - ), - name="Job Type", - description="Type of render job", - default="JOB_BLENDER") - - NetRenderSettings.job_name = StringProperty( - name="Job name", - description="Name of the job", - maxlen = 128, - default = "[default]") - - NetRenderSettings.job_category = StringProperty( - name="Job category", - description="Category of the job", - maxlen = 128, - default = "") - - NetRenderSettings.chunks = IntProperty( - name="Chunks", - description="Number of frame to dispatch to each slave in one chunk", - default = 5, - min=1, - max=65535) - - NetRenderSettings.priority = IntProperty( - name="Priority", - description="Priority of the job", - default = 1, - min=1, - max=10) - - NetRenderSettings.vcs_wpath = StringProperty( - name="Working Copy", - description="Path of the local working copy", - maxlen = 1024, - default = "") - - NetRenderSettings.vcs_rpath = StringProperty( - name="Remote Path", - description="Path of the server copy (protocol specific)", - maxlen = 1024, - default = "") - - NetRenderSettings.vcs_revision = StringProperty( - name="Revision", - description="Revision for this job", - maxlen = 256, - default = "") - - NetRenderSettings.vcs_system = StringProperty( - name="VCS", - description="Version Control System", - maxlen = 64, - default = "Subversion") - - NetRenderSettings.job_id = StringProperty( - name="Network job id", - description="id of the last sent render job", - maxlen = 64, - default = "") - - NetRenderSettings.active_slave_index = IntProperty( - name="Index of the active slave", - description="", - default = -1, - min= -1, - max=65535) - - NetRenderSettings.active_blacklisted_slave_index = IntProperty( - name="Index of the active slave", - description="", - default = -1, - min= -1, - max=65535) - - NetRenderSettings.active_job_index = IntProperty( - name="Index of the active job", - description="", - default = -1, - min= -1, - max=65535) - - NetRenderSettings.mode = EnumProperty( - items=( - ("RENDER_CLIENT", "Client", "Act as render client"), - ("RENDER_MASTER", "Master", "Act as render master"), - ("RENDER_SLAVE", "Slave", "Act as render slave"), - ), - name="Network mode", - description="Mode of operation of this instance", - default="RENDER_CLIENT") - - NetRenderSettings.slaves = CollectionProperty(type=NetRenderSlave, name="Slaves", description="") - NetRenderSettings.slaves_blacklist = CollectionProperty(type=NetRenderSlave, name="Slaves Blacklist", description="") - NetRenderSettings.jobs = CollectionProperty(type=NetRenderJob, name="Job List", description="") - - NetRenderSlave.name = StringProperty( - name="Name of the slave", - description="", - maxlen = 64, - default = "") - - NetRenderJob.name = StringProperty( - name="Name of the job", - description="", - maxlen = 128, - default = "") diff --git a/release/scripts/io/netrender/utils.py b/release/scripts/io/netrender/utils.py deleted file mode 100644 index ed9fb2de812..00000000000 --- a/release/scripts/io/netrender/utils.py +++ /dev/null @@ -1,313 +0,0 @@ -# ##### 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 sys, os -import re -import http, http.client, http.server, urllib, socket -import subprocess, shutil, time, hashlib, zlib - -import netrender.model - -try: - import bpy -except: - bpy = None - -VERSION = bytes("1.3", encoding='utf8') - -# Jobs status -JOB_WAITING = 0 # before all data has been entered -JOB_PAUSED = 1 # paused by user -JOB_FINISHED = 2 # finished rendering -JOB_QUEUED = 3 # ready to be dispatched - -JOB_STATUS_TEXT = { - JOB_WAITING: "Waiting", - JOB_PAUSED: "Paused", - JOB_FINISHED: "Finished", - JOB_QUEUED: "Queued" - } - - -# Frames status -QUEUED = 0 -DISPATCHED = 1 -DONE = 2 -ERROR = 3 - -FRAME_STATUS_TEXT = { - QUEUED: "Queued", - DISPATCHED: "Dispatched", - DONE: "Done", - ERROR: "Error" - } - -class DirectoryContext: - def __init__(self, path): - self.path = path - - def __enter__(self): - self.curdir = os.path.abspath(os.curdir) - os.chdir(self.path) - - def __exit__(self, exc_type, exc_value, traceback): - os.chdir(self.curdir) - -class BreakableIncrementedSleep: - def __init__(self, increment, default_timeout, max_timeout, break_fct): - self.increment = increment - self.default = default_timeout - self.max = max_timeout - self.current = self.default - self.break_fct = break_fct - - def reset(self): - self.current = self.default - - def increase(self): - self.current = min(self.current + self.increment, self.max) - - def sleep(self): - for i in range(self.current): - time.sleep(1) - if self.break_fct(): - break - - self.increase() - -def responseStatus(conn): - response = conn.getresponse() - response.read() - return response.status - -def reporting(report, message, errorType = None): - if errorType: - t = 'ERROR' - else: - t = 'INFO' - - if report: - report(t, message) - return None - elif errorType: - raise errorType(message) - else: - return None - -def clientScan(report = None): - try: - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - s.settimeout(30) - - s.bind(('', 8000)) - - buf, address = s.recvfrom(64) - - address = address[0] - port = int(str(buf, encoding='utf8')) - - reporting(report, "Master server found") - - return (address, port) - except socket.timeout: - reporting(report, "No master server on network", IOError) - - return ("", 8000) # return default values - -def clientConnection(address, port, report = None, scan = True, timeout = 5): - if address == "[default]": -# calling operator from python is fucked, scene isn't in context -# if bpy: -# bpy.ops.render.netclientscan() -# else: - if not scan: - return None - - address, port = clientScan() - if address == "": - return None - - try: - conn = http.client.HTTPConnection(address, port, timeout = timeout) - - if conn: - if clientVerifyVersion(conn): - return conn - else: - conn.close() - reporting(report, "Incorrect master version", ValueError) - except BaseException as err: - if report: - report('ERROR', str(err)) - return None - else: - print(err) - return None - -def clientVerifyVersion(conn): - conn.request("GET", "/version") - response = conn.getresponse() - - if response.status != http.client.OK: - conn.close() - return False - - server_version = response.read() - - if server_version != VERSION: - print("Incorrect server version!") - print("expected", str(VERSION, encoding='utf8'), "received", str(server_version, encoding='utf8')) - return False - - return True - -def fileURL(job_id, file_index): - return "/file_%s_%i" % (job_id, file_index) - -def logURL(job_id, frame_number): - return "/log_%s_%i.log" % (job_id, frame_number) - -def renderURL(job_id, frame_number): - return "/render_%s_%i.exr" % (job_id, frame_number) - -def cancelURL(job_id): - return "/cancel_%s" % (job_id) - -def hashFile(path): - f = open(path, "rb") - value = hashData(f.read()) - f.close() - return value - -def hashData(data): - m = hashlib.md5() - m.update(data) - return m.hexdigest() - - -def prefixPath(prefix_directory, file_path, prefix_path, force = False): - if (os.path.isabs(file_path) or - len(file_path) >= 3 and (file_path[1:3] == ":/" or file_path[1:3] == ":\\") or # Windows absolute path don't count as absolute on unix, have to handle them myself - file_path[0] == "/" or file_path[0] == "\\"): # and vice versa - - # if an absolute path, make sure path exists, if it doesn't, use relative local path - full_path = file_path - if force or not os.path.exists(full_path): - p, n = os.path.split(os.path.normpath(full_path)) - - if prefix_path and p.startswith(prefix_path): - if len(prefix_path) < len(p): - directory = os.path.join(prefix_directory, p[len(prefix_path)+1:]) # +1 to remove separator - if not os.path.exists(directory): - os.mkdir(directory) - else: - directory = prefix_directory - full_path = os.path.join(directory, n) - else: - full_path = os.path.join(prefix_directory, n) - else: - full_path = os.path.join(prefix_directory, file_path) - - return full_path - -def getResults(server_address, server_port, job_id, resolution_x, resolution_y, resolution_percentage, frame_ranges): - if bpy.app.debug: - print("=============================================") - print("============= FETCHING RESULTS ==============") - - frame_arguments = [] - for r in frame_ranges: - if len(r) == 2: - frame_arguments.extend(["-s", str(r[0]), "-e", str(r[1]), "-a"]) - else: - frame_arguments.extend(["-f", str(r[0])]) - - filepath = os.path.join(bpy.app.tempdir, "netrender_temp.blend") - bpy.ops.wm.save_as_mainfile(filepath=filepath, copy=True, check_existing=False) - - arguments = [sys.argv[0], "-b", "-noaudio", filepath, "-o", bpy.path.abspath(bpy.context.scene.render.filepath), "-P", __file__] + frame_arguments + ["--", "GetResults", server_address, str(server_port), job_id, str(resolution_x), str(resolution_y), str(resolution_percentage)] - if bpy.app.debug: - print("Starting subprocess:") - print(" ".join(arguments)) - - process = subprocess.Popen(arguments, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - while process.poll() is None: - stdout = process.stdout.read(1024) - if bpy.app.debug: - print(str(stdout, encoding='utf-8'), end="") - - - # read leftovers if needed - stdout = process.stdout.read() - if bpy.app.debug: - print(str(stdout, encoding='utf-8')) - - os.remove(filepath) - - if bpy.app.debug: - print("=============================================") - return - -def _getResults(server_address, server_port, job_id, resolution_x, resolution_y, resolution_percentage): - render = bpy.context.scene.render - - netsettings = bpy.context.scene.network_render - - netsettings.server_address = server_address - netsettings.server_port = int(server_port) - netsettings.job_id = job_id - - render.engine = 'NET_RENDER' - render.resolution_x = int(resolution_x) - render.resolution_y = int(resolution_y) - render.resolution_percentage = int(resolution_percentage) - - render.use_full_sample = False - render.use_compositing = False - render.use_border = False - - -def getFileInfo(filepath, infos): - process = subprocess.Popen([sys.argv[0], "-b", "-noaudio", filepath, "-P", __file__, "--", "FileInfo"] + infos, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - stdout = bytes() - while process.poll() is None: - stdout += process.stdout.read(1024) - - # read leftovers if needed - stdout += process.stdout.read() - - stdout = str(stdout, encoding="utf8") - - values = [eval(v[1:].strip()) for v in stdout.split("\n") if v.startswith("$")] - - return values - - -if __name__ == "__main__": - try: - start = sys.argv.index("--") + 1 - except ValueError: - start = 0 - action, *args = sys.argv[start:] - - if action == "FileInfo": - for info in args: - print("$", eval(info)) - elif action == "GetResults": - _getResults(args[0], args[1], args[2], args[3], args[4], args[5]) diff --git a/release/scripts/io/netrender/versioning.py b/release/scripts/io/netrender/versioning.py deleted file mode 100644 index d4f8522cce8..00000000000 --- a/release/scripts/io/netrender/versioning.py +++ /dev/null @@ -1,72 +0,0 @@ -import sys, os -import re -import subprocess - -from netrender.utils import * - -class AbstractVCS: - name = "ABSTRACT VCS" - def __init__(self): - pass - - def update(self, info): - """update(info) - Update a working copy to the specified revision. - If working copy doesn't exist, do a full get from server to create it. - [info] model.VersioningInfo instance, specifies the working path, remote path and version number.""" - pass - - def revision(self, path): - """revision(path) - return the current revision of the specified working copy path""" - pass - - def path(self, path): - """path(path) - return the remote path of the specified working copy path""" - pass - -class Subversion(AbstractVCS): - name = "Subversion" - def __init__(self): - super().__init__() - self.version_exp = re.compile("([0-9]*)") - self.path_exp = re.compile("URL: (.*)") - - def update(self, info): - if not os.path.exists(info.wpath): - base, folder = os.path.split(info.wpath) - - with DirectoryContext(base): - subprocess.call(["svn", "co", "%s@%s" % (info.rpath, str(info.revision)), folder]) - else: - with DirectoryContext(info.wpath): - subprocess.call(["svn", "up", "--accept", "theirs-full", "-r", str(info.revision)]) - - def revision(self, path): - if not os.path.exists(path): - return - - with DirectoryContext(path): - stdout = subprocess.check_output(["svnversion"]) - - match = self.version_exp.match(str(stdout, encoding="utf-8")) - - if match: - return match.group(1) - - def path(self, path): - if not os.path.exists(path): - return - - with DirectoryContext(path): - stdout = subprocess.check_output(["svn", "info"]) - - match = self.path_exp.search(str(stdout, encoding="utf-8")) - - if match: - return match.group(1) - -SYSTEMS = { - Subversion.name: Subversion() - } |