diff --git a/netrender/__init__.py b/netrender/__init__.py index 1b4812d890ff3435e17f762481d5867454d0c5b6..26551977033670e01dbdd2f26588a70867a12e56 100644 --- a/netrender/__init__.py +++ b/netrender/__init__.py @@ -21,7 +21,7 @@ bl_info = { "name": "Network Renderer", "author": "Martin Poirier", - "version": (1, 7), + "version": (1, 8), "blender": (2, 6, 0), "api": 35011, "location": "Render > Engine > Network Render", diff --git a/netrender/baking.py b/netrender/baking.py index b01d44d7b5e2a30a330e2ef016b5745c099e1714..24d32e850abf812ecc5650aa9d5b1f911c2fd98d 100644 --- a/netrender/baking.py +++ b/netrender/baking.py @@ -17,10 +17,20 @@ # ##### END GPL LICENSE BLOCK ##### import bpy -import sys, subprocess +import sys, subprocess, re + +from netrender.utils import * BLENDER_PATH = sys.argv[0] +def commandToTask(command): + i = command.index("|") + ri = command.rindex("|") + return (command[:i], command[i+1:ri], command[ri+1:]) + +def taskToCommand(task): + return "|".join(task) + def bake(job, tasks): main_file = job.files[0] job_full_path = main_file.filepath @@ -33,43 +43,91 @@ def bake(job, tasks): return process -def process_cache(obj, point_cache): +result_pattern = re.compile("BAKE FILE\[ ([0-9]+) \]: (.*)") +def resultsFromOuput(lines): + results = [] + for line in lines: + match = result_pattern.match(line) + + if match: + task_id = int(match.groups()[0]) + task_filename = match.groups()[1] + + results.append((task_id, task_filename)) + + return results + +def bake_cache(obj, point_cache, task_index): if point_cache.is_baked: bpy.ops.ptcache.free_bake({"point_cache": point_cache}) point_cache.use_disk_cache = True + point_cache.use_external = False bpy.ops.ptcache.bake({"point_cache": point_cache}, bake=True) + + results = cache_results(obj, point_cache) + + print() + + for filename in results: + print("BAKE FILE[", task_index, "]:", filename) + + +def cache_results(obj, point_cache): + name = cacheName(obj, point_cache) + default_path = cachePath(bpy.data.filepath) + + 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_files.append(os.path.join(cache_path, cache_file)) + + cache_files.sort() + + return cache_files + + return [] -def process_generic(obj, index): +def process_generic(obj, index, task_index): modifier = obj.modifiers[index] point_cache = modifier.point_cache - process_cache(obj, point_cache) + bake_cache(obj, point_cache, task_index) -def process_smoke(obj, index): +def process_smoke(obj, index, task_index): modifier = obj.modifiers[index] point_cache = modifier.domain_settings.point_cache - process_cache(obj, point_cache) + bake_cache(obj, point_cache, task_index) -def process_particle(obj, index): +def process_particle(obj, index, task_index): psys = obj.particle_systems[index] point_cache = psys.point_cache - process_cache(obj, point_cache) + bake_cache(obj, point_cache, task_index) -def process_paint(obj, index): +def process_paint(obj, index, task_index): modifier = obj.modifiers[index] for surface in modifier.canvas_settings.canvas_surfaces: - process_cache(obj, surface.point_cache) + bake_cache(obj, surface.point_cache, task_index) -def process_null(obj, index): +def process_null(obj, index, task_index): raise ValueException("No baking possible with arguments: " + " ".join(sys.argv)) -bake_funcs = {} -bake_funcs["CLOTH"] = process_generic -bake_funcs["SOFT_BODY"] = process_generic -bake_funcs["PARTICLE_SYSTEM"] = process_particle -bake_funcs["SMOKE"] = process_smoke -bake_funcs["DYNAMIC_PAINT"] = process_paint +process_funcs = {} +process_funcs["CLOTH"] = process_generic +process_funcs["SOFT_BODY"] = process_generic +process_funcs["PARTICLE_SYSTEM"] = process_particle +process_funcs["SMOKE"] = process_smoke +process_funcs["DYNAMIC_PAINT"] = process_paint if __name__ == "__main__": try: @@ -84,4 +142,4 @@ if __name__ == "__main__": obj = bpy.data.objects[task_args[i+1]] index = int(task_args[i+2]) - bake_funcs.get(bake_type, process_null)(obj, index) + process_funcs.get(bake_type, process_null)(obj, index, i) diff --git a/netrender/balancing.py b/netrender/balancing.py index dde3ad53084137019d95f802d106d76560ef3ac6..24b6fcb2d8b2afae1958a08c323536ebf455d3bc 100644 --- a/netrender/balancing.py +++ b/netrender/balancing.py @@ -149,7 +149,7 @@ class NewJobPriority(PriorityRule): return "Priority to new jobs" def test(self, job): - return job.countFrames(status = DONE) < self.limit + return job.countFrames(status = FRAME_DONE) < self.limit class MinimumTimeBetweenDispatchPriority(PriorityRule): def __init__(self, limit = 10): @@ -166,14 +166,14 @@ class MinimumTimeBetweenDispatchPriority(PriorityRule): 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 + return job.countFrames(status = FRAME_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 + return job.status != JOB_QUEUED or job.countFrames(status = FRAME_QUEUED) == 0 class ExcludeSlavesLimit(ExclusionRule): def __init__(self, count_jobs, count_slaves, limit = 0.75): diff --git a/netrender/client.py b/netrender/client.py index 62eb48559fd2ee6eef8f9270807bce85d9b33a09..18a2302f821a3a7149de073161f35bab5849eea6 100644 --- a/netrender/client.py +++ b/netrender/client.py @@ -224,7 +224,7 @@ def sendJobBaking(conn, scene): for i, task in enumerate(tasks): job.addFrame(i + 1) - job.frames[-1].command = "|".join(task) + job.frames[-1].command = netrender.baking.taskToCommand(task) # try to send path first with ConnectionContext(): diff --git a/netrender/master.py b/netrender/master.py index 599277dc19a6996adde402cd5c1464275ff46378..65ff358394084f388b852945288caa3b7f3cbbd0 100644 --- a/netrender/master.py +++ b/netrender/master.py @@ -20,6 +20,7 @@ import sys, os import http, http.client, http.server, socket, socketserver import shutil, time, hashlib import pickle +import zipfile import select # for select.error import json @@ -131,7 +132,7 @@ class MRenderJob(netrender.model.RenderJob): def testFinished(self): for f in self.frames: - if f.status == QUEUED or f.status == DISPATCHED: + if f.status == FRAME_QUEUED or f.status == FRAME_DISPATCHED: break else: self.status = JOB_FINISHED @@ -175,13 +176,16 @@ class MRenderJob(netrender.model.RenderJob): def getFrames(self): frames = [] for f in self.frames: - if f.status == QUEUED: + if f.status == FRAME_QUEUED: self.last_dispatched = time.time() frames.append(f) if len(frames) >= self.chunks: break return frames + + def getResultPath(self, filename): + return os.path.join(self.save_path, filename) class MRenderFrame(netrender.model.RenderFrame): def __init__(self, frame, command): @@ -189,17 +193,23 @@ class MRenderFrame(netrender.model.RenderFrame): self.number = frame self.slave = None self.time = 0 - self.status = QUEUED + self.status = FRAME_QUEUED self.command = command self.log_path = None + def addDefaultRenderResult(self): + self.results.append(self.getRenderFilename()) + + def getRenderFilename(self): + return "%06d.exr" % self.number + def reset(self, all): - if all or self.status == ERROR: + if all or self.status == FRAME_ERROR: self.log_path = None self.slave = None self.time = 0 - self.status = QUEUED + self.status = FRAME_QUEUED # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= @@ -208,6 +218,7 @@ class MRenderFrame(netrender.model.RenderFrame): # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- file_pattern = re.compile("/file_([a-zA-Z0-9]+)_([0-9]+)") render_pattern = re.compile("/render_([a-zA-Z0-9]+)_([0-9]+).exr") +result_pattern = re.compile("/result_([a-zA-Z0-9]+).zip") 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]+)") @@ -295,18 +306,18 @@ class RenderHandler(http.server.BaseHTTPRequestHandler): frame = job[frame_number] if frame: - if frame.status in (QUEUED, DISPATCHED): + if frame.status in (FRAME_QUEUED, FRAME_DISPATCHED): self.send_head(http.client.ACCEPTED) - elif frame.status == DONE: + elif frame.status == FRAME_DONE: self.server.stats("", "Sending result to client") - filename = os.path.join(job.save_path, "%06d.exr" % frame_number) + filename = job.getResultPath(frame.getRenderFilename()) f = open(filename, 'rb') self.send_head(content = "image/x-exr") shutil.copyfileobj(f, self.wfile) f.close() - elif frame.status == ERROR: + elif frame.status == FRAME_ERROR: self.send_head(http.client.PARTIAL_CONTENT) else: # no such frame @@ -318,6 +329,38 @@ class RenderHandler(http.server.BaseHTTPRequestHandler): # invalid url self.send_head(http.client.NO_CONTENT) # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + elif self.path.startswith("/result"): + match = result_pattern.match(self.path) + + if match: + job_id = match.groups()[0] + + job = self.server.getJobID(job_id) + + if job: + self.server.stats("", "Sending result to client") + + zip_filepath = job.getResultPath("results.zip") + with zipfile.ZipFile(zip_filepath, "w") as zfile: + for frame in job.frames: + if frame.status == FRAME_DONE: + for filename in frame.results: + filepath = job.getResultPath(filename) + + zfile.write(filepath, filename) + + + f = open(zip_filepath, 'rb') + self.send_head(content = "application/x-zip-compressed") + shutil.copyfileobj(f, self.wfile) + f.close() + 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) @@ -331,10 +374,10 @@ class RenderHandler(http.server.BaseHTTPRequestHandler): frame = job[frame_number] if frame: - if frame.status in (QUEUED, DISPATCHED): + if frame.status in (FRAME_QUEUED, FRAME_DISPATCHED): self.send_head(http.client.ACCEPTED) - elif frame.status == DONE: - filename = os.path.join(job.save_path, "%06d.exr" % frame_number) + elif frame.status == FRAME_DONE: + filename = job.getResultPath(frame.getRenderFilename()) thumbname = thumbnail.generate(filename) @@ -346,7 +389,7 @@ class RenderHandler(http.server.BaseHTTPRequestHandler): else: # thumbnail couldn't be generated self.send_head(http.client.PARTIAL_CONTENT) return - elif frame.status == ERROR: + elif frame.status == FRAME_ERROR: self.send_head(http.client.PARTIAL_CONTENT) else: # no such frame @@ -371,7 +414,7 @@ class RenderHandler(http.server.BaseHTTPRequestHandler): frame = job[frame_number] if frame: - if not frame.log_path or frame.status in (QUEUED, DISPATCHED): + if not frame.log_path or frame.status in (FRAME_QUEUED, FRAME_DISPATCHED): self.send_head(http.client.PROCESSING) else: self.server.stats("", "Sending log to client") @@ -440,7 +483,7 @@ class RenderHandler(http.server.BaseHTTPRequestHandler): if job and frames: for f in frames: print("dispatch", f.number) - f.status = DISPATCHED + f.status = FRAME_DISPATCHED f.slave = slave slave.job = job @@ -790,10 +833,11 @@ class RenderHandler(http.server.BaseHTTPRequestHandler): self.send_head(content = None) if job.hasRenderResult(): - if job_result == DONE: - self.write_file(os.path.join(job.save_path, "%06d.exr" % job_frame)) + if job_result == FRAME_DONE: + frame.addDefaultRenderResult() + self.write_file(job.getResultPath(frame.getRenderFilename())) - elif job_result == ERROR: + elif job_result == FRAME_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: @@ -813,6 +857,50 @@ class RenderHandler(http.server.BaseHTTPRequestHandler): else: # invalid slave id self.send_head(http.client.NO_CONTENT) # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + elif self.path == "/result": + self.server.stats("", "Receiving job result") + + 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: + job_result = int(self.headers['job-result']) + job_finished = self.headers['job-finished'] == str(True) + + self.send_head(content = None) + + if job_result == FRAME_DONE: + result_filename = self.headers['result-filename'] + + frame.results.append(result_filename) + self.write_file(job.getResultPath(result_filename)) + + if job_finished: + job_time = float(self.headers['job-time']) + slave.finishedFrame(job_frame) + + frame.status = job_result + frame.time = job_time + + job.testFinished() + 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") @@ -953,7 +1041,7 @@ class RenderMasterServer(socketserver.ThreadingMixIn, http.server.HTTPServer): if slave.job: for f in slave.job_frames: - slave.job[f].status = ERROR + slave.job[f].status = FRAME_ERROR for slave in removed: self.removeSlave(slave) diff --git a/netrender/master_html.py b/netrender/master_html.py index a8e6eaa6d1988a9e8405b193b3f092b1bc49e396..a0414a031f0977202d6f16f55838ca1d2c325ec6 100644 --- a/netrender/master_html.py +++ b/netrender/master_html.py @@ -122,7 +122,7 @@ def get(handler): "usage", "wait", "status", - "length", + "total", "done", "dispatched", "error", @@ -150,13 +150,13 @@ def get(handler): """<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), + "%is" % int(time.time() - job.last_dispatched) if job.status != JOB_FINISHED else "N/A", 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 ""), + results[FRAME_DONE], + results[FRAME_DISPATCHED], + str(results[FRAME_ERROR]) + + """<button title="reset error frames" onclick="request('/reset_%s_0', null);" %s>R</button>""" % (job.id, "disabled=True" if not results[FRAME_ERROR] else ""), "yes" if handler.server.balancer.applyPriorities(job) else "no", "yes" if handler.server.balancer.applyExceptions(job) else "no" ) @@ -231,6 +231,8 @@ def get(handler): rowTable("resolution", "%ix%i at %i%%" % job.resolution) rowTable("tags", ";".join(sorted(job.tags)) if job.tags else "<i>None</i>") + + rowTable("results", link("download all", resultURL(job_id))) endTable() @@ -308,19 +310,32 @@ def get(handler): 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) - ) + + if job.hasRenderResult(): + 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 == FRAME_DONE else " ", + "<img name='thumb%i' title='hide thumbnails' src='' class='thumb' onclick='showThumb(%s, %i)'>" % (frame.number, job.id, frame.number) + ) + else: + headerTable("no", "status", "process time", "slave", "log") + + 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 " " + ) endTable() else: diff --git a/netrender/model.py b/netrender/model.py index c5ee54f0ed8452f5a27cb1238ef8ddbfe7ffb422..ec91587b54bb72e957615a8bf86f7f51cfbf1129 100644 --- a/netrender/model.py +++ b/netrender/model.py @@ -278,7 +278,7 @@ class RenderJob: def __len__(self): return len(self.frames) - def countFrames(self, status=QUEUED): + def countFrames(self, status=FRAME_QUEUED): total = 0 for f in self.frames: if f.status == status: @@ -287,17 +287,17 @@ class RenderJob: return total def countSlaves(self): - return len(set((frame.slave for frame in self.frames if frame.status == DISPATCHED))) + return len(set((frame.slave for frame in self.frames if frame.status == FRAME_DISPATCHED))) def statusText(self): return JOB_STATUS_TEXT[self.status] def framesStatus(self): results = { - QUEUED: 0, - DISPATCHED: 0, - DONE: 0, - ERROR: 0 + FRAME_QUEUED: 0, + FRAME_DISPATCHED: 0, + FRAME_DONE: 0, + FRAME_ERROR: 0 } for frame in self.frames: @@ -375,9 +375,10 @@ class RenderFrame: def __init__(self, number = 0, command = ""): self.number = number self.time = 0 - self.status = QUEUED + self.status = FRAME_QUEUED self.slave = None self.command = command + self.results = [] # List of filename of result files associated with this frame def statusText(self): return FRAME_STATUS_TEXT[self.status] @@ -388,7 +389,8 @@ class RenderFrame: "time": self.time, "status": self.status, "slave": None if not self.slave else self.slave.serialize(), - "command": self.command + "command": self.command, + "results": self.results } @staticmethod @@ -402,5 +404,6 @@ class RenderFrame: frame.status = data["status"] frame.slave = RenderSlave.materialize(data["slave"]) frame.command = data["command"] + frame.results = data["results"] return frame diff --git a/netrender/operators.py b/netrender/operators.py index d6d441badb7d5eb0cf7ca1b8ee78967ea4e4d485..bbfd42c2a4087d640b61bc7cc8054959447854b6 100644 --- a/netrender/operators.py +++ b/netrender/operators.py @@ -410,9 +410,9 @@ class netclientdownload(bpy.types.Operator): nb_missing = 0 for frame in job.frames: - if frame.status == DONE: + if frame.status == FRAME_DONE: finished_frames.append(frame.number) - elif frame.status == ERROR: + elif frame.status == FRAME_ERROR: nb_error += 1 else: nb_missing += 1 diff --git a/netrender/slave.py b/netrender/slave.py index 367688373ca3d4c29aa102b22c7809b5eb69e6ed..361b78fce6dda221436815737fc90ece455c141c 100644 --- a/netrender/slave.py +++ b/netrender/slave.py @@ -225,9 +225,7 @@ def render_slave(engine, netsettings, threads): elif job.subtype == netrender.model.JOB_SUB_BAKING: tasks = [] for frame in job.frames: - i = frame.command.index("|") - ri = frame.command.rindex("|") - tasks.append((frame.command[:i], frame.command[i+1:ri], frame.command[ri+1:])) + tasks.append(netrender.baking.commandToTask(frame.command)) with NoErrorDialogContext(): process = netrender.baking.bake(job, tasks) @@ -238,10 +236,13 @@ def render_slave(engine, netsettings, threads): process = subprocess.Popen(command.split(" "), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) headers = {"slave-id":slave_id} + + results = [] cancelled = False stdout = bytes() run_t = time.time() + line = "" while not cancelled and process.poll() is None: stdout += process.stdout.read(1024) current_t = time.time() @@ -255,9 +256,17 @@ def render_slave(engine, netsettings, threads): conn.request("PUT", logURL(job.id, first_frame), stdout, headers=headers) responseStatus(conn) + stdout_text = str(stdout, encoding='utf8') + # Also output on console if netsettings.use_slave_output_log: - print(str(stdout, encoding='utf8'), end="") + print(stdout_text, end="") + + lines = stdout_text.split("\n") + lines[0] = line + lines[0] + line = lines.pop() + if job.subtype == netrender.model.JOB_SUB_BAKING: + results.extend(netrender.baking.resultsFromOuput(lines)) stdout = bytes() @@ -283,10 +292,17 @@ def render_slave(engine, netsettings, threads): # flush the rest of the logs if stdout: + stdout_text = str(stdout, encoding='utf8') + # Also output on console - if netsettings.use_slave_thumb: - print(str(stdout, encoding='utf8'), end="") + if netsettings.use_slave_output_log: + print(stdout_text, end="") + lines = stdout_text.split("\n") + lines[0] = line + lines[0] + if job.subtype == netrender.model.JOB_SUB_BAKING: + results.extend(netrender.baking.resultsFromOuput(lines)) + # (only need to update on one frame, they are linked with ConnectionContext(): conn.request("PUT", logURL(job.id, first_frame), stdout, headers=headers) @@ -306,7 +322,7 @@ def render_slave(engine, netsettings, threads): if status == 0: # non zero status is error - headers["job-result"] = str(DONE) + headers["job-result"] = str(FRAME_DONE) for frame in job.frames: headers["job-frame"] = str(frame.number) if job.hasRenderResult(): @@ -333,12 +349,21 @@ def render_slave(engine, netsettings, threads): continue elif job.subtype == netrender.model.JOB_SUB_BAKING: - # For now just announce results - # TODO SEND ALL BAKING RESULTS - with ConnectionContext(): - conn.request("PUT", "/render", headers=headers) - if responseStatus(conn) == http.client.NO_CONTENT: - continue + index = job.frames.index(frame) + + frame_results = [result_filepath for task_index, result_filepath in results if task_index == index] + + for result_filepath in frame_results: + result_path, result_filename = os.path.split(result_filepath) + headers["result-filename"] = result_filename + headers["job-finished"] = str(result_filepath == frame_results[-1]) + + f = open(result_filepath, 'rb') + with ConnectionContext(): + conn.request("PUT", "/result", f, headers=headers) + f.close() + if responseStatus(conn) == http.client.NO_CONTENT: + continue elif job.type == netrender.model.JOB_PROCESS: with ConnectionContext(): @@ -346,7 +371,7 @@ def render_slave(engine, netsettings, threads): if responseStatus(conn) == http.client.NO_CONTENT: continue else: - headers["job-result"] = str(ERROR) + headers["job-result"] = str(FRAME_ERROR) for frame in job.frames: headers["job-frame"] = str(frame.number) # send error result back to server diff --git a/netrender/ui.py b/netrender/ui.py index 6193b7a6fee00caedab27ae7fb8d157a77837bd6..b32098cd254045e5dd73e0ceaa3bd14aa12a017c 100644 --- a/netrender/ui.py +++ b/netrender/ui.py @@ -30,11 +30,6 @@ VERSION = b"0.3" PATH_PREFIX = "/tmp/" -QUEUED = 0 -DISPATCHED = 1 -DONE = 2 -ERROR = 3 - LAST_ADDRESS_TEST = 0 ADDRESS_TEST_TIMEOUT = 30 @@ -207,7 +202,7 @@ class RENDER_PT_network_job(NetRenderButtonsPanel, bpy.types.Panel): if netsettings.server_address != "[default]": layout.operator("render.netclientanim", icon='RENDER_ANIMATION') layout.operator("render.netclientsend", icon='FILE_BLEND') - #layout.operator("render.netclientsendbake", icon='PHYSICS') + layout.operator("render.netclientsendbake", icon='PHYSICS') layout.operator("render.netclientsendframe", icon='RENDER_STILL') if netsettings.job_id: row = layout.row() @@ -341,8 +336,8 @@ class RENDER_PT_network_jobs(NeedValidAddress, NetRenderButtonsPanel, bpy.types. 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]) + layout.label(text="Done: %04i" % job.results[FRAME_DONE]) + layout.label(text="Error: %04i" % job.results[FRAME_ERROR]) import bl_ui.properties_render as properties_render class RENDER_PT_network_output(NeedValidAddress, NetRenderButtonsPanel, bpy.types.Panel): diff --git a/netrender/utils.py b/netrender/utils.py index da5b744c42cb6cbd187bbf479948a542474409d7..776f4db952bbdb370d9e0b3ef545c0e1998852bd 100644 --- a/netrender/utils.py +++ b/netrender/utils.py @@ -45,16 +45,16 @@ JOB_STATUS_TEXT = { # Frames status -QUEUED = 0 -DISPATCHED = 1 -DONE = 2 -ERROR = 3 +FRAME_QUEUED = 0 +FRAME_DISPATCHED = 1 +FRAME_DONE = 2 +FRAME_ERROR = 3 FRAME_STATUS_TEXT = { - QUEUED: "Queued", - DISPATCHED: "Dispatched", - DONE: "Done", - ERROR: "Error" + FRAME_QUEUED: "Queued", + FRAME_DISPATCHED: "Dispatched", + FRAME_DONE: "Done", + FRAME_ERROR: "Error" } try: @@ -244,6 +244,9 @@ def fileURL(job_id, file_index): def logURL(job_id, frame_number): return "/log_%s_%i.log" % (job_id, frame_number) +def resultURL(job_id): + return "/result_%s.zip" % job_id + def renderURL(job_id, frame_number): return "/render_%s_%i.exr" % (job_id, frame_number)