| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474 |
- from .utilities import (prRed, prGreen, prPurple, prWhite,
- prOrange,
- wrapRed, wrapGreen, wrapPurple, wrapWhite,
- wrapOrange,)
- from .base_definitions import GraphError, CircularDependencyError
- # BE VERY CAREFUL
- # the x_containers files import * from this file
- # so all the top-level imports are carried over
- def 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 nodes
- def 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:
- pass
- def 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 prop
- def 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 nodes
- def 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/out
- def 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, socket
- def 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 better
- def 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, subtarget
- def 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 better
- def 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 .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
|