Procházet zdrojové kódy

Cache IK Pole Angle results

this commit adds a cache to IK Pole Angle calculation
the thinking behind this is that every rig has IK, but the underlying skeleton
doesn't change all that much, so the Pole Angle is probably still good on every run
and it should only be updated if the IK system's shape has changed.

In practice, this reduces execution cost a little bit in slim scenes and a lot more in
scenes that have a costly scene update.
Joseph Brandenburg před 2 týdny
rodič
revize
64007c248e
1 změnil soubory, kde provedl 41 přidání a 9 odebrání
  1. 41 9
      link_nodes.py

+ 41 - 9
link_nodes.py

@@ -37,6 +37,16 @@ def TellClasses():
              LinkDrivenParameter,
              LinkDrivenParameter,
             ]
             ]
 
 
+# In order to save time and give users a little more control, we'll cache
+# the IK Pole Vector solution for a given set of bone matrices
+IK_POLE_VECTOR_CACHE = {}
+
+def make_ik_pole_vector_cache_key(start_effector, end_effector, pole_location, knee_location):
+    return frozenset({ tuple( start_effector[:]),
+                       tuple( end_effector[:]),
+                       tuple( pole_location[:]),
+                       tuple( knee_location[:]), })
+
 # set the name if it is available, otherwise just use the constraint's nice name
 # set the name if it is available, otherwise just use the constraint's nice name
 set_constraint_name = lambda mantis_node : mantis_node.evaluate_input("Name") if mantis_node.evaluate_input("Name") else mantis_node.__class__.__name__
 set_constraint_name = lambda mantis_node : mantis_node.evaluate_input("Name") if mantis_node.evaluate_input("Name") else mantis_node.__class__.__name__
 
 
@@ -47,7 +57,7 @@ class MantisLinkNode(MantisNode):
     def __init__(self, signature : tuple,
     def __init__(self, signature : tuple,
                  base_tree : NodeTree,):
                  base_tree : NodeTree,):
         super().__init__(signature, base_tree, self.socket_templates,
         super().__init__(signature, base_tree, self.socket_templates,
-                         additional_parameters={'Name':None})
+                         additional_parameters= {'Name':None})
         self.prepared = True; self.bObject=[]
         self.prepared = True; self.bObject=[]
 
 
     def bTransformPass(self, bContext=None):
     def bTransformPass(self, bContext=None):
@@ -511,7 +521,7 @@ class LinkInheritConstraint(MantisLinkNode):
             self.executed = True
             self.executed = True
 
 
 class LinkInverseKinematics(MantisLinkNode):
 class LinkInverseKinematics(MantisLinkNode):
-    __slots__ = ()
+    __slots__ = ("cache_available", "cache_key")
     socket_templates = LinkInverseKinematicsSockets
     socket_templates = LinkInverseKinematicsSockets
 
 
     def get_base_ik_bone(self, ik_bone):
     def get_base_ik_bone(self, ik_bone):
@@ -565,6 +575,18 @@ class LinkInverseKinematics(MantisLinkNode):
 
 
         # this is the X-Axis of the bone's rest-pose, added to its bone
         # this is the X-Axis of the bone's rest-pose, added to its bone
         knee_location = base_ik_bone.bone.matrix_local.col[0].xyz+start_effector
         knee_location = base_ik_bone.bone.matrix_local.col[0].xyz+start_effector
+
+        # At this point we can check the cache for the correct IK pole angle
+        cache_key = make_ik_pole_vector_cache_key(start_effector,
+                                                  end_effector,
+                                                  pole_location,
+                                                  knee_location)
+        self.cache_key.append(cache_key)
+        if pole_angle := IK_POLE_VECTOR_CACHE.get(cache_key):
+            self.cache_available.append(True)
+            return pole_angle
+        self.cache_available.append(False)
+
         ik_axis = (end_effector-start_effector).normalized()
         ik_axis = (end_effector-start_effector).normalized()
         from .utilities.utilities import project_point_to_plane
         from .utilities.utilities import project_point_to_plane
         pole_planar_projection = project_point_to_plane(pole_location, start_effector, ik_axis)
         pole_planar_projection = project_point_to_plane(pole_location, start_effector, ik_axis)
@@ -651,6 +673,8 @@ class LinkInverseKinematics(MantisLinkNode):
             i+=1
             i+=1
 
 
     def bRelationshipPass(self, context):
     def bRelationshipPass(self, context):
+        self.cache_available = []
+        self.cache_key = []
         prepare_parameters(self)
         prepare_parameters(self)
         for xf in self.GetxForm():
         for xf in self.GetxForm():
             print(wrapGreen("Creating ")+wrapOrange("Inverse Kinematics")+
             print(wrapGreen("Creating ")+wrapOrange("Inverse Kinematics")+
@@ -668,6 +692,9 @@ class LinkInverseKinematics(MantisLinkNode):
             #  a whole bunch of circular dependency crap from having infinite chain length
             #  a whole bunch of circular dependency crap from having infinite chain length
             if (c.pole_target):
             if (c.pole_target):
                 self.set_pole_angle(c, self.calc_pole_angle_pre(c, ik_bone))
                 self.set_pole_angle(c, self.calc_pole_angle_pre(c, ik_bone))
+            else: # no need for cache
+                self.cache_available.append(False)
+                self.cache_key.append(None)
 
 
             props_sockets = self.gen_property_socket_map()
             props_sockets = self.gen_property_socket_map()
             evaluate_sockets(self, c, props_sockets)
             evaluate_sockets(self, c, props_sockets)
@@ -676,17 +703,22 @@ class LinkInverseKinematics(MantisLinkNode):
         self.executed = True
         self.executed = True
 
 
     def bFinalize(self, bContext = None):
     def bFinalize(self, bContext = None):
-        # adding a test here
         if bContext:
         if bContext:
             for i, constraint in enumerate(self.bObject):
             for i, constraint in enumerate(self.bObject):
+                # retrieve the cached result if it is available
                 ik_bone = self.GetxForm()[i].bGetObject(mode='POSE')
                 ik_bone = self.GetxForm()[i].bGetObject(mode='POSE')
                 if constraint.pole_target:
                 if constraint.pole_target:
-                    prWhite(f"Fine-tuning IK Pole Angle for {self}")
-                    # make sure to enable it first
-                    enabled_before = constraint.mute
-                    constraint.mute = False
-                    self.calc_pole_angle_post(constraint, ik_bone, bContext)
-                    constraint.mute = enabled_before
+                    if self.cache_available[i]:
+                        prWhite(f"Retrieving fine-tuned IK Pole Angle for {self} from cache.")
+                        self.set_pole_angle(constraint, IK_POLE_VECTOR_CACHE[self.cache_key[i]])
+                    else:
+                        prWhite(f"Fine-tuning IK Pole Angle for {self}")
+                        # make sure to enable it first
+                        enabled_before = constraint.mute
+                        constraint.mute = False
+                        self.calc_pole_angle_post(constraint, ik_bone, bContext)
+                        constraint.mute = enabled_before
+                        IK_POLE_VECTOR_CACHE[self.cache_key[i]] = constraint.pole_angle
         super().bFinalize(bContext)
         super().bFinalize(bContext)
 
 
 def ik_report_error(pb, context, do_print=False):
 def ik_report_error(pb, context, do_print=False):