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

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Poirier <theeth@yahoo.com>2012-01-09 06:47:50 +0400
committerMartin Poirier <theeth@yahoo.com>2012-01-09 06:47:50 +0400
commitd84fff4b3a70906b320fd143596403e359c4b9a6 (patch)
tree1ca15a39bb5ec0976f2e596ccc41ead2191bff7d /netrender
parentd827762474e90e9db682e5c0694255777fc473c2 (diff)
netrender
- distributed point cache baking - (baking fluids or anything else needs to be added but is not a hard task) - master support getting all results as a zip (rendering, baking or otherwise), available as a link on the job's page in the web interface - framework to support multiple result files per frame/task (needed for baking)
Diffstat (limited to 'netrender')
-rw-r--r--netrender/__init__.py2
-rw-r--r--netrender/baking.py94
-rw-r--r--netrender/balancing.py6
-rw-r--r--netrender/client.py2
-rw-r--r--netrender/master.py126
-rw-r--r--netrender/master_html.py53
-rw-r--r--netrender/model.py19
-rw-r--r--netrender/operators.py4
-rw-r--r--netrender/slave.py53
-rw-r--r--netrender/ui.py11
-rw-r--r--netrender/utils.py19
11 files changed, 288 insertions, 101 deletions
diff --git a/netrender/__init__.py b/netrender/__init__.py
index 1b4812d8..26551977 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 b01d44d7..24d32e85 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 dde3ad53..24b6fcb2 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 62eb4855..18a2302f 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 599277dc..65ff3583 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 a8e6eaa6..a0414a03 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', &quot;{'priority': %i}&quot;);">+</button>""" % (job.id, job.priority + 1) +
"""<button title="decrease priority" onclick="request('/edit_%s', &quot;{'priority': %i}&quot;);" %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 "&nbsp;",
- link("view log", logURL(job_id, frame.number)) if frame.log_path else "&nbsp;",
- 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 "&nbsp;",
- "<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 "&nbsp;",
+ link("view log", logURL(job_id, frame.number)) if frame.log_path else "&nbsp;",
+ 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 "&nbsp;",
+ "<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 "&nbsp;",
+ link("view log", logURL(job_id, frame.number)) if frame.log_path else "&nbsp;"
+ )
endTable()
else:
diff --git a/netrender/model.py b/netrender/model.py
index c5ee54f0..ec91587b 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 d6d441ba..bbfd42c2 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 36768837..361b78fc 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 6193b7a6..b32098cd 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 da5b744c..776f4db9 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)