from .utilities import (prRed, prGreen, prPurple, prWhite, prOrange, wrapRed, wrapGreen, wrapPurple, wrapWhite, wrapOrange,) from .base_definitions import GraphError, NodeSocket, MantisNode from collections.abc import Callable # BE VERY CAREFUL # the x_containers files import * from this file # so all the top-level imports are carried over #TODO: refactor the socket definitions so this becomes unnecessary. def get_socket_value(node_socket): value = None if hasattr(node_socket, "default_value"): value = node_socket.default_value if node_socket.bl_idname == 'MatrixSocket': value = node_socket.TellValue() return value # TODO: modify this to work with multi-input nodes def trace_single_line(node_container, input_name, link_index=0): # DO: refactor this for new link class """Traces a line to its input.""" nodes = [node_container] # Trace a single line if (socket := node_container.inputs.get(input_name) ): while (socket.is_linked): link = socket.links[link_index]; link_index = 0 if (socket := link.from_node.outputs.get(link.from_socket)): nodes.append(socket.node) if socket.can_traverse: socket = socket.traverse_target else: # this is an output. 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,): """I use this to get the xForm from a link node.""" 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] # TODO: find out if this is wise. 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_line_up_branching(node : MantisNode, output_name : str, break_condition : Callable = lambda node : False): """ Returns all leaf nodes at the ends of branching lines from an output.""" leaf_nodes = [] if hasattr(node, "outputs"): # Trace a single line if (socket := node.outputs.get(output_name) ): check_sockets={socket} while (check_sockets): # This is bad, but it's efficient for nodes that only expect # one path along the given line socket = check_sockets.pop() for link in socket.links: other = link.to_node.inputs.get(link.to_socket) if (other): socket = other if break_condition(socket.node): leaf_nodes.append(socket.node) elif socket.is_input and socket.can_traverse: check_sockets.add(socket.traverse_target) else: # this is an input. leaf_nodes.append(socket.node) return leaf_nodes def setup_custom_props(nc): from .utilities import get_node_prototype if nc.signature[0] == 'SCHEMA_AUTOGENERATED': from .base_definitions import custom_props_types if nc.__class__.__name__ not in custom_props_types: # prRed(f"Reminder: figure out how to deal with custom property setting for Schema Node {nc}") raise RuntimeError(wrapRed(f"Custom Properties not set up for node {nc}")) return else: np = get_node_prototype(nc.signature, nc.base_tree) if np: setup_custom_props_from_np(nc, np) else: prRed("Failed to setup custom properties for: nc") def setup_custom_props_from_np(nc, np): for inp in np.inputs: if inp.identifier == "__extend__": continue if not (inp.name in nc.inputs.keys()) : socket = NodeSocket(is_input = True, name = inp.name, node = nc,) nc.inputs[inp.name] = socket nc.parameters[inp.name] = None for attr_name in ["min", "max", "soft_min", "soft_max", "description"]: try: setattr(socket, attr_name, getattr(inp, attr_name)) except AttributeError: pass for out in np.outputs: if out.identifier == "__extend__": continue if not (out.name in nc.outputs.keys()) : nc.outputs[out.name] = NodeSocket(is_input = False, name = out.name, node = 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. def check_for_driver(node_container, input_name, index = None): prop = node_container.evaluate_input(input_name) if (index is not None): prop = prop[index] return (prop.__class__.__name__ == 'MantisDriver') # TODO: this should handle sub-properties better def evaluate_sockets(nc, b_object, props_sockets,): # this is neccesary because some things use dict properties for dynamic properties and setattr doesn't work def safe_setattr(ob, att_name, val): if ob.__class__.__name__ in ["NodesModifier"]: ob[att_name]=val elif b_object.__class__.__name__ in ["Key"]: if not val: val=0 ob.key_blocks[att_name].value=val elif "]." in att_name: # it is of the form prop[int].prop2 prop=att_name.split('[')[0] prop1=att_name.split('.')[1] index = int(att_name.split('[')[1][0]) setattr(getattr(b_object, prop)[index], prop1, val) else: try: setattr(ob, att_name, val) except Exception as e: prRed(ob, att_name, val); raise e for prop, (sock, default) in props_sockets.items(): # 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) and not b_object.__class__.__name__ in ["Key"]: # this is a property of a property... sub_props = [b_object] 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)) safe_setattr(sub_props[-1], prop, default) # this is really stupid else: safe_setattr(b_object, prop, default) if nc.node_type in ['LINK',]: printname = wrapOrange(b_object.id_data.name) elif nc.node_type in ['XFORM',]: printname = wrapOrange(nc.bGetObject().name) else: printname = wrapOrange(nc) print("Adding driver %s to %s in %s" % (wrapPurple(original_prop), wrapWhite(nc.signature[-1]), printname)) if b_object.__class__.__name__ in ["NodesModifier"]: nc.drivers[sock] = "[\""+original_prop+"\"]" # lol. It is a dict element not a "true" property elif b_object.__class__.__name__ in ["Key"]: nc.drivers[sock] = "key_blocks[\""+original_prop+"\"].value" else: nc.drivers[sock] = original_prop else: # here we can do error checking for the socket if needed if (index is not None): safe_setattr(b_object, prop, nc.evaluate_input(sock)[index]) else: # 'mute' is better than 'enabled' # UGLY HACK # because it is available in older if (prop == 'mute'): # Blenders. safe_setattr(b_object, prop, not bool(nc.evaluate_input(sock))) elif (prop == 'hide'): # this will not cast it for me, annoying. safe_setattr(b_object, prop, bool(nc.evaluate_input(sock))) else: try: # prRed(b_object.name, nc, prop, nc.evaluate_input(sock) ) # print( nc.evaluate_input(sock)) # value_eval = nc.evaluate_input(sock) # just wanna see if we are dealing with some collection # check hasattr in case it is one of those ["such-and-such"] props, and ignore those if hasattr(b_object, prop) and (not isinstance(getattr(b_object, prop), str)) and hasattr(getattr(b_object, prop), "__getitem__"): # prGreen("Doing the thing") for val_index, value in enumerate(nc.evaluate_input(sock)): # assume this will work, both because val should have the right number of elements, and because this should be the right data type. from .drivers import MantisDriver if isinstance(value, MantisDriver): getattr(b_object,prop)[val_index] = default[val_index] print("Adding driver %s to %s in %s" % (wrapPurple(prop), wrapWhite(nc.signature[-1]), nc)) try: nc.drivers[sock].append((prop, val_index)) except: nc.drivers[sock] = [(prop, val_index)] else: getattr(b_object,prop)[val_index] = value else: # prOrange("Skipping the Thing", getattr(b_object, prop)) safe_setattr(b_object, prop, nc.evaluate_input(sock)) except Exception as e: prRed(b_object, nc, prop, sock, nc.evaluate_input(sock)) raise e def finish_driver(nc, b_object, driver_item, prop): # prWhite(nc, prop) index = driver_item[1]; driver_sock = driver_item[0] driver_trace = trace_single_line(nc, driver_sock) 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() # this is harmless and necessary for the weird ones where the property is a vector too driver["ind"] = index else: driver = driver_provider.parameters[driver_socket.name].copy() if driver: if ("." in prop) and nc.__class__.__name__ != "DeformerMorphTargetDeform": # this is a property of a property... sub_props = [b_object] 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] elif nc.node_type in ['XFORM',] and nc.__class__.__name__ in ['xFormBone']: # TODO: I really shouldn't have to hardcode this. Look into better solutions. if prop in ['hide', 'show_wire']: # we need to get the bone, not the pose bone. bone_col = nc.bGetParentArmature().data.bones else: bone_col = nc.bGetParentArmature().pose.bones driver["owner"] = bone_col[b_object] # we use "unsafe" brackets instead of get() because we want to see any errors that occur # HACK having special cases here is indicitave of a deeper problem that should be refactored elif nc.__class__.__name__ in ['xFormCurvePin'] and \ prop in ['offset_factor', 'forward_axis', 'up_axis']: driver["owner"] = b_object.constraints['Curve Pin'] else: driver["owner"] = b_object driver["prop"] = prop return driver else: prOrange("Provider", driver_provider) prGreen("socket", driver_socket) print (index) prPurple(driver_provider.parameters[driver_socket.name]) prRed("Failed to create driver for %s" % prop) return None def finish_drivers(nc): drivers = [] if not hasattr(nc, "drivers"): return # HACK for driver_item, prop in nc.drivers.items(): b_objects = [nc.bObject] if nc.node_type == 'LINK': b_objects = nc.bObject # this is already a list for b_object in b_objects: if isinstance(prop, list): for sub_item in prop: drivers.append(finish_driver(nc, b_object, (driver_item, sub_item[1]), sub_item[0])) else: drivers.append(finish_driver(nc, b_object, (driver_item, sub_item[1]), sub_item[0])) else: drivers.append(finish_driver(nc, b_object, driver_item, prop)) from .drivers import CreateDrivers CreateDrivers(drivers)