Browse Source

WIP string variables

string variables work in the simplest case already
all the trouble comes from the stuff that is in schemas or made at
 runtime
so I have a lot of complex stuff to figure out
and even more important: to figure out how the user interacts
for example: should frame variables be inherited or not?
do I need a way to get this stuff recursively?
then how do i evaluate a string from a node in a different frame?
I would need to use THAT node to look up the variables, right?
NO SPOOKY stuff can be allowed

anyhow this is a WIP, might forget about this for a while and come back

some unsaved stuff I forgot to add. trash?

Fix Schema fail when Group linked to In/Outgoing

The problem was that I was attempting to get the next node
but it hadn't been generated yet because it was in a node group
There was never a problem with outgoing connection after all
it was only handling the held link from the outgoing connection
that was messed up, and only then if the next node was a group!

oddly, it did work in some situations with the old code
because the node groups were generated.

this code maybe could be cleaned up by forcing the node group
to generate as soon as it is encountered
but I think this solution, though less elegant, is better
since I can then deal with the connection to the node group
which is already handled well elsewhere. So this doesn't add
complexity or modify the schema solve as a system.

Fix syntax error

Get String Variables at Execution Time

Cleanup misguided changes in DummyNode
Brandenburg 1 tháng trước cách đây
mục cha
commit
c37cd0df75
7 tập tin đã thay đổi với 130 bổ sung37 xóa
  1. 43 0
      base_definitions.py
  2. 0 1
      internal_containers.py
  3. 2 0
      link_nodes.py
  4. 23 7
      readtree.py
  5. 3 0
      schema_solve.py
  6. 58 28
      utilities.py
  7. 1 1
      visualize.py

+ 43 - 0
base_definitions.py

@@ -706,6 +706,7 @@ class MantisExecutionContext():
         self.execution_id = base_tree.execution_id
         self.execution_failed=False
         self.b_objects={} # objects created by Mantis during execution
+        self.string_variables={}
 
 class MantisNode:
     """
@@ -851,6 +852,46 @@ class MantisNode:
         for conn in self.hierarchy_connections:
             conn.reset_execution_recursive()
 
+    def get_interface_signature(self):
+        interface_signature = (*self.signature[:-1], 'InputInterface')
+        ui_name = self.ui_signature[-1]
+        name = self.signature[-1]
+        if ui_name != name:
+            schema_suffix = ''
+            for i in range(len(name)):
+                if name[i] == ui_name[i]: continue
+                break
+            schema_suffix = name[i+1:]
+            interface_signature = (*interface_signature[:-1],
+                                'InputInterface'+schema_suffix)
+        return interface_signature
+
+    # TODO: make this MUCH more efficient!
+    # alternatively: call this ONCE when initializing the tree, precache results?
+    def apply_string_variables(self, string):
+        # We get the mContext, iterate through the signature, and string-replace variables
+        # this function should be called by evaluate_input if the result is a string.
+        result=string; name=""
+        for i in range(len(self.signature[:-1])):
+            if i == 0: continue # it is None or AUTOGENERATED or something
+            name+=self.signature[i]
+        vars = self.mContext.string_variables.get(name, None)
+        if vars is None:
+            raise RuntimeError("Can't get string variables for node")
+        elif not vars:
+            prWhite(f"INFO: No vars available for {self}")
+        var_name_keys = list(vars.keys()); var_name_keys.sort(key=lambda a : -len(a))
+        for var_name in var_name_keys:
+            var_value = vars[var_name]
+            if var_value is None:
+                interface_signature = self.get_interface_signature()
+                interface_node = self.base_tree.parsed_tree.get(interface_signature)
+                from .utilities import set_string_variables_at_execution
+                set_string_variables_at_execution(interface_node, var_name)
+                var_value = vars[var_name]                
+            result = result.replace("$"+var_name, var_value)
+        return result
+
     def evaluate_input(self, input_name, index=0)  -> Any:
         from .node_container_common import trace_single_line
         if not (self.inputs.get(input_name)): # get the named parameter if there is no input
@@ -859,6 +900,8 @@ class MantisNode:
         #  it is NOT handled here because it should NOT happen - so I want the error message.
         trace = trace_single_line(self, input_name, index)
         prop = trace[0][-1].parameters[trace[1].name] #trace[0] = the list of traced nodes; read its parameters
+        # apply the string variables if possible
+        if isinstance(prop, str) and "$" in prop: prop = self.apply_string_variables(prop)
         return prop
 
     def fill_parameters(self, ui_node=None)  -> None:

+ 0 - 1
internal_containers.py

@@ -32,7 +32,6 @@ class DummyNode(MantisNode):
         # this is ugly and I hate it.
         self.execution_prepared=True # in case it gets left behind in the tree as a dependency
 
-
 class NoOpNode(MantisNode):
     def __init__(self, signature, base_tree):
         super().__init__(signature, base_tree)

+ 2 - 0
link_nodes.py

@@ -124,6 +124,8 @@ class MantisLinkNode(MantisNode):
     def bFinalize(self, bContext=None):
         finish_drivers(self)
 
+
+
 #*#-------------------------------#++#-------------------------------#*#
 # L I N K   N O D E S
 #*#-------------------------------#++#-------------------------------#*#

+ 23 - 7
readtree.py

@@ -141,7 +141,7 @@ def make_connections_to_ng_dummy(base_tree, tree_path_names, local_nc, all_nc, n
             else:
                 prRed("No available auto-generated class for input %s in %s" % (inp.name, np.name))
 
-def gen_node_containers(base_tree, current_tree, tree_path_names, all_nc, local_nc, dummy_nodes, group_nodes, schema_nodes ):
+def gen_mantis_nodes(base_tree, current_tree, tree_path_names, mContext, all_nc, local_nc, dummy_nodes, group_nodes, schema_nodes ):
     from .internal_containers import DummyNode
     for ui_node in current_tree.nodes:
         # HACK I found that this isn't being set sometimes. I wonder why? It makes the most sense to do this here.
@@ -155,13 +155,18 @@ def gen_node_containers(base_tree, current_tree, tree_path_names, all_nc, local_
             ui_sig = (None, *tree_path_names, ui_node.name)
             if not local_nc.get(sig):
                 nc = DummyNode( signature=sig , base_tree=base_tree, prototype=ui_node, ui_signature=ui_sig )
+                nc.mContext = mContext
                 local_nc[sig] = nc; all_nc[sig] = nc; dummy_nodes[sig] = nc
                 if ui_node.bl_idname in ["NodeGroupOutput"]:
                     nc.reroute_links = reroute_links_grpout
         elif ui_node.bl_idname in  ["MantisNodeGroup", "MantisSchemaGroup"]:
             nc = DummyNode( signature= (sig := (None, *tree_path_names, ui_node.name) ), base_tree=base_tree, prototype=ui_node )
+            nc.mContext = mContext
             local_nc[sig] = nc; all_nc[sig] = nc; dummy_nodes[sig] = nc
             make_connections_to_ng_dummy(base_tree, tree_path_names, local_nc, all_nc, nc)
+            # This will catch any variable set in a non-schema node-group.
+            from .utilities import set_string_variables_at_creation_time
+            set_string_variables_at_creation_time(nc, ui_node, mContext)
             if ui_node.bl_idname == "MantisNodeGroup":
                 group_nodes.append(nc)
                 nc.reroute_links = reroute_links_grp
@@ -176,6 +181,7 @@ def gen_node_containers(base_tree, current_tree, tree_path_names, all_nc, local_
                 if local_nc.get(sig):
                     continue # already made
             nc = nc_cls( sig , base_tree)
+            nc.mContext = mContext
             local_nc[sig] = nc; all_nc[sig] = nc
             nc.ui_signature = (*nc.ui_signature[:-1], ui_node.name) # just to ensure it points to a real node.
         else:
@@ -185,7 +191,7 @@ def gen_node_containers(base_tree, current_tree, tree_path_names, all_nc, local_
         if nc.signature[0] not in ['MANTIS_AUTOGENERATED'] and nc.node_type not in ['SCHEMA', 'DUMMY', 'DUMMY_SCHEMA']:
             nc.fill_parameters()
 
-def data_from_tree(base_tree, tree_path, dummy_nodes, all_nc, all_schema):#
+def data_from_tree(base_tree, tree_path, mContext, dummy_nodes, all_nc, all_schema):#
     # TODO: it should be relatively easy to make this use a while loop instead of recursion.
     local_nc, group_nodes = {}, []
     tree_path_names = [tree.name for tree in tree_path if hasattr(tree, "name")]
@@ -197,7 +203,7 @@ def data_from_tree(base_tree, tree_path, dummy_nodes, all_nc, all_schema):#
     if current_tree: # the node-group may not have a tree set - if so, ignore it.
         from .utilities import clear_reroutes
         links = clear_reroutes(list(current_tree.links))
-        gen_node_containers(base_tree, current_tree, tree_path_names, all_nc, local_nc, dummy_nodes, group_nodes, all_schema)
+        gen_mantis_nodes(base_tree, current_tree, tree_path_names, mContext, all_nc, local_nc, dummy_nodes, group_nodes, all_schema)
 
         from .utilities import link_node_containers
         for link in links:
@@ -207,7 +213,7 @@ def data_from_tree(base_tree, tree_path, dummy_nodes, all_nc, all_schema):#
             insert_default_values_base_tree(base_tree, all_nc)
         # Now, descend into the Node Groups and recurse
         for nc in group_nodes:
-            data_from_tree(base_tree, tree_path+[nc.prototype], dummy_nodes, all_nc, all_schema)
+            data_from_tree(base_tree, tree_path+[nc.prototype], mContext, dummy_nodes, all_nc, all_schema)
     return dummy_nodes, all_nc, all_schema
 
 from .utilities import check_and_add_root, init_connections, init_dependencies, init_schema_dependencies
@@ -380,7 +386,7 @@ def parse_tree(base_tree, error_popups=False):
     #  then they stick around because the function definition inits them once and keeps a reference
     # so instead I have to supply them to avoid ugly code or bugs elsewhere
     # it's REALLY confusing when you run into this sort of problem. So it warrants four entire lines of comments!
-    dummy_nodes, all_mantis_nodes, all_schema =  data_from_tree(base_tree, tree_path = [None], dummy_nodes = {}, all_nc = {}, all_schema={})
+    dummy_nodes, all_mantis_nodes, all_schema =  data_from_tree(base_tree, tree_path = [None], mContext=mContext, dummy_nodes = {}, all_nc = {}, all_schema={})
     for dummy in dummy_nodes.values():    # reroute the links in the group nodes
         if (hasattr(dummy, "reroute_links")):
             dummy.reroute_links(dummy, all_mantis_nodes)
@@ -483,12 +489,22 @@ def parse_tree(base_tree, error_popups=False):
 
     all_mantis_nodes = list(all_mantis_nodes.values())
     kept_nc = {}
+
     while (all_mantis_nodes):
         nc = all_mantis_nodes.pop()
         if nc in array_nodes:
             continue
-        if nc.node_type in ["DUMMY", 'SCHEMA', 'DUMMY_SCHEMA']:
-            continue # screen out the prototype schema nodes, group in/out, and group placeholders
+        if nc.node_type in ['SCHEMA', 'DUMMY_SCHEMA']:
+            # this is BAD because it "deletes" this implicitly instead of explicitly
+            continue # screen out the prototype schema nodes and schema nodes
+        if nc.node_type in ['DUMMY']:
+            if nc.signature[-1] in ["NodeGroupInput", "NodeGroupOutput"]:
+                continue
+            else:
+                prGreen(nc)
+                # but this doesn't have any links at this point?
+                # hold it. its purpose is to gather its string variables at runtime
+                pass
         # cleanup autogen nodes
         if nc.signature[0] == "MANTIS_AUTOGENERATED" and len(nc.inputs) == 0 and len(nc.outputs) == 1:
             from .base_definitions import can_remove_socket_for_autogen

+ 3 - 0
schema_solve.py

@@ -175,6 +175,9 @@ class SchemaSolver:
                     mantis_node = prototype_mantis_node.__class__(
                         signature, prototype_mantis_node.base_tree, prototype=ui_node,
                         ui_signature =  prototype_mantis_node.signature)
+                    # establish string variables
+                    from .utilities import set_string_variables_at_creation_time
+                    set_string_variables_at_creation_time(mantis_node, prototype_ui_node, mContext)
                     # now let's copy the links from the prototype node
                     if ui_node.bl_idname in ["MantisNodeGroup"]:
                         mantis_node.prepared = False

+ 58 - 28
utilities.py

@@ -104,10 +104,40 @@ def tree_from_nc(sig, base_tree):
             continue
         tree = tree.nodes.get(path_item).node_tree
     return tree
-    
+
 def get_node_prototype(sig, base_tree):
     return tree_from_nc(sig, base_tree).nodes.get( sig[-1] )
 
+# This one is the simplest case so it is easiest to use its own function.
+def set_string_variables_at_creation_time(n, prototype, mContext):
+    # we're gonna store the variables using the node's signature
+    group_key = ''.join(n.signature[1:])
+    # Get the variables if they exist, otherwise just get a dict
+    group_vars=mContext.string_variables.get(group_key, {})
+    mContext.string_variables[group_key]=group_vars
+    for input in prototype.inputs:
+        if hasattr(input, "default_value") and not input.is_linked:
+            if isinstance (input.default_value, str):
+                group_vars[input.name]=input.default_value
+            elif hasattr(input.default_value, "name"):
+                group_vars[input.name]=input.default_value.name
+        elif hasattr(input, "default_value") and input.is_linked:
+            group_vars[input.name]=None
+
+def set_string_variables_at_execution(interface_node, var_name):
+    # get the ID of the input
+    prototype = get_node_prototype(interface_node.signature[1:-1], 
+                                   interface_node.base_tree)
+    tree = prototype.node_tree
+    for interface_item in tree.interface.items_tree:
+        if interface_item.item_type == 'PANEL': continue
+        if interface_item.name == var_name: break
+    else:
+        prRed(f"Failed to get value for variable ${var_name}."); return
+    var_value = interface_node.evaluate_input(interface_item.identifier)
+    group_key = ''.join(interface_node.signature[1:-1])
+    group_vars=interface_node.mContext.string_variables.get(group_key, {})
+    group_vars[var_name]=var_value
 
 ##################################################################################################
 # groups and changing sockets -- this is used extensively by Schema.
@@ -496,14 +526,14 @@ def import_metarig_data(metarig_data : dict, ):
                     armature_data['matrix'][12:16], )
             )
         prGreen (armature_data['name'])
-        
+
         # have to add it to the view layer to switch modes.
         collection = get_default_collection(collection_type="ARMATURE")
         collection.objects.link(armature_object)
         # we'll do this to ensure it is actually in the scene for the mode switch
         context.scene.collection.objects.link(armature_object)
         switch_mode('EDIT', objects = [armature_object])
-        
+
         while (children):
             child_name = children.pop()
             child_data = metarig_data[child_name]
@@ -525,8 +555,8 @@ def import_metarig_data(metarig_data : dict, ):
         context.scene.collection.objects.unlink(armature_object)
     # note that this will not correct if the object exists and is wrong.
     return armature_object
-    
-        
+
+
 def import_curve_data_to_object(curve_name, curve_data):
     # the curve data will come as a single curve's data
     from bpy import data
@@ -544,7 +574,7 @@ def import_curve_data_to_object(curve_name, curve_data):
             points_collection = spline.bezier_points
         else:
             spline.points.add(len(points_data)-1) # it starts with 1 already
-            
+
         for i, point_data in enumerate(points_data):
             if spline.type == 'BEZIER':
                 pt = spline.bezier_points[i]
@@ -689,7 +719,7 @@ def init_schema_dependencies(schema, all_nc):
 
 def check_and_add_root(n, roots, include_non_hierarchy=False):
     if (include_non_hierarchy * len(n.dependencies)) > 0:
-        return 
+        return
     elif len(n.hierarchy_dependencies) > 0:
         return
     roots.append(n)
@@ -699,7 +729,7 @@ def get_link_in_out(link):
     from_name, to_name = link.from_socket.node.name, link.to_socket.node.name
     # catch special bl_idnames and bunch the connections up
     if link.from_socket.node.bl_idname in replace_types:
-        from_name = link.from_socket.node.bl_idname 
+        from_name = link.from_socket.node.bl_idname
     if link.to_socket.node.bl_idname in replace_types:
         to_name = link.to_socket.node.bl_idname
     return from_name, to_name
@@ -726,7 +756,7 @@ def link_node_containers(tree_path_names, link, local_nc, from_suffix='', to_suf
     else:
         prRed(nc_from, nc_to, (*tree_path_names, from_name+from_suffix), (*tree_path_names, to_name+to_suffix))
         raise RuntimeError(wrapRed(f"Link not connected: {nc_from} -> {nc_to} in tree" ))
-    
+
 def get_all_dependencies(nc):
     from .base_definitions import GraphError
     """ find all dependencies for a mantis node"""
@@ -744,7 +774,7 @@ def get_all_dependencies(nc):
             if new_node not in nodes_checked:
                 check_nodes.append(new_node)
     return nodes
-                
+
 def get_all_nodes_of_type(base_tree, bl_idname):
     nodes = []
     check_nodes = list(base_tree.nodes)
@@ -773,7 +803,7 @@ def trace_all_nodes_from_root(root, nodes):
             if new_node not in nodes_checked:
                 check_nodes.append(new_node)
     return nodes
-            
+
 ##################################################################################################
 # misc
 ##################################################################################################
@@ -805,7 +835,7 @@ def all_trees_in_tree(base_tree, selected=False):
                 if selected == True and node.select == False:
                     continue
                 if new_tree := getattr(node, "node_tree", None):
-                    if new_tree in trees: continue 
+                    if new_tree in trees: continue
                     new_trees.append(new_tree)
                     trees.append(new_tree)
         check_trees = new_trees
@@ -817,7 +847,7 @@ def SugiyamaGraph(tree, iterations):
         class defaultview(object):
             w,h = 1,1
             xz = (0,0)
-        
+
         graph = Graph()
         no_links = set()
         verts = {}
@@ -832,7 +862,7 @@ def SugiyamaGraph(tree, iterations):
                 no_links.add(n.name)
                 graph.add_vertex(v)
             n.select=False
-            
+
         edges = []
         inverted_edges=[]
         not_a_root = set()
@@ -853,7 +883,7 @@ def SugiyamaGraph(tree, iterations):
         try:
             from grandalf.layouts import SugiyamaLayout
             # .C[0] is the first "graph core" that contains a connected graph.
-            sug = SugiyamaLayout(graph.C[0]) 
+            sug = SugiyamaLayout(graph.C[0])
             sug.init_all()
             sug.draw(iterations)
             # Digco is good for small graphs.
@@ -870,7 +900,7 @@ def SugiyamaGraph(tree, iterations):
                     n.location.x = v.view.xy[1]
                     n.location.y = v.view.xy[0]
                     n.select = True
-        
+
         # now we can take all the input nodes and try to put them in a sensible place
         # not sure why but this absolutely does not do anything
         for n_name in no_links:
@@ -897,7 +927,7 @@ def SugiyamaGraph(tree, iterations):
                 else: # we'll just position it next to the next node
                     n.location = next_node.location
                     n.location.x -= next_node.width*1.5
-        
+
 
 def project_point_to_plane(point, origin, normal):
     return point - normal.dot(point- origin)*normal
@@ -926,8 +956,8 @@ def gen_nc_input_for_data(socket):
                         "HideSocket"                           : classes["InputBoolean"],
                         #
                         "DriverSocket"                         : None,
-                        "DriverVariableSocket"                 : None, 
-                        "FCurveSocket"                         : None, 
+                        "DriverVariableSocket"                 : None,
+                        "FCurveSocket"                         : None,
                         "KeyframeSocket"                       : None,
                         "BoneCollectionSocket"                 : classes["InputString"],
                         #
@@ -974,7 +1004,7 @@ def gen_nc_input_for_data(socket):
                         "VectorEulerSocket"                    : classes["InputVector"],
                         "VectorTranslationSocket"              : classes["InputVector"],
                         "VectorScaleSocket"                    : classes["InputVector"],
-                        # Drivers             
+                        # Drivers
                         "EnumDriverVariableType"               : classes["InputString"],
                         "EnumDriverVariableEvaluationSpace"    : classes["InputString"],
                         "EnumDriverRotationMode"               : classes["InputString"],
@@ -1121,9 +1151,9 @@ def RibbonMeshEdgeLengths(m, ribbon):
         else:
             v2NextInd = bE[cap((i+1) , len(bE) - 1 )]
         v2 = m.vertices[bE[i]]; v2Next = m.vertices[v2NextInd]
-        
+
         v = v1.co.lerp(v2.co, 0.5); vNext = v1Next.co.lerp(v2Next.co, 0.5)
-        # get the center, edges may not be straight so total length 
+        # get the center, edges may not be straight so total length
         #  of one edge may be more than the ribbon center's length
         lengths.append(( v - vNext ).length)
     return lengths
@@ -1248,7 +1278,7 @@ def FindNearestPointOnWireMesh(m, pointsList):
                     v1 = vertData[i]
                     v2 = vertData[i+1]
                     prevDist = curDist
-                    offset = intersect_point_line(p, m.vertices[v1[0]].co, 
+                    offset = intersect_point_line(p, m.vertices[v1[0]].co,
                                                      m.vertices[v2[0]].co)[1]
             if (offset < 0):
                 offset = 0
@@ -1321,7 +1351,7 @@ def DetectRibbon(f, bm, skipMe):
         bEdge.append (f.loops[3].vert.index) # bottom-left
         nEdge = bm.edges.get([f.loops[1].vert, f.loops[2].vert])
         nFaces = nEdge.link_faces
-        if (len(nFaces) == 1): 
+        if (len(nFaces) == 1):
             cont = False
         else:
             for nFace in nFaces:
@@ -1334,7 +1364,7 @@ def DetectRibbon(f, bm, skipMe):
         if (cont == False): # we've reached the end, get the last two:
             tEdge.append (f.loops[1].vert.index) # top-right
             bEdge.append (f.loops[2].vert.index) # bottom-right
-            # this will create a loop for rings -- 
+            # this will create a loop for rings --
             #  "the first shall be the last and the last shall be first"
     return (tEdge,bEdge,circle)
 
@@ -1397,7 +1427,7 @@ def data_from_ribbon_mesh(m, factorsList, mat, ribbons = None, fReport = None):
         if (ribbons is None):
             if (fReport):
                 fReport(type = {'ERROR'}, message="No ribbon to get data from.")
-            else:  
+            else:
                 print ("No ribbon to get data from.")
             return None
     ret = []
@@ -1476,7 +1506,7 @@ def data_from_ribbon_mesh(m, factorsList, mat, ribbons = None, fReport = None):
 # If the sign of the error is meaningful, a simpler function
 # can be used.
 def do_bisect_search_by_magnitude(
-        owner, 
+        owner,
         attribute,
         index = None,
         test_function = None,
@@ -1529,4 +1559,4 @@ def do_bisect_search_by_magnitude(
             update_dg.update()
     else: # Loop has completed without finding a solution
         i = best_so_far
-        modify(owner, attribute, best_so_far, context = context); i+=1
+        modify(owner, attribute, best_so_far, context = context); i+=1

+ 1 - 1
visualize.py

@@ -161,7 +161,7 @@ def visualize_tree(m_nodes, base_tree, context):
 
             vis_tree = bpy.data.node_groups.new(base_tree.name+'_visualized', type='MantisVisualizeTree')
 
-            for i, m in enumerate(mantis_nodes):
+            for m in mantis_nodes:
                 nodes[m.signature]=gen_vis_node(m, vis_tree, all_links)
                 # useful for debugging: check the connections for nodes that are
                 # not in the parsed tree or available from trace_all_nodes_from_root.