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', &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 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)