Improved cache baking operators which make use of threaded depsgraph
authorLukas Tönne <lukas.toenne@gmail.com>
Fri, 19 Dec 2014 10:26:29 +0000 (11:26 +0100)
committerLukas Tönne <lukas.toenne@gmail.com>
Fri, 19 Dec 2014 10:26:29 +0000 (11:26 +0100)
updates.

Previously each object was baked explicitly on its own. This is much
slower than the regular scene baking because it doesn't utilize the
threaded depsgraph updates.

Now the operator simply runs through the whole frame range once,
creating point cache data, and then bakes that data to make the caches
permanent. (Note: using the "bake_all" operator would also work, but
doesn't give much control and progress feedback is limited).

object_physics_meadow/physics.py
object_physics_meadow/ui.py

index f32b985..d7f7bda 100644 (file)
@@ -29,10 +29,58 @@ from object_physics_meadow import progress
 
 sim_modifier_types = { 'CLOTH', 'DYNAMIC_PAINT', 'FLUID_SIMULATION', 'PARTICLE_SYSTEM', 'SMOKE', 'SOFT_BODY' }
 
+# yield (modifier, pointcache) tuples of an object's simulations
+def object_sims(ob):
+    for md in ob.modifiers:
+        
+        if md.type == 'CLOTH':
+            yield (md, md.point_cache)
+        
+        elif md.type == 'DYNAMIC_PAINT':
+            if md.canvas_settings:
+                for surf in md.canvas_settings.canvas_surfaces:
+                    yield (md, surf,point_cache)
+
+        elif md.type == 'PARTICLE_SYSTEM':
+            yield (md, md.particle_system.point_cache)
+
+        elif md.type == 'SMOKE':
+            if md.smoke_type == 'DOMAIN':
+                yield (md, md.domain_settings.point_cache)
+
+        elif md.type == 'SOFT_BODY':
+            yield (md, md.point_cache)
+
+# XXX HACK! depsgraph doesn't work and there is no proper way
+# of forcing a cache reset after freeing a bake ...
+# this function just attempts to set some variable so the cache is tagged 'outdated'
+def object_mod_clear_cache(md):
+    if md.type == 'CLOTH':
+        md.settings.mass = md.settings.mass
+    
+    elif md.type == 'DYNAMIC_PAINT':
+        if md.canvas_settings:
+            for surf in md.canvas_settings.canvas_surfaces:
+                surf.use_dissolve = surf.use_dissolve
+
+    elif md.type == 'PARTICLE_SYSTEM':
+        md.particle_system.invert_vertex_group_density = md.particle_system.invert_vertex_group_density
+
+    elif md.type == 'SMOKE':
+        if md.smoke_type == 'DOMAIN':
+            md.domain_settings.strength = md.domain_settings.strength
+
+    elif md.type == 'SOFT_BODY':
+        md.settings.mass = md.settings.mass
+
+
 # stores the enabled state of sim objects to allow toggling for bakes
 class BakeSimContext():
     def __enter__(self):
         scene = bpy.context.scene
+
+        # frame is modified during baking
+        self.curframe = scene.frame_current
         
         self.mod_toggles = {}
         for ob in scene.objects:
@@ -50,75 +98,77 @@ class BakeSimContext():
                 if md.type in sim_modifier_types:
                     if (ob, md) in self.mod_toggles:
                         toggle = self.mod_toggles[(ob, md)]
-                        md.show_viewport = toggle[0]
-                        md.show_render = toggle[1]
+                        # XXX noop settings still invalidates point caches, avoid that
+                        if md.show_viewport != toggle[0]:
+                            md.show_viewport = toggle[0]
+                        if md.show_render != toggle[1]:
+                            md.show_render = toggle[1]
 
-def enable_single_sim_psys(context, sim_ob, sim_psys):
-    scene = context.scene
-    for ob in scene.objects:
-        for md in ob.modifiers:
-            if md.type not in sim_modifier_types:
-                continue
-            
-            enable = (md.type == 'PARTICLE_SYSTEM' and md.particle_system == sim_psys)
-            
-            md.show_viewport = enable
-            md.show_render = enable
-
-def bake_psys(context, ob, psys):
-    cache = psys.point_cache
-    context_override = context.copy()
-    context_override["point_cache"] = cache
-    
-    select_single_object(ob)
-    curpsys = ob.particle_systems.active
-    ob.particle_systems.active = psys
-    
-    if cache.is_baked:
-        bpy.ops.ptcache.free_bake(context_override)
-    
-    # make sure only the active psys is enabled
-    enable_single_sim_psys(context, ob, psys)
-    
-    bpy.ops.ptcache.bake(context_override, bake=True)
-    
-    # restore
-    ob.particle_systems.active = curpsys
-
-def count_bakeable(context):
-    num = 0
-    for ob in patch.patch_objects(context):
-        for psys in ob.particle_systems:
-            num += 1
-    return num
+        # restore frame
+        scene.frame_set(self.curframe)
 
 def bake_all(context):
+    context_override = context.copy() # context override for the 'point_cache' argument
+    scene = context.scene
     settings = _settings.get(context)
-    wm = context.window_manager
-    
-    total_time = 0.0
-    avg_time = 0.0
+
+    patches = patch.patch_objects(context)
+    nonpatches = set(scene.objects) - set(patches)
     
-    total = count_bakeable(context)
+    # XXX this could disable necessary external influences,
+    # so for now rely on the user to disable unnecessary stuff
+    # should not be a big issue in practice ...
+    """
+    # disable all non-patch objects to avoid possible simulation overhead
+    for ob in nonpatches:
+        for md in ob.modifiers:
+            if md.type in sim_modifier_types:
+                md.show_viewport = False
+                md.show_render = False
+    """
     
-    with progress.ProgressContext("Bake Blob", 0, total):
-        for ob in patch.patch_objects(context):
-            for psys in ob.particle_systems:
-                progress.progress_add(1)
-                bake_psys(context, ob, psys)
+    for ob in patches:
+        for md, ptcache in object_sims(ob):
+            # convenience feature: copy the frame range from the scene to the patch objects
+            # the per-cache frame range will probably be dropped anyway at some point
+            ptcache.frame_start = scene.frame_start
+            ptcache.frame_end = scene.frame_end
+
+            # reset unbaked cache data just to be sure (depsgraph is not reliable enough ...)
+            object_mod_clear_cache(md)
+
+    # walk through frames, this creates (unbaked) cache frames
+    totframes = scene.frame_end - scene.frame_start + 1 # note: +1 because the end frame is included
+    with progress.ProgressContext("Calculate Physics", scene.frame_start, scene.frame_end):
+        for cfra in range(scene.frame_start, scene.frame_end+1):
+            progress.progress_set(cfra)
+            scene.frame_set(cfra)
+            scene.update()
+
+    # make cached data "baked" so it doesn't get destroyed
+    for ob in patches:
+        for md, ptcache in object_sims(ob):
+            if not ptcache.is_baked:
+                select_single_object(ob)
+                context_override["point_cache"] = ptcache
+                bpy.ops.ptcache.bake_from_cache(context_override)
 
 def scene_bake_all(context):
-    settings = _settings.get(context)
-    wm = context.window_manager
-    
-    # we disable all sim modifiers selectively to make sure only one sim has to be calculated at a time
     with BakeSimContext():
-        scene = context.scene
-        curframe = scene.frame_current
-        
-        # XXX have to set this because bake operator only bakes up to the last frame ...
-        scene.frame_current = scene.frame_end
-        
         bake_all(context)
-        
-        scene.frame_set(curframe)
+
+def free_all(context):
+    context_override = context.copy() # context override for the 'point_cache' argument
+    patches = patch.patch_objects(context)
+
+    for ob in patches:
+        for md, ptcache in object_sims(ob):
+            if ptcache.is_baked:
+                select_single_object(ob)
+                context_override["point_cache"] = ptcache
+                bpy.ops.ptcache.free_bake(context_override)
+            object_mod_clear_cache(md)
+
+def scene_free_all(context):
+    with BakeSimContext():
+        free_all(context)
index ac5d64c..41a9e5a 100644 (file)
@@ -100,10 +100,13 @@ class OBJECT_PT_Meadow(Panel):
         
         layout.separator()
         
-        sub = layout.column()
-        sub.enabled = has_samples
-        sub.operator("meadow.make_patches", icon='PARTICLE_PATH', text="Update Patches")
-        sub.operator("meadow.rebake_meadow", icon='MOD_PHYSICS', text="Update Physics Cache")
+        col = layout.column()
+        col.enabled = has_samples
+        col.operator("meadow.make_patches", icon='PARTICLE_PATH', text="Update Patches")
+        
+        row = col.row()
+        row.operator("meadow.bake_physics", icon='MOD_PHYSICS')
+        row.operator("meadow.free_physics", icon='X')
 
         row = layout.row()
         row.prop(groundob.meadow, "use_layers")
@@ -235,10 +238,10 @@ class MakeMeadowOperator(MeadowOperatorBase, Operator):
         return {'FINISHED'}
 
 
-class RebakeMeadowOperator(MeadowOperatorBase, Operator):
-    """Rebake meadow simulation"""
-    bl_idname = "meadow.rebake_meadow"
-    bl_label = "Rebake Meadow"
+class MEADOW_OT_BakePhysics(MeadowOperatorBase, Operator):
+    """Bake all physics caches"""
+    bl_idname = "meadow.bake_physics"
+    bl_label = "Bake Physics"
     bl_options = {'REGISTER', 'UNDO'}
     
     def execute(self, context):
@@ -248,6 +251,19 @@ class RebakeMeadowOperator(MeadowOperatorBase, Operator):
         return {'FINISHED'}
 
 
+class MEADOW_OT_FreePhysics(MeadowOperatorBase, Operator):
+    """Free all physics caches"""
+    bl_idname = "meadow.free_physics"
+    bl_label = "Free Physics"
+    bl_options = {'REGISTER', 'UNDO'}
+    
+    def execute(self, context):
+        with ObjectSelection():
+            progress_baking()
+            physics.scene_free_all(context)
+        return {'FINISHED'}
+
+
 def menu_generate_meadow(self, context):
     self.layout.operator("meadow.make_meadow", icon='PARTICLE_PATH')
 
@@ -258,7 +274,8 @@ def register():
     bpy.utils.register_class(DeleteBlobsOperator)
     bpy.utils.register_class(MakePatchesOperator)
     bpy.utils.register_class(MakeMeadowOperator)
-    bpy.utils.register_class(RebakeMeadowOperator)
+    bpy.utils.register_class(MEADOW_OT_BakePhysics)
+    bpy.utils.register_class(MEADOW_OT_FreePhysics)
     bpy.types.INFO_MT_add.append(menu_generate_meadow)
 
 def unregister():
@@ -269,4 +286,5 @@ def unregister():
     bpy.utils.unregister_class(DeleteBlobsOperator)
     bpy.utils.unregister_class(MakePatchesOperator)
     bpy.utils.unregister_class(MakeMeadowOperator)
-    bpy.utils.unregister_class(RebakeMeadowOperator)
+    bpy.utils.unregister_class(MEADOW_OT_BakePhysics)
+    bpy.utils.unregister_class(MEADOW_OT_FreePhysics)