diff --git a/netrender/client.py b/netrender/client.py
index 4cbeb78fafc765c489dd5e7030d5c38556aadbb2..057e801eea9e8812b5087579f65bfac6bd521c8f 100644
--- a/netrender/client.py
+++ b/netrender/client.py
@@ -218,25 +218,17 @@ def clientSendJobBlender(conn, scene, anim = False):
     # FLUID + POINT CACHE
     ###########################
     default_path = cachePath(filename)
-
-    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 == "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)
+    
+    def pointCacheFunc(object, point_cache):
+        addPointCache(job, object, point_cache, default_path)
+        
+    def fluidFunc(object, modifier, cache_path):
+        addFluidFiles(job, cache_path)
+        
+    def multiresFunc(object, modifier, cache_path):
+        job.addFile(cache_path)
+        
+    processObjectDependencies(pointCacheFunc, fluidFunc, multiresFunc)
 
     #print(job.files)
 
diff --git a/netrender/master.py b/netrender/master.py
index f6d7eb476050cccbf9f5e74661ea14b9e17b174b..92181ec196a72b7e3e5fbb38540c27055edbc5f8 100644
--- a/netrender/master.py
+++ b/netrender/master.py
@@ -154,7 +154,8 @@ class MRenderJob(netrender.model.RenderJob):
         self.status = JOB_QUEUED
 
     def addLog(self, frames):
-        log_name = "_".join(("%06d" % f for f in frames)) + ".log"
+        frames = sorted(frames)
+        log_name = "%06d_%06d.log" % (frames[0], frames[-1])
         log_path = os.path.join(self.save_path, log_name)
 
         for number in frames:
diff --git a/netrender/repath.py b/netrender/repath.py
index bae90210d37a28baa719c32a9e1e40e2ac2a6c38..6b2a46ed5e26fd3846b4a2544df6943dcbd3f35f 100644
--- a/netrender/repath.py
+++ b/netrender/repath.py
@@ -64,22 +64,6 @@ def update(job):
         os.renames(new_path, job_full_path)
 
 def process(paths):
-    def processPointCache(ob, point_cache):
-        if not point_cache.use_disk_cache:
-            return
-
-        cache_name = cacheName(ob, point_cache)
-        new_path = path_map.get(cache_name, None)
-        if new_path:
-            point_cache.use_external = True
-            point_cache.filepath = new_path
-            point_cache.name = cache_name
-
-    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
@@ -119,28 +103,29 @@ def process(paths):
     ###########################
     # 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(object, modifier.settings)
-            elif modifier.type == "CLOTH":
-                processPointCache(object, modifier.point_cache)
-            elif modifier.type == "SOFT_BODY":
-                processPointCache(object, modifier.point_cache)
-            elif modifier.type == "SMOKE" and modifier.smoke_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
+    def pointCacheFunc(object, point_cache):
+        if not point_cache.use_disk_cache:
+            return
 
-        # 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)
+        cache_name = cacheName(object, point_cache)
+        new_path = path_map.get(cache_name, None)
+        if new_path:
+            point_cache.use_external = True
+            point_cache.filepath = new_path
+            point_cache.name = cache_name
+        
+    def fluidFunc(object, modifier, cache_path):
+        fluid = modifier.settings
+        new_path = path_map.get(cache_path, None)
+        if new_path:
+            fluid.path = new_path
+        
+    def multiresFunc(object, modifier, cache_path):
+        new_path = path_map.get(cache_path, None)
+        if new_path:
+            modifier.filepath = new_path
+        
+    processObjectDependencies(pointCacheFunc, fluidFunc, multiresFunc)
                 
 
 if __name__ == "__main__":
diff --git a/netrender/utils.py b/netrender/utils.py
index 95513219c570f16af3af42525c9e6245fbb9c0b0..108d9c5220b457376bb18ba4bf3c7c61fd1ab09f 100644
--- a/netrender/utils.py
+++ b/netrender/utils.py
@@ -243,6 +243,33 @@ def cachePath(file_path):
     root, ext = os.path.splitext(name)
     return path + os.sep + "blendcache_" + root # need an API call for that
 
+# Process dependencies of all objects with user defined functions
+#    pointCacheFunction(object, point_cache)
+#    fluidFunction(object, modifier, cache_path)
+#    multiresFunction(object, modifier, cache_path)
+def processObjectDependencies(pointCacheFunction, fluidFunction, multiresFunction):
+    for object in bpy.data.objects:
+        for modifier in object.modifiers:
+            if modifier.type == 'FLUID_SIMULATION' and modifier.settings.type == "DOMAIN":
+                fluidFunction(object, modifier, bpy.path.abspath(modifier.settings.filepath))
+            elif modifier.type == "CLOTH":
+                pointCacheFunction(object, modifier.point_cache)
+            elif modifier.type == "SOFT_BODY":
+                pointCacheFunction(object, modifier.point_cache)
+            elif modifier.type == "SMOKE" and modifier.smoke_type == "DOMAIN":
+                pointCacheFunction(object, modifier.domain_settings.point_cache)
+            elif modifier.type == "MULTIRES" and modifier.is_external:
+                multiresFunction(object, modifier, bpy.path.abspath(modifier.filepath))
+            elif modifier.type == "DYNAMIC_PAINT" and modifier.canvas_settings:
+                for surface in modifier.canvas_settings.canvas_surfaces:
+                    pointCacheFunction(object, surface.point_cache)
+    
+        # particles modifier are stupid and don't contain data
+        # we have to go through the object property
+        for psys in object.particle_systems:
+            pointCacheFunction(object, psys.point_cache)
+    
+
 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