Quellcode durchsuchen

Fix prep_driver_values in CurvePin

really ridiculous huge commit for such a small bug lmao
still not 100% done; I need to write an expression parser for drivers
and I also need an expression parser for overlay variables so... I'll implement it then
Joseph Brandenburg vor 2 Wochen
Ursprung
Commit
61403562b3
6 geänderte Dateien mit 222 neuen und 29 gelöschten Zeilen
  1. 4 4
      base_definitions.py
  2. 21 0
      execution/parse_expression.py
  3. 0 3
      execution/readtree.py
  4. 2 4
      misc_nodes.py
  5. 174 0
      utilities/node_common.py
  6. 21 18
      xForm_nodes.py

+ 4 - 4
base_definitions.py

@@ -862,12 +862,12 @@ custom_props_types = ["LinkArmature", "UtilityKeyframe", "UtilityFCurve", "Utili
                       "xFormArmature", "xFormGeometryObject", "xFormObjectInstance", "xFormCurvePin",]
 
 # filters for determining if a link is a hierarchy link or a non-hierarchy (cyclic) link.
-from_name_filter = ["Driver",]
-to_name_filter = [
+from_name_filter = frozenset({"Driver",})
+to_name_filter = frozenset({
                    "Custom Object xForm Override",
                    "Custom Object",
                    "Deform Bones",
-                 ]
+                 })
 
 # nodes that must be solved as if they were Schema because they send arrays out.
 array_output_types = [
@@ -1301,7 +1301,7 @@ class DummyLink:
 def detect_hierarchy_link(from_node, from_socket, to_node, to_socket,):
     if to_node.node_type in ['DUMMY_SCHEMA', 'SCHEMA']:
         return False #TODO: find out if filtering SCHEMA types is wise
-    if from_socket in from_name_filter and to_socket in ["Array"]:
+    if from_socket in from_name_filter and to_socket in {"Array"}:
         # SPECIAL CASE: arrays essentially extend the non-hierarchy edge
         #       so if the connection to the array isn't hierarchy, the array node
         #       in the middle gets disconnected and never executes.

+ 21 - 0
execution/parse_expression.py

@@ -0,0 +1,21 @@
+# This file is a minimal parser for simple expressions
+# It's used for e.g. Overlay variables
+# Also used to figure out the initial value of a driver for e.g. Curve Pin Node
+
+# For now I will only support basic operations.
+# Once I get this working it will be easy to expand
+tokens = {
+    "+" : "ADD",
+    "-" : 'SUBTRACT',
+    "/" : 'DIVIDE',
+    "*" : 'MULTIPLY',
+}
+
+#I'm too tired to work on this but I need to do it later so... I'll save the file.
+def parse_expression(expression):
+    numbers = []
+    current_number = ""
+    # for i range len(expression):
+    #     if 
+    pass
+        

+ 0 - 3
execution/readtree.py

@@ -732,7 +732,6 @@ def execute_tree(nodes, base_tree, context, error_popups = False, profile=False)
         execution_failed = True
     try:
         for n in sorted_nodes:
-            print ("Transform Pass: ", n)
             if execution_failed: break
             try:
                 check_and_apply_overlay(n)
@@ -759,7 +758,6 @@ def execute_tree(nodes, base_tree, context, error_popups = False, profile=False)
         switch_mode(mode='POSE', objects=switch_me)
 
         for n in sorted_nodes:
-            print ("Relationship Pass: ", n)
             try:
                 check_and_apply_overlay(n)
                 if not n.prepared:
@@ -782,7 +780,6 @@ def execute_tree(nodes, base_tree, context, error_popups = False, profile=False)
             ob.data.pose_position = 'POSE'
 
         for n in sorted_nodes:
-            print ("Finalize Pass: ", n)
             try:
                 if profiler: profiler.record(n, "bFinalize", context)
                 else: n.bFinalize(context)

+ 2 - 4
misc_nodes.py

@@ -804,6 +804,8 @@ class UtilityDriverVariable(MantisMiscNode):
             my_var["owner"]   = xForm1 # will be filled in by Driver
             my_var["xForm 1"] = xForm1 # self.GetxForm(index = 1),
             my_var["xForm 2"] = xForm2 # self.GetxForm(index = 2),
+        else:
+            self.prepared=False
 
     # NOTE _index for interface consistency
     def evaluate_input(self, input_name, _index=0):
@@ -908,10 +910,6 @@ class UtilityFCurve(MantisMiscNode):
     __slots__ = ()
     socket_templates = FCurveSockets
 
-
-    def reset_execution(self):
-        super().reset_execution()
-
     def bPrepare(self, bContext = None,):
         extrap_mode = self.evaluate_input("Extrapolation Mode")
         keys = []

+ 174 - 0
utilities/node_common.py

@@ -121,6 +121,180 @@ def trace_line_up_branching(mantis_node : MantisNode, output_name : str,
                             leaf_nodes.append(socket.node)
     return leaf_nodes
 
+
+# For this, we basically just evaluate the driver once and use the result
+def find_driven_default_value(mantis_node, socket_name):
+    from ..misc_nodes import UtilityDriver, UtilitySwitch
+    from .utilities import get_ui_node
+    xf_inputs_values_var_names = []
+
+    node_line, _last_socket = trace_single_line(mantis_node, socket_name)
+    driver_node = None
+    driver_type = 'SUM'
+    keys = [ (0, 0), (1, 1) ]
+    for driver_node in node_line:
+        if driver_node.node_type == 'DRIVER': break
+    else: # it's not being driven
+        return mantis_node.evaluate_input(socket_name)
+    
+    if isinstance(driver_node, UtilityDriver):
+        driver_type = driver_node.evaluate_input("Driver Type")
+        # then get the variables
+        for i, input in enumerate(driver_node.inputs):
+            # unicode for 'a'
+            var_name = chr(i - 2 + 96)
+            if i == 0 : continue # the Driver Type doesn't interest us
+            elif i ==1 : continue # for now. we do need the fCurve
+            else:
+                if not input.is_linked: continue # nothing to do here
+            var_node    = input.links[0].from_node
+            # NOTE: this var_node will not be prepared on time unless we sort the graph by depth
+            #       and it doesn't seem to be possible to do that without breaking drivers... so
+            # HACK it's safe to do this, but VERY ugly and bad.
+            if not var_node.prepared:
+                var_node.bPrepare()
+            # end HACK
+            var_data    = var_node.parameters["Driver Variable"]
+            var_type    = var_data['type']
+            var_channel = var_data['channel']
+            prop_name = None; value = None 
+            xf1 = var_node.GetxForm(index=1)
+            match var_type:
+                case "SINGLE_PROP":
+                    prop_name = var_node.evaluate_input("Property")
+                    # we'll get the property value in the next step
+                case "TRANSFORMS":
+                    matrix1 = xf1.evaluate_input['Matrix']
+                    # Mantis ensures that these are identity at creation time
+                    if "LOC" in var_channel or "ROT" in var_channel:
+                            value = 0
+                    else: # Scale, defaults to 1
+                            value = 1
+                case "ROTATION_DIFF":
+                    xf2 = var_node.GetxForm(index=2)
+                    matrix1 = xf1.evaluate_input['Matrix']
+                    matrix2 = xf2.evaluate_input['Matrix']
+                    value = matrix1.to_quaternion().rotation_difference(matrix2.to_quaternion()).angle
+                case "LOC_DIFF":
+                    xf2 = var_node.GetxForm(index=2)
+                    matrix1 = xf1.evaluate_input['Matrix']
+                    matrix2 = xf2.evaluate_input['Matrix']
+                    value = (matrix1.to_translation() - matrix2.to_translation()).length
+                case "CONTEXT_PROP":
+                    raise NotImplementedError("Mantis Does not yet support Context Property")
+                    pass
+            xf_inputs_values_var_names.append((xf1, prop_name, value, var_name))
+
+        # Now we'll get the keyframe points and sort them, too!
+        if driver_node.inputs['fCurve'].is_linked:
+            fCurve_node = driver_node.inputs['fCurve'].links[0].from_node
+        extrapolation_mode = 'CONSTANT'
+        keys = []
+        if fCurve_node:
+            extrapolation_mode = fCurve_node.evaluate_input("Extrapolation Mode")
+            for l in fCurve_node.inputs['Keyframes'].links:
+                key_node = l.from_node
+                if key_node:
+                    keys.append( (key_node.evaluate_input("Frame"),  key_node.evaluate_input("Value")) )
+        keys = sorted( keys, key = lambda a : a[0]) # sort by the frame
+
+    elif isinstance(driver_node, UtilitySwitch):
+        # Reimplementing the GetxForm from the switch here
+        # because it is faster to inline this and store the trace result
+        trace = trace_single_line(driver_node, "Parameter" )
+        for node in trace[0]:
+            if node.node_type == 'XFORM':
+                xf = node                    # reuse trace HERE
+                prop_name = trace[1].name    # saves us from tracing again.
+                xf_inputs_values_var_names.append(( xf, prop_name, None, "a")); break
+        else:
+            raise RuntimeError(f"ERROR: Cannot Set the default of the driven value {socket_name} \n"
+                                f"in node {mantis_node}")
+    # We're gonna assume the xForm is sorting these right
+    # but we'll check the name if it is cheap
+    default_values = {}
+    for xf, prop_name, value, var_name in xf_inputs_values_var_names:
+        i=0
+        for output in xf.outputs:
+            if 'xForm Out' == output.name: continue
+            if output.name == prop_name: break
+            i+=1
+        node_line, _last_socket = trace_single_line(xf, "Custom Properties", i)
+        custom_prop_node = node_line[-1]
+        if value:
+            default_values[var_name] = value; continue
+        if prop_name:
+            if custom_prop_node.evaluate_input("Name") == prop_name:
+                custom_prop_type = custom_prop_node.evaluate_input("Type")
+                default_prop_name = ""
+                match custom_prop_type:
+                    case "BOOL":
+                        default_prop_name = 'Default (Bool)'
+                    case "FLOAT":
+                        default_prop_name = 'Default (Float)'
+                    case "INT":
+                        default_prop_name = 'Default (Int)'
+                    case _:
+                        raise RuntimeError(f"ERROR: Cannot Set the default of the driven value {socket_name} \n"
+                                           f"in node {mantis_node}"
+                                           f"Reason: Custom Property {prop_name} is not a valid type." )
+                default_values[var_name] = float(custom_prop_node.evaluate_input(default_prop_name))
+            else:
+                raise RuntimeError(f"ERROR: failed to get proeprty {prop_name} for node {xf}, \n"
+                                   f"cannot Set the default of the driven value {socket_name} \n"
+                                   f"in node {mantis_node}"
+                                    "Reason: Cannot Find Custom Property Node")
+    frame = 0.0
+    if default_values:
+        match driver_type:
+            case "AVERAGE":
+                frame = sum(default_values.values())/len(default_values)
+            case "SUM":
+                frame = sum(default_values.values())
+            case "MIN":
+                frame = min(default_values.values())
+            case "MAX":
+                frame = max(default_values.values())
+            case "SCRIPTED":
+                raise NotImplementedError (f"ERROR: Cannot Set the default of the driven value {socket_name} \n"
+                                        f"in node {mantis_node} \n"
+                                            "Reason: Driver Expressions are not yet implemented.")
+    # Now we apply the fCurve
+    # For now, We'll assume LINEAR keys TODO: come back to this during Pose update!
+    if len(keys) >= 2:
+        previous_frame = None
+        next_frame = None
+        previous_value = None
+        next_value = None
+        for key in keys:
+            if key[0] <= frame: previous_frame, previous_value = key
+            if key[0] >= frame: next_frame, next_value = key
+            if previous_frame is not None and next_frame is not None: break
+        else: # if our frame is out-of-bounds we need a slope to extrapolate on.
+            if previous_frame and not next_frame:
+                next_frame, next_value = previous_frame, previous_value
+                if extrapolation_mode == 'LINEAR':
+                    previous_frame, previous_value = keys[-2] # we need the second last key to extrapolate
+            elif next_frame and not previous_frame:
+                previous_frame, previous_value = next_frame, next_value
+                if extrapolation_mode == 'LINEAR':
+                    next_frame, next_value = keys[1] # we need the second key to extrapolate
+            else:   
+                raise RuntimeError (f"ERROR: Cannot Set the default of the driven value {socket_name} \n"
+                                    f"in node {mantis_node} \n"
+                                    "Reason: Cannot place value on driver fCurve.")
+        if previous_frame == next_frame: # the frame is exactly on a key
+            return previous_value
+        # this is just a simple lerp because we are assuming the keys are linear for now
+        result = previous_value + ((frame - previous_frame) / (next_frame - previous_frame)) * (next_value - previous_value)
+    elif len(keys) ==1:
+        result = key[1] # flat curve, take the value of the only key
+    else:
+        raise RuntimeError (f"ERROR: Cannot Set the default of the driven value {socket_name} \n"
+                            f"in node {mantis_node} \n"
+                             "Reason: Invalid fCurve has 0 keyframes")
+    return result
+        
 # this function exists to walk back to the first ancestor node of XFORM or LINK without
 #  breaking the traverse behaviour which I still need for various reasons.
 def get_parent_xForm_info(mantis_node, socket_name = 'Relationship'):

+ 21 - 18
xForm_nodes.py

@@ -729,6 +729,7 @@ class xFormObjectInstance(xFormNode):
     def bGetObject(self, mode = 'POSE'):
         return self.bObject
 
+
 class xFormCurvePin(xFormNode):
     """An xForm pinned to a specific location on a curve."""
     __slots__ = ()
@@ -739,25 +740,26 @@ class xFormCurvePin(xFormNode):
         setup_custom_property_inputs_outputs(self)
 
     def prep_driver_values(self, constraint):
-        from .misc_nodes import UtilityDriver, UtilitySwitch
-        for socket_name in ["Curve Pin Factor", "Forward Axis","Up Axis",]:
-            if self.inputs.get(socket_name) is None: continue # in case it has been bypassed
-            if self.inputs[socket_name].is_linked:
-                node_line, _last_socket = trace_single_line(self, socket_name)
-                driver = None
-                for other_node in node_line:
-                    if other_node.node_type == 'DRIVER':
-                        driver = other_node; break
-                if isinstance(driver, UtilityDriver):
-                    prop_amount = driver.evaluate_input("Property")
-                elif isinstance(driver, UtilitySwitch):
-                    xf=driver.GetxForm()
-                    prop_amount = xf.evaluate_input(driver.evaluate_input('Parameter'))
-                else:
-                    return
-                for template in self.socket_templates:
-                    if template.name == socket_name: break
+        enums =  ( None,
+                   ("FORWARD_X", "FORWARD_Y", "FORWARD_Z", "TRACK_NEGATIVE_X", "TRACK_NEGATIVE_Y", "TRACK_NEGATIVE_Z"),
+                   ("UP_X", "UP_Y", "UP_Z"),
+                 )
+        for i, socket_name in enumerate(("Curve Pin Factor", "Forward Axis","Up Axis",)):
+            if self.inputs.get(socket_name) is None: continue 
+            enum = enums[i]
+            for template in self.socket_templates:
+                if template.name == socket_name: break
+            prop_amount = find_driven_default_value(self, socket_name)
+            # after all it may not be driven so it is probably a string if it is one of the axis properties.
+            if isinstance(prop_amount, (int, float)) and enum: # it is actually driving an enum value instead...
+                for i, enum_val in enumerate(enum):
+                    # check if the prop amount is in the current int as the enum val
+                    if i+1 > prop_amount: break
+                prop_amount = enum_val            
+            try:
                 setattr(constraint, template.blender_property, prop_amount )
+            except TypeError:
+                setattr(constraint, template.blender_property, template.default_value )
 
     def bPrepare(self, bContext = None,):
         from bpy import data
@@ -837,6 +839,7 @@ class xFormCurvePin(xFormNode):
         # because the drivers may not have initialized yet.
         self.prep_driver_values(c)
         # now if all goes well... the matrix will be correct.
+        # NOTE: this dg.update() makes this a very expensive node
         dg = bContext.view_layer.depsgraph
         dg.update()
         # and the matrix should be correct now - copy because it may be modified