|
|
@@ -84,7 +84,7 @@ class MantisTree(NodeTree):
|
|
|
bl_idname = 'MantisTree'
|
|
|
bl_label = "Rigging Nodes"
|
|
|
bl_icon = 'OUTLINER_OB_ARMATURE'
|
|
|
-
|
|
|
+
|
|
|
tree_valid:BoolProperty(default=False)
|
|
|
hash:StringProperty(default='')
|
|
|
do_live_update:BoolProperty(default=True) # use this to disable updates for e.g. scripts
|
|
|
@@ -104,14 +104,14 @@ class MantisTree(NodeTree):
|
|
|
|
|
|
#added to work around a bug in 4.5.0 LTS
|
|
|
interface_helper : StringProperty(default='')
|
|
|
-
|
|
|
+
|
|
|
parsed_tree={}
|
|
|
|
|
|
if (bpy.app.version < (4, 4, 0) or bpy.app.version >= (4,5,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(self): # set the reroute colors
|
|
|
if (bpy.app.version >= (4,4,0)):
|
|
|
fix_reroute_colors(self)
|
|
|
@@ -155,7 +155,7 @@ class MantisTree(NodeTree):
|
|
|
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.
|
|
|
@@ -219,7 +219,7 @@ class MantisSocketTemplate():
|
|
|
hide : bool = field(default=False)
|
|
|
use_multi_input : bool = field(default=False)
|
|
|
default_value : Any = field(default=None)
|
|
|
-
|
|
|
+
|
|
|
|
|
|
|
|
|
#TODO: do a better job explaining how MantisNode and MantisUINode relate.
|
|
|
@@ -236,7 +236,7 @@ class MantisUINode:
|
|
|
@classmethod
|
|
|
def poll(cls, ntree):
|
|
|
return (ntree.bl_idname in ['MantisTree', 'SchemaTree'])
|
|
|
-
|
|
|
+
|
|
|
@classmethod
|
|
|
def set_mantis_class(self):
|
|
|
from importlib import import_module
|
|
|
@@ -260,7 +260,7 @@ class MantisUINode:
|
|
|
node_tree.num_links+=1
|
|
|
elif (link.to_socket.is_multi_input):
|
|
|
node_tree.num_links+=1
|
|
|
-
|
|
|
+
|
|
|
def init_sockets(self, socket_templates : tuple[MantisSocketTemplate]):
|
|
|
for template in socket_templates:
|
|
|
collection = self.outputs
|
|
|
@@ -286,7 +286,7 @@ class MantisUINode:
|
|
|
# responsibility to send the right type.
|
|
|
if template.use_multi_input: # this is an array
|
|
|
socket.display_shape = 'SQUARE_DOT'
|
|
|
-
|
|
|
+
|
|
|
class SchemaUINode(MantisUINode):
|
|
|
mantis_node_library='.schema_nodes'
|
|
|
is_updating:BoolProperty(default=False)
|
|
|
@@ -299,7 +299,7 @@ class LinkNode(MantisUINode):
|
|
|
@classmethod
|
|
|
def poll(cls, ntree):
|
|
|
return (ntree.bl_idname in ['MantisTree', 'SchemaTree'])
|
|
|
-
|
|
|
+
|
|
|
class xFormNode(MantisUINode):
|
|
|
mantis_node_library='.xForm_nodes'
|
|
|
@classmethod
|
|
|
@@ -344,12 +344,12 @@ def node_group_update(node, force = False):
|
|
|
(node.id_data.is_exporting == True):
|
|
|
return
|
|
|
# note: if (node.id_data.is_exporting == True) I need to be able to update so I can make links.
|
|
|
-
|
|
|
+
|
|
|
if node.node_tree is None:
|
|
|
node.inputs.clear(); node.outputs.clear()
|
|
|
node.id_data.do_live_update = toggle_update
|
|
|
return
|
|
|
-
|
|
|
+
|
|
|
toggle_update = node.id_data.do_live_update
|
|
|
node.id_data.do_live_update = False
|
|
|
|
|
|
@@ -391,11 +391,11 @@ def node_group_update(node, force = False):
|
|
|
if update_input: continue # done here
|
|
|
if s.bl_idname != item.bl_socket_idname: 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', ''])
|
|
|
-
|
|
|
+
|
|
|
# get the socket maps before modifying stuff
|
|
|
if update_input or update_output:
|
|
|
socket_maps = get_socket_maps(node,)
|
|
|
@@ -408,7 +408,7 @@ def node_group_update(node, force = False):
|
|
|
# We have to initialize the node because it only has its base inputs.
|
|
|
elif socket_maps is None:
|
|
|
node.id_data.do_live_update = toggle_update
|
|
|
-
|
|
|
+
|
|
|
# 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:
|
|
|
@@ -444,7 +444,7 @@ def node_group_update(node, force = False):
|
|
|
remove_me.append(socket)
|
|
|
while remove_me:
|
|
|
node.inputs.remove(remove_me.pop())
|
|
|
-
|
|
|
+
|
|
|
if update_output:
|
|
|
remove_me=[]
|
|
|
for socket in node.outputs:
|
|
|
@@ -568,10 +568,10 @@ class MantisNodeGroup(Node, MantisUINode):
|
|
|
return "Node Group"
|
|
|
else:
|
|
|
return self.node_tree.name
|
|
|
-
|
|
|
+
|
|
|
def draw_buttons(self, context, layout):
|
|
|
group_draw_buttons(self, context, layout)
|
|
|
-
|
|
|
+
|
|
|
def update(self):
|
|
|
if self.node_tree is None:
|
|
|
return
|
|
|
@@ -606,7 +606,7 @@ def poll_node_tree_schema(self, object):
|
|
|
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)
|
|
|
|
|
|
@@ -618,7 +618,7 @@ class SchemaGroup(Node, MantisUINode):
|
|
|
return "Schema Group"
|
|
|
else:
|
|
|
return self.node_tree.name
|
|
|
-
|
|
|
+
|
|
|
def update(self):
|
|
|
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.
|
|
|
@@ -702,6 +702,7 @@ class MantisExecutionContext():
|
|
|
self.execution_id = base_tree.execution_id
|
|
|
self.execution_failed=False
|
|
|
self.b_objects={} # objects created by Mantis during execution
|
|
|
+ self.string_variables={}
|
|
|
|
|
|
class MantisNode:
|
|
|
"""
|
|
|
@@ -737,12 +738,12 @@ class MantisNode:
|
|
|
@property
|
|
|
def name(self):
|
|
|
return self.ui_signature[-1]
|
|
|
-
|
|
|
+
|
|
|
@property
|
|
|
def bl_idname(self): # this and the above exist solely to maintain interface w/bpy.types.Node
|
|
|
from .utilities import get_node_prototype
|
|
|
return get_node_prototype(self.ui_signature, self.base_tree).bl_idname
|
|
|
-
|
|
|
+
|
|
|
def reset_execution(self) -> None:
|
|
|
""" Reset the node for additional execution without re-building the tree."""
|
|
|
self.drivers={}; self.bObject=None
|
|
|
@@ -760,7 +761,7 @@ class MantisNode:
|
|
|
self.parameters[socket.name] = None
|
|
|
for key, value in additional_parameters.items():
|
|
|
self.parameters[key]=value
|
|
|
-
|
|
|
+
|
|
|
def gen_property_socket_map(self) -> dict:
|
|
|
props_sockets = {}
|
|
|
for s_template in self.socket_templates:
|
|
|
@@ -772,7 +773,7 @@ class MantisNode:
|
|
|
for index, sub_prop in enumerate(s_template.blender_property):
|
|
|
props_sockets[sub_prop]=( (s_template.name, index),s_template.default_value[index] )
|
|
|
return props_sockets
|
|
|
-
|
|
|
+
|
|
|
def set_traverse(self, traversal_pairs = [(str, str)]) -> None:
|
|
|
for (a, b) in traversal_pairs:
|
|
|
self.inputs[a].set_traverse_target(self.outputs[b])
|
|
|
@@ -783,12 +784,12 @@ class MantisNode:
|
|
|
inp.flush_links()
|
|
|
for out in self.outputs.values():
|
|
|
out.flush_links()
|
|
|
-
|
|
|
+
|
|
|
def update_socket_value(self, blender_property, value) -> bool:
|
|
|
change_handled=False
|
|
|
if self.node_type == 'LINK':
|
|
|
if len(self.bObject) == 0: # - there are no downstream xForms
|
|
|
- return True # so there is nothing to do here
|
|
|
+ return True # so there is nothing to do here
|
|
|
for b_ob in self.bObject:
|
|
|
try:
|
|
|
setattr(b_ob, blender_property, value)
|
|
|
@@ -838,7 +839,7 @@ class MantisNode:
|
|
|
change_handled=False
|
|
|
break # we don't have to look through any more socket templates
|
|
|
return change_handled
|
|
|
-
|
|
|
+
|
|
|
# the goal here is to tag the node as unprepared
|
|
|
# but some nodes are always prepared, so we have to kick it forward.
|
|
|
def reset_execution_recursive(self):
|
|
|
@@ -846,7 +847,42 @@ class MantisNode:
|
|
|
if self.prepared==False: return # all good from here
|
|
|
for conn in self.hierarchy_connections:
|
|
|
conn.reset_execution_recursive()
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
+ # TODO: make this MUCH more efficient!
|
|
|
+ # alternatively: call this ONCE when initializing the tree, precache results?
|
|
|
+ def apply_string_variables(self, string):
|
|
|
+ # We get the mContext, iterate through the signature, and string-replace variables
|
|
|
+ # this function should be called by evaluate_input if the result is a string.
|
|
|
+ all_vars = {} # maybe the individual nodes should store this as a class member, too...
|
|
|
+ name=""
|
|
|
+ do_once = False
|
|
|
+ for i in range(len(self.signature[:-1])):
|
|
|
+ if i == 0:
|
|
|
+ continue
|
|
|
+ name+=self.signature[i]
|
|
|
+ prWhite(name)
|
|
|
+ vars = self.mContext.string_variables.get(name, None)
|
|
|
+ if vars is None:
|
|
|
+ prRed("Can't get string variables for node")
|
|
|
+ print (vars, type(vars))
|
|
|
+ prRed (name)
|
|
|
+ prWhite(self.mContext.string_variables.keys())
|
|
|
+ for k in self.mContext.string_variables.keys():
|
|
|
+ print (name == k)
|
|
|
+ print (len(name), name,)
|
|
|
+ print (len(k), k)
|
|
|
+ raise RuntimeError
|
|
|
+ continue
|
|
|
+ elif not vars:
|
|
|
+ prRed(self)
|
|
|
+ for var_name, var_value in vars.items():
|
|
|
+ do_once=True
|
|
|
+ string = string.replace("$"+var_name, var_value)
|
|
|
+ if do_once == False:
|
|
|
+ raise NotImplementedError
|
|
|
+ return string
|
|
|
+
|
|
|
def evaluate_input(self, input_name, index=0) -> Any:
|
|
|
from .node_container_common import trace_single_line
|
|
|
if not (self.inputs.get(input_name)): # get the named parameter if there is no input
|
|
|
@@ -854,14 +890,24 @@ class MantisNode:
|
|
|
# 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
|
|
|
+ try:
|
|
|
+ prop = trace[0][-1].parameters[trace[1].name] #trace[0] = the list of traced nodes; read its parameters
|
|
|
+ except Exception as e:
|
|
|
+ print (trace[0][-1])
|
|
|
+ print (trace[1].name)
|
|
|
+ print (trace[0][-1].parameters.keys())
|
|
|
+ print (trace[0][-1].parameters.values())
|
|
|
+ raise e
|
|
|
+ if isinstance(prop, str) and "$" in prop:
|
|
|
+ print (self, prop)
|
|
|
+ prop = self.apply_string_variables(prop)
|
|
|
return prop
|
|
|
-
|
|
|
+
|
|
|
def fill_parameters(self, ui_node=None) -> 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
|
|
|
+ if ( (self.signature[0] in ["MANTIS_AUTOGENERATED", "SCHEMA_AUTOGENERATED" ]) or
|
|
|
(self.signature[-1] in ["NodeGroupOutput", "NodeGroupInput"]) ): # I think this is harmless
|
|
|
return None
|
|
|
else: # BUG shouldn't this use ui_signature??
|
|
|
@@ -916,7 +962,7 @@ class MantisNode:
|
|
|
if self in solved:
|
|
|
break
|
|
|
return
|
|
|
-
|
|
|
+
|
|
|
# gets targets for constraints and deformers and should handle all cases
|
|
|
def get_target_and_subtarget(self, constraint_or_deformer, input_name = "Target"):
|
|
|
from bpy.types import PoseBone, Object, SplineIKConstraint
|
|
|
@@ -928,7 +974,7 @@ class MantisNode:
|
|
|
else:
|
|
|
name = 'NAME NOT FOUND'
|
|
|
prRed(f"No {input_name} target found for {name} in {self} because there is no connected node, or node is wrong type")
|
|
|
- return
|
|
|
+ return
|
|
|
if (isinstance(target.bGetObject(), PoseBone)):
|
|
|
subtarget = target.bGetObject().name
|
|
|
target = target.bGetParentArmature()
|
|
|
@@ -936,7 +982,7 @@ class MantisNode:
|
|
|
target = target.bGetObject()
|
|
|
else:
|
|
|
raise RuntimeError("Cannot interpret constraint or deformer target!")
|
|
|
-
|
|
|
+
|
|
|
if (isinstance(constraint_or_deformer, SplineIKConstraint)):
|
|
|
if target and target.type not in ["CURVE"]:
|
|
|
raise GraphError(wrapRed("Error: %s requires a Curve input, not %s" %
|
|
|
@@ -963,12 +1009,12 @@ class MantisNode:
|
|
|
return
|
|
|
def bModifierApply(self, bContext=None):
|
|
|
return
|
|
|
-
|
|
|
+
|
|
|
if environ.get("DOERROR"):
|
|
|
- def __repr__(self):
|
|
|
+ def __repr__(self):
|
|
|
return self.signature.__repr__()
|
|
|
else:
|
|
|
- def __repr__(self):
|
|
|
+ def __repr__(self):
|
|
|
return self.ui_signature.__repr__()
|
|
|
|
|
|
# do I need this and the link class above?
|
|
|
@@ -1004,7 +1050,7 @@ class NodeLink:
|
|
|
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.")
|
|
|
@@ -1018,7 +1064,7 @@ class NodeLink:
|
|
|
self.to_node.inputs[self.to_socket].links.append(self)
|
|
|
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.
|
|
|
@@ -1026,12 +1072,12 @@ class NodeLink:
|
|
|
# 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
|
|
|
@@ -1049,7 +1095,7 @@ 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):
|
|
|
@@ -1062,7 +1108,7 @@ class NodeSocket:
|
|
|
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
|
|
|
@@ -1082,22 +1128,22 @@ class NodeSocket:
|
|
|
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.links.sort(key=lambda a : -a.multi_input_sort_id)
|
|
|
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
|
|
|
|
|
|
@@ -1105,7 +1151,7 @@ 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 isinstance(socket, str):
|
|
|
@@ -1115,15 +1161,14 @@ class MantisNodeSocketCollection(dict):
|
|
|
self[socket.name] = NodeSocket(is_input=self.is_input, name=socket.name, node=self.node)
|
|
|
else:
|
|
|
raise RuntimeError(f"NodeSocketCollection keys must be str or MantisSocketTemplate, not {type(socket)}")
|
|
|
-
|
|
|
+
|
|
|
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())
|
|
|
-
|