| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474 | from mantis.utilities import (prRed, prGreen, prPurple, prWhite,                              prOrange,                              wrapRed, wrapGreen, wrapPurple, wrapWhite,                              wrapOrange,)from mantis.base_definitions import GraphError, CircularDependencyError# BE VERY CAREFUL# the x_containers files import * from this file# so all the top-level imports are carried overdef get_socket_value(node_socket):    if node_socket.bl_idname in  ['RelationshipSocket', 'xFormSocket']:        return None    elif hasattr(node_socket, "default_value"):        from .utilities import to_mathutils_value        default_value_type = type(node_socket.default_value)        math_val = to_mathutils_value(node_socket)        if math_val:            return math_val        # maybe we can use it directly.. ?        elif ( (default_value_type == str) or (default_value_type == bool) or             (default_value_type == float) or (default_value_type == int) ):            return node_socket.default_value        return None# TODO: unify the fill_paramaters for auto-gen nodesdef fill_parameters(nc):    from .utilities import get_node_prototype    if (nc.signature[0] is "MANTIS_AUTOGENERATED"):        return None    else:        np = get_node_prototype(nc.signature, nc.base_tree)    if ( not np ):        raise RuntimeError(wrapRed("No node prototype found for... %s" % ( [nc.base_tree] + list(nc.signature[1:]) ) ) )    for key in nc.parameters.keys():        node_socket = np.inputs.get(key)        # if (nc.signature[0] is "MANTIS_AUTOGENERATED"):            # node_socket = None            # for node_socket in np.inputs:                # if node_socket.identifier == nc.signature[-1]:                    # break        if nc.parameters[key] is not None:            continue # will be filled by the node itself        if not node_socket:            #maybe the node socket has no name            if ( ( len(np.inputs) == 0) and ( len(np.outputs) == 1) ):                # this is a simple input node.                node_socket = np.outputs[0]            elif key == 'Name': # for Links we just use the Node Label                nc.parameters[key] = np.label                continue            else:                pass        if node_socket:            if node_socket.bl_idname in  ['RelationshipSocket', 'xFormSocket']:                continue            elif hasattr(node_socket, "default_value"):                if (value := get_socket_value(node_socket)) is not None:                    nc.parameters[key] = value                else:                    raise RuntimeError(wrapRed("No value found for " + nc.__repr__() + " when filling out node parameters for " + np.name + "::"+node_socket.name))            else:                passdef evaluate_input(node_container, input_name):    if not (node_container.inputs.get(input_name)):        # just return the parameter, there is no socket associated        return node_container.parameters.get(input_name)    trace = trace_single_line(node_container, input_name)    # this should give a key error if there is a problem    #  it is NOT handled here because it should NOT happen    prop = trace[0][-1].parameters[trace[1].name]    return propdef check_for_driver(node_container, input_name, index = None):    prop = evaluate_input(node_container, input_name)    if (index is not None):        prop = prop[index]    # This should work, even for None.    return (prop.__class__.__name__ == 'MantisDriver')def trace_node_lines(node_container):    """ Tells the depth of a node within the node tree. """    node_lines = []    if hasattr(node_container, "inputs"):        for key, socket in node_container.inputs.items():            # Recrusive search through the tree.            #  * checc each relevant input socket in the node            #  * for EACH input, find the node it's connected to            #    * repeat from here until you get all the lines            if ( ( key in ["Relationship", "Parent", "Input Relationship", "Target"])                          and (socket.is_connected) ):                # it is necesary to check the key because of Link nodes,                #   which don't really traverse like normal.                # TODO: see if I can refactor this to make it traverse                other = socket.from_node                if (other):                    other_lines = trace_node_lines(other)                    if not other_lines:                        node_lines.append([other])                    for line in other_lines:                        node_lines.append( [other] + line )    return node_lines# TODO: modify this to work with multi-input nodesdef trace_single_line(node_container, input_name):    # DO: refactor this for new link class    """Traces a line to its input."""    nodes = [node_container]    if hasattr(node_container, "inputs"):        # Trace a single line        if (socket := node_container.inputs.get(input_name) ):            while (socket.is_linked):                link = socket.links[0] # inputs can only get one link.                other = link.from_node.outputs.get(link.from_socket)                if (other):                    socket = other                    if socket.can_traverse:                        socket = socket.traverse_target                        nodes.append(socket.node)                    else: # this is an output.                        nodes.append(socket.node)                        break                else:                    break    return nodes, socket# this is same as the other, just flip from/to and in/outdef trace_single_line_up(node_container, output_name,):    """ Tells the depth of a node within the node tree. """    nodes = [node_container]    if hasattr(node_container, "outputs"):        # Trace a single line        if (socket := node_container.outputs.get(output_name) ):            while (socket.is_linked):                # This is bad, but it's efficient for nodes that only expect                #  one path along the given line                link = socket.links[0]                other = link.to_node.inputs.get(link.to_socket)                if (other):                    socket = other                    if socket.can_traverse:                        socket = socket.traverse_target                        nodes.append(socket.node)                    else: # this is an input.                        nodes.append(socket.node)                        break                else:                    break    return nodes, socketdef trace_all_lines_up(nc, output_name):    copy_items = {}    for item in dir(nc):        if "__" not in item:            copy_items[item]=getattr(nc, item)    # we want to copy it, BUT:    copy_items["outputs"]:{output_name:nc.outputs[output_name]}    # override outputs with just the one we care about.        check_me = type('', (object,), copy_items)    return get_depth_lines(check_me)[1]def get_depth_lines(root):    path, seek, nc_path = [0,], root, [root,]    lines, nc_paths = {}, {}    nc_len = len (root.hierarchy_connections)-1    curheight=0    while (path[0] <= nc_len):        nc_path.append(nc_path[-1].hierarchy_connections[path[-1]])        if (not (node_lines  := lines.get(nc_path[-1].signature, None))):            node_lines = lines[nc_path[-1].signature] = set()        if (not (node_paths  := nc_paths.get(nc_path[-1].signature, None))):            node_paths = nc_paths[nc_path[-1].signature] = set()        node_lines.add(tuple(path)); node_paths.add(tuple(nc_path))        if nc_path[-1].hierarchy_connections:            path.append(0); curheight+=1        else:            path[curheight] = path[curheight] + 1            nc_path.pop()            connected_nodes = nc_path[-1].hierarchy_connections            if ( path[-1] <= len(connected_nodes)-1 ):                seek = connected_nodes[path[-1]]            elif curheight > 0:                while(len(path) > 1):                    path.pop(); curheight -= 1; path[curheight]+=1; nc_path.pop()                    if ( (len(nc_path)>1) and path[-1] < len(nc_path[-1].hierarchy_connections) ):                        break     return lines, nc_paths    def node_depth(lines):    maxlen = 0    for line in lines:        if ( (l := len(line) ) > maxlen):            maxlen = l    return maxlen    #TODO rewrite this so it'll work with new nc_path thing#  not a high priority bc this was debugging code for something that#  works and has since ben refactored to work betterdef printable_path(nc, path, no_wrap = False):    string = ""; cur_nc = nc    #DO: find out if the copy is necessary    path = path.copy(); path.reverse()    dummy = lambda a : a    while path:        wrap = dummy        if not no_wrap:            wrap=wrapWhite            if (cur_nc.node_type == 'DRIVER'):                wrap = wrapPurple            elif (cur_nc.node_type == 'XFORM'):                wrap = wrapOrange            elif (cur_nc.node_type == 'LINK'):                wrap = wrapGreen        string += wrap(cur_nc.__repr__()) + " -> "        try:            cur_nc = get_from_path(cur_nc, [path.pop()] )        except IndexError:            string = string[:-4]            return string    string = string[:-4]    return string    # why is this not printing groups in brackets?def get_parent(node_container, type = 'XFORM'):    # type variable for selecting whether to get either     #   the parent xForm  or the inheritance node    node_line, socket = trace_single_line(node_container, "Relationship")    parent_nc = None    for i in range(len(node_line)):        # check each of the possible parent types.        if ( (node_line[ i ].__class__.__name__ == 'LinkInherit') ):            try: # it's the next one                if (type == 'XFORM'):                    return node_line[ i + 1 ]                else: # type = 'LINK'                    return node_line[ i ]            except IndexError: # if there is no next one...                return None # then there's no parent!    return None    # TODO!    #    # make this do shorthand parenting - if no parent, then use World    #  if the parent node is skipped, use the previous node (an xForm)    #  with default settings.    # it is OK to generate a new, "fake" node container for this!def get_target_and_subtarget(node_container, linkOb, input_name = "Target"):    from bpy.types import PoseBone, Object, SplineIKConstraint, ArmatureModifier    subtarget = ''; target = node_container.evaluate_input(input_name)    if target:        if (isinstance(target.bGetObject(), PoseBone)):            subtarget = target.bGetObject().name            target = target.bGetParentArmature()        elif (isinstance(target.bGetObject(), Object) ):            target = target.bGetObject()        else:            raise RuntimeError("Cannot interpret linkOb target!")        if   (isinstance(linkOb, SplineIKConstraint)):            if target and target.type not in ["CURVE"]:                raise GraphError(wrapRed("Error: %s requires a Curve input, not %s" %                                 (node_container, type(target))))            # don't get a subtarget            linkOb.target = target    elif (isinstance(linkOb, ArmatureModifier)):        linkOb.object = target    elif (input_name == 'Target'): # this is sloppy, but it werks        linkOb.target, linkOb.subtarget = target, subtarget    elif (input_name == 'Pole Target'):        linkOb.pole_target, linkOb.pole_subtarget = target, subtarget    else: # this is really just for Armature Constraint targets...        linkOb.target, linkOb.subtarget = target, subtargetdef setup_custom_props(nc):    from .utilities import get_node_prototype        if np := get_node_prototype(nc.signature, nc.base_tree):        for inp in np.inputs:            if not (inp.name in nc.inputs.keys()) :                nc.inputs[inp.name] = NodeSocket(is_input = True, name = inp.name, node = nc,)                nc.parameters[inp.name] = None        for out in np.outputs:            if not (out.name in nc.outputs.keys()) :                nc.outputs[out.name] = NodeSocket(is_input = False, name = out.name, node = nc,)                #nc.parameters[out.name] = None                # probably not something I want?                # I don't think this supports in and out by the same name oof    else:        prRed(nc)            def prepare_parameters(nc):    # some nodes add new parameters at runtime, e.g. Drivers    # so we need to take that stuff from the node_containers that have    #  been executed prior to this node.    for s_name, sock in nc.inputs.items():        if not (sock.is_linked):            continue        if (sock.name  in sock.links[0].from_node.parameters.keys()):            nc.parameters[s_name] = sock.links[0].from_node.parameters[sock.name]    # should work, this is ugly.# TODO: this should handle sub-properties betterdef evaluate_sockets(nc, c, props_sockets):    # HACK I think I should do this in __init__    if not hasattr(nc, "drivers"):        nc.drivers = {}    # end HACK    for prop, (sock, default) in props_sockets.items():        # c = nc.bObject        # annoyingly, sometimes the socket is an array        index = None        if isinstance(sock, tuple):            index = sock[1]; sock = sock[0]        if (check_for_driver(nc, sock, index)):            sock = (sock, index)            original_prop = prop            # TODO: deduplicate this terrible hack            if ("." in prop): # this is a property of a property...                sub_props = [c]                while ("." in prop):                    split_prop = prop.split(".")                    prop = split_prop[1]                    sub_prop = (split_prop[0])                    if ("[" in sub_prop):                        sub_prop, index = sub_prop.split("[")                        index = int(index[0])                        sub_props.append(getattr(sub_props[-1], sub_prop)[index])                    else:                        sub_props.append(getattr(sub_props[-1], sub_prop))                setattr(sub_props[-1], prop, default)            # this is really stupid            else:                setattr(c, prop, default)            print("Adding driver %s to %s in %s" % (wrapPurple(original_prop), wrapWhite(nc.signature[-1]), wrapOrange(nc.GetxForm().bGetObject().name)))            nc.drivers[sock] = original_prop        else: # here we can do error checking for the socket if needed            if (index is not None):                setattr(c, prop, nc.evaluate_input(sock)[index])            else:                    # 'mute' is better than 'enabled'                # UGLY HACK          # because it is available in older                if (prop == 'mute'): # Blenders.                    setattr(c, prop, not nc.evaluate_input(sock))                else:                    setattr(c, prop, nc.evaluate_input(sock))def finish_drivers(self):    # gonna make this into a common function...    drivers = []    if not hasattr(self, "drivers"):        return # HACK    for driver, prop in self.drivers.items():        #annoyingly, the driver may be a vector-driver...        index = driver[1]; driver = driver[0]        driver_trace = trace_single_line(self, driver)        driver_provider, driver_socket = driver_trace[0][-1], driver_trace[1]        if index is not None:            driver = driver_provider.parameters[driver_socket.name][index].copy()        else:            driver = driver_provider.parameters[driver_socket.name].copy()        if driver:            # todo: deduplicate this terrible hack            c = self.bObject # STUPID            if ("." in prop): # this is a property of a property...                sub_props = [c]                while ("." in prop):                    split_prop = prop.split(".")                    prop = split_prop[1]                    sub_prop = (split_prop[0])                    if ("[" in sub_prop):                        sub_prop, index = sub_prop.split("[")                        index = int(index[0])                        sub_props.append(getattr(sub_props[-1], sub_prop)[index])                    else:                        sub_props.append(getattr(sub_props[-1], sub_prop))                driver["owner"] = sub_props[-1]            else:                driver["owner"] = self.bObject            # prPurple("Successfully created driver for %s" % prop)            driver["prop"] = prop            drivers.append(driver)        else:            prRed("Failed to create driver for %s" % prop)    from mantis.drivers import CreateDrivers    CreateDrivers(drivers)#Dummy classes for logic with node containers, they are not meant to do#  each and every little thing the "real" Blender classes do.class NodeLink:    from_node = None    from_socket = None    to_node = None    to_socket = None        def __init__(self, from_node, from_socket, to_node, to_socket):        self.from_node = from_node        self.from_socket = from_socket        self.to_node = to_node        self.to_socket = to_socket        self.from_node.outputs[self.from_socket].links.append(self)        self.to_node.inputs[self.to_socket].links.append(self)        def __repr__(self):        return self.from_node.outputs[self.from_socket].__repr__() + " --> " + self.to_node.inputs[self.to_socket].__repr__()class NodeSocket:    @property # this is a read-only property.    def is_linked(self):        return len(self.links) > 0            def __init__(self, is_input = False,                 node = None, name = None,                 traverse_target = None):        self.can_traverse = False # to/from the other side of the parent node        self.traverse_target = None # a reference to another Socket        # will set in a sec        self.node = node        self.name = name        self.is_input = is_input        self.links = []        if (traverse_target):            set_traverse_target(traverse_target)            def connect(self, node, socket):        if  (self.is_input):            to_node   = self.node; from_node = node            to_socket = self.name; from_socket = socket        else:            from_node   = self.node; to_node   = node            from_socket = self.name; to_socket = socket        new_link = NodeLink(                from_node,                from_socket,                to_node,                to_socket)                # if (from_node.signature[-2] in ["Chiral Identifier"] and            # from_node.signature[-1] in ['Input_4']):                # print(wrapRed("Connecting %s" % new_link),)        # if (from_node.signature[-2] in ["Chiral Identifier"] and            # from_node.signature[-1] in ['Input_3',]):                # print(wrapRed("Connecting %s" % new_link),)                return new_link        def set_traverse_target(self, traverse_target):        self.traverse_target = traverse_target        self.can_traverse = True            @property    def is_connected(self):        return len(self.links)>0            def __repr__(self):        return self.node.__repr__() + "::" + self.name
 |