| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694 |
- #Mantis Nodes Base
- import bpy
- from bpy.props import (BoolProperty, StringProperty, EnumProperty, CollectionProperty, \
- IntProperty, IntVectorProperty, PointerProperty, BoolVectorProperty)
- from . import ops_nodegroup
- from bpy.types import NodeTree, Node, PropertyGroup, Operator, UIList, Panel
- from .utilities import (prRed, prGreen, prPurple, prWhite,
- prOrange,
- wrapRed, wrapGreen, wrapPurple, wrapWhite,
- wrapOrange,)
- from .utilities import get_socket_maps, relink_socket_map, do_relink
- def TellClasses():
- #Why use a function to do this? Because I don't need every class to register.
- return [ MantisTree,
- SchemaTree,
- MantisNodeGroup,
- SchemaGroup,
- ]
- def error_popup_draw(self, context):
- self.layout.label(text="Error executing tree. See Console.")
- mantis_root = ".".join(__name__.split('.')[:-1]) # absolute HACK
- # https://docs.blender.org/api/master/bpy.types.NodeTree.html#bpy.types.NodeTree.valid_socket_type
- # thank you, Sverchok
- def valid_interface_types(cls : NodeTree, socket_idname : str):
- from .socket_definitions import tell_valid_bl_idnames
- return socket_idname in tell_valid_bl_idnames()
- class MantisTree(NodeTree):
- '''A custom node tree type that will show up in the editor type list'''
- bl_idname = 'MantisTree'
- bl_label = "Rigging Nodes"
- bl_icon = 'OUTLINER_OB_ARMATURE'
-
- tree_valid:BoolProperty(default=False)
- do_live_update:BoolProperty(default=True) # use this to disable updates for e.g. scripts
- num_links:IntProperty(default=-1)
- filepath:StringProperty(default="", subtype='FILE_PATH')
- is_executing:BoolProperty(default=False)
- is_exporting:BoolProperty(default=False)
- execution_id:StringProperty(default='')
- mantis_version:IntVectorProperty(default=[0,9,2])
- # this prevents the node group from executing on the next depsgraph update
- # because I don't always have control over when the dg update happens.
- prevent_next_exec:BoolProperty(default=False)
-
- parsed_tree={}
- if (bpy.app.version < (4, 4, 0)): # in 4.4 this leads to a crash
- @classmethod
- def valid_socket_type(cls : NodeTree, socket_idname: str):
- return valid_interface_types(cls, socket_idname)
-
- def update_tree(self, context = None):
- if self.is_exporting:
- return
- # return
- self.is_executing = True
- from . import readtree
- prGreen("Validating Tree: %s" % self.name)
- try:
- self.parsed_tree = readtree.parse_tree(self)
- if context:
- self.display_update(context)
- self.is_executing = False
- self.tree_valid = True
- except GraphError as e:
- prRed("Failed to update node tree due to error.")
- self.tree_valid = False
- self.is_executing = False
- raise e
- finally:
- self.is_executing = False
-
- def display_update(self, context):
- if self.is_exporting:
- return
- self.is_executing = True
- current_tree = bpy.context.space_data.path[-1].node_tree
- for node in current_tree.nodes:
- if hasattr(node, "display_update"):
- try:
- node.display_update(self.parsed_tree, context)
- except Exception as e:
- print("Node \"%s\" failed to update display with error: %s" %(wrapGreen(node.name), wrapRed(e)))
- self.is_executing = False
-
- # TODO: deal with invalid links properly.
- # - Non-hierarchy links should be ignored in the circle-check and so the links should be marked valid in such a circle
- # - hierarchy-links should be marked invalid and prevent the tree from executing.
-
-
- def execute_tree(self,context, error_popups = False):
- self.prevent_next_exec = False
- if self.is_exporting:
- return
- # return
- prGreen("Executing Tree: %s" % self.name)
- self.is_executing = True
- from . import readtree
- try:
- readtree.execute_tree(self.parsed_tree, self, context, error_popups)
- except RecursionError as e:
- prRed("Recursion error while parsing tree.")
- finally:
- self.is_executing = False
- class SchemaTree(NodeTree):
- '''A node tree representing a schema to generate a Mantis tree'''
- bl_idname = 'SchemaTree'
- bl_label = "Rigging Nodes Schema"
- bl_icon = 'RIGID_BODY_CONSTRAINT'
- # these are only needed for consistent interface, but should not be used
- do_live_update:BoolProperty(default=True) # default to true so that updates work
- is_executing:BoolProperty(default=False)
- is_exporting:BoolProperty(default=False)
- mantis_version:IntVectorProperty(default=[0,9,2])
- if (bpy.app.version < (4, 4, 0)): # in 4.4 this leads to a crash
- @classmethod
- def valid_socket_type(cls : NodeTree, socket_idname: str):
- return valid_interface_types(cls, socket_idname)
-
- #TODO: do a better job explaining how MantisNode and MantisUINode relate.
- class MantisUINode:
- """
- This class contains the common user-interface features of Mantis nodes.
- MantisUINode objects will spawn one or more MantisNode objects when the graph is evaluated.
- The MantisNode objects will pull the data from the UI node and use it to generate the graph.
- """
- mantis_node_library=''
- mantis_node_class_name=''
- mantis_class=None
- @classmethod
- def poll(cls, ntree):
- return (ntree.bl_idname in ['MantisTree', 'SchemaTree'])
-
- @classmethod
- def set_mantis_class(self):
- from importlib import import_module
- # do not catch errors, they should cause a failure.
- try:
- module = import_module(self.mantis_node_library, package=mantis_root)
- self.mantis_class=getattr(module, self.mantis_node_class_name)
- except Exception as e:
- print(self)
- raise e
- def insert_link(self, link):
- if (bpy.app.version < (4, 4, 0)):
- return # this causes a crasah due to a bug.
- context = bpy.context
- if context.space_data:
- node_tree = context.space_data.path[0].node_tree
- if node_tree.do_live_update:
- node_tree.update_tree(context)
- if (link.to_socket.is_linked == False):
- node_tree.num_links+=1
- elif (link.to_socket.is_multi_input):
- node_tree.num_links+=1
-
-
- class SchemaUINode(MantisUINode):
- mantis_node_library='.schema_containers'
- @classmethod
- def poll(cls, ntree):
- return (ntree.bl_idname in ['SchemaTree'])
- class LinkNode(MantisUINode):
- mantis_node_library='.link_containers'
- @classmethod
- def poll(cls, ntree):
- return (ntree.bl_idname in ['MantisTree', 'SchemaTree'])
-
- class xFormNode(MantisUINode):
- mantis_node_library='.xForm_containers'
- @classmethod
- def poll(cls, ntree):
- return (ntree.bl_idname in ['MantisTree', 'SchemaTree'])
- class DeformerNode(MantisUINode):
- mantis_node_library='.deformer_containers'
- @classmethod
- def poll(cls, ntree):
- return (ntree.bl_idname in ['MantisTree', 'SchemaTree'])
- def poll_node_tree(self, object):
- if isinstance(object, MantisTree):
- return True
- return False
- # TODO: try to check identifiers instead of name.
- def node_group_update(node, force = False):
- if not force:
- if (node.id_data.do_live_update == False) or (node.id_data.is_executing == True):
- return
- # note: if (node.id_data.is_exporting == True) I need to be able to update so I can make links.
- toggle_update = node.id_data.do_live_update
- node.id_data.do_live_update = False
- identifiers_in={socket.identifier:socket for socket in node.inputs}
- identifiers_out={socket.identifier:socket for socket in node.outputs}
- if node.node_tree is None:
- node.inputs.clear(); node.outputs.clear()
- node.id_data.do_live_update = toggle_update
- return
- found_in, found_out = [], []
- update_input, update_output = False, False
- for item in node.node_tree.interface.items_tree:
- if item.item_type != "SOCKET": continue
- if item.in_out == 'OUTPUT':
- if s:= identifiers_out.get(item.identifier): # if the requested output doesn't exist, update
- found_out.append(item.identifier)
- if update_output: continue
- if s.bl_idname != item.socket_type: update_output = True; continue
- else: update_output = True; continue
- else:
- if s:= identifiers_in.get(item.identifier): # if the requested input doesn't exist, update
- found_in.append(item.identifier)
- if update_input: continue # done here
- if s.bl_idname != item.socket_type: update_input = True; continue
- else: update_input = True; continue
-
- # Schema has an extra input for Length and for Extend.
- if node.bl_idname == 'MantisSchemaGroup':
- found_in.extend(['Schema Length', ''])
- # if we have too many elements, just get rid of the ones we don't need
- if len(node.inputs) > len(found_in):#
- for inp in node.inputs:
- if inp.identifier in found_in: continue
- node.inputs.remove(inp)
- if len(node.outputs) > len(found_out):
- for out in node.outputs:
- if out.identifier in found_out: continue
- node.outputs.remove(out)
- #
- if len(node.inputs) > 0 and (inp := node.inputs[-1]).bl_idname == 'WildcardSocket' and inp.is_linked:
- update_input = True
- if len(node.outputs) > 0 and (out := node.outputs[-1]).bl_idname == 'WildcardSocket' and out.is_linked:
- update_output = True
- #
- if not (update_input or update_output):
- node.id_data.do_live_update = toggle_update
- return
- if update_input or update_output:
- socket_map_in, socket_map_out = get_socket_maps(node)
- if update_input :
- if node.bl_idname == 'MantisSchemaGroup':
- schema_length=0
- if sl := node.inputs.get("Schema Length"):
- schema_length = sl.default_value
- # sometimes this isn't available yet # TODO not happy about this solution
- node.inputs.clear()
- if node.bl_idname == 'MantisSchemaGroup':
- node.inputs.new("IntSocket", "Schema Length", identifier='Schema Length')
- node.inputs['Schema Length'].default_value = schema_length
- if update_output: node.outputs.clear()
- for item in node.node_tree.interface.items_tree:
- if item.item_type != "SOCKET": continue
- if (item.in_out == 'INPUT' and update_input):
- relink_socket_map(node, node.inputs, socket_map_in, item)
- if (item.in_out == 'OUTPUT' and update_output):
- relink_socket_map(node, node.outputs, socket_map_out, item)
-
- # at this point there is no wildcard socket
- if '__extend__' in socket_map_in.keys():
- do_relink(node, None, socket_map_in, in_out='INPUT', parent_name='Constant' )
- node.id_data.do_live_update = toggle_update
- def node_tree_prop_update(self, context):
- if self.is_updating: # update() can be called from update() and that leads to an infinite loop.
- return # so we check if an update is currently running.
- self.is_updating = True
- node_group_update(self)
- self.is_updating = False
- if self.bl_idname in ['MantisSchemaGroup'] and self.node_tree is not None:
- if len(self.inputs) == 0:
- self.inputs.new("IntSocket", "Schema Length", identifier='Schema Length')
- if self.inputs[-1].bl_idname != "WildcardSocket":
- self.inputs.new("WildcardSocket", "", identifier="__extend__")
- from bpy.types import NodeCustomGroup
- class MantisNodeGroup(Node, MantisUINode):
- bl_idname = "MantisNodeGroup"
- bl_label = "Node Group"
- node_tree:PointerProperty(type=NodeTree, poll=poll_node_tree, update=node_tree_prop_update,)
- is_updating:BoolProperty(default=False)
-
- def update(self):
- live_update = self.id_data.do_live_update
- if self.is_updating: # update() can be called from update() and that leads to an infinite loop.
- return # so we check if an update is currently running.
- try:
- self.is_updating = True
- node_group_update(self)
- self.is_updating = False
- finally: # we need to reset this regardless of whether or not the operation succeeds!
- self.is_updating = False
- self.id_data.do_live_update = live_update # ensure this remains the same
- def draw_buttons(self, context, layout):
- row = layout.row(align=True)
- row.prop(self, "node_tree", text="")
- row.operator("mantis.edit_group", text="", icon='NODETREE', emboss=True)
-
- class GraphError(Exception):
- pass
- def get_signature_from_edited_tree(node, context):
- sig_path=[None,]
- for item in context.space_data.path[:-1]:
- sig_path.append(item.node_tree.nodes.active.name)
- return tuple(sig_path+[node.name])
- def poll_node_tree_schema(self, object):
- if isinstance(object, SchemaTree):
- return True
- return False
- # TODO tiny UI problem - inserting new links into the tree will not place them in the right place.
- class SchemaGroup(Node, MantisUINode):
- bl_idname = "MantisSchemaGroup"
- bl_label = "Node Schema"
-
- node_tree:PointerProperty(type=NodeTree, poll=poll_node_tree_schema, update=node_tree_prop_update,)
- is_updating:BoolProperty(default=False)
- def draw_buttons(self, context, layout):
- row = layout.row(align=True)
- row.prop(self, "node_tree", text="")
- row.operator("mantis.edit_group", text="", icon='NODETREE', emboss=True)
- def update(self):
- live_update = self.id_data.do_live_update
- if self.is_updating: # update() can be called from update() and that leads to an infinite loop.
- return # so we check if an update is currently running.
- self.is_updating = True
- try:
- node_group_update(self)
- # reset things if necessary:
- if self.node_tree:
- if len(self.inputs) == 0:
- self.inputs.new("IntSocket", "Schema Length", identifier='Schema Length')
- if self.inputs[-1].bl_idname != "WildcardSocket":
- self.inputs.new("WildcardSocket", "", identifier="__extend__")
- finally: # we need to reset this regardless of whether or not the operation succeeds!
- self.is_updating = False
- self.id_data.do_live_update = live_update # ensure this remains the same
- NODES_REMOVED=["xFormRootNode"]
- # Node bl_idname, # Socket Name
- SOCKETS_REMOVED=[("UtilityDriverVariable", "Transform Channel"),
- ("xFormRootNode","World Out"),
- ("UtilitySwitch","xForm"),
- ("LinkDrivenParameter", "Enable")]
- # Node Class #Prior bl_idname # prior name # new bl_idname # new name, # Multi
- SOCKETS_RENAMED=[ ("LinkDrivenParameter", "DriverSocket", "Driver", "FloatSocket", "Value", False)]
- # NODE CLASS NAME IN_OUT SOCKET TYPE SOCKET NAME INDEX MULTI DEFAULT
- SOCKETS_ADDED=[("DeformerMorphTargetDeform", 'INPUT', 'BooleanSocket', "Use Shape Key", 1, False, False),
- ("DeformerMorphTargetDeform", 'INPUT', 'BooleanSocket', "Use Offset", 2, False, True),
- ("UtilityFCurve", 'INPUT', "eFCrvExtrapolationMode", "Extrapolation Mode", 0, False, 'CONSTANT')]
- # replace names with bl_idnames for reading the tree and solving schemas.
- replace_types = ["NodeGroupInput", "NodeGroupOutput", "SchemaIncomingConnection",
- "SchemaArrayInput", "SchemaConstInput", "SchemaConstOutput", "SchemaIndex",
- "SchemaOutgoingConnection", "SchemaConstantOutput", "SchemaArrayOutput",
- "SchemaArrayInputGet",]
- # anything that gets properties added in the graph... this is a clumsy approach but I need to watch for this
- # in schema generation and this is the easiest way to do it for now.
- custom_props_types = ["LinkArmature", "UtilityKeyframe", "UtilityFCurve", "UtilityDriver", "xFormBone"]
- # filters for determining if a link is a hierarchy link or a non-hierarchy (cyclic) link.
- from_name_filter = ["Driver",]
- to_name_filter = [
- "Custom Object xForm Override",
- "Custom Object",
- "Deform Bones",
- ]
- class MantisNode:
- """
- This class contains the basic interface for a Mantis Node.
- A MantisNode is used internally by Mantis to represent the final evaluated node graph.
- It gets generated with data from a MantisUINode when the graph is read.
- """
- def __init__(self, signature, base_tree):
- self.base_tree=base_tree
- self.signature = signature
- self.inputs = MantisNodeSocketCollection(node=self, is_input=True)
- self.outputs = MantisNodeSocketCollection(node=self, is_input=False)
- self.parameters = {}
- self.drivers = {}
- self.node_type='UNINITIALIZED'
- self.hierarchy_connections, self.connections = [], []
- self.hierarchy_dependencies, self.dependencies = [], []
- self.prepared = False
- self.executed = False
- def init_parameters(self, additional_parameters = {}):
- for socket in self.inputs:
- self.parameters[socket.name] = None
- for socket in self.outputs:
- self.parameters[socket.name] = None
- for key, value in additional_parameters.items():
- self.parameters[key]=value
-
- def set_traverse(self, traversal_pairs = [(str, str)]):
- for (a, b) in traversal_pairs:
- self.inputs[a].set_traverse_target(self.outputs[b])
- self.outputs[b].set_traverse_target(self.inputs[a])
-
- def flush_links(self):
- for inp in self.inputs.values():
- inp.flush_links()
- for out in self.outputs.values():
- out.flush_links()
-
- def evaluate_input(self, input_name, index=0):
- from .node_container_common import trace_single_line
- if not (self.inputs.get(input_name)): # get the named parameter if there is no input
- return self.parameters.get(input_name) # this will return None if the parameter does not exist.
- # 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
- return prop
-
- def fill_parameters(self, ui_node=None):
- from .utilities import get_node_prototype
- from .node_container_common import get_socket_value
- if not ui_node:
- if ( (self.signature[0] in ["MANTIS_AUTOGENERATED", "SCHEMA_AUTOGENERATED" ]) or
- (self.signature[-1] in ["NodeGroupOutput", "NodeGroupInput"]) ): # I think this is harmless
- return None
- else:
- ui_node = get_node_prototype(self.signature, self.base_tree)
- if not ui_node:
- raise RuntimeError(wrapRed("No node prototype found for... %s" % ( [self.base_tree] + list(self.signature[1:]) ) ) )
- for key in self.parameters.keys():
- node_socket = ui_node.inputs.get(key)
- if self.parameters[key] is not None: # the parameters are usually initialized as None.
- continue # will be filled by the node itself
- if not node_socket: #maybe the node socket has no name
- if ( ( len(ui_node.inputs) == 0) and ( len(ui_node.outputs) == 1) ):
- # this is a simple input node.
- node_socket = ui_node.outputs[0]
- elif key == 'Name': # for Links we just use the Node Label, or if there is no label, the name.
- self.parameters[key] = ui_node.label if ui_node.label else ui_node.name
- continue
- else:
- pass
- if node_socket:
- if node_socket.bl_idname in ['RelationshipSocket', 'xFormSocket']:
- continue
- elif node_socket.is_linked and (not node_socket.is_output):
- pass # we will get the value from the link, because this is a linked input port.
- # very importantly, we do not pass linked outputs- fill these because they are probably Input nodes.
- elif hasattr(node_socket, "default_value"):
- if (value := get_socket_value(node_socket)) is not None:
- self.parameters[key] = value
- # TODO: try and remove the input if it is not needed (for performance speed)
- else:
- raise RuntimeError(wrapRed("No value found for " + self.__repr__() + " when filling out node parameters for " + ui_node.name + "::"+node_socket.name))
- else:
- pass
- def call_on_all_ancestors(self, *args, **kwargs):
- """Resolve the dependencies of this node with the named method and its arguments.
- First, dependencies are discovered by walking backwards through the tree. Once the root
- nodes are discovered, the method is called by each node in dependency order.
- The first argument MUST be the name of the method as a string.
- """
- prGreen(self)
- if args[0] == 'call_on_all_ancestors': raise RuntimeError("Very funny!")
- from .utilities import get_all_dependencies
- from collections import deque
- # get all dependencies by walking backward through the tree.
- all_dependencies = get_all_dependencies(self)
- # get just the roots
- can_solve = deque(filter(lambda a : len(a.hierarchy_connections) == 0, all_dependencies))
- solved = set()
- while can_solve:
- node = can_solve.pop()
- print(node)
- method = getattr(node, args[0])
- method(*args[0:], **kwargs)
- solved.add(node)
- can_solve.extendleft(filter(lambda a : a in all_dependencies, node.hierarchy_connections))
- # prPurple(can_solve)
- if self in solved:
- break
- # else:
- # for dep in all_dependencies:
- # if dep not in solved:
- # prOrange(dep)
- return
-
-
-
- def bPrepare(self, bContext=None):
- return
- def bExecute(self, bContext=None):
- return
- def bFinalize(self, bContext=None):
- return
- def __repr__(self):
- return self.signature.__repr__()
- # do I need this and the link class above?
- 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, multi_input_sort_id=0):
- self.from_socket = from_socket
- self.to_socket = to_socket
- self.nc_from = nc_from
- self.nc_to = nc_to
- self.multi_input_sort_id = multi_input_sort_id
- # self.from_node = from_socket.node
- # self.to_node = to_socket.node
- 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)
- 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, multi_input_sort_id=0):
- if from_node.signature == to_node.signature:
- raise RuntimeError("Cannot connect a node to itself.")
- 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)
- # it is the responsibility of the node that uses these links to sort them correctly based on the sort_id
- self.multi_input_sort_id = multi_input_sort_id
- self.to_node.inputs[self.to_socket].links.append(self)
- from .node_container_common import detect_hierarchy_link
- self.is_hierarchy = detect_hierarchy_link(from_node, from_socket, to_node, to_socket,)
- self.is_alive = True
-
- def __repr__(self):
- return self.from_node.outputs[self.from_socket].__repr__() + " --> " + self.to_node.inputs[self.to_socket].__repr__()
- # link_string = # if I need to colorize output for debugging.
- # if self.is_hierarchy:
- # return wrapOrange(link_string)
- # else:
- # return wrapWhite(link_string)
-
- def die(self):
- self.is_alive = False
- self.to_node.inputs[self.to_socket].flush_links()
- self.from_node.outputs[self.from_socket].flush_links()
-
- def insert_node(self, middle_node, middle_node_in, middle_node_out, re_init_hierarchy = True):
- to_node = self.to_node
- to_socket = self.to_socket
- self.to_node = middle_node
- self.to_socket = middle_node_in
- middle_node.outputs[middle_node_out].connect(to_node, to_socket)
- if re_init_hierarchy:
- from .utilities import init_connections, init_dependencies
- init_connections(self.from_node)
- init_connections(middle_node)
- init_dependencies(middle_node)
- init_dependencies(to_node)
- class NodeSocket:
- # @property # this is a read-only property.
- # def is_linked(self):
- # return bool(self.links)
-
- 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
- self.node = node
- self.name = name
- self.is_input = is_input
- self.links = []
- self.is_linked = False
- if (traverse_target):
- self.can_traverse = True
-
- def connect(self, node, socket, sort_id=0):
- 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
- from_node.outputs[from_socket].is_linked = True
- to_node.inputs[to_socket].is_linked = True
- for l in from_node.outputs[from_socket].links:
- if l.to_node==to_node and l.to_socket==to_socket:
- return None
- new_link = NodeLink(
- from_node,
- from_socket,
- to_node,
- to_socket,
- sort_id)
- return new_link
-
- def set_traverse_target(self, traverse_target):
- self.traverse_target = traverse_target
- self.can_traverse = True
-
- def flush_links(self):
- """ Removes dead links from this socket."""
- self.links = [l for l in self.links if l.is_alive]
- self.is_linked = bool(self.links)
-
- @property
- def is_connected(self):
- return len(self.links)>0
-
-
- def __repr__(self):
- return self.node.__repr__() + "::" + self.name
- class MantisNodeSocketCollection(dict):
- def __init__(self, node, is_input=False):
- self.is_input = is_input
- self.node = node
-
- def init_sockets(self, sockets):
- for socket in sockets:
- if not isinstance(socket, str): raise RuntimeError("NodeSocketCollection keys must be str.")
- self[socket] = NodeSocket(is_input=self.is_input, name=socket, node=self.node)
-
- def __delitem__(self, key):
- """Deletes a node socket by name, and all its links."""
- socket = self[key]
- for l in socket.links:
- l.die()
- super().__delitem__(key)
-
- def __iter__(self):
- """Makes the class iterable"""
- return iter(self.values())
|