| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643 | from .utilities import prRed, prGreen, prPurple, prWhite, prOrange, \                        wrapRed, wrapGreen, wrapPurple, wrapWhite, wrapOrange# what in the cuss is this horrible abomination??def class_for_mantis_prototype_node(prototype_node):    """ This is a class which returns a class to instantiate for        the given prototype node."""    #from .node_container_classes import TellClasses    from mantis import xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers    classes = {}    for module in [xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers]:        for cls in module.TellClasses():            classes[cls.__name__] = cls    # I could probably do a string.replace() here    # But I actually think this is a bad idea since I might not    #  want to use this name convention in the future    #  this is easy enough for now, may refactor.    #    # kek, turns out it was completely friggin' inconsistent already    if prototype_node.bl_idname == 'xFormRootNode':        return classes["xFormRoot"]    elif prototype_node.bl_idname == 'xFormArmatureNode':        return classes["xFormArmature"]    elif prototype_node.bl_idname == 'xFormBoneNode':        return classes["xFormBone"]    elif prototype_node.bl_idname == 'xFormGeometryObject':        return classes["xFormGeometryObject"]    elif prototype_node.bl_idname == 'linkInherit':        return classes["LinkInherit"]        # BAD need to fix the above, bl_idname is not consistent    # Copy's    elif prototype_node.bl_idname == 'LinkCopyLocation':        return classes["LinkCopyLocation"] # also bad    elif prototype_node.bl_idname == 'LinkCopyRotation':        return classes["LinkCopyRotation"]    elif prototype_node.bl_idname == 'LinkCopyScale':        return classes["LinkCopyScale"]    elif prototype_node.bl_idname == 'LinkCopyTransforms':        return classes["LinkCopyTransforms"]    elif prototype_node.bl_idname == 'LinkTransformation':        return classes["LinkTransformation"]    # Limits    elif prototype_node.bl_idname == 'LinkLimitLocation':        return classes["LinkLimitLocation"]    elif prototype_node.bl_idname == 'LinkLimitRotation':        return classes["LinkLimitRotation"]    elif prototype_node.bl_idname == 'LinkLimitScale':        return classes["LinkLimitScale"]    elif prototype_node.bl_idname == 'LinkLimitDistance':        return classes["LinkLimitDistance"]    # tracking    elif prototype_node.bl_idname == 'LinkStretchTo':        return classes["LinkStretchTo"]    elif prototype_node.bl_idname == 'LinkDampedTrack':        return classes["LinkDampedTrack"]    elif prototype_node.bl_idname == 'LinkLockedTrack':        return classes["LinkLockedTrack"]    elif prototype_node.bl_idname == 'LinkTrackTo':        return classes["LinkTrackTo"]    # misc    elif prototype_node.bl_idname == 'LinkInheritConstraint':        return classes["LinkInheritConstraint"]    # IK    elif prototype_node.bl_idname == 'LinkInverseKinematics':        return classes["LinkInverseKinematics"]    elif prototype_node.bl_idname == 'LinkSplineIK':        return classes["LinkSplineIK"]    # utilities    elif prototype_node.bl_idname == 'InputFloatNode':        return classes["InputFloat"]    elif prototype_node.bl_idname == 'InputVectorNode':        return classes["InputVector"]    elif prototype_node.bl_idname == 'InputBooleanNode':        return classes["InputBoolean"]    elif prototype_node.bl_idname == 'InputBooleanThreeTupleNode':        return classes["InputBooleanThreeTuple"]    elif prototype_node.bl_idname == 'InputRotationOrderNode':        return classes["InputRotationOrder"]    elif prototype_node.bl_idname == 'InputTransformSpaceNode':        return classes["InputTransformSpace"]    elif prototype_node.bl_idname == 'InputStringNode':        return classes["InputString"]    elif prototype_node.bl_idname == 'InputQuaternionNode':        return classes["InputQuaternion"]    elif prototype_node.bl_idname == 'InputQuaternionNodeAA':        return classes["InputQuaternionAA"]    elif prototype_node.bl_idname == 'InputMatrixNode':        return classes["InputMatrix"]    elif prototype_node.bl_idname == 'MetaRigMatrixNode':        return classes["InputMatrix"]    elif prototype_node.bl_idname == 'InputLayerMaskNode':        return classes["InputLayerMask"]    # geometry    elif prototype_node.bl_idname == 'GeometryCirclePrimitive':        return classes["CirclePrimitive"]    # Deformers:    elif prototype_node.bl_idname == 'DeformerArmature':        return classes["DeformerArmature"]            # every node before this point is not guarenteed to follow the pattern    # but every node not checked above does follow the pattern.        try:        return classes[ prototype_node.bl_idname ]    except KeyError:        pass        if prototype_node.bl_idname in [                                     "NodeReroute",                                    "NodeGroupInput",                                    "NodeGroupOutput",                                    "MantisNodeGroup",                                    "NodeFrame",                                   ]:           return None        prRed(prototype_node.bl_idname)    raise RuntimeError("Failed to create node container for: %s" % prototype_node.bl_idname)    return None# This is really, really stupid HACKdef gen_nc_input_for_data(socket):    # Class List #TODO deduplicate    from mantis import xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers    classes = {}    for module in [xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers]:        for cls in module.TellClasses():            classes[cls.__name__] = cls    #    socket_class_map = {                        "MatrixSocket"                      : classes["InputMatrix"],                        "xFormSocket"                       : None,                        "xFormMultiSocket"                  : None,                        "RelationshipSocket"                : classes["xFormRoot"], # world in                        "DeformerSocket"                    : classes["xFormRoot"], # world in                        "GeometrySocket"                    : classes["InputExistingGeometryData"],                        "EnableSocket"                      : classes["InputBoolean"],                        "HideSocket"                        : classes["InputBoolean"],                        #                        "DriverSocket"                      : None,                        "DriverVariableSocket"              : None,                         "FCurveSocket"                      : None,                         "KeyframeSocket"                    : None,                        "LayerMaskInputSocket"              : classes["InputLayerMask"],                        "LayerMaskSocket"                   : classes["InputLayerMask"],                        #                        "xFormParameterSocket"              : None,                        "ParameterBoolSocket"               : classes["InputBoolean"],                        "ParameterIntSocket"                : classes["InputFloat"],  #TODO: make an Int node for this                        "ParameterFloatSocket"              : classes["InputFloat"],                        "ParameterVectorSocket"             : classes["InputVector"],                        "ParameterStringSocket"             : classes["InputString"],                        #                        "TransformSpaceSocket"              : classes["InputTransformSpace"],                        "BooleanSocket"                     : classes["InputBoolean"],                        "BooleanThreeTupleSocket"           : classes["InputBooleanThreeTuple"],                        "RotationOrderSocket"               : classes["InputRotationOrder"],                        "QuaternionSocket"                  : classes["InputQuaternion"],                        "QuaternionSocketAA"                : classes["InputQuaternionAA"],                        "IntSocket"                         : classes["InputFloat"],                        "StringSocket"                      : classes["InputString"],                        #                        "BoolUpdateParentNode"              : classes["InputBoolean"],                        "IKChainLengthSocket"               : classes["InputFloat"],                        "EnumInheritScale"                  : classes["InputString"],                        "EnumRotationMix"                   : classes["InputString"],                        "EnumRotationMixCopyTransforms"     : classes["InputString"],                        "EnumMaintainVolumeStretchTo"       : classes["InputString"],                        "EnumRotationStretchTo"             : classes["InputString"],                        "EnumTrackAxis"                     : classes["InputString"],                        "EnumUpAxis"                        : classes["InputString"],                        "EnumLockAxis"                      : classes["InputString"],                        "EnumLimitMode"                     : classes["InputString"],                        "EnumYScaleMode"                    : classes["InputString"],                        "EnumXZScaleMode"                   : classes["InputString"],                        # Deformers                        "EnumSkinning"                      : classes["InputString"],                        #                        "FloatSocket"                       : classes["InputFloat"],                        "FloatFactorSocket"                 : classes["InputFloat"],                        "FloatPositiveSocket"               : classes["InputFloat"],                        "FloatAngleSocket"                  : classes["InputFloat"],                        "VectorSocket"                      : classes["InputVector"],                        "VectorEulerSocket"                 : classes["InputVector"],                        "VectorTranslationSocket"           : classes["InputVector"],                        "VectorScaleSocket"                 : classes["InputVector"],                        # Drivers                                     "EnumDriverVariableType"            : classes["InputString"],                        "EnumDriverVariableEvaluationSpace" : classes["InputString"],                        "EnumDriverRotationMode"            : classes["InputString"],                        "EnumDriverType"                    : classes["InputString"],                       }    return socket_class_map.get(socket.bl_idname, None)class DummyLink:    #gonna use this for faking links to keep the interface consistent    def __init__(self, from_socket, to_socket, nc_from=None, nc_to=None, original_from=None):        self.from_socket = from_socket        self.to_socket = to_socket        self.nc_from = nc_from        self.nc_to = nc_to        if (original_from):            self.original_from = original_from        else:            self.original_from = self.from_socket    def __repr__(self):        return(self.nc_from.__repr__()+":"+self.from_socket.name + " -> " + self.nc_to.__repr__()+":"+self.to_socket.name)# A fuction for getting to the end of a Reroute.def socket_seek(start_link, links):    link = start_link    while(link.from_socket):        for newlink in links:            if link.from_socket.node.inputs:                if newlink.to_socket == link.from_socket.node.inputs[0]:                    link=newlink; break        else:            break    return link.from_socket    def clear_reroutes(links):    kept_links, rerouted_starts = [], []    rerouted = []    all_links = links.copy()    while(all_links):        link = all_links.pop()        to_cls = link.to_socket.node.bl_idname        from_cls = link.from_socket.node.bl_idname        reroute_classes = ["NodeReroute"]        if (to_cls in reroute_classes and            from_cls in reroute_classes):                rerouted.append(link)        elif (to_cls in reroute_classes and not            from_cls in reroute_classes):                rerouted.append(link)        elif (from_cls in reroute_classes and not            to_cls in reroute_classes):                rerouted_starts.append(link)        else:            kept_links.append(link)    for start in rerouted_starts:        from_socket = socket_seek(start, rerouted)        new_link = DummyLink(from_socket=from_socket, to_socket=start.to_socket, nc_from=None, nc_to=None)        kept_links.append(new_link)    return kept_linksdef reroute_common(nc, nc_to, all_nc):    # we need to do this: go  to the to-node    # then reroute the link in the to_node all the way to the beginning    # so that the number of links in "real" nodes is unchanged    # then the links in the dummy nodes need to be deleted    watch=False    # if nc.signature[-1] == 'NodeGroupOutput': watch=True    for inp_name, inp in nc.inputs.items():        # assume each input socket only has one input for now        if inp.is_connected:            while (inp.links):                in_link = inp.links.pop()                from_nc = in_link.from_node                from_socket = in_link.from_socket                links = []                from_links = from_nc.outputs[from_socket].links.copy()                while(from_links):                    from_link = from_links.pop()                    if from_link == in_link:                        continue # DELETE the dummy node link                    links.append(from_link)                from_nc.outputs[from_socket].links = links                down = nc_to.outputs[inp_name]                for downlink in down.links:                    downlink.from_node = from_nc                    downlink.from_socket = from_socket                    from_nc.outputs[from_socket].links.append(downlink)                    if hasattr(downlink.to_node, "reroute_links"):                        # Recurse!                        downlink.to_node.reroute_links(downlink.to_node, all_nc)        def reroute_links_grp(nc, all_nc):    nc_to = all_nc.get( ( *nc.signature, "NodeGroupInput") )    reroute_common(nc, nc_to, all_nc)def reroute_links_grpout(nc, all_nc):    nc_to = all_nc.get( ( *nc.signature[:-1],) )    reroute_common(nc, nc_to, all_nc)def data_from_tree(base_tree, tree_path = [None], dummy_nodes = {}, all_nc = {}):    from mantis.node_container_common import NodeSocket    from .internal_containers import DummyNode    nc_dict = {}    tree_path_names = [tree.name for tree in tree_path if hasattr(tree, "name")]    all_child_ng = []    tree = base_tree    if tree_path[-1]:        tree = tree_path[-1].node_tree        # Start by looking through the nodes and making nc's where possible    #  store the groups, we'll process them soon.    for np in tree.nodes:        if (nc_cls := class_for_mantis_prototype_node(np)):            nc = nc_cls( sig := (None, *tree_path_names, np.name) , base_tree)            nc_dict[sig] = nc; all_nc[sig] = nc        elif np.bl_idname in ["NodeGroupInput", "NodeGroupOutput"]: # make a Dummy Node            # we only want ONE dummy in/out per tree_path, so use the bl_idname            sig = (None, *tree_path_names, np.bl_idname)            if nc_dict.get(sig):                continue            nc = DummyNode( signature=sig , base_tree=base_tree, prototype=np )            nc_dict[sig] = nc; all_nc[sig] = nc#; dummy_nodes[sig] = nc            # dummy_nodes[sig]=nc            if np.bl_idname in ["NodeGroupOutput"]:                nc.reroute_links = reroute_links_grpout                dummy_nodes[sig]=nc        elif np.bl_idname in  ["MantisNodeGroup"]:            # if we do this here, no duplicate links.            nc = DummyNode( signature= (sig := (None, *tree_path_names, np.name) ), base_tree=base_tree, prototype=np )            nc_dict[sig] = nc; all_nc[sig] = nc; dummy_nodes[sig] = nc            nc.reroute_links = reroute_links_grp            all_child_ng.append(np)        # else:            # prRed(np.bl_idname)        # Then deal with the links in the current tree and the held_nodes.    kept_links = clear_reroutes(list(tree.links))        for link in kept_links:        from_name = link.from_socket.node.name        to_name = link.to_socket.node.name        if link.from_socket.node.bl_idname in ["NodeGroupInput", "NodeGroupOutput"]:            from_name = link.from_socket.node.bl_idname        if link.to_socket.node.bl_idname in ["NodeGroupInput", "NodeGroupOutput"]:            to_name = link.to_socket.node.bl_idname        if link.to_socket.node.bl_idname in ["MantisNodeGroup"]:            continue                nc_from = nc_dict.get( tuple([None] + tree_path_names + [from_name]) )        nc_to = nc_dict.get( tuple([None] + tree_path_names + [to_name]) )                if (nc_from and nc_to):            from_s, to_s = link.from_socket.name, link.to_socket.name            if nc_to.node_type == "DUMMY": to_s = link.to_socket.identifier            if nc_from.node_type == "DUMMY": from_s = link.from_socket.identifier            connection = nc_from.outputs[from_s].connect(node=nc_to, socket=to_s)        else:            raise RuntimeError(wrapRed("Link not connected: %s -> %s in tree %s" % (from_name, to_name, tree_path_names[-1])))    nc_from = None; nc_to = None #clear them, since we use the same variable names again            # Now, descend into the Node Group    for ng in  all_child_ng:        nc_to = nc_dict[(None, *tree_path_names, ng.name)]        for inp in ng.inputs:            # nc_to = nc_dict.get((None, *tree_path_names, ng.name))            to_s = inp.identifier            if not inp.is_linked:                nc_cls = gen_nc_input_for_data(inp)                # at this point we also need to get the "Dummy Node" for                #  this node group.                if (nc_cls):                    sig = ("MANTIS_AUTOGENERATED", *tree_path_names, ng.name, inp.name, inp.identifier)                    nc_from = nc_cls(sig, base_tree)                    # HACK HACK HACK                    nc_from.inputs = {}                    nc_from.outputs = {inp.name:NodeSocket(name = inp.name, node=nc_from)}                    from .node_container_common import get_socket_value                    nc_from.parameters = {inp.name:get_socket_value(inp)}                    # HACK HACK HACK                    nc_dict[sig] = nc_from; all_nc[sig] = nc_from                    from_s = inp.name                else:                    prRed("No available auto-generated class for input %s:%s:%s" % (tree_path, ng.name, inp.name))            else: # We need to handle the incoming connections                for link in inp.links: #Usually there will only be 1                    from_socket = link.from_socket                    if (link.from_socket.node.bl_idname == "NodeReroute"):                        from_socket = socket_seek(link, list(tree.links))                    sig =  tuple( [None] + tree_path_names +[from_socket.node.name])                                        from_s = from_socket.name                    if (link.from_socket.node.bl_idname in ["NodeGroupInput"]):                        sig =  tuple( [None] + tree_path_names +[from_socket.node.bl_idname])                        from_s = from_socket.identifier                    nc_from = nc_dict.get(sig)            nc_from.outputs[from_s].connect(node=nc_to, socket=to_s)            nc_from = None        nc_to = None        # Recurse!        data_from_tree(base_tree, tree_path+[ng], dummy_nodes, all_nc)    return dummy_nodes, all_ncdef establish_node_connections(nc):    # This is ugly bc it adds parameters to an object    #  but it's kinda necesary to do it after the fact; and it    #  wouldn't be ugly if I just initialized the parameter elsewhere    connections, hierarchy_connections = [], []    for socket in nc.outputs.values():        for link in socket.links:            connections.append(link.to_node)            # this may catch custom properties... too bad.            if link.from_socket in from_name_filter:                continue            if link.to_socket in to_name_filter:                continue            hierarchy_connections.append(link.to_node)    nc.connected_to = connections    nc.hierarchy_connections = hierarchy_connections    if nc.node_type == 'DUMMY':        nc.hierarchy_connections = []def insert_lazy_parents(nc):    from .link_containers import LinkInherit    from .node_container_common import NodeLink    inherit_nc = None    if nc.inputs["Relationship"].is_connected:        link = nc.inputs["Relationship"].links[0]        from_nc = link.from_node        if from_nc.__class__.__name__ == "xFormRoot":            return        if from_nc.node_type in ["XFORM"] and link.from_socket in ["xForm Out"]:            inherit_nc = LinkInherit(("MANTIS_AUTOGENERATED", *nc.signature[1:], "LAZY_INHERIT"), nc.base_tree)            for from_link in from_nc.outputs["xForm Out"].links:                if from_link.to_node == nc and from_link.to_socket == "Relationship":                    break # this is it            from_link.to_node = inherit_nc; from_link.to_socket="Parent"                        links=[]            while (nc.inputs["Relationship"].links):                to_link = nc.inputs["Relationship"].links.pop()                if to_link.from_node == from_nc and to_link.from_socket == "xForm Out":                    continue # don't keep this one                links.append(to_link)                        nc.inputs["Relationship"].links=links            link=NodeLink(from_node=inherit_nc, from_socket="Inheritance", to_node=nc, to_socket="Relationship")            inherit_nc.inputs["Parent"].links.append(from_link)                        inherit_nc.parameters = {                                     "Parent":None,                                     "Inherit Rotation":True,                                     "Inherit Scale":'FULL',                                     "Connected":False,                                    }            # because the from node may have already been done.            establish_node_connections(from_nc)            establish_node_connections(inherit_nc)            # and the inherit node never was    return inherit_ncdef parse_tree(base_tree, do_reroute=True):    dummy_nodes, all_nc =  data_from_tree(base_tree, tree_path = [None], dummy_nodes = {}, all_nc = {})    if do_reroute:        for sig, dummy in dummy_nodes.items():            if (hasattr(dummy, "reroute_links")):                dummy.reroute_links(dummy, all_nc)                all_nc = list(all_nc.values()).copy()    kept_nc = {}    while (all_nc):        nc = all_nc.pop()        nc.fill_parameters()        if (nc.node_type in ['XFORM']) and ("Relationship" in nc.inputs.keys()):            new_nc = insert_lazy_parents(nc)            if new_nc:                kept_nc[new_nc.signature]=new_nc        establish_node_connections(nc)        if nc.connected_to == 0:            continue        if nc.node_type == 'DUMMY' and do_reroute:            continue        kept_nc[nc.signature]=nc    # return {}    return kept_ncfrom_name_filter = ["Driver", ]to_name_filter = [                   "Custom Object xForm Override",                   "Custom Object",                   "Deform Bones"                 ]def sort_tree_into_layers(nodes, context):    from time import time    from mantis.node_container_common import (get_depth_lines,      node_depth)    # All this function needs to do is sort out the hierarchy and    #  get things working in order of their dependencies.        roots, drivers = [], []    start = time()        for n in nodes.values():        if n.node_type == 'DRIVER': drivers.append(n)        # ugly but necesary to ensure that drivers are always connected.        if not (hasattr(n, 'inputs')) or ( len(n.inputs) == 0):            roots.append(n)        elif (hasattr(n, 'inputs')):            none_connected = True            for inp in n.inputs.values():                if inp.is_linked: none_connected = False            if none_connected: roots.append(n)        layers, nodes_heights = {}, {}    #Possible improvement: unify roots if they represent the same data    all_sorted_nodes = []    for root in roots:        nodes_heights[root.signature] = 0        depth_lines = get_depth_lines(root)[0]                for n in nodes.values():            if n.signature not in (depth_lines.keys()):                continue #belongs to a different root            d = nodes_heights.get(n.signature, 0)            if (new_d := node_depth(depth_lines[n.signature])) > d:                d = new_d            nodes_heights[n.signature] = d                    for k, v in nodes_heights.items():        if (layer := layers.get(v, None)):            layer.append(nodes[k]) # add it to the existing layer        else: layers[v] = [nodes[k]] # or make a new layer with the node        all_sorted_nodes.append(nodes[k]) # add it to the sorted list        for n in nodes.values():        if n not in all_sorted_nodes:            for drv in drivers:                if n in drv.connected_to:                    depth = nodes_heights[drv.signature] + 1                    nodes_heights[n.signature] = depth                    # don't try to push downstream deps up bc this                    #  is a driver and it will be done in the                    #  finalize pass anyway                    if (layer := layers.get(depth, None)):                        layer.append(n)                    else: layers[v] = [n]                else:                    prRed(n)                    raise RuntimeError(wrapRed("Failed to depth-sort nodes (because of a driver-combine node?)"))    #    prGreen("Sorting depth for %d nodes finished in %s seconds" %               (len(nodes), time() - start))        keys = list(layers.keys())    keys.sort()        if (False): # True to print the layers        for i in keys:            # print_layer = [l_item for l_item in layers[i] if l_item.node_type in ["XFORM",]]# "LINK", "DRIVER"]]            print_layer = [l_item for l_item in layers[i]]            print(wrapGreen("%d: " % i), wrapWhite("%s" % print_layer))    return layersdef execute_tree(nodes, base_tree, context):    import bpy    from time import time    from mantis.node_container_common import GraphError    start_time  = time()    original_active = context.view_layer.objects.active        layers = sort_tree_into_layers(nodes, context)    start_execution_time = time()        #             Execute the first pass (xForm, Utility)              #    for i in range(len(layers)):        for node in layers[i]:            if (node.node_type in ['XFORM', 'UTILITY']):                try:                    node.bExecute(context)                except Exception as e:                    prRed("Execution failed at %s" % node); raise e    #                     Switch to Pose Mode                          #    active = None    switch_me = []    for n in nodes.values():        # if it is a armature, switch modes        # total hack                   #kinda dumb        if ((hasattr(n, "bGetObject")) and (n.__class__.__name__ == "xFormArmature" )):            try:                ob = n.bGetObject()            except KeyError: # for bones                ob = None            # TODO this will be a problem if and when I add mesh/curve stuff            if (hasattr(ob, 'mode') and ob.mode == 'EDIT'):                switch_me.append(ob)                active = ob                context.view_layer.objects.active = ob# need to have an active ob, not None, to switch modes.            # we override selected_objects to prevent anyone else from mode-switching    # TODO it's possible but unlikely that the user will try to run a     #    graph with no armature nodes in it.    if (active):        bpy.ops.object.mode_set({'active_object':active, 'selected_objects':switch_me}, mode='POSE')        #               Execute second pass (Link, Driver)                 #    for i in range(len(layers)):        for n in layers[i]:            # Now do the Link & Driver nodes during the second pass.            if (n.node_type in ['LINK', 'DRIVER']):                try:                    n.bExecute(context)                   except GraphError:                    pass                                     except Exception as e:                    print (n); raise e                        #                          Finalize                                #    for i in range(len(layers)):        for node in layers[i]:            if (hasattr(node, "bFinalize")):                node.bFinalize(context)            for n in nodes.values():        # if it is a armature, switch modes        # total hack                   #kinda dumb        if ((hasattr(n, "bGetObject")) and (n.__class__.__name__ == "xFormArmature" )):            try:                ob = n.bGetObject()            except KeyError: # for bones                ob = None            # TODO this will be a problem if and when I add mesh/curve stuff            if (hasattr(ob, 'mode') and ob.mode == 'POSE'):                switch_me.append(ob)                active = ob    if (active):        bpy.ops.object.mode_set({'active_object':active, 'selected_objects':switch_me}, mode='OBJECT')        prGreen("Executed Tree in %s seconds" % (time() - start_execution_time))    prGreen("Finished executing tree in %f seconds" % (time() - start_time))    if (original_active):        context.view_layer.objects.active = original_active        original_active.select_set(True)
 |