Forráskód Böngészése

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
Brandenburg 1 hónapja
szülő
commit
980a110e2e
6 módosított fájl, 142 hozzáadás és 40 törlés
  1. 47 1
      base_definitions.py
  2. 8 0
      internal_containers.py
  3. 26 7
      readtree.py
  4. 4 3
      schema_solve.py
  5. 56 28
      utilities.py
  6. 1 1
      visualize.py

+ 47 - 1
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,41 @@ class MantisNode:
         for conn in self.hierarchy_connections:
             conn.reset_execution_recursive()
 
+
+    # 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.
+        all_vars = {} # maybe the individual nodes should store this as a class member, too...
+        name=""
+        do_once = False
+        for i in range(len(self.signature[:-1])):
+            if i == 0:
+                continue
+            name+=self.signature[i]
+            prWhite(name)
+            vars = self.mContext.string_variables.get(name, None)
+            if vars is None:
+                prRed("Can't get string variables for node")
+                print (vars, type(vars))
+                prRed (name)
+                prWhite(self.mContext.string_variables.keys())
+                for k in self.mContext.string_variables.keys():
+                    print (name == k)
+                    print (len(name), name,)
+                    print (len(k), k)
+                raise RuntimeError
+                continue
+            elif not vars:
+                prRed(self)
+            for var_name, var_value in vars.items():
+                do_once=True
+                string = string.replace("$"+var_name, var_value)
+        if do_once == False:
+            raise NotImplementedError
+        return string
+
     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
@@ -858,7 +894,17 @@ class MantisNode:
         # this trace() should give a key error if there is a problem
         #  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
+        try:
+            prop = trace[0][-1].parameters[trace[1].name] #trace[0] = the list of traced nodes; read its parameters
+        except Exception as e:
+            print (trace[0][-1])
+            print (trace[1].name)
+            print (trace[0][-1].parameters.keys())
+            print (trace[0][-1].parameters.values())
+            raise e
+        if isinstance(prop, str) and "$" in prop:
+            print (self, prop)
+            prop = self.apply_string_variables(prop)
         return prop
 
     def fill_parameters(self, ui_node=None)  -> None:

+ 8 - 0
internal_containers.py

@@ -11,6 +11,7 @@ class DummyNode(MantisNode):
         self.prepared = True
         self.uuid = uuid4()
         self.solver = None
+        self.did_set_variables = False
         if prototype:
             if prototype.bl_idname in ["MantisSchemaGroup"]:
                 self.node_type = 'DUMMY_SCHEMA'
@@ -32,6 +33,13 @@ 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
 
+    def bPrepare(self, bContext=None):
+        from .utilities import set_string_variables_during_exec
+        set_string_variables_during_exec(self, self.mContext)
+        self.did_set_variables = True # I just need to know if this is getting them all
+
+    def __del__(self):
+        print (self, self.did_set_variables)
 
 class NoOpNode(MantisNode):
     def __init__(self, signature, base_tree):

+ 26 - 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,25 @@ 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)
+                for input in nc.inputs:
+                    for l in input.links:
+                        print (l)
+                        raise RuntimeError
+                # 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

+ 4 - 3
schema_solve.py

@@ -175,8 +175,6 @@ class SchemaSolver:
 
 
 
-    def is_node_deeper_nested(self, queried_node, compare_node):
-        return len(compare_node.signature) < len(queried_node.signature)
 
     def gen_solve_iteration_mantis_nodes(self, frame_mantis_nodes, unprepared):
         for prototype_ui_node in self.tree.nodes:
@@ -204,6 +202,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
@@ -665,7 +666,7 @@ class SchemaSolver:
             if isinstance(to_ui_node, SchemaOutgoingConnection):
                 if isinstance(from_ui_node, (MantisNodeGroup, SchemaGroup)):
                     self.handle_link_from_subschema_to_output(frame_mantis_nodes, ui_link, to_ui_node)
-                self.held_links.append(ui_link) # is this wise? Why am I doing this?
+                self.held_links.append(ui_link)
                 continue
             # HOLD these links until prep is done a little later
             if isinstance(to_ui_node, (SchemaConstOutput, NodeGroupOutput)) or isinstance(to_ui_node, SchemaArrayOutput):

+ 56 - 28
utilities.py

@@ -104,10 +104,38 @@ 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
+    prev_group_key = ''
+    prev_group_vars = {}
+    for i in range(len(n.signature[:-1])):
+        if i == 0: continue # this will cut any AUTOGEN or None in the base
+        prev_group_key+=n.signature[i]
+        prev_group_vars=mContext.string_variables.get(prev_group_key, {})
+    # IS THIS WISE???
+    from copy import deepcopy # so nothing spooky will happen
+    group_vars = mContext.string_variables["".join(n.signature[1:])] = deepcopy(prev_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
+
+
+def set_string_variables_during_exec(n, mContext):
+    print(n)
+    pass
+    # so for this we need to get the UI node to get the string
+    # when a node is executed, we check for string variables that were set at runtime
+    # we need the dummy node for this
+
+
 
 ##################################################################################################
 # groups and changing sockets -- this is used extensively by Schema.
@@ -496,14 +524,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 +553,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 +572,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 +717,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 +727,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 +754,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 +772,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 +801,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 +833,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 +845,7 @@ def SugiyamaGraph(tree, iterations):
         class defaultview(object):
             w,h = 1,1
             xz = (0,0)
-        
+
         graph = Graph()
         no_links = set()
         verts = {}
@@ -832,7 +860,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 +881,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 +898,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 +925,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 +954,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 +1002,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 +1149,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 +1276,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 +1349,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 +1362,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 +1425,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 +1504,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 +1557,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.