|
|
@@ -88,7 +88,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
|
|
|
@@ -109,14 +109,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)
|
|
|
@@ -136,6 +136,8 @@ class MantisTree(NodeTree):
|
|
|
scene = bpy.context.scene
|
|
|
scene.render.use_lock_interface = True
|
|
|
self.parsed_tree = readtree.parse_tree(self, error_popups)
|
|
|
+ from .visualize import visualize_tree
|
|
|
+ visualize_tree(self.parsed_tree, self, context)
|
|
|
if context:
|
|
|
self.display_update(context)
|
|
|
self.tree_valid = True
|
|
|
@@ -160,7 +162,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.
|
|
|
@@ -225,7 +227,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.
|
|
|
@@ -242,7 +244,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
|
|
|
@@ -266,7 +268,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
|
|
|
@@ -292,7 +294,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)
|
|
|
@@ -305,7 +307,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
|
|
|
@@ -394,11 +396,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,)
|
|
|
@@ -411,7 +413,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:
|
|
|
@@ -447,7 +449,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:
|
|
|
@@ -571,10 +573,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
|
|
|
@@ -609,7 +611,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)
|
|
|
|
|
|
@@ -621,7 +623,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.
|
|
|
@@ -740,12 +742,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
|
|
|
@@ -763,7 +765,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:
|
|
|
@@ -775,7 +777,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])
|
|
|
@@ -786,12 +788,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)
|
|
|
@@ -841,7 +843,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):
|
|
|
@@ -849,7 +851,7 @@ class MantisNode:
|
|
|
if self.prepared==False: return # all good from here
|
|
|
for conn in self.hierarchy_connections:
|
|
|
conn.reset_execution_recursive()
|
|
|
-
|
|
|
+
|
|
|
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
|
|
|
@@ -859,12 +861,12 @@ class MantisNode:
|
|
|
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) -> 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??
|
|
|
@@ -919,7 +921,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
|
|
|
@@ -931,7 +933,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()
|
|
|
@@ -939,7 +941,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" %
|
|
|
@@ -966,12 +968,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?
|
|
|
@@ -1007,7 +1009,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.")
|
|
|
@@ -1021,7 +1023,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.
|
|
|
@@ -1029,12 +1031,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
|
|
|
@@ -1052,7 +1054,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):
|
|
|
@@ -1065,7 +1067,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
|
|
|
@@ -1085,22 +1087,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
|
|
|
|
|
|
@@ -1108,7 +1110,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):
|
|
|
@@ -1118,15 +1120,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())
|
|
|
-
|