Joseph Brandenburg преди 3 години
ревизия
2141a5fcc2

+ 198 - 0
__init__.py

@@ -0,0 +1,198 @@
+bl_info = {
+    "name": "Mantis Rigging Nodes",
+    "author": "Joseph Brandenburg",
+    "version": (000, 000, 000),
+    "blender": (3, 00, 0),
+    "location": "todo",
+    "description": "todo",
+    "warning": "experimental, likely to freeze or crash. Use at your own risk!",
+    "wiki_url": "",
+    "tracker_url": "",
+    "category": "Rigging"}
+
+
+from . import ( ops_nodegroup,
+                base_definitions,
+                socket_definitions,
+                link_definitions,
+                xForm_definitions,
+                nodes_generic,
+                primitives_definitions,
+                deformer_definitions,
+              )
+from mantis.ops_generate_tree import CreateMantisTree
+from bpy.types import NodeSocket
+
+
+
+classLists = [module.TellClasses() for module in [
+ link_definitions,
+ xForm_definitions,
+ base_definitions,
+ nodes_generic,
+ socket_definitions,
+ ops_nodegroup,
+ primitives_definitions,
+                deformer_definitions,
+]]
+# lol
+classLists.append( [CreateMantisTree] )
+#
+classes = []
+while (classLists):
+    classes.extend(classLists.pop())
+
+interface_classes = []
+for cls in [cls for cls in socket_definitions.TellClasses() if issubclass(cls, NodeSocket)]:
+    name = cls.__name__+"Interface"
+    from bpy.types import NodeSocketInterface
+    def default_draw_color(self, context,):
+        return self.color
+    def default_draw(self, context, layout):
+        return
+    interface = type(
+                  name,
+                  (NodeSocketInterface,),
+                  {
+                      "color"            : cls.color,
+                      "draw_color"       : default_draw_color,
+                      "draw"             : default_draw,
+                      "bl_idname"        : name,
+                      "bl_socket_idname" : cls.__name__,
+                  },
+              )
+    interface_classes.append(interface)
+
+classes.extend(interface_classes)
+
+import nodeitems_utils
+from nodeitems_utils import NodeCategory, NodeItem
+
+class AllNodeCategory(NodeCategory):
+    @classmethod
+    def poll(cls, context):
+        return (context.space_data.tree_type == 'MantisTree')
+
+
+
+# THIS is stupid, should be filled out automatically
+node_categories = [
+    # identifier, label, items list
+    AllNodeCategory('INPUT', "Input", items=[
+            NodeItem("UtilityMetaRig"),
+            NodeItem("InputFloatNode"),
+            NodeItem("InputVectorNode"),
+            NodeItem("InputBooleanNode"),
+            # NodeItem("InputBooleanThreeTupleNode"),
+            # NodeItem("InputRotationOrderNode"),
+            # NodeItem("InputTransformSpaceNode"),
+            NodeItem("InputStringNode"),
+            # NodeItem("InputQuaternionNode"),
+            # NodeItem("InputQuaternionNodeAA"),
+            NodeItem("InputMatrixNode"),
+            NodeItem("InputLayerMaskNode"),
+            NodeItem("InputExistingGeometryObject"),
+            NodeItem("InputExistingGeometryData"),
+    ]),
+    # AllNodeCategory('LINK', "Link", items=[]),
+    # AllNodeCategory('LINK_TRACKING', "Link", items=[]),
+    AllNodeCategory('LINK_TRANSFORM', "Link (Transform)", items=[
+        NodeItem("LinkCopyLocation"),
+        NodeItem("LinkCopyRotation"),
+        NodeItem("LinkCopyScale"),
+        NodeItem("LinkCopyTransforms"),
+        NodeItem("LinkLimitLocation"),
+        NodeItem("LinkLimitScale"),
+        NodeItem("LinkLimitRotation"),
+        NodeItem("LinkLimitDistance"),
+        NodeItem("LinkTransformation"),
+    ]),
+    AllNodeCategory('LINK_TRACKING', "Link (Tracking)", items=[
+        NodeItem("LinkInverseKinematics"),
+        NodeItem("LinkSplineIK"),
+        NodeItem("LinkStretchTo"),
+        NodeItem("LinkDampedTrack"),
+        NodeItem("LinkLockedTrack"),
+        NodeItem("LinkTrackTo"),
+    ]),
+    AllNodeCategory('LINK_RELATIONSHIP', "Link (Inheritance)", items=[
+        NodeItem("linkInherit"),
+        NodeItem("LinkInheritConstraint"),
+        NodeItem("LinkArmature"),
+    ]),
+    AllNodeCategory('DEFORMER', "Deformer", items=[
+            NodeItem("DeformerArmature"),
+    ]),
+    AllNodeCategory('XFORM', "Transform", items=[
+         NodeItem("xFormGeometryObject"),
+        # NodeItem("xFormNullNode"),
+        NodeItem("xFormBoneNode"),
+        NodeItem("xFormRootNode"),
+        NodeItem("xFormArmatureNode"),
+    ]),
+    AllNodeCategory('DRIVER', "Driver", items=[
+        NodeItem("UtilityFCurve"),
+        NodeItem("UtilityBoneProperties"),
+        NodeItem("LinkDrivenParameter"),
+        NodeItem("UtilityDriverVariable"),
+        NodeItem("UtilitySwitch"),
+        NodeItem("UtilityDriver"),
+    ]),
+    AllNodeCategory('GEOMETRY', "Geometry", items = [
+        NodeItem("GeometryCirclePrimitive"),
+    ]),
+    AllNodeCategory('UTILITIES', "Utility", items=[
+        NodeItem("UtilityCatStrings"),
+        NodeItem("UtilityCombineThreeBool"),
+        NodeItem("UtilityCombineVector"),
+    ]),
+    AllNodeCategory('GROUPS', "Groups", items=[
+        NodeItem("MantisNodeGroup"),
+    ]),
+]
+
+import bpy
+def init_keymaps():
+    kc = bpy.context.window_manager.keyconfigs.addon
+    km = kc.keymaps.new(name="Node Generic", space_type='NODE_EDITOR')
+    kmi = [
+        # km.keymap_items.new("sorcar.execute_node", 'E', 'PRESS'),
+        km.keymap_items.new("mantis.group_nodes", 'G', 'PRESS', ctrl=True),
+        km.keymap_items.new("mantis.edit_group", 'TAB', 'PRESS'),
+        km.keymap_items.new("mantis.query_sockets", 'Q', 'PRESS'),
+        km.keymap_items.new("mantis.execute_node_tree", 'E', 'PRESS'),
+        km.keymap_items.new("mantis.mute_node", 'M', 'PRESS'),
+        km.keymap_items.new("mantis.test_operator", 'T', 'PRESS'),
+        km.keymap_items.new("mantis.nodes_cleanup", "C", 'PRESS', shift=True,)
+    ]
+    return km, kmi
+
+addon_keymaps = []
+
+def register():
+    from bpy.utils import register_class
+    
+    for cls in classes:
+        register_class(cls)
+
+
+    nodeitems_utils.register_node_categories('AllNodeCategories', node_categories)
+
+
+    if (not bpy.app.background):
+        km, kmi = init_keymaps()
+        for k in kmi:
+            k.active = True
+            addon_keymaps.append((km, k))
+
+
+def unregister():
+    nodeitems_utils.unregister_node_categories('AllNodeCategories')
+
+    from bpy.utils import unregister_class
+    for cls in reversed(classes):
+        unregister_class(cls)
+    
+    for km, kmi in addon_keymaps:
+        km.keymap_items.remove(kmi)
+    addon_keymaps.clear()

+ 420 - 0
add_node.sh

@@ -0,0 +1,420 @@
+#!/bin/bash
+# A tool for adding the boilerplate code for adding a new node to the tree.
+
+parentclass=""
+nodefile=""
+cnodefile=""
+classfile=""
+icon=""
+
+
+#                     title: name           menu: name,      H  W  menu size    pairs of tag,description,  trick to swap stdout and stderr
+nodetype=$(whiptail --title "add_node.sh" --menu "Node Type" 25 78 16 "xForm" "" "Link" "" "Utility" "" 3>&2 2>&1 1>&3)
+if [[ $nodetype == "" ]]; then
+    echo "Cancelled."
+else
+    case $nodetype in
+      Link)
+        nodefile="link_definitions.py"
+        parentclass="LinkNode"
+        cnodefile="link_containers.py"
+        icon="CONSTRAINT_BONE"
+        ;;
+       
+      xForm)
+        nodefile="xForm_definitions.py"
+        cnodefile="xForm_containers.py"
+        parentclass="xFormNode"
+        icon="EMPTY_AXIS"
+        ;;
+        
+      Utility)
+        nodefile="nodes_generic.py"
+        cnodefile="misc_containers.py"
+        parentclass="MantisNode"
+        icon="NODE"
+        ;;
+    esac
+
+
+    cancelled=0
+
+    check_cancelled() {
+        if [[ $cancelled == "1" ]]; then
+            echo "Cancelled."
+            exit
+        fi
+    }
+    
+    
+    #read class_name
+    class_name=$(whiptail --inputbox "Name of new node?" 8 39 "" --title "add_node.sh" 3>&1 1>&2 2>&3)
+    cancelled=$?; check_cancelled
+    whiptail --title "add_node.sh" --msgbox \
+    "Node class will be named $nodetype$class_name$n_text with bl_idname $nodetype$class_name" 8 78
+    bl_label=$(whiptail --title "add_node.sh" --inputbox "UI Label new node class?" 8 39 "" 3>&1 1>&2 2>&3)
+    cancelled=$?; check_cancelled
+    docstring=$(whiptail --title "add_node.sh" --inputbox "Docstring for new node class?" 8 39 "" 3>&1 1>&2 2>&3)
+    cancelled=$?; check_cancelled
+    
+    # TODO: I would like to be able to define the number of inputs and their names
+    #        and the script will add them to the file
+    #        it should also give me the option to choose socket type and such
+    #       Utility nodes would also give the option of adding outputs.
+    
+    declare -i num_inputs="0"
+    declare -i num_outputs="0"
+    
+    if [[ $nodetype == 'Link' || $nodetype == 'Utility' ]]; then
+        choice=$(whiptail --title "add_node.sh"\
+          --inputbox "Number of inputs? Note: This number is in addition to the default inputs." 8 39 "0" 3>&1 1>&2 2>&3)
+        if [[ "$choice" -eq "$choice" ]]; then
+            num_inputs=$choice
+        else
+            echo "Error, must be a number"
+            cancelled=1
+        fi
+        cancelled=$?; check_cancelled
+    else
+        num_inputs=0
+    fi
+    
+    
+    if [[ $nodetype == 'Utility' ]]; then
+        choice=$(whiptail --title "add_node.sh"\
+          --inputbox "Number of outputs?" 8 39 "0" 3>&1 1>&2 2>&3)
+        if [[ "$choice" -eq "$choice" ]]; then
+            num_outputs=$choice
+        else
+            echo "Error, must be a number"
+            cancelled=1
+        fi
+        cancelled=$?; check_cancelled
+    else
+        num_outputs=0
+    fi
+    
+    if [[ $nodetype == 'Utility' && $num_inputs == "0" && $num_outputs == "0" ]]; then
+        echo "Error. The node must have at least one socket."
+        exit
+    fi
+    # now, set a flag if this is a utility and has no inputs but has outputs
+    isinput=0
+    if [[ $nodetype == 'Utility' && $num_inputs == "0" && $num_outputs -gt 0 ]]; then
+        isinput=1
+    fi
+    
+    
+    
+    
+    n_text=Node # surely there is a better way to do this...
+    classheader=\
+"\nclass $nodetype$class_name$n_text(Node, $parentclass):
+    \"\"\"$docstring\"\"\"
+    bl_idname = \"$nodetype$class_name\"
+    bl_label = \"$bl_label\"
+    bl_icon = \"$icon\"
+    
+    def init(self, context):\n"
+    printf "$(echo "$classheader")">classheader.txt
+    
+    #now do the cnode:
+    echo "class $nodetype$class_name:" > cnode_def # this is the bl_idname!! important!
+    echo "    '''A node representing an armature object'''" >> cnode_def
+    echo >> cnode_def
+    if [[ $nodetype == 'xForm' ]]; then
+        echo "    bObject = None echo " >> cnode_def
+        echo >> cnode_def
+    fi
+    
+    echo "    def __init__(self, signature, base_tree):" >> cnode_def
+    echo "        self.base_tree=base_tree" >> cnode_def
+    echo "        self.executed = False" >> cnode_def
+    echo "        self.signature = signature" >> cnode_def
+    echo "        self.inputs = {" >> cnode_def
+    
+    echo > parameters
+    
+    if [[ $nodetype == 'xForm' ]]; then
+        #node
+        echo "        self.inputs.new('StringSocket', \"Name\")" >> classheader.txt
+        echo "        self.inputs.new('MatrixSocket', \"Matrix\")" >> classheader.txt
+        echo "        self.inputs.new('RelationshipSocket', \"Relationship\")" >> classheader.txt
+        #cnode
+        echo "          \"Name\"           : NodeSocket(is_input = True, to_socket = \"Name\", to_node = self)," >> cnode_def
+        echo "          \"Rotation Order\" : NodeSocket(is_input = True, to_socket = \"Rotation Order\", to_node = self)," >> cnode_def
+        echo "          \"Matrix\"         : NodeSocket(is_input = True, to_socket = \"Matrix\", to_node = self)," >> cnode_def
+        echo "          \"Relationship\"   : NodeSocket(is_input = True, to_socket = \"Relationship\", to_node = self)," >> cnode_def
+        #parameters; should be identical to cnode inputs
+        echo "          \"Name\":None, " >> parameters
+        echo "          \"Rotation Order\":None, " >> parameters
+        echo "          \"Matrix\":None, " >> parameters
+        echo "          \"Relationship\":None, " >> parameters
+    elif [[ $nodetype == 'Link' ]]; then
+        #node
+        echo "        self.inputs.new (\"RelationshipSocket\", \"Input Relationship\")" >> classheader.txt
+        #cnode
+        echo "        \"Input Relationship\" : NodeSocket(is_input = True, to_socket = \"Input Relationship\", to_node = self,)," >> cnode_def
+        #parameters; should be identical to cnode inputs
+        echo "        \"Input Relationship\":None, " >> parameters
+    fi
+    
+    
+    # New Inputs
+    until [[ $num_inputs == "0" ]]; do
+        sockettype=$(whiptail --title "add_node.sh" --menu "Input Socket Type" 25 78 16\
+          "RelationshipSocket" ""\
+          "MatrixSocket" "" \
+          "xFormSocket" ""\
+          "GenericRotationSocket" ""\
+          "RelationshipSocket" ""\
+          "xFormParameterSocket" ""\
+          "DriverSocket" ""\
+          "DriverVariableSocket" ""\
+          "TransformSpaceSocket" ""\
+          "BooleanSocket" ""\
+          "BooleanThreeTupleSocket" ""\
+          "RotationOrderSocket" ""\
+          "QuaternionSocket" ""\
+          "QuaternionSocketAA" ""\
+          "IntSocket" ""\
+	  "GeometrySocket" ""\
+          "StringSocket" ""\
+          "LayerMaskSocket" ""\
+          "BoolUpdateParentNode" ""\
+          "LabelSocket" ""\
+          "IKChainLengthSocket" ""\
+          "EnumInheritScale" ""\
+          "EnumRotationMix" ""\
+          "EnumRotationMixCopyTransforms" ""\
+          "EnumMaintainVolumeStretchTo" ""\
+          "EnumRotationStretchTo" ""\
+          "EnumTrackAxis" ""\
+          "EnumUpAxis" ""\
+          "EnumLockAxis" ""\
+          "EnumLimitMode" ""\
+          "EnumDriverVariableType" ""\
+          "EnumDriverVariableEvaluationSpace" ""\
+          "EnumDriverRotationMode" ""\
+          "FloatSocket" ""\
+          "FloatFactorSocket" ""\
+          "FloatAngleSocket" ""\
+          "VectorSocket" ""\
+          "VectorEulerSocket" ""\
+          "VectorTranslationSocket" ""\
+          "VectorScaleSocket" ""\
+          3>&2 2>&1 1>&3)
+        socketname=$(whiptail --title "add_node.sh"\
+          --inputbox "Input Socket Name" 8 39 "" 3>&1 1>&2 2>&3)
+        cancelled=$?; check_cancelled
+        ((num_inputs = num_inputs-1))
+        #node
+        echo "        self.inputs.new(\"$sockettype\", \"$socketname\")" >> classheader.txt
+        #cnode
+        echo "          \"$socketname\"   : NodeSocket(is_input = True, to_socket = \"$socketname\", to_node = self)," >> cnode_def
+        #parameters; should be identical to cnode inputs
+        echo "          \"$socketname\":None, " >> parameters
+    done
+    
+    echo "        }" >> cnode_def
+    echo "        self.outputs = {" >> cnode_def
+    
+    # add the defaults for xForm, Link:
+    if [[ $nodetype == 'xForm' ]]; then
+        #node
+        echo "        self.outputs.new('xFormSocket', \"xForm Out\")" >> classheader.txt
+        #cnode
+        echo "          \"xForm Out\" : NodeSocket(from_socket=\"xForm Out\", from_node = self), }" >> cnode_def
+    elif [[ $nodetype == 'Link' ]]; then
+        #node
+        echo "        self.outputs.new(\"RelationshipSocket\", \"Output Relationship\")" >> classheader.txt
+        #cnode
+        echo "          \"Output Relationship\" : NodeSocket(from_socket = \"Output Relationship\", from_node=self), }" >> cnode_def
+    # New Outputs
+    elif [[ $nodetype == 'Utility' ]]; then
+        
+        until [[ $num_outputs == "0" ]]; do
+            sockettype=$(whiptail --title "add_node.sh" --menu "Output Socket Type" 25 78 16\
+            "RelationshipSocket" ""\
+            "MatrixSocket" "" \
+            "xFormSocket" ""\
+            "GenericRotationSocket" ""\
+            "RelationshipSocket" ""\
+            "xFormParameterSocket" ""\
+            "DriverSocket" ""\
+            "DriverVariableSocket" ""\
+            "TransformSpaceSocket" ""\
+            "BooleanSocket" ""\
+            "BooleanThreeTupleSocket" ""\
+            "RotationOrderSocket" ""\
+            "QuaternionSocket" ""\
+            "QuaternionSocketAA" ""\
+            "IntSocket" ""\
+	    "GeometrySocket" ""\
+            "StringSocket" ""\
+            "LayerMaskSocket" ""\
+            "BoolUpdateParentNode" ""\
+            "LabelSocket" ""\
+            "IKChainLengthSocket" ""\
+            "EnumInheritScale" ""\
+            "EnumRotationMix" ""\
+            "EnumRotationMixCopyTransforms" ""\
+            "EnumMaintainVolumeStretchTo" ""\
+            "EnumRotationStretchTo" ""\
+            "EnumTrackAxis" ""\
+            "EnumUpAxis" ""\
+            "EnumLockAxis" ""\
+            "EnumLimitMode" ""\
+            "EnumDriverVariableType" ""\
+            "EnumDriverVariableEvaluationSpace" ""\
+            "EnumDriverRotationMode" ""\
+            "FloatSocket" ""\
+            "FloatFactorSocket" ""\
+            "FloatAngleSocket" ""\
+            "VectorSocket" ""\
+            "VectorEulerSocket" ""\
+            "VectorTranslationSocket" ""\
+            "VectorScaleSocket" ""\
+            3>&2 2>&1 1>&3)
+            socketname=$(whiptail --title "add_node.sh"\
+            --inputbox "Output Socket Name" 8 39 "" 3>&1 1>&2 2>&3)
+            cancelled=$?; check_cancelled
+            ((num_outputs = num_outputs-1))
+            #node
+            echo "        self.outputs.new(\"$sockettype\", \"$socketname\")" >> classheader.txt
+            #cnode
+            echo "          \"$socketname\" : NodeSocket(from_socket = \"$socketname\", from_node=self)," >> cnode_def
+            #parameters , this time it should by the cnode outputs!
+            echo "          \"$socketname\":None, " >> parameters
+        done
+        echo "        }" >> cnode_def
+    fi
+    
+    #cnode
+    echo "        self.parameters = {" >> cnode_def
+    cat parameters >> cnode_def
+    echo "        }" >> cnode_def
+    if [[ $nodetype == 'xForm' ]]; then
+        echo "        self.links = {} # leave this empty for now!" >> cnode_def
+        echo "        # now set up the traverse target..." >> cnode_def
+        echo "        self.inputs["Relationship"].set_traverse_target(self.outputs["xForm Out"])" >> cnode_def
+        echo "        self.outputs["xForm Out"].set_traverse_target(self.inputs["Relationship"])" >> cnode_def
+        echo "        self.node_type = \"XFORM\"" >> cnode_def
+    elif [[ $nodetype == 'Link' ]]; then
+        echo "        # now set up the traverse target..." >> cnode_def
+        echo "        self.inputs[\"Input Relationship\"].set_traverse_target(self.outputs[\"Output Relationship\"])" >> cnode_def
+        echo "        self.outputs[\"Output Relationship\"].set_traverse_target(self.inputs[\"Input Relationship\"])" >> cnode_def
+        echo "        self.node_type = \"LINK\"" >> cnode_def
+    else
+        echo "        self.node_type = \"UTILITY\"" >> cnode_def
+    fi
+    echo >> cnode_def
+    echo "    def evaluate_input(self, input_name):" >> cnode_def
+    echo "        return evaluate_input(self, input_name)" >> cnode_def
+    echo >> cnode_def
+    echo "    def bExecute(self, bContext = None,):" >> cnode_def
+    echo "        pass" >> cnode_def
+    echo >> cnode_def
+    echo "    def __repr__(self):" >> cnode_def
+    echo "        return self.signature.__repr__()" >> cnode_def
+    echo >> cnode_def    
+    echo "    def fill_parameters(self, node_prototype):" >> cnode_def
+    echo "        fill_parameters(self, node_prototype)" >> cnode_def
+    # now it's done!
+    
+    cat cnode_def >> $cnodefile
+    
+    #time to fill upo the node definition
+    echo "    def traverse(self, socket):" >> classheader.txt
+    echo "        return default_traverse(self,socket)" >> classheader.txt
+    
+    # NODE FILE
+    # operate on a duplicate of the file, use sed to rename.
+    bakfile=$(echo $nodefile | sed s/.py/.bak.py/g)
+    cp $nodefile $bakfile
+
+    #find the line that is at the end of TellClasses:
+    declare -i tc_end=$(grep -n -m 1 "]" $bakfile |  cut -f1 -d:)
+    ((tc_end=$tc_end-1))
+    # the total length of the file, in lines
+    nodefile_len=$(cat $bakfile | wc -l)
+    
+    #get indentation level
+    declare -i ind_level=$(head -n $tc_end $bakfile | tail -n 1 | grep -o " " | wc -l)
+    
+    #create the string (the class name with the proper indentation, ending in a comma).
+    tc_line_add="$tc_line_add$nodetype$class_name$n_text"
+    until [ $ind_level == 0 ]
+    do
+        tc_line_add=" $tc_line_add"
+        ((ind_level--))
+    done
+    tc_line_add="$tc_line_add,"
+    
+    #slice the text, then add some stuff to the middle, then add the end back to it
+    head -n $tc_end $bakfile > tmp
+    echo "$tc_line_add" >> tmp
+    ((tc_end=$tc_end+1))
+    tail -n +$tc_end $bakfile >> tmp
+    cp tmp $bakfile
+    cat classheader.txt >> $bakfile # add the class
+    
+    cp $bakfile $nodefile
+    rm $bakfile
+    
+    # __init__.py
+    # operate on a duplicate of the file, use sed to rename.
+    bakfile="__init__.bak.py"
+    cp __init__.py $bakfile
+
+    #find the line that marks the node category.
+    declare -i tc_end
+    if [[ $nodetype == 'Link' ]]; then
+        tc_end=$(grep -n -m 1 "AllNodeCategory('LINK'"  $bakfile |  cut -f1 -d:)
+    elif [[ $nodetype == 'xForm' ]]; then
+        tc_end=$(grep -n -m 1 "AllNodeCategory('XFORM'" $bakfile |  cut -f1 -d:)
+    elif [[ $nodetype == 'Utility' && $isinput == "0" ]]; then
+        tc_end=$(grep -n -m 1 "AllNodeCategory('UTILITIES'" $bakfile |  cut -f1 -d:)
+    elif [[ $nodetype == 'Utility' && $isinput == "1" ]]; then
+        tc_end=$(grep -n -m 1 "AllNodeCategory('INPUT'" $bakfile |  cut -f1 -d:)
+    fi
+    
+    # the total length of the file, in lines
+    nodefile_len=$(cat $bakfile | wc -l)
+    
+    #get indentation level
+    ((tc_end=$tc_end+1))
+    declare -i ind_level=$(head -n $tc_end $bakfile | tail -n 1 | grep -o " " | wc -l)
+    ((tc_end=$tc_end-1))
+    
+    #create the string (the class name with the proper indentation, ending in a comma).
+    tc_line_add="NodeItem(\"$nodetype$class_name\")"
+    until [ $ind_level == 0 ]
+    do
+        tc_line_add=" $tc_line_add"
+        ((ind_level--))
+    done
+    tc_line_add="$tc_line_add,"
+    
+    #slice the text, then add some stuff to the middle, then add the end back to it
+    head -n $tc_end $bakfile > tmp
+    echo "$tc_line_add" >> tmp
+    ((tc_end=$tc_end+1))
+    tail -n +$tc_end $bakfile >> tmp
+    cp tmp $bakfile
+    cp $bakfile __init__.py
+    rm $bakfile
+    
+    
+    #clean up
+    rm classheader.txt
+    rm tmp
+    rm cnode_def
+    rm parameters
+    
+    # now we need to do the same for the container classes.
+    whiptail --title "add_node.sh" --msgbox \
+    "Finished adding node to addon!" 8 78
+    
+fi
+

+ 227 - 0
base_definitions.py

@@ -0,0 +1,227 @@
+#Mantis Nodes Base
+import bpy
+from bpy.props import BoolProperty, StringProperty, EnumProperty, CollectionProperty, IntProperty
+from . import ops_nodegroup
+from bpy.types import NodeTree, Node, PropertyGroup, Operator, UIList, Panel
+
+from mantis.utilities import (prRed, prGreen, prPurple, prWhite,
+                              prOrange,
+                              wrapRed, wrapGreen, wrapPurple, wrapWhite,
+                              wrapOrange,)
+
+from bpy.app.handlers import persistent
+
+def TellClasses():
+    #Why use a function to do this? Because I don't need every class to register.
+    return [MantisNodeGroup, MantisTree, ]
+
+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
+    locked:BoolProperty(default=True)
+    
+    num_links:IntProperty(default=-1)
+    
+    parsed_tree={}
+    
+    def interface_update(self, context):
+        # no idea what this does
+        print ("Update Interface function in MantisTree class")
+
+           
+    def interface_update(self, context):
+        prGreen("interface_update")
+        
+
+
+    if bpy.app.version >= (3, 2):  # in 3.1 this can lead to a crash
+        @classmethod
+        def valid_socket_type(cls, socket_type: str):
+            # https://docs.blender.org/api/master/bpy.types.NodeTree.html#bpy.types.NodeTree.valid_socket_type
+            from mantis.socket_definitions import Tell_bl_idnames
+            return socket_type in Tell_bl_idnames()
+            # thank you, Sverchok
+            
+    def update_tree(self, context):
+        if self.do_live_update == False:
+            return
+        from mantis import readtree
+        prGreen("Validating Tree: %s" % self.name)
+        parsed_tree = readtree.parse_tree(self)
+        self.parsed_tree=parsed_tree
+        current_tree = bpy.context.space_data.path[-1].node_tree
+        self.tree_valid = True
+        prWhite("Number of Nodes: %s" % (len(self.parsed_tree)))
+        self.display_update(context)
+    
+    def display_update(self, context):
+        if self.do_live_update == False:
+            return
+        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)))
+                    # raise e
+        
+    
+    def execute_tree(self,context):
+        prGreen("Executing Tree: %s" % self.name)
+        from mantis import readtree
+        readtree.execute_tree(self.parsed_tree, self, context)
+
+    
+
+def update_handler(scene):
+    context=bpy.context
+    if context.space_data:
+        node_tree = context.space_data.path[0].node_tree
+        if node_tree.do_live_update:
+            prev_links = node_tree.num_links
+            node_tree.num_links = len(node_tree.links)
+            if (prev_links == -1):
+                return
+            if prev_links != node_tree.num_links:
+                node_tree.tree_valid = False
+            if node_tree.tree_valid == False:
+                from mantis import readtree
+                node_tree.update_tree(context)
+
+def execute_handler(scene):
+    context = bpy.context
+    if context.space_data:
+        node_tree = context.space_data.path[0].node_tree
+        if node_tree.tree_valid and node_tree.do_live_update:
+            node_tree.execute_tree(context)
+            self.tree_valid = False
+
+# bpy.app.handlers.load_post.append(set_tree_invalid)
+bpy.app.handlers.depsgraph_update_pre.append(update_handler)
+bpy.app.handlers.depsgraph_update_post.append(execute_handler)
+    
+
+class MantisNode:
+    num_links:IntProperty(default=-1)
+    # do_display_update:BoolProperty(default=False)
+    @classmethod
+    def poll(cls, ntree):
+        return (ntree.bl_idname == 'MantisTree')
+                
+    def insert_link(self, link):
+        context = bpy.context
+        if context.space_data:
+            node_tree = context.space_data.path[0].node_tree
+            from mantis import readtree
+            prOrange("Updating from insert_link callback")
+            node_tree.update_tree(context)
+            if (link.to_socket.is_linked == False):
+                node_tree.num_links+=1
+            elif (link.to_socket.is_multi_input and 
+                  link.to_socket.links < link.to_socket.link_limit ):
+                node_tree.num_links+=1
+            
+                
+
+class LinkNode(MantisNode):
+    useTarget : BoolProperty(default=False)
+    @classmethod
+    def poll(cls, ntree):
+        return (ntree.bl_idname == 'MantisTree')
+
+class xFormNode(MantisNode):
+    @classmethod
+    def poll(cls, ntree):
+        return (ntree.bl_idname == 'MantisTree')
+
+class DeformerNode(MantisNode):
+    @classmethod
+    def poll(cls, ntree):
+        return (ntree.bl_idname == 'MantisTree')
+
+
+
+from bpy.types import NodeCustomGroup
+# TODO: make this one's traverse() function actually work
+class MantisNodeGroup(NodeCustomGroup, MantisNode):
+    bl_idname = "MantisNodeGroup"
+    bl_label = "Node Group"
+    
+    # def poll_node_tree(self, object):
+        # if object.bl_idname not in "MantisTree":
+            # return False
+        # context=bpy.context
+        # context = bpy.context
+        # if context.space_data:
+            # used_trees = [ pathitem.node_tree for pathitem in context.space_data.path]
+            # if object in used_trees:
+                # return False
+    # node_tree:bpy.props.PointerProperty(type=bpy.types.NodeTree, poll=poll_node_tree)
+    
+    def init(self, context):
+        pass
+    
+    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)
+        
+# I don't remember why I need this?
+class GroupOutputDummySocket:
+    # a dummy class for acting like a socket that is coming from every
+    #  group output node
+    def __init__(self, tree, identifier, is_input=True):
+        #
+        # So, we need to go through the node tree and find all 
+        #   the Group Input sockets that match this
+        #   socket's identifier
+        #
+        sockets = []
+        s = None
+        for node in tree.nodes:
+            if (is_input):
+                if node.bl_idname == 'NodeGroupInput':
+                    # Group Inputs have outputs... confusing.
+                    for s in node.outputs:
+                        if (s.identifier == identifier):
+                            sockets.append(s)
+            else:
+                if node.bl_idname == 'NodeGroupOutput':
+                    for s in node.inputs:
+                        if (s.identifier == identifier):
+                            sockets.append(s)
+        sock = sockets[-1]
+        # whatever the last socket is should be OK for most of this stuff
+        self.bl_idname=sock.bl_idname
+        self.identifier = identifier
+        self.name = sock.name
+        is_linked = False
+        for s in sockets:
+            if s.is_linked:
+                is_linked = True; break
+        self.is_linked = is_linked
+        self.is_output = not is_input
+        # hopefully this doesn't matter, since it is a group node...
+        self.node = sock.node
+        self.links = []
+        for s in sockets:
+            self.links.extend(s.links)
+        # seems to werk
+
+class CircularDependencyError(Exception):
+    pass
+class GraphError(Exception):
+    pass
+
+def get_signature_from_edited_tree(self, 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+[self.name])

+ 152 - 0
deformer_containers.py

@@ -0,0 +1,152 @@
+from mantis.node_container_common import *
+from mantis.xForm_containers import xFormGeometryObject
+from bpy.types import Node
+from .base_definitions import MantisNode
+
+def TellClasses():
+             
+    return [ 
+             DeformerArmature,
+           ]
+
+
+
+def default_evaluate_input(nc, input_name):
+    # duped from link_containers... should be common?
+    # should catch 'Target', 'Pole Target' and ArmatureConstraint targets, too
+    if ('Target' in input_name) and input_name != "Target Space":
+        socket = nc.inputs.get(input_name)
+        if socket.is_linked:
+            return socket.links[0].from_node
+        return None
+        
+    else:
+        return evaluate_input(nc, input_name)
+
+
+# semi-duplicated from link_containers
+def GetxForm(nc):
+    trace = trace_single_line_up(nc, "Deformer")
+    for node in trace[0]:
+        if (node.__class__ in [xFormGeometryObject]):
+            return node
+    raise GraphError("%s is not connected to a downstream xForm" % nc)
+
+class DeformerArmature:
+    '''A node representing an armature deformer'''
+
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+          "Input Relationship" : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+          "Target"             : NodeSocket(is_input = True, name = "Target", node = self,),
+          "Vertex Group"       : NodeSocket(is_input = True, name = "Vertex Group", node = self),
+          "Preserve Volume"    : NodeSocket(is_input = True, name = "Preserve Volume", node = self),
+          "Use Multi Modifier" : NodeSocket(is_input = True, name = "Use Multi Modifier", node = self),
+          "Use Envelopes"      : NodeSocket(is_input = True, name = "Use Envelopes", node = self),
+          "Use Vertex Groups"  : NodeSocket(is_input = True, name = "Use Vertex Groups", node = self),
+          "Skinning Method"    : NodeSocket(is_input = True, name = "Skinning Method", node = self),
+        }
+        self.outputs = {
+          "Deformer" : NodeSocket(is_input = False, name = "Deformer", node=self), }
+        self.parameters = {
+          "Name"               : None,
+          "Target"             : None,
+          "Vertex Group"       : None,
+          "Preserve Volume"    : None,
+          "Use Multi Modifier" : None,
+          "Use Envelopes"      : None,
+          "Use Vertex Groups"  : None,
+          "Skinning Method"    : None,
+        }
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Deformer"])
+        self.outputs["Deformer"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = "LINK"
+
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def bExecute(self, bContext = None,):
+        prGreen("Executing Armature Deform Node\n")
+        print(self.GetxForm())
+        prOrange("My object: %s\n" % (self.GetxForm().bGetObject()))
+        prepare_parameters(self)
+        d = self.GetxForm().bGetObject().modifiers.new(self.evaluate_input("Name"), type='ARMATURE')
+        self.bObject = d
+        get_target_and_subtarget(self, d)
+        props_sockets = {
+        'vertex_group'       : ("Vertex Group", ""),
+        'use_deform_preserve_volume' : ("Preserve Volume", False),
+        'use_multi_modifier' : ("Use Multi Modifier", False),
+        'use_bone_envelopes' : ("Use Envelopes", False),
+        'use_vertex_groups' : ("Use Vertex Groups", False),
+        }
+        evaluate_sockets(self, d, props_sockets)   
+    
+    def initialize_vgroups(self, use_existing = False):
+        ob = self.GetxForm().bGetObject()
+        if use_existing == False:
+            ob.vertex_groups.clear()
+        armOb = self.evaluate_input("Target").bGetObject()
+        deform_bones = []
+        for b in armOb.data.bones:
+            if b.use_deform == True:
+                deform_bones.append(b)
+        for b in deform_bones:
+            vg = ob.vertex_groups.get(b.name)
+            if not vg:
+                vg = ob.vertex_groups.new(name=b.name)
+                num_verts = len(ob.data.vertices)
+                vg.add(range(num_verts), 0, 'REPLACE')
+            
+    
+    def bFinalize(self, bContext=None):
+        if (skin_method := self.evaluate_input("Skinning Method")) == "AUTOMATIC_HEAT":
+            # This is reatarded and leads to somewhat unpredictable
+            #  behaviour, e.g. what object will be selected? What mode?
+            # also bpy.ops is ugly and prone to error when used in
+            #  scripts. I don't intend to use bpy.ops when I can avoid it.
+            import bpy
+            self.initialize_vgroups()
+            bContext.view_layer.depsgraph.update()
+            ob = self.GetxForm().bGetObject()
+            armOb = self.evaluate_input("Target").bGetObject()
+            deform_bones = []
+            for pb in armOb.pose.bones:
+                if pb.bone.use_deform == True:
+                    deform_bones.append(pb)
+            
+            context_override = {
+                                  'active_object':ob,
+                                  'selected_objects':[ob, armOb],
+                                  'active_pose_bone':deform_bones[0],
+                                  'selected_pose_bones':deform_bones,}
+            #
+            bpy.ops.object.mode_set({'active_object':armOb}, mode='POSE')
+            bpy.ops.pose.select_all(action='SELECT')
+            #
+            bpy.ops.paint.weight_paint_toggle(context_override)
+            bpy.ops.paint.weight_from_bones(context_override, type='AUTOMATIC')
+            # this is not working right now but I would like to normalize stuff.
+            # hmm. I think it normalizes automatically?
+            # bpy.ops.object.vertex_group_normalize_all({'active_object':ob}, group_select_mode='BONE_DEFORM', lock_active=False)
+            bpy.ops.paint.weight_paint_toggle(context_override)
+            #
+            bpy.ops.object.mode_set({'active_object':armOb}, mode='POSE')
+            bpy.ops.pose.select_all(action='DESELECT')
+            bpy.ops.object.mode_set({'active_object':armOb}, mode='OBJECT')
+            # TODO: modify Blender to make this available as a Python API function.
+        if skin_method == "EXISTING_GROUPS":
+            self.initialize_vgroups(use_existing = True)
+
+        
+    def __repr__(self):
+        return self.signature.__repr__()
+
+    def fill_parameters(self):
+        fill_parameters(self)

+ 287 - 0
deformer_definitions.py

@@ -0,0 +1,287 @@
+import bpy
+from bpy.types import NodeTree, Node, NodeSocket
+from .base_definitions import MantisNode, DeformerNode
+
+
+def TellClasses():
+    return [
+             DeformerArmatureNode,
+           ]
+icons = (
+          'NONE', 'QUESTION', 'ERROR', 'CANCEL', 'TRIA_RIGHT',
+          'TRIA_DOWN', 'TRIA_LEFT', 'TRIA_UP', 'ARROW_LEFTRIGHT',
+          'PLUS', 'DISCLOSURE_TRI_RIGHT', 'DISCLOSURE_TRI_DOWN',
+          'RADIOBUT_OFF', 'RADIOBUT_ON', 'MENU_PANEL', 'BLENDER',
+          'GRIP', 'DOT', 'COLLAPSEMENU', 'X', 'DUPLICATE', 'TRASH',
+          'COLLECTION_NEW', 'OPTIONS', 'NODE', 'NODE_SEL', 'WINDOW', 
+          'WORKSPACE', 'RIGHTARROW_THIN', 'BORDERMOVE', 'VIEWZOOM',
+          'ADD', 'REMOVE', 'PANEL_CLOSE', 'COPY_ID', 'EYEDROPPER',
+          'CHECKMARK', 'AUTO', 'CHECKBOX_DEHLT', 'CHECKBOX_HLT',
+          'UNLOCKED', 'LOCKED', 'UNPINNED', 'PINNED', 'SCREEN_BACK',
+          'RIGHTARROW', 'DOWNARROW_HLT', 'FCURVE_SNAPSHOT',
+          'OBJECT_HIDDEN', 'TOPBAR', 'STATUSBAR', 'PLUGIN', 'HELP',
+          'GHOST_ENABLED', 'COLOR', 'UNLINKED', 'LINKED', 'HAND',
+          'ZOOM_ALL', 'ZOOM_SELECTED', 'ZOOM_PREVIOUS', 'ZOOM_IN',
+          'ZOOM_OUT', 'DRIVER_DISTANCE', 'DRIVER_ROTATIONAL_DIFFERENCE',
+          'DRIVER_TRANSFORM', 'FREEZE', 'STYLUS_PRESSURE',
+          'GHOST_DISABLED', 'FILE_NEW', 'FILE_TICK', 'QUIT', 'URL',
+          'RECOVER_LAST', 'THREE_DOTS', 'FULLSCREEN_ENTER',
+          'FULLSCREEN_EXIT', 'BRUSHES_ALL', 'LIGHT', 'MATERIAL',
+          'TEXTURE', 'ANIM', 'WORLD', 'SCENE', 'OUTPUT', 'SCRIPT',
+          'PARTICLES', 'PHYSICS', 'SPEAKER', 'TOOL_SETTINGS',
+          'SHADERFX', 'MODIFIER', 'BLANK1', 'FAKE_USER_OFF',
+          'FAKE_USER_ON', 'VIEW3D', 'GRAPH', 'OUTLINER', 'PROPERTIES',
+          'FILEBROWSER', 'IMAGE', 'INFO', 'SEQUENCE', 'TEXT',
+          'SPREADSHEET', 'SOUND', 'ACTION', 'NLA', 'PREFERENCES',
+          'TIME', 'NODETREE', 'GEOMETRY_NODES', 'CONSOLE', 'TRACKER',
+          'ASSET_MANAGER', 'NODE_COMPOSITING', 'NODE_TEXTURE',
+          'NODE_MATERIAL', 'UV', 'OBJECT_DATAMODE', 'EDITMODE_HLT',
+          'UV_DATA', 'VPAINT_HLT', 'TPAINT_HLT', 'WPAINT_HLT',
+          'SCULPTMODE_HLT', 'POSE_HLT', 'PARTICLEMODE', 'TRACKING',
+          'TRACKING_BACKWARDS', 'TRACKING_FORWARDS',
+          'TRACKING_BACKWARDS_SINGLE', 'TRACKING_FORWARDS_SINGLE',
+          'TRACKING_CLEAR_BACKWARDS', 'TRACKING_CLEAR_FORWARDS',
+          'TRACKING_REFINE_BACKWARDS', 'TRACKING_REFINE_FORWARDS',
+          'SCENE_DATA', 'RENDERLAYERS', 'WORLD_DATA', 'OBJECT_DATA',
+          'MESH_DATA', 'CURVE_DATA', 'META_DATA', 'LATTICE_DATA',
+          'LIGHT_DATA', 'MATERIAL_DATA', 'TEXTURE_DATA', 'ANIM_DATA',
+          'CAMERA_DATA', 'PARTICLE_DATA', 'LIBRARY_DATA_DIRECT',
+          'GROUP', 'ARMATURE_DATA', 'COMMUNITY', 'BONE_DATA',
+          'CONSTRAINT', 'SHAPEKEY_DATA', 'CONSTRAINT_BONE',
+          'CAMERA_STEREO', 'PACKAGE', 'UGLYPACKAGE', 'EXPERIMENTAL',
+          'BRUSH_DATA', 'IMAGE_DATA', 'FILE', 'FCURVE', 'FONT_DATA',
+          'RENDER_RESULT', 'SURFACE_DATA', 'EMPTY_DATA', 'PRESET',
+          'RENDER_ANIMATION', 'RENDER_STILL', 'LIBRARY_DATA_BROKEN',
+          'BOIDS', 'STRANDS', 'GREASEPENCIL', 'LINE_DATA',
+          'LIBRARY_DATA_OVERRIDE', 'GROUP_BONE', 'GROUP_VERTEX',
+          'GROUP_VCOL', 'GROUP_UVS', 'FACE_MAPS', 'RNA', 'RNA_ADD',
+          'MOUSE_LMB', 'MOUSE_MMB', 'MOUSE_RMB', 'MOUSE_MOVE',
+          'MOUSE_LMB_DRAG', 'MOUSE_MMB_DRAG', 'MOUSE_RMB_DRAG',
+          'MEMORY', 'PRESET_NEW', 'DECORATE', 'DECORATE_KEYFRAME',
+          'DECORATE_ANIMATE', 'DECORATE_DRIVER', 'DECORATE_LINKED',
+          'DECORATE_LIBRARY_OVERRIDE', 'DECORATE_UNLOCKED',
+          'DECORATE_LOCKED', 'DECORATE_OVERRIDE', 'FUND',
+          'TRACKER_DATA', 'HEART', 'ORPHAN_DATA', 'USER', 'SYSTEM',
+          'SETTINGS', 'OUTLINER_OB_EMPTY', 'OUTLINER_OB_MESH',
+          'OUTLINER_OB_CURVE', 'OUTLINER_OB_LATTICE',
+          'OUTLINER_OB_META', 'OUTLINER_OB_LIGHT', 'OUTLINER_OB_CAMERA',
+          'OUTLINER_OB_ARMATURE', 'OUTLINER_OB_FONT',
+          'OUTLINER_OB_SURFACE', 'OUTLINER_OB_SPEAKER',
+          'OUTLINER_OB_FORCE_FIELD', 'OUTLINER_OB_GROUP_INSTANCE',
+          'OUTLINER_OB_GREASEPENCIL', 'OUTLINER_OB_LIGHTPROBE',
+          'OUTLINER_OB_IMAGE', 'OUTLINER_COLLECTION',
+          'RESTRICT_COLOR_OFF', 'RESTRICT_COLOR_ON', 'HIDE_ON',
+          'HIDE_OFF', 'RESTRICT_SELECT_ON', 'RESTRICT_SELECT_OFF',
+          'RESTRICT_RENDER_ON', 'RESTRICT_RENDER_OFF',
+          'RESTRICT_INSTANCED_OFF', 'OUTLINER_DATA_EMPTY',
+          'OUTLINER_DATA_MESH', 'OUTLINER_DATA_CURVE',
+          'OUTLINER_DATA_LATTICE', 'OUTLINER_DATA_META',
+          'OUTLINER_DATA_LIGHT', 'OUTLINER_DATA_CAMERA',
+          'OUTLINER_DATA_ARMATURE', 'OUTLINER_DATA_FONT',
+          'OUTLINER_DATA_SURFACE', 'OUTLINER_DATA_SPEAKER',
+          'OUTLINER_DATA_LIGHTPROBE', 'OUTLINER_DATA_GP_LAYER',
+          'OUTLINER_DATA_GREASEPENCIL', 'GP_SELECT_POINTS',
+          'GP_SELECT_STROKES', 'GP_MULTIFRAME_EDITING',
+          'GP_ONLY_SELECTED', 'GP_SELECT_BETWEEN_STROKES',
+          'MODIFIER_OFF', 'MODIFIER_ON', 'ONIONSKIN_OFF',
+          'ONIONSKIN_ON', 'RESTRICT_VIEW_ON', 'RESTRICT_VIEW_OFF',
+          'RESTRICT_INSTANCED_ON', 'MESH_PLANE', 'MESH_CUBE',
+          'MESH_CIRCLE', 'MESH_UVSPHERE', 'MESH_ICOSPHERE', 'MESH_GRID',
+          'MESH_MONKEY', 'MESH_CYLINDER', 'MESH_TORUS', 'MESH_CONE',
+          'MESH_CAPSULE', 'EMPTY_SINGLE_ARROW', 'LIGHT_POINT',
+          'LIGHT_SUN', 'LIGHT_SPOT', 'LIGHT_HEMI', 'LIGHT_AREA', 'CUBE',
+          'SPHERE', 'CONE', 'META_PLANE', 'META_CUBE', 'META_BALL',
+          'META_ELLIPSOID', 'META_CAPSULE', 'SURFACE_NCURVE',
+          'SURFACE_NCIRCLE', 'SURFACE_NSURFACE', 'SURFACE_NCYLINDER',
+          'SURFACE_NSPHERE', 'SURFACE_NTORUS', 'EMPTY_AXIS', 'STROKE',
+          'EMPTY_ARROWS', 'CURVE_BEZCURVE', 'CURVE_BEZCIRCLE',
+          'CURVE_NCURVE', 'CURVE_NCIRCLE', 'CURVE_PATH',
+          'LIGHTPROBE_CUBEMAP', 'LIGHTPROBE_PLANAR', 'LIGHTPROBE_GRID',
+          'COLOR_RED', 'COLOR_GREEN', 'COLOR_BLUE', 'TRIA_RIGHT_BAR',
+          'TRIA_DOWN_BAR', 'TRIA_LEFT_BAR', 'TRIA_UP_BAR',
+          'FORCE_FORCE', 'FORCE_WIND', 'FORCE_VORTEX', 'FORCE_MAGNETIC',
+          'FORCE_HARMONIC', 'FORCE_CHARGE', 'FORCE_LENNARDJONES',
+          'FORCE_TEXTURE', 'FORCE_CURVE', 'FORCE_BOID',
+          'FORCE_TURBULENCE', 'FORCE_DRAG', 'FORCE_FLUIDFLOW',
+          'RIGID_BODY', 'RIGID_BODY_CONSTRAINT', 'IMAGE_PLANE',
+          'IMAGE_BACKGROUND', 'IMAGE_REFERENCE', 'NODE_INSERT_ON',
+          'NODE_INSERT_OFF', 'NODE_TOP', 'NODE_SIDE', 'NODE_CORNER',
+          'ANCHOR_TOP', 'ANCHOR_BOTTOM', 'ANCHOR_LEFT', 'ANCHOR_RIGHT',
+          'ANCHOR_CENTER', 'SELECT_SET', 'SELECT_EXTEND',
+          'SELECT_SUBTRACT', 'SELECT_INTERSECT', 'SELECT_DIFFERENCE',
+          'ALIGN_LEFT', 'ALIGN_CENTER', 'ALIGN_RIGHT', 'ALIGN_JUSTIFY',
+          'ALIGN_FLUSH', 'ALIGN_TOP', 'ALIGN_MIDDLE', 'ALIGN_BOTTOM',
+          'BOLD', 'ITALIC', 'UNDERLINE', 'SMALL_CAPS', 'CON_ACTION',
+          'MOD_LENGTH', 'MOD_DASH', 'MOD_LINEART', 'HOLDOUT_OFF',
+          'HOLDOUT_ON', 'INDIRECT_ONLY_OFF', 'INDIRECT_ONLY_ON',
+          'CON_CAMERASOLVER', 'CON_FOLLOWTRACK', 'CON_OBJECTSOLVER',
+          'CON_LOCLIKE', 'CON_ROTLIKE', 'CON_SIZELIKE', 'CON_TRANSLIKE',
+          'CON_DISTLIMIT', 'CON_LOCLIMIT', 'CON_ROTLIMIT',
+          'CON_SIZELIMIT', 'CON_SAMEVOL', 'CON_TRANSFORM',
+          'CON_TRANSFORM_CACHE', 'CON_CLAMPTO', 'CON_KINEMATIC',
+          'CON_LOCKTRACK', 'CON_SPLINEIK', 'CON_STRETCHTO',
+          'CON_TRACKTO', 'CON_ARMATURE', 'CON_CHILDOF', 'CON_FLOOR',
+          'CON_FOLLOWPATH', 'CON_PIVOT', 'CON_SHRINKWRAP',
+          'MODIFIER_DATA', 'MOD_WAVE', 'MOD_BUILD', 'MOD_DECIM',
+          'MOD_MIRROR', 'MOD_SOFT', 'MOD_SUBSURF', 'HOOK',
+          'MOD_PHYSICS', 'MOD_PARTICLES', 'MOD_BOOLEAN',
+          'MOD_EDGESPLIT', 'MOD_ARRAY', 'MOD_UVPROJECT', 'MOD_DISPLACE',
+          'MOD_CURVE', 'MOD_LATTICE', 'MOD_TINT', 'MOD_ARMATURE',
+          'MOD_SHRINKWRAP', 'MOD_CAST', 'MOD_MESHDEFORM', 'MOD_BEVEL',
+          'MOD_SMOOTH', 'MOD_SIMPLEDEFORM', 'MOD_MASK', 'MOD_CLOTH',
+          'MOD_EXPLODE', 'MOD_FLUIDSIM', 'MOD_MULTIRES', 'MOD_FLUID',
+          'MOD_SOLIDIFY', 'MOD_SCREW', 'MOD_VERTEX_WEIGHT',
+          'MOD_DYNAMICPAINT', 'MOD_REMESH', 'MOD_OCEAN', 'MOD_WARP',
+          'MOD_SKIN', 'MOD_TRIANGULATE', 'MOD_WIREFRAME',
+          'MOD_DATA_TRANSFER', 'MOD_NORMALEDIT',
+          'MOD_PARTICLE_INSTANCE', 'MOD_HUE_SATURATION', 'MOD_NOISE',
+          'MOD_OFFSET', 'MOD_SIMPLIFY', 'MOD_THICKNESS', 'MOD_INSTANCE',
+          'MOD_TIME', 'MOD_OPACITY', 'REC', 'PLAY', 'FF', 'REW',
+          'PAUSE', 'PREV_KEYFRAME', 'NEXT_KEYFRAME', 'PLAY_SOUND',
+          'PLAY_REVERSE', 'PREVIEW_RANGE', 'ACTION_TWEAK', 'PMARKER_ACT',
+          'PMARKER_SEL', 'PMARKER', 'MARKER_HLT', 'MARKER',
+          'KEYFRAME_HLT', 'KEYFRAME', 'KEYINGSET', 'KEY_DEHLT',
+          'KEY_HLT', 'MUTE_IPO_OFF', 'MUTE_IPO_ON', 'DRIVER',
+          'SOLO_OFF', 'SOLO_ON', 'FRAME_PREV', 'FRAME_NEXT',
+          'NLA_PUSHDOWN', 'IPO_CONSTANT', 'IPO_LINEAR', 'IPO_BEZIER',
+          'IPO_SINE', 'IPO_QUAD', 'IPO_CUBIC', 'IPO_QUART', 'IPO_QUINT',
+          'IPO_EXPO', 'IPO_CIRC', 'IPO_BOUNCE', 'IPO_ELASTIC',
+          'IPO_BACK', 'IPO_EASE_IN', 'IPO_EASE_OUT', 'IPO_EASE_IN_OUT',
+          'NORMALIZE_FCURVES', 'VERTEXSEL', 'EDGESEL', 'FACESEL',
+          'CURSOR', 'PIVOT_BOUNDBOX', 'PIVOT_CURSOR',
+          'PIVOT_INDIVIDUAL', 'PIVOT_MEDIAN', 'PIVOT_ACTIVE',
+          'CENTER_ONLY', 'ROOTCURVE', 'SMOOTHCURVE', 'SPHERECURVE',
+          'INVERSESQUARECURVE', 'SHARPCURVE', 'LINCURVE', 'NOCURVE',
+          'RNDCURVE', 'PROP_OFF', 'PROP_ON', 'PROP_CON',
+          'PROP_PROJECTED', 'PARTICLE_POINT', 'PARTICLE_TIP',
+          'PARTICLE_PATH', 'SNAP_FACE_NEAREST', 'SNAP_FACE_CENTER',
+          'SNAP_PERPENDICULAR', 'SNAP_MIDPOINT', 'SNAP_OFF', 'SNAP_ON',
+          'SNAP_NORMAL', 'SNAP_GRID', 'SNAP_VERTEX', 'SNAP_EDGE',
+          'SNAP_FACE', 'SNAP_VOLUME', 'SNAP_INCREMENT',
+          'STICKY_UVS_LOC', 'STICKY_UVS_DISABLE', 'STICKY_UVS_VERT',
+          'CLIPUV_DEHLT', 'CLIPUV_HLT', 'SNAP_PEEL_OBJECT', 'GRID',
+          'OBJECT_ORIGIN', 'ORIENTATION_GLOBAL', 'ORIENTATION_GIMBAL',
+          'ORIENTATION_LOCAL', 'ORIENTATION_NORMAL', 'ORIENTATION_VIEW',
+          'COPYDOWN', 'PASTEDOWN', 'PASTEFLIPUP', 'PASTEFLIPDOWN',
+          'VIS_SEL_11', 'VIS_SEL_10', 'VIS_SEL_01', 'VIS_SEL_00',
+          'AUTOMERGE_OFF', 'AUTOMERGE_ON', 'UV_VERTEXSEL', 'UV_EDGESEL',
+          'UV_FACESEL', 'UV_ISLANDSEL', 'UV_SYNC_SELECT',
+          'GP_CAPS_FLAT', 'GP_CAPS_ROUND', 'FIXED_SIZE',
+          'TRANSFORM_ORIGINS', 'GIZMO', 'ORIENTATION_CURSOR',
+          'NORMALS_VERTEX', 'NORMALS_FACE', 'NORMALS_VERTEX_FACE',
+          'SHADING_BBOX', 'SHADING_WIRE', 'SHADING_SOLID',
+          'SHADING_RENDERED', 'SHADING_TEXTURE', 'OVERLAY', 'XRAY',
+          'LOCKVIEW_OFF', 'LOCKVIEW_ON', 'AXIS_SIDE', 'AXIS_FRONT',
+          'AXIS_TOP', 'LAYER_USED', 'LAYER_ACTIVE',
+          'OUTLINER_OB_CURVES', 'OUTLINER_DATA_CURVES', 'CURVES_DATA',
+          'OUTLINER_OB_POINTCLOUD', 'OUTLINER_DATA_POINTCLOUD',
+          'POINTCLOUD_DATA', 'OUTLINER_OB_VOLUME',
+          'OUTLINER_DATA_VOLUME', 'VOLUME_DATA', 'CURRENT_FILE', 'HOME',
+          'DOCUMENTS', 'TEMP', 'SORTALPHA', 'SORTBYEXT', 'SORTTIME',
+          'SORTSIZE', 'SHORTDISPLAY', 'LONGDISPLAY', 'IMGDISPLAY',
+          'BOOKMARKS', 'FONTPREVIEW', 'FILTER', 'NEWFOLDER',
+          'FOLDER_REDIRECT', 'FILE_PARENT', 'FILE_REFRESH',
+          'FILE_FOLDER', 'FILE_BLANK', 'FILE_BLEND', 'FILE_IMAGE',
+          'FILE_MOVIE', 'FILE_SCRIPT', 'FILE_SOUND', 'FILE_FONT',
+          'FILE_TEXT', 'SORT_DESC', 'SORT_ASC', 'LINK_BLEND',
+          'APPEND_BLEND', 'IMPORT', 'EXPORT', 'LOOP_BACK',
+          'LOOP_FORWARDS', 'BACK', 'FORWARD', 'FILE_ARCHIVE',
+          'FILE_CACHE', 'FILE_VOLUME', 'FILE_3D', 'FILE_HIDDEN',
+          'FILE_BACKUP', 'DISK_DRIVE', 'MATPLANE', 'MATSPHERE',
+          'MATCUBE', 'MONKEY', 'CURVES', 'ALIASED', 'ANTIALIASED',
+          'MAT_SPHERE_SKY', 'MATSHADERBALL', 'MATCLOTH', 'MATFLUID',
+          'WORDWRAP_OFF', 'WORDWRAP_ON', 'SYNTAX_OFF', 'SYNTAX_ON',
+          'LINENUMBERS_OFF', 'LINENUMBERS_ON', 'SCRIPTPLUGINS', 'DISC',
+          'DESKTOP', 'EXTERNAL_DRIVE', 'NETWORK_DRIVE', 'SEQ_SEQUENCER',
+          'SEQ_PREVIEW', 'SEQ_LUMA_WAVEFORM', 'SEQ_CHROMA_SCOPE',
+          'SEQ_HISTOGRAM', 'SEQ_SPLITVIEW', 'SEQ_STRIP_META',
+          'SEQ_STRIP_DUPLICATE', 'IMAGE_RGB', 'IMAGE_RGB_ALPHA',
+          'IMAGE_ALPHA', 'IMAGE_ZDEPTH', 'HANDLE_AUTOCLAMPED',
+          'HANDLE_AUTO', 'HANDLE_ALIGNED', 'HANDLE_VECTOR',
+          'HANDLE_FREE', 'VIEW_PERSPECTIVE', 'VIEW_ORTHO',
+          'VIEW_CAMERA', 'VIEW_PAN', 'VIEW_ZOOM', 'BRUSH_BLOB',
+          'BRUSH_BLUR', 'BRUSH_CLAY', 'BRUSH_CLAY_STRIPS',
+          'BRUSH_CLONE', 'BRUSH_CREASE', 'BRUSH_FILL', 'BRUSH_FLATTEN',
+          'BRUSH_GRAB', 'BRUSH_INFLATE', 'BRUSH_LAYER', 'BRUSH_MASK',
+          'BRUSH_MIX', 'BRUSH_NUDGE', 'BRUSH_PAINT_SELECT',
+          'BRUSH_PINCH', 'BRUSH_SCRAPE', 'BRUSH_SCULPT_DRAW',
+          'BRUSH_SMEAR', 'BRUSH_SMOOTH', 'BRUSH_SNAKE_HOOK',
+          'BRUSH_SOFTEN', 'BRUSH_TEXDRAW', 'BRUSH_TEXFILL',
+          'BRUSH_TEXMASK', 'BRUSH_THUMB', 'BRUSH_ROTATE',
+          'GPBRUSH_SMOOTH', 'GPBRUSH_THICKNESS', 'GPBRUSH_STRENGTH',
+          'GPBRUSH_GRAB', 'GPBRUSH_PUSH', 'GPBRUSH_TWIST',
+          'GPBRUSH_PINCH', 'GPBRUSH_RANDOMIZE', 'GPBRUSH_CLONE',
+          'GPBRUSH_WEIGHT', 'GPBRUSH_PENCIL', 'GPBRUSH_PEN',
+          'GPBRUSH_INK', 'GPBRUSH_INKNOISE', 'GPBRUSH_BLOCK',
+          'GPBRUSH_MARKER', 'GPBRUSH_FILL', 'GPBRUSH_AIRBRUSH',
+          'GPBRUSH_CHISEL', 'GPBRUSH_ERASE_SOFT', 'GPBRUSH_ERASE_HARD',
+          'GPBRUSH_ERASE_STROKE', 'BRUSH_CURVES_ADD',
+          'BRUSH_CURVES_COMB', 'BRUSH_CURVES_CUT',
+          'BRUSH_CURVES_DELETE', 'BRUSH_CURVES_DENSITY',
+          'BRUSH_CURVES_GROW_SHRINK', 'BRUSH_CURVES_PINCH',
+          'BRUSH_CURVES_PUFF', 'BRUSH_CURVES_SLIDE',
+          'BRUSH_CURVES_SMOOTH', 'BRUSH_CURVES_SNAKE_HOOK',
+          'KEYTYPE_KEYFRAME_VEC', 'KEYTYPE_BREAKDOWN_VEC',
+          'KEYTYPE_EXTREME_VEC', 'KEYTYPE_JITTER_VEC',
+          'KEYTYPE_MOVING_HOLD_VEC', 'HANDLETYPE_FREE_VEC',
+          'HANDLETYPE_ALIGNED_VEC', 'HANDLETYPE_VECTOR_VEC',
+          'HANDLETYPE_AUTO_VEC', 'HANDLETYPE_AUTO_CLAMP_VEC',
+          'COLORSET_01_VEC', 'COLORSET_02_VEC', 'COLORSET_03_VEC',
+          'COLORSET_04_VEC', 'COLORSET_05_VEC', 'COLORSET_06_VEC',
+          'COLORSET_07_VEC', 'COLORSET_08_VEC', 'COLORSET_09_VEC',
+          'COLORSET_10_VEC', 'COLORSET_11_VEC', 'COLORSET_12_VEC',
+          'COLORSET_13_VEC', 'COLORSET_14_VEC', 'COLORSET_15_VEC',
+          'COLORSET_16_VEC', 'COLORSET_17_VEC', 'COLORSET_18_VEC',
+          'COLORSET_19_VEC', 'COLORSET_20_VEC', 'COLLECTION_COLOR_01',
+          'COLLECTION_COLOR_02', 'COLLECTION_COLOR_03',
+          'COLLECTION_COLOR_04', 'COLLECTION_COLOR_05',
+          'COLLECTION_COLOR_06', 'COLLECTION_COLOR_07',
+          'COLLECTION_COLOR_08', 'SEQUENCE_COLOR_01',
+          'SEQUENCE_COLOR_02', 'SEQUENCE_COLOR_03',
+          'SEQUENCE_COLOR_04', 'SEQUENCE_COLOR_05',
+          'SEQUENCE_COLOR_06', 'SEQUENCE_COLOR_07',
+          'SEQUENCE_COLOR_08', 'SEQUENCE_COLOR_09',
+          'LIBRARY_DATA_INDIRECT', 'LIBRARY_DATA_OVERRIDE_NONEDITABLE',
+          'EVENT_A', 'EVENT_B', 'EVENT_C', 'EVENT_D', 'EVENT_E',
+          'EVENT_F', 'EVENT_G', 'EVENT_H', 'EVENT_I', 'EVENT_J',
+          'EVENT_K', 'EVENT_L', 'EVENT_M', 'EVENT_N', 'EVENT_O',
+          'EVENT_P', 'EVENT_Q', 'EVENT_R', 'EVENT_S', 'EVENT_T',
+          'EVENT_U', 'EVENT_V', 'EVENT_W', 'EVENT_X', 'EVENT_Y',
+          'EVENT_Z', 'EVENT_SHIFT', 'EVENT_CTRL', 'EVENT_ALT',
+          'EVENT_OS', 'EVENT_F1', 'EVENT_F2', 'EVENT_F3', 'EVENT_F4',
+          'EVENT_F5', 'EVENT_F6', 'EVENT_F7', 'EVENT_F8', 'EVENT_F9',
+          'EVENT_F10', 'EVENT_F11', 'EVENT_F12', 'EVENT_ESC',
+          'EVENT_TAB', 'EVENT_PAGEUP', 'EVENT_PAGEDOWN', 'EVENT_RETURN',
+          'EVENT_SPACEKEY')
+
+
+def default_traverse(self, socket):
+        if (socket == self.outputs["Deformer"]):
+            return self.inputs["Input Relationship"]
+        if (socket == self.inputs["Input Relationship"]):
+            return self.outputs["Deformer"]
+        return None
+
+class DeformerArmatureNode(Node, DeformerNode):
+    '''A node representing an Armature Deformer'''
+    bl_idname = 'DeformerArmature'
+    bl_label = "Armature Deform"
+    bl_icon = 'MOD_ARMATURE'
+
+    def init(self, context):
+        # self.inputs.new ("RelationshipSocket", "Input Relationship")
+        self.inputs.new('xFormSocket', "Target")
+        self.inputs.new('StringSocket', "Vertex Group")
+        
+        self.inputs.new('BooleanSocket', "Preserve Volume")
+        # TODO: make the above controlled by a vertex group instead.
+        self.inputs.new('BooleanSocket', "Use Multi Modifier")# might just set this auto
+        self.inputs.new('BooleanSocket', "Use Envelopes")
+        self.inputs.new('BooleanSocket', "Use Vertex Groups")
+        
+        self.inputs.new("EnumSkinning", "Skinning Method")
+        
+        self.outputs.new('DeformerSocket', "Deformer")
+
+    def traverse(self, socket):
+        return default_traverse(self,socket)

+ 126 - 0
drivers.py

@@ -0,0 +1,126 @@
+from mantis.utilities import (prRed, prGreen, prPurple, prWhite,
+                              prOrange,
+                              wrapRed, wrapGreen, wrapPurple, wrapWhite,
+                              wrapOrange,)
+
+
+
+##########################################################################
+#  Drivers!
+##########################################################################
+
+
+# SO: the idea is that the driver's input is a Python dictionary
+#     with all of the requisite information to build the driver
+
+# I need a generic function to create the driver
+
+# EXAMPLE INPUT:
+
+
+# example   =     {"owner":None,
+                    # "prop":None,
+                    # "ind":-1,
+                    # "type":"AVERAGE",
+                    # "vars":[{"id":None,
+                            # "name":"a",
+                            # "type":"TRANSFORMS",
+                            # "space":'LOCAL_SPACE',
+                            # "channel":'LOC_Z',},],
+                    # "keys":[{"co":(0,0.5),
+                            # "interpolation": "BEZIER",
+                            # "handle_left_type":  "AUTO_CLAMPED", #if AUTO then handle_left will be ignored
+                            # "handle_right_type": "AUTO_CLAMPED",
+                            # "type":"KEYFRAME",}, #display type
+                            # {"co":(-1,0),
+                            # "interpolation": "BEZIER",
+                            # "handle_left_type":  "AUTO_CLAMPED",
+                            # "handle_right_type": "AUTO_CLAMPED",
+                            # "type":"KEYFRAME",},
+                            # {"co":(1,1),
+                            # "interpolation": "BEZIER",
+                            # "handle_left_type":  "ALIGNED",
+                            # "handle_right_type": "ALIGNED",
+                            # "handle_left":  (-0.4,0), #these are treated as offsets
+                            # "handle_right": ( 0.04,0), #only valid if interp. == BEZIER
+                            # "type":"KEYFRAME",},],
+                    # }
+
+#Explain this ^ ?
+#
+
+class MantisDriver(dict):
+    pass
+
+def CreateDrivers(drivers):
+    def brackets(s):
+        return "[\""+s+"\"]"
+    from bpy.types import Object
+    for driver in drivers:
+        # print (driver)
+        if (isinstance(driver["owner"], Object)):
+            ob = driver["owner"]
+        else: # Pose Bone:
+            ob = driver["owner"].id_data
+        fc = ob.driver_add(driver["owner"].path_from_id(driver["prop"]), driver["ind"])
+        drv = fc.driver
+        try: # annoyingly, this initializes with a modifier
+            fc.modifiers.remove(fc.modifiers[0])
+        except IndexError: #haven't seen this happen, but should handle
+            pass # perhaps this has been fixed for 3.0?
+        drv.type = driver["type"]
+        if (expr := driver.get("expression")) and isinstance(expr, str):
+            drv.expression = expr
+        
+        # logic for handling type can go here
+        
+        # start by clearing
+        while (len(drv.variables) > 0):
+            v = drv.variables[0]
+            dVar = drv.variables.remove(v)
+            
+        for v in driver["vars"]:
+            if (isinstance(v["owner"], Object)):
+                vob = v["owner"]
+            else:
+                vob = v["owner"].id_data
+                bone = v["owner"].name
+            
+            dVar = drv.variables.new()
+            
+            
+            dVar.name = v["name"]
+            dVar.type = v["type"]
+            #for now, assume this is always true:
+            #dVar.targets[0].id_type = "OBJECT"
+            #it's possible to use other datablocks, but this is not commonly done
+            #actually, it looks like Blender figures this out for me.
+            
+            dVar.targets[0].id = vob
+            dVar.targets[0].bone_target = bone
+            
+            if (dVar.type == "TRANSFORMS"):
+                dVar.targets[0].transform_space = v["space"]
+                dVar.targets[0].transform_type = v["channel"]
+            if (dVar.type == 'SINGLE_PROP'):
+                stub = "pose.bones[\""+v["owner"].name+"\"]"
+                if (hasattr( v["owner"], v["prop"] )):
+                    dVar.targets[0].data_path = stub + "."+ (v["prop"])
+                else:
+                    dVar.targets[0].data_path = stub + brackets(v["prop"])
+        # setup keyframe points
+        kp = fc.keyframe_points
+        for key in driver["keys"]:
+            k = kp.insert(frame=key["co"][0], value = key["co"][1],)
+            
+            k.interpolation     = key["interpolation"]
+            if (key["interpolation"] == 'BEZIER'):
+                k.handle_left_type  = key["handle_left_type" ]
+                k.handle_right_type = key["handle_right_type"]
+                if (k.handle_left_type in ("ALIGNED", "VECTOR", "FREE")):
+                    k.handle_left       = (k.co[0] + key["handle_left"][0], k.co[1] + key["handle_left"][1])
+                if (k.handle_right_type in ("ALIGNED", "VECTOR", "FREE")):
+                    k.handle_right      = (k.co[0] + key["handle_right"][0], k.co[1] + key["handle_right"][1])
+            k.type = key["type"]
+      
+      

+ 107 - 0
f_nodegraph.py

@@ -0,0 +1,107 @@
+#Node Graph Functions
+
+        
+        
+def SeekNodePathUntil(node, input_name, nodeType, direction = 'BACK'):
+    from .node_container_common import trace_single_line, trace_single_line_up
+    if direction == 'BACK':
+        return trace_single_line(node, input_name)
+    else: # 'FORWARD'
+        return trace_single_line_up(node, input_name)
+    
+
+def get_node_container(node, context):
+    from .utilities import parse_node_tree, print_lines
+    base_tree = context.space_data.path[0].node_tree
+    from .readtree import get_tree_data
+    parsed_tree = parse_node_tree(base_tree)
+    nodes = get_tree_data(parsed_tree, base_tree)
+    node_container = None
+    if (node.id_data != context.space_data.path[-1].node_tree):
+        return None, None
+    if (node.id_data == base_tree):
+        try:
+            #other_node = node.inputs['Parent'].links[0].from_node
+            node_container = nodes.get( ('NONE', node.name) )
+        except IndexError: # node just isn't connected'
+            nodes = None
+        return node_container, nodes
+    else: # find it in Node-Groups
+          # I am checking the active node, which should always
+          #  be the path of Group Nodes.
+          # if not, then the user is doing something sp0oky
+        for node_container in nodes.values():
+            if len(node_container.signature) != len(context.space_data.path)+1:
+                continue
+            tree = base_tree; found = False
+            for name in node_container.signature[0:]:
+                g_node = tree.nodes.get(name)
+                if not (g_node == tree.nodes.active): continue 
+                if (hasattr(g_node, 'node_tree')):
+                    tree = g_node.node_tree
+                elif name == node.name: found = True; break
+            else:
+                found = False
+                continue
+            if found == True:
+                return node_container, nodes
+        else:
+            return None, None
+    return None, None
+                
+def GetUpstreamXFormNodes(node_container, context):
+    if (node_container):
+        input_name=None
+        if node_container.node_type == 'LINK':
+            input_name = 'Input Relationship'
+            if node_container.__class__.__name__ == 'LinkInherit':
+                input_name = 'Parent'
+        elif node_container.node_type == 'XFORM':
+            input_name = 'Relationship'
+        xF = SeekNodePathUntil(node_container, input_name, ['xFormArmature', 'xFormBone', 'xFormRoot'])
+        return xF
+        
+    else:
+        return None
+        
+def GetDownstreamXFormNodes(node_container, context):
+    if (node_container):
+        output_name=None
+        if node_container.node_type == 'LINK':
+            output_name = 'Output Relationship'
+            if node_container.__class__.__name__ == 'LinkInherit':
+                output_name = 'Inheritance'
+        elif node_container.node_type == 'XFORM':
+            output_name = 'xForm Out'
+        xF = SeekNodePathUntil(node_container, output_name, ['xFormArmature', 'xFormBone', 'xFormRoot'], direction = 'FORWARD')
+        return xF
+    else:
+        return None
+    
+        
+# def get_parent(node_container):
+    # node_line, socket = trace_single_line(node_container, "Relationship")
+    # parent_nc = None
+    # for i in range(len(node_line)):
+        # print (node_line[i])
+        # # check each of the possible parent types.
+        # if ( (node_line[ i ].__class__.__name__ == 'LinkInherit') ):
+            # try: # it's the next one
+                # return node_line[ i + 1 ]
+            # except IndexError: # if there is no next one...
+                # return None # then there's no parent!
+    # return None
+    # # TO DO!
+    # #
+    # # 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!
+    
+    # #my_sig = get_node_signature(node, tree)
+    
+    
+    
+def FindIKNode():
+    pass
+ 

+ 1 - 0
grandalf/__init__.py

@@ -0,0 +1 @@
+__all__ = ["graphs", "layouts", "routing", "utils"]

+ 879 - 0
grandalf/graphs.py

@@ -0,0 +1,879 @@
+# -*- coding: utf-8 -*-
+
+"""
+.. _graphs:
+
+graphs.py
+=========
+This module implements essential graph classes for representing
+vertices (nodes), edges (links), and graphs.
+
+"""
+
+# This code is part of Grandalf
+# Copyright (C) 2008-2011 Axel Tillequin (bdcht3@gmail.com)
+# published under GPLv2 license or EPLv1 license
+
+from mantis.grandalf.utils import Poset
+
+# ------------------------------------------------------------------------------
+
+
+class vertex_core(object):
+    """ The Vertex essentials attributes and methods.
+
+        Attributes:
+            e (list[Edge]): list of edges associated with this vertex.
+
+        Methods:
+            deg() : degree of the vertex (number of edges).
+            e_in() : list of edges directed toward this vertex.
+            e_out(): list of edges directed outward this vertex.
+            e_dir(int): either e_in, e_out or all edges depending on
+               provided direction parameter (>0 means outward).
+            N(f_io=0): list of neighbor vertices in all directions (default)
+               or in filtered f_io direction (>0 means outward).
+            e_to(v): returns the Edge from this vertex directed toward vertex v.
+            e_from(v): returns the Edge from vertex v directed toward this vertex.
+            e_with(v): return the Edge with both this vertex and vertex v
+            detach(): removes this vertex from all its edges and returns this list
+               of edges.
+    """
+
+    def __init__(self):
+        # will hold list of edges for this vertex (adjacency list)
+        self.e = []
+
+    def deg(self):
+        return len(self.e)
+
+    def e_in(self):
+        return list(filter((lambda e: e.v[1] == self), self.e))
+
+    def e_out(self):
+        return list(filter((lambda e: e.v[0] == self), self.e))
+
+    def e_dir(self, dir):
+        if dir > 0:
+            return self.e_out()
+        if dir < 0:
+            return self.e_in()
+        return self.e
+
+    def N(self, f_io=0):
+        N = []
+        if f_io <= 0:
+            N += [e.v[0] for e in self.e_in()]
+        if f_io >= 0:
+            N += [e.v[1] for e in self.e_out()]
+        return N
+
+    def e_to(self, y):
+        for e in self.e_out():
+            if e.v[1] == y:
+                return e
+        return None
+
+    def e_from(self, x):
+        for e in self.e_in():
+            if e.v[0] == x:
+                return e
+        return None
+
+    def e_with(self, v):
+        for e in self.e:
+            if v in e.v:
+                return e
+        return None
+
+    def detach(self):
+        E = self.e[:]
+        for e in E:
+            e.detach()
+        assert self.deg() == 0
+        return E
+
+
+# ------------------------------------------------------------------------------
+
+
+class edge_core(object):
+    """The Edge essentials attributes.
+
+       Attributes:
+          v (list[Vertex]): list of vertices associated with this edge.
+          deg (int): degree of the edge (number of unique vertices).
+    """
+
+    def __init__(self, x, y):
+        self.deg = 0 if x == y else 1
+        self.v = (x, y)
+
+
+# ------------------------------------------------------------------------------
+
+
+class Vertex(vertex_core):
+    """Vertex class enhancing a vertex_core with graph-related features.
+       
+       Attributes:
+          c (graph_core): the component of connected vertices that contains this vertex.
+             By default a vertex belongs no component but when it is added in a
+             graph, c points to the connected component in this graph.
+          data (object) : an object associated with the vertex.
+    """
+
+    def __init__(self, data=None):
+        super().__init__()
+        # by default, a new vertex belongs to its own component
+        # but when the vertex is added to a graph, c points to the
+        # connected component where it belongs.
+        self.c = None
+        self.data = data
+        self.__index = None
+
+    @property
+    def index(self):
+        if self.__index:
+            return self.__index
+        elif isinstance(self.c, graph_core):
+            self.__index = self.c.sV.index(self)
+            return self.__index
+        else:
+            return None
+
+    def __lt__(self, v):
+        return 0
+
+    def __gt__(self, v):
+        return 0
+
+    def __le__(self, v):
+        return 0
+
+    def __ge__(self, v):
+        return 0
+
+    def __getstate__(self):
+        return (self.index, self.data)
+
+    def __setstate__(self, state):
+        self.__index, self.data = state
+        self.c = None
+        self.e = []
+
+
+# ------------------------------------------------------------------------------
+
+
+class Edge(edge_core):
+    """Edge class enhancing edge_core with attributes and methods related to the graph.
+
+       Attributes:
+         w (int): a weight associated with the edge (default 1) used by Dijkstra to
+           find min-flow paths.
+         data (object): an object associated with the edge.
+         feedback (bool): indicates if the edge has been marked as a *feeback* edge
+           by the Tarjan algorithm which means that it is part of a cycle and that
+           inverting this edge would remove this cycle.
+
+       Methods:
+         attach(): add this edge in its vertices edge lists.
+         detach(): remove this edge from its vertices edge lists.
+    """
+
+    def __init__(self, x, y, w=1, data=None, connect=False):
+        super().__init__(x, y)
+        # w is an optional weight associated with the edge.
+        self.w = w
+        self.data = data
+        self.feedback = False
+        if connect and (x.c is None or y.c is None):
+            c = x.c or y.c
+            c.add_edge(self)
+
+    def attach(self):
+        if not self in self.v[0].e:
+            self.v[0].e.append(self)
+        if not self in self.v[1].e:
+            self.v[1].e.append(self)
+
+    def detach(self):
+        if self.deg == 1:
+            assert self in self.v[0].e
+            assert self in self.v[1].e
+            self.v[0].e.remove(self)
+            self.v[1].e.remove(self)
+        else:
+            if self in self.v[0].e:
+                self.v[0].e.remove(self)
+            assert self not in self.v[0].e
+        return [self]
+
+    def __lt__(self, v):
+        return 0
+
+    def __gt__(self, v):
+        return 0
+
+    def __le__(self, v):
+        return 0
+
+    def __ge__(self, v):
+        return 0
+
+    def __getstate__(self):
+        xi, yi = (self.v[0].index, self.v[1].index)
+        return (xi, yi, self.w, self.data, self.feedback)
+
+    def __setstate__(self, state):
+        xi, yi, self.w, self.data, self.feedback = state
+        self._v = [xi, yi]
+        self.deg = 0 if xi == yi else 1
+
+
+# ------------------------------------------------------------------------------
+
+
+class graph_core(object):
+    """A connected graph of Vertex/Edge objects. A graph_core is a *component*
+       of a Graph that contains a connected set of Vertex and Edges.
+
+       Attributes:
+         sV (poset[Vertex]): the partially ordered set of vertices of the graph.
+         sE (poset[Edge]): the partially ordered set of edges of the graph.
+         degenerated_edges (set[Edge]): the set of *degenerated* edges (of degree 0).
+         directed (bool): indicates if the graph is considered *oriented* or not.
+
+       Methods:
+         V(cond=None): generates an iterator over vertices, with optional filter
+         E(cond=None): generates an iterator over edges, with optional filter
+         M(cond=None): returns the associativity matrix of the graph component
+         order(): the order of the graph (number of vertices)
+         norm(): the norm of the graph (number of edges)
+         deg_min(): the minimum degree of vertices
+         deg_max(): the maximum degree of vertices
+         deg_avg(): the average degree of vertices
+         eps(): the graph epsilon value (norm/order), average number of edges per vertex. 
+         path(x,y,f_io=0,hook=None): shortest path between vertices x and y by breadth-first descent,
+           contrained by f_io direction if provided. The path is returned as a list of Vertex objects.
+           If a *hook* function is provided, it is called at every vertex added to the path, passing
+           the vertex object as argument.
+         roots(): returns the list of *roots* (vertices with no inward edges).
+         leaves(): returns the list of *leaves* (vertices with no outward edges).
+         add_single_vertex(v): allow a graph_core to hold a single vertex.
+         add_edge(e): add edge e. At least one of its vertex must belong to the graph,
+           the other being added automatically.
+         remove_edge(e): remove Edge e, asserting that the resulting graph is still connex.
+         remove_vertex(x): remove Vertex x and all associated edges.
+         dijkstra(x,f_io=0,hook=None): shortest weighted-edges paths between x and all other vertices
+           by dijkstra's algorithm with heap used as priority queue.
+         get_scs_with_feedback(): returns the set of strongly connected components
+           ("scs") by using Tarjan algorithm.
+           These are maximal sets of vertices such that there is a path from each
+           vertex to every other vertex.
+           The algorithm performs a DFS from the provided list of root vertices.
+           A cycle is of course a strongly connected component,
+           but a strongly connected component can include several cycles.
+           The Feedback Acyclic Set of edge to be removed/reversed is provided by
+           marking the edges with a "feedback" flag.
+           Complexity is O(V+E).
+         partition(): returns a *partition* of the connected graph as a list of lists.
+         N(v): returns neighbours of a vertex v.
+    """
+
+    def __init__(self, V=None, E=None, directed=True):
+        if V is None:
+            V = []
+        if E is None:
+            E = []
+        self.directed = directed
+        self.sV = Poset(V)
+        self.sE = Poset([])
+
+        self.degenerated_edges = set()
+
+        if len(self.sV) == 1:
+            v = self.sV[0]
+            v.c = self
+            for e in v.e:
+                e.detach()
+            return
+
+        for e in E:
+            x = self.sV.get(e.v[0])
+            y = self.sV.get(e.v[1])
+            if x is None or y is None:
+                raise ValueError("unknown Vertex (%s or %s)" % e.v)
+            e.v = (x, y)
+            if e.deg == 0:
+                self.degenerated_edges.add(e)
+            e = self.sE.add(e)
+            e.attach()
+            if x.c is None:
+                x.c = Poset([x])
+            if y.c is None:
+                y.c = Poset([y])
+            if id(x.c) != id(y.c):
+                x, y = (x, y) if len(x.c) > len(y.c) else (y, x)
+                x.c.update(y.c)
+                for v in y.c:
+                    v.c = x.c
+            s = x.c
+        # check if graph is connected:
+        for v in self.V():
+            if v.c is None or (v.c != s):
+                raise ValueError("unconnected Vertex %s" % v.data)
+            else:
+                v.c = self
+
+    def roots(self):
+        return list(filter(lambda v: len(v.e_in()) == 0, self.sV))
+
+    def leaves(self):
+        return list(filter(lambda v: len(v.e_out()) == 0, self.sV))
+
+    def add_single_vertex(self, v):
+        if len(self.sE) == 0 and len(self.sV) == 0:
+            v = self.sV.add(v)
+            v.c = self
+            return v
+        return None
+
+    def add_edge(self, e):
+        if e in self.sE:
+            return self.sE.get(e)
+        x = e.v[0]
+        y = e.v[1]
+        if not ((x in self.sV) or (y in self.sV)):
+            raise ValueError("unconnected edge")
+        x = self.sV.add(x)
+        y = self.sV.add(y)
+        e.v = (x, y)
+        e.attach()
+        e = self.sE.add(e)
+        x.c = self
+        y.c = self
+        if e.deg == 0:
+            self.degenerated_edges.add(e)
+        return e
+
+    def remove_edge(self, e):
+        if not e in self.sE:
+            return
+        e.detach()
+        # check if still connected (path is not oriented here):
+        if e.deg == 1 and not self.path(e.v[0], e.v[1]):
+            # return to inital state by reconnecting everything:
+            e.attach()
+            # exit with exception!
+            raise ValueError(e)
+        else:
+            e = self.sE.remove(e)
+            if e in self.degenerated_edges:
+                self.degenerated_edges.remove(e)
+            return e
+
+    def remove_vertex(self, x):
+        if x not in self.sV:
+            return
+        V = x.N()  # get all neighbor vertices to check paths
+        E = x.detach()  # remove the edges from x and neighbors list
+        # now we need to check if all neighbors are still connected,
+        # and it is sufficient to check if one of them is connected to
+        # all others:
+        v0 = V.pop(0)
+        for v in V:
+            if not self.path(v0, v):
+                # repair everything and raise exception if not connected:
+                for e in E:
+                    e.attach()
+                raise ValueError(x)
+        # remove edges and vertex from internal sets:
+        for e in E:
+            self.sE.remove(e)
+        x = self.sV.remove(x)
+        x.c = None
+        return x
+
+    def V(self, cond=None):
+        V = self.sV
+        if cond is None:
+            cond = lambda x: True
+        for v in V:
+            if cond(v):
+                yield v
+
+    def E(self, cond=None):
+        E = self.sE
+        if cond is None:
+            cond = lambda x: True
+        for e in E:
+            if cond(e):
+                yield e
+
+    def M(self, cond=None):
+        from array import array
+
+        mat = []
+        for v in self.V(cond):
+            vec = array("b", [0] * self.order())
+            mat.append(vec)
+            for e in v.e_in():
+                v0 = e.v[0]
+                if v0.index == v.index:
+                    continue
+                vec[v0.index] = -e.w
+            for e in v.e_out():
+                v1 = e.v[1]
+                vec[v1.index] = e.w
+        return mat
+
+    def order(self):
+        return len(self.sV)
+
+    def norm(self):
+        return len(self.sE)
+
+    def deg_min(self):
+        return min([v.deg() for v in self.sV])
+
+    def deg_max(self):
+        return max([v.deg() for v in self.sV])
+
+    def deg_avg(self):
+        return sum([v.deg() for v in self.sV]) / float(self.order())
+
+    def eps(self):
+        return float(self.norm()) / self.order()
+
+    def path(self, x, y, f_io=0, hook=None):
+        assert x in self.sV
+        assert y in self.sV
+        x = self.sV.get(x)
+        y = self.sV.get(y)
+        if x == y:
+            return []
+        if f_io != 0:
+            assert self.directed == True
+        # path:
+        p = None
+        if hook is None:
+            hook = lambda x: False
+        # apply hook:
+        hook(x)
+        # visisted:
+        v = {x: None}
+        # queue:
+        q = [x]
+        while (not p) and len(q) > 0:
+            c = q.pop(0)
+            for n in c.N(f_io):
+                if not n in v:
+                    hook(n)
+                    v[n] = c
+                    if n == y:
+                        p = [n]
+                    q.append(n)
+                if p:
+                    break
+        # now we fill the path p backward from y to x:
+        while p and p[0] != x:
+            p.insert(0, v[p[0]])
+        return p
+
+    def dijkstra(self, x, f_io=0, hook=None):
+        from collections import defaultdict
+        from heapq import heappop, heappush
+
+        if x not in self.sV:
+            return None
+        if f_io != 0:
+            assert self.directed == True
+        # initiate with path to itself...
+        v = self.sV.get(x)
+        # D is the returned vector of distances:
+        D = defaultdict(lambda: None)
+        D[v] = 0.0
+        L = [(D[v], v)]
+        while len(L) > 0:
+            l, u = heappop(L)
+            for e in u.e_dir(f_io):
+                v = e.v[0] if (u is e.v[1]) else e.v[1]
+                Dv = l + e.w
+                if D[v] != None:
+                    # check if heap/D needs updating:
+                    # ignore if a shorter path was found already...
+                    if Dv < D[v]:
+                        for i, t in enumerate(L):
+                            if t[1] is v:
+                                L.pop(i)
+                                break
+                        D[v] = Dv
+                        heappush(L, (Dv, v))
+                else:
+                    D[v] = Dv
+                    heappush(L, (Dv, v))
+        return D
+
+    def get_scs_with_feedback(self, roots=None):
+        from sys import getrecursionlimit, setrecursionlimit
+
+        limit = getrecursionlimit()
+        N = self.norm() + 10
+        if N > limit:
+            setrecursionlimit(N)
+
+        def _visit(v, L):
+            v.ind = v.ncur
+            v.lowlink = v.ncur
+            Vertex.ncur += 1
+            self.tstack.append(v)
+            v.mark = True
+            for e in v.e_out():
+                w = e.v[1]
+                if w.ind == 0:
+                    _visit(w, L)
+                    v.lowlink = min(v.lowlink, w.lowlink)
+                elif w.mark:
+                    e.feedback = True
+                if w in self.tstack:
+                    v.lowlink = min(v.lowlink, w.ind)
+            if v.lowlink == v.ind:
+                l = [self.tstack.pop()]
+                while l[0] != v:
+                    l.insert(0, self.tstack.pop())
+                # print "unstacked %s"%('-'.join([x.data[1:13] for x in l]))
+                L.append(l)
+            v.mark = False
+
+        if roots is None:
+            roots = self.roots()
+        self.tstack = []
+        scs = []
+        Vertex.ncur = 1
+        for v in self.sV:
+            v.ind = 0
+        # start exploring tree from roots:
+        for v in roots:
+            v = self.sV.get(v)
+            if v.ind == 0:
+                _visit(v, scs)
+        # now possibly unvisited vertices:
+        for v in self.sV:
+            if v.ind == 0:
+                _visit(v, scs)
+        # clean up Tarjan-specific data:
+        for v in self.sV:
+            del v.ind
+            del v.lowlink
+            del v.mark
+        del Vertex.ncur
+        del self.tstack
+        setrecursionlimit(limit)
+        return scs
+
+    def partition(self):
+        V = self.sV.copy()
+        R = self.roots()
+        for r in R:
+            V.remove(r)
+        parts = []
+        while len(R) > 0:
+            v = R.pop(0)
+            p = Poset([v])
+            l = v.N(+1)
+            while len(l) > 0:
+                x = l.pop(0)
+                if x in p:
+                    continue
+                if all([(y in p) for y in x.N(-1)]):
+                    p.add(x)
+                    if x in R:
+                        R.remove(x)
+                    else:
+                        V.remove(x)
+                    l.extend(x.N(+1))
+                else:
+                    if x in V:
+                        V.remove(x)
+                        R.append(x)
+            parts.append(list(p))
+        return parts
+
+    def N(self, v, f_io=0):
+        return v.N(f_io)
+
+    # general graph properties:
+    # -------------------------
+
+    # returns True iff
+    #  - o is a subgraph of self, or
+    #  - o is a vertex in self, or
+    #  - o is an edge in self
+    def __contains__(self, o):
+        try:
+            return o.sV.issubset(self.sV) and o.sE.issubset(self.sE)
+        except AttributeError:
+            return (o in self.sV) or (o in self.sE)
+
+    # merge graph_core G into self
+    def union_update(self, G):
+        for v in G.sV:
+            v.c = self
+        self.sV.update(G.sV)
+        self.sE.update(G.sE)
+
+    # derivated graphs:
+    # -----------------
+
+    # returns subgraph spanned by vertices V
+    def spans(self, V):
+        raise NotImplementedError
+
+    # returns join of G (if disjoint)
+    def __mul__(self, G):
+        raise NotImplementedError
+
+    # returns complement of a graph G
+    def complement(self, G):
+        raise NotImplementedError
+
+    # contraction G\e
+    def contract(self, e):
+        raise NotImplementedError
+
+    def __getstate__(self):
+        V = [v for v in self.sV]
+        E = [e for e in self.sE]
+        return (V, E, self.directed)
+
+    def __setstate__(self, state):
+        V, E, directed = state
+        for e in E:
+            e.v = [V[x] for x in e._v]
+            del e._v
+        graph_core.__init__(self, V, E, directed)
+
+
+# ------------------------------------------------------------------------------
+
+
+class Graph(object):
+    """Disjoint-set Graph.
+       The graph is stored in disjoint-sets holding each connex component
+       in self.C as a list of graph_core objects.
+
+       Attributes:
+          C (list[graph_core]): list of graph_core components.
+
+       Methods:
+          add_vertex(v): add vertex v into the Graph as a new component
+          add_edge(e): add edge e and its vertices into the Graph possibly merging the
+            associated graph_core components
+          get_vertices_count(): see order()
+          V(): see graph_core
+          E(): see graph_core
+          remove_edge(e): remove edge e possibly spawning two new cores
+            if the graph_core that contained e gets disconnected.
+          remove_vertex(v): remove vertex v and all its edges.
+          order(): the order of the graph (number of vertices)
+          norm(): the norm of the graph (number of edges)
+          deg_min(): the minimum degree of vertices
+          deg_max(): the maximum degree of vertices
+          deg_avg(): the average degree of vertices
+          eps(): the graph epsilon value (norm/order), average number of edges per vertex. 
+          connected(): returns True if the graph is connected (i.e. it has only one component).
+          components(): returns self.C
+    """
+
+    component_class = graph_core
+
+    def __init__(self, V=None, E=None, directed=True):
+        if V is None:
+            V = []
+        if E is None:
+            E = []
+        self.directed = directed
+        # tag connex set of vertices:
+        # at first, every vertex is its own component
+        for v in V:
+            v.c = Poset([v])
+        CV = [v.c for v in V]
+        # then pass through edges and union associated vertices such that
+        # CV finally holds only connected sets:
+        for e in E:
+            x = e.v[0]
+            y = e.v[1]
+            assert x in V
+            assert y in V
+            assert x.c in CV
+            assert y.c in CV
+            e.attach()
+            if x.c != y.c:
+                # merge y.c into x.c :
+                x.c.update(y.c)
+                # update set list (MUST BE DONE BEFORE UPDATING REFS!)
+                CV.remove(y.c)
+                # update reference:
+                for z in y.c:
+                    z.c = x.c
+        # now create edge sets from connected vertex sets and
+        # make the graph_core connected graphs for this component :
+        self.C = []
+        for c in CV:
+            s = set()
+            for v in c:
+                s.update(v.e)
+            self.C.append(self.component_class(c, s, directed))
+
+    def add_vertex(self, v):
+        for c in self.C:
+            if v in c.sV:
+                return c.sV.get(v)
+        g = self.component_class(directed=self.directed)
+        v = g.add_single_vertex(v)
+        self.C.append(g)
+        return v
+
+    def add_edge(self, e):
+        # take vertices:
+        x = e.v[0]
+        y = e.v[1]
+        x = self.add_vertex(x)
+        y = self.add_vertex(y)
+        # take respective graph_cores:
+        cx = x.c
+        cy = y.c
+        # add edge:
+        e = cy.add_edge(e)
+        # connect (union) the graphs:
+        if cx != cy:
+            cx.union_update(cy)
+            self.C.remove(cy)
+        return e
+
+    def get_vertices_count(self):
+        return sum([c.order() for c in self.C])
+
+    def V(self):
+        for c in self.C:
+            V = c.sV
+            for v in V:
+                yield v
+
+    def E(self):
+        for c in self.C:
+            E = c.sE
+            for e in E:
+                yield e
+
+    def remove_edge(self, e):
+        # get the graph_core:
+        c = e.v[0].c
+        assert c == e.v[1].c
+        if not c in self.C:
+            return None
+        # remove edge in graph_core and replace it with two new cores
+        # if removing edge disconnects the graph_core:
+        try:
+            e = c.remove_edge(e)
+        except ValueError:
+            e = c.sE.remove(e)
+            e.detach()
+            self.C.remove(c)
+            tmpg = type(self)(c.sV, c.sE, self.directed)
+            assert len(tmpg.C) == 2
+            self.C.extend(tmpg.C)
+        return e
+
+    def remove_vertex(self, x):
+        # get the graph_core:
+        c = x.c
+        if not c in self.C:
+            return None
+        try:
+            x = c.remove_vertex(x)
+            if c.order() == 0:
+                self.C.remove(c)
+        except ValueError:
+            for e in x.detach():
+                c.sE.remove(e)
+            x = c.sV.remove(x)
+            self.C.remove(c)
+            tmpg = type(self)(c.sV, c.sE, self.directed)
+            assert len(tmpg.C) == 2
+            self.C.extend(tmpg.C)
+        return x
+
+    def order(self):
+        return sum([c.order() for c in self.C])
+
+    def norm(self):
+        return sum([c.norm() for c in self.C])
+
+    def deg_min(self):
+        return min([c.deg_min() for c in self.C])
+
+    def deg_max(self):
+        return max([c.deg_max() for c in self.C])
+
+    def deg_avg(self):
+        t = 0.0
+        for c in self.C:
+            t += sum([v.deg() for v in c.sV])
+        return t / float(self.order())
+
+    def eps(self):
+        return float(self.norm()) / self.order()
+
+    def path(self, x, y, f_io=0, hook=None):
+        if x == y:
+            return []
+        if x.c != y.c:
+            return None
+        # path:
+        return x.c.path(x, y, f_io, hook)
+
+    def N(self, v, f_io=0):
+        return v.N(f_io)
+
+    def __contains__(self, G):
+        r = False
+        for c in self.C:
+            r |= G in c
+        return r
+
+    def connected(self):
+        return len(self.C) == 1
+
+    # returns connectivity (kappa)
+    def connectivity(self):
+        raise NotImplementedError
+
+    # returns edge-connectivity (lambda)
+    def e_connectivity(self):
+        raise NotImplementedError
+
+    # returns the list of graphs components
+    def components(self):
+        return self.C
+
+    # derivated graphs:
+    # -----------------
+
+    # returns subgraph spanned by vertices V
+    def spans(self, V):
+        raise NotImplementedError
+
+    # returns join of G (if disjoint)
+    def __mul__(self, G):
+        raise NotImplementedError
+
+    # returns complement of a graph G
+    def complement(self, G):
+        raise NotImplementedError
+
+    # contraction G\e
+    def contract(self, e):
+        raise NotImplementedError

+ 1083 - 0
grandalf/layouts.py

@@ -0,0 +1,1083 @@
+# -*- coding: utf-8 -*-
+
+"""
+.. _layouts:
+
+layouts.py
+==========
+Layouts are classes that provide graph drawing algorithms.
+
+These classes all take a :class:`graph_core` argument. The graph
+topology will never be permanently modified by the drawing algorithm:
+e.g. "dummy" node insertion, edge reversal for making the graph
+acyclic and so on, are all kept inside the layout object.
+"""
+
+# This code is part of Grandalf
+# Copyright (C) 2010-2012 Axel Tillequin (bdcht3@gmail.com)
+# published under the GPLv2 license or EPLv1 license
+
+import importlib
+from bisect import bisect
+from sys import getrecursionlimit, setrecursionlimit
+
+from mantis.grandalf.utils import *
+
+# ------------------------------------------------------------------------------
+
+
+class VertexViewer(object):
+    """
+    The VertexViewer class is used as the default provider of
+    Vertex dimensions (w,h) and position (xy).
+    In most cases it should be replaced by *view* instances associated
+    with a ui widgets library, allowing to get dimensions and 
+    set position directly on the widget.
+    """
+
+    def __init__(self, w=2, h=2, data=None):
+        self.w = w
+        self.h = h
+        self.data = data
+        self.xy = None
+
+    def __str__(self, *args, **kwargs):
+        return "VertexViewer (xy: %s) w: %s h: %s" % (self.xy, self.w, self.h)
+
+
+# ------------------------------------------------------------------------------
+
+
+class _sugiyama_vertex_attr(object):
+    """
+    The sugiyama layout adds new attributes to vertices.
+    These attributes are stored in an internal _sugimyama_vertex_attr object.
+
+    Attributes:
+        rank (int): the rank number is the index of the layer that
+                    contains this vertex.
+        dummy (0/1): a flag indicating if the vertex is *dummy*
+        pos (int): the index of the vertex in the layer
+        x (list(float)): the list of computed horizontal coordinates of the vertex
+        bar (float): the current *barycenter* of the vertex 
+    """
+
+    def __init__(self, r=None, d=0):
+        self.rank = r
+        self.dummy = d
+        self.pos = None
+        self.x = 0
+        self.bar = None
+
+    def __str__(self):
+        s = "(%3d,%3d) x=%s" % (self.rank, self.pos, str(self.x))
+        if self.dummy:
+            s = "[d] %s" % s
+        return s
+
+    # def __eq__(self,x):
+    #    return self.bar == x.bar
+    # def __ne__(self,x):
+    #    return self.bar != x.bar
+    # def __lt__(self,x):
+    #    return self.bar < x.bar
+    # def __le__(self,x):
+    #    return self.bar <= x.bar
+    # def __gt__(self,x):
+    #    return self.bar > x.bar
+    # def __ge__(self,x):
+    #    return self.bar >= x.bar
+
+
+# ------------------------------------------------------------------------------
+
+
+class DummyVertex(_sugiyama_vertex_attr):
+    """
+    The DummyVertex class is used by the sugiyama layout to represent
+    *long* edges, i.e. edges that span over several ranks.
+    For these edges, a DummyVertex is inserted in every inner layer.
+
+    Attributes:
+        view (viewclass): since a DummyVertex is acting as a Vertex, it
+                          must have a view.
+        ctrl (list[_sugiyama_attr]): the list of associated dummy vertices
+
+    Methods:
+        N(dir): reflect the Vertex method and returns the list of adjacent
+                 vertices (possibly dummy) in the given direction.
+        inner(dir): return True if a neighbor in the given direction is *dummy*.
+    """
+
+    def __init__(self, r=None, viewclass=VertexViewer):
+        self.view = viewclass()
+        self.ctrl = None
+        super().__init__(r, d=1)
+
+    def N(self, dir):
+        assert dir == +1 or dir == -1
+        v = self.ctrl.get(self.rank + dir, None)
+        return [v] if v is not None else []
+
+    def inner(self, dir):
+        assert dir == +1 or dir == -1
+        try:
+            return any([x.dummy == 1 for x in self.N(dir)])
+        except KeyError:
+            return False
+        except AttributeError:
+            return False
+
+    def __str__(self):
+        s = "(%3d,%3d) x=%s" % (self.rank, self.pos, str(self.x))
+        if self.dummy:
+            s = "[d] %s" % s
+        return s
+
+
+# ------------------------------------------------------------------------------
+
+
+class Layer(list):
+    """
+    Layer is where Sugiyama layout organises vertices in hierarchical lists.
+    The placement of a vertex is done by the Sugiyama class, but it highly relies on
+    the *ordering* of vertices in each layer to reduce crossings.
+    This ordering depends on the neighbors found in the upper or lower layers.
+
+    Attributes:
+        layout (SugiyamaLayout): a reference to the sugiyama layout instance that
+                                 contains this layer
+        upper (Layer): a reference to the *upper* layer (rank-1)
+        lower (Layer): a reference to the *lower* layer (rank+1)
+        ccount (int) : number of crossings detected in this layer
+
+    Methods:
+        setup (layout): set initial attributes values from provided layout
+        nextlayer(): returns *next* layer in the current layout's direction parameter.
+        prevlayer(): returns *previous* layer in the current layout's direction parameter.
+        order(): compute *optimal* ordering of vertices within the layer.
+    """
+
+    __r = None
+    layout = None
+    upper = None
+    lower = None
+    __x = 1.0
+    ccount = None
+
+    def __eq__(self, other):
+        return super().__eq__(other)
+
+    def __str__(self):
+        s = "<Layer %d" % self.__r
+        s += ", len=%d" % len(self)
+        xc = self.ccount or "?"
+        s += ", crossings=%s>" % xc
+        return s
+
+    def setup(self, layout):
+        self.layout = layout
+        r = layout.layers.index(self)
+        self.__r = r
+        if len(self) > 1:
+            self.__x = 1.0 / (len(self) - 1)
+        for i, v in enumerate(self):
+            assert layout.grx[v].rank == r
+            layout.grx[v].pos = i
+            layout.grx[v].bar = i * self.__x
+        if r > 0:
+            self.upper = layout.layers[r - 1]
+        if r < len(layout.layers) - 1:
+            self.lower = layout.layers[r + 1]
+
+    def nextlayer(self):
+        return self.lower if self.layout.dirv == -1 else self.upper
+
+    def prevlayer(self):
+        return self.lower if self.layout.dirv == +1 else self.upper
+
+    def order(self):
+        sug = self.layout
+        sug._edge_inverter()
+        c = self._cc()
+        if c > 0:
+            for v in self:
+                sug.grx[v].bar = self._meanvalueattr(v)
+            # now resort layers l according to bar value:
+            self.sort(key=lambda x: sug.grx[x].bar)
+            # reduce & count crossings:
+            c = self._ordering_reduce_crossings()
+            # assign new position in layer l:
+            for i, v in enumerate(self):
+                sug.grx[v].pos = i
+                sug.grx[v].bar = i * self.__x
+        sug._edge_inverter()
+        self.ccount = c
+        return c
+
+    def _meanvalueattr(self, v):
+        """
+        find new position of vertex v according to adjacency in prevlayer.
+        position is given by the mean value of adjacent positions.
+        experiments show that meanvalue heuristic performs better than median.
+        """
+        sug = self.layout
+        if not self.prevlayer():
+            return sug.grx[v].bar
+        bars = [sug.grx[x].bar for x in self._neighbors(v)]
+        return sug.grx[v].bar if len(bars) == 0 else float(sum(bars)) / len(bars)
+
+    def _medianindex(self, v):
+        """
+        find new position of vertex v according to adjacency in layer l+dir.
+        position is given by the median value of adjacent positions.
+        median heuristic is proven to achieve at most 3 times the minimum
+        of crossings (while barycenter achieve in theory the order of |V|)
+        """
+        assert self.prevlayer() != None
+        N = self._neighbors(v)
+        g = self.layout.grx
+        pos = [g[x].pos for x in N]
+        lp = len(pos)
+        if lp == 0:
+            return []
+        pos.sort()
+        pos = pos[:: self.layout.dirh]
+        i, j = divmod(lp - 1, 2)
+        return [pos[i]] if j == 0 else [pos[i], pos[i + j]]
+
+    def _neighbors(self, v):
+        """
+        neighbors refer to upper/lower adjacent nodes.
+        Note that v.N() provides neighbors of v in the graph, while
+        this method provides the Vertex and DummyVertex adjacent to v in the
+        upper or lower layer (depending on layout.dirv state).
+        """
+        assert self.layout.dag
+        dirv = self.layout.dirv
+        grxv = self.layout.grx[v]
+        try:  # (cache)
+            return grxv.nvs[dirv]
+        except AttributeError:
+            grxv.nvs = {-1: v.N(-1), +1: v.N(+1)}
+            if grxv.dummy:
+                return grxv.nvs[dirv]
+            # v is real, v.N are graph neigbors but we need layers neighbors
+            for d in (-1, +1):
+                tr = grxv.rank + d
+                for i, x in enumerate(v.N(d)):
+                    if self.layout.grx[x].rank == tr:
+                        continue
+                    e = v.e_with(x)
+                    dum = self.layout.ctrls[e][tr]
+                    grxv.nvs[d][i] = dum
+            return grxv.nvs[dirv]
+
+    def _crossings(self):
+        """
+        counts (inefficently but at least accurately) the number of
+        crossing edges between layer l and l+dirv.
+        P[i][j] counts the number of crossings from j-th edge of vertex i.
+        The total count of crossings is the sum of flattened P:
+        x = sum(sum(P,[]))
+        """
+        g = self.layout.grx
+        P = []
+        for v in self:
+            P.append([g[x].pos for x in self._neighbors(v)])
+        for i, p in enumerate(P):
+            candidates = sum(P[i + 1 :], [])
+            for j, e in enumerate(p):
+                p[j] = len(filter((lambda nx: nx < e), candidates))
+            del candidates
+        return P
+
+    def _cc(self):
+        """
+        implementation of the efficient bilayer cross counting by insert-sort
+        (see Barth & Mutzel paper "Simple and Efficient Bilayer Cross Counting")
+        """
+        g = self.layout.grx
+        P = []
+        for v in self:
+            P.extend(sorted([g[x].pos for x in self._neighbors(v)]))
+        # count inversions in P:
+        s = []
+        count = 0
+        for i, p in enumerate(P):
+            j = bisect(s, p)
+            if j < i:
+                count += i - j
+            s.insert(j, p)
+        return count
+
+    def _ordering_reduce_crossings(self):
+        assert self.layout.dag
+        g = self.layout.grx
+        N = len(self)
+        X = 0
+        for i, j in zip(range(N - 1), range(1, N)):
+            vi = self[i]
+            vj = self[j]
+            ni = [g[v].bar for v in self._neighbors(vi)]
+            Xij = Xji = 0
+            for nj in [g[v].bar for v in self._neighbors(vj)]:
+                x = len([nx for nx in ni if nx > nj])
+                Xij += x
+                Xji += len(ni) - x
+            if Xji < Xij:
+                self[i] = vj
+                self[j] = vi
+                X += Xji
+            else:
+                X += Xij
+        return X
+
+
+# ------------------------------------------------------------------------------
+
+
+class SugiyamaLayout(object):
+    """
+    The Sugiyama layout is the traditional "layered" graph layout called
+    *dot* in graphviz. This layout is quite efficient but heavily relies 
+    on drawing heuristics. Adaptive drawing is limited to
+    extending the leaves only, but since the algorithm is quite fast
+    redrawing the entire graph (up to about a thousand nodes) gives
+    usually good results in less than a second.
+
+    The Sugiyama Layout Class takes as input a core_graph object and implements
+    an efficient drawing algorithm based on nodes dimensions provided through
+    a user-defined *view* property in each vertex.
+
+    Attributes:
+        dirvh (int): the current aligment state
+                     for alignment policy:
+                     dirvh=0 -> dirh=+1, dirv=-1: leftmost upper
+                     dirvh=1 -> dirh=-1, dirv=-1: rightmost upper
+                     dirvh=2 -> dirh=+1, dirv=+1: leftmost lower
+                     dirvh=3 -> dirh=-1, dirv=+1: rightmost lower
+        order_inter (int): the default number of layer placement iterations
+        order_attr (str): set attribute name used for layer ordering
+        xspace (int): horizontal space between vertices in a layer
+        yspace (int): vertical space between layers
+        dw (int): default width of a vertex
+        dh (int): default height of a vertex
+        g (graph_core): the graph component reference
+        layers (list[Layer]): the list of layers
+        grx (dict): associate vertex (possibly dummy) with their sugiyama attributes
+        ctrls (dict): associate edge with all its vertices (including dummies)
+        dag (bool): the current acyclic state 
+        initdone (bool): True if state is initialized (see init_all).
+    """
+
+    def __init__(self, g):
+        from mantis.grandalf.utils.geometry import median_wh
+
+        # drawing parameters:
+        self.dirvh = 0
+        self.order_iter = 8
+        self.order_attr = "pos"
+        self.xspace = 20
+        self.yspace = 20
+        self.dw = 10
+        self.dh = 10
+        # For layered graphs, vertices and edges need to have some additional
+        # attributes that make sense only for this kind of layout:
+        # update graph struct:
+        self.g = g
+        self.layers = []
+        self.grx = {}
+        self.ctrls = {}
+        self.dag = False
+        for v in self.g.V():
+            assert hasattr(v, "view")
+            self.grx[v] = _sugiyama_vertex_attr()
+        self.dw, self.dh = median_wh([v.view for v in self.g.V()])
+        self.initdone = False
+
+    def init_all(self, roots=None, inverted_edges=None, optimize=False):
+        """initializes the layout algorithm by computing roots (unless provided),
+           inverted edges (unless provided), vertices ranks and creates all dummy
+           vertices and layers. 
+             
+             Parameters:
+                roots (list[Vertex]): set *root* vertices (layer 0)
+                inverted_edges (list[Edge]): set edges to invert to have a DAG.
+                optimize (bool): optimize ranking if True (default False)
+        """
+        if self.initdone:
+            return
+        # For layered sugiyama algorithm, the input graph must be acyclic,
+        # so we must provide a list of root nodes and a list of inverted edges.
+        if roots is None:
+            roots = [v for v in self.g.sV if len(v.e_in()) == 0]
+        if inverted_edges is None:
+            _ = self.g.get_scs_with_feedback(roots)
+            inverted_edges = [x for x in self.g.sE if x.feedback]
+        self.alt_e = inverted_edges
+        # assign rank to all vertices:
+        self.rank_all(roots, optimize)
+        # add dummy vertex/edge for 'long' edges:
+        for e in self.g.E():
+            self.setdummies(e)
+        # precompute some layers values:
+        for l in self.layers:
+            l.setup(self)
+        self.initdone = True
+
+    def draw(self, N=1.5):
+        """compute every node coordinates after converging to optimal ordering by N
+           rounds, and finally perform the edge routing.
+        """
+        while N > 0.5:
+            for (l, mvmt) in self.ordering_step():
+                pass
+            N = N - 1
+        if N > 0:
+            for (l, mvmt) in self.ordering_step(oneway=True):
+                pass
+        self.setxy()
+        self.draw_edges()
+
+    def _edge_inverter(self):
+        for e in self.alt_e:
+            x, y = e.v
+            e.v = (y, x)
+        self.dag = not self.dag
+        if self.dag:
+            for e in self.g.degenerated_edges:
+                e.detach()
+                self.g.sE.remove(e)
+        else:
+            for e in self.g.degenerated_edges:
+                self.g.add_edge(e)
+
+    @property
+    def dirvh(self):
+        return self.__dirvh
+
+    @property
+    def dirv(self):
+        return self.__dirv
+
+    @property
+    def dirh(self):
+        return self.__dirh
+
+    @dirvh.setter
+    def dirvh(self, dirvh):
+        assert dirvh in range(4)
+        self.__dirvh = dirvh
+        self.__dirh, self.__dirv = {0: (1, -1),
+                                    1: (-1, -1),
+                                    2: (1, 1),
+                                    3: (-1, 1)}[dirvh]
+
+    @dirv.setter
+    def dirv(self, dirv):
+        assert dirv in (-1, +1)
+        dirvh = (dirv + 1) + (1 - self.__dirh) // 2
+        self.dirvh = dirvh
+
+    @dirh.setter
+    def dirh(self, dirh):
+        assert dirh in (-1, +1)
+        dirvh = (self.__dirv + 1) + (1 - dirh) // 2
+        self.dirvh = dirvh
+
+    def rank_all(self, roots, optimize=False):
+        """Computes rank of all vertices.
+        add provided roots to rank 0 vertices,
+        otherwise update ranking from provided roots.
+        The initial rank is based on precedence relationships,
+        optimal ranking may be derived from network flow (simplex).
+        """
+        self._edge_inverter()
+        r = [x for x in self.g.sV if (len(x.e_in()) == 0 and x not in roots)]
+        self._rank_init(roots + r)
+        if optimize:
+            self._rank_optimize()
+        self._edge_inverter()
+
+    def _rank_init(self, unranked):
+        """Computes rank of provided unranked list of vertices and all
+           their children. A vertex will be asign a rank when all its 
+           inward edges have been *scanned*. When a vertex is asigned
+           a rank, its outward edges are marked *scanned*.
+        """
+        assert self.dag
+        scan = {}
+        # set rank of unranked based on its in-edges vertices ranks:
+        while len(unranked) > 0:
+            l = []
+            for v in unranked:
+                self.setrank(v)
+                # mark out-edges has scan-able:
+                for e in v.e_out():
+                    scan[e] = True
+                # check if out-vertices are rank-able:
+                for x in v.N(+1):
+                    if not (False in [scan.get(e, False) for e in x.e_in()]):
+                        if x not in l:
+                            l.append(x)
+            unranked = l
+
+    def _rank_optimize(self):
+        """optimize ranking by pushing long edges toward lower layers as much as possible.
+        see other interersting network flow solver to minimize total edge length
+        (http://jgaa.info/accepted/2005/EiglspergerSiebenhallerKaufmann2005.9.3.pdf)
+        """
+        assert self.dag
+        for l in reversed(self.layers):
+            for v in l:
+                gv = self.grx[v]
+                for x in v.N(-1):
+                    if all((self.grx[y].rank >= gv.rank for y in x.N(+1))):
+                        gx = self.grx[x]
+                        self.layers[gx.rank].remove(x)
+                        gx.rank = gv.rank - 1
+                        self.layers[gv.rank - 1].append(x)
+
+    def setrank(self, v):
+        """set rank value for vertex v and add it to the corresponding layer.
+           The Layer is created if it is the first vertex with this rank.
+        """
+        assert self.dag
+        r = max([self.grx[x].rank for x in v.N(-1)] + [-1]) + 1
+        self.grx[v].rank = r
+        # add it to its layer:
+        try:
+            self.layers[r].append(v)
+        except IndexError:
+            assert r == len(self.layers)
+            self.layers.append(Layer([v]))
+
+    def dummyctrl(self, r, ctrl):
+        """creates a DummyVertex at rank r inserted in the ctrl dict
+           of the associated edge and layer.
+
+           Arguments:
+              r (int): rank value
+              ctrl (dict): the edge's control vertices
+           
+           Returns:
+              DummyVertex : the created DummyVertex.
+        """
+        dv = DummyVertex(r)
+        dv.view.w, dv.view.h = self.dw, self.dh
+        self.grx[dv] = dv
+        dv.ctrl = ctrl
+        ctrl[r] = dv
+        self.layers[r].append(dv)
+        return dv
+
+    def setdummies(self, e):
+        """creates and defines all needed dummy vertices for edge e.
+        """
+        v0, v1 = e.v
+        r0, r1 = self.grx[v0].rank, self.grx[v1].rank
+        if r0 > r1:
+            assert e in self.alt_e
+            v0, v1 = v1, v0
+            r0, r1 = r1, r0
+        if (r1 - r0) > 1:
+            # "dummy vertices" are stored in the edge ctrl dict,
+            # keyed by their rank in layers.
+            ctrl = self.ctrls[e] = {}
+            ctrl[r0] = v0
+            ctrl[r1] = v1
+            for r in range(r0 + 1, r1):
+                self.dummyctrl(r, ctrl)
+
+    def draw_step(self):
+        """iterator that computes all vertices coordinates and edge routing after
+           just one step (one layer after the other from top to bottom to top).
+           Purely inefficient ! Use it only for "animation" or debugging purpose.
+        """
+        ostep = self.ordering_step()
+        for s in ostep:
+            self.setxy()
+            self.draw_edges()
+            yield s
+
+    def ordering_step(self, oneway=False):
+        """iterator that computes all vertices ordering in their layers
+           (one layer after the other from top to bottom, to top again unless
+           oneway is True).
+        """
+        self.dirv = -1
+        crossings = 0
+        for l in self.layers:
+            mvmt = l.order()
+            crossings += mvmt
+            yield (l, mvmt)
+        if oneway or (crossings == 0):
+            return
+        self.dirv = +1
+        while l:
+            mvmt = l.order()
+            yield (l, mvmt)
+            l = l.nextlayer()
+
+    def setxy(self):
+        """computes all vertex coordinates (x,y) using
+        an algorithm by Brandes & Kopf.
+        """
+        self._edge_inverter()
+        self._detect_alignment_conflicts()
+        inf = float("infinity")
+        # initialize vertex coordinates attributes:
+        for l in self.layers:
+            for v in l:
+                self.grx[v].root = v
+                self.grx[v].align = v
+                self.grx[v].sink = v
+                self.grx[v].shift = inf
+                self.grx[v].X = None
+                self.grx[v].x = [0.0] * 4
+        curvh = self.dirvh  # save current dirvh value
+        for dirvh in range(4):
+            self.dirvh = dirvh
+            self._coord_vertical_alignment()
+            self._coord_horizontal_compact()
+        self.dirvh = curvh  # restore it
+        # vertical coordinate assigment of all nodes:
+        Y = 0
+        for l in self.layers:
+            dY = max([v.view.h / 2.0 for v in l])
+            for v in l:
+                vx = sorted(self.grx[v].x)
+                # mean of the 2 medians out of the 4 x-coord computed above:
+                avgm = (vx[1] + vx[2]) / 2.0
+                # final xy-coordinates :
+                v.view.xy = (avgm, Y + dY)
+            Y += 2 * dY + self.yspace
+        self._edge_inverter()
+
+    def _detect_alignment_conflicts(self):
+        """mark conflicts between edges:
+        inner edges are edges between dummy nodes
+        type 0 is regular crossing regular (or sharing vertex)
+        type 1 is inner crossing regular (targeted crossings)
+        type 2 is inner crossing inner (avoided by reduce_crossings phase)
+        """
+        curvh = self.dirvh  # save current dirvh value
+        self.dirvh = 0
+        self.conflicts = []
+        for L in self.layers:
+            last = len(L) - 1
+            prev = L.prevlayer()
+            if not prev:
+                continue
+            k0 = 0
+            k1_init = len(prev) - 1
+            l = 0
+            for l1, v in enumerate(L):
+                if not self.grx[v].dummy:
+                    continue
+                if l1 == last or v.inner(-1):
+                    k1 = k1_init
+                    if v.inner(-1):
+                        k1 = self.grx[v.N(-1)[-1]].pos
+                    for vl in L[l : l1 + 1]:
+                        for vk in L._neighbors(vl):
+                            k = self.grx[vk].pos
+                            if k < k0 or k > k1:
+                                self.conflicts.append((vk, vl))
+                    l = l1 + 1
+                    k0 = k1
+        self.dirvh = curvh  # restore it
+
+    def _coord_vertical_alignment(self):
+        """performs vertical alignment according to current dirvh internal state.
+        """
+        dirh, dirv = self.dirh, self.dirv
+        g = self.grx
+        for l in self.layers[::-dirv]:
+            if not l.prevlayer():
+                continue
+            r = None
+            for vk in l[::dirh]:
+                for m in l._medianindex(vk):
+                    # take the median node in dirv layer:
+                    um = l.prevlayer()[m]
+                    # if vk is "free" align it with um's root
+                    if g[vk].align is vk:
+                        if dirv == 1:
+                            vpair = (vk, um)
+                        else:
+                            vpair = (um, vk)
+                        # if vk<->um link is used for alignment
+                        if (vpair not in self.conflicts) and (
+                            (r is None) or (dirh * r < dirh * m)
+                        ):
+                            g[um].align = vk
+                            g[vk].root = g[um].root
+                            g[vk].align = g[vk].root
+                            r = m
+
+    def _coord_horizontal_compact(self):
+        limit = getrecursionlimit()
+        N = len(self.layers) + 10
+        if N > limit:
+            setrecursionlimit(N)
+        dirh, dirv = self.dirh, self.dirv
+        g = self.grx
+        L = self.layers[::-dirv]
+        # recursive placement of blocks:
+        for l in L:
+            for v in l[::dirh]:
+                if g[v].root is v:
+                    self.__place_block(v)
+        setrecursionlimit(limit)
+        # mirror all nodes if right-aligned:
+        if dirh == -1:
+            for l in L:
+                for v in l:
+                    x = g[v].X
+                    if x:
+                        g[v].X = -x
+        # then assign x-coord of its root:
+        inf = float("infinity")
+        rb = inf
+        for l in L:
+            for v in l[::dirh]:
+                g[v].x[self.dirvh] = g[g[v].root].X
+                rs = g[g[v].root].sink
+                s = g[rs].shift
+                if s < inf:
+                    g[v].x[self.dirvh] += dirh * s
+                rb = min(rb, g[v].x[self.dirvh])
+        # normalize to 0, and reinit root/align/sink/shift/X
+        for l in self.layers:
+            for v in l:
+                # g[v].x[dirvh] -= rb
+                g[v].root = g[v].align = g[v].sink = v
+                g[v].shift = inf
+                g[v].X = None
+
+    # TODO: rewrite in iterative form to avoid recursion limit...
+    def __place_block(self, v):
+        g = self.grx
+        if g[v].X is None:
+            # every block is initially placed at x=0
+            g[v].X = 0.0
+            # place block in which v belongs:
+            w = v
+            while 1:
+                j = g[w].pos - self.dirh  # predecessor in rank must be placed
+                r = g[w].rank
+                if 0 <= j < len(self.layers[r]):
+                    wprec = self.layers[r][j]
+                    delta = (
+                        self.xspace + (wprec.view.w + w.view.w) / 2.0
+                    )  # abs positive minimum displ.
+                    # take root and place block:
+                    u = g[wprec].root
+                    self.__place_block(u)
+                    # set sink as sink of prec-block root
+                    if g[v].sink is v:
+                        g[v].sink = g[u].sink
+                    if g[v].sink != g[u].sink:
+                        s = g[u].sink
+                        newshift = g[v].X - (g[u].X + delta)
+                        g[s].shift = min(g[s].shift, newshift)
+                    else:
+                        g[v].X = max(g[v].X, (g[u].X + delta))
+                # take next node to align in block:
+                w = g[w].align
+                # quit if self aligned
+                if w is v:
+                    break
+
+    def draw_edges(self):
+        """Basic edge routing applied only for edges with dummy points.
+        Enhanced edge routing can be performed by using the apropriate
+        *route_with_xxx* functions from :ref:routing_ in the edges' view.
+        """
+        for e in self.g.E():
+            if hasattr(e, "view"):
+                l = []
+                if e in self.ctrls:
+                    D = self.ctrls[e]
+                    r0, r1 = self.grx[e.v[0]].rank, self.grx[e.v[1]].rank
+                    if r0 < r1:
+                        ranks = range(r0 + 1, r1)
+                    else:
+                        ranks = range(r0 - 1, r1, -1)
+                    l = [D[r].view.xy for r in ranks]
+                l.insert(0, e.v[0].view.xy)
+                l.append(e.v[1].view.xy)
+                try:
+                    self.route_edge(e, l)
+                except AttributeError:
+                    pass
+                e.view.setpath(l)
+
+
+#  DIRECTED GRAPH WITH CONSTRAINTS LAYOUT
+# ------------------------------------------------------------------------------
+
+
+class DigcoLayout(object):
+    linalg = importlib.import_module("mantis.grandalf.utils.geometry")
+
+    def __init__(self, g):
+        # drawing parameters:
+        self.xspace = 10
+        self.yspace = 10
+        self.dr = 10
+        self.debug = False
+
+        self.g = g
+        self.levels = []
+        for i, v in enumerate(self.g.V()):
+            assert hasattr(v, "view")
+            v.i = i
+            self.dr = max((self.dr, v.view.w, v.view.h))
+        # solver parameters:
+        self._cg_max_iter = g.order()
+        self._cg_tolerance = 1.0e-6
+        self._eps = 1.0e-5
+        self._cv_max_iter = self._cg_max_iter
+
+    def init_all(self, alpha=0.1, beta=0.01):
+        y = None
+        if self.g.directed:
+            # partition g in hierarchical levels:
+            y = self.part_to_levels(alpha, beta)
+        # initiate positions (y and random in x):
+        self.Z = self._xyinit(y)
+
+    def draw(self, N=None):
+        if N is None:
+            N = self._cv_max_iter
+        self.Z = self._optimize(self.Z, limit=N)
+        # set view xy from near-optimal coords matrix:
+        for v in self.g.V():
+            v.view.xy = (self.Z[v.i][0, 0] * self.dr, self.Z[v.i][0, 1] * self.dr)
+        self.draw_edges()
+
+    def draw_step(self):
+        for x in range(self._cv_max_iter):
+            self.draw(N=1)
+            self.draw_edges()
+            yield
+
+    # Basic edge routing with segments
+    def draw_edges(self):
+        for e in self.g.E():
+            if hasattr(e, "view"):
+                l = [e.v[0].view.xy, e.v[1].view.xy]
+                try:
+                    self.route_edge(e, l)
+                except AttributeError:
+                    pass
+                e.view.setpath(l)
+
+    # partition the nodes into levels:
+    def part_to_levels(self, alpha, beta):
+        opty, err = self.optimal_arrangement()
+        ordering = list(zip(opty, self.g.sV))
+        eps = alpha * (opty.max() - opty.min()) / (len(opty) - 1)
+        eps = max(beta, eps)
+        sorted(ordering, reverse=True)
+        l = []
+        self.levels.append(l)
+        for i in range(len(list(ordering)) - 1):
+            y, v = ordering[i]
+            l.append(v)
+            v.level = self.levels.index(l)
+            if (y - ordering[i + 1][0]) > eps:
+                l = []
+                self.levels.append(l)
+        y, v = ordering[-1]
+        l.append(v)
+        v.level = self.levels.index(l)
+        return opty
+
+    def optimal_arrangement(self):
+        b = self.balance()
+        y = DigcoLayout.linalg.rand_ortho1(self.g.order())
+        return self._conjugate_gradient_L(y, b)
+
+    # balance vector is assembled in finite-element way...
+    # this is faster than computing b[i] for each i.
+    def balance(self):
+        b = DigcoLayout.linalg.array([0.0] * self.g.order(), dtype=float)
+        for e in self.g.E():
+            s = e.v[0]
+            d = e.v[1]
+            q = e.w * (self.yspace + (s.view.h + d.view.h) / 2.0)
+            b[s.i] += q
+            b[d.i] -= q
+        return b
+
+    # We compute the solution Y of L.Y = b by conjugate gradient method
+    # (L is semi-definite positive so Y is unique and convergence is O(n))
+    # note that only arrays are involved here...
+    def _conjugate_gradient_L(self, y, b):
+        Lii = self.__Lii_()
+        r = b - self.__L_pk(Lii, y)
+        p = DigcoLayout.linalg.array(r, copy=True)
+        rr = sum(r * r)
+        for k in range(self._cg_max_iter):
+            try:
+                Lp = self.__L_pk(Lii, p)
+                alpha = rr / sum(p * Lp)
+                y += alpha / p
+                r -= alpha * Lp
+                newrr = sum(r * r)
+                beta = newrr / rr
+                rr = newrr
+                if rr < self._cg_tolerance:
+                    break
+                p = r + beta * p
+            except ZeroDivisionError:
+                return (None, rr)
+        return (y, rr)
+
+    # _xyinit can use diagonally scaled initial vertices positioning to provide
+    # better convergence in constrained stress majorization
+    def _xyinit(self, y=None):
+        if y is None:
+            y = DigcoLayout.linalg.rand_ortho1(self.g.order())
+        x = DigcoLayout.linalg.rand_ortho1(self.g.order())
+        # translate and normalize:
+        x = x - x[0]
+        y = y - y[0]
+        sfactor = 1.0 / max(list(map(abs, y)) + list(map(abs, x)))
+        return DigcoLayout.linalg.matrix(list(zip(x * sfactor, y * sfactor)))
+
+    # provide the diagonal of the Laplacian matrix of g
+    # the rest of L (sparse!) is already stored in every edges.
+    def __Lii_(self):
+        Lii = []
+        for v in self.g.V():
+            Lii.append(sum([e.w for e in v.e]))
+        return DigcoLayout.linalg.array(Lii, dtype=float)
+
+    # we don't compute the L.Pk matrix/vector product here since
+    # L is sparse (order of |E| not |V|^2 !) so we let each edge
+    # contribute to the resulting L.Pk vector in a FE assembly way...
+    def __L_pk(self, Lii, pk):
+        y = Lii * pk
+        for e in self.g.sE:
+            i1 = e.v[0].i
+            i2 = e.v[1].i
+            y[i1] -= e.w * pk[i2]
+            y[i2] -= e.w * pk[i1]
+        return y
+
+    # conjugate_gradient with given matrix Lw:
+    # it is assumed that b is not a multivector,
+    # so _cg_Lw should be called in all directions separately.
+    # note that everything is a matrix here, (arrays are row vectors only)
+    def _cg_Lw(self, Lw, z, b):
+        scal = lambda U, V: float(U.transpose() * V)
+        r = b - Lw * z
+        p = r.copy()
+        rr = scal(r, r)
+        for k in range(self._cg_max_iter):
+            if rr < self._cg_tolerance:
+                break
+            Lp = Lw * p
+            alpha = rr / scal(p, Lp)
+            z = z + alpha * p
+            r = r - alpha * Lp
+            newrr = scal(r, r)
+            beta = newrr / rr
+            rr = newrr
+            p = r + beta * p
+        return (z, rr)
+
+    def __Dij_(self):
+        Dji = []
+        for v in self.g.V():
+            wd = self.g.dijkstra(v)
+            Di = [wd[w] for w in self.g.V()]
+            Dji.append(Di)
+        # at this point  D is stored by rows,
+        # but anymway it's a symmetric matrix
+        return DigcoLayout.linalg.matrix(Dji, dtype=float)
+
+    # returns matrix -L^w
+    def __Lij_w_(self):
+        self.Dij = self.__Dij_()  # we keep D also for L^Z computations
+        Lij = self.Dij.copy()
+        n = self.g.order()
+        for i in range(n):
+            d = 0
+            for j in range(n):
+                if j == i:
+                    continue
+                Lij[i, j] = 1.0 / self.Dij[i, j] ** 2
+                d += Lij[i, j]
+            Lij[i, i] = -d
+        return Lij
+
+    # returns vector -L^Z.Z:
+    def __Lij_Z_Z(self, Z):
+        n = self.g.order()
+        # init:
+        lzz = Z.copy() * 0.0  # lzz has dim Z (n x 2)
+        liz = DigcoLayout.linalg.matrix([0.0] * n)  # liz is a row of L^Z (size n)
+        # compute lzz = L^Z.Z while assembling L^Z by row (liz):
+        for i in range(n):
+            iterk_except_i = (k for k in range(n) if k != i)
+            for k in iterk_except_i:
+                v = Z[i] - Z[k]
+                liz[0, k] = 1.0 / (
+                    self.Dij[i, k] * DigcoLayout.linalg.sqrt(v * v.transpose())
+                )
+            liz[0, i] = 0.0  # forced, otherwise next liz.sum() is wrong !
+            liz[0, i] = -liz.sum()
+            # now that we have the i-th row of L^Z, just dotprod with Z:
+            lzz[i] = liz * Z
+        return lzz
+
+    def _optimize(self, Z, limit=100):
+        Lw = self.__Lij_w_()
+        K = self.g.order() * (self.g.order() - 1.0) / 2.0
+        stress = float("inf")
+        count = 0
+        deep = 0
+        b = self.__Lij_Z_Z(Z)
+        while count < limit:
+            if self.debug:
+                print("count %d" % count)
+                print("Z = ", Z)
+                print("b = ", b)
+            # find next Z by solving Lw.Z = b in every direction:
+            x, xerr = self._cg_Lw(Lw[1:, 1:], Z[1:, 0], b[1:, 0])
+            y, yerr = self._cg_Lw(Lw[1:, 1:], Z[1:, 1], b[1:, 1])
+            Z[1:, 0] = x
+            Z[1:, 1] = y
+            if self.debug:
+                print(" cg -> ")
+                print(Z, xerr, yerr)
+            # compute new stress:
+            FZ = K - float(x.transpose() * b[1:, 0] + y.transpose() * b[1:, 1])
+            # precompute new b:
+            b = self.__Lij_Z_Z(Z)
+            # update new stress:
+            FZ += 2 * float(x.transpose() * b[1:, 0] + y.transpose() * b[1:, 1])
+            # test convergence:
+            print("stress=%.10f" % FZ)
+            if stress == 0.0:
+                break
+            elif abs((stress - FZ) / stress) < self._eps:
+                if deep == 2:
+                    break
+                else:
+                    deep += 1
+            stress = FZ
+            count += 1
+        return Z
+
+
+# ------------------------------------------------------------------------------
+class DwyerLayout(DigcoLayout):
+    pass

+ 132 - 0
grandalf/routing.py

@@ -0,0 +1,132 @@
+# This code is part of Grandalf
+#  Copyright (C) 2011 Axel Tillequin (bdcht3@gmail.com) and others
+# published under GPLv2 license or EPLv1 license
+# Contributor(s): Axel Tillequin, Fabio Zadrozny
+
+#  Edge routing algorithms.
+#  These are mosty helpers for routing an edge 'e' through
+#  points pts with various tweaks like moving the starting point
+#  to the intersection with the bounding box and taking some constraints
+#  into account, and/or moving the head also to its prefered position.
+#  Of course, since gandalf only works with bounding boxes, the exact
+#  shape of the nodes are not known and the edge drawing inside the bb
+#  shall be performed by the drawing engine associated with 'views'.
+#  (e.g. look at intersectC when the node shape is a circle)
+
+from mantis.grandalf.utils.geometry import intersectR, getangle, sqrt
+
+# ------------------------------------------------------------------------------
+class EdgeViewer(object):
+    def setpath(self, pts):
+        self._pts = pts
+
+
+# ------------------------------------------------------------------------------
+#  basic edge routing with lines : nothing to do for routing
+#  since the layout engine has already provided to list of points through which
+#  the edge shall be drawn. We just compute the position where to adjust the
+#  tail and head.
+def route_with_lines(e, pts):
+    assert hasattr(e, "view")
+    tail_pos = intersectR(e.v[0].view, topt=pts[1])
+    head_pos = intersectR(e.v[1].view, topt=pts[-2])
+    pts[0] = tail_pos
+    pts[-1] = head_pos
+    e.view.head_angle = getangle(pts[-2], pts[-1])
+
+
+# ------------------------------------------------------------------------------
+#  enhanced edge routing where 'corners' of the above polyline route are
+#  rounded with a bezier curve.
+def route_with_splines(e, pts):
+    from mantis.grandalf.utils.geometry import setroundcorner
+
+    route_with_lines(e, pts)
+    splines = setroundcorner(e, pts)
+    e.view.splines = splines
+
+
+def _gen_point(p1, p2, new_distance):
+    from mantis.grandalf.utils.geometry import new_point_at_distance
+
+    initial_distance = distance = sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2)
+    if initial_distance < 1e-10:
+        return None
+    if distance > new_distance:
+        distance = distance - new_distance
+    else:
+        return None
+    angle = getangle(p1, p2)
+    new = new_point_at_distance(p1, distance, angle)
+    return new
+
+
+def _gen_smoother_middle_points_from_3_points(pts, initial):
+    p1 = pts[0]
+    p2 = pts[1]
+    p3 = pts[2]
+    distance1 = sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2)
+    distance2 = sqrt((p3[0] - p1[0]) ** 2 + (p3[1] - p1[1]) ** 2)
+    if distance1 < 1e-10 or distance2 < 1e-10:
+        yield p2
+    else:
+        if distance1 < initial or distance2 < initial:
+            yield p2
+        else:
+            p2a = _gen_point(p1, p2, initial)
+            p2b = _gen_point(p3, p2, initial)
+            if p2a is None or p2b is None:
+                yield p2
+            else:
+                yield p2a
+                yield p2b
+
+
+# Future work: possibly work better when we already have 4 points?
+# maybe: http://stackoverflow.com/questions/1251438/catmull-rom-splines-in-python
+def _round_corners(pts, round_at_distance):
+    if len(pts) > 2:
+        calc_with_distance = round_at_distance
+        while calc_with_distance > 0.5:
+            new_lst = [pts[0]]
+            for i, curr in enumerate(pts[1:-1]):
+                i += 1
+                p1 = pts[i - 1]
+                p2 = curr
+                p3 = pts[i + 1]
+                if len(pts) > 3:
+                    # i.e.: at least 4 points
+                    if sqrt((p3[0] - p2[0]) ** 2 + (p3[1] - p2[1]) ** 2) < (
+                        2 * calc_with_distance
+                    ):
+                        # prevent from crossing over.
+                        new_lst.append(p2)
+                        continue
+                generated = _gen_smoother_middle_points_from_3_points(
+                    [p1, p2, p3], calc_with_distance
+                )
+                for j in generated:
+                    new_lst.append(j)
+            new_lst.append(pts[-1])
+            pts = new_lst
+            calc_with_distance /= 2.0
+    return pts
+
+
+# ------------------------------------------------------------------------------
+# Routing with a custom algorithm to round corners
+# It works by generating new points up to a distance from where an edge is
+# found (and then iteratively refining based on that).
+# This is a custom implementation as this interpolation method worked
+# well for me where others weren't so great.
+
+# This is the point where it'll start rounding from an edge.
+# (can be changed to decide up to which distance it starts
+# rounding from an edge).
+ROUND_AT_DISTANCE = 40
+
+
+def route_with_rounded_corners(e, pts):
+    route_with_lines(e, pts)
+    new_pts = _round_corners(pts, round_at_distance=ROUND_AT_DISTANCE)
+    pts[:] = new_pts[:]

+ 3 - 0
grandalf/utils/__init__.py

@@ -0,0 +1,3 @@
+from .poset import *
+from .dot import *
+from .nx import *

+ 401 - 0
grandalf/utils/dot.py

@@ -0,0 +1,401 @@
+# This code is part of Grandalf
+# Copyright (C) 2008 Axel Tillequin (bdcht3@gmail.com) and others
+# published under GPLv2 license or EPLv1 license
+# Contributor(s): Axel Tillequin
+
+try:
+    import ply.lex as lex
+    import ply.yacc as yacc
+
+    _has_ply = True
+except ImportError:
+    _has_ply = False
+
+__all__ = ["_has_ply", "Dot"]
+
+# ------------------------------------------------------------------------------
+# LALR(1) parser for Graphviz dot file format.
+class Dot:
+
+    _reserved = (
+        "strict",
+        "graph",
+        "digraph",
+        "subgraph",
+        "node",
+        "edge",
+    )
+    _tokens = ("regulars", "string", "html", "comment",) + _reserved
+
+    _literals = [",", ";", "-", ">", "=", ":", "[", "]", "{", "}"]
+
+    class Lexer(object):
+        def __init__(self):
+            self.whitespace = "\0\t\n\f\r "
+            self.reserved = Dot._reserved
+            self.tokens = Dot._tokens
+            self.literals = Dot._literals
+            self.t_ignore = self.whitespace
+
+        def t_regulars(self, t):
+            r"[-]?[\w.]+"
+            v = t.value.lower()
+            if v in self.reserved:
+                t.type = v
+                return t
+            # check numeric string
+            if v[0].isdigit() or v[0] in ["-", "."]:
+                try:
+                    float(v)
+                except ValueError:
+                    print("invalid numeral token: %s" % v)
+                    raise SyntaxError
+            elif "." in v:  # forbidden in non-numeric
+                raise SyntaxError
+            return t
+
+        def t_comment_online(self, t):
+            r"(//(.*)\n)|\\\n"
+            pass
+
+        def t_comment_macro(self, t):
+            r"(\#(.*)\n)"
+            pass
+
+        def t_comment_multline(self, t):
+            r"(/\*)"
+            start = t.lexer.lexpos
+            t.lexer.lexpos = t.lexer.lexdata.index("*/", start) + 2
+
+        def t_string(self, t):
+            r'"'
+            start = t.lexer.lexpos - 1
+            i = t.lexer.lexdata.index('"', start + 1)
+            while t.lexer.lexdata[i - 1] == "\\":
+                i = t.lexer.lexdata.index('"', i + 1)
+            t.value = t.lexer.lexdata[start : i + 1]
+            t.lexer.lexpos = i + 1
+            return t
+
+        def t_html(self, t):
+            r"<"
+            start = t.lexer.lexpos - 1
+            level = 1
+            i = start + 1
+            while level > 0:
+                c = t.lexer.lexdata[i]
+                if c == "<":
+                    level += 1
+                if c == ">":
+                    level -= 1
+                i += 1
+            t.value = t.lexer.lexdata[start:i]
+            t.lexer.lexpos = i
+            return t
+
+        def t_ANY_error(self, t):
+            print("Illegal character '%s'" % t.value[0])
+            t.lexer.skip(1)
+
+        def build(self, **kargs):
+            if _has_ply:
+                self._lexer = lex.lex(module=self, **kargs)
+
+        def test(self, data):
+            self._lexer.input(data)
+            while 1:
+                tok = self._lexer.token()
+                if not tok:
+                    break
+                print(tok)
+
+    # Classes for the AST returned by Parser:
+    class graph(object):
+        def __init__(self, name, data, strict=None, direct=None):
+            self.name = name
+            self.strict = strict
+            self.direct = direct
+            self.nodes = {}
+            self.edges = []
+            self.subgraphs = []
+            self.attr = {}
+            eattr = {}
+            nattr = {}
+            for x in data:  # data is a statements (list of stmt)
+                # x is a stmt, ie one of:
+                # a graph object (subgraph)
+                # a attr object (graph/node/edge attributes)
+                # a dict object (ID=ID)
+                # a node object
+                # a list of edges
+                if isinstance(x, Dot.graph):
+                    self.subgraphs.append(x)
+                elif isinstance(x, Dot.attr):
+                    if x.type == "graph":
+                        self.attr.update(x.D)
+                    elif x.type == "node":
+                        nattr.update(x.D)
+                    elif x.type == "edge":
+                        eattr.update(x.D)
+                    else:
+                        raise TypeError("invalid attribute type")
+                elif isinstance(x, dict):
+                    self.attr.update(x)
+                elif isinstance(x, Dot.node):
+                    x.attr.update(nattr)
+                    self.nodes[x.name] = x
+                else:
+                    for e in x:
+                        e.attr.update(eattr)
+                        self.edges.append(e)
+                        for n in [e.n1, e.n2]:
+                            if isinstance(n, Dot.graph):
+                                continue
+                            if n.name not in self.nodes:
+                                n.attr.update(nattr)
+                                self.nodes[n.name] = n
+
+        def __repr__(self):
+            u = "<%s instance at %x, name: %s, %d nodes>" % (
+                self.__class__,
+                id(self),
+                self.name,
+                len(self.nodes),
+            )
+            return u
+
+    class attr(object):
+        def __init__(self, type, D):
+            self.type = type
+            self.D = D
+
+    class edge(object):
+        def __init__(self, n1, n2):
+            self.n1 = n1
+            self.n2 = n2
+            self.attr = {}
+
+    class node(object):
+        def __init__(self, name, port=None):
+            self.name = name
+            self.port = port
+            self.attr = {}
+
+    class Parser(object):
+        def __init__(self):
+            self.tokens = Dot._tokens
+
+        def __makelist(self, p):
+            N = len(p)
+            if N > 2:
+                L = p[1]
+                L.append(p[N - 1])
+            else:
+                L = []
+                if N > 1:
+                    L.append(p[N - 1])
+            p[0] = L
+
+        def p_Data(self, p):
+            """Data : Data Graph
+                    | Graph"""
+            self.__makelist(p)
+
+        def p_Graph_strict(self, p):
+            """Graph : strict graph name Block"""
+            p[0] = Dot.graph(name=p[3], data=p[4], strict=1, direct=0)
+            # print 'Dot.Parser: graph object %s created'%p[0].name
+
+        def p_Graph_graph(self, p):
+            """Graph : graph name Block"""
+            p[0] = Dot.graph(name=p[2], data=p[3], strict=0, direct=0)
+
+        def p_Graph_strict_digraph(self, p):
+            """Graph : strict digraph name Block"""
+            p[0] = Dot.graph(name=p[3], data=p[4], strict=1, direct=1)
+
+        def p_Graph_digraph(self, p):
+            """Graph : digraph name Block"""
+            p[0] = Dot.graph(name=p[2], data=p[3], strict=0, direct=1)
+
+        def p_ID(self, p):
+            """ID : regulars
+                  | string
+                  | html """
+            p[0] = p[1]
+
+        def p_name(self, p):
+            """name : ID
+                    | """
+            if len(p) == 1:
+                p[0] = ""
+            else:
+                p[0] = p[1]
+
+        def p_Block(self, p):
+            """Block : '{' statements '}' """
+            p[0] = p[2]
+
+        def p_statements(self, p):
+            """statements : statements stmt
+                          | stmt
+                          | """
+            self.__makelist(p)
+
+        def p_stmt(self, p):
+            """stmt : stmt ';' """
+            p[0] = p[1]
+
+        def p_comment(self, p):
+            """stmt : comment"""
+            pass  # comment tokens are not outputed by lexer anyway
+
+        def p_stmt_sub(self, p):
+            """stmt : sub"""
+            p[0] = p[1]
+
+        def p_subgraph(self, p):
+            """sub : subgraph name Block
+                   | Block """
+            N = len(p)
+            if N > 2:
+                ID = p[2]
+            else:
+                ID = ""
+            p[0] = Dot.graph(name=ID, data=p[N - 1], strict=0, direct=0)
+
+        def p_stmt_assign(self, p):
+            """stmt : affect """
+            p[0] = p[1]
+
+        def p_affect(self, p):
+            """affect : ID '=' ID """
+            p[0] = dict([(p[1], p[3])])
+
+        def p_stmt_lists(self, p):
+            """stmt : graph attrs
+                    | node  attrs
+                    | edge  attrs """
+            p[0] = Dot.attr(p[1], p[2])
+
+        def p_attrs(self, p):
+            """attrs : attrs attrl
+                     | attrl """
+            if len(p) == 3:
+                p[1].update(p[2])
+            p[0] = p[1]
+
+        def p_attrl(self, p):
+            """attrl : '[' alist ']' """
+            L = {}
+            for a in p[2]:
+                if isinstance(a, dict):
+                    L.update(a)
+                else:
+                    L[a] = "true"
+            p[0] = L
+
+        def p_alist_comma(self, p):
+            """alist : alist ',' alist """
+            p[1].extend(p[3])
+            p[0] = p[1]
+
+        def p_alist_affect(self, p):
+            """alist : alist affect
+                     | alist ID
+                     | affect
+                     | ID
+                     | """
+            self.__makelist(p)
+
+        def p_stmt_E_attrs(self, p):
+            """stmt : E attrs """
+            for e in p[1]:
+                e.attr = p[2]
+            p[0] = p[1]
+
+        def p_stmt_N_attrs(self, p):
+            """stmt : N attrs """
+            p[1].attr = p[2]
+            p[0] = p[1]
+
+        def p_stmt_EN(self, p):
+            """stmt : E
+                    | N """
+            p[0] = p[1]
+
+        def p_E(self, p):
+            """E : E   link
+                 | elt link """
+            try:
+                L = p[1]
+                L.append(Dot.edge(L[-1].n2, p[2]))
+            except Exception:
+                L = []
+                L.append(Dot.edge(p[1], p[2]))
+            p[0] = L
+
+        def p_elt(self, p):
+            """elt : N
+                   | sub """
+            p[0] = p[1]
+
+        def p_link(self, p):
+            """link : '-' '>' elt
+                    | '-' '-' elt """
+            p[0] = p[3]
+
+        def p_N_port(self, p):
+            """N : ID port """
+            p[0] = Dot.node(p[1], port=p[2])
+
+        def p_N(self, p):
+            """N : ID """
+            p[0] = Dot.node(p[1])
+
+        def p_port(self, p):
+            """port : ':' ID """
+            p[0] = p[2]
+
+        def p_port2(self, p):
+            """port : port port"""
+            assert p[2] in ["n", "ne", "e", "se", "s", "sw", "w", "nw", "c", "_"]
+            p[0] = "%s:%s" % (p[1], p[2])
+
+        def p_error(self, p):
+            print("Syntax Error: %s" % (p,))
+            self._parser.restart()
+
+        def build(self, **kargs):
+            opt = dict(debug=0, write_tables=0)
+            opt.update(**kargs)
+            if _has_ply:
+                self._parser = yacc.yacc(module=self, **opt)
+
+    def __init__(self, **kargs):
+        self.lexer = Dot.Lexer()
+        self.parser = Dot.Parser()
+        if not _has_ply:
+            print("warning: Dot parser not supported (install python-ply)")
+
+    def parse(self, data):
+        try:
+            self.parser._parser.restart()
+        except AttributeError:
+            self.lexer.build(reflags=lex.re.UNICODE)
+            self.parser.build()
+        except Exception:
+            print("unexpected error")
+            return None
+        try:
+            s = data.decode("utf-8")
+        except UnicodeDecodeError:
+            s = data
+        L = self.parser._parser.parse(s, lexer=self.lexer._lexer)
+        return L
+
+    def read(self, filename):
+        f = open(
+            filename, "rb"
+        )  # As it'll try to decode later on with utf-8, read it binary at this point.
+        return self.parse(f.read())

+ 214 - 0
grandalf/utils/geometry.py

@@ -0,0 +1,214 @@
+#!/usr/bin/env python
+#
+# This code is part of Grandalf
+# Copyright (C) 2008 Axel Tillequin (bdcht3@gmail.com) and others
+# published under GPLv2 license or EPLv1 license
+# Contributor(s): Axel Tillequin, Fabio Zadrozny
+from .poset import *
+from .dot import *
+
+from math import atan2, sqrt
+from random import SystemRandom
+
+# ------------------------------------------------------------------------------
+def intersect2lines(xy1, xy2, xy3, xy4):
+    (x1, y1) = xy1
+    (x2, y2) = xy2
+    (x3, y3) = xy3
+    (x4, y4) = xy4
+    b = (x2 - x1, y2 - y1)
+    d = (x4 - x3, y4 - y3)
+    det = b[0] * d[1] - b[1] * d[0]
+    if det == 0:
+        return None
+    c = (x3 - x1, y3 - y1)
+    t = float(c[0] * b[1] - c[1] * b[0]) / (det * 1.0)
+    if t < 0.0 or t > 1.0:
+        return None
+    t = float(c[0] * d[1] - c[1] * d[0]) / (det * 1.0)
+    if t < 0.0 or t > 1.0:
+        return None
+    x = x1 + t * b[0]
+    y = y1 + t * b[1]
+    return (x, y)
+
+
+# ------------------------------------------------------------------------------
+#  intersectR returns the intersection point between the Rectangle
+#  (w,h) that characterize the view object and the line that goes
+#  from the views' object center to the 'topt' point.
+def intersectR(view, topt):
+    # we compute intersection in local views' coord:
+    # center of view is obviously :
+    x1, y1 = 0, 0
+    # endpoint in view's coord:
+    x2, y2 = topt[0] - view.xy[0], topt[1] - view.xy[1]
+    # bounding box:
+    bbx2 = view.w // 2
+    bbx1 = -bbx2
+    bby2 = view.h // 2
+    bby1 = -bby2
+    # all 4 segments of the bb:
+    S = [
+        ((x1, y1), (x2, y2), (bbx1, bby1), (bbx2, bby1)),
+        ((x1, y1), (x2, y2), (bbx2, bby1), (bbx2, bby2)),
+        ((x1, y1), (x2, y2), (bbx1, bby2), (bbx2, bby2)),
+        ((x1, y1), (x2, y2), (bbx1, bby2), (bbx1, bby1)),
+    ]
+    # check intersection with each seg:
+    for segs in S:
+        xy = intersect2lines(*segs)
+        if xy != None:
+            x, y = xy
+            # return global coord:
+            x += view.xy[0]
+            y += view.xy[1]
+            return (x, y)
+    # there can't be no intersection unless the endpoint was
+    # inside the bb !
+    raise ValueError(
+        "no intersection found (point inside ?!). view: %s topt: %s" % (view, topt)
+    )
+
+
+# ------------------------------------------------------------------------------
+def getangle(p1, p2):
+    x1, y1 = p1
+    x2, y2 = p2
+    theta = atan2(y2 - y1, x2 - x1)
+    return theta
+
+
+# ------------------------------------------------------------------------------
+def median_wh(views):
+    mw = [v.w for v in views]
+    mh = [v.h for v in views]
+    mw.sort()
+    mh.sort()
+    return (mw[len(mw) // 2], mh[len(mh) // 2])
+
+
+# ------------------------------------------------------------------------------
+try:
+    from numpy import array, matrix, cos, sin
+
+    has_numpy = True
+except ImportError:
+    has_numpy = False
+    from math import cos, sin, pi
+    from .linalg import array, matrix
+
+#  rand_ortho1 returns a numpy.array representing
+#  a random normalized n-dimension vector orthogonal to (1,1,1,...,1).
+def rand_ortho1(n):
+    r = SystemRandom()
+    pos = [r.random() for x in range(n)]
+    s = sum(pos)
+    v = array(pos, dtype=float) - (s / float(n))
+    norm = sqrt(sum(v * v))
+    return v / norm
+
+
+# ------------------------------------------------------------------------------
+#  intersectC returns the intersection point between the Circle
+#  of radius r and centered on views' position with the line
+#  to the 'topt' point.
+def intersectC(view, r, topt):
+    theta = getangle(view.xy, topt)
+    x = int(cos(theta) * r)
+    y = int(sin(theta) * r)
+    return (x, y)
+
+
+# ------------------------------------------------------------------------------
+#  setcurve returns the spline curve that path through the list of points P.
+#  The spline curve is a list of cubic bezier curves (nurbs) that have
+#  matching tangents at their extreme points.
+#  The method considered here is taken from "The NURBS book" (Les A. Piegl,
+#  Wayne Tiller, Springer, 1997) and implements a local interpolation rather
+#  than a global interpolation.
+def setcurve(e, pts, tgs=None):
+    P = list(map(array, pts))
+    n = len(P)
+    # tangent estimation
+    if tgs:
+        assert len(tgs) == n
+        T = list(map(array, tgs))
+        Q = [P[k + 1] - P[k] for k in range(0, n - 1)]
+    else:
+        Q, T = tangents(P, n)
+    splines = []
+    for k in range(n - 1):
+        t = T[k] + T[k + 1]
+        a = 16.0 - (t.dot(t))
+        b = 12.0 * (Q[k].dot(t))
+        c = -36.0 * Q[k].dot(Q[k])
+        D = (b * b) - 4.0 * a * c
+        assert D >= 0
+        sd = sqrt(D)
+        s1, s2 = (-b - sd) / (2.0 * a), (-b + sd) / (2.0 * a)
+        s = s2
+        if s1 >= 0:
+            s = s1
+        C0 = tuple(P[k])
+        C1 = tuple(P[k] + (s / 3.0) * T[k])
+        C2 = tuple(P[k + 1] - (s / 3.0) * T[k + 1])
+        C3 = tuple(P[k + 1])
+        splines.append([C0, C1, C2, C3])
+    return splines
+
+
+# ------------------------------------------------------------------------------
+def tangents(P, n):
+    assert n >= 2
+    Q = []
+    T = []
+    for k in range(0, n - 1):
+        q = P[k + 1] - P[k]
+        t = q / sqrt(q.dot(q))
+        Q.append(q)
+        T.append(t)
+    T.append(t)
+    return (Q, T)
+
+
+# ------------------------------------------------------------------------------
+def setroundcorner(e, pts):
+    P = list(map(array, pts))
+    n = len(P)
+    Q, T = tangents(P, n)
+    c0 = P[0]
+    t0 = T[0]
+    k0 = 0
+    splines = []
+    k = 1
+    while k < n:
+        z = abs(t0[0] * T[k][1] - (t0[1] * T[k][0]))
+        if z < 1.0e-6:
+            k += 1
+            continue
+        if (k - 1) > k0:
+            splines.append([c0, P[k - 1]])
+        if (k + 1) < n:
+            splines.extend(setcurve(e, [P[k - 1], P[k + 1]], tgs=[T[k - 1], T[k + 1]]))
+        else:
+            splines.extend(setcurve(e, [P[k - 1], P[k]], tgs=[T[k - 1], T[k]]))
+            break
+        if (k + 2) < n:
+            c0 = P[k + 1]
+            t0 = T[k + 1]
+            k0 = k + 1
+            k += 2
+        else:
+            break
+    return splines or [[P[0], P[-1]]]
+
+
+# ------------------------------------------------------------------------------
+def new_point_at_distance(pt, distance, angle):
+    # angle in radians
+    distance = float(distance)
+    x, y = pt[0], pt[1]
+    x += distance * cos(angle)
+    y += distance * sin(angle)
+    return x, y

+ 319 - 0
grandalf/utils/linalg.py

@@ -0,0 +1,319 @@
+from math import sqrt
+from array import array as _array
+
+constants = (int, float)
+
+
+def coerce_(types):
+    if types is None:
+        types = []
+    if str in types:
+        raise TypeError
+    if complex in types:
+        raise TypeError
+    dtype = ("i", int)
+    if float in types:
+        dtype = ("d", float)
+    return dtype
+
+
+def _mkslice(i, n):
+    if not isinstance(i, slice):
+        i = slice(i, i + 1, 1)
+    start, stop, stride = i.indices(n)
+    return slice(start, stop, stride)
+
+
+def make_ij_slices(f):
+    def wrapper(self, ij, *args):
+        # I = slice(0,self.n,1)
+        J = slice(0, self.p, 1)
+        if isinstance(ij, tuple):
+            I = _mkslice(ij[0], self.n)
+            J = _mkslice(ij[1], self.p)
+        else:
+            I = _mkslice(ij, self.n)
+        return f(self, (I, J), *args)
+
+    return wrapper
+
+
+# minimalistic numpy.array replacement class used as fallback
+# when numpy is not found in geometry module
+class array(object):
+    def __init__(self, data, dtype=None, copy=True):
+        self.dim = len(data)
+        types = None
+        if self.dim > 0:
+            types = set([type(x) for x in data])
+        if dtype is not None:
+            types = (dtype,)
+        tc, self.dtype = coerce_(types)
+        data = [self.dtype(x) for x in data]
+        if copy is True:
+            self.data = _array(tc, data)
+        else:
+            raise NotImplementedError
+
+    def coerce(self, dtype):
+        data = [dtype(x) for x in self.data]
+        tc, dtype = coerce_((dtype,))
+        self.data = _array(tc, data)
+        self.dtype = dtype
+
+    @property
+    def typecode(self):
+        return self.data.typecode
+
+    def __len__(self):
+        return self.dim
+
+    def __str__(self):
+        s = " ".join(("%.12s" % x).ljust(12) for x in self)
+        return "[%s]" % s.strip()
+
+    def copy(self):
+        return array(self.data, self.dtype)
+
+    def __add__(self, v):
+        if isinstance(v, constants):
+            v = array([v] * self.dim)
+        assert v.dim == self.dim
+        return array([x + y for (x, y) in zip(self.data, v.data)])
+
+    def __sub__(self, v):
+        if isinstance(v, constants):
+            v = array([v] * self.dim)
+        assert v.dim == self.dim
+        return array([x - y for (x, y) in zip(self.data, v.data)])
+
+    def __neg__(self):
+        return array([-x for x in self.data], dtype=self.dtype)
+
+    def __radd__(self, v):
+        return self + v
+
+    def __rsub__(self, v):
+        return (-self) + v
+
+    def dot(self, v):
+        assert v.dim == self.dim
+        return sum([x * y for (x, y) in zip(self.data, v.data)])
+
+    def __rmul__(self, k):
+        return array([k * x for x in self.data])
+
+    def __mul__(self, v):
+        if isinstance(v, constants):
+            v = array([v] * self.dim)
+        assert v.dim == self.dim
+        return array([x * y for (x, y) in zip(self.data, v.data)])
+
+    def __truediv__(self, v):
+        if isinstance(v, constants):
+            v = array([v] * self.dim)
+        assert v.dim == self.dim
+        return array([x / y for (x, y) in zip(self.data, v.data)])
+
+    __div__ = __truediv__
+
+    def __rtruediv__(self, v):
+        if isinstance(v, constants):
+            v = array([v] * self.dim)
+        assert v.dim == self.dim
+        return array([x / y for (x, y) in zip(v.data, self.data)])
+
+    __rdiv__ = __rtruediv__
+
+    def __floordiv__(self, v):
+        if isinstance(v, constants):
+            v = array([v] * self.dim)
+        assert v.dim == self.dim
+        return array([x // y for (x, y) in zip(self.data, v.data)])
+
+    def __rfloordiv__(self, v):
+        if isinstance(v, constants):
+            v = array([v] * self.dim)
+        assert v.dim == self.dim
+        return array([x // y for (x, y) in zip(v.data, self.data)])
+
+    def norm(self):
+        return sqrt(self.dot(self))
+
+    def max(self):
+        return max(self.data)
+
+    def min(self):
+        return min(self.data)
+
+    def __iter__(self):
+        for x in self.data:
+            yield x
+
+    def __setitem__(self, i, v):
+        assert isinstance(i, int)
+        self.data[i] = self.dtype(v)
+
+    def __getitem__(self, i):
+        i = _mkslice(i, self.dim)
+        res = self.data[i]
+        if len(res) == 1:
+            return res[0]
+        return array(res)
+
+    def transpose(self):
+        return matrix(self.data, self.dtype)
+
+    def __float__(self):
+        assert self.dim == 1
+        return float(self.data[0])
+
+
+# ------------------------------------------------------------------------------
+# minimalistic numpy.matrix replacement class used as fallback
+# when numpy is not found in geometry module
+class matrix(object):
+    def __init__(self, data, dtype=None, copy=True, transpose=False):
+        # check input data types:
+        types = set([type(v) for v in data])
+        if len(types) > 1:
+            raise TypeError
+        t = types.pop()
+        # import data:
+        if t in constants:
+            self.data = [array(data, dtype, copy)]
+        else:
+            if transpose:
+                data = zip(*data)
+            self.data = [array(v, dtype, copy) for v in data]
+        # define matrix sizes:
+        self.n = len(self.data)
+        sizes = set([len(v) for v in self.data])
+        if len(sizes) > 1:
+            raise ValueError
+        self.p = sizes.pop()
+        if dtype is None:
+            # coerce types of arrays of matrix:
+            types = set([v.dtype for v in self.data])
+            tc, dtype = coerce_(types)
+            for v in self.data:
+                v.coerce(dtype)
+        self.dtype = dtype
+
+    def __len__(self):
+        return self.n * self.p
+
+    def __str__(self):
+        s = "\n ".join([str(v) for v in self.data])
+        return "[%s]" % s.strip()
+
+    @property
+    def shape(self):
+        return (self.n, self.p)
+
+    def lvecs(self):
+        return self.data
+
+    def cvecs(self):
+        return [array(v, self.dtype) for v in zip(*self.data)]
+
+    def copy(self):
+        return matrix(self.data, self.dtype)
+
+    def transpose(self):
+        return matrix(self.data, dtype=self.dtype, transpose=True)
+
+    def sum(self):
+        return sum([sum(v) for v in self.data])
+
+    @make_ij_slices
+    def __getitem__(self, ij):
+        I, J = ij
+        l = self.lvecs()[I]
+        m = matrix([v[J] for v in l])
+        if m.n == 1:
+            v = m.data[0]
+            if v.dim == 1:
+                return v[0]
+            if len(l) > 1:
+                return v
+        return m
+
+    @make_ij_slices
+    def __setitem__(self, ij, v):
+        I, J = ij
+        Ri = range(I.start, I.stop, I.step)
+        Rj = range(J.start, J.stop, J.step)
+        if type(v) in constants:
+            v = (v,)
+        value = (x for x in v)
+        for i in Ri:
+            for j in Rj:
+                self.data[i][j] = next(value)
+
+    def __add__(self, m):
+        if isinstance(m, constants):
+            return matrix([u + m for u in self.data])
+        else:
+            assert self.shape == m.shape
+            return matrix([u + v for (u, v) in zip(self.data, m.data)])
+
+    def __sub__(self, m):
+        if isinstance(m, constants):
+            return matrix([u - m for u in self.data])
+        else:
+            assert self.shape == m.shape
+            return matrix([u - v for (u, v) in zip(self.data, m.data)])
+
+    def __neg__(self):
+        return matrix([-x for x in self.data], dtype=self.dtype)
+
+    def __float__(self):
+        assert self.n == 1 and self.p == 1
+        return self[0, 0]
+
+    def __radd__(self, v):
+        return self + v
+
+    def __rsub__(self, v):
+        return (-self) + v
+
+    def __rmul__(self, k):
+        if not isinstance(k, constants):
+            raise TypeError
+        return matrix([k * v for v in self.data])
+
+    def __mul__(self, X):
+        if isinstance(X, constants):
+            return X * self
+        if isinstance(X, array):
+            assert X.dim == self.p
+            return array([v.dot(X) for v in self.data])
+        if isinstance(X, matrix):
+            assert X.n == self.p
+            return matrix([self * v for v in X.cvecs()])
+
+    def __pow__(self, v):
+        S = [self] * v
+        assert len(S) > 0
+        return reduce(lambda x, y: x * y, S)
+
+    def __iter__(self):
+        for l in self.data:
+            for v in l:
+                yield v
+
+
+class SimplexMin(object):
+    def __init__(self, A, b, c):
+        self.A = A
+        self.b = b
+        self.c = c
+        self.tableau()
+
+    def tableau(self):
+        self.T = []
+
+    def setup(self):
+        self.enter = []
+        delf.outer = []

+ 39 - 0
grandalf/utils/nx.py

@@ -0,0 +1,39 @@
+# This code is part of Grandalf
+#  Copyright (C) 2014 Axel Tillequin (bdcht3@gmail.com) and others
+# published under GPLv2 license or EPLv1 license
+# Contributor(s): Fabio Zadrozny
+
+__all__ = [
+    "convert_grandalf_graph_to_networkx_graph",
+    "convert_nextworkx_graph_to_grandalf",
+]
+
+# Some utilities to interact with networkx.
+
+# Converts a grandalf graph to a networkx graph.
+# Note that the edge concept is the same, but a vertex in grandalf is called a node in networkx.
+def convert_grandalf_graph_to_networkx_graph(G):
+    from networkx import MultiDiGraph
+
+    nxg = MultiDiGraph()
+    for v in G.V():
+        nxg.add_node(v.data)
+    for e in G.E():
+        nxg.add_edge(e.v[0].data, e.v[1].data)
+    return nxg
+
+
+# Converts a networkx graph to a grandalf graph.
+# Note that the edge concept is the same, but a vertex in grandalf is called a node in networkx.
+def convert_nextworkx_graph_to_grandalf(G):
+    from grandalf.graphs import Graph, Vertex, Edge
+
+    V = []
+    data_to_V = {}
+    for x in G.nodes():
+        vertex = Vertex(x)
+        V.append(vertex)
+        data_to_V[x] = vertex
+    E = [Edge(data_to_V[xy[0]], data_to_V[xy[1]], data=xy) for xy in G.edges()]
+    g = Graph(V, E)
+    return g

+ 155 - 0
grandalf/utils/poset.py

@@ -0,0 +1,155 @@
+# This code is part of Grandalf
+# Copyright (C) 2008-2015 Axel Tillequin (bdcht3@gmail.com) and others
+# published under GPLv2 license or EPLv1 license
+# Contributor(s): Axel Tillequin
+
+from collections import OrderedDict
+
+__all__ = ["Poset"]
+
+# ------------------------------------------------------------------------------
+# Poset class implements a set but allows to interate over the elements in a
+# deterministic way and to get specific objects in the set.
+# Membership operator defaults to comparing __hash__  of objects but Poset
+# allows to check for __cmp__/__eq__ membership by using contains__cmp__(obj)
+class Poset(object):
+    def __init__(self, L):
+        self.o = OrderedDict()
+        for obj in L:
+            self.add(obj)
+
+    def __repr__(self):
+        return "Poset(%r)" % (self.o,)
+
+    def __str__(self):
+        f = "%%%dd" % len(str(len(self.o)))
+        s = []
+        for i, x in enumerate(self.o.values()):
+            s.append(f % i + ".| %s" % repr(x))
+        return "\n".join(s)
+
+    def add(self, obj):
+        if obj in self:
+            return self.get(obj)
+        else:
+            self.o[obj] = obj
+            return obj
+
+    def remove(self, obj):
+        if obj in self:
+            obj = self.get(obj)
+            del self.o[obj]
+            return obj
+        return None
+
+    def index(self, obj):
+        return list(self.o.values()).index(obj)
+
+    def get(self, obj):
+        return self.o.get(obj, None)
+
+    def __getitem__(self, i):
+        return list(self.o.values())[i]
+
+    def __len__(self):
+        return len(self.o)
+
+    def __iter__(self):
+        for obj in iter(self.o.values()):
+            yield obj
+
+    def __cmp__(self, other):
+        s1 = set(other.o.values())
+        s2 = set(self.o.values())
+        return cmp(s1, s2)
+
+    def __eq__(self, other):
+        s1 = set(other.o.values())
+        s2 = set(self.o.values())
+        return s1 == s2
+
+    def __ne__(self, other):
+        s1 = set(other.o.values())
+        s2 = set(self.o.values())
+        return s1 != s2
+
+    def copy(self):
+        return Poset(self.o.values())
+
+    __copy__ = copy
+
+    def deepcopy(self):
+        from copy import deepcopy
+
+        L = deepcopy(self.o.values())
+        return Poset(L)
+
+    def __or__(self, other):
+        return self.union(other)
+
+    def union(self, other):
+        p = Poset([])
+        p.o.update(self.o)
+        p.o.update(other.o)
+        return p
+
+    def update(self, other):
+        self.o.update(other.o)
+
+    def __and__(self, other):
+        s1 = set(self.o.values())
+        s2 = set(other.o.values())
+        return Poset(s1.intersection(s2))
+
+    def intersection(self, *args):
+        p = self
+        for other in args:
+            p = p & other
+        return p
+
+    def __xor__(self, other):
+        s1 = set(self.o.values())
+        s2 = set(other.o.values())
+        return Poset(s1.symmetric_difference(s2))
+
+    def symmetric_difference(self, *args):
+        p = self
+        for other in args:
+            p = p ^ other
+        return p
+
+    def __sub__(self, other):
+        s1 = set(self.o.values())
+        s2 = set(other.o.values())
+        return Poset(s1.difference(s2))
+
+    def difference(self, *args):
+        p = self
+        for other in args:
+            p = p - other
+        return p
+
+    def __contains__(self, obj):
+        return obj in self.o
+
+    def contains__cmp__(self, obj):
+        return obj in self.o.values()
+
+    def issubset(self, other):
+        s1 = set(self.o.values())
+        s2 = set(other.o.values())
+        return s1.issubset(s2)
+
+    def issuperset(self, other):
+        s1 = set(self.o.values())
+        s2 = set(other.o.values())
+        return s1.issuperset(s2)
+
+    __le__ = issubset
+    __ge__ = issuperset
+
+    def __lt__(self, other):
+        return self <= other and len(self) != len(other)
+
+    def __gt__(self, other):
+        return self >= other and len(self) != len(other)

+ 71 - 0
internal_containers.py

@@ -0,0 +1,71 @@
+from .node_container_common import *
+from bpy.types import Node
+from .base_definitions import MantisNode
+
+
+class DummyNode:
+    def __init__(self, signature, base_tree, prototype = None):
+        self.signature = signature
+        self.base_tree = base_tree
+        self.prototype = prototype
+        self.inputs={}
+        self.outputs={}
+        self.parameters = {}
+        self.node_type = 'DUMMY'
+        if prototype:
+            for sock in prototype.inputs:
+                if sock.name == "__extend__":
+                    continue
+                self.inputs[sock.identifier] = NodeSocket(is_input = True, name = sock.identifier, node = self)
+            for sock in prototype.outputs:
+                if sock.name == "__extend__":
+                    continue
+                self.outputs[sock.identifier] = NodeSocket(is_input = False, name = sock.identifier, node = self)
+                self.parameters[sock.identifier]=None
+
+    def evaluate_input(self, input_name):
+        pass
+        # return evaluate_input(self, input_name)
+    
+    def bExecute(self, bContext = None,):
+        pass
+        
+    def bFinalize(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self,):
+        pass# fill_parameters(self)
+        # I don't think I am using this but it doesn't hurt
+
+
+# a class for duplicating an existing node for e.g. temporary
+#  traces
+class DupeNode:
+    def __init__(self, signature, base_tree):
+        self.signature = signature
+        self.base_tree = base_tree
+        self.prototype = prototype
+        self.inputs={}
+        self.outputs={}
+        self.parameters = {}
+        self.node_type = 'DUMMY'
+
+    def evaluate_input(self, input_name):
+        pass
+        # return evaluate_input(self, input_name)
+    
+    def bExecute(self, bContext = None,):
+        pass
+        
+    def bFinalize(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self,):
+        fill_parameters(self)
+        # I don't think I am using this but it doesn't hurt

+ 1601 - 0
link_containers.py

@@ -0,0 +1,1601 @@
+from mantis.node_container_common import *
+from bpy.types import Node
+from .base_definitions import MantisNode, GraphError
+
+#TODO: get rid of this, it's unnecesary here, we always want to import
+#  all classes in this file
+def TellClasses():
+    return [
+             # special
+             LinkInherit,
+             # copy
+             LinkCopyLocation,
+             LinkCopyRotation,
+             LinkCopyScale,
+             LinkCopyTransforms,
+             LinkTransformation,
+             # limit
+             LinkLimitLocation,
+             LinkLimitRotation,
+             LinkLimitScale,
+             LinkLimitDistance,
+             # tracking
+             LinkStretchTo,
+             LinkDampedTrack,
+             LinkLockedTrack,
+             LinkTrackTo,
+             #misc
+             LinkInheritConstraint,
+             LinkArmature,
+             # IK
+             LinkInverseKinematics,
+             LinkSplineIK,
+             # Drivers
+             LinkDrivenParameter,
+            ]
+
+
+
+def default_evaluate_input(nc, input_name):
+    # should catch 'Target', 'Pole Target' and ArmatureConstraint targets, too
+    if ('Target' in input_name) and input_name != "Target Space":
+        socket = nc.inputs.get(input_name)
+        if socket.is_linked:
+            return socket.links[0].from_node
+        return None
+        
+    else:
+        return evaluate_input(nc, input_name)
+
+
+
+#*#-------------------------------#++#-------------------------------#*#
+# L I N K   N O D E S
+#*#-------------------------------#++#-------------------------------#*#
+
+def GetxForm(nc):
+    trace = trace_single_line_up(nc, "Output Relationship")
+    for node in trace[0]:
+        if (node.node_type == 'XFORM'):
+            return node
+    raise GraphError("%s is not connected to a downstream xForm" % nc)
+
+class LinkInherit:
+    '''A node representing inheritance'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+         "Parent"           : NodeSocket(is_input = True, name = "Parent", node = self,),
+         # bone only:
+         "Inherit Rotation" : NodeSocket(is_input = True, name = "Inherit Rotation", node = self,),
+         "Inherit Scale"    : NodeSocket(is_input = True, name = "Inherit Scale", node = self,),
+         "Connected"        : NodeSocket(is_input = True, name = "Connected", node = self,),
+        }
+        self.outputs = { "Inheritance" : NodeSocket(name = "Inheritance", node = self) }
+        self.parameters = {
+         "Parent":None,
+         # bone only:
+         "Inherit Rotation":None,
+         "Inherit Scale":None,
+         "Connected":None,
+        }
+        self.links = {} # leave this empty for now!
+        # now set up the traverse target...
+        self.inputs["Parent"].set_traverse_target(self.outputs["Inheritance"])
+        self.outputs["Inheritance"].set_traverse_target(self.inputs["Parent"])
+        self.node_type = 'LINK'
+    
+        
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+        
+    def GetxForm(self): # DUPLICATED, TODO fix this
+        trace = trace_single_line_up(self, "Inheritance")
+        for node in trace[0]:
+            if (node.node_type == 'XFORM'):
+                return node
+        raise GraphError("%s is not connected to a downstream xForm" % self)
+    
+    def bExecute(self, bContext = None,):
+        # this is handled by the xForm objects, since it isn't really
+        #  a constraint.
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+
+class LinkCopyLocation:
+    '''A node representing Copy Location'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+            "Head/Tail"          : NodeSocket(is_input = True, name = "Head/Tail", node = self,),
+            "UseBBone"           : NodeSocket(is_input = True, name = "UseBBone", node = self,),
+            "Axes"               : NodeSocket(is_input = True, name = "Axes", node = self,),
+            "Invert"             : NodeSocket(is_input = True, name = "Invert", node = self,),
+            "Target Space"       : NodeSocket(is_input = True, name = "Target Space", node = self,),
+            "Owner Space"        : NodeSocket(is_input = True, name = "Owner Space", node = self,),
+            "Influence"          : NodeSocket(is_input = True, name = "Influence", node = self,),
+            "Target"             : NodeSocket(is_input = True, name = "Target", node = self,),
+            "Enable"             : NodeSocket(is_input = True, name = "Enable", node = self,), }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(name = "Output Relationship", node=self) }
+        self.parameters = {
+            "Name":None,
+            "Input Relationship":None,
+            "Head/Tail":None,
+            "UseBBone":None,
+            "Axes":None,
+            "Invert":None,
+            "Target Space":None,
+            "Owner Space":None,
+            "Influence":None,
+            "Target":None,
+            "Enable":None, }
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+        
+        
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+    
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def bExecute(self, context):
+        prepare_parameters(self)
+        c = self.GetxForm().bGetObject().constraints.new('COPY_LOCATION')
+        get_target_and_subtarget(self, c)
+        print(wrapGreen("Creating ")+wrapWhite("Copy Location")+
+             wrapGreen(" Constraint for bone: ") +
+             wrapOrange(self.GetxForm().bGetObject().name))
+        c.name = self.evaluate_input("Name")
+        self.bObject = c
+        props_sockets = {
+        'head_tail'       : ("Head/Tail", 0),
+        'use_bbone_shape' : ("UseBBone", False),
+        'invert_x'        : ( ("Invert", 0), False),
+        'invert_y'        : ( ("Invert", 1), False),
+        'invert_z'        : ( ("Invert", 2), False),
+        'use_x'           : ( ("Axes", 0), False),
+        'use_y'           : ( ("Axes", 1), False),
+        'use_z'           : ( ("Axes", 2), False),
+        'owner_space'     : ("Owner Space",  'WORLD'),
+        'target_space'    : ("Target Space", 'WORLD'),
+        'influence'       : ("Influence", 1),
+        'mute'            : ("Enable", True),
+        }
+        evaluate_sockets(self, c, props_sockets)    
+        
+    def bFinalize(self, bContext = None):
+        finish_drivers(self)
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+        
+
+class LinkCopyRotation:
+    '''A node representing Copy Rotation'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+            "RotationOrder"      : NodeSocket(is_input = True, name = "RotationOrder", node = self,),
+            "Rotation Mix"       : NodeSocket(is_input = True, name = "Rotation Mix", node = self,),
+            "Axes"               : NodeSocket(is_input = True, name = "Axes", node = self,),
+            "Invert"             : NodeSocket(is_input = True, name = "Invert", node = self,),
+            "Target Space"       : NodeSocket(is_input = True, name = "Target Space", node = self,),
+            "Owner Space"        : NodeSocket(is_input = True, name = "Owner Space", node = self,),
+            "Influence"          : NodeSocket(is_input = True, name = "Influence", node = self,),
+            "Target"             : NodeSocket(is_input = True, name = "Target", node = self,),
+            "Enable"             : NodeSocket(is_input = True, name = "Enable", node = self,),  }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(name = "Output Relationship", node=self) }
+        self.parameters = {
+            "Name":None,
+            "Input Relationship":None,
+            "RotationOrder":None,
+            "Rotation Mix":None,
+            "Axes":None,
+            "Invert":None,
+            "Target Space":None,
+            "Owner Space":None,
+            "Influence":None,
+            "Target":None,
+            "Enable":None, }
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+    
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def bExecute(self, context):
+        prepare_parameters(self)
+        c = self.GetxForm().bGetObject().constraints.new('COPY_ROTATION')
+        get_target_and_subtarget(self, c)
+        print(wrapGreen("Creating ")+wrapWhite("Copy Rotation")+
+             wrapGreen(" Constraint for bone: ") +
+             wrapOrange(self.GetxForm().bGetObject().name))
+        if self.parameters["Enable"]:
+            c.enabled = True; c.mute = False
+        
+        rotation_order = self.evaluate_input("RotationOrder")
+        if ((rotation_order == 'QUATERNION') or (rotation_order == 'AXIS_ANGLE')):
+            c.euler_order = 'AUTO'
+        else:
+            try:
+                c.euler_order = rotation_order
+            except TypeError: # it's a driver or incorrect
+                c.euler_order = 'AUTO'
+        c.name = self.evaluate_input("Name")
+        self.bObject = c
+        props_sockets = {
+        'euler_order' : ("RotationOrder", 'AUTO'),
+        'mix_mode'       : ("Rotation Mix", 'REPLACE'),
+        'invert_x'       : ( ("Invert", 0), False),
+        'invert_y'       : ( ("Invert", 1), False),
+        'invert_z'       : ( ("Invert", 2), False),
+        'use_x'          : ( ("Axes", 0), False),
+        'use_y'          : ( ("Axes", 1), False),
+        'use_z'          : ( ("Axes", 2), False),
+        'owner_space'    : ("Owner Space",  'WORLD'),
+        'target_space'   : ("Target Space", 'WORLD'),
+        'influence'      : ("Influence", 1),
+        'mute'            : ("Enable", True),
+        }
+        evaluate_sockets(self, c, props_sockets)    
+            
+    def bFinalize(self, bContext = None):
+        finish_drivers(self)
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+        
+class LinkCopyScale:
+    '''A node representing Copy Scale'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+            "Offset"             : NodeSocket(is_input = True, name = "Offset", node = self,),
+            "Average"            : NodeSocket(is_input = True, name = "Average", node = self,),
+            "Additive"           : NodeSocket(is_input = True, name = "Additive", node = self,),
+            "Axes"               : NodeSocket(is_input = True, name = "Axes", node = self,),
+            #"Invert"             : NodeSocket(is_input = True, name = "Invert", node = self,),
+            "Target Space"       : NodeSocket(is_input = True, name = "Target Space", node = self,),
+            "Owner Space"        : NodeSocket(is_input = True, name = "Owner Space", node = self,),
+            "Influence"          : NodeSocket(is_input = True, name = "Influence", node = self,),
+            "Target"             : NodeSocket(is_input = True, name = "Target", node = self,),
+            "Enable"             : NodeSocket(is_input = True, name = "Enable", node = self,), }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(name = "Output Relationship", node=self) }
+        self.parameters = {
+            "Name":None,
+            "Input Relationship":None,
+            "Offset":None,
+            "Average":None,
+            "Axes":None,
+            #"Invert":None,
+            "Target Space":None,
+            "Owner Space":None,
+            "Influence":None,
+            "Target":None,
+            "Enable":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+    
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def bExecute(self, context):
+        prepare_parameters(self)
+        c = self.GetxForm().bGetObject().constraints.new('COPY_SCALE')
+        get_target_and_subtarget(self, c)
+        print(wrapGreen("Creating ")+wrapWhite("Copy Scale")+
+             wrapGreen(" Constraint for bone: ") +
+             wrapOrange(self.GetxForm().bGetObject().name))
+        c.name = self.evaluate_input("Name")
+        self.bObject = c
+        props_sockets = {
+        'use_offset'       : ("Offset", False),
+        'use_make_uniform' : ("Average", False),
+        'owner_space'      : ("Owner Space",  'WORLD'),
+        'target_space'     : ("Target Space", 'WORLD'),
+        'use_x'            : ( ("Axes", 0), False),
+        'use_y'            : ( ("Axes", 1), False),
+        'use_z'            : ( ("Axes", 2), False),
+        'influence'        : ("Influence", 1),
+        'mute'             : ("Enable", True),
+        }
+        evaluate_sockets(self, c, props_sockets)    
+            
+    def bFinalize(self, bContext = None):
+        finish_drivers(self)
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+class LinkCopyTransforms:
+    '''A node representing Copy Transfoms'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+            "Head/Tail"          : NodeSocket(is_input = True, name = "Head/Tail", node = self,),
+            "UseBBone"           : NodeSocket(is_input = True, name = "UseBBone", node = self,),
+            "Additive"           : NodeSocket(is_input = True, name = "Additive", node = self,),
+            "Mix"                : NodeSocket(is_input = True, name = "Mix", node = self,),
+            "Target Space"       : NodeSocket(is_input = True, name = "Target Space", node = self,),
+            "Owner Space"        : NodeSocket(is_input = True, name = "Owner Space", node = self,),
+            "Influence"          : NodeSocket(is_input = True, name = "Influence", node = self,),
+            "Target"             : NodeSocket(is_input = True, name = "Target", node = self,),
+            "Enable"             : NodeSocket(is_input = True, name = "Enable", node = self,),  }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(name = "Output Relationship", node=self) }
+        self.parameters = {
+            "Name":None,
+            "Input Relationship":None,
+            "Head/Tail":None,
+            "UseBBone":None,
+            "Mix":None,
+            "Target Space":None,
+            "Owner Space":None,
+            "Influence":None,
+            "Target":None,
+            "Enable":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+    
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def bExecute(self, context):
+        prepare_parameters(self)
+        c = self.GetxForm().bGetObject().constraints.new('COPY_TRANSFORMS')
+        get_target_and_subtarget(self, c)
+        print(wrapGreen("Creating ")+wrapWhite("Copy Transforms")+
+             wrapGreen(" Constraint for bone: ") +
+             wrapOrange(self.GetxForm().bGetObject().name))
+        c.name = self.evaluate_input("Name")
+        self.bObject = c
+        props_sockets = {
+        'head_tail'       : ("Head/Tail", 0),
+        'use_bbone_shape' : ("UseBBone", False),
+        'mix_mode'        : ("Mix", 'REPLACE'),
+        'owner_space'     : ("Owner Space",  'WORLD'),
+        'target_space'    : ("Target Space", 'WORLD'),
+        'influence'       : ("Influence", 1),
+        'mute'            :  ("Enable", False)
+        }
+        evaluate_sockets(self, c, props_sockets)        
+    def bFinalize(self, bContext = None):
+        finish_drivers(self)
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+
+transformation_props_sockets = {
+            'use_motion_extrapolate' : ("Extrapolate", False),
+            'map_from'               : ("Map From", 'LOCATION'),
+            'from_rotation_mode'     : ("Rotation Mode", 'AUTO'),
+            'from_min_x'             : ("X Min From", 0.0),
+            'from_max_x'             : ("X Max From", 0.0),
+            'from_min_y'             : ("Y Min From", 0.0),
+            'from_max_y'             : ("Y Max From", 0.0),
+            'from_min_z'             : ("Z Min From", 0.0),
+            'from_max_z'             : ("Z Max From", 0.0),
+            'from_min_x_rot'         : ("X Min From", 0.0),
+            'from_max_x_rot'         : ("X Max From", 0.0),
+            'from_min_y_rot'         : ("Y Min From", 0.0),
+            'from_max_y_rot'         : ("Y Max From", 0.0),
+            'from_min_z_rot'         : ("Z Min From", 0.0),
+            'from_max_z_rot'         : ("Z Max From", 0.0),
+            'from_min_x_scale'       : ("X Min From", 0.0),
+            'from_max_x_scale'       : ("X Max From", 0.0),
+            'from_min_y_scale'       : ("Y Min From", 0.0),
+            'from_max_y_scale'       : ("Y Max From", 0.0),
+            'from_min_z_scale'       : ("Z Min From", 0.0),
+            'from_max_z_scale'       : ("Z Max From", 0.0),
+            'map_to'                 : ("Map To", "LOCATION"),
+            'map_to_x_from'          : ("X Source Axis", "X"),
+            'map_to_y_from'          : ("Y Source Axis", "Y"),
+            'map_to_z_from'          : ("Z Source Axis", "Z"),
+            'to_min_x'               : ("X Min To", 0.0),
+            'to_max_x'               : ("X Max To", 0.0),
+            'to_min_y'               : ("Y Min To", 0.0),
+            'to_max_y'               : ("Y Max To", 0.0),
+            'to_min_z'               : ("Z Min To", 0.0),
+            'to_max_z'               : ("Z Max To", 0.0),
+            'to_min_x_rot'           : ("X Min To", 0.0),
+            'to_max_x_rot'           : ("X Max To", 0.0),
+            'to_min_y_rot'           : ("Y Min To", 0.0),
+            'to_max_y_rot'           : ("Y Max To", 0.0),
+            'to_min_z_rot'           : ("Z Min To", 0.0),
+            'to_max_z_rot'           : ("Z Max To", 0.0),
+            'to_min_x_scale'         : ("X Min To", 0.0),
+            'to_max_x_scale'         : ("X Max To", 0.0),
+            'to_min_y_scale'         : ("Y Min To", 0.0),
+            'to_max_y_scale'         : ("Y Max To", 0.0),
+            'to_min_z_scale'         : ("Z Min To", 0.0),
+            'to_max_z_scale'         : ("Z Max To", 0.0),
+            'to_euler_order'         : ("Rotation Mode", "AUTO"),
+            'mix_mode'               : ("Mix Mode (Translation)", "ADD"),
+            'mix_mode_rot'           : ("Mix Mode (Rotation)", "ADD"),
+            'mix_mode_scale'         : ("Mix Mode (Scale)", "MULTIPLY"),
+            'owner_space'            : ("Owner Space",  'WORLD'),
+            'target_space'           : ("Target Space", 'WORLD'),
+            'influence'              : ("Influence", 1),
+            'mute'                   : ("Enable", False),
+        }
+
+class LinkTransformation:
+    '''A node representing Copy Transfoms'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship"     : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+            "Target Space"           : NodeSocket(is_input = True, name = "Target Space", node = self,),
+            "Owner Space"            : NodeSocket(is_input = True, name = "Owner Space", node = self,),
+            "Influence"              : NodeSocket(is_input = True, name = "Influence", node = self,),
+            "Target"                 : NodeSocket(is_input = True, name = "Target", node = self,),
+            "Enable"                 : NodeSocket(is_input = True, name = "Enable", node = self,),  
+            "Extrapolate"            : NodeSocket(is_input = True, name = "Extrapolate", node = self,),  
+            "Map From"               : NodeSocket(is_input = True, name = "Map From", node = self,),
+            "Rotation Mode"          : NodeSocket(is_input = True, name = "Rotation Mode", node = self,),
+            "X Min From"             : NodeSocket(is_input = True, name = "X Min From", node = self,),
+            "X Max From"             : NodeSocket(is_input = True, name = "X Max From", node = self,),
+            "Y Min From"             : NodeSocket(is_input = True, name = "Y Min From", node = self,),
+            "Y Max From"             : NodeSocket(is_input = True, name = "Y Max From", node = self,),
+            "Z Min From"             : NodeSocket(is_input = True, name = "Z Min From", node = self,),
+            "Z Max From"             : NodeSocket(is_input = True, name = "Z Max From", node = self,),
+            "Map To"                 : NodeSocket(is_input = True, name = "Map To", node = self,),
+            "X Source Axis"          : NodeSocket(is_input = True, name = "X Source Axis", node = self,),
+            "X Min To"               : NodeSocket(is_input = True, name = "X Min To", node = self,),
+            "X Max To"               : NodeSocket(is_input = True, name = "X Max To", node = self,),
+            "Y Source Axis"          : NodeSocket(is_input = True, name = "Y Source Axis", node = self,),
+            "Y Min To"               : NodeSocket(is_input = True, name = "Y Min To", node = self,),
+            "Y Max To"               : NodeSocket(is_input = True, name = "Y Max To", node = self,),
+            "Z Source Axis"          : NodeSocket(is_input = True, name = "Z Source Axis", node = self,),
+            "Z Min To"               : NodeSocket(is_input = True, name = "Z Min To", node = self,),
+            "Z Max To"               : NodeSocket(is_input = True, name = "Z Max To", node = self,),
+            "Rotation Mode"          : NodeSocket(is_input = True, name = "Rotation Mode", node = self,),
+            "Mix Mode (Translation)" : NodeSocket(is_input = True, name = "Mix Mode (Translation)", node = self,),
+            "Mix Mode (Rotation)"    : NodeSocket(is_input = True, name = "Mix Mode (Rotation)", node = self,),
+            "Mix Mode (Scale)"       : NodeSocket(is_input = True, name = "Mix Mode (Scale)", node = self,),
+            }
+        self.outputs = {
+            "Output Relationship"    : NodeSocket(name = "Output Relationship", node=self) }
+        self.parameters = {
+            "Name"                   : None,
+            "Input Relationship"     : None,
+            "Target Space"           : None,
+            "Owner Space"            : None,
+            "Influence"              : None,
+            "Target"                 : None,
+            "Enable"                 : None,
+            "Extrapolate"            : None,
+            "Map From"               : None,
+            "Rotation Mode"          : None,
+            "X Min From"             : None,
+            "X Max From"             : None,
+            "Y Min From"             : None,
+            "Y Max From"             : None,
+            "Z Min From"             : None,
+            "Z Max From"             : None,
+            "Map To"                 : None,
+            "X Source Axis"          : None,
+            "X Min To"               : None,
+            "X Max To"               : None,
+            "Y Source Axis"          : None,
+            "Y Min To"               : None,
+            "Y Max To"               : None,
+            "Z Source Axis"          : None,
+            "Z Min To"               : None,
+            "Z Max To"               : None,
+            "Rotation Order"         : None,
+            "Mix Mode (Translation)" : None,
+            "Mix Mode (Rotation)"    : None,
+            "Mix Mode (Scale)"       : None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+    
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def bExecute(self, context):
+        prepare_parameters(self)
+        c = self.GetxForm().bGetObject().constraints.new('TRANSFORM')
+        get_target_and_subtarget(self, c)
+        print(wrapGreen("Creating ")+wrapWhite("Transformation")+
+             wrapGreen(" Constraint for bone: ") +
+             wrapOrange(self.GetxForm().bGetObject().name))
+        c.name = self.evaluate_input("Name")
+        self.bObject = c
+        props_sockets = transformation_props_sockets
+        evaluate_sockets(self, c, props_sockets)        
+    def bFinalize(self, bContext = None):
+        finish_drivers(self)
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+class LinkLimitLocation:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+            "Use Max X"          : NodeSocket(is_input = True, name = "Use Max X", node = self,),
+            "Max X"              : NodeSocket(is_input = True, name = "Max X", node = self,),
+            "Use Max Y"          : NodeSocket(is_input = True, name = "Use Max Y", node = self,),
+            "Max Y"              : NodeSocket(is_input = True, name = "Max Y", node = self,),
+            "Use Max Z"          : NodeSocket(is_input = True, name = "Use Max Z", node = self,),
+            "Max Z"              : NodeSocket(is_input = True, name = "Max Z", node = self,),
+            "Use Min X"          : NodeSocket(is_input       = True, name = "Use Min X", node = self,),
+            "Min X"              : NodeSocket(is_input = True, name = "Min X", node = self,),
+            "Use Min Y"          : NodeSocket(is_input = True, name = "Use Min Y", node = self,),
+            "Min Y"              : NodeSocket(is_input = True, name = "Min Y", node = self,),
+            "Use Min Z"          : NodeSocket(is_input = True, name = "Use Min Z", node = self,),
+            "Min Z"              : NodeSocket(is_input = True, name = "Min Z", node = self,),
+            "Affect Transform"   : NodeSocket(is_input = True, name = "Affect Transform", node = self,),
+            "Owner Space"        : NodeSocket(is_input = True, name = "Owner Space", node = self,),
+            "Influence"          : NodeSocket(is_input = True, name = "Influence", node = self,),
+            "Enable"             : NodeSocket(is_input = True, name = "Enable", node = self,),  }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(name = "Output Relationship", node=self) }
+        self.parameters = {
+            "Name":None,
+            "Input Relationship":None,
+            "Use Max X":None,
+            "Max X":None,
+            "Use Max Y":None,
+            "Max Y":None,
+            "Use Max Z":None,
+            "Max Z":None,
+            "Use Min X":None,
+            "Min X":None,
+            "Use Min Y":None,
+            "Min Y":None,
+            "Use Min Z":None,
+            "Min Z":None,
+            "Affect Transform":None,
+            "Owner Space":None,
+            "Influence":None,
+            "Enable":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+    
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def bExecute(self, context):
+        prepare_parameters(self)
+        c = self.GetxForm().bGetObject().constraints.new('LIMIT_LOCATION')
+        #
+        print(wrapGreen("Creating ")+wrapWhite("Limit Location")+
+             wrapGreen(" Constraint for bone: ") +
+             wrapOrange(self.GetxForm().bGetObject().name))
+        
+        c.name = self.evaluate_input("Name")
+        self.bObject = c
+        props_sockets = {
+        'use_transform_limit' : ("Affect Transform", False),
+        'use_max_x'           : ("Use Max X", False),
+        'use_max_y'           : ("Use Max Y", False),
+        'use_max_z'           : ("Use Max Z", False),
+        'use_min_x'           : ("Use Min X", False),
+        'use_min_y'           : ("Use Min Y", False),
+        'use_min_z'           : ("Use Min Z", False),
+        'max_x'               : ("Max X", 0),
+        'max_y'               : ("Max Y", 0),
+        'max_z'               : ("Max Z", 0),
+        'min_x'               : ("Min X", 0),
+        'min_y'               : ("Min Y", 0),
+        'min_z'               : ("Min Z", 0),
+        'owner_space'         : ("Owner Space", 'WORLD'),
+        'influence'           : ("Influence", 1),
+        'mute'               : ("Enable", True),
+        }
+        evaluate_sockets(self, c, props_sockets)        
+    def bFinalize(self, bContext = None):
+        finish_drivers(self)
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+        
+class LinkLimitRotation:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+            "Use X"              : NodeSocket(is_input = True, name = "Use X", node = self,),
+            "Use Y"              : NodeSocket(is_input = True, name = "Use Y", node = self,),
+            "Use Z"              : NodeSocket(is_input = True, name = "Use Z", node = self,),
+            "Max X"              : NodeSocket(is_input = True, name = "Max X", node = self,),
+            "Max Y"              : NodeSocket(is_input = True, name = "Max Y", node = self,),
+            "Max Z"              : NodeSocket(is_input = True, name = "Max Z", node = self,),
+            "Min X"              : NodeSocket(is_input = True, name = "Min X", node = self,),
+            "Min Y"              : NodeSocket(is_input = True, name = "Min Y", node = self,),
+            "Min Z"              : NodeSocket(is_input = True, name = "Min Z", node = self,),
+            "Affect Transform"   : NodeSocket(is_input = True, name = "Affect Transform", node = self,),
+            "Owner Space"        : NodeSocket(is_input = True, name = "Owner Space", node = self,),
+            "Influence"          : NodeSocket(is_input = True, name = "Influence", node = self,),
+            "Enable"             : NodeSocket(is_input = True, name = "Enable", node = self,),  }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(name = "Output Relationship", node=self) }
+        self.parameters = {
+            "Name":None,
+            "Input Relationship":None,
+            "Use X":None,
+            "Use Y":None,
+            "Use Z":None,
+            "Max X":None,
+            "Max Y":None,
+            "Max Z":None,
+            "Min X":None,
+            "Min Y":None,
+            "Min Z":None,
+            "Affect Transform":None,
+            "Owner Space":None,
+            "Influence":None,
+            "Enable":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+    
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def bExecute(self, context):
+        prepare_parameters(self)
+        c = self.GetxForm().bGetObject().constraints.new('LIMIT_ROTATION')
+        print(wrapGreen("Creating ")+wrapWhite("Limit Rotation")+
+             wrapGreen(" Constraint for bone: ") +
+             wrapOrange(self.GetxForm().bGetObject().name))
+        
+        c.name = self.evaluate_input("Name")
+        self.bObject = c
+        props_sockets = {
+        'use_transform_limit' : ("Affect Transform", False),
+        'use_limit_x'         : ("Use X", False),
+        'use_limit_y'         : ("Use Y", False),
+        'use_limit_z'         : ("Use Z", False),
+        'max_x'               : ("Max X", 0),
+        'max_y'               : ("Max Y", 0),
+        'max_z'               : ("Max Z", 0),
+        'min_x'               : ("Min X", 0),
+        'min_y'               : ("Min Y", 0),
+        'min_z'               : ("Min Z", 0),
+        'owner_space'         : ("Owner Space", 'WORLD'),
+        'influence'           : ("Influence", 1),
+        'mute'               : ("Enable", True),
+        }
+        evaluate_sockets(self, c, props_sockets)        
+    def bFinalize(self, bContext = None):
+        finish_drivers(self)
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+        
+class LinkLimitScale:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+            "Use Max X"          : NodeSocket(is_input = True, name = "Use Max X", node = self,),
+            "Max X"              : NodeSocket(is_input = True, name = "Max X", node = self,),
+            "Use Max Y"          : NodeSocket(is_input = True, name = "Use Max Y", node = self,),
+            "Max Y"              : NodeSocket(is_input = True, name = "Max Y", node = self,),
+            "Use Max Z"          : NodeSocket(is_input = True, name = "Use Max Z", node = self,),
+            "Max Z"              : NodeSocket(is_input = True, name = "Max Z", node = self,),
+            "Use Min X"          : NodeSocket(is_input = True, name = "Use Min X", node = self,),
+            "Min X"              : NodeSocket(is_input = True, name = "Min X", node = self,),
+            "Use Min Y"          : NodeSocket(is_input = True, name = "Use Min Y", node = self,),
+            "Min Y"              : NodeSocket(is_input = True, name = "Min Y", node = self,),
+            "Use Min Z"          : NodeSocket(is_input = True, name = "Use Min Z", node = self,),
+            "Min Z"              : NodeSocket(is_input = True, name = "Min Z", node = self,),
+            "Affect Transform"   : NodeSocket(is_input = True, name = "Affect Transform", node = self,),
+            "Owner Space"        : NodeSocket(is_input = True, name = "Owner Space", node = self,),
+            "Influence"          : NodeSocket(is_input = True, name = "Influence", node = self,),
+            "Enable"             : NodeSocket(is_input = True, name = "Enable", node = self,),  }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(name = "Output Relationship", node=self) }
+        self.parameters = {
+            "Name":None,
+            "Input Relationship":None,
+            "Use Max X":None,
+            "Max X":None,
+            "Use Max Y":None,
+            "Max Y":None,
+            "Use Max Z":None,
+            "Max Z":None,
+            "Use Min X":None,
+            "Min X":None,
+            "Use Min Y":None,
+            "Min Y":None,
+            "Use Min Z":None,
+            "Min Z":None,
+            "Affect Transform":None,
+            "Owner Space":None,
+            "Influence":None,
+            "Enable":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+    
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def bExecute(self, context):
+        prepare_parameters(self)
+        c = self.GetxForm().bGetObject().constraints.new('LIMIT_SCALE')
+        print(wrapGreen("Creating ")+wrapWhite("Limit Scale")+
+             wrapGreen(" Constraint for bone: ") +
+             wrapOrange(self.GetxForm().bGetObject().name))
+        
+        c.name = self.evaluate_input("Name")
+        self.bObject = c
+        props_sockets = {
+        'use_transform_limit' : ("Affect Transform", False),
+        'use_max_x'           : ("Use Max X", False),
+        'use_max_y'           : ("Use Max Y", False),
+        'use_max_z'           : ("Use Max Z", False),
+        'use_min_x'           : ("Use Min X", False),
+        'use_min_y'           : ("Use Min Y", False),
+        'use_min_z'           : ("Use Min Z", False),
+        'max_x'               : ("Max X", 0),
+        'max_y'               : ("Max Y", 0),
+        'max_z'               : ("Max Z", 0),
+        'min_x'               : ("Min X", 0),
+        'min_y'               : ("Min Y", 0),
+        'min_z'               : ("Min Z", 0),
+        'owner_space'         : ("Owner Space", 'WORLD'),
+        'influence'           : ("Influence", 1),
+        'mute'               :  ("Enable", True),
+        }
+        evaluate_sockets(self, c, props_sockets)        
+    def bFinalize(self, bContext = None):
+        finish_drivers(self)
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+        
+class LinkLimitDistance:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+            "Head/Tail"          : NodeSocket(is_input = True, name = "Head/Tail", node = self,),
+            "UseBBone"           : NodeSocket(is_input = True, name = "UseBBone", node = self,),
+            "Distance"           : NodeSocket(is_input = True, name = "Distance", node = self,),
+            "Clamp Region"       : NodeSocket(is_input = True, name = "Clamp Region", node = self,),
+            "Affect Transform"   : NodeSocket(is_input = True, name = "Affect Transform", node = self,),
+            "Owner Space"        : NodeSocket(is_input = True, name = "Owner Space", node = self,),
+            "Target Space"       : NodeSocket(is_input = True, name = "Target Space", node = self,),
+            "Influence"          : NodeSocket(is_input = True, name = "Influence", node = self,),
+            "Target"             : NodeSocket(is_input = True, name = "Target", node = self,),
+            "Enable"             : NodeSocket(is_input = True, name = "Enable", node = self,),  }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(name = "Output Relationship", node=self) }
+        self.parameters = {
+            "Name":None,
+            "Input Relationship":None,
+            "Head/Tail":None,
+            "UseBBone":None,
+            "Distance":None,
+            "Clamp Region":None,
+            "Affect Transform":None,
+            "Owner Space":None,
+            "Target Space":None,
+            "Influence":None,
+            "Target":None,
+            "Enable":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def bExecute(self, context):
+        prepare_parameters(self)
+        print(wrapGreen("Creating ")+wrapWhite("Limit Distance")+
+             wrapGreen(" Constraint for bone: ") +
+             wrapOrange(self.GetxForm().bGetObject().name))
+        c = self.GetxForm().bGetObject().constraints.new('LIMIT_DISTANCE')
+        get_target_and_subtarget(self, c)
+        c.name = self.evaluate_input("Name")
+        self.bObject = c
+        #
+        # TODO: set distance automagically
+        # IMPORTANT TODO BUG
+        
+        props_sockets = {
+        'distance'            : ("Distance", 0),
+        'head_tail'           : ("Head/Tail", 0),
+        'limit_mode'          : ("Clamp Region", "LIMITDIST_INSIDE"),
+        'use_bbone_shape'     : ("UseBBone", False),
+        'use_transform_limit' : ("Affect Transform", 1),
+        'owner_space'         : ("Owner Space", 1),
+        'target_space'        : ("Target Space", 1),
+        'influence'           : ("Influence", 1),
+        'mute'               : ("Enable", True),
+        }
+        evaluate_sockets(self, c, props_sockets)
+
+    def bFinalize(self, bContext = None):
+        finish_drivers(self)
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+# Tracking
+
+class LinkStretchTo:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+            "Head/Tail"          : NodeSocket(is_input = True, name = "Head/Tail", node = self,),
+            "UseBBone"           : NodeSocket(is_input = True, name = "UseBBone", node = self,),
+            "Original Length"    : NodeSocket(is_input = True, name = "Original Length", node = self,),
+            "Volume Variation"   : NodeSocket(is_input = True, name = "Volume Variation", node = self,),
+            "Use Volume Min"     : NodeSocket(is_input = True, name = "Use Volume Min", node = self,),
+            "Volume Min"         : NodeSocket(is_input = True, name = "Volume Min", node = self,),
+            "Use Volume Max"     : NodeSocket(is_input = True, name = "Use Volume Max", node = self,),
+            "Volume Max"         : NodeSocket(is_input = True, name = "Volume Max", node = self,),
+            "Smooth"             : NodeSocket(is_input = True, name = "Smooth", node = self,),
+            "Maintain Volume"    : NodeSocket(is_input = True, name = "Maintain Volume", node = self,),
+            "Rotation"           : NodeSocket(is_input = True, name = "Rotation", node = self,),
+            "Influence"          : NodeSocket(is_input = True, name = "Influence", node = self,),
+            "Target"             : NodeSocket(is_input = True, name = "Target", node = self,),
+            "Enable"             : NodeSocket(is_input = True, name = "Enable", node = self,),  }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(name = "Output Relationship", node=self) }
+        self.parameters = {
+            "Name":None,
+            "Input Relationship":None,
+            "Head/Tail":None,
+            "UseBBone":None,
+            "Original Length":None,
+            "Volume Variation":None,
+            "Use Volume Min":None,
+            "Volume Min":None,
+            "Use Volume Max":None,
+            "Volume Max":None,
+            "Smooth":None,
+            "Maintain Volume":None,
+            "Rotation":None,
+            "Influence":None,
+            "Target":None,
+            "Enable":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def bExecute(self, context):
+        prepare_parameters(self)
+        print(wrapGreen("Creating ")+wrapWhite("Stretch-To")+
+             wrapGreen(" Constraint for bone: ") +
+             wrapOrange(self.GetxForm().bGetObject().name))
+        c = self.GetxForm().bGetObject().constraints.new('STRETCH_TO')
+        get_target_and_subtarget(self, c)
+        c.name = self.evaluate_input("Name")
+        self.bObject = c
+        props_sockets = {
+        'head_tail'       : ("Head/Tail", 0),
+        'use_bbone_shape' : ("UseBBone", False),
+        'bulge'           : ("Volume Variation", 0),
+        'use_bulge_min'   : ("Use Volume Min", False),
+        'bulge_min'       : ("Volume Min", 0),
+        'use_bulge_max'   : ("Use Volume Max", False),
+        'bulge_max'       : ("Volume Max", 0),
+        'bulge_smooth'    : ("Smooth", 0),
+        'volume'          : ("Maintain Volume", 'VOLUME_XZX'),
+        'keep_axis'       : ("Rotation", 'PLANE_X'),
+        'rest_length'     : ("Original Length", self.GetxForm().bGetObject().bone.length),
+        'influence'       : ("Influence", 1),
+        'mute'           : ("Enable", True),
+        }
+        evaluate_sockets(self, c, props_sockets)
+        
+        if (self.evaluate_input("Original Length") == 0):
+            # this is meant to be set automatically.
+            c.rest_length = self.GetxForm().bGetObject().bone.length
+        
+    def bFinalize(self, bContext = None):
+        finish_drivers(self)
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+class LinkDampedTrack:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+            "Head/Tail"          : NodeSocket(is_input = True, name = "Head/Tail", node = self,),
+            "UseBBone"           : NodeSocket(is_input = True, name = "UseBBone", node = self,),
+            "Track Axis"         : NodeSocket(is_input = True, name = "Track Axis", node = self,),
+            "Influence"          : NodeSocket(is_input = True, name = "Influence", node = self,),
+            "Target"             : NodeSocket(is_input = True, name = "Target", node = self,),
+            "Enable"             : NodeSocket(is_input = True, name = "Enable", node = self,),  }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(name = "Output Relationship", node=self) }
+        self.parameters = {
+            "Name":None,
+            "Input Relationship":None,
+            "Head/Tail":None,
+            "UseBBone":None,
+            "Track Axis":None,
+            "Influence":None,
+            "Target":None,
+            "Enable":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def bExecute(self, context):
+        prepare_parameters(self)
+        print(wrapGreen("Creating ")+wrapWhite("Damped Track")+
+             wrapGreen(" Constraint for bone: ") +
+             wrapOrange(self.GetxForm().bGetObject().name))
+        c = self.GetxForm().bGetObject().constraints.new('DAMPED_TRACK')
+        get_target_and_subtarget(self, c)
+        c.name = self.evaluate_input("Name")
+        self.bObject = c
+        props_sockets = {
+        'head_tail'       : ("Head/Tail", 0),
+        'use_bbone_shape' : ("UseBBone", False),
+        'track_axis'      : ("Track Axis", 'TRACK_Y'),
+        'influence'       : ("Influence", 1),
+        'mute'            : ("Enable", True),
+        }
+        evaluate_sockets(self, c, props_sockets)
+    
+    def bFinalize(self, bContext = None):
+        finish_drivers(self)
+        
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+class LinkLockedTrack:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+            "Head/Tail"          : NodeSocket(is_input = True, name = "Head/Tail", node = self,),
+            "UseBBone"           : NodeSocket(is_input = True, name = "UseBBone", node = self,),
+            "Track Axis"         : NodeSocket(is_input = True, name = "Track Axis", node = self,),
+            "Lock Axis"          : NodeSocket(is_input = True, name = "Lock Axis", node = self,),
+            "Influence"          : NodeSocket(is_input = True, name = "Influence", node = self,),
+            "Target"             : NodeSocket(is_input = True, name = "Target", node = self,),
+            "Enable"             : NodeSocket(is_input = True, name = "Enable", node = self,),  }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(name = "Output Relationship", node=self) }
+        self.parameters = {
+            "Name":None,
+            "Input Relationship":None,
+            "Head/Tail":None,
+            "UseBBone":None,
+            "Track Axis":None,
+            "Lock Axis":None,
+            "Influence":None,
+            "Target":None,
+            "Enable":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def bExecute(self, context):
+        prepare_parameters(self)
+        print(wrapGreen("Creating ")+wrapWhite("Locked Track")+
+             wrapGreen(" Constraint for bone: ") +
+             wrapOrange(self.GetxForm().bGetObject().name))
+        c = self.GetxForm().bGetObject().constraints.new('LOCKED_TRACK')
+        get_target_and_subtarget(self, c)
+        c.name = self.evaluate_input("Name")
+        self.bObject = c
+        props_sockets = {
+        'head_tail'       : ("Head/Tail", 0),
+        'use_bbone_shape' : ("UseBBone", False),
+        'track_axis'      : ("Track Axis", 'TRACK_Y'),
+        'lock_axis'       : ("Lock Axis", 'UP_X'),
+        'influence'       : ("Influence", 1),
+        'mute'           : ("Enable", True),
+        }
+        evaluate_sockets(self, c, props_sockets)
+
+    def bFinalize(self, bContext = None):
+        finish_drivers(self)
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+class LinkTrackTo:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+            "Head/Tail"          : NodeSocket(is_input = True, name = "Head/Tail", node = self,),
+            "UseBBone"           : NodeSocket(is_input = True, name = "UseBBone", node = self,),
+            "Track Axis"         : NodeSocket(is_input = True, name = "Track Axis", node = self,),
+            "Up Axis"            : NodeSocket(is_input = True, name = "Up Axis", node = self,),
+            "Use Target Z"       : NodeSocket(is_input = True, name = "Use Target Z", node = self,),
+            "Influence"          : NodeSocket(is_input = True, name = "Influence", node = self,),
+            "Target"             : NodeSocket(is_input = True, name = "Target", node = self,),
+            "Enable"             : NodeSocket(is_input = True, name = "Enable", node = self,),  }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(name = "Output Relationship", node=self) }
+        self.parameters = {
+            "Name":None,
+            "Input Relationship":None,
+            "Head/Tail":None,
+            "UseBBone":None,
+            "Track Axis":None,
+            "Up Axis":None,
+            "Use Target Z":None, 
+            "Influence":None,
+            "Target":None,
+            "Enable":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def bExecute(self, context):
+        prepare_parameters(self)
+        print(wrapGreen("Creating ")+wrapWhite("Track-To")+
+             wrapGreen(" Constraint for bone: ") +
+             wrapOrange(self.GetxForm().bGetObject().name))
+        c = self.GetxForm().bGetObject().constraints.new('TRACK_TO')
+        get_target_and_subtarget(self, c)
+        c.name = self.evaluate_input("Name")
+        self.bObject = c
+        props_sockets = {
+        'head_tail'       : ("Head/Tail", 0),
+        'use_bbone_shape' : ("UseBBone", False),
+        'track_axis'      : ("Track Axis", "TRACK_Y"),
+        'up_axis'         : ("Up Axis", "UP_Z"),
+        'use_target_z'    : ("Use Target Z", False),
+        'influence'       : ("Influence", 1),
+        'mute'           : ("Enable", True),
+        }
+        evaluate_sockets(self, c, props_sockets)
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+    def bFinalize(self, bContext = None):
+        finish_drivers(self)
+
+# relationships & misc.
+
+class LinkInheritConstraint:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+            "Location"           : NodeSocket(is_input = True, name = "Location", node = self,),
+            "Rotation"           : NodeSocket(is_input = True, name = "Rotation", node = self,),
+            "Scale"              : NodeSocket(is_input = True, name = "Scale", node = self,),
+            "Influence"          : NodeSocket(is_input = True, name = "Influence", node = self,),
+            "Target"             : NodeSocket(is_input = True, name = "Target", node = self,),
+            "Enable"             : NodeSocket(is_input = True, name = "Enable", node = self,),  }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(name = "Output Relationship", node=self) }
+        self.parameters = {
+            "Name":None,
+            "Input Relationship":None,
+            "Location":None,
+            "Rotation":None,
+            "Scale":None,
+            "Influence":None,
+            "Target":None,
+            "Enable":None,}
+        self.drivers = {}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def bExecute(self, context):
+        prepare_parameters(self)
+        print(wrapGreen("Creating ")+wrapWhite("Child-Of")+
+             wrapGreen(" Constraint for bone: ") +
+             wrapOrange(self.GetxForm().bGetObject().name))
+        c = self.GetxForm().bGetObject().constraints.new('CHILD_OF')
+        get_target_and_subtarget(self, c)
+        c.name = self.evaluate_input("Name")
+        self.bObject = c
+        
+        props_sockets = {
+        'use_location_x'   : (("Location", 0) , 1),
+        'use_location_y'   : (("Location", 1) , 1),
+        'use_location_z'   : (("Location", 2) , 1),
+        'use_rotation_x'   : (("Rotation", 0) , 1),
+        'use_rotation_y'   : (("Rotation", 1) , 1),
+        'use_rotation_z'   : (("Rotation", 2) , 1),
+        'use_scale_x'      : (("Scale"   , 0) , 1),
+        'use_scale_y'      : (("Scale"   , 1) , 1),
+        'use_scale_z'      : (("Scale"   , 2) , 1),
+        'influence'        : ( "Influence"    , 1),
+        'mute'             : ("Enable", True),
+        }
+        evaluate_sockets(self, c, props_sockets)
+            
+        
+        c.set_inverse_pending
+        
+        
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+    def bFinalize(self, bContext = None):
+        finish_drivers(self)
+
+class LinkInverseKinematics:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship"  : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+            "Chain Length"        : NodeSocket(is_input = True, name = "Chain Length", node = self,),
+            "Use Tail"            : NodeSocket(is_input = True, name = "Use Tail", node = self,),
+            "Stretch"             : NodeSocket(is_input = True, name = "Stretch", node = self,),
+            "Position"            : NodeSocket(is_input = True, name = "Position", node = self,),
+            "Rotation"            : NodeSocket(is_input = True, name = "Rotation", node = self,),
+            "Influence"           : NodeSocket(is_input = True, name = "Influence", node = self,),
+            "Target"              : NodeSocket(is_input = True, name = "Target", node = self,),
+            "Pole Target"         : NodeSocket(is_input = True, name = "Pole Target", node = self,),
+            "Enable"              : NodeSocket(is_input = True, name = "Enable", node = self,),  }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(name = "Output Relationship", node=self) }
+        self.parameters = {
+            "Name":None,
+            "Connected":None,
+            "Chain Length":None,
+            "Use Tail":None,
+            "Stretch":None,
+            "Position":None,
+            "Rotation":None,
+            "Influence":None,
+            "Target":None, 
+            "Pole Target":None,
+            "Enable":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        self.bObject = None
+        self.drivers = {}
+        
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def bExecute(self, context):
+        prepare_parameters(self)
+        print(wrapGreen("Creating ")+wrapOrange("Inverse Kinematics")+
+             wrapGreen(" Constraint for bone: ") +
+             wrapOrange(self.GetxForm().bGetObject().name))
+        myOb = self.GetxForm().bGetObject()
+        c = self.GetxForm().bGetObject().constraints.new('IK')
+        get_target_and_subtarget(self, c)
+        get_target_and_subtarget(self, c, input_name = 'Pole Target')
+        c.name = self.evaluate_input("Name")
+        
+        self.bObject = c
+        if (c.pole_target): # Calculate the pole angle, the user shouldn't have to.
+            pole_object = c.pole_target
+            pole_location = pole_object.matrix_world.decompose()[0]
+            if (c.pole_subtarget):
+                pole_object = c.pole_target.pose.bones[c.pole_subtarget]
+                pole_location = pole_object.matrix.decompose()[0]
+            #HACK HACK
+            handle_location = myOb.bone.tail_local if (self.evaluate_input("Use Tail")) else myOb.bone.head_local
+            counter = 0
+            parent = myOb
+            base_bone = myOb
+            while (parent is not None):
+                if ((self.evaluate_input("Chain Length") != 0) and (counter > self.evaluate_input("Chain Length"))):
+                    break
+                base_bone = parent
+                parent = parent.parent
+                counter+=1
+            head_location = base_bone.bone.head_local
+
+            pole_normal = (handle_location - head_location).cross(pole_location - head_location)
+            vector_u = myOb.bone.x_axis
+            vector_v = pole_normal.cross(base_bone.bone.y_axis)
+            angle = -1 * vector_u.angle(vector_v)
+            # TODO: create warnings for edge cases that fail such as pole target in the same place as handle
+            # if (vector_u.cross(vector_v).angle(base_bone.bone.y_axis) < 1):
+                # angle = angle
+            
+            c.pole_angle = angle
+        
+        props_sockets = {
+        'chain_count'   : ("Chain Length", 1),
+        'use_tail'      : ("Use Tail", True),
+        'use_stretch'   : ("Stretch", True),
+        "weight"        : ("Position", 1.0),
+        "orient_weight" : ("Rotation", 0.0),
+        "influence"     : ("Influence", 1.0),
+        'mute'          : ("Enable", True),
+        }
+        evaluate_sockets(self, c, props_sockets)
+                
+        # TODO: handle drivers
+        #        (it should be assumed we want it on if it's plugged
+        #         into a driver).
+        c.use_location   = self.evaluate_input("Position") > 0
+        c.use_rotation   = self.evaluate_input("Rotation") > 0
+        
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+    
+    def bFinalize(self, bContext = None):
+        finish_drivers(self)
+        
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+# This is kinda a weird design decision?
+class LinkDrivenParameter:
+    '''A node representing an armature object'''
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+        "Input Relationship" : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+          "Driver"      : NodeSocket(is_input = True, name = "Driver", node = self),
+          "Parameter"   : NodeSocket(is_input = True, name = "Parameter", node = self),
+          "Index"       : NodeSocket(is_input = True, name = "Index", node = self),
+        }
+        self.outputs = {
+          "Output Relationship" : NodeSocket(name = "Output Relationship", node=self), }
+        self.parameters = {
+          "Input Relationship":None, 
+          "Driver":None, 
+          "Parameter":None,
+          "Index":None,
+        }
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = "LINK"
+
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+
+    def bExecute(self, bContext = None,):
+        prepare_parameters(self)
+        prGreen("Executing Driven Parameter node")
+        
+        # example_ driver ={
+                    # "owner":None,
+                    # "prop":None, # will be filled out in the node that uses the driver
+                    # "ind":-1, # same here
+                    # "type": self.evaluate_input("Driver Type"),
+                    # "vars": my_vars,
+                    # "keys": self.evaluate_input("fCurve"),}
+                    
+        driver = self.evaluate_input("Driver")
+        driver["owner"] = self.GetxForm().bGetObject()
+        driver["prop"] = self.evaluate_input("Parameter")
+        driver["ind"] = self.evaluate_input("Index")
+        
+        self.parameters["Driver"] = driver
+    def bFinalize(self, bContext = None):
+        # TODO HACK BUG
+        # This probably no longer works
+        from mantis.drivers import CreateDrivers
+        CreateDrivers( [ self.parameters["Driver"] ] )
+        
+    def __repr__(self):
+        return self.signature.__repr__()
+
+    def fill_parameters(self):
+        fill_parameters(self)
+        
+class LinkArmature:
+    '''A node representing an armature object'''
+
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+        "Input Relationship"   : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+        "Preserve Volume"      : NodeSocket(is_input = True, name = "Preserve Volume", node = self),
+        "Use Envelopes"        : NodeSocket(is_input = True, name = "Use Envelopes", node = self),
+        "Use Current Location" : NodeSocket(is_input = True, name = "Use Current Location", node = self),
+        "Influence"            : NodeSocket(is_input = True, name = "Influence", node = self),
+        "Enable"               : NodeSocket(is_input = True, name = "Enable", node = self),
+        }
+        self.outputs = {
+          "Output Relationship" : NodeSocket(name = "Output Relationship", node=self), }
+        self.parameters = {
+            "Name":None,
+            "Input Relationship":None, 
+            "Preserve Volume":None, 
+            "Use Envelopes":None, 
+            "Use Current Location":None, 
+            "Influence":None, 
+            "Enable":None,
+        }
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = "LINK"
+        setup_custom_props(self)
+
+
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+
+    def bExecute(self, bContext = None,):
+        prGreen("Creating Armature Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
+        prepare_parameters(self)
+        c = self.GetxForm().bGetObject().constraints.new('ARMATURE')
+        c.name = self.evaluate_input("Name")
+        self.bObject = c
+        # get number of targets
+        num_targets = len( list(self.inputs.values())[6:] )//2
+        
+        props_sockets = {
+        'use_deform_preserve_volume' : ("Preserve Volume", 0),
+        'use_bone_envelopes'         : ("Use Envelopes", 0),
+        'use_current_location'       : ("Use Current Location", 0),
+        'influence'                  : ( "Influence"    , 1),
+        'mute'                       : ("Enable", True),
+        }
+        targets_weights = {}
+        for i in range(num_targets):
+            target = c.targets.new()
+            target_input_name = list(self.inputs.keys())[i*2+6  ]
+            weight_input_name = list(self.inputs.keys())[i*2+6+1]
+            get_target_and_subtarget(self, target, target_input_name)
+            targets_weights[i]=self.evaluate_input(weight_input_name)
+            # props_sockets["targets[%d].weight" % i] = (weight_input_name, 0)
+            # targets_weights.append({"weight":(weight_input_name, 0)})
+        evaluate_sockets(self, c, props_sockets)
+        for target, value in targets_weights.items():
+            c.targets[target].weight=value
+        # for i, (target, weight) in enumerate(zip(c.targets, targets_weights)):
+            # evaluate_sockets(self, target, weight)
+
+    def bFinalize(self, bContext = None):
+        finish_drivers(self)
+
+    def __repr__(self):
+        return self.signature.__repr__()
+
+    def fill_parameters(self):
+        fill_parameters(self)
+
+
+class LinkSplineIK:
+    '''A node representing an armature object'''
+
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+          "Input Relationship" : NodeSocket(is_input = True, name = "Input Relationship", node = self,),
+          "Target"             : NodeSocket(is_input = True, name = "Target", node = self),
+          "Chain Length"       : NodeSocket(is_input = True, name = "Chain Length", node = self),
+          "Even Divisions"     : NodeSocket(is_input = True, name = "Even Divisions", node = self),
+          "Chain Offset"       : NodeSocket(is_input = True, name = "Chain Offset", node = self),
+          "Use Curve Radius"   : NodeSocket(is_input = True, name = "Use Curve Radius", node = self),
+          "Y Scale Mode"       : NodeSocket(is_input = True, name = "Y Scale Mode", node = self),
+          "XZ Scale Mode"      : NodeSocket(is_input = True, name = "XZ Scale Mode", node = self),
+          "Use Original Scale" : NodeSocket(is_input = True, name = "Use Original Scale", node = self),
+          "Influence"          : NodeSocket(is_input = True, name = "Influence", node = self),
+        }
+        self.outputs = {
+          "Output Relationship" : NodeSocket(is_input = False, name = "Output Relationship", node=self), }
+        self.parameters = {
+          "Name":None,
+          "Input Relationship":None, 
+          "Target":None, 
+          "Chain Length":None, 
+          "Even Divisions":None, 
+          "Chain Offset":None, 
+          "Use Curve Radius":None, 
+          "Y Scale Mode":None, 
+          "XZ Scale Mode":None, 
+          "Use Original Scale":None, 
+          "Influence":None, 
+        }
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = "LINK"
+
+    def evaluate_input(self, input_name):
+        return default_evaluate_input(self, input_name)
+
+    def GetxForm(self):
+        return GetxForm(self)
+
+    def bExecute(self, bContext = None,):
+        prepare_parameters(self)
+        prGreen("Creating Spline-IK Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
+        c = self.GetxForm().bGetObject().constraints.new('SPLINE_IK')
+        get_target_and_subtarget(self, c)
+        c.name = self.evaluate_input("Name")
+        self.bObject = c
+        props_sockets = {
+        'chain_count' : ("Chain Length", 0),
+        'use_even_divisions'      : ("Even Divisions", False),
+        'use_chain_offset'         : ("Chain Offset", False),
+        'use_curve_radius'    : ("Use Curve Radius", False),
+        'y_scale_mode'       : ("Y Scale Mode", "FIT_CURVE"),
+        'xz_scale_mode'           : ("XZ Scale Mode", "NONE"),
+        'use_original_scale'           : ("Use Original Scale", False),
+        'influence'       : ("Influence", 1),
+        }
+
+        evaluate_sockets(self, c, props_sockets)
+
+    def __repr__(self):
+        return self.signature.__repr__()
+
+    def fill_parameters(self):
+        fill_parameters(self)

+ 655 - 0
link_definitions.py

@@ -0,0 +1,655 @@
+import bpy
+from bpy.types import NodeTree, Node, NodeSocket
+from .base_definitions import MantisNode, LinkNode, GraphError
+from mantis.utilities import (prRed, prGreen, prPurple, prWhite,
+                              prOrange,
+                              wrapRed, wrapGreen, wrapPurple, wrapWhite,
+                              wrapOrange,)
+
+from .base_definitions import get_signature_from_edited_tree
+
+def TellClasses():
+    return [ LinkInheritNode,
+             LinkInverseKinematics,
+             LinkCopyLocationNode,
+             LinkCopyRotationNode,
+             LinkCopyScaleNode,
+             LinkInheritConstraintNode,
+             LinkCopyTransformNode,
+             LinkStretchToNode,
+             LinkDampedTrackNode,
+             LinkLockedTrackNode,
+             LinkTrackToNode,
+             LinkLimitLocationNode,
+             LinkLimitScaleNode,
+             LinkLimitRotationNode,
+             LinkLimitDistanceNode,
+             LinkDrivenParameterNode,
+             LinkArmatureNode,
+             LinkSplineIKNode,
+             LinkTransformationNode,
+           ]
+
+def default_traverse(self, socket):
+        if (socket == self.outputs["Output Relationship"]):
+            return self.inputs["Input Relationship"]
+        if (socket == self.inputs["Input Relationship"]):
+            return self.outputs["Output Relationship"]
+        return None
+
+class LinkInheritNode(Node, LinkNode):
+    '''A node representing inheritance'''
+    # cuss, messed this up
+    bl_idname = 'linkInherit' # l should be L
+    # need to fix this
+    bl_label = "Inherit"
+    bl_icon = 'CONSTRAINT_BONE'
+    initialized : bpy.props.BoolProperty(default = False)
+    useTarget : bpy.props.BoolProperty(default=False)
+    
+    # bone_prev : bpy.props.BoolProperty(default=False)
+    # bone_next : bpy.props.BoolProperty(default=False)
+
+    def init(self, context):
+        r = self.inputs.new('BooleanSocket', "Inherit Rotation")
+        s = self.inputs.new('EnumInheritScale', "Inherit Scale")
+        c = self.inputs.new('BooleanSocket', "Connected")
+        i = self.outputs.new('RelationshipSocket', "Inheritance")
+        p = self.inputs.new('xFormSocket', "Parent")
+        # set default values...
+        self.initialized = True
+
+    def traverse(self, socket):
+        if (socket == self.outputs["Inheritance"]):
+            return self.inputs["Parent"]
+        if (socket == self.inputs["Parent"]):
+            return self.outputs["Inheritance"]
+        return None
+    
+    def display_update(self, parsed_tree, context):
+        node_tree = context.space_data.path[0].node_tree
+        nc = parsed_tree.get(get_signature_from_edited_tree(self, context))
+        if nc:
+            bone_prev, bone_next = False, False
+            if (inp := nc.inputs["Parent"]).is_connected:
+                if  from_node := inp.links[0].from_node:
+                    if from_node.__class__.__name__ in ["xFormBone"]:
+                        bone_prev=True
+            bone_next=True
+            try:
+                xForm = nc.GetxForm()
+                if xForm.__class__.__name__ not in "xFormBone":
+                    
+                    bone_next=False
+            except GraphError:
+
+                bone_next=False
+            # print(bone_prev, bone_next )
+            if bone_next and bone_prev:
+                self.inputs["Inherit Rotation"].hide = False
+                self.inputs["Inherit Scale"].hide    = False
+                self.inputs["Connected"].hide       = False
+            else:
+                self.inputs["Inherit Rotation"].hide = True
+                self.inputs["Inherit Scale"].hide    = True
+                self.inputs["Connected"].hide        = True
+            # the node_groups on the way here ought to be active if there
+            #  is no funny business going on.
+    
+
+# DO: make another node for ITASC IK, eh?
+class LinkInverseKinematics(Node, LinkNode):
+    '''A node representing inverse kinematics'''
+    bl_idname = 'LinkInverseKinematics'
+    bl_label = "Inverse Kinematics"
+    bl_icon = 'CON_KINEMATIC'
+    initialized : bpy.props.BoolProperty(default = False)
+    useTarget : bpy.props.BoolProperty(default=True)
+
+    def init(self, context):
+        self.inputs.new('RelationshipSocket', "Input Relationship")
+        self.inputs.new ('xFormSocket', "Target")
+        self.inputs.new ('xFormSocket', "Pole Target")
+        self.inputs.new ('IKChainLengthSocket', "Chain Length")
+        self.inputs.new ('BooleanSocket', "Use Tail")
+        self.inputs.new ('BooleanSocket', "Stretch")
+        self.inputs.new ('FloatFactorSocket', "Position")
+        self.inputs.new ('FloatFactorSocket', "Rotation")
+        self.inputs.new ('FloatFactorSocket', "Influence")
+        self.inputs.new ('EnableSocket', "Enable")
+        
+        #Well, it turns out that this has to be a constraint like
+        # everything else, because of course, there can be more than one.
+        
+        #self.outputs.new('RelationshipSocket', "Inheritance")
+        self.outputs.new('RelationshipSocket', "Output Relationship")
+
+        self.initialized = True
+
+
+        
+class LinkSplineIK(Node, LinkNode):
+    '''A node representing Spline IK'''
+    bl_idname = 'LinkSplineIK'
+    bl_label = "LinkSplineIK"
+    bl_icon = 'CON_SPLINEIK'
+    useTarget : bpy.props.BoolProperty(default=True)
+    def init(self, context):
+        self.inputs.new('xFormSocket', "Parent")
+        self.inputs.new ('xFormSocket', "Target")
+        self.inputs.new ('xFormSocket', "Pole Target")
+        self.inputs.new ('BooleanSocket', "Use Tail")
+        self.inputs.new ('BooleanSocket', "Stretch")
+        self.inputs.new ('FloatFactorSocket', "Position")
+        self.inputs.new ('FloatFactorSocket', "Rotation")
+        self.inputs.new ('FloatFactorSocket', "Influence")
+        self.inputs.new ('EnableSocket', "Enable")
+        self.outputs.new('RelationshipSocket', "Inheritance")
+
+    def traverse(self, socket):
+        if (socket == self.outputs["Inheritance"]):
+            return self.inputs["Parent"]
+        if (socket == self.inputs["Parent"]):
+            return self.outputs["Inheritance"]
+        return None
+
+class LinkCopyLocationNode(Node, LinkNode):
+    '''A node representing Copy Location'''
+    bl_idname = 'LinkCopyLocation'
+    bl_label = "Copy Location"
+    bl_icon = 'CON_LOCLIKE'
+    useTarget : bpy.props.BoolProperty(default=True)
+
+    def init(self, context):
+        self.inputs.new ('RelationshipSocket', "Input Relationship")
+        self.inputs.new ('FloatFactorSocket', "Head/Tail")
+        self.inputs.new ('BooleanSocket', "UseBBone")
+        self.inputs.new ('BooleanThreeTupleSocket', "Axes")
+        self.inputs.new ('BooleanThreeTupleSocket', "Invert")
+        self.inputs.new ('TransformSpaceSocket', "Target Space")
+        self.inputs.new ('TransformSpaceSocket', "Owner Space")
+        self.inputs.new ('FloatFactorSocket', "Influence")
+        self.inputs.new ('xFormSocket', "Target")
+        self.inputs.new ('EnableSocket', "Enable")
+        #
+        self.outputs.new('RelationshipSocket', "Output Relationship")
+
+
+        
+    
+    # def display_update(self, parsed_tree, context):
+        # node_tree = context.space_data.path[0].node_tree
+        # nc = parsed_tree.get(get_signature_from_edited_tree(self, context))
+        # if nc:
+            # bone_prev, bone_next = False, False
+            # if (inp := nc.inputs["Input Relationship"]).is_connected:
+                # if  from_node := inp.links[0].from_node:
+                    # if from_node.__class__.__name__ in ["xFormBone"]:
+                        # bone_prev=True
+            # bone_next=True
+            # try:
+                # xForm = nc.GetxForm()
+                # if xForm.__class__.__name__ not in "xFormBone":
+                    # bone_next=False
+            # except GraphError:
+                # bone_next=False
+            # if bone_next and bone_prev:
+                # # this is where we will set whether you can pick
+                # #  the other spaces or not.
+        
+class LinkCopyRotationNode(Node, LinkNode):
+    '''A node representing Copy Rotation'''
+    bl_idname = 'LinkCopyRotation'
+    bl_label = "Copy Rotation"
+    bl_icon = 'CON_ROTLIKE'
+    useTarget : bpy.props.BoolProperty(default=True)
+
+    def init(self, context):
+        self.inputs.new ('RelationshipSocket', "Input Relationship")
+        self.inputs.new ('RotationOrderSocket', "RotationOrder")
+        self.inputs.new ('EnumRotationMix', "Rotation Mix")
+        self.inputs.new ('BooleanThreeTupleSocket', "Axes")
+        self.inputs.new ('BooleanThreeTupleSocket', "Invert")
+        self.inputs.new ('TransformSpaceSocket', "Target Space")
+        self.inputs.new ('TransformSpaceSocket', "Owner Space")
+        self.inputs.new ('FloatFactorSocket', "Influence")
+        self.inputs.new ('xFormSocket', "Target")
+        self.inputs.new ('EnableSocket', "Enable")
+        #
+        self.outputs.new('RelationshipSocket', "Output Relationship")
+
+
+        
+class LinkCopyScaleNode(Node, LinkNode):
+    '''A node representing Copy Scale'''
+    bl_idname = 'LinkCopyScale'
+    bl_label = "Copy Scale"
+    bl_icon = 'CON_SIZELIKE'
+    useTarget : bpy.props.BoolProperty(default=True)
+
+    def init(self, context):
+        self.inputs.new ('RelationshipSocket', "Input Relationship")
+        self.inputs.new ('BooleanSocket', "Offset")
+        self.inputs.new ('BooleanSocket', "Average")
+        self.inputs.new ('BooleanThreeTupleSocket', "Axes")
+        #self.inputs.new ('BooleanThreeTupleSocket', "Invert")
+        # dingus, this one doesn't have inverts
+        self.inputs.new ('TransformSpaceSocket', "Target Space")
+        self.inputs.new ('TransformSpaceSocket', "Owner Space")
+        self.inputs.new ('FloatFactorSocket', "Influence")
+        self.inputs.new ('xFormSocket', "Target")
+        self.inputs.new ('EnableSocket', "Enable")
+        #
+        self.outputs.new('RelationshipSocket', "Output Relationship")
+
+
+        
+class LinkInheritConstraintNode(Node, LinkNode):
+    # === Basics ===
+    # Description string
+    '''A node representing a parent constraint'''
+    bl_idname = 'LinkInheritConstraint'
+    bl_label = "Inherit (constraint)"
+    bl_icon = 'CON_CHILDOF'
+    useTarget : bpy.props.BoolProperty(default=True)
+
+    # === Optional Functions ===
+    def init(self, context):
+        self.inputs.new ('RelationshipSocket', "Input Relationship")
+        self.inputs.new ('BooleanThreeTupleSocket', "Location")
+        self.inputs.new ('BooleanThreeTupleSocket', "Rotation")
+        self.inputs.new ('BooleanThreeTupleSocket', "Scale")
+        self.inputs.new ('FloatFactorSocket', "Influence")
+        self.inputs.new ('xFormSocket', "Target")
+        self.inputs.new ('EnableSocket', "Enable")
+        #
+        self.outputs.new('RelationshipSocket', "Output Relationship")
+
+
+        
+class LinkCopyTransformNode(Node, LinkNode):
+    # === Basics ===
+    # Description string
+    '''A node representing Copy Transform'''
+    bl_idname = 'LinkCopyTransforms'
+    bl_label = "Copy Transform"
+    bl_icon = 'CON_TRANSLIKE'
+    useTarget : bpy.props.BoolProperty(default=True)
+
+
+    # === Optional Functions ===
+    def init(self, context):
+        self.inputs.new ('RelationshipSocket', "Input Relationship")
+        self.inputs.new ('FloatFactorSocket', "Head/Tail")
+        self.inputs.new ('BooleanSocket', "UseBBone")
+        self.inputs.new ('EnumRotationMixCopyTransforms', "Mix")
+        self.inputs.new ('TransformSpaceSocket', "Target Space")
+        self.inputs.new ('TransformSpaceSocket', "Owner Space")
+        self.inputs.new ('FloatFactorSocket', "Influence")
+        self.inputs.new ('xFormSocket', "Target")
+        self.inputs.new ('EnableSocket', "Enable")
+        #
+        self.outputs.new('RelationshipSocket', "Output Relationship")
+
+
+        
+class LinkStretchToNode(Node, LinkNode):
+    '''A node representing Stretch-To'''
+    bl_idname = 'LinkStretchTo'
+    bl_label = "Stretch To"
+    bl_icon = 'CON_STRETCHTO'
+    useTarget : bpy.props.BoolProperty(default=True)
+    initialized : bpy.props.BoolProperty(default = False)
+    def init(self, context):
+        self.inputs.new ('RelationshipSocket', "Input Relationship")
+        self.inputs.new ('FloatFactorSocket', "Head/Tail")
+        self.inputs.new ('BooleanSocket', "UseBBone")
+        self.inputs.new ('FloatSocket', "Original Length")
+        self.inputs.new ('FloatSocket', "Volume Variation")
+        self.inputs.new ('BoolUpdateParentNode', "Use Volume Min")
+        self.inputs.new ('FloatSocket', "Volume Min")
+        self.inputs.new ('BoolUpdateParentNode', "Use Volume Max")
+        self.inputs.new ('FloatSocket', "Volume Max")
+        self.inputs.new ('FloatFactorSocket', "Smooth")
+        self.inputs.new ('EnumMaintainVolumeStretchToSocket', "Maintain Volume")
+        self.inputs.new ('EnumRotationStretchTo', "Rotation")
+        self.inputs.new ('FloatFactorSocket', "Influence")
+        self.inputs.new ('xFormSocket', "Target")
+        self.inputs.new ('EnableSocket', "Enable")
+        #
+        self.outputs.new('RelationshipSocket', "Output Relationship")
+
+        self.initialized = True
+
+
+        
+class LinkDampedTrackNode(Node, LinkNode):
+    '''A node representing Stretch-To'''
+    bl_idname = 'LinkDampedTrack'
+    bl_label = "Damped Track"
+    bl_icon = 'CON_TRACKTO'
+    useTarget : bpy.props.BoolProperty(default=True)
+    initialized : bpy.props.BoolProperty(default = False)
+    def init(self, context):
+        self.inputs.new ('RelationshipSocket', "Input Relationship")
+        self.inputs.new ('FloatFactorSocket', "Head/Tail")
+        self.inputs.new ('BooleanSocket', "UseBBone")
+        self.inputs.new ('EnumTrackAxis', "Track Axis")
+        self.inputs.new ('FloatFactorSocket', "Influence")
+        self.inputs.new ('xFormSocket', "Target")
+        self.inputs.new ('EnableSocket', "Enable")
+        #
+        self.outputs.new('RelationshipSocket', "Output Relationship")
+
+        self.initialized = True
+
+
+        
+class LinkLockedTrackNode(Node, LinkNode):
+    '''A node representing Stretch-To'''
+    bl_idname = 'LinkLockedTrack'
+    bl_label = "Locked Track"
+    bl_icon = 'CON_LOCKTRACK'
+    useTarget : bpy.props.BoolProperty(default=True)
+    initialized : bpy.props.BoolProperty(default = False)
+
+    def init(self, context):
+        self.inputs.new ('RelationshipSocket', "Input Relationship")
+        self.inputs.new ('FloatFactorSocket', "Head/Tail")
+        self.inputs.new ('BooleanSocket', "UseBBone")
+        self.inputs.new ('EnumTrackAxis', "Track Axis")
+        self.inputs.new ('EnumLockAxis', "Lock Axis")
+        self.inputs.new ('FloatFactorSocket', "Influence")
+        self.inputs.new ('xFormSocket', "Target")
+        self.inputs.new ('EnableSocket', "Enable")
+        #
+        self.outputs.new('RelationshipSocket', "Output Relationship")
+
+        self.initialized = True
+
+
+        
+class LinkTrackToNode(Node, LinkNode):
+    '''A node representing Stretch-To'''
+    bl_idname = 'LinkTrackTo'
+    bl_label = "Track To"
+    bl_icon = 'CON_TRACKTO'
+    useTarget : bpy.props.BoolProperty(default=True)
+    initialized : bpy.props.BoolProperty(default = False)
+
+    def init(self, context):
+        self.inputs.new ('RelationshipSocket', "Input Relationship")
+        self.inputs.new ('FloatFactorSocket', "Head/Tail")
+        self.inputs.new ('BooleanSocket', "UseBBone")
+        self.inputs.new ('EnumTrackAxis', "Track Axis")
+        self.inputs.new ('EnumUpAxis', "Up Axis")
+        self.inputs.new ('BooleanSocket', "Use Target Z")
+        self.inputs.new ('TransformSpaceSocket', "Target Space")
+        self.inputs.new ('TransformSpaceSocket', "Owner Space")
+        self.inputs.new ('FloatFactorSocket', "Influence")
+        self.inputs.new ('xFormSocket', "Target")
+        self.inputs.new ('EnableSocket', "Enable")
+        #
+        self.outputs.new('RelationshipSocket', "Output Relationship")
+
+        self.initialized = True
+
+
+        
+class LinkLimitLocationNode(Node, LinkNode):
+    '''A node representing Limit Location'''
+    bl_idname = 'LinkLimitLocation'
+    bl_label = "Limit Location"
+    bl_icon = 'CON_LOCLIMIT'
+    useTarget : bpy.props.BoolProperty(default=False)
+    initialized : bpy.props.BoolProperty(default = False)
+
+    def init(self, context):
+        self.inputs.new ('RelationshipSocket', "Input Relationship")
+        self.inputs.new ('BoolUpdateParentNode', "Use Max X")
+        self.inputs.new ('FloatSocket', "Max X")
+        self.inputs.new ('BoolUpdateParentNode', "Use Min X")
+        self.inputs.new ('FloatSocket', "Min X")
+        self.inputs.new ('BoolUpdateParentNode', "Use Max Y")
+        self.inputs.new ('FloatSocket', "Max Y")
+        self.inputs.new ('BoolUpdateParentNode', "Use Min Y")
+        self.inputs.new ('FloatSocket', "Min Y")
+        self.inputs.new ('BoolUpdateParentNode', "Use Max Z")
+        self.inputs.new ('FloatSocket', "Max Z")
+        self.inputs.new ('BoolUpdateParentNode', "Use Min Z")
+        self.inputs.new ('FloatSocket', "Min Z")
+        self.inputs.new ('BooleanSocket', "Affect Transform")
+        self.inputs.new ('TransformSpaceSocket', "Owner Space")
+        self.inputs.new ('FloatFactorSocket', "Influence")
+        self.inputs.new ('EnableSocket', "Enable")
+        #
+        self.outputs.new('RelationshipSocket', "Output Relationship")
+        self.initialized = True
+
+
+            
+class LinkLimitScaleNode(Node, LinkNode):
+    '''A node representing Limit Scale'''
+    bl_idname = 'LinkLimitScale'
+    bl_label = "Limit Scale"
+    bl_icon = 'CON_SIZELIMIT'
+    useTarget : bpy.props.BoolProperty(default=False)
+    initialized : bpy.props.BoolProperty(default = False)
+
+    def init(self, context):
+        self.inputs.new ('RelationshipSocket', "Input Relationship")
+        self.inputs.new ('BoolUpdateParentNode', "Use Max X")
+        self.inputs.new ('FloatSocket', "Max X")
+        self.inputs.new ('BoolUpdateParentNode', "Use Min X")
+        self.inputs.new ('FloatSocket', "Min X")
+        self.inputs.new ('BoolUpdateParentNode', "Use Max Y")
+        self.inputs.new ('FloatSocket', "Max Y")
+        self.inputs.new ('BoolUpdateParentNode', "Use Min Y")
+        self.inputs.new ('FloatSocket', "Min Y")
+        self.inputs.new ('BoolUpdateParentNode', "Use Max Z")
+        self.inputs.new ('FloatSocket', "Max Z")
+        self.inputs.new ('BoolUpdateParentNode', "Use Min Z")
+        self.inputs.new ('FloatSocket', "Min Z")
+        self.inputs.new ('BooleanSocket', "Affect Transform")
+        self.inputs.new ('TransformSpaceSocket', "Owner Space")
+        self.inputs.new ('FloatFactorSocket', "Influence")
+        self.inputs.new ('EnableSocket', "Enable")
+        #
+        self.outputs.new('RelationshipSocket', "Output Relationship")
+        self.initialized = True
+
+
+            
+class LinkLimitRotationNode(Node, LinkNode):
+    # === Basics ===
+    # Description string
+    '''A node representing Limit Rotation'''
+    bl_idname = 'LinkLimitRotation'
+    bl_label = "Limit Rotation"
+    bl_icon = 'CON_ROTLIMIT'
+    useTarget : bpy.props.BoolProperty(default=False)
+    initialized : bpy.props.BoolProperty(default = False)
+
+    # === Optional Functions ===
+    def init(self, context):
+        self.inputs.new ('RelationshipSocket', "Input Relationship")
+        self.inputs.new ('BoolUpdateParentNode', "Use X")
+        self.inputs.new ('FloatAngleSocket', "Min X")
+        self.inputs.new ('FloatAngleSocket', "Max X")
+        self.inputs.new ('BoolUpdateParentNode', "Use Y")
+        self.inputs.new ('FloatAngleSocket', "Min Y")
+        self.inputs.new ('FloatAngleSocket', "Max Y")
+        self.inputs.new ('BoolUpdateParentNode', "Use Z")
+        self.inputs.new ('FloatAngleSocket', "Min Z")
+        self.inputs.new ('FloatAngleSocket', "Max Z")
+        self.inputs.new ('BooleanSocket', "Affect Transform")
+        self.inputs.new ('TransformSpaceSocket', "Owner Space")
+        self.inputs.new ('FloatFactorSocket', "Influence")
+        self.inputs.new ('EnableSocket', "Enable")
+        #
+        self.outputs.new('RelationshipSocket', "Output Relationship")
+        self.initialized = True
+
+
+        
+class LinkLimitDistanceNode(Node, LinkNode):
+    '''A node representing Limit Distance'''
+    bl_idname = 'LinkLimitDistance'
+    bl_label = "Limit Distance"
+    bl_icon = 'CON_DISTLIMIT'
+    useTarget : bpy.props.BoolProperty(default=True)
+
+    def init(self, context):
+        self.inputs.new ('RelationshipSocket', "Input Relationship")
+        self.inputs.new ('FloatFactorSocket', "Head/Tail")
+        self.inputs.new ('BooleanSocket', "UseBBone")
+        self.inputs.new ('FloatSocket', "Distance")
+        self.inputs.new ('EnumLimitMode', "Clamp Region")
+        self.inputs.new ('BooleanSocket', "Affect Transform")
+        self.inputs.new ('TransformSpaceSocket', "Owner Space")
+        self.inputs.new ('TransformSpaceSocket', "Target Space")
+        self.inputs.new ('FloatFactorSocket', "Influence")
+        self.inputs.new ('xFormSocket', "Target")
+        self.inputs.new ('EnableSocket', "Enable")
+        #
+        self.outputs.new('RelationshipSocket', "Output Relationship")
+
+        
+        
+class LinkTransformationNode(Node, LinkNode):
+    '''A node representing Transformation (Constraint)'''
+    bl_idname = 'LinkTransformation'
+    bl_label = "Transformation"
+    bl_icon = 'CON_TRANSFORM'
+    useTarget : bpy.props.BoolProperty(default=True)
+
+    def init(self, context):
+        hide_me = []
+        self.inputs.new ('RelationshipSocket', "Input Relationship")
+        self.inputs.new ('xFormSocket', "Target")
+        self.inputs.new ('TransformSpaceSocket', "Owner Space")
+        self.inputs.new ('TransformSpaceSocket', "Target Space")
+        self.inputs.new ('BooleanSocket', "Extrapolate")
+        self.inputs.new ('EnumTransformationMap', "Map From")
+        hide_me.append( self.inputs.new ('EnumTransformationRotationMode', "Rotation Mode"))
+        self.inputs.new ('FloatSocket', "X Min From")
+        self.inputs.new ('FloatSocket', "X Max From")
+        self.inputs.new ('FloatSocket', "Y Min From")
+        self.inputs.new ('FloatSocket', "Y Max From")
+        self.inputs.new ('FloatSocket', "Z Min From")
+        self.inputs.new ('FloatSocket', "Z Max From")
+        self.inputs.new ('EnumTransformationMap', "Map To")
+        hide_me.append( self.inputs.new ('EnumTransformationRotationOrder', "Rotation Order"))
+        self.inputs.new ('EnumTransformationAxes', "X Source Axis")
+        self.inputs.new ('FloatSocket', "X Min To")
+        self.inputs.new ('FloatSocket', "X Max To")
+        self.inputs.new ('EnumTransformationAxes', "Y Source Axis")
+        self.inputs.new ('FloatSocket', "Y Min To")
+        self.inputs.new ('FloatSocket', "Y Max To")
+        self.inputs.new ('EnumTransformationAxes', "Z Source Axis")
+        self.inputs.new ('FloatSocket', "Z Min To")
+        self.inputs.new ('FloatSocket', "Z Max To")
+        self.inputs.new ('EnumTransformationTranslationMixMode', "Mix Mode (Translation)")
+        hide_me.append( self.inputs.new ('EnumTransformationRotationMixMode', "Mix Mode (Rotation)"))
+        hide_me.append( self.inputs.new ('EnumTransformationScaleMixMode', "Mix Mode (Scale)"))
+        self.inputs.new ('FloatFactorSocket', "Influence")
+        self.inputs.new ('EnableSocket', "Enable")
+        #
+        self.outputs.new('RelationshipSocket', "Output Relationship")
+        
+        for s in hide_me:
+            s.hide = True
+
+    
+    
+    def display_update(self, parsed_tree, context):
+        node_tree = context.space_data.path[0].node_tree
+        nc = parsed_tree.get(get_signature_from_edited_tree(self, context))
+        if nc:
+            if nc.evaluate_input("Map From") == "ROTATION":
+                self.inputs["Rotation Mode"].hide=False
+            else:
+                self.inputs["Rotation Mode"].hide=True
+            if   nc.evaluate_input("Map To") == "TRANSLATION":
+                    self.inputs["Rotation Order"].hide=True
+                    self.inputs["Mix Mode (Translation)"].hide=False
+                    self.inputs["Mix Mode (Rotation)"].hide=True
+                    self.inputs["Mix Mode (Scale)"].hide=True
+            elif nc.evaluate_input("Map To") == "ROTATION":
+                    self.inputs["Rotation Order"].hide=False
+                    self.inputs["Mix Mode (Translation)"].hide=True
+                    self.inputs["Mix Mode (Rotation)"].hide=False
+                    self.inputs["Mix Mode (Scale)"].hide=True
+            elif nc.evaluate_input("Map To") == "SCALE":
+                    self.inputs["Rotation Order"].hide=True
+                    self.inputs["Mix Mode (Translation)"].hide=True
+                    self.inputs["Mix Mode (Rotation)"].hide=True
+                    self.inputs["Mix Mode (Scale)"].hide=False
+
+
+
+class LinkArmatureNode(Node, LinkNode):
+    """A node representing Blender's Armature Constraint"""
+    bl_idname = "LinkArmature"
+    bl_label = "Armature (Constraint)"
+    bl_icon = "CON_ARMATURE"
+    
+    def init(self, context):
+        self.inputs.new ("RelationshipSocket", "Input Relationship")
+        self.inputs.new("BooleanSocket", "Preserve Volume")
+        self.inputs.new("BooleanSocket", "Use Envelopes")
+        self.inputs.new("BooleanSocket", "Use Current Location")
+        self.inputs.new("FloatSocket", "Influence")
+        self.inputs.new ('EnableSocket', "Enable")
+        self.outputs.new("RelationshipSocket", "Output Relationship")
+    def traverse(self, socket):
+        return default_traverse(self,socket)
+    
+    def draw_buttons(self, context, layout):
+        layout.operator( 'mantis.link_armature_node_add_target' )
+        if (len(self.inputs) > 6):
+            layout.operator( 'mantis.link_armature_node_remove_target' )
+        else:
+            layout.label(text="")
+
+class LinkSplineIKNode(Node, LinkNode):
+    """"A node representing Spline IK"""
+    bl_idname = "LinkSplineIK"
+    bl_label = "Spline IK"
+    bl_icon = "CON_SPLINEIK"
+    
+    def init(self, context):
+        self.inputs.new ("RelationshipSocket", "Input Relationship")
+        self.inputs.new("xFormSocket", "Target")
+        self.inputs.new("IntSocket", "Chain Length")
+        self.inputs.new("BooleanSocket", "Even Divisions")
+        self.inputs.new("BooleanSocket", "Chain Offset")
+        self.inputs.new("BooleanSocket", "Use Curve Radius")
+        self.inputs.new("EnumYScaleMode", "Y Scale Mode")
+        self.inputs.new("EnumXZScaleMode", "XZ Scale Mode")
+        self.inputs.new("BooleanSocket", "Use Original Scale")
+        self.inputs.new("FloatSocket", "Influence")
+        self.outputs.new("RelationshipSocket", "Output Relationship")
+    def traverse(self, socket):
+        return default_traverse(self,socket)
+        
+        
+# DRIVERS!!
+
+class LinkDrivenParameterNode(Node, LinkNode):
+    """Represents a driven parameter in the downstream xForm node."""
+    bl_idname = "LinkDrivenParameter"
+    bl_label = "Driven Parameter"
+    bl_icon = "CONSTRAINT_BONE"
+    
+    def init(self, context):
+        self.inputs.new ( "RelationshipSocket", "Input Relationship" )
+        self.inputs.new ( "DriverSocket", "Driver" )
+        self.inputs.new ( "ParameterStringSocket", "Parameter" )
+        self.inputs.new ( "IntSocket", "Index" )
+        self.inputs.new ('EnableSocket', "Enable")
+        #
+        self.outputs.new( "RelationshipSocket", "Output Relationship" )
+    def traverse(self, socket):
+        return default_traverse(self,socket)
+

+ 960 - 0
misc_containers.py

@@ -0,0 +1,960 @@
+from mantis.node_container_common import *
+
+# The fact that I need this means that some of these classes should
+#  probably be moved to link_containers.py
+from mantis.xForm_containers import xFormRoot, xFormArmature, xFormBone
+
+def TellClasses():
+    return [
+             # utility
+             InputFloat,
+             InputVector,
+             InputBoolean,
+             InputBooleanThreeTuple,
+             InputRotationOrder,
+             InputTransformSpace,
+             InputString,
+             InputQuaternion,
+             InputQuaternionAA,
+             InputMatrix,
+             InputLayerMask,
+             # InputGeometry,
+             InputExistingGeometryObject,
+             InputExistingGeometryData,
+             UtilityMetaRig,
+             UtilityBoneProperties,
+             UtilityDriverVariable,
+             UtilityDriver,
+             UtilityFCurve,
+             UtilitySwitch,
+             UtilityCombineThreeBool,
+             UtilityCombineVector,
+             UtilityCatStrings,
+            ]
+
+#*#-------------------------------#++#-------------------------------#*#
+# G E N E R I C   N O D E S
+#*#-------------------------------#++#-------------------------------#*#
+
+
+# in reality, none of these inputs have names
+#  so I am using the socket name for now
+#  I suppose I could use any name :3
+
+# TODO: the inputs that do not have names should have an empty string
+#   TODO after that: make this work with identifiers instead, stupid.
+
+class InputFloat:
+    '''A node representing float input'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {}
+        self.outputs = {"Float Input" : NodeSocket(name = "Float Input", node=self) }
+        self.parameters = {"Float Input":None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["Float Input"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+    
+class InputVector:
+    '''A node representing vector input'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {}
+        self.outputs = {"VectorSocket" : NodeSocket(name = 'VectorSocket', node=self) }
+        self.parameters = {'VectorSocket':None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["VectorSocket"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+class InputBoolean:
+    '''A node representing boolean input'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {}
+        self.outputs = {"BooleanSocket" : NodeSocket(name = 'BooleanSocket', node=self) }
+        self.parameters = {'BooleanSocket':None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["BooleanSocket"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+class InputBooleanThreeTuple:
+    '''A node representing inheritance'''
+        
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {}
+        self.outputs = {"BooleanThreeTupleSocket" : NodeSocket(name = 'BooleanThreeTupleSocket', node=self) }
+        self.parameters = {'BooleanThreeTupleSocket':None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["BooleanThreeTupleSocket"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+class InputRotationOrder:
+    '''A node representing string input for rotation order'''
+        
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {}
+        self.outputs = {"RotationOrderSocket" : NodeSocket(name = 'RotationOrderSocket', node=self) }
+        self.parameters = {'RotationOrderSocket':None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["RotationOrderSocket"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+class InputTransformSpace:
+    '''A node representing string input for transform space'''
+        
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {}
+        self.outputs = {"TransformSpaceSocket" : NodeSocket(name = 'TransformSpaceSocket', node=self) }
+        self.parameters = {'TransformSpaceSocket':None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["TransformSpaceSocket"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+class InputString:
+    '''A node representing string input'''
+        
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {}
+        self.outputs = {"" : NodeSocket(name = '', node=self) }
+        self.parameters = {'':None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+    def evaluate_input(self, input_name):
+        return self.parameters[""]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+class InputQuaternion:
+    '''A node representing quaternion input'''
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {}
+        self.outputs = {"QuaternionSocket" : NodeSocket(name = 'QuaternionSocket', node=self) }
+        self.parameters = {'QuaternionSocket':None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["QuaternionSocket"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+class InputQuaternionAA:
+    '''A node representing axis-angle quaternion input'''
+        
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {}
+        self.outputs  = {"QuaternionSocketAA" : NodeSocket(name = 'QuaternionSocketAA', node=self) }
+        self.parameters = {'QuaternionSocketAA':None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["QuaternionSocketAA"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+
+
+class InputMatrix:
+    '''A node representing axis-angle quaternion input'''
+        
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {}
+        self.outputs  = {"Matrix" : NodeSocket(name = 'Matrix', node=self) }
+        self.parameters = {'Matrix':None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["Matrix"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        # this node is peculiar for how its data is input
+        # It uses node properties that are not addressable as sockets.
+        from mathutils import Matrix
+        
+        matrix = ( node_prototype.first_row[ 0], node_prototype.first_row[ 1], node_prototype.first_row[ 2], node_prototype.first_row[ 3],
+                   node_prototype.second_row[0], node_prototype.second_row[1], node_prototype.second_row[2], node_prototype.second_row[3],
+                   node_prototype.third_row[ 0], node_prototype.third_row[ 1], node_prototype.third_row[ 2], node_prototype.third_row[ 3],
+                   node_prototype.fourth_row[0], node_prototype.fourth_row[1], node_prototype.fourth_row[2], node_prototype.fourth_row[3], )
+        self.parameters["Matrix"] = Matrix([matrix[0:4], matrix[4:8], matrix[8:12], matrix[12:16]])
+        # print (self.parameters["Matrix"])
+        
+
+# # NOT YET IMPLEMENTED:
+# class InputMatrixNode(Node, MantisNode):
+    # '''A node representing matrix input'''
+    # inputs = 
+    # # the node is implemented as a set of sixteen float inputs
+    # # but I think I can boil it down to one matrix input
+
+
+# class ScaleBoneLengthNode(Node, MantisNode):
+    # '''Scale Bone Length'''
+    # pass
+
+ 
+class UtilityMetaRig:
+    '''A node representing an armature object'''
+
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+          "Meta-Armature" : NodeSocket(is_input = True, name = "Meta-Armature", node=self),
+          "Meta-Bone"     : NodeSocket(is_input = True, name = "Meta-Bone", node=self),
+        }
+        self.outputs = {
+          "Matrix" : NodeSocket(name = "Matrix", node=self),
+        }
+        self.parameters = {
+          "Meta-Armature" : None,
+          "Meta-Bone" : None,
+        }
+        self.node_type = "UTILITY"
+
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+
+    def bExecute(self, bContext = None,):
+        #kinda clumsy, whatever
+        import bpy
+        from mathutils import Matrix
+        m = Matrix.Identity(4)
+        
+        meta_rig  = self.evaluate_input("Meta-Armature")
+        meta_bone = self.evaluate_input("Meta-Bone")
+        
+        if meta_rig:
+            if ( armOb := bpy.data.objects.get(meta_rig) ):
+                m = armOb.matrix_world
+                if ( b := armOb.data.bones.get(meta_bone)):
+                    # calculate the correct object-space matrix
+                    m = Matrix.Identity(3)
+                    bones = []
+                    while (b): bones.append(b); b = b.parent
+                    while (bones): b = bones.pop(); m = m @ b.matrix
+                    m = Matrix.Translation(b.head_local) @ m.to_4x4()
+                    m[3][3] = b.length # this is where I arbitrarily decided to store length
+        
+        
+        self.parameters["Matrix"] = m
+
+
+    def __repr__(self):
+        return self.signature.__repr__()
+
+    def fill_parameters(self):
+        fill_parameters(self)
+        self.parameters["Matrix"] = None
+
+
+class UtilityBoneProperties:
+    '''A node representing an armature object'''
+
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {}
+        self.outputs = {
+          "matrix" : NodeSocket(name = "matrix", node=self),
+          "matrix_local" : NodeSocket(name = "matrix_local", node=self),
+          "matrix_basis" : NodeSocket(name = "matrix_basis", node=self),
+          "head" : NodeSocket(name = "head", node=self),
+          "tail" : NodeSocket(name = "tail", node=self),
+          "length" : NodeSocket(name = "length", node=self),
+          "rotation" : NodeSocket(name = "rotation", node=self),
+          "location" : NodeSocket(name = "location", node=self),
+          "scale" : NodeSocket(name = "scale", node=self),
+        }
+        self.parameters = {
+
+          "matrix":None, 
+          "matrix_local":None, 
+          "matrix_basis":None, 
+          "head":None, 
+          "tail":None, 
+          "length":None, 
+          "rotation":None, 
+          "location":None, 
+          "scale":None, 
+        }
+        self.node_type = "UTILITY"
+
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+
+    def bExecute(self, bContext = None,):
+        pass
+
+    def __repr__(self):
+        return self.signature.__repr__()
+
+    def fill_parameters(self):
+        pass#fill_parameters(self)
+        
+class UtilityDriverVariable:
+    '''A node representing an armature object'''
+
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+          "Variable Type"   : NodeSocket(is_input = True, name = "Variable Type", node = self),
+          "Property"   : NodeSocket(is_input = True, name = "Property", node = self),
+          "Property Index"   : NodeSocket(is_input = True, name = "Property Index", node = self),
+          "Evaluation Space"   : NodeSocket(is_input = True, name = "Evaluation Space", node = self),
+          "Rotation Mode"   : NodeSocket(is_input = True, name = "Rotation Mode", node = self),
+          "xForm 1"   : NodeSocket(is_input = True, name = "xForm 1", node = self),
+          "xForm 2"   : NodeSocket(is_input = True, name = "xForm 2", node = self),
+        }
+        self.outputs = {
+          "Driver Variable" : NodeSocket(name = "Driver Variable", node=self),
+        }
+        self.parameters = {
+          "Variable Type":None, 
+          "Property":None, 
+          "Property Index":None, 
+          "Evaluation Space":None, 
+          "Rotation Mode":None, 
+          "xForm 1":None, 
+          "xForm 2":None,
+        }
+        self.node_type = "LINK" # MUST be run in Pose mode
+        
+    def evaluate_input(self, input_name):
+        if input_name == 'Property':
+            if self.inputs['Property'].is_linked:
+            # get the name instead...
+                trace = trace_single_line(self, input_name)
+                return trace[1].name # the name of the socket
+            return self.parameters["Property"]
+        return evaluate_input(self, input_name)
+        
+    def GetxForm(self, index=1):
+        trace = trace_single_line(self, "xForm 1" if index == 1 else "xForm 2")
+        for node in trace[0]:
+            if (node.__class__ in [xFormRoot, xFormArmature, xFormBone]):
+                return node #this will fetch the first one, that's good!
+        return None
+
+    def bExecute(self, bContext = None,):
+        prepare_parameters(self)
+        #prPurple ("Executing Driver Variable Node")
+        xForm1 = self.GetxForm()
+        xForm2 = self.GetxForm(index=2)
+        # kinda clumsy
+        if xForm1 : xForm1 = xForm1.bGetObject()
+        if xForm2 : xForm2 = xForm2.bGetObject()
+        
+        v_type = self.evaluate_input("Variable Type")
+        i = self.evaluate_input("Property Index"); dVarChannel = ""
+        if (i >= 0): #negative values will use the vector property.
+            if self.evaluate_input("Property") == 'location':
+                if   i == 0: dVarChannel = "LOC_X"
+                elif i == 1: dVarChannel = "LOC_Y"
+                elif i == 2: dVarChannel = "LOC_Z"
+                else: raise RuntimeError("Invalid property index for %s" % self)
+            if self.evaluate_input("Property") == 'rotation':
+                if   i == 0: dVarChannel = "ROT_X"
+                elif i == 1: dVarChannel = "ROT_Y"
+                elif i == 2: dVarChannel = "ROT_Z"
+                elif i == 3: dVarChannel = "ROT_W"
+                else: raise RuntimeError("Invalid property index for %s" % self)
+            if self.evaluate_input("Property") == 'scale':
+                if   i == 0: dVarChannel = "SCALE_X"
+                elif i == 1: dVarChannel = "SCALE_Y"
+                elif i == 2: dVarChannel = "SCALE_Z"
+                elif i == 3: dVarChannel = "SCALE_AVG"
+                else: raise RuntimeError("Invalid property index for %s" % self)
+        if dVarChannel: v_type = "TRANSFORMS"
+        
+        my_var = {
+            "owner"         : xForm1, # will be filled in by Driver
+            "prop"          : self.evaluate_input("Property"), # will be filled in by Driver
+            "type"          : v_type,
+            "space"         : self.evaluate_input("Evaluation Space"),
+            "rotation_mode" : self.evaluate_input("Rotation Mode"),
+            "xForm 1"       : self.GetxForm(index = 1),
+            "xForm 2"       : self.GetxForm(index = 2),
+            "channel"       : dVarChannel,}
+        
+        # Push parameter to downstream connected node.connected:
+        if (out := self.outputs["Driver Variable"]).is_linked:
+            self.parameters[out.name] = my_var
+            for link in out.links:
+                link.to_node.parameters[link.to_socket] = my_var
+            
+
+    def __repr__(self):
+        return self.signature.__repr__()
+
+    def fill_parameters(self):
+        fill_parameters(self)
+
+class UtilityFCurve:
+    '''A node representing an armature object'''
+
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {}
+        self.outputs = {
+          "fCurve" : NodeSocket(name = "fCurve", node=self),
+        }
+        self.parameters = {
+          "fCurve":None, 
+        }
+        self.node_type = "UTILITY"
+        setup_custom_props(self)
+
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+
+    def bExecute(self, bContext = None,):
+        prepare_parameters(self)
+        from mantis.utilities import get_node_prototype
+        np = get_node_prototype(self.signature, self.base_tree)
+        keys = []
+        #['amplitude', 'back', 'bl_rna', 'co', 'co_ui', 'easing', 'handle_left', 'handle_left_type', 'handle_right', 'handle_right_type',
+        # 'interpolation', 'period', 'rna_type', 'select_control_point', 'select_left_handle', 'select_right_handle', 'type']
+
+        if np.use_kf_nodes:
+            pass # for now
+        else:
+            fc_ob = np.fake_fcurve_ob
+            fc = fc_ob.animation_data.action.fcurves[0]
+            for k in fc.keyframe_points:
+                key = {}
+                for prop in dir(k):
+                    if ("__" in prop) or ("bl_" in prop): continue
+                    #it's __name__ or bl_rna or something
+                    key[prop] = getattr(k, prop)
+                keys.append(key)
+        
+        # Push parameter to downstream connected node.connected:
+        # TODO: find out if this is necesary, even
+        if (out := self.outputs["fCurve"]).is_linked:
+            self.parameters[out.name] = keys
+            for link in out.links:
+                link.to_node.parameters[link.to_socket] = keys
+                
+
+    def __repr__(self):
+        return self.signature.__repr__()
+
+    def fill_parameters(self):
+        fill_parameters(self)
+
+class UtilityDriver:
+    '''A node representing an armature object'''
+
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+          "Driver Type"   : NodeSocket(is_input = True, name = "Driver Type", node = self),
+          "Expression"   : NodeSocket(is_input = True, name = "Expression", node = self),
+          "fCurve"   : NodeSocket(is_input = True, name = "fCurve", node = self),
+        }
+        self.outputs = {
+          "Driver" : NodeSocket(name = "Driver", node=self),
+        }
+        from mantis.drivers import MantisDriver
+        self.parameters = {
+          "Driver Type":None, 
+          "Expression":None, 
+          "fCurve":None,
+          "Driver":MantisDriver(), 
+        }
+        self.node_type = "DRIVER" # MUST be run in Pose mode
+        setup_custom_props(self)
+
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+
+    def bExecute(self, bContext = None,):
+        prepare_parameters(self)
+        from mantis.drivers import MantisDriver
+        #prPurple("Executing Driver Node")
+        my_vars = []
+        
+        for inp in list(self.inputs.keys() )[3:]:
+            if (new_var := self.evaluate_input(inp)):
+                new_var["name"] = inp
+                my_vars.append(new_var)
+            else:
+                raise RuntimeError("Failed to initialize Driver variable")
+        my_driver ={ "owner"      :  None,
+                     "prop"       :  None, # will be filled out in the node that uses the driver
+                     "expression" :  self.evaluate_input("Expression"),
+                     "ind"        :  -1, # same here
+                     "type"       :  self.evaluate_input("Driver Type"),
+                     "vars"       :  my_vars,
+                     "keys"       :  self.evaluate_input("fCurve"), }
+        
+        my_driver = MantisDriver(my_driver)
+        
+        self.parameters["Driver"].update(my_driver)
+        print("Initializing driver %s " % (wrapPurple(self.__repr__())) )
+
+    def __repr__(self):
+        return self.signature.__repr__()
+
+    def fill_parameters(self):
+        fill_parameters(self)
+
+class UtilitySwitch:
+    '''A node representing an armature object'''
+
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+          "xForm"   : NodeSocket(is_input = True, name = "xForm", node = self),
+          "Parameter"   : NodeSocket(is_input = True, name = "Parameter", node = self),
+          "Parameter Index"   : NodeSocket(is_input = True, name = "Parameter Index", node = self),
+          "Invert Switch" : NodeSocket(is_input = True, name = "Invert Switch", node = self),
+        }
+        self.outputs = {
+          "Driver" : NodeSocket(name = "Driver", node=self),
+        }
+        from mantis.drivers import MantisDriver
+        self.parameters = {
+          "xForm":None, 
+          "Parameter":None,
+          "Parameter Index":None, 
+          "Invert Switch":None,
+          "Driver":MantisDriver(), # empty for now
+        }
+        self.node_type = "DRIVER" # MUST be run in Pose mode
+
+    def evaluate_input(self, input_name):
+        if input_name == 'Parameter':
+            if self.inputs['Parameter'].is_connected:
+                trace = trace_single_line(self, input_name)
+                return trace[1].name # the name of the socket
+            return self.parameters["Parameter"]
+        return evaluate_input(self, input_name)
+
+    def GetxForm(self,):
+        trace = trace_single_line(self, "xForm" )
+        for node in trace[0]:
+            if (node.__class__ in [xFormRoot, xFormArmature, xFormBone]):
+                return node #this will fetch the first one, that's good!
+        return None
+
+    def bExecute(self, bContext = None,):
+        #prepare_parameters(self)
+        #prPurple ("Executing Switch Node")
+        xForm = self.GetxForm()
+        if xForm : xForm = xForm.bGetObject() 
+        if not xForm:
+            raise RuntimeError("Could not evaluate xForm for %s" % self)
+        from mantis.drivers import MantisDriver
+        my_driver ={ "owner" : None,
+                     "prop"  : None, # will be filled out in the node that uses the driver 
+                     "ind"   : -1, # same here
+                     "type"  : "SCRIPTED",
+                     "vars"  : [ { "owner" : xForm,
+                                   "prop"  : self.evaluate_input("Parameter"),
+                                   "name"  : "a",
+                                   "type"  : "SINGLE_PROP", } ],
+                     "keys"  : [ { "co":(0,0),
+                                   "interpolation": "LINEAR",
+                                   "type":"KEYFRAME",}, #display type
+                                 { "co":(1,1),
+                                   "interpolation": "LINEAR",
+                                   "type":"KEYFRAME",},], }
+        my_driver   ["expression"] = "a"
+        
+        my_driver = MantisDriver(my_driver)
+        # this makes it so I can check for type later!
+        
+        if self.evaluate_input("Invert Switch") == True:
+            my_driver   ["expression"] = "1 - a"
+        
+        # this way, regardless of what order things are handled, the
+        #  driver is sent to the next node.
+        # In the case of some drivers, the parameter may be sent out
+        #  before it's filled in (because there is a circular dependency)
+        # I want to support this behaviour because Blender supports it,
+        #  but I also do not want to support it because it makes things
+        #  more complex and IMO it's bad practice.
+        # We do not make a copy. We update the driver, so that
+        #  the same instance is filled out. 
+        self.parameters["Driver"].update(my_driver)
+        print("Initializing driver %s " % (wrapPurple(self.__repr__())) )
+
+    def __repr__(self):
+        return self.signature.__repr__()
+
+    def fill_parameters(self):
+        fill_parameters(self)
+        
+class UtilityCombineThreeBool:
+    '''A node for combining three booleans into a boolean three-tuple'''
+
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+          "X"   : NodeSocket(is_input = True, name = "X", node = self),
+          "Y"   : NodeSocket(is_input = True, name = "Y", node = self),
+          "Z"   : NodeSocket(is_input = True, name = "Z", node = self),
+        }
+        self.outputs = {
+          "Three-Bool" : NodeSocket(name = "Three-Bool", node=self),
+        }
+        self.parameters = {
+          "X":None,
+          "Y":None,
+          "Z":None, }
+        self.node_type = "UTILITY"
+
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+
+    def bExecute(self, bContext = None,):
+        #prPurple("Executing CombineThreeBool Node")
+        #prepare_parameters(self)
+        self.parameters["Three-Bool"] = (
+          self.evaluate_input("X"),
+          self.evaluate_input("Y"),
+          self.evaluate_input("Z"), )
+        # DO:
+        # figure out how to get the driver at execute-time
+        #  because Blender allows circular dependencies in drivers
+        #  (sort of), I need to adopt a more convoluted way of doing
+        #  things here or elsewhere.
+
+    def __repr__(self):
+        return self.signature.__repr__()
+
+    def fill_parameters(self):
+        fill_parameters(self)
+
+# Note this is a copy of the above. This needs to be de-duplicated into
+  # a simpler CombineVector node_container.
+  # TODO
+class UtilityCombineVector:
+    '''A node for combining three floats into a vector'''
+
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+          "X"   : NodeSocket(is_input = True, name = "X", node = self),
+          "Y"   : NodeSocket(is_input = True, name = "Y", node = self),
+          "Z"   : NodeSocket(is_input = True, name = "Z", node = self),
+        }
+        self.outputs = {
+          "Vector" : NodeSocket(name = "Vector", node=self),
+        }
+        self.parameters = {
+          "X":None,
+          "Y":None,
+          "Z":None, }
+        self.node_type = "UTILITY"
+
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+
+    def bExecute(self, bContext = None,):
+        #prPurple("Executing CombineVector Node")
+        prepare_parameters(self)
+        self.parameters["Vector"] = (
+          self.evaluate_input("X"),
+          self.evaluate_input("Y"),
+          self.evaluate_input("Z"), )
+
+    def __repr__(self):
+        return self.signature.__repr__()
+
+    def fill_parameters(self):
+        fill_parameters(self)
+
+class UtilityCatStrings:
+    '''A node representing an armature object'''
+
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+          "String_1"   : NodeSocket(is_input = True, name = "String_1", node = self),
+          "String_2"   : NodeSocket(is_input = True, name = "String_2", node = self),
+        }
+        self.outputs = {
+          "OutputString" : NodeSocket(name = "OutputString", node=self),
+        }
+        self.parameters = {
+          "String_1":None, 
+          "String_2":None,
+        }
+        self.node_type = "UTILITY"
+
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+
+    def bExecute(self, bContext = None,):
+        self.parameters["OutputString"] = self.evaluate_input("String_1")+self.evaluate_input("String_2")
+
+    def __repr__(self):
+        return self.signature.__repr__()
+
+    def fill_parameters(self):
+        fill_parameters(self)
+
+class InputLayerMask:
+    '''A node representing an armature object'''
+
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+        }
+        self.outputs = {
+          "Layer Mask" : NodeSocket(is_input = True, name = "Layer Mask", node = self),
+        }
+        self.parameters = {
+
+          "Layer Mask":None, 
+        }
+        self.node_type = "UTILITY"
+
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+
+    def bExecute(self, bContext = None,):
+        pass
+
+    def __repr__(self):
+        return self.signature.__repr__()
+
+    def fill_parameters(self):
+        fill_parameters(self)
+
+# class InputGeometry:
+    # '''A node representing an armature object'''
+
+    # def __init__(self, signature, base_tree):
+        # self.base_tree=base_tree
+        # self.executed = False
+        # self.signature = signature
+        # self.inputs = {
+          # "Geometry Name"   : NodeSocket(is_input = True, to_socket = "Geometry Name", to_node = self),
+        # }
+        # self.outputs = {
+          # "Geometry" : NodeSocket(from_socket = "Geometry", from_node=self),
+        # }
+        # self.parameters = {
+
+          # "Geometry Name":None, 
+          # "Geometry":None, 
+        # }
+        # self.node_type = "UTILITY"
+
+    # def evaluate_input(self, input_name):
+        # return evaluate_input(self, input_name)
+
+    # def bExecute(self, bContext = None,):
+        # pass
+
+    # def __repr__(self):
+        # return self.signature.__repr__()
+
+    # def fill_parameters(self, node_prototype):
+        # fill_parameters(self, node_prototype)
+
+class InputExistingGeometryObject:
+    '''A node representing an existing object'''
+
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+          "Name"   : NodeSocket(is_input = True, name = "Name", node = self),
+        }
+        self.outputs = {
+          "Object" : NodeSocket(is_input = False, name = "Object", node=self),
+        }
+        self.parameters = {
+          "Name":None, 
+          "Object":None, 
+        }
+        self.node_type = "UTILITY"
+        
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+
+    def bGetObject(self):
+        from bpy import data
+        return data.objects.get( self.evaluate_input("Name") )
+        
+    def bExecute(self, bContext = None,):
+        pass
+
+        # DO: make this data, of course
+        # try curve and then mesh
+        # probably should print a warning on the node if it is ambiguous
+
+    def __repr__(self):
+        return self.signature.__repr__()
+
+    def fill_parameters(self):
+        fill_parameters(self)
+
+class InputExistingGeometryData:
+    '''A node representing existing object data'''
+
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+          "Name"   : NodeSocket(is_input = True, name = "Name", node = self),
+        }
+        self.outputs = {
+          "Geometry" : NodeSocket(is_input = False, name = "Geometry", node=self),
+        }
+        self.parameters = {
+
+          "Name":None, 
+          "Geometry":None, 
+        }
+        self.node_type = "UTILITY"
+
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+
+    def bGetObject(self):
+        from bpy import data
+        # first try Curve, then try Mesh
+        bObject = data.curves.get(self.evaluate_input("Name"))
+        if not bObject:
+            bObject = data.meshes.get(self.evaluate_input("Name"))
+        return bObject
+        
+    def bExecute(self, bContext = None,):
+        pass
+
+    def __repr__(self):
+        return self.signature.__repr__()
+
+    def fill_parameters(self):
+        fill_parameters(self)

+ 2125 - 0
node_container_classes.py

@@ -0,0 +1,2125 @@
+# generic node classes
+
+#everything here sucks and is dumb and needs renaiming
+
+# this is stupid, lol
+def TellClasses():
+             # xForm
+    return [ xFormRoot,
+             xFormArmature,
+             xFormBone,
+             # special
+             LinkInherit,
+             # copy
+             LinkCopyLocation,
+             LinkCopyRotation,
+             LinkCopyScale,
+             LinkCopyTransforms,
+             # limit
+             LinkLimitLocation,
+             LinkLimitRotation,
+             LinkLimitScale,
+             LinkLimitDistance,
+             # tracking
+             LinkStretchTo,
+             LinkDampedTrack,
+             LinkLockedTrack,
+             LinkTrackTo,
+             #misc
+             LinkInheritConstraint,
+             # IK
+             LinkInverseKinematics,
+             # utility
+             InputFloat,
+             InputVector,
+             InputBoolean,
+             InputBooleanThreeTuple,
+             InputRotationOrder,
+             InputTransformSpace,
+             InputString,
+             InputQuaternion,
+             InputQuaternionAA,
+             InputMatrix,
+            ]
+    #kinda a dumb way to do this but it werks so whatever
+            
+            
+
+
+# have these so far
+  # simple nodes:
+             # InputFloatNode,
+             # InputVectorNode,
+             # InputBooleanNode,
+             # InputBooleanThreeTupleNode,
+             # InputRotationOrderNode,
+             # InputTransformSpaceNode,
+             # InputStringNode,
+             # InputQuaternionNode,
+             # InputQuaternionNodeAA,
+             # InputMatrixNode,
+
+  # xForm nodes:
+        # xFormNullNode,
+        # xFormBoneNode,
+        # xFormRootNode,
+        # xFormArmatureNode,
+        
+
+  # Link nodes:
+             # LinkInheritNode,
+             # LinkInverseKinematics,
+             # LinkCopyLocationNode,
+             # LinkCopyRotationNode,
+             # LinkCopyScaleNode,
+             # LinkInheritConstraintNode,
+             # LinkCopyTransformNode,
+             # LinkStretchToNode,
+             # LinkDampedTrackNode,
+             # LinkLockedTrackNode,
+             # LinkTrackToNode,
+             # LinkLimitLocationNode,
+             # LinkLimitScaleNode,
+             # LinkLimitRotationNode,
+             # LinkLimitDistanceNode,]
+             
+             
+#eventually add this:
+    # def print_to_mantis_script():
+        # #gonna eventually make this into a script
+        # # that can be written and read from a text file
+        # # and loaded into the interpretor
+        # pass
+# 
+
+
+
+# DO THIS:
+#  each node should have a name or UUID, since there must be a way to
+#   associate them with the lines that are read from the input-graph
+#  actually I cna and should just use the signature, since it can gimme
+#   any prototype node I need, and it encodes relationships, too.
+
+
+def fill_parameters(node_container, node_prototype):
+    from .utilities import to_mathutils_value
+    for key, value in node_container.parameters.items():
+        node_socket = node_prototype.inputs.get(key)
+        if not node_socket:
+            #maybe the node socket has no name
+            if ( ( len(node_prototype.inputs) == 0) and ( len(node_prototype.outputs) == 1) ):
+                # this is a simple input node.
+                node_socket = node_prototype.outputs[0]
+            elif key == 'Mute':
+                node_container.parameters[key] = node_prototype.mute
+                continue
+            else: # really don't know!
+                raise RuntimeError("No node socket found for " + key + " when filling out node parameters.")
+                continue
+        
+        if node_socket.bl_idname in  ['RelationshipSocket', 'xFormSocket']:
+            continue
+        
+        elif hasattr(node_socket, "default_value"):
+            default_value_type = type(node_socket.default_value)
+            #print (default_value_type)
+            math_val = to_mathutils_value(node_socket)
+            if math_val:
+                node_container.parameters[key] = 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) ):
+                node_container.parameters[key] = node_socket.default_value
+            # HACK: there should be no sets, I think, but...
+            elif default_value_type == set:
+                node_container.parameters[key] = node_socket.default_value
+                # TODO: make this make sense sometime in the future!
+                # There should not be any sets!
+            else:
+                raise RuntimeError("No value found for " + key + " when filling out node parameters for " + node_prototype.name)
+        else:
+                print (key, node_socket)
+                # do: remove these from parameters maybe
+                # since they are always None if not connected
+    # for key, value in node_container.parameters.items():
+        # if value:
+            # print (key, value)
+
+def evaluate_input(node_container, input_name):
+    # for simple cases
+    trace = trace_single_line(node_container, input_name)
+    prop = trace[0][-1].parameters.get(trace[1].to_socket)
+    # WHY doesn't this work for the Matrix inputs .. ?
+    
+    return prop
+
+
+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
+    
+    
+
+def trace_single_line(node_container, input_name):
+    """ Tells the depth of a node within the node tree. """
+    nodes = [node_container]
+    if hasattr(node_container, "inputs"):
+        # Trace a single line
+        if (socket := node_container.inputs.get(input_name) ):
+            while (socket.is_connected):
+                other = socket.from_node.outputs.get(socket.from_socket)
+                if (other):
+                    socket = other
+                    if socket.can_traverse:
+                        socket = socket.traverse_target
+                        nodes.append(socket.to_node)
+                    else: # this is an output.
+                        nodes.append(socket.from_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_connected):
+                other = socket.to_node.inputs.get(socket.to_socket)
+                if (other):
+                    socket = other
+                    if socket.can_traverse:
+                        socket = socket.traverse_target
+                        nodes.append(socket.from_node)
+                    else: # this is an input.
+                        nodes.append(socket.to_node)
+                        break
+                else:
+                    break
+    return nodes, socket
+
+def node_depth(node_container):
+    maxlen = 0
+    for nodes in trace_node_lines(node_container):
+        if (len(nodes) > maxlen):
+            maxlen = len(nodes)
+    return maxlen
+        
+def get_parent(node_container):
+    node_line, socket = trace_single_line(node_container, "Relationship")
+    parent_nc = None
+    for i in range(len(node_line)):
+        print (node_line[i])
+        # check each of the possible parent types.
+        if ( isinstance(node_line[ i ], LinkInherit) ):
+            try: # it's the next one
+                return node_line[ i + 1 ]
+            except IndexError: # if there is no next one...
+                return None # then there's no parent!
+    return None
+    # TO DO!
+    #
+    # 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, constraint, input_name = "Target"):
+    from bpy.types import PoseBone, Object
+    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 constraint target!")
+    if (input_name == 'Target'): # this is sloppy, but it werks
+        constraint.target, constraint.subtarget = target, subtarget
+    elif (input_name == 'Pole Target'):
+        constraint.pole_target, constraint.pole_subtarget = target, subtarget
+
+
+
+
+
+
+class NodeSocket:
+    # this is not meant to be a particularly robust class
+    # e.g., there will be no disconnect() method since it isn't needed
+    # I just wanna have something persistent (an object)
+    # I'd perfer to use pointers and structs, whatever
+    is_input = False
+    is_connected = False
+    from_node = None
+    to_node = None
+    from_socket = None
+    to_socket = None
+    can_traverse = False
+    traverse_target = None
+    
+    def __init__(self, is_input = False,
+                 from_socket = None, to_socket = None,
+                 from_node = None, to_node = None,
+                 traverse_target = None):
+        self.from_socket = from_socket
+        self.to_socket   = to_socket
+        self.from_node   = from_node
+        self.to_node     = to_node
+        self.is_input    = is_input
+        if (self.is_input and (self.from_node or self.from_socket)):
+            self.is_connected = True
+        elif ( not self.is_input and (self.to_node or self.to_socket)):
+            self.is_connected = True
+        self.set_traverse_target(traverse_target)
+        
+    def connect(self, node, socket):
+        if (self.is_input):
+            self.from_node   = node
+            self.from_socket = socket
+        else:
+            self.to_node   =  node
+            self.to_socket = socket
+        self.is_connected = True
+    
+    def set_traverse_target(self, traverse_target):
+        if (traverse_target):
+            self.traverse_target = traverse_target
+            self.can_traverse = True
+    
+    def __repr__(self):
+        if self.is_input:
+            return ( self.to_node.__repr__() + "::" + self.to_socket )
+        else:
+            return (self.from_node.__repr__() + "::" + self.from_socket)
+    
+    
+
+
+#*#-------------------------------#++#-------------------------------#*#
+# X - F O R M   N O D E S
+#*#-------------------------------#++#-------------------------------#*#
+
+# class xFormNull:
+    # '''A node representing an Empty object'''
+    # inputs =
+    # {
+     # "Name":None,
+     # "Rotation Order":None,
+     # "Matrix":None,
+     # "Relationship":None,
+    # }
+    # outputs =
+    # {
+     # "xFormOut":None,
+    # }
+    # parameters =
+    # {
+     # "Name":None,
+     # "Rotation Order":None,
+     # "Matrix":None,
+     # "Relationship":None,
+    # }
+    
+    # def evaluate_input(self, input):
+        # pass
+    
+    # def instantiate_blender_object(self):
+        # pass
+# for whatever reason, the above isn't implemented yet in the node-tree
+# so I'm not implementing it here, either
+
+
+class xFormRoot:
+    '''A node representing the root of the scene.'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {}
+        self.outputs = {"xForm Out":NodeSocket(from_socket="xForm Out", from_node = self),}
+        self.parameters = {}
+        self.links = {} # leave this empty for now!
+        self.node_type = 'XFORM'
+    
+    def init_to_node_line(line,):
+        pass
+    
+    def evaluate_input(self, input_name):
+        return "ROOT"
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+class xFormArmature:
+    '''A node representing an armature object'''
+    
+    bObject = None
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+         "Name"           : NodeSocket(is_input = True, to_socket = "Name", to_node = self),
+         "Rotation Order" : NodeSocket(is_input = True, to_socket = "Rotation Order", to_node = self),
+         "Matrix"         : NodeSocket(is_input = True, to_socket = "Matrix", to_node = self),
+         "Relationship"   : NodeSocket(is_input = True, to_socket = "Relationship", to_node = self),
+        }
+        self.outputs = {
+         "xForm Out" : NodeSocket(from_socket="xForm Out", from_node = self),
+        }
+        self.parameters = {
+         "Name":None,
+         "Rotation Order":None,
+         "Matrix":None,
+         "Relationship":None,
+        }
+        self.links = {} # leave this empty for now!
+        # now set up the traverse target...
+        self.inputs["Relationship"].set_traverse_target(self.outputs["xForm Out"])
+        self.outputs["xForm Out"].set_traverse_target(self.inputs["Relationship"])
+        self.node_type = 'XFORM'
+    
+        
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+    
+    def bExecute(self, bContext = None,):
+        from .utilities import get_node_prototype
+        
+        import bpy
+        if (not isinstance(bContext, bpy.types.Context)):
+            raise RuntimeError("Incorrect context")
+
+        name = self.evaluate_input("Name")
+        matrix = self.evaluate_input('Matrix')
+
+
+        #check if an object by the name exists
+        if (name) and (ob := bpy.data.objects.get(name)):
+            for pb in ob.pose.bones:
+                # clear it, even after deleting the edit bones, 
+                #  if we create them again the pose bones will be reused
+                while (pb.constraints):
+                    pb.constraints.remove(pb.constraints[-1])
+                pb.location = (0,0,0)
+                pb.rotation_euler = (0,0,0)
+                pb.rotation_quaternion = (1.0,0,0,0)
+                pb.rotation_axis_angle = (0,0,1.0,0)
+                pb.scale = (1.0,1.0,1.0)
+        else:
+            # Create the Object
+            ob = bpy.data.objects.new(name, bpy.data.armatures.new(name)) #create ob
+            if (ob.name != name):
+                raise RuntimeError("Could not create xForm object", name)
+            
+        self.bObject = ob.name
+        
+        ob.matrix_world = matrix
+        
+        
+        # first, get the parent object
+        parent_node = get_parent(self)
+        if hasattr(parent_node, "bObject"):
+            # this won't work of course, TODO
+            self.bObject.parent = parent_node.bObject
+        
+        # Link to Scene:
+        if (ob.name not in bContext.view_layer.active_layer_collection.collection.objects):
+            bContext.view_layer.active_layer_collection.collection.objects.link(ob)
+        #self.bParent(bContext)
+        
+        # Finalize the action
+        # prevAct = bContext.view_layer.objects.active
+        bContext.view_layer.objects.active = ob
+        bpy.ops.object.mode_set(mode='EDIT')
+        print ("Changing Armature Mode to EDIT")
+        # clear it
+        while (len(ob.data.edit_bones) > 0):
+            ob.data.edit_bones.remove(ob.data.edit_bones[0])
+        # bContext.view_layer.objects.active = prevAct
+
+        print ("Created Armature object: \""+ ob.name +"\"")
+        
+        
+        self.executed = True
+    
+    # # not used yet
+    # #
+    # def bFinalize(self, bContext = None):
+        # import bpy
+        # ob = self.bGetObject()
+        # prevAct = bContext.view_layer.objects.active
+        # bContext.view_layer.objects.active = ob
+        # bpy.ops.object.mode_set(mode='OBJECT')
+        # print ("Changing Armature Mode to OBJECT")
+        # bContext.view_layer.objects.active = prevAct
+
+    def bGetObject(self, mode = ''):
+        import bpy
+        return bpy.data.objects[self.bObject]
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+class xFormBone:
+    '''A node representing a bone in an armature'''
+    # DO: make a way to identify which armature this belongs to
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+         "Name"           : NodeSocket(is_input = True, to_socket = "Name", to_node = self,),
+         "Rotation Order" : NodeSocket(is_input = True, to_socket = "Rotation Order", to_node = self,),
+         "Matrix"         : NodeSocket(is_input = True, to_socket = "Matrix", to_node = self,),
+         "Relationship"   : NodeSocket(is_input = True, to_socket = "Relationship", to_node = self,),
+         # IK settings
+         "IK Stretch"     : NodeSocket(is_input = True, to_socket = "IK Stretch", to_node = self,),
+         "Lock IK"        : NodeSocket(is_input = True, to_socket = "Lock IK", to_node = self,),
+         "IK Stiffness"   : NodeSocket(is_input = True, to_socket = "IK Stiffness", to_node = self,),
+         "Limit IK"       : NodeSocket(is_input = True, to_socket = "Limit IK", to_node = self,),
+         "X Min"          : NodeSocket(is_input = True, to_socket = "X Min", to_node = self,),
+         "X Max"          : NodeSocket(is_input = True, to_socket = "X Max", to_node = self,),
+         "Y Min"          : NodeSocket(is_input = True, to_socket = "Y Min", to_node = self,),
+         "Y Max"          : NodeSocket(is_input = True, to_socket = "Y Max", to_node = self,),
+         "Z Min"          : NodeSocket(is_input = True, to_socket = "Z Min", to_node = self,),
+         "Z Max"          : NodeSocket(is_input = True, to_socket = "Z Max", to_node = self,),
+        }
+        self.outputs = {
+         "xForm Out"       : NodeSocket(from_socket = "xForm Out", from_node = self),
+        }
+        self.parameters = {
+         "Name":None,
+         "Rotation Order":None,
+         "Matrix":None,
+         "Relationship":None,
+         # IK settings
+         "IK Stretch":None,
+         "Lock IK":None,
+         "IK Stiffness":None,
+         "Limit IK":None,
+         "X Min":None,
+         "X Max":None,
+         "Y Min":None,
+         "Y Max":None,
+         "Z Min":None,
+         "Z Max":None,
+        }
+        self.links = {} # leave this empty for now!
+        # now set up the traverse target...
+        self.inputs["Relationship"].set_traverse_target(self.outputs["xForm Out"])
+        self.outputs["xForm Out"].set_traverse_target(self.inputs["Relationship"])
+        self.node_type = 'XFORM'
+        
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+    
+    def bGetParentArmature(self):
+        finished = False
+        if (trace := trace_single_line(self, "Relationship")[0] ) :
+            for i in range(len(trace)):
+                # have to look in reverse, actually
+                if ( isinstance(trace[ i ], xFormArmature ) ):
+                    return trace[ i ].bGetObject()
+        return None
+        #should do the trick...
+    
+    def bSetParent(self, eb):
+        from bpy.types import EditBone
+        parent_nc = get_parent(self)
+        parent = parent_nc.bGetObject(mode = 'EDIT')
+        if isinstance(parent, EditBone):
+            print (parent.name)
+            eb.parent = parent
+        else:
+            print(parent)
+        # otherwise, no need to do anything.
+        
+         
+    def bExecute(self, bContext = None,): #possibly will need to pass context?
+        import bpy
+        from mathutils import Vector
+        if (not isinstance(bContext, bpy.types.Context)):
+            raise RuntimeError("Incorrect context")
+        xF = self.bGetParentArmature()
+        
+        name = self.evaluate_input("Name")
+        matrix = self.evaluate_input("Matrix")
+        
+        length = matrix[3][3]
+        matrix[3][3] = 1.0 # set this bacc, could cause problems otherwise.
+        
+        if (xF):
+            if (xF.mode != "EDIT"):
+                raise RuntimeError("Armature Object Not in Edit Mode, exiting...")
+        else:
+            raise RuntimeError("No armature object to add bone to.")
+        #
+        # Create the Object
+        d = xF.data
+        eb = d.edit_bones.new(name)
+        
+        if (eb.name != name):
+            raise RuntimeError("Could not create bone ", name, "; Perhaps there is a duplicate bone name in the node tree?")
+        eb.matrix  = matrix.copy()
+        tailoffset = Vector((0,length,0)) #Vector((0,self.tailoffset, 0))
+        tailoffset = matrix.copy().to_3x3() @ tailoffset
+        eb.tail    = eb.head + tailoffset
+        
+        if (eb.name != name):
+            raise RuntimeError("Could not create edit bone: ", name)
+        self.bObject = eb.name
+        # The bone should have relationships going in at this point.
+        
+        
+            
+        self.bSetParent(eb)
+        
+        
+        return
+        self.bParent(bContext)
+
+        print ("Created Bone: \""+ eb.name+ "\" in \"" + self.bGetParentArmature().name +"\"")
+        self.executed = True
+
+    def bFinalize(self, bContext = None):
+        # prevAct = bContext.view_layer.objects.active
+        # bContext.view_layer.objects.active = ob
+        # bpy.ops.object.mode_set(mode='OBJECT')
+        # bContext.view_layer.objects.active = prevAct
+        #
+        #get relationship
+        # ensure we have a pose bone...
+        # set the ik parameters
+        pass
+
+
+    def bGetObject(self, mode = 'POSE'):
+        if (mode == 'EDIT'):
+            try:
+                return self.bGetParentArmature().data.edit_bones[self.bObject]
+            except KeyError:
+                return None
+        if (mode == 'OBJECT'):
+            try:
+                return self.bGetParentArmature().data.bones[self.bObject]
+            except KeyError:
+                return None
+        if (mode == 'POSE'):
+            try:
+                return self.bGetParentArmature().pose.bones[self.bObject]
+            except KeyError:
+                return None
+    
+        
+        
+
+#*#-------------------------------#++#-------------------------------#*#
+# L I N K   N O D E S
+#*#-------------------------------#++#-------------------------------#*#
+
+class LinkInherit:
+    '''A node representing inheritance'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+         "Parent"           : NodeSocket(is_input = True, to_socket = "Parent", to_node = self,),
+         # bone only:
+         "Inherit Rotation" : NodeSocket(is_input = True, to_socket = "Inherit Rotation", to_node = self,),
+         "Inherit Scale"    : NodeSocket(is_input = True, to_socket = "Inherit Scale", to_node = self,),
+         "Connected"        : NodeSocket(is_input = True, to_socket = "Connected", to_node = self,),
+        }
+        self.outputs = { "Inheritance" : NodeSocket(from_socket = "Inheritance", from_node = self) }
+        self.parameters = {
+         "Parent":None,
+         # bone only:
+         "Inherit Rotation":None,
+         "Inherit Scale":None,
+         "Connected":None,
+         "Mute":None,
+        }
+        self.links = {} # leave this empty for now!
+        # now set up the traverse target...
+        self.inputs["Parent"].set_traverse_target(self.outputs["Inheritance"])
+        self.outputs["Inheritance"].set_traverse_target(self.inputs["Parent"])
+        self.node_type = 'LINK'
+    
+        
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+    
+    def bExecute(self, bContext = None,):
+        # this is handled by the xForm objects, since it isn't really
+        #  a constraint.
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+
+class LinkCopyLocation:
+    '''A node representing Copy Location'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, to_socket = "Input Relationship", to_node = self,),
+            "Head/Tail"          : NodeSocket(is_input = True, to_socket = "Head/Tail", to_node = self,),
+            "UseBBone"           : NodeSocket(is_input = True, to_socket = "UseBBone", to_node = self,),
+            "Axes"               : NodeSocket(is_input = True, to_socket = "Axes", to_node = self,),
+            "Invert"             : NodeSocket(is_input = True, to_socket = "Invert", to_node = self,),
+            "Target Space"       : NodeSocket(is_input = True, to_socket = "Target Space", to_node = self,),
+            "Owner Space"        : NodeSocket(is_input = True, to_socket = "Owner Space", to_node = self,),
+            "Influence"          : NodeSocket(is_input = True, to_socket = "Influence", to_node = self,),
+            "Target"             : NodeSocket(is_input = True, to_socket = "Target", to_node = self,), }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(from_socket = "Output Relationship", from_node=self) }
+        self.parameters = {
+            "Input Relationship":None,
+            "Head/Tail":None,
+            "UseBBone":None,
+            "Axes":None,
+            "Invert":None,
+            "Target Space":None,
+            "Owner Space":None,
+            "Influence":None,
+            "Target":None,
+            "Mute":None, }
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+        
+        
+    def evaluate_input(self, input_name):
+        if (input_name == 'Target'):            
+            socket = self.inputs.get(input_name)
+            return socket.from_node
+            
+        else:
+            return evaluate_input(self, input_name)
+    
+    
+    
+    def GetxForm(self):
+        # I don't think I have a function for getting children yet!
+        trace = trace_single_line_up(self, "Output Relationship")
+        for node in trace[0]:
+            if (node.__class__ in [xFormRoot, xFormArmature, xFormBone]):
+                return node
+        return None
+
+
+
+    def bExecute(self, context):
+        
+        c = self.GetxForm().bGetObject().constraints.new('COPY_LOCATION')
+        get_target_and_subtarget(self, c)
+        c.head_tail = self.evaluate_input("Head/Tail")
+        c.use_bbone_shape = self.evaluate_input("UseBBone")
+
+        c.owner_space = self.evaluate_input("Owner Space")
+        c.target_space = self.evaluate_input("Target Space")
+        c.invert_x = self.evaluate_input("Invert")[0]
+        c.invert_y = self.evaluate_input("Invert")[1]
+        c.invert_z = self.evaluate_input("Invert")[2]
+        
+        c.use_x = self.evaluate_input("Axes")[0]
+        c.use_y = self.evaluate_input("Axes")[1]
+        c.use_z = self.evaluate_input("Axes")[2]
+        c.influence = self.evaluate_input("Influence")
+        print ("Creating Copy Location Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
+        
+        if self.parameters["Mute"]:
+            c.enabled = False
+
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+        
+
+class LinkCopyRotation:
+    '''A node representing Copy Rotation'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, to_socket = "Input Relationship", to_node = self,),
+            "RotationOrder"      : NodeSocket(is_input = True, to_socket = "RotationOrder", to_node = self,),
+            "Rotation Mix"       : NodeSocket(is_input = True, to_socket = "Rotation Mix", to_node = self,),
+            "Axes"               : NodeSocket(is_input = True, to_socket = "Axes", to_node = self,),
+            "Invert"             : NodeSocket(is_input = True, to_socket = "Invert", to_node = self,),
+            "Target Space"       : NodeSocket(is_input = True, to_socket = "Target Space", to_node = self,),
+            "Owner Space"        : NodeSocket(is_input = True, to_socket = "Owner Space", to_node = self,),
+            "Influence"          : NodeSocket(is_input = True, to_socket = "Influence", to_node = self,),
+            "Target"             : NodeSocket(is_input = True, to_socket = "Target", to_node = self,), }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(from_socket = "Output Relationship", from_node=self) }
+        self.parameters = {
+            "Input Relationship":None,
+            "RotationOrder":None,
+            "Rotation Mix":None,
+            "Axes":None,
+            "Invert":None,
+            "Target Space":None,
+            "Owner Space":None,
+            "Influence":None,
+            "Target":None,
+            "Mute":None, }
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        if (input_name == 'Target'):            
+            socket = self.inputs.get(input_name)
+            return socket.from_node
+            
+        else:
+            return evaluate_input(self, input_name)
+    
+    def GetxForm(self):
+        # I don't think I have a function for getting children yet!
+        trace = trace_single_line_up(self, "Output Relationship")
+        for node in trace[0]:
+            if (node.__class__ in [xFormRoot, xFormArmature, xFormBone]):
+                return node
+        return None
+
+    def bExecute(self, context):
+        
+        c = self.GetxForm().bGetObject().constraints.new('COPY_ROTATION')
+        
+        get_target_and_subtarget(self, c)
+        
+        rotation_order = self.evaluate_input("RotationOrder")
+        
+        
+        if ((rotation_order == 'QUATERNION') or (rotation_order == 'AXIS_ANGLE')):
+            c.euler_order = 'AUTO'
+        else:
+            c.euler_order = rotation_order
+        
+        #c.mix_mode = self.evaluate_input("Rotation Mix")
+        # kek, deal with this later
+        # TODO HACK
+        # dumb enums
+
+        c.owner_space = self.evaluate_input("Owner Space")
+        c.target_space = self.evaluate_input("Target Space")
+        c.invert_x = self.evaluate_input("Invert")[0]
+        c.invert_y = self.evaluate_input("Invert")[1]
+        c.invert_z = self.evaluate_input("Invert")[2]
+        
+        c.use_x = self.evaluate_input("Axes")[0]
+        c.use_y = self.evaluate_input("Axes")[1]
+        c.use_z = self.evaluate_input("Axes")[2]
+        c.influence = self.evaluate_input("Influence")
+        print ("Creating Copy Rotation Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
+        
+        if self.parameters["Mute"]:
+            c.enabled = False
+
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+        
+class LinkCopyScale:
+    '''A node representing Copy Scale'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, to_socket = "Input Relationship", to_node = self,),
+            "Offset"             : NodeSocket(is_input = True, to_socket = "Offset", to_node = self,),
+            "Average"            : NodeSocket(is_input = True, to_socket = "Average", to_node = self,),
+            "Additive"           : NodeSocket(is_input = True, to_socket = "Additive", to_node = self,),
+            "Axes"               : NodeSocket(is_input = True, to_socket = "Axes", to_node = self,),
+            #"Invert"             : NodeSocket(is_input = True, to_socket = "Invert", to_node = self,),
+            "Target Space"       : NodeSocket(is_input = True, to_socket = "Target Space", to_node = self,),
+            "Owner Space"        : NodeSocket(is_input = True, to_socket = "Owner Space", to_node = self,),
+            "Influence"          : NodeSocket(is_input = True, to_socket = "Influence", to_node = self,),
+            "Target"             : NodeSocket(is_input = True, to_socket = "Target", to_node = self,), }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(from_socket = "Output Relationship", from_node=self) }
+        self.parameters = {
+            "Input Relationship":None,
+            "Offset":None,
+            "Average":None,
+            "Axes":None,
+            #"Invert":None,
+            "Target Space":None,
+            "Owner Space":None,
+            "Influence":None,
+            "Target":None,
+            "Mute":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        if (input_name == 'Target'):            
+            socket = self.inputs.get(input_name)
+            return socket.from_node
+            
+        else:
+            return evaluate_input(self, input_name)
+    
+    def GetxForm(self):
+        trace = trace_single_line_up(self, "Output Relationship")
+        for node in trace[0]:
+            if (node.__class__ in [xFormRoot, xFormArmature, xFormBone]):
+                return node
+        return None
+
+    def bExecute(self, context):
+        
+        c = self.GetxForm().bGetObject().constraints.new('COPY_SCALE')
+        
+        get_target_and_subtarget(self, c)
+        
+        c.use_offset       = self.evaluate_input("Offset")
+        c.use_make_uniform = self.evaluate_input("Average")
+
+        c.owner_space = self.evaluate_input("Owner Space")
+        c.target_space = self.evaluate_input("Target Space")
+        c.use_x = self.evaluate_input("Axes")[0]
+        c.use_y = self.evaluate_input("Axes")[1]
+        c.use_z = self.evaluate_input("Axes")[2]
+        c.influence = self.evaluate_input("Influence")
+        print ("Creating Copy Location Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
+        
+        if self.parameters["Mute"]:
+            c.enabled = False
+
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+class LinkCopyTransforms:
+    '''A node representing Copy Transfoms'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, to_socket = "Input Relationship", to_node = self,),
+            "Head/Tail"          : NodeSocket(is_input = True, to_socket = "Head/Tail", to_node = self,),
+            "UseBBone"           : NodeSocket(is_input = True, to_socket = "UseBBone", to_node = self,),
+            "Additive"           : NodeSocket(is_input = True, to_socket = "Additive", to_node = self,),
+            "Mix"                : NodeSocket(is_input = True, to_socket = "Mix", to_node = self,),
+            "Target Space"       : NodeSocket(is_input = True, to_socket = "Target Space", to_node = self,),
+            "Owner Space"        : NodeSocket(is_input = True, to_socket = "Owner Space", to_node = self,),
+            "Influence"          : NodeSocket(is_input = True, to_socket = "Influence", to_node = self,),
+            "Target"             : NodeSocket(is_input = True, to_socket = "Target", to_node = self,), }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(from_socket = "Output Relationship", from_node=self) }
+        self.parameters = {
+            "Input Relationship":None,
+            "Head/Tail":None,
+            "UseBBone":None,
+            "Mix":None,
+            "Target Space":None,
+            "Owner Space":None,
+            "Influence":None,
+            "Target":None,
+            "Mute":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        if (input_name == 'Target'):            
+            socket = self.inputs.get(input_name)
+            return socket.from_node
+            
+        else:
+            return evaluate_input(self, input_name)
+    
+    def GetxForm(self):
+        trace = trace_single_line_up(self, "Output Relationship")
+        for node in trace[0]:
+            if (node.__class__ in [xFormRoot, xFormArmature, xFormBone]):
+                return node
+        return None
+
+    def bExecute(self, context):
+        
+        c = self.GetxForm().bGetObject().constraints.new('COPY_TRANSFORMS')
+        
+        get_target_and_subtarget(self, c)
+        
+        c.head_tail       = self.evaluate_input("Head/Tail")
+        c.use_bbone_shape = self.evaluate_input("UseBBone")
+        c.mix_mode = self.evaluate_input("Mix")
+
+        c.owner_space = self.evaluate_input("Owner Space")
+        c.target_space = self.evaluate_input("Target Space")
+        c.influence = self.evaluate_input("Influence")
+        print ("Creating Copy Transforms Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
+        
+        if self.parameters["Mute"]:
+            c.enabled = False
+
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+class LinkLimitLocation:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, to_socket = "Input Relationship", to_node = self,),
+            "Use Max X"          : NodeSocket(is_input = True, to_socket = "Use Max X", to_node = self,),
+            "Max X"              : NodeSocket(is_input = True, to_socket = "Max X", to_node = self,),
+            "Use Max Y"          : NodeSocket(is_input = True, to_socket = "Use Max Y", to_node = self,),
+            "Max Y"              : NodeSocket(is_input = True, to_socket = "Max Y", to_node = self,),
+            "Use Max Z"          : NodeSocket(is_input = True, to_socket = "Use Max Z", to_node = self,),
+            "Max Z"              : NodeSocket(is_input = True, to_socket = "Max Z", to_node = self,),
+            "Use Min X"          : NodeSocket(is_input = True, to_socket = "Use Min X", to_node = self,),
+            "Min X"              : NodeSocket(is_input = True, to_socket = "Min X", to_node = self,),
+            "Use Min Y"          : NodeSocket(is_input = True, to_socket = "Use Min Y", to_node = self,),
+            "Min Y"              : NodeSocket(is_input = True, to_socket = "Min Y", to_node = self,),
+            "Use Min Z"          : NodeSocket(is_input = True, to_socket = "Use Min Z", to_node = self,),
+            "Min Z"              : NodeSocket(is_input = True, to_socket = "Min Z", to_node = self,),
+            "Affect Transform"   : NodeSocket(is_input = True, to_socket = "Affect Transform", to_node = self,),
+            "Owner Space"        : NodeSocket(is_input = True, to_socket = "Owner Space", to_node = self,),
+            "Influence"          : NodeSocket(is_input = True, to_socket = "Influence", to_node = self,), }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(from_socket = "Output Relationship", from_node=self) }
+        self.parameters = {
+            "Input Relationship":None,
+            "Use Max X":None,
+            "Max X":None,
+            "Use Max Y":None,
+            "Max Y":None,
+            "Use Max Z":None,
+            "Max Z":None,
+            "Use Min X":None,
+            "Min X":None,
+            "Use Min Y":None,
+            "Min Y":None,
+            "Use Min Z":None,
+            "Min Z":None,
+            "Affect Transform":None,
+            "Owner Space":None,
+            "Influence":None,
+            "Mute":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+    
+    def GetxForm(self):
+        trace = trace_single_line_up(self, "Output Relationship")
+        for node in trace[0]:
+            if (node.__class__ in [xFormRoot, xFormArmature, xFormBone]):
+                return node
+        return None
+
+    def bExecute(self, context):
+        c = self.GetxForm().bGetObject().constraints.new('LIMIT_LOCATION')
+        #
+        c.max_x       = self.evaluate_input("Max X")
+        c.max_y       = self.evaluate_input("Max Y")
+        c.max_z       = self.evaluate_input("Max Z")
+        #
+        c.min_x       = self.evaluate_input("Min X")
+        c.min_y       = self.evaluate_input("Min Y")
+        c.min_z       = self.evaluate_input("Min Z")
+        
+        c.use_max_x       = self.evaluate_input("Use Max X")
+        c.use_max_y       = self.evaluate_input("Use Max Y")
+        c.use_max_z       = self.evaluate_input("Use Max Z")
+        #
+        c.use_min_x       = self.evaluate_input("Use Min X")
+        c.use_min_y       = self.evaluate_input("Use Min Y")
+        c.use_min_z       = self.evaluate_input("Use Min Z")
+        
+        c.use_transform_limit = self.evaluate_input("Affect Transform")
+
+        c.owner_space = self.evaluate_input("Owner Space")
+        c.influence = self.evaluate_input("Influence")
+        print ("Creating Limit Location Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
+        
+        if self.parameters["Mute"]:
+            c.enabled = False
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+        
+class LinkLimitRotation:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, to_socket = "Input Relationship", to_node = self,),
+            "Use X"              : NodeSocket(is_input = True, to_socket = "Use X", to_node = self,),
+            "Use Y"              : NodeSocket(is_input = True, to_socket = "Use Y", to_node = self,),
+            "Use Z"              : NodeSocket(is_input = True, to_socket = "Use Z", to_node = self,),
+            "Max X"              : NodeSocket(is_input = True, to_socket = "Max X", to_node = self,),
+            "Max Y"              : NodeSocket(is_input = True, to_socket = "Max Y", to_node = self,),
+            "Max Z"              : NodeSocket(is_input = True, to_socket = "Max Z", to_node = self,),
+            "Min X"              : NodeSocket(is_input = True, to_socket = "Min X", to_node = self,),
+            "Min Y"              : NodeSocket(is_input = True, to_socket = "Min Y", to_node = self,),
+            "Min Z"              : NodeSocket(is_input = True, to_socket = "Min Z", to_node = self,),
+            "Affect Transform"   : NodeSocket(is_input = True, to_socket = "Affect Transform", to_node = self,),
+            "Owner Space"        : NodeSocket(is_input = True, to_socket = "Owner Space", to_node = self,),
+            "Influence"          : NodeSocket(is_input = True, to_socket = "Influence", to_node = self,), }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(from_socket = "Output Relationship", from_node=self) }
+        self.parameters = {
+            "Input Relationship":None,
+            "Use X":None,
+            "Use Y":None,
+            "Use Z":None,
+            "Max X":None,
+            "Max Y":None,
+            "Max Z":None,
+            "Min X":None,
+            "Min Y":None,
+            "Min Z":None,
+            "Affect Transform":None,
+            "Owner Space":None,
+            "Influence":None,
+            "Mute":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+    
+    def GetxForm(self):
+        trace = trace_single_line_up(self, "Output Relationship")
+        for node in trace[0]:
+            if (node.__class__ in [xFormRoot, xFormArmature, xFormBone]):
+                return node
+        return None
+
+    def bExecute(self, context):
+        c = self.GetxForm().bGetObject().constraints.new('LIMIT_ROTATION')
+        #
+        c.max_x       = self.evaluate_input("Max X")
+        c.max_y       = self.evaluate_input("Max Y")
+        c.max_z       = self.evaluate_input("Max Z")
+        #
+        c.min_x       = self.evaluate_input("Min X")
+        c.min_y       = self.evaluate_input("Min Y")
+        c.min_z       = self.evaluate_input("Min Z")
+        #
+        c.use_limit_x       = self.evaluate_input("Use X")
+        c.use_limit_y       = self.evaluate_input("Use Y")
+        c.use_limit_z       = self.evaluate_input("Use Z")
+        #
+        c.use_transform_limit = self.evaluate_input("Affect Transform")
+        #
+        c.owner_space = self.evaluate_input("Owner Space")
+        c.influence = self.evaluate_input("Influence")
+        print ("Creating Limit Rotation Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
+        
+        if self.parameters["Mute"]:
+            c.enabled = False
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+        
+class LinkLimitScale:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, to_socket = "Input Relationship", to_node = self,),
+            "Use Max X"          : NodeSocket(is_input = True, to_socket = "Use Max X", to_node = self,),
+            "Max X"              : NodeSocket(is_input = True, to_socket = "Max X", to_node = self,),
+            "Use Max Y"          : NodeSocket(is_input = True, to_socket = "Use Max Y", to_node = self,),
+            "Max Y"              : NodeSocket(is_input = True, to_socket = "Max Y", to_node = self,),
+            "Use Max Z"          : NodeSocket(is_input = True, to_socket = "Use Max Z", to_node = self,),
+            "Max Z"              : NodeSocket(is_input = True, to_socket = "Max Z", to_node = self,),
+            "Use Min X"          : NodeSocket(is_input = True, to_socket = "Use Min X", to_node = self,),
+            "Min X"              : NodeSocket(is_input = True, to_socket = "Min X", to_node = self,),
+            "Use Min Y"          : NodeSocket(is_input = True, to_socket = "Use Min Y", to_node = self,),
+            "Min Y"              : NodeSocket(is_input = True, to_socket = "Min Y", to_node = self,),
+            "Use Min Z"          : NodeSocket(is_input = True, to_socket = "Use Min Z", to_node = self,),
+            "Min Z"              : NodeSocket(is_input = True, to_socket = "Min Z", to_node = self,),
+            "Affect Transform"   : NodeSocket(is_input = True, to_socket = "Affect Transform", to_node = self,),
+            "Owner Space"        : NodeSocket(is_input = True, to_socket = "Owner Space", to_node = self,),
+            "Influence"          : NodeSocket(is_input = True, to_socket = "Influence", to_node = self,), }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(from_socket = "Output Relationship", from_node=self) }
+        self.parameters = {
+            "Input Relationship":None,
+            "Use Max X":None,
+            "Max X":None,
+            "Use Max Y":None,
+            "Max Y":None,
+            "Use Max Z":None,
+            "Max Z":None,
+            "Use Min X":None,
+            "Min X":None,
+            "Use Min Y":None,
+            "Min Y":None,
+            "Use Min Z":None,
+            "Min Z":None,
+            "Affect Transform":None,
+            "Owner Space":None,
+            "Influence":None,
+            "Mute":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+    
+    def GetxForm(self):
+        trace = trace_single_line_up(self, "Output Relationship")
+        for node in trace[0]:
+            if (node.__class__ in [xFormRoot, xFormArmature, xFormBone]):
+                return node
+        return None
+
+    def bExecute(self, context):
+        c = self.GetxForm().bGetObject().constraints.new('LIMIT_SCALE')
+        #
+        c.max_x       = self.evaluate_input("Max X")
+        c.max_y       = self.evaluate_input("Max Y")
+        c.max_z       = self.evaluate_input("Max Z")
+        #
+        c.min_x       = self.evaluate_input("Min X")
+        c.min_y       = self.evaluate_input("Min Y")
+        c.min_z       = self.evaluate_input("Min Z")
+        
+        c.use_max_x       = self.evaluate_input("Use Max X")
+        c.use_max_y       = self.evaluate_input("Use Max Y")
+        c.use_max_z       = self.evaluate_input("Use Max Z")
+        #
+        c.use_min_x       = self.evaluate_input("Use Min X")
+        c.use_min_y       = self.evaluate_input("Use Min Y")
+        c.use_min_z       = self.evaluate_input("Use Min Z")
+        
+        c.use_transform_limit = self.evaluate_input("Affect Transform")
+
+        c.owner_space = self.evaluate_input("Owner Space")
+        c.influence = self.evaluate_input("Influence")
+        print ("Creating Limit Scale Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
+        
+        if self.parameters["Mute"]:
+            c.enabled = False
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+        
+class LinkLimitDistance:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, to_socket = "Input Relationship", to_node = self,),
+            "Head/Tail"          : NodeSocket(is_input = True, to_socket = "Head/Tail", to_node = self,),
+            "UseBBone"           : NodeSocket(is_input = True, to_socket = "UseBBone", to_node = self,),
+            "Distance"           : NodeSocket(is_input = True, to_socket = "Distance", to_node = self,),
+            "Clamp Region"       : NodeSocket(is_input = True, to_socket = "Clamp Region", to_node = self,),
+            "Affect Transform"   : NodeSocket(is_input = True, to_socket = "Affect Transform", to_node = self,),
+            "Owner Space"        : NodeSocket(is_input = True, to_socket = "Owner Space", to_node = self,),
+            "Target Space"       : NodeSocket(is_input = True, to_socket = "Target Space", to_node = self,),
+            "Influence"          : NodeSocket(is_input = True, to_socket = "Influence", to_node = self,),
+            "Target"             : NodeSocket(is_input = True, to_socket = "Target", to_node = self,), }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(from_socket = "Output Relationship", from_node=self) }
+        self.parameters = {
+            "Input Relationship":None,
+            "Head/Tail":None,
+            "UseBBone":None,
+            "Distance":None,
+            "Clamp Region":None,
+            "Affect Transform":None,
+            "Owner Space":None,
+            "Target Space":None,
+            "Influence":None,
+            "Target":None,
+            "Mute":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        if (input_name == 'Target'):            
+            socket = self.inputs.get(input_name)
+            return socket.from_node
+            
+        else:
+            return evaluate_input(self, input_name)
+
+    def GetxForm(self):
+        trace = trace_single_line_up(self, "Output Relationship")
+        for node in trace[0]:
+            if (node.__class__ in [xFormRoot, xFormArmature, xFormBone]):
+                return node
+        return None
+
+    def bExecute(self, context):
+        
+        c = self.GetxForm().bGetObject().constraints.new('LIMIT_DISTANCE')
+        
+        get_target_and_subtarget(self, c)
+        #
+        c.distance            = self.evaluate_input("Distance")
+        c.head_tail           = self.evaluate_input("Head/Tail")
+        c.limit_mode          = self.evaluate_input("Clamp Region")
+        c.use_bbone_shape     = self.evaluate_input("UseBBone")
+        c.use_transform_limit = self.evaluate_input("Affect Transform")
+        c.owner_space         = self.evaluate_input("Owner Space")
+        c.target_space        = self.evaluate_input("Target Space")
+        c.influence           = self.evaluate_input("Influence")
+        print ("Creating Limit Distance Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
+        
+        if self.parameters["Mute"]:
+            c.enabled = False
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+# Tracking
+
+class LinkStretchTo:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, to_socket = "Input Relationship", to_node = self,),
+            "Head/Tail"          : NodeSocket(is_input = True, to_socket = "Head/Tail", to_node = self,),
+            "UseBBone"           : NodeSocket(is_input = True, to_socket = "UseBBone", to_node = self,),
+            "Original Length"    : NodeSocket(is_input = True, to_socket = "Original Length", to_node = self,),
+            "Volume Variation"   : NodeSocket(is_input = True, to_socket = "Volume Variation", to_node = self,),
+            "Use Volume Min"     : NodeSocket(is_input = True, to_socket = "Use Volume Min", to_node = self,),
+            "Volume Min"         : NodeSocket(is_input = True, to_socket = "Volume Min", to_node = self,),
+            "Use Volume Max"     : NodeSocket(is_input = True, to_socket = "Use Volume Max", to_node = self,),
+            "Volume Max"         : NodeSocket(is_input = True, to_socket = "Volume Max", to_node = self,),
+            "Smooth"             : NodeSocket(is_input = True, to_socket = "Smooth", to_node = self,),
+            "Maintain Volume"    : NodeSocket(is_input = True, to_socket = "Maintain Volume", to_node = self,),
+            "Rotation"           : NodeSocket(is_input = True, to_socket = "Rotation", to_node = self,),
+            "Influence"          : NodeSocket(is_input = True, to_socket = "Influence", to_node = self,),
+            "Target"             : NodeSocket(is_input = True, to_socket = "Target", to_node = self,), }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(from_socket = "Output Relationship", from_node=self) }
+        self.parameters = {
+            "Input Relationship":None,
+            "Head/Tail":None,
+            "UseBBone":None,
+            "Original Length":None,
+            "Volume Variation":None,
+            "Use Volume Min":None,
+            "Volume Min":None,
+            "Use Volume Max":None,
+            "Volume Max":None,
+            "Smooth":None,
+            "Maintain Volume":None,
+            "Rotation":None,
+            "Influence":None,
+            "Target":None,
+            "Mute":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        if (input_name == 'Target'):            
+            socket = self.inputs.get(input_name)
+            return socket.from_node
+            
+        else:
+            return evaluate_input(self, input_name)
+
+    def GetxForm(self):
+        trace = trace_single_line_up(self, "Output Relationship")
+        for node in trace[0]:
+            if (node.__class__ in [xFormRoot, xFormArmature, xFormBone]):
+                return node
+        return None
+
+    def bExecute(self, context):
+        
+        c = self.GetxForm().bGetObject().constraints.new('STRETCH_TO')
+        
+        get_target_and_subtarget(self, c)
+        
+        c.head_tail       = self.evaluate_input("Head/Tail")
+        c.use_bbone_shape = self.evaluate_input("UseBBone")
+        c.bulge           = self.evaluate_input("Volume Variation")
+        c.use_bulge_min   = self.evaluate_input("Use Volume Min")
+        c.bulge_min       = self.evaluate_input("Volume Min")
+        c.use_bulge_max   = self.evaluate_input("Use Volume Max")
+        c.bulge_max       = self.evaluate_input("Volume Max")
+        c.bulge_smooth    = self.evaluate_input("Smooth")
+        c.keep_axis       = self.evaluate_input("Rotation")
+        c.volume          = self.evaluate_input("Maintain Volume")
+        c.rest_length     = self.evaluate_input("Original Length")
+        c.influence       = self.evaluate_input("Influence")
+        
+        if (c.rest_length == 0):
+            # this is meant to be set automatically.
+            c.rest_length = self.GetxForm().bGetObject().bone.length
+        
+
+        print ("Creating Stretch-To Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
+        
+        if self.parameters["Mute"]:
+            c.enabled = False
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+class LinkDampedTrack:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, to_socket = "Input Relationship", to_node = self,),
+            "Head/Tail"          : NodeSocket(is_input = True, to_socket = "Head/Tail", to_node = self,),
+            "UseBBone"           : NodeSocket(is_input = True, to_socket = "UseBBone", to_node = self,),
+            "Track Axis"         : NodeSocket(is_input = True, to_socket = "Track Axis", to_node = self,),
+            "Influence"          : NodeSocket(is_input = True, to_socket = "Influence", to_node = self,),
+            "Target"             : NodeSocket(is_input = True, to_socket = "Target", to_node = self,), }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(from_socket = "Output Relationship", from_node=self) }
+        self.parameters = {
+            "Input Relationship":None,
+            "Head/Tail":None,
+            "UseBBone":None,
+            "Track Axis":None,
+            "Influence":None,
+            "Target":None,
+            "Mute":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        if (input_name == 'Target'):            
+            socket = self.inputs.get(input_name)
+            return socket.from_node
+            
+        else:
+            return evaluate_input(self, input_name)
+
+    def GetxForm(self):
+        trace = trace_single_line_up(self, "Output Relationship")
+        for node in trace[0]:
+            if (node.__class__ in [xFormRoot, xFormArmature, xFormBone]):
+                return node
+        return None
+
+    def bExecute(self, context):
+        
+        c = self.GetxForm().bGetObject().constraints.new('DAMPED_TRACK')
+        
+        get_target_and_subtarget(self, c)
+        
+        c.head_tail       = self.evaluate_input("Head/Tail")
+        c.use_bbone_shape = self.evaluate_input("UseBBone")
+        c.track_axis      = self.evaluate_input("Track Axis")
+        c.influence       = self.evaluate_input("Influence")
+
+        print ("Creating Damped-Track Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
+        
+        if self.parameters["Mute"]:
+            c.enabled = False
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+class LinkLockedTrack:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, to_socket = "Input Relationship", to_node = self,),
+            "Head/Tail"          : NodeSocket(is_input = True, to_socket = "Head/Tail", to_node = self,),
+            "UseBBone"           : NodeSocket(is_input = True, to_socket = "UseBBone", to_node = self,),
+            "Track Axis"         : NodeSocket(is_input = True, to_socket = "Track Axis", to_node = self,),
+            "Lock Axis"          : NodeSocket(is_input = True, to_socket = "Lock Axis", to_node = self,),
+            "Influence"          : NodeSocket(is_input = True, to_socket = "Influence", to_node = self,),
+            "Target"             : NodeSocket(is_input = True, to_socket = "Target", to_node = self,), }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(from_socket = "Output Relationship", from_node=self) }
+        self.parameters = {
+            "Input Relationship":None,
+            "Head/Tail":None,
+            "UseBBone":None,
+            "Track Axis":None,
+            "Lock Axis":None,
+            "Influence":None,
+            "Target":None,
+            "Mute":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        if (input_name == 'Target'):            
+            socket = self.inputs.get(input_name)
+            return socket.from_node
+            
+        else:
+            return evaluate_input(self, input_name)
+
+    def GetxForm(self):
+        trace = trace_single_line_up(self, "Output Relationship")
+        for node in trace[0]:
+            if (node.__class__ in [xFormRoot, xFormArmature, xFormBone]):
+                return node
+        return None
+
+    def bExecute(self, context):
+        
+        c = self.GetxForm().bGetObject().constraints.new('LOCKED_TRACK')
+        
+        get_target_and_subtarget(self, c)
+        
+        c.head_tail       = self.evaluate_input("Head/Tail")
+        c.use_bbone_shape = self.evaluate_input("UseBBone")
+        c.track_axis      = self.evaluate_input("Track Axis")
+        c.lock_axis       = self.evaluate_input("Lock Axis")
+        c.influence       = self.evaluate_input("Influence")
+
+        print ("Creating Locked-Track Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
+        
+        if self.parameters["Mute"]:
+            c.enabled = False
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+class LinkTrackTo:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, to_socket = "Input Relationship", to_node = self,),
+            "Head/Tail"          : NodeSocket(is_input = True, to_socket = "Head/Tail", to_node = self,),
+            "UseBBone"           : NodeSocket(is_input = True, to_socket = "UseBBone", to_node = self,),
+            "Track Axis"         : NodeSocket(is_input = True, to_socket = "Track Axis", to_node = self,),
+            "Up Axis"            : NodeSocket(is_input = True, to_socket = "Up Axis", to_node = self,),
+            "Use Target Z"       : NodeSocket(is_input = True, to_socket = "Use Target Z", to_node = self,),
+            "Influence"          : NodeSocket(is_input = True, to_socket = "Influence", to_node = self,),
+            "Target"             : NodeSocket(is_input = True, to_socket = "Target", to_node = self,), }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(from_socket = "Output Relationship", from_node=self) }
+        self.parameters = {
+            "Input Relationship":None,
+            "Head/Tail":None,
+            "UseBBone":None,
+            "Track Axis":None,
+            "Up Axis":None,
+            "Use Target Z":None, 
+            "Influence":None,
+            "Target":None,
+            "Mute":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        if (input_name == 'Target'):            
+            socket = self.inputs.get(input_name)
+            return socket.from_node
+            
+        else:
+            return evaluate_input(self, input_name)
+
+    def GetxForm(self):
+        trace = trace_single_line_up(self, "Output Relationship")
+        for node in trace[0]:
+            if (node.__class__ in [xFormRoot, xFormArmature, xFormBone]):
+                return node
+        return None
+
+    def bExecute(self, context):
+        
+        c = self.GetxForm().bGetObject().constraints.new('TRACK_TO')
+        
+        get_target_and_subtarget(self, c)
+        
+        c.head_tail       = self.evaluate_input("Head/Tail")
+        c.use_bbone_shape = self.evaluate_input("UseBBone")
+        c.track_axis      = self.evaluate_input("Track Axis")
+        c.up_axis         = self.evaluate_input("Up Axis")
+        c.use_target_z    = self.evaluate_input("Use Target Z")
+        c.influence       = self.evaluate_input("Influence")
+
+        print ("Creating Track-To Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
+        
+        if self.parameters["Mute"]:
+            c.enabled = False
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+# relationships & misc.
+
+class LinkInheritConstraint:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            "Input Relationship" : NodeSocket(is_input = True, to_socket = "Input Relationship", to_node = self,),
+            "Location"           : NodeSocket(is_input = True, to_socket = "Location", to_node = self,),
+            "Rotation"           : NodeSocket(is_input = True, to_socket = "Rotation", to_node = self,),
+            "Scale"              : NodeSocket(is_input = True, to_socket = "Scale", to_node = self,),
+            "Influence"          : NodeSocket(is_input = True, to_socket = "Influence", to_node = self,),
+            "Target"             : NodeSocket(is_input = True, to_socket = "Target", to_node = self,), }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(from_socket = "Output Relationship", from_node=self) }
+        self.parameters = {
+            "Input Relationship":None,
+            "Location":None,
+            "Rotation":None,
+            "Scale":None,
+            "Influence":None,
+            "Target":None,
+            "Mute":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        if (input_name == 'Target'):            
+            socket = self.inputs.get(input_name)
+            return socket.from_node
+            
+        else:
+            return evaluate_input(self, input_name)
+
+    def GetxForm(self):
+        trace = trace_single_line_up(self, "Output Relationship")
+        for node in trace[0]:
+            if (node.__class__ in [xFormRoot, xFormArmature, xFormBone]):
+                return node
+        return None
+
+    def bExecute(self, context):
+        
+        c = self.GetxForm().bGetObject().constraints.new('CHILD_OF')
+        
+        get_target_and_subtarget(self, c)
+        
+        c.use_location_x = self.evaluate_input("Location")[0]
+        c.use_location_y = self.evaluate_input("Location")[1]
+        c.use_location_z = self.evaluate_input("Location")[2]
+        c.use_rotation_x = self.evaluate_input("Rotation")[0]
+        c.use_rotation_y = self.evaluate_input("Rotation")[1]
+        c.use_rotation_z = self.evaluate_input("Rotation")[2]
+        c.use_scale_x    = self.evaluate_input("Scale")[0]
+        c.use_scale_y    = self.evaluate_input("Scale")[1]
+        c.use_scale_z    = self.evaluate_input("Scale")[2]
+        c.influence      = self.evaluate_input("Influence")
+        c.set_inverse_pending
+
+
+        print ("Creating Child-of Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
+        
+        if self.parameters["Mute"]:
+            c.enabled = False
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+# Inverse Kinematics:
+
+#
+# r = self.inputs.new('BooleanSocket', "Inherit Rotation")
+# c = self.inputs.new('BooleanSocket', "Connected")
+# s = self.inputs.new('EnumInheritScale', "Inherit Scale")
+# self.inputs.new ('xFormSocket', "Target")
+# self.inputs.new ('xFormSocket', "Pole Target")
+# self.inputs.new ('IKChainLengthSocket', "Chain Length")
+# self.inputs.new ('BooleanSocket', "Use Tail")
+# self.inputs.new ('BooleanSocket', "Stretch")
+# self.inputs.new ('FloatFactorSocket', "Position")
+# self.inputs.new ('FloatFactorSocket', "Rotation")
+# self.inputs.new ('FloatFactorSocket', "Influence")
+# self.outputs.new('RelationshipSocket', "Inheritance")
+# self.inputs.new('xFormSocket', "Parent")
+#
+
+# Ugghh, this one is a little weird
+# I treat IK as a kind of inheritance
+#  that is mutually exclusive with other inheritance
+#  since Blender and other softwares treat it specially, anyway
+#  e.g. in Blender it's always treated as the last constraint
+# While it may be annoying to have it always appear first in each bones'
+#  stack, because this does not affect behaviour, I do not really care.
+class LinkInverseKinematics:
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {
+            # Inheritance bits
+            "Input Relationship"  : NodeSocket(is_input = True, to_socket = "Input Relationship", to_node = self,),
+            "Inherit Rotation"    : NodeSocket(is_input = True, to_socket = "Inherit Rotation", to_node = self,),
+            "Inherit Scale"       : NodeSocket(is_input = True, to_socket = "Inherit Scale", to_node = self,),
+            "Connected"           : NodeSocket(is_input = True, to_socket = "Connected", to_node = self,),
+            # Constraint stuff
+            "Chain Length"        : NodeSocket(is_input = True, to_socket = "Chain Length", to_node = self,),
+            "Use Tail"            : NodeSocket(is_input = True, to_socket = "Use Tail", to_node = self,),
+            "Stretch"             : NodeSocket(is_input = True, to_socket = "Stretch", to_node = self,),
+            "Position"            : NodeSocket(is_input = True, to_socket = "Position", to_node = self,),
+            "Rotation"            : NodeSocket(is_input = True, to_socket = "Rotation", to_node = self,),
+            "Influence"           : NodeSocket(is_input = True, to_socket = "Influence", to_node = self,),
+            "Target"              : NodeSocket(is_input = True, to_socket = "Target", to_node = self,),
+            "Pole Target"         : NodeSocket(is_input = True, to_socket = "Pole Target", to_node = self,), }
+        self.outputs = {
+            "Output Relationship" : NodeSocket(from_socket = "Output Relationship", from_node=self) }
+        self.parameters = {
+            "Input Relationship":None,
+            "Inherit Rotation":None,
+            "Inherit Scale":None,
+            "Connected":None,
+            "Chain Length":None,
+            "Use Tail":None,
+            "Stretch":None,
+            "Position":None,
+            "Rotation":None,
+            "Influence":None,
+            "Target":None, 
+            "Pole Target":None,
+            "Mute":None,}
+        # now set up the traverse target...
+        self.inputs["Input Relationship"].set_traverse_target(self.outputs["Output Relationship"])
+        self.outputs["Output Relationship"].set_traverse_target(self.inputs["Input Relationship"])
+        self.node_type = 'LINK'
+        
+    def evaluate_input(self, input_name):
+        if (input_name == 'Target') or (input_name == 'Pole Target'):
+            socket = self.inputs.get(input_name)
+            return socket.from_node
+        else:
+            return evaluate_input(self, input_name)
+
+    def GetxForm(self):
+        trace = trace_single_line_up(self, "Output Relationship")
+        for node in trace[0]:
+            if (node.__class__ in [xFormRoot, xFormArmature, xFormBone]):
+                return node
+        return None
+
+    def bExecute(self, context):
+        # do not handle any inheritance stuff here, that is dealt with
+        #  by the xForm nodes instead!
+        myOb = self.GetxForm().bGetObject()
+        c = self.GetxForm().bGetObject().constraints.new('IK')
+        
+        get_target_and_subtarget(self, c)
+        get_target_and_subtarget(self, c, input_name = 'Pole Target')
+        
+        
+        if (c.pole_target): # Calculate the pole angle, the user shouldn't have to.
+            pole_object = c.pole_target
+            pole_location = pole_object.matrix_world.decompose()[0]
+            if (c.pole_subtarget):
+                pole_object = c.pole_target.pose.bones[c.subtarget]
+                pole_location = pole_object.matrix.decompose()[0]
+            #HACK HACK
+            handle_location = myOb.bone.tail_local if (self.evaluate_input("Use Tail")) else myOb.bone.head_local
+            counter = 0
+            parent = myOb
+            base_bone = myOb
+            while (parent is not None):
+                if ((self.evaluate_input("Chain Length") != 0) and (counter > self.evaluate_input("Chain Length"))):
+                    break
+                base_bone = parent
+                parent = parent.parent
+                counter+=1
+            head_location = base_bone.bone.head_local
+
+            pole_normal = (handle_location - head_location).cross(pole_location - head_location)
+            vector_u = myOb.bone.x_axis
+            vector_v = pole_normal.cross(base_bone.bone.y_axis)
+            angle = vector_u.angle(vector_v)
+            if (vector_u.cross(vector_v).angle(base_bone.bone.y_axis) < 1):
+                angle = -angle
+            
+            c.pole_angle = angle
+        
+        c.chain_count    = self.evaluate_input("Chain Length")
+        c.use_tail       = self.evaluate_input("Use Tail")
+        c.use_stretch    = self.evaluate_input("Stretch")
+        c.weight         = self.evaluate_input("Position")
+        c.orient_weight  = self.evaluate_input("Rotation")
+        c.influence      = self.evaluate_input("Influence")
+        # this should be sufficient, I think use_location and use_rotation
+        #  are meaningless if the weight is 0, anyway.
+        # Well, the minimum weight is 0.01, so we have to get the input again.
+        c.use_location   = self.evaluate_input("Position") > 0 
+        c.use_rotation   = self.evaluate_input("Rotation") > 0
+        # this is a little annoying because the constraint can have a
+        #  positive value for position/rotation without it being enabled.
+        print ("Creating IK Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
+        
+        if self.parameters["Mute"]:
+            c.enabled = False
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+
+
+
+#*#-------------------------------#++#-------------------------------#*#
+# G E N E R I C   N O D E S
+#*#-------------------------------#++#-------------------------------#*#
+
+
+# in reality, none of these inputs have names
+#  so I am using the socket name for now
+#  I suppose I could use any name :3
+class InputFloat:
+    '''A node representing float input'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.outputs = {"Float Input" : NodeSocket(from_socket = "Float Input", from_node=self) }
+        self.parameters = {"Float Input":None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["Float Input"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+    
+class InputVector:
+    '''A node representing vector input'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.outputs = {"VectorSocket" : NodeSocket(from_socket = 'VectorSocket', from_node=self) }
+        self.parameters = {'VectorSocket':None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["VectorSocket"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+class InputBoolean:
+    '''A node representing boolean input'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.outputs = {"BooleanSocket" : NodeSocket(from_socket = 'BooleanSocket', from_node=self) }
+        self.parameters = {'BooleanSocket':None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["BooleanSocket"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+class InputBooleanThreeTuple:
+    '''A node representing inheritance'''
+        
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.outputs = {"BooleanThreeTupleSocket" : NodeSocket(from_socket = 'BooleanThreeTupleSocket', from_node=self) }
+        self.parameters = {'BooleanThreeTupleSocket':None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["BooleanThreeTupleSocket"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+class InputRotationOrder:
+    '''A node representing string input for rotation order'''
+        
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.outputs = {"RotationOrderSocket" : NodeSocket(from_socket = 'RotationOrderSocket', from_node=self) }
+        self.parameters = {'RotationOrderSocket':None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["RotationOrderSocket"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+class InputTransformSpace:
+    '''A node representing string input for transform space'''
+        
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.outputs = {"TransformSpaceSocket" : NodeSocket(from_socket = 'TransformSpaceSocket', from_node=self) }
+        self.parameters = {'TransformSpaceSocket':None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["TransformSpaceSocket"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+class InputString:
+    '''A node representing string input'''
+        
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.outputs = {"StringSocket" : NodeSocket(from_socket = 'StringSocket', from_node=self) }
+        self.parameters = {'StringSocket':None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["StringSocket"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+class InputQuaternion:
+    '''A node representing quaternion input'''
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.outputs = {"QuaternionSocket" : NodeSocket(from_socket = 'QuaternionSocket', from_node=self) }
+        self.parameters = {'QuaternionSocket':None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["QuaternionSocket"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+class InputQuaternionAA:
+    '''A node representing axis-angle quaternion input'''
+        
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.outputs  = {"QuaternionSocketAA" : NodeSocket(from_socket = 'QuaternionSocketAA', from_node=self) }
+        self.parameters = {'QuaternionSocketAA':None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["QuaternionSocketAA"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        fill_parameters(self, node_prototype)
+
+
+class InputMatrix:
+    '''A node representing axis-angle quaternion input'''
+        
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.outputs  = {"Matrix" : NodeSocket(from_socket = 'Matrix', from_node=self) }
+        self.parameters = {'Matrix':None, "Mute":None}
+        self.node_type = 'UTILITY'
+        
+    def evaluate_input(self, input_name):
+        return self.parameters["Matrix"]
+    
+    def bExecute(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self, node_prototype):
+        # this node is peculiar for how its data is input
+        # It uses node properties that are not addressable as sockets.
+        from mathutils import Matrix
+        
+        matrix = ( node_prototype.first_row[ 0], node_prototype.first_row[ 1], node_prototype.first_row[ 2], node_prototype.first_row[ 3],
+                   node_prototype.second_row[0], node_prototype.second_row[1], node_prototype.second_row[2], node_prototype.second_row[3],
+                   node_prototype.third_row[ 0], node_prototype.third_row[ 1], node_prototype.third_row[ 2], node_prototype.third_row[ 3],
+                   node_prototype.fourth_row[0], node_prototype.fourth_row[1], node_prototype.fourth_row[2], node_prototype.fourth_row[3], )
+        self.parameters["Matrix"] = Matrix([matrix[0:4], matrix[4:8], matrix[8:12], matrix[12:16]])
+        print (self.parameters["Matrix"])
+        
+
+# # NOT YET IMPLEMENTED:
+# class InputMatrixNode(Node, MantisNode):
+    # '''A node representing matrix input'''
+    # inputs = 
+    # # the node is implemented as a set of sixteen float inputs
+    # # but I think I can boil it down to one matrix input
+
+
+# class ScaleBoneLengthNode(Node, MantisNode):
+    # '''Scale Bone Length'''
+    # pass
+

+ 474 - 0
node_container_common.py

@@ -0,0 +1,474 @@
+from mantis.utilities import (prRed, prGreen, prPurple, prWhite,
+                              prOrange,
+                              wrapRed, wrapGreen, wrapPurple, wrapWhite,
+                              wrapOrange,)
+from mantis.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 mantis.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

+ 523 - 0
nodes_generic.py

@@ -0,0 +1,523 @@
+import bpy
+from bpy.types import Node
+from .base_definitions import MantisNode
+
+def TellClasses():
+    return [ InputFloatNode,
+             InputVectorNode,
+             InputBooleanNode,
+             InputBooleanThreeTupleNode,
+             InputRotationOrderNode,
+             InputTransformSpaceNode,
+             InputStringNode,
+             InputQuaternionNode,
+             InputQuaternionNodeAA,
+             InputMatrixNode,
+             InputLayerMaskNode,
+             # InputGeometryNode,
+             InputExistingGeometryObjectNode,
+             InputExistingGeometryDataNode,
+             
+            #  ComposeMatrixNode,
+             MetaRigMatrixNode,
+            #  ScaleBoneLengthNode,
+             UtilityMetaRigNode,
+             UtilityBonePropertiesNode,
+             UtilityDriverVariableNode,
+             UtilityFCurveNode,
+             UtilityDriverNode,
+             UtilitySwitchNode,
+             UtilityCombineThreeBoolNode,
+             UtilityCombineVectorNode,
+             UtilityCatStringsNode,
+            ]
+
+
+def default_traverse(self,socket):
+    return None
+
+
+class InputFloatNode(Node, MantisNode):
+    '''A node representing inheritance'''
+    bl_idname = 'InputFloatNode'
+    bl_label = "Float"
+    bl_icon = 'NODE'
+
+    def init(self, context):
+        self.outputs.new('FloatSocket', "Float Input").input = True
+    
+class InputVectorNode(Node, MantisNode):
+    '''A node representing inheritance'''
+    bl_idname = 'InputVectorNode'
+    bl_label = "Vector"
+    bl_icon = 'NODE'
+
+    def init(self, context):
+        self.outputs.new('VectorSocket', "").input = True
+
+class InputBooleanNode(Node, MantisNode):
+    '''A node representing inheritance'''
+    bl_idname = 'InputBooleanNode'
+    bl_label = "Boolean"
+    bl_icon = 'NODE'
+
+    def init(self, context):
+        self.outputs.new('BooleanSocket', "").input = True
+
+class InputBooleanThreeTupleNode(Node, MantisNode):
+    '''A node representing inheritance'''
+    bl_idname = 'InputBooleanThreeTupleNode'
+    bl_label = "Boolean Vector"
+    bl_icon = 'NODE'
+
+    def init(self, context):
+        self.outputs.new('BooleanThreeTupleSocket', "")
+
+class InputRotationOrderNode(Node, MantisNode):
+    '''A node representing inheritance'''
+    bl_idname = 'InputRotationOrderNode'
+    bl_label = "Rotation Order"
+    bl_icon = 'NODE'
+
+    def init(self, context):
+        self.outputs.new('RotationOrderSocket', "").input = True
+
+class InputTransformSpaceNode(Node, MantisNode):
+    '''A node representing inheritance'''
+    bl_idname = 'InputTransformSpaceNode'
+    bl_label = "Transform Space"
+    bl_icon = 'NODE'
+
+    def init(self, context):
+        self.outputs.new('TransformSpaceSocket', "").input = True
+
+class InputStringNode(Node, MantisNode):
+    '''A node representing inheritance'''
+    bl_idname = 'InputStringNode'
+    bl_label = "String"
+    bl_icon = 'NODE'
+
+    def init(self, context):
+        self.outputs.new('StringSocket', "").input = True
+
+class InputQuaternionNode(Node, MantisNode):
+    '''A node representing inheritance'''
+    bl_idname = 'InputQuaternionNode'
+    bl_label = "Quaternion"
+    bl_icon = 'NODE'
+
+    def init(self, context):
+        self.outputs.new('QuaternionSocket', "").input = True
+
+class InputQuaternionNodeAA(Node, MantisNode):
+    '''A node representing inheritance'''
+    bl_idname = 'InputQuaternionNodeAA'
+    bl_label = "Axis Angle"
+    bl_icon = 'NODE'
+
+    def init(self, context):
+        self.outputs.new('QuaternionSocketAA', "").input = True
+
+
+class InputMatrixNode(Node, MantisNode):
+    '''A node representing inheritance'''
+    bl_idname = 'InputMatrixNode'
+    bl_label = "Matrix"
+    bl_icon = 'NODE'
+    first_row  : bpy.props.FloatVectorProperty(name="", size=4, default = (1.0, 0.0, 0.0, 0.0,))
+    second_row : bpy.props.FloatVectorProperty(name="", size=4, default = (0.0, 1.0, 0.0, 0.0,))
+    third_row  : bpy.props.FloatVectorProperty(name="", size=4, default = (0.0, 0.0, 1.0, 0.0,))
+    fourth_row : bpy.props.FloatVectorProperty(name="", size=4, default = (0.0, 0.0, 0.0, 1.0,))
+
+    def set_matrix(self):
+        return (self.first_row[ 0], self.first_row[ 1], self.first_row[ 2], self.first_row[ 3],
+                self.second_row[0], self.second_row[1], self.second_row[2], self.second_row[3],
+                self.third_row[ 0], self.third_row[ 1], self.third_row[ 2], self.third_row[ 3],
+                self.fourth_row[0], self.fourth_row[1], self.fourth_row[2], self.fourth_row[3],)
+
+    def init(self, context):
+        self.outputs.new('MatrixSocket', "Matrix")
+        
+    def update_node(self, context):
+        self.outputs["Matrix"].default_value = self.set_matrix()
+
+    def draw_buttons(self, context, layout):
+        layout.prop(self, "first_row")
+        layout.prop(self, "second_row")
+        layout.prop(self, "third_row")
+        layout.prop(self, "fourth_row")
+
+    def update(self):
+        mat_sock = self.outputs[0]
+        mat_sock.default_value = self.set_matrix()
+
+# TODO: reimplement the nodes beneath here
+
+# from .utilities import QuerySocket, to_mathutils_value
+# class ComposeMatrixNode(Node, MantisNode):
+#     '''A utility node for composing a matrix'''
+#     bl_idname = 'ComposeMatrixNode'
+#     bl_label = "Compose Matrix"
+#     bl_icon = 'NODE'
+
+#     def init(self, context):
+#         self.inputs.new('VectorTranslationSocket', "Translation")
+#         self.inputs.new('GenericRotationSocket', "Rotation")
+#         self.inputs.new('VectorScaleSocket', "Scale")
+#         self.outputs.new('MatrixSocket', "Matrix")
+
+#     def update_node(self, context = None):
+#         from mathutils import Matrix, Euler, Quaternion, Vector
+#         mat_sock = self.outputs[0]
+#         rotation = Matrix.Identity(4)
+#         scale = Matrix.Identity(4)
+#         translation = Matrix.Identity(4)
+
+#         sock = QuerySocket(self.inputs["Rotation"])[0]
+#         val = to_mathutils_value(sock)
+#         if (val):
+#             if (isinstance(val, Vector)):
+#                 val = Euler((val[0], val[1], val[2]), 'XYZ')
+#             rotation = val.to_matrix().to_4x4()
+#         sock = QuerySocket(self.inputs["Scale"])[0]
+#         val = to_mathutils_value(sock)
+#         if (val):
+#             if (isinstance(val, Vector)):
+#                 scale = Matrix.Scale(val[0],4,(1.0,0.0,0.0)) @ Matrix.Scale(val[1],4,(0.0,1.0,0.0)) @ Matrix.Scale(val[2],4,(0.0,0.0,1.0))
+#         sock = QuerySocket(self.inputs["Translation"])[0]
+#         val = to_mathutils_value(sock)
+#         if (val):
+#             if (isinstance(val, Vector)):
+#                 translation = Matrix.Translation((val))
+        
+#         mat = translation @ rotation @ scale
+#         mat_sock.default_value = ( mat[0][0], mat[0][1], mat[0][2], mat[0][3],
+#                                    mat[1][0], mat[1][1], mat[1][2], mat[1][3],
+#                                    mat[2][0], mat[2][1], mat[2][2], mat[2][3],
+#                                    mat[3][0], mat[3][1], mat[3][2], mat[3][3], )
+
+
+class ScaleBoneLengthNode(Node, MantisNode):
+    '''Scale Bone Length'''
+    bl_idname = 'ScaleBoneLength'
+    bl_label = "Scale Bone Length"
+    bl_icon = 'NODE'
+
+    # === Optional Functions ===
+    def init(self, context):
+        self.inputs.new('MatrixSocket', "In Matrix")
+        self.inputs.new('FloatSocket', "Factor")
+        self.outputs.new('MatrixSocket', "Out Matrix")
+
+
+
+class MetaRigMatrixNode(Node, MantisNode):
+    # Identical to the above, except
+    '''A node representing a bone's matrix'''
+    bl_idname = 'MetaRigMatrixNode'
+    bl_label = "Matrix"
+    bl_icon = 'NODE'
+    first_row  : bpy.props.FloatVectorProperty(name="", size=4, default = (1.0, 0.0, 0.0, 0.0,))
+    second_row : bpy.props.FloatVectorProperty(name="", size=4, default = (0.0, 1.0, 0.0, 0.0,))
+    third_row  : bpy.props.FloatVectorProperty(name="", size=4, default = (0.0, 0.0, 1.0, 0.0,))
+    fourth_row : bpy.props.FloatVectorProperty(name="", size=4, default = (0.0, 0.0, 0.0, 1.0,))
+
+    def set_matrix(self):
+        return (self.first_row[ 0], self.first_row[ 1], self.first_row[ 2], self.first_row[ 3],
+                self.second_row[0], self.second_row[1], self.second_row[2], self.second_row[3],
+                self.third_row[ 0], self.third_row[ 1], self.third_row[ 2], self.third_row[ 3],
+                self.fourth_row[0], self.fourth_row[1], self.fourth_row[2], self.fourth_row[3],)
+
+    def init(self, context):
+        self.outputs.new('MatrixSocket', "Matrix")
+    
+    def traverse(self, context):
+        from mathutils import Matrix
+        v = self.outputs[0].default_value
+        # print( Matrix( ( ( v[ 0], v[ 1], v[ 2], v[ 3],),
+        #                  ( v[ 4], v[ 5], v[ 6], v[ 7],),
+        #                  ( v[ 8], v[ 9], v[10], v[11],),
+        #                  ( v[12], v[13], v[14], v[15],), ) ) )
+        return None
+    
+    def update_node(self, context):
+        self.outputs["Matrix"].default_value = self.set_matrix()
+
+    def update(self):
+        mat_sock = self.outputs[0]
+        mat_sock.default_value = self.set_matrix()
+
+
+
+class UtilityMetaRigNode(Node, MantisNode):
+    """Gets a matrix from a meta-rig bone."""
+    bl_idname = "UtilityMetaRig"
+    bl_label = "Meta-Rig"
+    bl_icon = "NODE"
+    
+    armature:bpy.props.StringProperty()
+    pose_bone:bpy.props.StringProperty()
+    
+    def init(self, context):
+        armt = self.inputs.new("EnumMetaRigSocket", "Meta-Armature")
+        bone = self.inputs.new("EnumMetaBoneSocket", "Meta-Bone")
+        armt.icon = "OUTLINER_OB_ARMATURE"
+        bone.icon = "BONE_DATA"
+        bone.hide=True
+        self.outputs.new("MatrixSocket", "Matrix")
+    
+    def display_update(self, parsed_tree, context):
+        from .base_definitions import get_signature_from_edited_tree
+        nc = parsed_tree.get(get_signature_from_edited_tree(self, context))
+        if nc:
+            self.armature= nc.evaluate_input("Meta-Armature")
+            self.pose_bone= nc.evaluate_input("Meta-Bone")
+        if not self.armature:
+            self.inputs["Meta-Bone"].hide=True
+        else:
+            self.inputs["Meta-Bone"].hide=False
+
+class UtilityBonePropertiesNode(Node, MantisNode):
+    """Provides as sockets strings identifying bone transform properties."""
+    bl_idname = "UtilityBoneProperties"
+    bl_label = "Bone Properties"
+    bl_icon = "NODE"
+    #bl_width_default = 250
+    
+    def init(self, context):
+        self.outputs.new("ParameterStringSocket", "matrix")
+        self.outputs.new("ParameterStringSocket", "matrix_local")
+        self.outputs.new("ParameterStringSocket", "matrix_basis")
+        self.outputs.new("ParameterStringSocket", "head")
+        self.outputs.new("ParameterStringSocket", "tail")
+        self.outputs.new("ParameterStringSocket", "length")
+        self.outputs.new("ParameterStringSocket", "rotation")
+        self.outputs.new("ParameterStringSocket", "location")
+        self.outputs.new("ParameterStringSocket", "scale")
+        
+        for o in self.outputs:
+            o.text_only = True
+
+class UtilityDriverVariableNode(Node, MantisNode):
+    """Creates a variable for use in a driver."""
+    bl_idname = "UtilityDriverVariable"
+    bl_label = "Driver Variable"
+    bl_icon = "NODE"
+    
+    
+    def init(self, context):
+        self.inputs.new("EnumDriverVariableType", "Variable Type")
+        self.inputs.new("ParameterStringSocket", "Property")
+        self.inputs.new("IntSocket", "Property Index")
+        self.inputs.new("EnumDriverVariableEvaluationSpace", "Evaluation Space")
+        self.inputs.new("EnumDriverRotationMode", "Rotation Mode")
+        self.inputs.new("xFormSocket", "xForm 1")
+        self.inputs.new("xFormSocket", "xForm 2")
+        self.outputs.new("DriverVariableSocket", "Driver Variable")
+        
+    def update_on_socket_change(self, context):
+        self.update()
+    
+    def display_update(self, parsed_tree, context):
+        from .base_definitions import get_signature_from_edited_tree
+        if context.space_data:
+            node_tree = context.space_data.path[0].node_tree
+            nc = parsed_tree.get(get_signature_from_edited_tree(self, context))
+            if nc:
+                driver_type = nc.evaluate_input("Variable Type")
+                if driver_type == 'SINGLE_PROP':
+                    self.inputs[0].hide = False
+                    self.inputs[1].hide = False
+                    self.inputs[2].hide = False
+                    self.inputs[3].hide = False
+                    self.inputs[4].hide = False
+                    self.inputs[5].hide = False
+                    self.inputs[6].hide = True
+                elif driver_type == 'LOC_DIFF':
+                    self.inputs[0].hide = False
+                    self.inputs[1].hide = True
+                    self.inputs[2].hide = True
+                    self.inputs[3].hide = True
+                    self.inputs[4].hide = True
+                    self.inputs[5].hide = False
+                    self.inputs[6].hide = False
+                elif driver_type == 'ROTATION_DIFF':
+                    self.inputs[0].hide = False
+                    self.inputs[1].hide = True
+                    self.inputs[2].hide = True
+                    self.inputs[3].hide = True
+                    self.inputs[4].hide = False
+                    self.inputs[5].hide = False
+                    self.inputs[6].hide = False
+    
+
+class UtilityFCurveNode(Node, MantisNode):
+    """Creates an fCurve for use with a driver."""
+    bl_idname = "UtilityFCurve"
+    bl_label = "fCurve"
+    bl_icon = "NODE"
+    
+    use_kf_nodes   : bpy.props.BoolProperty(default=False)
+    fake_fcurve_ob : bpy.props.PointerProperty(type=bpy.types.Object)
+    
+    def init(self, context):
+        self.outputs.new("FCurveSocket", "fCurve")
+        if not self.fake_fcurve_ob:
+            ob = bpy.data.objects.new("fake_ob_"+self.name, None)
+            self.fake_fcurve_ob = ob
+            ob.animation_data_create()
+            ob.animation_data.action = bpy.data.actions.new('fake_action_'+self.name)
+            fc = ob.animation_data.action.fcurves.new('location', index=0, action_group='location')
+            fc.keyframe_points.add(2)
+            kf0 = fc.keyframe_points[0]; kf0.co_ui = (0, 0)
+            kf1 = fc.keyframe_points[1]; kf1.co_ui = (1, 1)
+            #
+            kf0.interpolation = 'BEZIER'
+            kf0.handle_left_type  = 'AUTO_CLAMPED'
+            kf0.handle_right_type = 'AUTO_CLAMPED'
+            kf1.interpolation = 'BEZIER'
+            kf1.handle_left_type  = 'AUTO_CLAMPED'
+            kf1.handle_right_type = 'AUTO_CLAMPED'
+            #
+            
+    def draw_buttons(self, context, layout):
+        if self.use_kf_nodes:
+            layout.prop(self, "use_kf_nodes",  text="[ Use fCurve data ]", toggle=True, invert_checkbox=True)
+            layout.operator( 'mantis.fcurve_node_add_kf' )
+            if (len(self.inputs) > 0):
+                layout.operator( 'mantis.fcurve_node_remove_kf' )
+        else:
+            layout.prop(self, "use_kf_nodes",  text="[ Use Keyframe Nodes ]", toggle=True)
+            layout.operator('mantis.edit_fcurve_node')
+        
+            
+    
+    # THE DIFFICULT part is getting it to show up in the graph editor
+    # TRY:
+    #       a modal operator that opens the Graph Editor
+    #       and then finishes when it is closed
+    #       it would reveal the object holding the fCurve before
+    #       showing the Graph Editor
+    #       And hide it after closing it.
+    #
+        
+        
+        
+        
+
+class UtilityDriverNode(Node, MantisNode):
+    """Represents a Driver relationship"""
+    bl_idname = "UtilityDriver"
+    bl_label = "Driver"
+    bl_icon = "NODE"
+    
+    def init(self, context):
+        self.inputs.new("EnumDriverType", "Driver Type")
+        self.inputs.new("FCurveSocket", "fCurve")
+        self.inputs.new("StringSocket", "Expression")
+        self.outputs.new("DriverSocket", "Driver")
+        
+    def update(self):
+        return
+        context = bpy.context
+        try:
+            tree = context.space_data.path[0].node_tree
+            proceed = True
+        except AttributeError:
+            proceed = False
+        if proceed:
+            from mantis.f_nodegraph import (GetDownstreamXFormNodes, get_node_container)
+            if (node_container := get_node_container(self, context)[0]):
+                dType = node_container.evaluate_input("Driver Type")
+            else:
+                dType = self.inputs[0].default_value
+            
+            if dType == 'SCRIPTED':
+                self.inputs["Expression"].hide = False
+            else:
+                self.inputs["Expression"].hide = True
+    
+    def draw_buttons(self, context, layout):
+        layout.operator( 'mantis.driver_node_add_variable' )
+        if (len(self.inputs) > 2):
+            layout.operator( 'mantis.driver_node_remove_variable' )
+
+class UtilitySwitchNode(Node, MantisNode):
+    """Represents a switch relationship between one driver property and one or more driven properties."""
+    bl_idname = "UtilitySwitch"
+    bl_label = "Switch"
+    bl_icon = "NODE"
+    
+    def init(self, context):
+        self.inputs.new("xFormSocket", "xForm")
+        self.inputs.new("ParameterStringSocket", "Parameter")
+        self.inputs.new("IntSocket", "Parameter Index")
+        self.inputs.new("BooleanSocket", "Invert Switch")
+        self.outputs.new("DriverSocket", "Driver")
+
+class UtilityCombineThreeBoolNode(Node, MantisNode):
+    """Combines three booleans into a three-bool."""
+    bl_idname = "UtilityCombineThreeBool"
+    bl_label = "CombineThreeBool"
+    bl_icon = "NODE"
+    
+    def init(self, context):
+        self.inputs.new("BooleanSocket", "X")
+        self.inputs.new("BooleanSocket", "Y")
+        self.inputs.new("BooleanSocket", "Z")
+        self.outputs.new("BooleanThreeTupleSocket", "Three-Bool")
+        # this node should eventually just be a Combine Boolean Three-Tuple node
+        # and the "Driver" output will need to be figured out some other way
+class UtilityCombineVectorNode(Node, MantisNode):
+    """Combines three floats into a vector."""
+    bl_idname = "UtilityCombineVector"
+    bl_label = "CombineVector"
+    bl_icon = "NODE"
+    
+    def init(self, context):
+        self.inputs.new("FloatSocket", "X")
+        self.inputs.new("FloatSocket", "Y")
+        self.inputs.new("FloatSocket", "Z")
+        self.outputs.new("VectorSocket", "Vector")
+        # this node should eventually just be a Combine Boolean Three-Tuple node
+        # and the "Driver" output will need to be figured out some other way
+        
+class UtilityCatStringsNode(Node, MantisNode):
+    """Adds a suffix to a string"""
+    bl_idname = "UtilityCatStrings"
+    bl_label = "Concatenate Strings"
+    bl_icon = "NODE"
+    
+    def init(self, context):
+        self.inputs.new("StringSocket", "String_1")
+        self.inputs.new("StringSocket", "String_2")
+        self.outputs.new("StringSocket", "OutputString")
+    
+class InputLayerMaskNode(Node, MantisNode):
+    """Represents a layer mask for a bone."""
+    bl_idname = "InputLayerMaskNode"
+    bl_label = "Layer Mask"
+    bl_icon = "NODE"
+    
+    def init(self, context):
+        self.outputs.new("LayerMaskInputSocket", "Layer Mask")
+
+class InputExistingGeometryObjectNode(Node, MantisNode):
+    """Represents an existing geometry object from within the scene."""
+    bl_idname = "InputExistingGeometryObject"
+    bl_label = "Existing Object"
+    bl_icon = "NODE"
+    
+    def init(self, context):
+        self.inputs.new("StringSocket", "Name")
+        self.outputs.new("xFormSocket", "Object")
+
+class InputExistingGeometryDataNode(Node, MantisNode):
+    """Represents a mesh or curve datablock from the scene."""
+    bl_idname = "InputExistingGeometryData"
+    bl_label = "Existing Geometry"
+    bl_icon = "NODE"
+    
+    def init(self, context):
+        self.inputs.new("StringSocket", "Name")
+        self.outputs.new("GeometrySocket", "Geometry")

+ 902 - 0
ops_generate_tree.py

@@ -0,0 +1,902 @@
+from bpy.types import Operator
+import bpy
+from mantis.utilities import (prRed, prGreen, prPurple, prWhite,
+                              prOrange,
+                              wrapRed, wrapGreen, wrapPurple, wrapWhite,
+                              wrapOrange,)
+
+def mantis_poll_op(context):
+    space = context.space_data
+    if hasattr(space, "node_tree"):
+        if (space.node_tree):
+            return (space.tree_type == "MantisTree")
+    return False
+
+def create_inheritance_node(pb, parent_name, bone_inherit_node, node_tree):
+    parent_node = node_tree.nodes.new("linkInherit")
+    parent_bone_node = node_tree.nodes.get(parent_name)
+    if not parent_bone_node:
+        raise RuntimeError("Can't Find parent node!!")
+    parent_node.location = parent_bone_node.location; parent_node.location.x+=200
+    node_tree.links.new(parent_bone_node.outputs["xForm Out"], parent_node.inputs['Parent'])
+    parent_node.inputs["Connected"].default_value        = pb.bone.use_connect
+    parent_node.inputs["Inherit Scale"].default_value    = pb.bone.inherit_scale
+    parent_node.inputs["Inherit Rotation"].default_value = pb.bone.use_inherit_rotation
+    if (bone_inherit_node.get(parent_bone_node.name)):
+        bone_inherit_node[parent_bone_node.name].append(parent_node)
+    else:
+        bone_inherit_node[parent_bone_node.name] = [parent_node]
+    return parent_node
+
+def get_pretty_name(name):
+    pretty = name.replace("_", " ")
+    words = pretty.split(" "); pretty = ''
+    for word in words:
+        pretty+=(word.capitalize()); pretty+=' '
+    return pretty [:-1] #omit the last trailing space
+
+
+constraint_link_map={
+    'COPY_LOCATION'   : "LinkCopyLocation",
+    'COPY_ROTATION'   : "LinkCopyRotation",
+    'COPY_SCALE'      : "LinkCopyScale",
+    'COPY_TRANSFORMS' : "LinkCopyTransforms",
+    'LIMIT_DISTANCE'  : "LinkLimitDistance",
+    'LIMIT_LOCATION'  : "LinkLimitLocation",
+    'LIMIT_ROTATION'  : "LinkLimitRotation",
+    'LIMIT_SCALE'     : "LinkLimitScale",
+    'DAMPED_TRACK'    : "LinkDampedTrack",
+    'LOCKED_TRACK'    : "LinkLockedTrack",
+    'STRETCH_TO'      : "LinkStretchTo",
+    'TRACK_TO'        : "LinkTrackTo",
+    'CHILD_OF'        : "LinkInheritConstraint",
+    'IK'              : "LinkInverseKinematics",
+    'ARMATURE'        : "LinkArmature",
+    'SPLINE_IK'       : "LinkSplineIK",
+    'TRANSFORM'       : "LinkTransformation",
+    }
+
+def create_relationship_node_for_constraint(node_tree, c):
+    if cls_name := constraint_link_map.get(c.type):
+        return node_tree.nodes.new(cls_name)
+    else:
+        prRed ("Not yet implemented: %s" % c.type)
+        return None
+    
+    
+def fill_parameters(node, c):
+    # just try the basic parameters...
+    
+    node.mute = not c.enabled
+    if c.mute == True and c.enabled == True:
+        node.mute = c.mute
+        # this is obviously stupid, but it's the new API as of, IIRC, 2.80 
+    
+    try:
+        owner_space = c.owner_space
+        if c.owner_space == 'CUSTOM':
+            raise NotImplementedError("Custom Space is a TODO")
+        if ( input := node.inputs.get("Owner Space") ):
+            input.default_value = owner_space
+    except AttributeError:
+        pass
+    
+    try:
+        target_space = c.target_space
+        if c.target_space == 'CUSTOM':
+            raise NotImplementedError("Custom Space is a TODO")
+        if ( input := node.inputs.get("Target Space") ):
+            input.default_value = target_space
+    except AttributeError:
+        pass
+    
+    try:
+        use_x, use_y, use_z = c.use_x, c.use_y, c.use_z
+        if ( input := node.inputs.get("Axes") ):
+            input.default_value[0] = use_x
+            input.default_value[1] = use_y
+            input.default_value[2] = use_z
+    except AttributeError:
+        pass
+    try:
+        invert_x, invert_y, invert_z = c.invert_x, c.invert_y, c.invert_z
+        if ( input := node.inputs.get("Invert") ):
+            input.default_value[0] = invert_x
+            input.default_value[1] = invert_y
+            input.default_value[2] = invert_z
+    except AttributeError:
+        pass
+    
+    try:
+        influence = c.influence
+        if ( input := node.inputs.get("Influence") ):
+            input.default_value = influence
+    except AttributeError:
+        pass
+    
+    # gonna dispense with the try/except from here on
+    if   (c.type == 'COPY_LOCATION'):
+        node.inputs["Head/Tail"].default_value = c.head_tail
+        node.inputs["UseBBone"].default_value  = c.use_bbone_shape
+    elif (c.type == 'COPY_ROTATION'):
+        node.inputs["RotationOrder"].default_value = c.euler_order
+        # ofset (legacy) is not supported TODO BUG
+        if (mix_mode := c.mix_mode) == 'OFFSET':
+            mix_mode = 'AFTER'
+        node.inputs["Rotation Mix"].default_value  = mix_mode
+    elif (c.type == 'COPY_SCALE'):
+        #node.inputs["Additive"].default_value = c.use_make_uniform # not yet implemented
+        #node.inputs["Power"].default_value = c.head_tail
+        node.inputs["Average"].default_value  = c.use_make_uniform
+        node.inputs["Offset"].default_value  = c.use_offset
+    elif (c.type == 'COPY_TRANSFORMS'):
+        node.inputs["Head/Tail"].default_value = c.head_tail
+        node.inputs["UseBBone"].default_value  = c.use_bbone_shape
+        node.inputs["Mix"].default_value  = c.mix_mode
+    elif (c.type == 'LIMIT_DISTANCE'):
+        print ("Not yet handled: ", c.type)
+    elif (c.type in ['LIMIT_LOCATION', 'LIMIT_ROTATION', 'LIMIT_SCALE']):
+        # print (c.type)
+        try:
+            node.inputs["Use Max X"].default_value = c.use_max_x
+            node.inputs["Use Max Y"].default_value = c.use_max_y
+            node.inputs["Use Max Z"].default_value = c.use_max_z
+            #
+            node.inputs["Use Min X"].default_value = c.use_min_x
+            node.inputs["Use Min Y"].default_value = c.use_min_y
+            node.inputs["Use Min Z"].default_value = c.use_min_z
+        except AttributeError: # rotation
+            node.inputs["Use X"].default_value = c.use_limit_x
+            node.inputs["Use Y"].default_value = c.use_limit_y
+            node.inputs["Use Z"].default_value = c.use_limit_z
+        node.inputs["Max X"].default_value = c.max_x
+        node.inputs["Max Y"].default_value = c.max_y
+        node.inputs["Max Z"].default_value = c.max_z
+        #
+        node.inputs["Min X"].default_value = c.min_x
+        node.inputs["Min Y"].default_value = c.min_y
+        node.inputs["Min Z"].default_value = c.min_z
+    elif (c.type == 'DAMPED_TRACK'):
+        node.inputs["Head/Tail"].default_value   = c.head_tail
+        node.inputs["UseBBone"].default_value    = c.use_bbone_shape
+        node.inputs["Track Axis"].default_value  = c.track_axis
+    elif (c.type == 'LOCKED_TRACK'):
+        node.inputs["Head/Tail"].default_value   = c.head_tail
+        node.inputs["UseBBone"].default_value    = c.use_bbone_shape
+        node.inputs["Track Axis"].default_value  = c.track_axis
+        node.inputs["Lock Axis"].default_value   = c.lock_axis
+    elif (c.type == 'STRETCH_TO'):
+        node.inputs["Head/Tail"].default_value = c.head_tail
+        node.inputs["UseBBone"].default_value  = c.use_bbone_shape
+        node.inputs["Volume Variation"].default_value = c.bulge
+        node.inputs["Use Volume Min"].default_value = c.use_bulge_min
+        node.inputs["Volume Min"].default_value = c.bulge_min
+        node.inputs["Use Volume Max"].default_value = c.use_bulge_max
+        node.inputs["Volume Max"].default_value = c.bulge_max
+        node.inputs["Smooth"].default_value = c.bulge_smooth
+        node.inputs["Maintain Volume"].default_value = c.volume
+        node.inputs["Rotation"].default_value        = c.keep_axis
+    elif (c.type == 'TRACK_TO'):
+        print ("Not yet handled: ", c.type)
+    elif (c.type == 'CHILD_OF'):
+        print ("Not yet handled: ", c.type)
+    elif (c.type == 'IK'):
+        node.inputs["Chain Length"].default_value = c.chain_count
+        node.inputs["Use Tail"].default_value = c.use_tail
+        node.inputs["Stretch"].default_value = c.use_stretch
+        # this isn't quite right lol
+        node.inputs["Position"].default_value = c.weight
+        node.inputs["Rotation"].default_value = c.orient_weight
+        if not (c.use_location):
+            node.inputs["Position"].default_value = 0
+        if not (c.use_rotation):
+            node.inputs["Rotation"].default_value = 0
+    elif (c.type == 'ARMATURE'):
+        node.inputs["Preserve Volume"].default_value = c.use_deform_preserve_volume
+        node.inputs["Use Envelopes"].default_value = c.use_bone_envelopes
+        node.inputs["Use Current Location"].default_value = c.use_current_location
+        for i in range(len(c.targets)):
+            bpy.ops.mantis.link_armature_node_add_target({'node':node})
+    elif (c.type == 'SPLINE_IK'):
+        node.inputs["Chain Length"].default_value = c.chain_count
+        node.inputs["Even Divisions"].default_value = c.use_even_divisions
+        node.inputs["Chain Offset"].default_value = c.use_chain_offset
+        node.inputs["Use Curve Radius"].default_value = c.use_curve_radius
+        node.inputs["Y Scale Mode"].default_value = c.y_scale_mode
+        node.inputs["XZ Scale Mode"].default_value = c.xz_scale_mode
+    elif (c.type == 'TRANSFORM'):
+        # I can't be arsed to do all this work..
+        from .link_containers import transformation_props_sockets as props
+        for prop, (sock_name, _unused) in props.items():
+            if "from" in prop:
+                if prop in ["map_from"] or "to" in prop:
+                   pass 
+                elif c.map_from == 'LOCATION':
+                    if "scale" in prop:
+                        continue
+                    if "rot" in prop:
+                        continue
+                elif c.map_from == 'ROTATION':
+                    if "rot" not in prop:
+                        continue
+                elif c.map_from == 'SCALE':
+                    if "scale" not in prop:
+                        continue
+            if "to" in prop:
+                if prop in ["map_to"] or "from" in prop:
+                   pass 
+                elif c.map_from == 'LOCATION':
+                    if "scale" in prop:
+                        continue
+                    if "rot" in prop:
+                        continue
+                elif c.map_from == 'ROTATION':
+                    if "rot" not in prop:
+                        continue
+                elif c.map_from == 'SCALE':
+                    if "scale" not in prop:
+                        continue
+            node.inputs[sock_name].default_value = getattr(c, prop)
+            if prop in "mute":
+                node.inputs[sock_name].default_value = not getattr(c, prop)
+            
+        # should probably do it this way all over actually.
+
+
+    else:
+        raise NotImplementedError("Not handled yet: %s" % c.type)
+        return None
+
+
+
+
+def walk_edit_bone(armOb, bone):
+    # this is a simplified version of the node-tree walking code
+    bonePath, bones, lines, seek = [0,], set(), [], bone
+    while (True):
+        curheight = len(bonePath)-1; ind = bonePath[-1]
+        if (curheight == 0) and (ind > len(bone.children)-1):
+            break 
+        if (curheight > 0):
+            parent = seek.parent
+            if (ind > len(seek.children)-1 ):
+                bonePath[curheight-1]+=1
+                del bonePath[curheight]
+                seek = parent
+                continue
+                # should work...
+        seek = get_bone_from_path(bone, bonePath)
+
+        if (seek.name not in bones):
+            lines.append(bonePath.copy())
+        bones.add(seek.name)
+        
+        if (seek.children):
+            bonePath.append(0)
+        else:
+            bonePath[curheight] = bonePath[curheight] + 1
+            seek = seek.parent
+    return lines
+
+
+def get_bone_from_path(root_bone, path):
+    # this function assumes the path is valid
+    path = path.copy(); bone = root_bone
+    while(path):
+        bone = bone.children[path.pop(0)]
+    return bone
+
+def setup_custom_properties(bone_node, pb):
+    for k, v in pb.items(): # Custom Properties
+        socktype, prop_type = '', type(v)
+        # print (prop_type)
+        if   prop_type == bool:
+            socktype = 'ParameterBoolSocket'
+        elif prop_type == int:
+            socktype = 'ParameterIntSocket'
+        elif prop_type == float:
+            socktype = 'ParameterFloatSocket'
+        elif prop_type == bpy.props.FloatVectorProperty:
+            socktype = 'ParameterVectorSocket'
+        elif prop_type == str:
+            socktype = 'ParameterStringSocket'
+        else:
+            continue # it's a PointerProp or something
+        #if self.prop_type == 'ENUM':
+        #    sock_type = 'ParameterStringSocket'
+        new_prop = bone_node.inputs.new( socktype, k)
+        bone_node.outputs.new( socktype, k)
+        # set its value and limits and such
+        # from rna_prop_ui import rna_idprop_ui_create
+        # I have no idea how to get the data in a sane way, I guess I will use this...
+        ui_data = pb.id_properties_ui(k).as_dict()
+        
+        new_prop.default_value = ui_data['default']
+        try:
+            new_prop.min      = ui_data['min']
+            new_prop.max      = ui_data['max']
+            new_prop.soft_min = ui_data['soft_min']
+            new_prop.soft_max = ui_data['soft_max']
+        except AttributeError:
+            pass # it's not a number
+        except KeyError:
+            pass # same, or not defined maybe
+        try:
+            new_prop.description = ui_data['description']
+        except KeyError:
+            prOrange("Figure out why this happens?")
+
+def setup_ik_settings(bone_node, pb):
+    # Set Up IK settings:
+    stiffness = [pb.ik_stiffness_x, pb.ik_stiffness_y, pb.ik_stiffness_z]
+    lock = [pb.lock_ik_x, pb.lock_ik_y, pb.lock_ik_z]
+    limit = [pb.use_ik_limit_x, pb.use_ik_limit_y, pb.use_ik_limit_z]
+    bone_node.inputs["IK Stretch"].default_value = pb.ik_stretch
+    bone_node.inputs["Lock IK"].default_value = lock
+    bone_node.inputs["IK Stiffness"].default_value = stiffness
+    bone_node.inputs["Limit IK"].default_value = limit
+    bone_node.inputs["X Min"].default_value = pb.ik_min_x
+    bone_node.inputs["X Max"].default_value = pb.ik_max_x
+    bone_node.inputs["Y Min"].default_value = pb.ik_min_y
+    bone_node.inputs["Y Max"].default_value = pb.ik_max_y
+    bone_node.inputs["Z Min"].default_value = pb.ik_min_z
+    bone_node.inputs["Z Max"].default_value = pb.ik_max_z
+
+def setup_vp_settings(bone_node, pb, do_after, node_tree):
+    # bone_node.inputs["Hide"].default_value = pb.bone.hide
+    bone_node.inputs["Custom Object Scale"].default_value = pb.custom_shape_scale_xyz
+    bone_node.inputs["Custom Object Translation"].default_value = pb.custom_shape_translation
+    bone_node.inputs["Custom Object Rotation"].default_value = pb.custom_shape_rotation_euler
+    bone_node.inputs["Custom Object Scale to Bone Length"].default_value = pb.use_custom_shape_bone_size
+    bone_node.inputs["Custom Object Wireframe"].default_value = pb.bone.show_wire
+    bone_node.inputs["Layer Mask"].default_value = pb.bone.layers
+    
+    if (shape_ob := pb.custom_shape):
+        shape_n = None
+        for n in node_tree.nodes:
+            if n.name == shape_ob.name:
+                shape_n = n
+                break
+        else: # we make it now
+            shape_n = node_tree.nodes.new("InputExistingGeometryObject")
+            shape_n.name = shape_ob.name
+            shape_n.label = shape_ob.name
+            shape_n.inputs["Name"].default_value = shape_ob.name
+        node_tree.links.new(shape_n.outputs["Object"], bone_node.inputs['Custom Object'])
+    
+    if (shape_xform_ob := pb.custom_shape_transform): # not implemented just yet
+        shape_xform_n = None
+        for n in node_tree.nodes:
+            if n.name == shape_xform_ob.name:
+                shape_xform_n = n
+                node_tree.links.new(shape_xform_n.outputs["xForm"], bone_node.inputs['Custom Object xForm Override'])
+                break
+        else: # make it a task
+            do_after.append( ("Custom Object xForm Override", bone_node.name , shape_xform_ob.name ) )
+    # all the above should be in a function.
+
+
+def setup_df_settings(bone_node, pb):
+        bone_node.inputs["Deform"] = pb.bone.use_deform
+        # TODO: get the rest of these working
+        # eb.envelope_distance     = self.evaluate_input("Envelope Distance")
+        # eb.envelope_weight       = self.evaluate_input("Envelope Weight")
+        # eb.use_envelope_multiply = self.evaluate_input("Envelope Multiply")
+        # eb.head_radius           = self.evaluate_input("Envelope Head Radius")
+        # eb.tail_radius           = self.evaluate_input("Envelope Tail Radius")
+
+def create_driver(in_node_name, out_node_name, armOb, finished_drivers, switches, driver_vars, fcurves, drivers, node_tree):
+    # TODO: CLEAN this ABOMINATION
+    print ("DRIVER: ", in_node_name, out_node_name)
+    in_node  = node_tree.nodes[ in_node_name]
+    out_node = node_tree.nodes[out_node_name]
+    for fc in armOb.animation_data.drivers:
+        if (in_node.label not in fc.data_path) or ( "[\""+out_node.label+"\"]" not in fc.data_path):
+#                        print ("node not in name?: %s" % fc.data_path)
+            continue
+        if fc.data_path in finished_drivers:
+            continue
+        finished_drivers.add(fc.data_path)
+        print ("Creating driver.... %s" % fc.data_path)
+        keys = []
+        for k in fc.keyframe_points:
+            key = {}
+            for prop in dir(k):
+                if ("__" in prop) or ("bl_" in prop): continue
+                #it's __name__ or bl_rna or something
+                key[prop] = getattr(k, prop)
+            keys.append(key)
+        switch, inverted = False, False
+        if (fc.evaluate(0) == 0) and (fc.evaluate(1) == 1):
+            switch = True
+        elif (fc.evaluate(0) == 1) and (fc.evaluate(1) == 0):
+            switch = True; inverted = True
+        if (fc.driver.type == 'SCRIPTED'):
+            #print (fc.driver.expression)
+            if not (len(fc.driver.variables) == 1 and fc.driver.expression == fc.driver.variables[0].name):
+                switch = False
+        if (switch):
+            # OK, let's prepare before making the node
+            #  we want to reuse existing nodes if possible.
+            target_string = fc.driver.variables[0].targets[0].data_path
+            bone = target_string.split("pose.bones[\"")[1]
+            bone = bone.split("\"]")[0]
+            bone_node = node_tree.nodes.get(bone)
+            if not (bone_node):
+                raise RuntimeError("excpected to find....", bone)
+            
+            
+            p_string = fc.driver.variables[0].targets[0].data_path
+            p_string = p_string.split("[\"")[-1]; p_string = p_string.split("\"]")[0]
+            #switch_node.inputs["Parameter"].default_value = p_string
+            #switch_node.inputs["Parameter Index"].default_value = fc.array_index
+            #switch_node.inputs["Invert Switch"].default_value = inverted
+            parameter = fc.data_path
+            
+            # Try to find an existing node.
+            fail = False
+            switch_node = None
+            for n in switches:
+                if n.inputs[0].is_linked:
+                    if n.inputs[0].links[0].from_node != bone_node:
+                        fail = True
+                if n.inputs[1].is_linked:
+                    if n.inputs[1].links[0].from_node != bone_node:
+                        fail = True
+                    if n.inputs[1].links[0].from_socket != bone_node.outputs.get(p_string):
+                        fail = True
+                else:
+                    if n.inputs[1].default_value != p_string:
+                        fail = True
+                if n.inputs[2].default_value != fc.array_index:
+                    fail = True
+                if n.inputs[3].default_value != inverted:
+                    fail = True
+                if not fail:
+                    switch_node = n
+                    break # found it!
+            else:
+                # make and connect the switch node
+                switch_node = node_tree.nodes.new("UtilitySwitch"); switches.append(switch_node)
+                node_tree.links.new(bone_node.outputs["xForm Out"], switch_node.inputs[0])
+                node_tree.links.new(bone_node.outputs[p_string], switch_node.inputs[1])
+                switch_node.inputs[2].default_value = fc.array_index
+                switch_node.inputs[3].default_value = inverted
+                #print ("   Inverted?  ", inverted, (fc.evaluate(0) == 1) and (fc.evaluate(1) == 0), switch_node.inputs[3].default_value)
+                if not inverted:
+                    print ("    --> Check this node: %s" % switch_node.name)
+            # this may be a custom property or a normal property...
+            # this should lead to a constraint
+            if len(parameter.split("[\"") ) == 3:
+                property = parameter.split(".")[-1]
+                if (property == 'mute'): # this is mapped to the 'Enable' socket...
+                    prop_in = out_node.inputs.get('Enable')
+                else:
+                    prop_in = out_node.inputs.get(get_pretty_name(property))
+                if prop_in:
+                    node_tree.links.new(switch_node.outputs["Driver"], prop_in)
+                else:
+                    print ("   couldn't find: ", property, out_node.label, out_node.name)
+                # this won't always work tho
+            #Finally, it should be noted that we are assuming it uses the same object ...
+            #  drivers from Rigify always should use the same object, but I want to support
+            #  detecting drivers across objects.
+        else: # we'll have to set this one up manually
+            # Let's make the variable nodes, the Driver node, and the fCurve node.
+            # Get the variable information
+            if (True):
+                var_nodes = []; num_vars = 0
+                for num_vars, var in enumerate(fc.driver.variables):
+                    target1, target2, bone_target, bone_target2 = [None]*4
+                    var_data = {}
+                    var_data["Variable Type"] = var.type
+                    if len(var.targets) >= 1:
+                        target1 = var.targets[0]
+                        if (var_data["Variable Type"] != 'SINGLE_PROP'):
+                            bone_target = var.targets[0].bone_target
+                        else: # figure it out by the data path string.
+                            target_string = var.targets[0].data_path
+                            bone_target = target_string.split("pose.bones[\"")[1]; bone_target = bone_target.split("\"]")[0]
+                            # we also need to get the property.
+                            p_string = fc.driver.variables[0].targets[0].data_path
+                            p_string = p_string.split("[\"")[-1]; p_string = p_string.split("\"]")[0]
+                            var_data["Property"] = p_string
+                        if (var_data["Variable Type"] == 'TRANSFORMS'):
+                            transform_channel_map = {
+                                "LOC_X"     : ('location', 0),
+                                "LOC_Y"     : ('location', 1),
+                                "LOC_Z"     : ('location', 2),
+                                "ROT_X"     : ('rotation', 0),
+                                "ROT_Y"     : ('rotation', 1),
+                                "ROT_Z"     : ('rotation', 2),
+                                "ROT_W"     : ('rotation', 3),
+                                "SCALE_X"   : ('scale', 0),
+                                "SCALE_Y"   : ('scale', 1),
+                                "SCALE_Z"   : ('scale', 2),
+                                "SCALE_AVG" : ('scale', 3), }
+                            if (var.transform_type in transform_channel_map.keys()):
+                                var_data["Property"], var_data["Property Index"] = transform_channel_map[var.transform_type]
+                            var_data["Evaluation Space"] = var.targets[0].transform_space
+                            var_data["Rotation Mode"] = var.targets[0].rotation_mode
+                    if len(var.targets) == 2:
+                        target2 = var.targets[1]
+                        bone_target2 = var.targets[1].bone_target
+                    # check if the variable already exists in the tree.
+                    target_node1, target_node2 = None, None
+                    if (target1 and bone_target):
+                        target_node1 = node_tree.nodes[bone_target]
+                    elif (target1 and not bone_target):
+                        target_node1 = node_tree.nodes[target1]
+                    if (target2 and bone_target2):
+                        target_node2 = node_tree.nodes[bone_target2]
+                    elif (target2 and not bone_target2):
+                        target_node2 = node_tree.nodes[target2]
+                    
+                    
+                    var_node = None
+                    for n in driver_vars:                            
+                        fail = False
+                        if (inp := n.inputs['xForm 1']).is_linked:
+                            if inp.links[0].from_node != target_node1:
+                                fail = True
+                        if (inp := n.inputs['xForm 2']).is_linked:
+                            if inp.links[0].from_node != target_node2:
+                                fail = True
+                        #
+                        if n.inputs[0].default_value != var_data["Variable Type"]:
+                            fail = True
+                        if n.inputs[1].default_value != var_data["Property"]:
+                            fail = True
+                        try:
+                            if n.inputs[2].default_value != var_data["Property Index"]:
+                                fail = True
+                            if n.inputs[3].default_value != var_data["Evaluation Space"]:
+                                fail = True
+                            if n.inputs[4].default_value != var_data["Rotation Mode"]:
+                                fail = True
+                        except KeyError:
+                            pass # this is a SCRIPTED node it seems
+                        if not fail:
+                            var_node = n
+                            prWhite("Variable Node Found %s!" % var_node )
+                            break # found it!
+                    else:
+                        var_node = node_tree.nodes.new("UtilityDriverVariable"); driver_vars.append(var_node)
+                        prRed("Creating Node: %s" % var_node.name)
+                        for key, value in var_data.items():
+                            var_node.inputs[key].default_value = value
+                        if (target1 and bone_target):
+                            node_tree.links.new(node_tree.nodes[bone_target].outputs['xForm Out'], var_node.inputs['xForm 1'])
+                        elif (target1 and not bone_target):
+                            node_tree.links.new(node_tree.nodes[target1].outputs['xForm Out'], var_node.inputs['xForm 1'])
+                        if (target2 and bone_target2):
+                            node_tree.links.new(node_tree.nodes[bone_target2].outputs['xForm Out'], var_node.inputs['xForm 2'])
+                        elif (target2 and not bone_target2):
+                            node_tree.links.new(node_tree.nodes[target2].outputs['xForm Out'], var_node.inputs['xForm 2'])
+                    var_nodes.append(var_node)
+                    num_vars+=1 # so the len(num_vars) will be correct
+                # get the keyframes from the driver fCurve
+                keys = {}
+                from mathutils import Vector
+                if len(fc.keyframe_points) > 0:
+                    # TODO: make this do more than co_ui
+                    for i, k in enumerate(fc.keyframe_points):
+                        keys[i] = {'co_ui':k.co_ui}
+#                                print (fc.data_path)
+#                                print (len(fc.keyframe_points))
+#                                raise NotImplementedError("Not needed for first milestone")
+                else:
+#                                fc_ob = fCurve_node.fake_fcurve_ob
+#                                node_fc = fc_ob.animation_data.action.fcurves[0]
+#                                fc.keyframe_points.add(2)
+                    if ((len(fc.modifiers) == 0) or ((fc.evaluate(0) == 0) and (fc.evaluate(1) == 1))):
+                        keys[0] = {'co_ui':Vector((0, 0))}
+                        keys[1] = {'co_ui':Vector((1, 1))}
+                    elif (fc.evaluate(0) == 1) and (fc.evaluate(1) == 0):
+                        keys[0] = {'co_ui':Vector((0, 1))}
+                        keys[1] = {'co_ui':Vector((1, 0))}
+                    else:
+                        print ("Could not get keys!")
+                        # TODO find out why this happens
+                        # I HAVE NO IDEA
+                        pass
+#                                elif (fc.evaluate(0) == 1) and (fc.evaluate(1) == 0):
+#                                    kf0 = fc.keyframe_points[0]; kf0.co_ui = (0, 1)
+#                                    kf1 = fc.keyframe_points[1]; kf1.co_ui = (1, 0)
+                # now get the fCurve
+                fCurve_node = None
+                for n in fcurves:
+                    fc_ob = n.fake_fcurve_ob; node_fc = fc_ob.animation_data.action.fcurves[0]
+                    node_keys = {}
+                    for i, k in enumerate(node_fc.keyframe_points):
+                        node_keys[i] = {'co_ui':k.co_ui}
+                    # now let's see if they are the same:
+                    if (keys != node_keys):
+                        continue
+                    fCurve_node = n
+                    break
+                else:
+                    fCurve_node = node_tree.nodes.new("UtilityFCurve")
+                    fc_ob = fCurve_node.fake_fcurve_ob
+                    node_fc = fc_ob.animation_data.action.fcurves[0]
+                    fcurves.append(fCurve_node)
+                    while(node_fc.keyframe_points): # clear it, it has a default FC
+                        node_fc.keyframe_points.remove(node_fc.keyframe_points[0], fast=True)
+                    node_fc.update()
+                    node_fc.keyframe_points.add(len(keys))
+                    for k, v in keys.items():
+                        node_fc.keyframe_points[k].co_ui = v['co_ui']
+                        # todo eventually the other dict elements ofc
+                    
+                # NOW the driver itself
+                driver_node = None
+                # checc for it...
+                driver_node = node_tree.nodes.new("UtilityDriver")
+                driver_node.inputs["Driver Type"].default_value = fc.driver.type
+                driver_node.inputs["Expression"].default_value = fc.driver.expression.replace ('var', 'a')
+                # HACK, fix the above with a more robust solution
+                
+                node_tree.links.new(fCurve_node.outputs[0], driver_node.inputs['fCurve'])
+                for i, var_node in zip(range(num_vars), var_nodes):
+                    # TODO TODO
+                    bpy.ops.mantis.driver_node_add_variable({'node':driver_node})
+                    # This causes an error when you run it from the console! DO NOT leave this
+                    node_tree.links.new(var_node.outputs[0], driver_node.inputs[-1])
+                # HACK duplicated code from earlier...
+                parameter = fc.data_path
+                prWhite( "parameter: %s" % parameter)
+                if len(parameter.split("[\"") ) == 3:
+                    property = parameter.split(".")[-1]
+                    if (property == 'mute'): # this is mapped to the 'Enable' socket...
+                        prop_in = out_node.inputs.get('Enable')
+                    else:
+                        prop_in = out_node.inputs.get(get_pretty_name(property))
+                    if not prop_in:
+                        # try one last thing:
+                        property = parameter.split("targets[")[-1]
+                        target_index = int(property[0])
+                        property = "targets[" + property # HACK lol
+                        # get the property by index...
+                        prop_in = out_node.inputs[target_index*2+6+1] # this is the weight, not the target
+                    if prop_in:
+                        prRed ("   found: %s, %s, %s" % (property, out_node.label, out_node.name))
+                        node_tree.links.new(driver_node.outputs["Driver"], prop_in)
+                    else:
+                        prRed ("   couldn't find: %s, %s, %s" % (property, out_node.label, out_node.name))
+                else:
+                    prWhite( "parameter: %s" % parameter)
+                    prRed ("   couldn't find: ", property, out_node.label, out_node.name)
+
+
+def do_generate_armature(context, node_tree):
+    
+        from time import time
+        start = time()
+        node_tree.do_live_update = False
+        
+        armOb = bpy.context.active_object
+        
+        #This will generate it in the current node tree and OVERWRITE!
+        node_tree.nodes.clear()
+        
+        world_in = node_tree.nodes.new("xFormRootNode")
+        armature = node_tree.nodes.new("xFormArmatureNode")
+        world_in.location = (-200, 0)
+        armature.location = ( 0, 0)
+        
+        
+        do_after = []
+        
+        
+        for root in armOb.data.bones:
+            if root.parent is None:
+                iter_start= time()
+                lines = []
+                lines = walk_edit_bone(armOb, root)
+                lines.append([]) # add the root itself HACK ugly
+                milestone=time()
+                prPurple("got the bone paths", time() - milestone); milestone=time()
+                # create links:
+                node_tree.links.new(world_in.outputs["World Out"], armature.inputs['Relationship'])
+                
+                # set up some properties:
+                armature.inputs["Name"].default_value = armOb.name
+                armature.name = armOb.name; armature.label = armOb.name
+                
+                # for getting parent nodes
+                bone_inherit_node = {}
+                
+                # do short lines first bc longer lines rely on their results
+                sort_by_len = lambda elem : len(elem)
+                lines.sort(key=sort_by_len)
+                
+                for bone_path in lines:
+                    prGreen("for bone_path in lines", time() - milestone); milestone=time()
+                    # first go through the bone path and find relevant information
+                    bone = get_bone_from_path(root, bone_path)
+                    bone_node = node_tree.nodes.new("xFormBoneNode")
+                    bone_node.inputs["Name"].default_value = bone.name
+                    bone_node.name, bone_node.label = bone.name, bone.name
+                    matrix = bone.matrix_local.copy()
+                    bone_node.inputs["Matrix"].default_value = [
+                           matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3],
+                           matrix[1][0], matrix[1][1], matrix[1][2], matrix[1][3],
+                           matrix[2][0], matrix[2][1], matrix[2][2], matrix[2][3], # last element is bone length, for mantis
+                           matrix[3][0], matrix[3][1], matrix[3][2], bone.length ] #matrix[3][3], ]
+                    x_distance, y_distance = 0, 0
+                    pb = armOb.pose.bones[bone.name]
+                    possible_parent_nodes = []
+                    
+                    if bone_path: # not a root
+                        x_distance, y_distance = len(bone_path), bone_path[-1]
+                        possible_parent_nodes = bone_inherit_node.get(bone.parent.name)
+                        # Set the parent
+                        parent_node = None
+                        
+                        if not (possible_parent_nodes):
+                            parent_node = create_inheritance_node(pb, bone.parent.name, bone_inherit_node, node_tree)
+                        else:
+                            for ppn in possible_parent_nodes:
+                                # check if it has the right connected, inherit scale, inherit rotation
+                                if ppn.inputs["Connected"].default_value  != pb.bone.use_connect:
+                                    continue
+                                if ppn.inputs["Inherit Scale"].default_value != pb.bone.inherit_scale:
+                                    continue
+                                if ppn.inputs["Inherit Rotation"].default_value != pb.bone.use_inherit_rotation:
+                                    continue
+                                parent_node = ppn; break
+                            else:
+                                parent_node = create_inheritance_node(pb, bone.parent.name, bone_inherit_node, node_tree)
+                        
+                        print("Got parent node", time() - milestone); milestone=time()
+                        if parent_node is None:
+                            raise RuntimeError("No parent node?")
+                    else: # This is a root
+                        prOrange("else this is a root",time() - milestone); milestone=time()
+                        parent_node = node_tree.nodes.new("linkInherit")
+                        # root_child = node_tree.nodes.new("linkInherit")
+                        node_tree.links.new(parent_node.outputs["Inheritance"], bone_node.inputs['Relationship'])
+                        # node_tree.links.new(bone_node.outputs["xForm Out"], root_child.inputs['Parent'])
+                        node_tree.links.new(armature.outputs["xForm Out"], parent_node.inputs['Parent'])
+                
+                        parent_node.inputs["Inherit Rotation"].default_value = True
+                        parent_node.location = (200, 0)
+                        bone_node.location = (400, 0)
+                        # root_child.location = (600, 0)
+                        
+                        # bone_inherit_node[bone_node.name]=[root_child]
+                    
+                    setup_custom_properties(bone_node, pb)
+                    setup_ik_settings(bone_node, pb)
+                    setup_vp_settings(bone_node, pb, do_after, node_tree)
+                    setup_df_settings(bone_node, pb)
+                    
+                    #
+                    for c in pb.constraints:
+                        prWhite("constraint %s for %s" % (c.name, pb.name), time() - milestone); milestone=time()
+                        # make relationship nodes and set up links...
+                        if ( c_node := create_relationship_node_for_constraint(node_tree, c)):
+                            c_node.label = c.name
+                            # this node definitely has a parent inherit node.
+                            c_node.location = parent_node.location; c_node.location.x += 200
+                            
+                            try:
+                                node_tree.links.new(parent_node.outputs["Inheritance"], c_node.inputs['Input Relationship'])
+                            except KeyError: # not a inherit node anymore
+                                node_tree.links.new(parent_node.outputs["Output Relationship"], c_node.inputs['Input Relationship'])
+                            parent_node = c_node
+                            
+                            #Target Tasks:
+                            if (hasattr(c, "target") and not hasattr(c, "subtarget")):
+                                do_after.append( ("Object Target", c_node.name , c.target.name ) )
+                            if (hasattr(c, "subtarget")):
+                                if c.target and c.subtarget: # this node has a target, find the node associated with it... 
+                                    do_after.append( ("Target", c_node.name , c.subtarget ) )
+                                else:
+                                    do_after.append( ("Object Target", c_node.name , c.target.name ) )
+                            if (hasattr(c, "pole_subtarget")):
+                                if c.pole_target and c.pole_subtarget: # this node has a pole target, find the node associated with it... 
+                                    do_after.append( ("Pole Target", c_node.name , c.pole_subtarget ) )
+                            fill_parameters(c_node, c)
+                            if (hasattr(c, "targets")): # Armature Modifier, annoying.
+                                for i in range(len(c.targets)):
+                                    if (c.targets[i].subtarget):
+                                        do_after.append( ("Target."+str(i).zfill(3), c_node.name , c.targets[i].subtarget ) )
+                            # Driver Tasks
+                            if armOb.animation_data:
+                                for fc in armOb.animation_data.drivers:
+                                    pb_string = fc.data_path.split("[\"")[1]; pb_string = pb_string.split("\"]")[0]
+                                    try:
+                                        c_string = fc.data_path.split("[\"")[2]; c_string = c_string.split("\"]")[0]
+                                    except IndexError:
+                                        print ("Find out what causes this:   %s" % c_string)
+                                    if pb.name == pb_string and c.name == c_string:
+                                        do_after.append ( ("driver", bone_node.name, c_node.name) )
+                    try:
+                        node_tree.links.new(parent_node.outputs["Inheritance"], bone_node.inputs['Relationship'])
+                    except KeyError: # may have changed, see above
+                        node_tree.links.new(parent_node.outputs["Output Relationship"], bone_node.inputs['Relationship'])
+                    bone_node.location = (400 + parent_node.location.x, -200*y_distance + parent_node.location.y)
+                    prPurple("iteration: ", time() - iter_start)
+                finished_drivers = set()
+                switches, driver_vars, fcurves, drivers = [],[],[],[]
+        
+        # Now do the tasks.
+        for (task, in_node_name, out_node_name) in do_after:
+            prOrange(task, in_node_name, out_node_name)
+            prPurple(len(node_tree.nodes))
+            if task in ['Object Target']:
+                in_node  = node_tree.nodes[ in_node_name ]
+                out_node= node_tree.nodes.new("InputExistingGeometryObject")
+                out_node.inputs["Name"].default_value=out_node_name
+                node_tree.links.new(out_node.outputs["Object"], in_node.inputs["Target"])
+            if task in ['Target', 'Pole Target']:
+                in_node  = node_tree.nodes[ in_node_name ]
+                out_node = node_tree.nodes[ out_node_name ]
+                #
+                node_tree.links.new(out_node.outputs["xForm Out"], in_node.inputs[task])
+            elif (task[:6] == 'Target'):
+                in_node  = node_tree.nodes[ in_node_name ]
+                out_node = node_tree.nodes[ out_node_name ]
+                #
+                node_tree.links.new(out_node.outputs["xForm Out"], in_node.inputs[task])
+            elif task in ["Custom Object xForm Override"]:
+                shape_xform_n = None
+                for n in node_tree.nodes:
+                    if n.name == out_node_name:
+                        shape_xform_n = n
+                        node_tree.links.new(shape_xform_n.outputs["xForm"], node_tree.nodes[in_node_name].inputs['Custom Object xForm Override'])
+                        break
+                else: # make it a task
+                    prRed("Cannot set custom object transform override for %s to %s" % (in_node_name, out_node_name))
+                
+            elif task in ["driver"]:
+                create_driver(in_node_name, out_node_name, armOb, finished_drivers, switches, driver_vars, fcurves, drivers, node_tree)
+                        
+            # annoyingly, Rigify uses f-modifiers to setup its fcurves
+            # I do not intend to support fcurve modifiers in Mantis at this time
+
+        
+        for node in node_tree.nodes:
+            if (node == world_in):
+                continue
+            node.select = False
+        node_tree.nodes.active = world_in
+        
+        prGreen("Finished generating %d nodes in %f seconds." % (len(node_tree.nodes), time() - start))
+        #bpy.ops.node.cleanup()
+        node_tree.do_live_update = True
+
+
+
+class CreateMantisTree(Operator):
+    """Create Mantis Tree From Selected"""
+    bl_idname = "mantis.create_tree"
+    bl_label = "Create Mantis Tree"
+
+    @classmethod
+    def poll(cls, context):
+        return (mantis_poll_op(context))
+
+    def execute(self, context):
+        space = context.space_data
+        path = space.path
+        node_tree = space.path[len(path)-1].node_tree
+        
+        do_profile=False
+        
+        import cProfile
+        from os import environ
+        print (environ.get("DOPROFILE"))
+        if environ.get("DOPROFILE"):
+            do_profile=True
+        if do_profile:
+            cProfile.runctx("do_generate_armature(context, node_tree)", None, locals())
+        else:
+            do_generate_armature(context, node_tree)
+        return {"FINISHED"}
+        
+        
+        return {"FINISHED"}
+
+

+ 846 - 0
ops_nodegroup.py

@@ -0,0 +1,846 @@
+import bpy
+from bpy.types import Operator
+from mathutils import Vector
+
+def TellClasses():
+    return [
+        MantisGroupNodes,
+        MantisEditGroup,
+        ExecuteNodeTree,
+        # CreateMetaGroup,
+        QueryNodeSockets,
+        CleanUpNodeGraph,
+        MantisMuteNode,
+        TestOperator,
+        # xForm
+        AddCustomProperty,
+        RemoveCustomProperty,
+        # Fcurve
+        EditFCurveNode,
+        FcurveAddKeyframeInput,
+        FcurveRemoveKeyframeInput,
+        # Driver
+        DriverAddDriverVariableInput,
+        DriverRemoveDriverVariableInput,
+        # Armature Link Node
+        LinkArmatureAddTargetInput,
+        LinkArmatureRemoveTargetInput,]
+
+def mantis_tree_poll_op(context):
+    # return True
+    space = context.space_data
+    if hasattr(space, "node_tree"):
+        if (space.node_tree):
+            return (space.tree_type == "MantisTree")
+    return False
+
+
+def get_override(active=None, edit=False, selected=[], type='VIEW_3D'):
+    #no clue what this does... taken from sorcar
+    override = bpy.context.copy()
+    if (type == 'VIEW_3D'):
+        override["active_object"] = active
+        if (edit):
+            override["edit_object"] = active
+        if (active not in selected):
+            selected.append(active)
+        override["selected_object"] = selected
+    flag = False
+    for window in bpy.data.window_managers[0].windows:
+        for area in window.screen.areas:
+            if area.type == type:
+                override["area"] = area
+                override["region"] = [i for i in area.regions if i.type == 'WINDOW'][0]
+                flag = True
+                break
+        if (flag):
+            break
+    return override
+
+def ChooseNodeGroupNode(treetype):
+    #I don't need this anymore... but I'm leaving it here
+    #  because this is a useful thing to separate
+    #  in case I add multiple tree types in the future
+    if (treetype == "MantisTree"):
+        return "MantisNodeGroup"
+    # if (treetype == "LinkTree"):
+    #     return "linkGroupNode"
+
+#########################################################################3
+
+class MantisGroupNodes(Operator):
+    """Create node-group from selected nodes"""
+    bl_idname = "mantis.group_nodes"
+    bl_label = "Group Nodes"
+
+    @classmethod
+    def poll(cls, context):
+        return mantis_tree_poll_op(context)
+
+
+# source is https://github.com/aachman98/Sorcar/blob/master/operators/ScGroupNodes.py
+# checc here: https://github.com/nortikin/sverchok/blob/9002fd4af9ec8603e86f86ed7e567a4ed0d2e07c/core/node_group.py#L568
+
+    def execute(self, context):
+        # Get space, path, current nodetree, selected nodes and a newly created group
+        space = context.space_data
+        path = space.path
+        node_tree = space.path[len(path)-1].node_tree
+        node_group = bpy.data.node_groups.new(ChooseNodeGroupNode(space.tree_type), space.tree_type)
+        selected_nodes = [i for i in node_tree.nodes if i.select]
+        nodes_len = len(selected_nodes)
+
+        # Store all links (internal/external) for the selected nodes to be created as group inputs/outputs
+        links_external_in = []
+        links_external_out = []
+        for n in selected_nodes:
+            for i in n.inputs:
+                if (i.is_linked):
+                    l = i.links[0]
+                    if (not l.from_node in selected_nodes):
+                        if (not l in links_external_in):
+                            links_external_in.append(l)
+            for o in n.outputs:
+                if (o.is_linked):
+                    for l in o.links:
+                        if (not l.to_node in selected_nodes):
+                            if (not l in links_external_out):
+                                links_external_out.append(l)
+
+        # Calculate the required locations for placement of grouped node and input/output nodes
+        loc_x_in = 0
+        loc_x_out = 0
+        loc_avg = Vector((0, 0))
+        for n in selected_nodes:
+            loc_avg += n.location/nodes_len
+            if (n.location[0] < loc_x_in):
+                loc_x_in = n.location[0]
+            if (n.location[0] > loc_x_out):
+                loc_x_out = n.location[0]
+        
+        # Create and relocate group input & output nodes in the newly created group
+        group_input = node_group.nodes.new("NodeGroupInput")
+        group_output = node_group.nodes.new("NodeGroupOutput")
+        group_input.location = Vector((loc_x_in-200, loc_avg[1]))
+        group_output.location = Vector((loc_x_out+200, loc_avg[1]))
+        
+        # Copy the selected nodes from current nodetree
+        if (nodes_len > 0):
+            bpy.ops.node.clipboard_copy(get_override(type='NODE_EDITOR'))
+        
+        # Create a grouped node with correct location and assign newly created group
+        group_node = node_tree.nodes.new(ChooseNodeGroupNode(space.tree_type))
+        node_tree.nodes.active = group_node
+        group_node.location = loc_avg
+        group_node.node_tree = node_group
+        
+        # Add overlay to node editor for the newly created group
+        path.append(node_group, node=group_node)
+        
+        # Paste the copied nodes to newly created group
+        if (nodes_len > 0):
+            bpy.ops.node.clipboard_paste(get_override(type='NODE_EDITOR'))
+
+        # Create group input/output links in the newly created group
+        o = group_input.outputs
+        for link in links_external_in:
+            # node_group.links.new(o.get(link.from_socket.name, o[len(o)-1]), node_group.nodes[link.to_node.name].inputs[link.to_socket.name])
+            node_group.links.new(group_input.outputs[''], node_group.nodes[link.to_node.name].inputs[link.to_socket.name])
+        i = group_output.inputs
+        for link in links_external_out:
+            # node_group.links.new(node_group.nodes[link.from_node.name].outputs[link.from_socket.name], i.get(link.to_socket.name, i[len(i)-1]))
+            node_group.links.new(node_group.nodes[link.from_node.name].outputs[link.from_socket.name], group_output.inputs[''])
+        
+        # Add new links to grouped node from original external links
+        for i in range(0, len(links_external_in)):
+            link = links_external_in[i]
+            node_tree.links.new(link.from_node.outputs[link.from_socket.name], group_node.inputs[i])
+        for i in range(0, len(links_external_out)):
+            link = links_external_out[i]
+            node_tree.links.new(group_node.outputs[i], link.to_node.inputs[link.to_socket.name])
+        
+        # Remove redundant selected nodes
+        for n in selected_nodes:
+            node_tree.nodes.remove(n)
+
+        return {"FINISHED"}
+
+class MantisEditGroup(Operator):
+    """Edit the group referenced by the active node (or exit the current node-group)"""
+    bl_idname = "mantis.edit_group"
+    bl_label = "Edit Group"
+
+    @classmethod
+    def poll(cls, context):
+        return (
+            mantis_tree_poll_op(context)
+        )
+
+    def execute(self, context):
+        space = context.space_data
+        path = space.path
+        node = path[len(path)-1].node_tree.nodes.active
+
+        if hasattr(node, "node_tree"):
+            if (node.node_tree):
+                path.append(node.node_tree, node=node)
+                path[0].node_tree.display_update(context)
+                return {"FINISHED"}
+        elif len(path) > 1:
+            path.pop()
+            path[0].node_tree.display_update(context)
+        return {"CANCELLED"}
+
+class ExecuteNodeTree(Operator):
+    """Execute this node tree"""
+    bl_idname = "mantis.execute_node_tree"
+    bl_label = "Execute Node Tree"
+
+    @classmethod
+    def poll(cls, context):
+        return (mantis_tree_poll_op(context))
+
+    def execute(self, context):
+        from .utilities import parse_node_tree, print_lines
+        from time import time
+        from .utilities import wrapGreen
+        
+        tree=context.space_data.path[0].node_tree
+        
+        import cProfile
+        from os import environ
+        do_profile=False
+        print (environ.get("DOPROFILE"))
+        if environ.get("DOPROFILE"):
+            do_profile=True
+        if do_profile:
+            cProfile.runctx("tree.update_tree(context)", None, locals())
+            cProfile.runctx("tree.execute_tree(context)", None, locals())
+        else:
+            tree.update_tree(context)
+            tree.execute_tree(context)
+        return {"FINISHED"}
+
+# class CreateMetaGroup(Operator):
+    # """Create Meta Rig group node"""
+    # bl_idname = "mantis.create_meta_group"
+    # bl_label = "Create Meta Rig group node"
+
+    # @classmethod
+    # def poll(cls, context):
+        # return (mantis_tree_poll_op(context))
+
+    # def execute(self, context):
+        # space = context.space_data
+        # path = space.path
+        # node_tree = space.path[len(path)-1].node_tree
+        # # selected_nodes = [i for i in node_tree.nodes if i.select]
+        # ob = bpy.context.active_object
+        # matrices_build = []
+        # if (ob):
+            # if (ob.type == 'ARMATURE'):
+                # for pb in ob.pose.bones:
+                    # matrices_build.append((pb.name, pb.matrix, pb.length))
+        # xloc = -400
+        # yloc = 400
+        # loops = 0
+        # node_group = bpy.data.node_groups.new(ob.name, space.tree_type) 
+        # group_node = node_tree.nodes.new("MantisNodeGroup")
+        # group_output = node_group.nodes.new("NodeGroupOutput")
+        # path.append(node_group, node=group_node)
+        # group_node.node_tree = node_group
+        # gTree = group_node.node_tree
+        # for name, m, length in matrices_build:
+            # n = gTree.nodes.new("MetaRigMatrixNode")
+            # n.first_row = m[0]
+            # n.second_row = m[1]
+            # n.third_row = m[2]
+            # n.fourth_row = [m[3][0], m[3][1], m[3][2], length]
+            # print (n.fourth_row[3])
+            # n.name = name
+            # n.label = name
+            # n.location = (xloc + loops*250, yloc)
+            # if (yloc > -800):
+                # yloc-=55
+            # else:
+                # loops+=1
+                # yloc = 400
+            # node_group.links.new(n.outputs["Matrix"], group_output.inputs[''])
+            # node_group.outputs["Matrix"].name = name
+        # return {"FINISHED"}
+
+
+class QueryNodeSockets(Operator):
+    """Utility Operator for querying the data in a socket"""
+    bl_idname = "mantis.query_sockets"
+    bl_label = "Query Node Sockets"
+
+    @classmethod
+    def poll(cls, context):
+        return (mantis_tree_poll_op(context))
+
+    def execute(self, context):
+        node = context.active_node
+        print ("Node type: ", node.bl_idname)
+        
+        # This is useful. Todo: reimplement this eventually.
+        
+        return {"FINISHED"}
+
+
+class CleanUpNodeGraph(bpy.types.Operator):
+    """Clean Up Node Graph"""
+    bl_idname = "mantis.nodes_cleanup"
+    bl_label = "Clean Up Node Graph"
+
+    @classmethod
+    def poll(cls, context):
+        return hasattr(context, 'active_node')
+
+    def execute(self, context):
+        
+        base_tree=context.space_data.path[-1].node_tree
+        
+        from mantis.grandalf.graphs import Vertex, Edge, Graph, graph_core
+        
+        class defaultview(object):
+            w,h = 1,1
+            xz = (0,0)
+        
+        verts = {}
+        for n in base_tree.nodes:
+            has_links=False
+            for inp in n.inputs:
+                if inp.is_linked:
+                    has_links=True
+                    break
+            for out in n.outputs:
+                if out.is_linked:
+                    has_links=True
+                    break
+            if not has_links:
+                continue
+                
+            v = Vertex(n.name)
+            v.view = defaultview()
+            v.view.xy = n.location
+            v.view.h = n.height*3
+            v.view.w = n.width*3
+            verts[n.name] = v
+            
+        edges = []
+        for link in base_tree.links:
+            weight = 1 # maybe this is useful
+            edges.append(Edge(verts[link.from_node.name], verts[link.to_node.name], weight) )
+        graph = Graph(verts.values(), edges)
+        
+
+        
+        from mantis.grandalf.layouts import SugiyamaLayout
+        
+        
+        sug = SugiyamaLayout(graph.C[0]) # no idea what .C[0] is
+        
+        roots=[]
+        for node in base_tree.nodes:
+            
+            has_links=False
+            for inp in node.inputs:
+                if inp.is_linked:
+                    has_links=True
+                    break
+            for out in node.outputs:
+                if out.is_linked:
+                    has_links=True
+                    break
+            if not has_links:
+                continue
+                
+            if len(node.inputs)==0:
+                roots.append(verts[node.name])
+            else:
+                for inp in node.inputs:
+                    if inp.is_linked==True:
+                        break
+                else:
+                    roots.append(verts[node.name])
+        
+        sug.init_all(roots=roots,)
+        sug.draw(8)
+        for v in graph.C[0].sV:
+            for n in base_tree.nodes:
+                if n.name == v.data:
+                    n.location.x = v.view.xy[1]
+                    n.location.y = v.view.xy[0]
+        
+        
+        
+        return {'FINISHED'}
+
+
+
+class MantisMuteNode(Operator):
+    """Mantis Test Operator"""
+    bl_idname = "mantis.mute_node"
+    bl_label = "Mute Node"
+
+    @classmethod
+    def poll(cls, context):
+        return (mantis_tree_poll_op(context))
+
+    def execute(self, context):
+        path = context.space_data.path
+        node = path[len(path)-1].node_tree.nodes.active
+        node.mute = not node.mute
+        # There should only be one of these
+        if (enable := node.inputs.get("Enable")):
+                # annoyingly, 'mute' and 'enable' are opposites
+                enable.default_value = not node.mute
+        if (hide := node.inputs.get("Hide")):
+                hide.default_value = node.mute
+        return {"FINISHED"}
+
+
+class TestOperator(Operator):
+    """Mantis Test Operator"""
+    bl_idname = "mantis.test_operator"
+    bl_label = "Mantis Test Operator"
+
+    @classmethod
+    def poll(cls, context):
+        return (mantis_tree_poll_op(context))
+
+    def execute(self, context):
+        path = context.space_data.path
+        node = path[len(path)-1].node_tree.nodes.active
+        print("Inputs:")
+        for sock in node.inputs:
+            print(sock.identifier)
+        print("Outputs:")
+        for sock in node.outputs:
+            print(sock.identifier)
+        print ("\n")
+        # if (not node):
+        #     return {"FINISHED"}
+        # for out in node.outputs:
+        #     utilities.lines_from_socket(out)
+        
+        # import bpy
+        # c = bpy.context
+        # print (c.space_data.path)
+        return {"FINISHED"}
+
+ePropertyType =(
+        ('BOOL'  , "Boolean", "Boolean", 0),
+        ('INT'   , "Integer", "Integer", 1),
+        ('FLOAT' , "Float"  , "Float"  , 2),
+        ('VECTOR', "Vector" , "Vector" , 3),
+        ('STRING', "String" , "String" , 4),
+        #('ENUM'  , "Enum"   , "Enum"   , 5),
+    )
+    
+
+from .base_definitions import xFormNode
+
+
+class AddCustomProperty(bpy.types.Operator):
+    """Add Custom Property to xForm Node"""
+    bl_idname = "mantis.add_custom_property"
+    bl_label = "Add Custom Property"
+
+
+    prop_type : bpy.props.EnumProperty(
+        items=ePropertyType,
+        name="New Property Type",
+        description="Type of data for new Property",
+        default = 'BOOL',)
+    prop_name  : bpy.props.StringProperty(default='Prop')
+    
+    min:bpy.props.FloatProperty(default = 0)
+    max:bpy.props.FloatProperty(default = 1)
+    soft_min:bpy.props.FloatProperty(default = 0)
+    soft_max:bpy.props.FloatProperty(default = 1)
+    description:bpy.props.StringProperty(default = "")
+    
+    node_invoked : bpy.props.PointerProperty(type=bpy.types.Node, 
+                options ={'HIDDEN'}) # note this seems to affect all
+                                     # subsequent properties
+
+    @classmethod
+    def poll(cls, context):
+        return True #( hasattr(context, 'node') ) 
+
+    def invoke(self, context, event):
+        self.node_invoked = context.node
+        wm = context.window_manager
+        return wm.invoke_props_dialog(self)
+        
+    def execute(self, context):
+        n = self.node_invoked
+        # For whatever reason, context.node doesn't exist anymore
+        #   (probably because I use a window to execute)
+        # so as a sort of dumb workaround I am saving it to a hidden
+        #  property of the operator... it works.
+        socktype = ''
+        if not (self.prop_name):
+            self.report({'ERROR_INVALID_INPUT'}, "Must name the property.")
+            return {'CANCELLED'}
+        if self.prop_type == 'BOOL':
+            socktype = 'ParameterBoolSocket'
+        if self.prop_type == 'INT':
+            socktype = 'ParameterIntSocket'
+        if self.prop_type == 'FLOAT':
+            socktype = 'ParameterFloatSocket'
+        if self.prop_type == 'VECTOR':
+            socktype = 'ParameterVectorSocket'
+        if self.prop_type == 'STRING':
+            socktype = 'ParameterStringSocket'
+        #if self.prop_type == 'ENUM':
+        #    sock_type = 'ParameterStringSocket'
+        if (s := n.inputs.get(self.prop_name)):
+            try:
+                number = int(self.prop_name[-3:])
+                # see if it has a number
+                number+=1
+                self.prop_name = self.prop_name[:-3] + str(number).zfill(3)
+            except ValueError:
+                self.prop_name+='.001'
+                # WRONG
+        new_prop = n.inputs.new( socktype, self.prop_name)
+        if self.prop_type in ['INT','FLOAT']:
+            new_prop.min = self.min
+            new_prop.max = self.max
+            new_prop.soft_min = self.soft_min
+            new_prop.soft_max = self.soft_max
+        new_prop.description = self.description
+        # now do the output
+        n.outputs.new( socktype, self.prop_name)
+        
+        if (False):
+            print (new_prop.is_property_set("default_value"))
+            ui_data = new_prop.id_properties_ui("default_value")
+            ui_data.update(
+                description=new_prop.description,
+                default=0,) # for now
+            #if a number
+            for num_type in ['Float', 'Int', 'Bool']:
+                if num_type in new_prop.bl_idname:
+                    ui_data.update(
+                        min = new_prop.min,
+                        max = new_prop.max,
+                        soft_min = new_prop.soft_min,
+                        soft_max = new_prop.soft_max,)
+        return {'FINISHED'}
+
+
+class RemoveCustomProperty(bpy.types.Operator):
+    """Remove a Custom Property from an xForm Node"""
+    bl_idname = "mantis.remove_custom_property"
+    bl_label = "Remove Custom Property"
+
+    def get_existing_custom_properties(self, context):
+        ret = []; i = -1
+        n = context.active_node
+        for inp in n.inputs:
+            if 'Parameter' in inp.bl_idname:
+                ret.append( (inp.identifier, inp.name, "Parameter to remove", i := i + 1), )
+        if ret:
+            return ret
+        return None
+                
+
+    prop_remove : bpy.props.EnumProperty(
+        items=get_existing_custom_properties,
+        name="Property to remove?",
+        description="Select which property to remove",)
+    node_invoked : bpy.props.PointerProperty(type=bpy.types.Node, 
+                options ={'HIDDEN'}) # note this seems to affect all
+                                     # subsequent properties
+
+    @classmethod
+    def poll(cls, context):
+        return True #(hasattr(context, 'active_node') )
+
+    def invoke(self, context, event):
+        print (context.node)
+        self.node_invoked = context.node
+        t = context.node.id_data
+        # HACK the props dialog makes this necesary
+        #  because context.node only exists during the event that
+        #  was created by clicking on the node.
+        t.nodes.active = context.node # HACK
+        context.node.select = True # HACK
+        # I need this bc of the callback for the enum property.
+        #  for whatever reason I can't use node_invoked there
+        wm = context.window_manager
+        return wm.invoke_props_dialog(self)
+        
+    def execute(self, context):
+        n = self.node_invoked
+        # For whatever reason, context.node doesn't exist anymore
+        #   (probably because I use a window to execute)
+        # so as a sort of dumb workaround I am saving it to a hidden
+        #  property of the operator... it works.
+        for i, inp in enumerate(n.inputs):
+            if inp.identifier == self.prop_remove:
+                break
+        else:
+            self.report({'ERROR'}, "Input not found")
+            raise RuntimeError("This should not happen!")
+        # it's possible that the output property's identifier isn't the
+        #   exact same... but I don' care. Shouldn't ever happen. TODO
+        for j, out in enumerate(n.outputs):
+            if out.identifier == self.prop_remove:
+                break
+        else:
+            self.report({'ERROR'}, "Output not found")
+            raise RuntimeError("This should not happen!")
+        n.inputs.remove ( n.inputs [i] )
+        n.outputs.remove( n.outputs[j] )
+        return {'FINISHED'}
+
+
+# TODO: not a priority
+#  This one will remove the old socket and add a new one
+#  and it'll put it back in place and reconnect the links
+#   It's OK to just ask the user to do this manually for now
+#
+ # class EditCustomProperty(bpy.types.Operator):
+    # """Edit Custom Property in xForm Node"""
+    # bl_idname = "mantis.edit_custom_property"
+    # bl_label = "Edit Custom Property"
+
+
+    # prop_type : bpy.props.EnumProperty(
+        # items=ePropertyType,
+        # name="New Property Type",
+        # description="Type of data for new Property",
+        # default = 'BOOL',)
+    # prop_name  : bpy.props.StringProperty(default='Prop')
+    
+    # min:bpy.props.FloatProperty(default = 0)
+    # max:bpy.props.FloatProperty(default = 1)
+    # soft_min:bpy.props.FloatProperty(default = 0)
+    # soft_max:bpy.props.FloatProperty(default = 1)
+    # description:bpy.props.StringProperty(default = "")
+    
+    # node_invoked : bpy.props.PointerProperty(type=bpy.types.Node, 
+                # options ={'HIDDEN'}) # note this seems to affect all
+                                     # # subsequent properties
+
+    # @classmethod
+    # def poll(cls, context):
+        # return True #( hasattr(context, 'node') ) 
+
+    # def invoke(self, context, event):
+        # print (context.node)
+        # self.node_invoked = context.node
+        # print(dir(self))
+        # wm = context.window_manager
+        # return wm.invoke_props_dialog(self)
+        
+    # def execute(self, context):
+        # n = self.node_invoked
+        # # For whatever reason, context.node doesn't exist anymore
+        # #   (probably because I use a window to execute)
+        # # so as a sort of dumb workaround I am saving it to a hidden
+        # #  property of the operator... it works.
+        # socktype = ''
+        # if not (self.prop_name):
+            # self.report({'ERROR_INVALID_INPUT'}, "Must name the property.")
+            # return {'CANCELLED'}
+        # if self.prop_type == 'BOOL':
+            # socktype = 'ParameterBoolSocket'
+        # if self.prop_type == 'INT':
+            # socktype = 'ParameterIntSocket'
+        # if self.prop_type == 'FLOAT':
+            # socktype = 'ParameterFloatSocket'
+        # if self.prop_type == 'VECTOR':
+            # socktype = 'ParameterVectorSocket'
+        # if self.prop_type == 'STRING':
+            # socktype = 'ParameterStringSocket'
+        # #if self.prop_type == 'ENUM':
+        # #    sock_type = 'ParameterStringSocket'
+        # if (s := n.inputs.get(self.prop_name)):
+            # try:
+                # number = int(self.prop_name[-3:])
+                # # see if it has a number
+                # number+=1
+                # self.prop_name = self.prop_name[:-3] + str(number).zfill(3)
+            # except ValueError:
+                # self.prop_name+='.001'
+        # new_prop = n.inputs.new( socktype, self.prop_name)
+        # if self.prop_type in ['INT','FLOAT']:
+            # new_prop.min = self.min
+            # new_prop.max = self.max
+            # new_prop.soft_min = self.soft_min
+            # new_prop.soft_max = self.soft_max
+        # new_prop.description = self.description
+            
+            
+        # return {'FINISHED'}
+
+class EditFCurveNode(bpy.types.Operator):
+    """Edit the fCurve owned by fCurve node"""
+    bl_idname = "mantis.edit_fcurve_node"
+    bl_label = "Edit fCurve"
+    bl_options = {'INTERNAL'}
+    
+    my_window : bpy.props.StringProperty(default = "-1")
+    node_invoked : bpy.props.PointerProperty(type=bpy.types.Node, 
+                options ={'HIDDEN'}) # note this seems to affect all
+                                     # subsequent properties
+    fake_fcurve_ob: bpy.props.PointerProperty(
+                type=bpy.types.Object, 
+                options ={'HIDDEN'},)
+    prev_active: bpy.props.PointerProperty(
+                type=bpy.types.Object, 
+                options ={'HIDDEN'},)
+    
+
+    @classmethod
+    def poll(cls, context):
+        return True #(hasattr(context, 'active_node') )
+
+    def modal(self, context, event):
+        for w in context.window_manager.windows:
+            if str(w.as_pointer()) == self.my_window:
+                break
+        else:
+            context.scene.collection.objects.unlink( self.fake_fcurve_ob )
+            context.view_layer.objects.active = self.prev_active
+            self.prev_active.select_set(True)
+            # at this point I will push the fcurve to nodes
+            #  or some kind of internal data
+            return {'FINISHED'}
+        # I can't currently think of anything I need to do with w
+        return {'PASS_THROUGH'}
+        
+        
+    def invoke(self, context, event):
+        self.node_invoked = context.node
+        self.fake_fcurve_ob = self.node_invoked.fake_fcurve_ob
+        context.scene.collection.objects.link( self.fake_fcurve_ob )
+        self.prev_active = context.view_layer.objects.active
+        context.view_layer.objects.active = self.fake_fcurve_ob
+        self.fake_fcurve_ob.select_set(True)
+        context.window_manager.modal_handler_add(self)
+        # this is added to the active window.
+        if (self.my_window == "-1"):
+            prev_windows = set()
+            for w in context.window_manager.windows:
+                prev_windows.add(w.as_pointer())
+            bpy.ops.wm.window_new()
+            for w in context.window_manager.windows:
+                w_int = w.as_pointer()
+                if (w_int not in prev_windows):
+                    self.my_window = str(w_int)
+                    break
+            else:
+                print ("cancelled")
+                return {'CANCELLED'}
+            # set up properties for w
+            # w.height = 256 # READ
+            # w.width = 400  # ONLY
+            w.screen.areas[0].type = 'GRAPH_EDITOR'
+            w.screen.areas[0].spaces[0].auto_snap = 'NONE'
+            
+        return {'RUNNING_MODAL'}
+
+
+# SIMPLE node operators...
+# May rewrite these in a more generic way later
+class FcurveAddKeyframeInput(bpy.types.Operator):
+    """Add a keyframe input to the fCurve node"""
+    bl_idname = "mantis.fcurve_node_add_kf"
+    bl_label = "Add Keyframe"
+    bl_options = {'INTERNAL'}
+    
+    @classmethod
+    def poll(cls, context):
+        return (hasattr(context, 'active_node') )
+
+    def execute(self, context):
+        context.node.inputs.new("KeyframeSocket", "Keyframe")
+        return {'FINISHED'}
+
+
+class FcurveRemoveKeyframeInput(bpy.types.Operator):
+    """Remove a keyframe input from the fCurve node"""
+    bl_idname = "mantis.fcurve_node_remove_kf"
+    bl_label = "Remove Keyframe"
+    bl_options = {'INTERNAL'}
+        
+    @classmethod
+    def poll(cls, context):
+        return (hasattr(context, 'active_node') )
+
+    def execute(self, context):
+        n = context.node
+        n.inputs.remove(n.inputs[-1])
+        return {'FINISHED'}
+
+class DriverAddDriverVariableInput(bpy.types.Operator):
+    """Add a Driver Variable input to the Driver node"""
+    bl_idname = "mantis.driver_node_add_variable"
+    bl_label = "Add Driver Variable"
+    bl_options = {'INTERNAL'}
+    
+    @classmethod
+    def poll(cls, context):
+        return (hasattr(context, 'active_node') )
+
+    def execute(self, context):           # unicode for 'a'
+        i = len (context.node.inputs) - 2 + 96
+        context.node.inputs.new("DriverVariableSocket", chr(i))
+        return {'FINISHED'}
+
+
+class DriverRemoveDriverVariableInput(bpy.types.Operator):
+    """Remove a DriverVariable input from the active Driver node"""
+    bl_idname = "mantis.driver_node_remove_variable"
+    bl_label = "Remove Driver Variable"
+    bl_options = {'INTERNAL'}
+        
+    @classmethod
+    def poll(cls, context):
+        return (hasattr(context, 'active_node') )
+
+    def execute(self, context):
+        n = context.node
+        n.inputs.remove(n.inputs[-1])
+        return {'FINISHED'}
+        
+        
+        
+class LinkArmatureAddTargetInput(bpy.types.Operator):
+    """Add a Driver Variable input to the Driver node"""
+    bl_idname = "mantis.link_armature_node_add_target"
+    bl_label = "Add Target"
+    bl_options = {'INTERNAL'}
+    
+    @classmethod
+    def poll(cls, context):
+        return hasattr(context, 'node')
+
+    def execute(self, context):           # unicode for 'a'
+        num_targets = len( list(context.node.inputs)[6:])//2
+        context.node.inputs.new("xFormSocket", "Target."+str(num_targets).zfill(3))
+        context.node.inputs.new("FloatSocket", "Weight."+str(num_targets).zfill(3))
+        return {'FINISHED'}
+
+
+class LinkArmatureRemoveTargetInput(bpy.types.Operator):
+    """Remove a DriverVariable input from the active Driver node"""
+    bl_idname = "mantis.link_armature_node_remove_target"
+    bl_label = "Remove Target"
+    bl_options = {'INTERNAL'}
+        
+    @classmethod
+    def poll(cls, context):
+        return hasattr(context, 'node')
+
+    def execute(self, context):
+        n = context.node
+        n.inputs.remove(n.inputs[-1]); n.inputs.remove(n.inputs[-1])
+        return {'FINISHED'}

+ 77 - 0
primitives_containers.py

@@ -0,0 +1,77 @@
+from mantis.node_container_common import *
+from bpy.types import Node
+from .base_definitions import MantisNode
+
+def TellClasses():
+    return [
+             # Primitives
+             CirclePrimitive,
+            ]
+
+#*#-------------------------------#++#-------------------------------#*#
+# P R I M I T I V E S
+#*#-------------------------------#++#-------------------------------#*#
+
+
+
+class CirclePrimitive:
+    '''A node representing float input'''
+
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+          "Name"               : NodeSocket(is_input = True, name = "Name", node = self),
+          "Radius"             : NodeSocket(is_input = True, name = "Radius", node = self),
+          "Number of Points"   : NodeSocket(is_input = True, name = "Number of Points", node = self),
+        }
+        self.outputs = {
+          "Circle" : NodeSocket(is_input = False, name = "Circle", node=self),
+        }
+        self.parameters = {
+          "Name":None,
+          "Radius":None,
+          "Number of Points":None, 
+          "Circle":None, 
+        }
+        self.node_type = "UTILITY"
+
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+
+    def bGetObject(self):
+        from bpy import data
+        # first try Curve, then try Mesh
+        bObject = data.curves.get(self.evaluate_input("Name"))
+        if not bObject:
+            bObject = data.meshes.get(self.evaluate_input("Name"))
+        return bObject
+        
+    def bExecute(self, bContext = None,):
+        # Get the datablock
+        data = self.bGetObject()
+        import bpy
+        if not data:
+            data = bpy.data.meshes.new( self.evaluate_input("Name") )
+        # make the circle
+        import bmesh; bm = bmesh.new()
+        bmesh.ops.create_circle( # lazy but easy
+            bm,
+            cap_ends=False,
+            radius=max(self.evaluate_input("Radius"), 0.0001),
+            segments=min( max( self.evaluate_input("Number of Points"), 3), 1024),
+            )
+        # this is rotated 90 degrees, we need Y-up instead of Z-up
+        from mathutils import Matrix
+        from math import pi
+        for v in bm.verts:
+            v.co = Matrix.Rotation(pi/2, 4, 'X') @ v.co
+        # done with this, push it to the data and free the bmesh.
+        bm.to_mesh(data); bm.free()
+
+    def __repr__(self):
+        return self.signature.__repr__()
+
+    def fill_parameters(self):
+        fill_parameters(self)

+ 24 - 0
primitives_definitions.py

@@ -0,0 +1,24 @@
+import bpy
+from bpy.types import NodeTree, Node, NodeSocket
+from .base_definitions import MantisNode
+
+def TellClasses():
+    return [
+             GeometryCirclePrimitive,
+           ]
+
+def default_traverse(self,socket):
+    return None
+
+
+class GeometryCirclePrimitive(Node, MantisNode):
+    '''A node representing a circle primitive'''
+    bl_idname = 'GeometryCirclePrimitive'
+    bl_label = "Circle Primitive"
+    bl_icon = 'NODE'
+
+    def init(self, context):
+        self.inputs.new('StringSocket', "Name")
+        self.inputs.new('FloatPositiveSocket', "Radius")
+        self.inputs.new('IntSocket', "Number of Points")
+        self.outputs.new('GeometrySocket', "Circle")

+ 645 - 0
readtree.py

@@ -0,0 +1,645 @@
+from .utilities import prRed, prGreen, prPurple, prWhite, prOrange, \
+                        wrapRed, wrapGreen, wrapPurple, wrapWhite, wrapOrange
+
+# what in the cuss is this horrible abomination??
+def class_for_mantis_prototype_node(prototype_node):
+    """ This is a class which returns a class to instantiate for
+        the given prototype node."""
+    #from .node_container_classes import TellClasses
+    from mantis import xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers
+    classes = {}
+    for module in [xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers]:
+        for cls in module.TellClasses():
+            classes[cls.__name__] = cls
+    # I could probably do a string.replace() here
+    # But I actually think this is a bad idea since I might not
+    #  want to use this name convention in the future
+    #  this is easy enough for now, may refactor.
+    #
+    # kek, turns out it was completely friggin' inconsistent already
+    if prototype_node.bl_idname == 'xFormRootNode':
+        return classes["xFormRoot"]
+    elif prototype_node.bl_idname == 'xFormArmatureNode':
+        return classes["xFormArmature"]
+    elif prototype_node.bl_idname == 'xFormBoneNode':
+        return classes["xFormBone"]
+    elif prototype_node.bl_idname == 'xFormGeometryObject':
+        return classes["xFormGeometryObject"]
+    elif prototype_node.bl_idname == 'linkInherit':
+        return classes["LinkInherit"]
+        # BAD need to fix the above, bl_idname is not consistent
+    # Copy's
+    elif prototype_node.bl_idname == 'LinkCopyLocation':
+        return classes["LinkCopyLocation"] # also bad
+    elif prototype_node.bl_idname == 'LinkCopyRotation':
+        return classes["LinkCopyRotation"]
+    elif prototype_node.bl_idname == 'LinkCopyScale':
+        return classes["LinkCopyScale"]
+    elif prototype_node.bl_idname == 'LinkCopyTransforms':
+        return classes["LinkCopyTransforms"]
+    elif prototype_node.bl_idname == 'LinkTransformation':
+        return classes["LinkTransformation"]
+    # Limits
+    elif prototype_node.bl_idname == 'LinkLimitLocation':
+        return classes["LinkLimitLocation"]
+    elif prototype_node.bl_idname == 'LinkLimitRotation':
+        return classes["LinkLimitRotation"]
+    elif prototype_node.bl_idname == 'LinkLimitScale':
+        return classes["LinkLimitScale"]
+    elif prototype_node.bl_idname == 'LinkLimitDistance':
+        return classes["LinkLimitDistance"]
+    # tracking
+    elif prototype_node.bl_idname == 'LinkStretchTo':
+        return classes["LinkStretchTo"]
+    elif prototype_node.bl_idname == 'LinkDampedTrack':
+        return classes["LinkDampedTrack"]
+    elif prototype_node.bl_idname == 'LinkLockedTrack':
+        return classes["LinkLockedTrack"]
+    elif prototype_node.bl_idname == 'LinkTrackTo':
+        return classes["LinkTrackTo"]
+    # misc
+    elif prototype_node.bl_idname == 'LinkInheritConstraint':
+        return classes["LinkInheritConstraint"]
+    # IK
+    elif prototype_node.bl_idname == 'LinkInverseKinematics':
+        return classes["LinkInverseKinematics"]
+    elif prototype_node.bl_idname == 'LinkSplineIK':
+        return classes["LinkSplineIK"]
+    # utilities
+    elif prototype_node.bl_idname == 'InputFloatNode':
+        return classes["InputFloat"]
+    elif prototype_node.bl_idname == 'InputVectorNode':
+        return classes["InputVector"]
+    elif prototype_node.bl_idname == 'InputBooleanNode':
+        return classes["InputBoolean"]
+    elif prototype_node.bl_idname == 'InputBooleanThreeTupleNode':
+        return classes["InputBooleanThreeTuple"]
+    elif prototype_node.bl_idname == 'InputRotationOrderNode':
+        return classes["InputRotationOrder"]
+    elif prototype_node.bl_idname == 'InputTransformSpaceNode':
+        return classes["InputTransformSpace"]
+    elif prototype_node.bl_idname == 'InputStringNode':
+        return classes["InputString"]
+    elif prototype_node.bl_idname == 'InputQuaternionNode':
+        return classes["InputQuaternion"]
+    elif prototype_node.bl_idname == 'InputQuaternionNodeAA':
+        return classes["InputQuaternionAA"]
+    elif prototype_node.bl_idname == 'InputMatrixNode':
+        return classes["InputMatrix"]
+    elif prototype_node.bl_idname == 'MetaRigMatrixNode':
+        return classes["InputMatrix"]
+    elif prototype_node.bl_idname == 'InputLayerMaskNode':
+        return classes["InputLayerMask"]
+    # geometry
+    elif prototype_node.bl_idname == 'GeometryCirclePrimitive':
+        return classes["CirclePrimitive"]
+    # Deformers:
+    elif prototype_node.bl_idname == 'DeformerArmature':
+        return classes["DeformerArmature"]
+        
+    # every node before this point is not guarenteed to follow the pattern
+    # but every node not checked above does follow the pattern.
+    
+    try:
+        return classes[ prototype_node.bl_idname ]
+    except KeyError:
+        pass
+    
+    if prototype_node.bl_idname in [ 
+                                    "NodeReroute",
+                                    "NodeGroupInput",
+                                    "NodeGroupOutput",
+                                    "MantisNodeGroup",
+                                    "NodeFrame",
+                                   ]:
+           return None
+    
+    prRed(prototype_node.bl_idname)
+    raise RuntimeError("Failed to create node container for: %s" % prototype_node.bl_idname)
+    return None
+
+# This is really, really stupid HACK
+def gen_nc_input_for_data(socket):
+    # Class List #TODO deduplicate
+    from mantis import xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers
+    classes = {}
+    for module in [xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers]:
+        for cls in module.TellClasses():
+            classes[cls.__name__] = cls
+    #
+    socket_class_map = {
+                        "MatrixSocket"                      : classes["InputMatrix"],
+                        "xFormSocket"                       : None,
+                        "xFormMultiSocket"                  : None,
+                        "RelationshipSocket"                : classes["xFormRoot"], # world in
+                        "DeformerSocket"                    : classes["xFormRoot"], # world in
+                        "GeometrySocket"                    : classes["InputExistingGeometryData"],
+                        "EnableSocket"                      : classes["InputBoolean"],
+                        "HideSocket"                        : classes["InputBoolean"],
+                        #
+                        "DriverSocket"                      : None,
+                        "DriverVariableSocket"              : None, 
+                        "FCurveSocket"                      : None, 
+                        "KeyframeSocket"                    : None,
+                        "LayerMaskInputSocket"              : classes["InputLayerMask"],
+                        "LayerMaskSocket"                   : classes["InputLayerMask"],
+                        #
+                        "xFormParameterSocket"              : None,
+                        "ParameterBoolSocket"               : classes["InputBoolean"],
+                        "ParameterIntSocket"                : classes["InputFloat"],  #TODO: make an Int node for this
+                        "ParameterFloatSocket"              : classes["InputFloat"],
+                        "ParameterVectorSocket"             : classes["InputVector"],
+                        "ParameterStringSocket"             : classes["InputString"],
+                        #
+                        "TransformSpaceSocket"              : classes["InputTransformSpace"],
+                        "BooleanSocket"                     : classes["InputBoolean"],
+                        "BooleanThreeTupleSocket"           : classes["InputBooleanThreeTuple"],
+                        "RotationOrderSocket"               : classes["InputRotationOrder"],
+                        "QuaternionSocket"                  : classes["InputQuaternion"],
+                        "QuaternionSocketAA"                : classes["InputQuaternionAA"],
+                        "IntSocket"                         : classes["InputFloat"],
+                        "StringSocket"                      : classes["InputString"],
+                        #
+                        "BoolUpdateParentNode"              : classes["InputBoolean"],
+                        "IKChainLengthSocket"               : classes["InputFloat"],
+                        "EnumInheritScale"                  : classes["InputString"],
+                        "EnumRotationMix"                   : classes["InputString"],
+                        "EnumRotationMixCopyTransforms"     : classes["InputString"],
+                        "EnumMaintainVolumeStretchTo"       : classes["InputString"],
+                        "EnumRotationStretchTo"             : classes["InputString"],
+                        "EnumTrackAxis"                     : classes["InputString"],
+                        "EnumUpAxis"                        : classes["InputString"],
+                        "EnumLockAxis"                      : classes["InputString"],
+                        "EnumLimitMode"                     : classes["InputString"],
+                        "EnumYScaleMode"                    : classes["InputString"],
+                        "EnumXZScaleMode"                   : classes["InputString"],
+                        # Deformers
+                        "EnumSkinning"                      : classes["InputString"],
+                        #
+                        "FloatSocket"                       : classes["InputFloat"],
+                        "FloatFactorSocket"                 : classes["InputFloat"],
+                        "FloatPositiveSocket"               : classes["InputFloat"],
+                        "FloatAngleSocket"                  : classes["InputFloat"],
+                        "VectorSocket"                      : classes["InputVector"],
+                        "VectorEulerSocket"                 : classes["InputVector"],
+                        "VectorTranslationSocket"           : classes["InputVector"],
+                        "VectorScaleSocket"                 : classes["InputVector"],
+                        # Drivers             
+                        "EnumDriverVariableType"            : classes["InputString"],
+                        "EnumDriverVariableEvaluationSpace" : classes["InputString"],
+                        "EnumDriverRotationMode"            : classes["InputString"],
+                        "EnumDriverType"                    : classes["InputString"],
+                       }
+    return socket_class_map.get(socket.bl_idname, None)
+
+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):
+        self.from_socket = from_socket
+        self.to_socket = to_socket
+        self.nc_from = nc_from
+        self.nc_to = nc_to
+        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)
+
+# A fuction for getting to the end of a Reroute.
+def socket_seek(start_link, links):
+    link = start_link
+    while(link.from_socket):
+        for newlink in links:
+            if link.from_socket.node.inputs:
+                if newlink.to_socket == link.from_socket.node.inputs[0]:
+                    link=newlink; break
+        else:
+            break
+    return link.from_socket
+    
+def clear_reroutes(links):
+    kept_links, rerouted_starts = [], []
+    rerouted = []
+    all_links = links.copy()
+    while(all_links):
+        link = all_links.pop()
+        to_cls = link.to_socket.node.bl_idname
+        from_cls = link.from_socket.node.bl_idname
+        reroute_classes = ["NodeReroute"]
+        if (to_cls in reroute_classes and
+            from_cls in reroute_classes):
+                rerouted.append(link)
+        elif (to_cls in reroute_classes and not
+            from_cls in reroute_classes):
+                rerouted.append(link)
+        elif (from_cls in reroute_classes and not
+            to_cls in reroute_classes):
+                rerouted_starts.append(link)
+        else:
+            kept_links.append(link)
+    for start in rerouted_starts:
+        from_socket = socket_seek(start, rerouted)
+        new_link = DummyLink(from_socket=from_socket, to_socket=start.to_socket, nc_from=None, nc_to=None)
+        kept_links.append(new_link)
+    return kept_links
+
+
+def reroute_common(nc, nc_to, all_nc):
+    # we need to do this: go  to the to-node
+    # then reroute the link in the to_node all the way to the beginning
+    # so that the number of links in "real" nodes is unchanged
+    # then the links in the dummy nodes need to be deleted
+    watch=False
+    # if nc.signature[-1] == 'NodeGroupOutput': watch=True
+    for inp_name, inp in nc.inputs.items():
+        # assume each input socket only has one input for now
+        if inp.is_connected:
+            while (inp.links):
+                in_link = inp.links.pop()
+                from_nc = in_link.from_node
+                from_socket = in_link.from_socket
+                links = []
+                from_links = from_nc.outputs[from_socket].links.copy()
+                while(from_links):
+                    from_link = from_links.pop()
+                    if from_link == in_link:
+                        continue # DELETE the dummy node link
+                    links.append(from_link)
+                from_nc.outputs[from_socket].links = links
+                down = nc_to.outputs[inp_name]
+                for downlink in down.links:
+                    downlink.from_node = from_nc
+                    downlink.from_socket = from_socket
+                    from_nc.outputs[from_socket].links.append(downlink)
+                    if hasattr(downlink.to_node, "reroute_links"):
+                        # Recurse!
+                        downlink.to_node.reroute_links(downlink.to_node, all_nc)
+        
+
+
+def reroute_links_grp(nc, all_nc):
+    nc_to = all_nc.get( ( *nc.signature, "NodeGroupInput") )
+    reroute_common(nc, nc_to, all_nc)
+
+def reroute_links_grpout(nc, all_nc):
+    nc_to = all_nc.get( ( *nc.signature[:-1],) )
+    reroute_common(nc, nc_to, all_nc)
+
+
+def data_from_tree(base_tree, tree_path = [None], dummy_nodes = {}, all_nc = {}):
+    from mantis.node_container_common import NodeSocket
+    from .internal_containers import DummyNode
+    nc_dict = {}
+    tree_path_names = [tree.name for tree in tree_path if hasattr(tree, "name")]
+    all_child_ng = []
+    tree = base_tree
+    if tree_path[-1]:
+        tree = tree_path[-1].node_tree
+    
+    # Start by looking through the nodes and making nc's where possible
+    #  store the groups, we'll process them soon.
+    for np in tree.nodes:
+        if (nc_cls := class_for_mantis_prototype_node(np)):
+            nc = nc_cls( sig := (None, *tree_path_names, np.name) , base_tree)
+            nc_dict[sig] = nc; all_nc[sig] = nc
+        elif np.bl_idname in ["NodeGroupInput", "NodeGroupOutput"]: # make a Dummy Node
+            # we only want ONE dummy in/out per tree_path, so use the bl_idname
+            sig = (None, *tree_path_names, np.bl_idname)
+            if nc_dict.get(sig):
+                continue
+            nc = DummyNode( signature=sig , base_tree=base_tree, prototype=np )
+            nc_dict[sig] = nc; all_nc[sig] = nc#; dummy_nodes[sig] = nc
+            # dummy_nodes[sig]=nc
+            if np.bl_idname in ["NodeGroupOutput"]:
+                nc.reroute_links = reroute_links_grpout
+                dummy_nodes[sig]=nc
+        elif np.bl_idname in  ["MantisNodeGroup"]:
+            # if we do this here, no duplicate links.
+            nc = DummyNode( signature= (sig := (None, *tree_path_names, np.name) ), base_tree=base_tree, prototype=np )
+            nc_dict[sig] = nc; all_nc[sig] = nc; dummy_nodes[sig] = nc
+            nc.reroute_links = reroute_links_grp
+            all_child_ng.append(np)
+        # else:
+            # prRed(np.bl_idname)
+    
+
+    # Then deal with the links in the current tree and the held_nodes.
+    kept_links = clear_reroutes(list(tree.links))
+    
+    for link in kept_links:
+        from_name = link.from_socket.node.name
+        to_name = link.to_socket.node.name
+        if link.from_socket.node.bl_idname in ["NodeGroupInput", "NodeGroupOutput"]:
+            from_name = link.from_socket.node.bl_idname
+        if link.to_socket.node.bl_idname in ["NodeGroupInput", "NodeGroupOutput"]:
+            to_name = link.to_socket.node.bl_idname
+        if link.to_socket.node.bl_idname in ["MantisNodeGroup"]:
+            continue
+        
+        nc_from = nc_dict.get( tuple([None] + tree_path_names + [from_name]) )
+        nc_to = nc_dict.get( tuple([None] + tree_path_names + [to_name]) )
+        
+        if (nc_from and nc_to):
+            from_s, to_s = link.from_socket.name, link.to_socket.name
+            if nc_to.node_type == "DUMMY": to_s = link.to_socket.identifier
+            if nc_from.node_type == "DUMMY": from_s = link.from_socket.identifier
+            connection = nc_from.outputs[from_s].connect(node=nc_to, socket=to_s)
+        else:
+            raise RuntimeError(wrapRed("Link not connected: %s -> %s in tree %s" % (from_name, to_name, tree_path_names[-1])))
+    nc_from = None; nc_to = None #clear them, since we use the same variable names again
+    
+    
+    # Now, descend into the Node Group
+    for ng in  all_child_ng:
+        nc_to = nc_dict[(None, *tree_path_names, ng.name)]
+        for inp in ng.inputs:
+            # nc_to = nc_dict.get((None, *tree_path_names, ng.name))
+            to_s = inp.identifier
+            if not inp.is_linked:
+                nc_cls = gen_nc_input_for_data(inp)
+                # at this point we also need to get the "Dummy Node" for
+                #  this node group.
+                if (nc_cls):
+                    sig = ("MANTIS_AUTOGENERATED", *tree_path_names, ng.name, inp.name, inp.identifier)
+                    nc_from = nc_cls(sig, base_tree)
+                    # HACK HACK HACK
+                    nc_from.inputs = {}
+                    nc_from.outputs = {inp.name:NodeSocket(name = inp.name, node=nc_from)}
+                    from .node_container_common import get_socket_value
+                    nc_from.parameters = {inp.name:get_socket_value(inp)}
+                    # HACK HACK HACK
+                    nc_dict[sig] = nc_from; all_nc[sig] = nc_from
+                    from_s = inp.name
+                else:
+                    prRed("No available auto-generated class for input %s:%s:%s" % (tree_path, ng.name, inp.name))
+            else: # We need to handle the incoming connections
+                for link in inp.links: #Usually there will only be 1
+                    from_socket = link.from_socket
+                    if (link.from_socket.node.bl_idname == "NodeReroute"):
+                        from_socket = socket_seek(link, list(tree.links))
+                    sig =  tuple( [None] + tree_path_names +[from_socket.node.name])
+                    
+                    from_s = from_socket.name
+                    if (link.from_socket.node.bl_idname in ["NodeGroupInput"]):
+                        sig =  tuple( [None] + tree_path_names +[from_socket.node.bl_idname])
+                        from_s = from_socket.identifier
+                    nc_from = nc_dict.get(sig)
+            nc_from.outputs[from_s].connect(node=nc_to, socket=to_s)
+            nc_from = None
+        nc_to = None
+        # Recurse!
+        data_from_tree(base_tree, tree_path+[ng], dummy_nodes, all_nc)
+    return dummy_nodes, all_nc
+
+
+def establish_node_connections(nc):
+    # This is ugly bc it adds parameters to an object
+    #  but it's kinda necesary to do it after the fact; and it
+    #  wouldn't be ugly if I just initialized the parameter elsewhere
+    connections, hierarchy_connections = [], []
+    for socket in nc.outputs.values():
+        for link in socket.links:
+            connections.append(link.to_node)
+            # this may catch custom properties... too bad.
+            if link.from_socket in from_name_filter:
+                continue
+            if link.to_socket in to_name_filter:
+                continue
+            hierarchy_connections.append(link.to_node)
+    nc.connected_to = connections
+    nc.hierarchy_connections = hierarchy_connections
+    if nc.node_type == 'DUMMY':
+        nc.hierarchy_connections = []
+
+
+def insert_lazy_parents(nc):
+    from .link_containers import LinkInherit
+    from .node_container_common import NodeLink
+    inherit_nc = None
+    if nc.inputs["Relationship"].is_connected:
+        link = nc.inputs["Relationship"].links[0]
+        from_nc = link.from_node
+        if from_nc.__class__.__name__ == "xFormRoot":
+            return
+        if from_nc.node_type in ["XFORM"] and link.from_socket in ["xForm Out"]:
+            inherit_nc = LinkInherit(("MANTIS_AUTOGENERATED", *nc.signature[1:], "LAZY_INHERIT"), nc.base_tree)
+            for from_link in from_nc.outputs["xForm Out"].links:
+                if from_link.to_node == nc and from_link.to_socket == "Relationship":
+                    break # this is it
+            from_link.to_node = inherit_nc; from_link.to_socket="Parent"
+            
+            links=[]
+            while (nc.inputs["Relationship"].links):
+                to_link = nc.inputs["Relationship"].links.pop()
+                if to_link.from_node == from_nc and to_link.from_socket == "xForm Out":
+                    continue # don't keep this one
+                links.append(to_link)
+            
+            nc.inputs["Relationship"].links=links
+            link=NodeLink(from_node=inherit_nc, from_socket="Inheritance", to_node=nc, to_socket="Relationship")
+            inherit_nc.inputs["Parent"].links.append(from_link)
+            
+            inherit_nc.parameters = {
+                                     "Parent":None,
+                                     "Inherit Rotation":True,
+                                     "Inherit Scale":'FULL',
+                                     "Connected":False,
+                                    }
+            # because the from node may have already been done.
+            establish_node_connections(from_nc)
+            establish_node_connections(inherit_nc)
+            # and the inherit node never was
+    return inherit_nc
+
+
+def parse_tree(base_tree, do_reroute=True):
+    dummy_nodes, all_nc =  data_from_tree(base_tree, tree_path = [None], dummy_nodes = {}, all_nc = {})
+    if do_reroute:
+        for sig, dummy in dummy_nodes.items():
+            if (hasattr(dummy, "reroute_links")):
+                dummy.reroute_links(dummy, all_nc)
+        
+    
+    all_nc = list(all_nc.values()).copy()
+    kept_nc = {}
+    while (all_nc):
+        nc = all_nc.pop()
+        nc.fill_parameters()
+        if (nc.node_type in ['XFORM']) and ("Relationship" in nc.inputs.keys()):
+            new_nc = insert_lazy_parents(nc)
+            if new_nc:
+                kept_nc[new_nc.signature]=new_nc
+        establish_node_connections(nc)
+        if nc.connected_to == 0:
+            continue
+        if nc.node_type == 'DUMMY' and do_reroute:
+            continue
+        kept_nc[nc.signature]=nc
+    # return {}
+    return kept_nc
+
+
+from_name_filter = ["Driver", ]
+
+to_name_filter = [
+                   "Custom Object xForm Override",
+                   "Custom Object",
+                   "Deform Bones"
+                 ]
+
+
+
+def sort_tree_into_layers(nodes, context):
+    from time import time
+    from mantis.node_container_common import (get_depth_lines,
+      node_depth)
+    # All this function needs to do is sort out the hierarchy and
+    #  get things working in order of their dependencies.
+    
+    roots, drivers = [], []
+    start = time()
+    
+    for n in nodes.values():
+        if n.node_type == 'DRIVER': drivers.append(n)
+        # ugly but necesary to ensure that drivers are always connected.
+        if not (hasattr(n, 'inputs')) or ( len(n.inputs) == 0):
+            roots.append(n)
+        elif (hasattr(n, 'inputs')):
+            none_connected = True
+            for inp in n.inputs.values():
+                if inp.is_linked: none_connected = False
+            if none_connected: roots.append(n)
+    
+    layers, nodes_heights = {}, {}
+    for root in roots:
+            nodes_heights[root.signature] = 0
+        
+    #Possible improvement: unify roots if they represent the same data
+    all_sorted_nodes = []
+    for root in roots:
+        depth_lines = get_depth_lines(root)[0]
+        
+        for n in nodes.values():
+            if n.signature not in (depth_lines.keys()):
+                continue #belongs to a different root
+            d = nodes_heights.get(n.signature, 0)
+            if (new_d := node_depth(depth_lines[n.signature])) > d:
+                d = new_d
+            nodes_heights[n.signature] = d
+                
+    for k, v in nodes_heights.items():
+        if (layer := layers.get(v, None)):
+            layer.append(nodes[k]) # add it to the existing layer
+        else: layers[v] = [nodes[k]] # or make a new layer with the node
+        all_sorted_nodes.append(nodes[k]) # add it to the sorted list
+    
+    for n in nodes.values():
+        if n not in all_sorted_nodes:
+            for drv in drivers:
+                if n in drv.connected_to:
+                    depth = nodes_heights[drv.signature] + 1
+                    nodes_heights[n.signature] = depth
+                    # don't try to push downstream deps up bc this
+                    #  is a driver and it will be done in the
+                    #  finalize pass anyway
+                    if (layer := layers.get(depth, None)):
+                        layer.append(n)
+                    else: layers[v] = [n]
+                else:
+                    prRed(n)
+                    raise RuntimeError(wrapRed("Failed to depth-sort nodes (because of a driver-combine node?)"))
+    #
+    prGreen("Sorting depth for %d nodes finished in %s seconds" %
+               (len(nodes), time() - start))
+    
+    keys = list(layers.keys())
+    keys.sort()
+    
+    if (False): # True to print the layers
+        for i in keys:
+            # print_layer = [l_item for l_item in layers[i] if l_item.node_type in ["XFORM",]]# "LINK", "DRIVER"]]
+            print_layer = [l_item for l_item in layers[i]]
+            print(wrapGreen("%d: " % i), wrapWhite("%s" % print_layer))
+    return layers
+
+
+def execute_tree(nodes, base_tree, context):
+    import bpy
+    from time import time
+    from mantis.node_container_common import GraphError
+    start_time  = time()
+    original_active = context.view_layer.objects.active
+    
+    layers = sort_tree_into_layers(nodes, context)
+    start_execution_time = time()
+    
+    #             Execute the first pass (xForm, Utility)              #
+    for i in range(len(layers)):
+        for node in layers[i]:
+            if (node.node_type in ['XFORM', 'UTILITY']):
+                try:
+                    node.bExecute(context)
+                except Exception as e:
+                    prRed("Execution failed at %s" % node); raise e
+    #                     Switch to Pose Mode                          #
+    active = None
+    switch_me = []
+    for n in nodes.values():
+        # if it is a armature, switch modes
+        # total hack                   #kinda dumb
+        if ((hasattr(n, "bGetObject")) and (n.__class__.__name__ == "xFormArmature" )):
+            try:
+                ob = n.bGetObject()
+            except KeyError: # for bones
+                ob = None
+            # TODO this will be a problem if and when I add mesh/curve stuff
+            if (hasattr(ob, 'mode') and ob.mode == 'EDIT'):
+                switch_me.append(ob)
+                active = ob
+                context.view_layer.objects.active = ob# need to have an active ob, not None, to switch modes.
+            # we override selected_objects to prevent anyone else from mode-switching
+    # TODO it's possible but unlikely that the user will try to run a 
+    #    graph with no armature nodes in it.
+    if (active):
+        bpy.ops.object.mode_set({'active_object':active, 'selected_objects':switch_me}, mode='POSE')
+    
+    #               Execute second pass (Link, Driver)                 #
+    for i in range(len(layers)):
+        for n in layers[i]:
+            # Now do the Link & Driver nodes during the second pass.
+            if (n.node_type in ['LINK', 'DRIVER']):
+                try:
+                    n.bExecute(context)   
+                except GraphError:
+                    pass                     
+                except Exception as e:
+                    print (n); raise e
+                    
+    #                          Finalize                                #
+    for i in range(len(layers)):
+        for node in layers[i]:
+            if (hasattr(node, "bFinalize")):
+                node.bFinalize(context)
+    
+    
+    for n in nodes.values():
+        # if it is a armature, switch modes
+        # total hack                   #kinda dumb
+        if ((hasattr(n, "bGetObject")) and (n.__class__.__name__ == "xFormArmature" )):
+            try:
+                ob = n.bGetObject()
+            except KeyError: # for bones
+                ob = None
+            # TODO this will be a problem if and when I add mesh/curve stuff
+            if (hasattr(ob, 'mode') and ob.mode == 'POSE'):
+                switch_me.append(ob)
+                active = ob
+    if (active):
+        bpy.ops.object.mode_set({'active_object':active, 'selected_objects':switch_me}, mode='OBJECT')
+    
+    prGreen("Executed Tree in %s seconds" % (time() - start_execution_time))
+    prGreen("Finished executing tree in %f seconds" % (time() - start_time))
+    if (original_active):
+        context.view_layer.objects.active = original_active
+        original_active.select_set(True)

+ 641 - 0
readtree_compare.py

@@ -0,0 +1,641 @@
+from .utilities import prRed, prGreen, prPurple, prWhite, prOrange, \
+                        wrapRed, wrapGreen, wrapPurple, wrapWhite, wrapOrange
+
+# what in the cuss is this horrible abomination??
+def class_for_mantis_prototype_node(prototype_node):
+    """ This is a class which returns a class to instantiate for
+        the given prototype node."""
+    #from .node_container_classes import TellClasses
+    from mantis import xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers
+    classes = {}
+    for module in [xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers]:
+        for cls in module.TellClasses():
+            classes[cls.__name__] = cls
+    # I could probably do a string.replace() here
+    # But I actually think this is a bad idea since I might not
+    #  want to use this name convention in the future
+    #  this is easy enough for now, may refactor.
+    #
+    # kek, turns out it was completely friggin' inconsistent already
+    if prototype_node.bl_idname == 'xFormRootNode':
+        return classes["xFormRoot"]
+    elif prototype_node.bl_idname == 'xFormArmatureNode':
+        return classes["xFormArmature"]
+    elif prototype_node.bl_idname == 'xFormBoneNode':
+        return classes["xFormBone"]
+    elif prototype_node.bl_idname == 'xFormGeometryObject':
+        return classes["xFormGeometryObject"]
+    elif prototype_node.bl_idname == 'linkInherit':
+        return classes["LinkInherit"]
+        # BAD need to fix the above, bl_idname is not consistent
+    # Copy's
+    elif prototype_node.bl_idname == 'LinkCopyLocation':
+        return classes["LinkCopyLocation"] # also bad
+    elif prototype_node.bl_idname == 'LinkCopyRotation':
+        return classes["LinkCopyRotation"]
+    elif prototype_node.bl_idname == 'LinkCopyScale':
+        return classes["LinkCopyScale"]
+    elif prototype_node.bl_idname == 'LinkCopyTransforms':
+        return classes["LinkCopyTransforms"]
+    # Limits
+    elif prototype_node.bl_idname == 'LinkLimitLocation':
+        return classes["LinkLimitLocation"]
+    elif prototype_node.bl_idname == 'LinkLimitRotation':
+        return classes["LinkLimitRotation"]
+    elif prototype_node.bl_idname == 'LinkLimitScale':
+        return classes["LinkLimitScale"]
+    elif prototype_node.bl_idname == 'LinkLimitDistance':
+        return classes["LinkLimitDistance"]
+    # tracking
+    elif prototype_node.bl_idname == 'LinkStretchTo':
+        return classes["LinkStretchTo"]
+    elif prototype_node.bl_idname == 'LinkDampedTrack':
+        return classes["LinkDampedTrack"]
+    elif prototype_node.bl_idname == 'LinkLockedTrack':
+        return classes["LinkLockedTrack"]
+    elif prototype_node.bl_idname == 'LinkTrackTo':
+        return classes["LinkTrackTo"]
+    # misc
+    elif prototype_node.bl_idname == 'LinkInheritConstraint':
+        return classes["LinkInheritConstraint"]
+    # IK
+    elif prototype_node.bl_idname == 'LinkInverseKinematics':
+        return classes["LinkInverseKinematics"]
+    elif prototype_node.bl_idname == 'LinkSplineIK':
+        return classes["LinkSplineIK"]
+    # utilities
+    elif prototype_node.bl_idname == 'InputFloatNode':
+        return classes["InputFloat"]
+    elif prototype_node.bl_idname == 'InputVectorNode':
+        return classes["InputVector"]
+    elif prototype_node.bl_idname == 'InputBooleanNode':
+        return classes["InputBoolean"]
+    elif prototype_node.bl_idname == 'InputBooleanThreeTupleNode':
+        return classes["InputBooleanThreeTuple"]
+    elif prototype_node.bl_idname == 'InputRotationOrderNode':
+        return classes["InputRotationOrder"]
+    elif prototype_node.bl_idname == 'InputTransformSpaceNode':
+        return classes["InputTransformSpace"]
+    elif prototype_node.bl_idname == 'InputStringNode':
+        return classes["InputString"]
+    elif prototype_node.bl_idname == 'InputQuaternionNode':
+        return classes["InputQuaternion"]
+    elif prototype_node.bl_idname == 'InputQuaternionNodeAA':
+        return classes["InputQuaternionAA"]
+    elif prototype_node.bl_idname == 'InputMatrixNode':
+        return classes["InputMatrix"]
+    elif prototype_node.bl_idname == 'MetaRigMatrixNode':
+        return classes["InputMatrix"]
+    elif prototype_node.bl_idname == 'InputLayerMaskNode':
+        return classes["InputLayerMask"]
+    # geometry
+    elif prototype_node.bl_idname == 'GeometryCirclePrimitive':
+        return classes["CirclePrimitive"]
+    # Deformers:
+    elif prototype_node.bl_idname == 'DeformerArmature':
+        return classes["DeformerArmature"]
+        
+    # every node before this point is not guarenteed to follow the pattern
+    # but every node not checked above does follow the pattern.
+    
+    try:
+        return classes[ prototype_node.bl_idname ]
+    except KeyError:
+        pass
+    
+    if prototype_node.bl_idname in [ 
+                                    "NodeReroute",
+                                    "NodeGroupInput",
+                                    "NodeGroupOutput",
+                                    "MantisNodeGroup",
+                                    "NodeFrame",
+                                   ]:
+           return None
+    
+    prRed(prototype_node.bl_idname)
+    raise RuntimeError("Failed to create node container for: %s" % prototype_node.bl_idname)
+    return None
+
+# This is really, really stupid HACK
+def gen_nc_input_for_data(socket):
+    # Class List #TODO deduplicate
+    from mantis import xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers
+    classes = {}
+    for module in [xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers]:
+        for cls in module.TellClasses():
+            classes[cls.__name__] = cls
+    #
+    socket_class_map = {
+                        "MatrixSocket"                      : classes["InputMatrix"],
+                        "xFormSocket"                       : None,
+                        "xFormMultiSocket"                  : None,
+                        "RelationshipSocket"                : classes["xFormRoot"], # world in
+                        "DeformerSocket"                    : classes["xFormRoot"], # world in
+                        "GeometrySocket"                    : classes["InputExistingGeometryData"],
+                        "EnableSocket"                      : classes["InputBoolean"],
+                        "HideSocket"                        : classes["InputBoolean"],
+                        #
+                        "DriverSocket"                      : None,
+                        "DriverVariableSocket"              : None, 
+                        "FCurveSocket"                      : None, 
+                        "KeyframeSocket"                    : None,
+                        "LayerMaskInputSocket"              : classes["InputLayerMask"],
+                        "LayerMaskSocket"                   : classes["InputLayerMask"],
+                        #
+                        "xFormParameterSocket"              : None,
+                        "ParameterBoolSocket"               : classes["InputBoolean"],
+                        "ParameterIntSocket"                : classes["InputFloat"],  #TODO: make an Int node for this
+                        "ParameterFloatSocket"              : classes["InputFloat"],
+                        "ParameterVectorSocket"             : classes["InputVector"],
+                        "ParameterStringSocket"             : classes["InputString"],
+                        #
+                        "TransformSpaceSocket"              : classes["InputTransformSpace"],
+                        "BooleanSocket"                     : classes["InputBoolean"],
+                        "BooleanThreeTupleSocket"           : classes["InputBooleanThreeTuple"],
+                        "RotationOrderSocket"               : classes["InputRotationOrder"],
+                        "QuaternionSocket"                  : classes["InputQuaternion"],
+                        "QuaternionSocketAA"                : classes["InputQuaternionAA"],
+                        "IntSocket"                         : classes["InputFloat"],
+                        "StringSocket"                      : classes["InputString"],
+                        #
+                        "BoolUpdateParentNode"              : classes["InputBoolean"],
+                        "IKChainLengthSocket"               : classes["InputFloat"],
+                        "EnumInheritScale"                  : classes["InputString"],
+                        "EnumRotationMix"                   : classes["InputString"],
+                        "EnumRotationMixCopyTransforms"     : classes["InputString"],
+                        "EnumMaintainVolumeStretchTo"       : classes["InputString"],
+                        "EnumRotationStretchTo"             : classes["InputString"],
+                        "EnumTrackAxis"                     : classes["InputString"],
+                        "EnumUpAxis"                        : classes["InputString"],
+                        "EnumLockAxis"                      : classes["InputString"],
+                        "EnumLimitMode"                     : classes["InputString"],
+                        "EnumYScaleMode"                    : classes["InputString"],
+                        "EnumXZScaleMode"                   : classes["InputString"],
+                        # Deformers
+                        "EnumSkinning"                      : classes["InputString"],
+                        #
+                        "FloatSocket"                       : classes["InputFloat"],
+                        "FloatFactorSocket"                 : classes["InputFloat"],
+                        "FloatPositiveSocket"               : classes["InputFloat"],
+                        "FloatAngleSocket"                  : classes["InputFloat"],
+                        "VectorSocket"                      : classes["InputVector"],
+                        "VectorEulerSocket"                 : classes["InputVector"],
+                        "VectorTranslationSocket"           : classes["InputVector"],
+                        "VectorScaleSocket"                 : classes["InputVector"],
+                        # Drivers             
+                        "EnumDriverVariableType"            : classes["InputString"],
+                        "EnumDriverVariableEvaluationSpace" : classes["InputString"],
+                        "EnumDriverRotationMode"            : classes["InputString"],
+                        "EnumDriverType"                    : classes["InputString"],
+                       }
+    return socket_class_map.get(socket.bl_idname, None)
+
+
+
+
+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):
+        self.from_socket = from_socket
+        self.to_socket = to_socket
+        self.nc_from = nc_from
+        self.nc_to = nc_to
+        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)
+
+# We'll treat this as a "dangling" link if either socket is unset...
+
+
+# # May or may not use this for my Dummy Links to help me connect things
+# #  I think I can avoid it tho
+# class DummyNode:
+    # def __init__(self, signature, base_tree, prototype):
+        # self.signature = signature
+        # self.base_tree = base_tree
+        # self.prototype = prototype
+
+# This really might be useful in sorting the tree, too.
+
+
+def socket_seek(start_link, links):
+    link = start_link
+    while(link.from_socket):
+        for newlink in links:
+            if link.from_socket.node.inputs:
+                if newlink.to_socket == link.from_socket.node.inputs[0]:
+                    link=newlink; break
+        else:
+            break
+    return link.from_socket
+    
+def clear_reroutes(links):
+    kept_links, rerouted_starts = [], []
+    rerouted = []
+    all_links = links.copy()
+    while(all_links):
+        link = all_links.pop()
+        to_cls = link.to_socket.node.bl_idname
+        from_cls = link.from_socket.node.bl_idname
+        reroute_classes = ["NodeReroute"]
+        if (to_cls in reroute_classes and
+            from_cls in reroute_classes):
+                rerouted.append(link)
+        elif (to_cls in reroute_classes and not
+            from_cls in reroute_classes):
+                rerouted.append(link)
+        elif (from_cls in reroute_classes and not
+            to_cls in reroute_classes):
+                rerouted_starts.append(link)
+        else:
+            kept_links.append(link)
+    for start in rerouted_starts:
+        from_socket = socket_seek(start, rerouted)
+        new_link = DummyLink(from_socket=from_socket, to_socket=start.to_socket, nc_from=None, nc_to=None)
+        kept_links.append(new_link)
+    return kept_links
+
+def data_from_tree(base_tree, tree_path = [None], held_links = {}, all_nc = {}):
+    # prGreen("Starting! Base Tree: %s, held nodes: %d, held links: %d, nc's: %d" % (base_tree.name, len(held_nodes), len(held_links), len(all_nc)))
+    # prPurple(tree_path)
+    nc_dict = {}
+    tree_path_names = [tree.name for tree in tree_path if hasattr(tree, "name")]
+    all_child_ng = []
+    tree = base_tree
+    if tree_path[-1]:
+        tree = tree_path[-1].node_tree
+    
+    # Start by looking through the nodes and making nc's where possible
+    #  store the groups, we'll pricess them soon.
+    for np in tree.nodes:
+        if (nc_cls := class_for_mantis_prototype_node(np)):
+            nc = nc_cls( sig := tuple([None] + tree_path_names + [np.name]) , base_tree)
+            nc_dict[sig] = nc; all_nc[sig] = nc
+        if hasattr(np, "node_tree"):
+            all_child_ng.append(np)
+
+    # Then deal with the links in the current tree and the held_links.
+    kept_links, incoming, outgoing = [], [], []
+    all_links = clear_reroutes(list(tree.links))
+    while(all_links):
+        link = all_links.pop()
+        to_cls = link.to_socket.node.bl_idname
+        from_cls = link.from_socket.node.bl_idname
+        if (from_cls in ["NodeGroupInput"]):
+                incoming.append(link)
+        elif (to_cls in ["NodeGroupOutput"]):
+            nc_from = nc_dict.get( tuple([None]+tree_path_names+[link.from_socket.node.name]) )
+            to_s = link.to_socket.identifier
+            # Let's try and connect it now; go UP:
+            nc_to, new_link = None, None
+            
+            if len(tree_path)==1:
+                prRed("Warning: There is a GroupOutput node in the Base Tree.")
+                kept_links.append(link)
+                continue
+            elif tree_path[-2] is None:
+                up_tree=base_tree
+            else:
+                up_tree=tree_path[-2].node_tree
+            
+            for up_node in up_tree.nodes:
+                for out in up_node.outputs:
+                    if not hasattr(up_node, "node_tree"):
+                        continue
+                    if not out.is_linked:
+                        continue
+                    for up_link in out.links:
+                        if up_link.from_socket.identifier == to_s:
+                            new_link = DummyLink(from_socket=up_link.from_socket, to_socket=up_link.to_socket, nc_from=nc_from, nc_to=None)
+                            link_sig = tuple([None]+tree_path_names[:-1]+[up_link.to_socket.node.name, link.from_socket.name])
+                            held_links[link_sig] = new_link
+            nc_from, nc_to = None, None # clear them
+        elif (from_cls in ["MantisNodeGroup"]):
+            outgoing.append(link)
+        else:
+            kept_links.append(link)
+    # Make the connections:
+    for link in kept_links:
+        nc_from = nc_dict.get( tuple([None] + tree_path_names + [link.from_socket.node.name]) )
+        nc_to = nc_dict.get( tuple([None] + tree_path_names + [link.to_socket.node.name]) )
+        if (nc_from and nc_to):
+            from_s, to_s = link.from_socket.name, link.to_socket.name
+            connection = nc_from.outputs[from_s].connect(node=nc_to, socket=to_s)
+    nc_from = None; nc_to = None #clear them, since we use the same variable names again
+    
+    # At this point we're also gonna deal with held links and held nodes
+    hold_further = {}
+    del_me = set() # I donno why but there can be dupes.
+    for link_sig, held in held_links.items():
+        found, connected = False, False
+        nc_from, from_s = held.nc_from, held.original_from.name
+        watch = (nc_from.signature[-1] == 'Parent') and (tree_path_names[-1] in ["right", "left"])
+        for link in incoming:
+            if watch:
+                prWhite(link_sig)
+            if ((link.from_socket.identifier != link_sig[-1]) or
+                (link.from_socket.name != link_sig[-2])):
+                continue # This ain't it
+            if (link.from_socket.identifier == link_sig[-1]):
+                if (link.to_socket.node.bl_idname in [ "MantisNodeGroup" ]):
+                    del_me.add(link_sig)
+                    link_sig = tuple(list(link_sig[:-2]) + [link.to_socket.name, link.to_socket.identifier])
+                    hold_further[link_sig] = DummyLink(from_socket = held.from_socket, to_socket = link.to_socket, nc_from=nc_from, nc_to = None, original_from=held.original_from)
+                    prGreen("Holding further %s" % held.original_from.name)
+                    continue # just continue to hold it
+                found = True
+                # TO-Node:
+                to_s = link.to_socket.name
+                sig_to = tuple([None] + tree_path_names + [link.to_socket.node.name])
+                nc_to = nc_dict.get( sig_to )
+                if (nc_to and nc_from):
+                    if watch:
+                        prGreen("Connecting: %s%s -> %s%s" % (nc_from, from_s, nc_to, to_s) )
+                    connection = nc_from.outputs[from_s].connect(node=nc_to, socket=to_s)
+                    connected = True
+                elif watch:
+                    prRed("Not Connecting: %s%s -> %s%s" % (nc_from, from_s, nc_to, to_s) )
+        if (connected) != found:
+            print(wrapRed("Not Connected: ") ,link_sig, held)
+    
+    # it's fairly annoying that I can't do this while I go.
+    # for k in del_me:
+        # del held_links[k]
+    for k,v in hold_further.items():
+        held_links[k] = v
+    
+    
+    for ng in  all_child_ng:
+        for inp in ng.inputs:
+            if not inp.is_linked:
+                nc_cls = gen_nc_input_for_data(inp)
+                if (nc_cls):
+                    sig = ("MANTIS_AUTOGENERATED", *tree_path_names, inp.node.name, inp.identifier)
+                    nc = nc_cls(sig, tree)
+                    # HACK HACK HACK
+                    for k, v in nc.outputs.items():
+                        v.name = inp.name; break
+                    from mantis.node_container_common import NodeSocket
+                    nc.outputs[inp.name] = NodeSocket(name = inp.name, node=nc)
+                    # del nc.outputs[k]; del nc.parameters[k]
+                    nc.parameters[inp.name]=inp.default_value
+                    # HACK HACK HACK
+                    
+                    nc_dict[sig] = nc; all_nc[sig] = nc
+                    
+                    dummy = DummyLink(from_socket = inp, to_socket = inp, nc_from=nc, nc_to=None)
+                    link_sig =  tuple([None] + tree_path_names +[ng.name, inp.name, inp.identifier])
+                    held_links[link_sig]=dummy
+            else: # We need to hold the incoming connections
+                for link in inp.links: #Usually there will only be 1
+                    from_socket = link.from_socket
+                    if (link.from_socket.node.bl_idname == "NodeGroupInput"):
+                        # shouldn't there be a held link for this?
+                        continue
+                    
+                    if (link.from_socket.node.bl_idname == "NodeReroute"):
+                        from_socket = socket_seek(link, list(tree.links))
+                    sig =  tuple( [None] + tree_path_names +[from_socket.node.name])
+                    # print(sig)
+                    nc_from = nc_dict.get(sig)
+                    # This is kind of stupid
+                    if from_socket.node.bl_idname in "NodeGroupInput":
+                        nc_from = held_links.get( tuple([None] + tree_path_names + [link.from_socket.name, inp.identifier]) )
+                        if not (nc_from):
+                            prRed( [None] + tree_path_names + [link.from_socket.name, inp.identifier])
+                            for signature, link in held_links.items():
+                                print ( wrapGreen(signature), wrapWhite(link))
+                        nc_from = nc_from.nc_from
+                        
+                    if (nc_from):
+                        dummy = DummyLink(from_socket = from_socket, to_socket = inp, nc_from=nc_from, nc_to=None, original_from=from_socket )
+                        # The link sig should take us back to the group node.
+                        link_sig =  tuple( [None] + tree_path_names + [ng.name, inp.name, inp.identifier])
+                        held_links[link_sig]=dummy
+                        prGreen("Adding %s" % from_socket)
+                    else:
+                        prRed("no nc?")
+                        prOrange(sig)
+        # Recurse!
+        # data_from_tree(base_tree, tree_path+[ng], grps, solved_trees, solved_tree_links, held_nodes, held_links, all_nc)
+        
+        
+        data_from_tree(base_tree, tree_path+[ng], held_links, all_nc)
+        
+        for link_sig, held in held_links.items():
+            from_cls = held.from_socket.node.bl_idname
+            to_cls = held.to_socket.node.bl_idname
+            if (from_cls in ["MantisNodeGroup"] and not
+                    to_cls in ["MantisNodeGroup"]):
+                nc_from = held.nc_from
+                for link in outgoing:
+                    if link.from_socket.node.name == nc_from.signature[-2]:
+                        to_sig = tuple([None] + tree_path_names + [held.to_socket.node.name])
+                        nc_to = nc_dict.get( to_sig )
+                        if (nc_from and nc_to):
+                            from_s, to_s = link_sig[-1], held.to_socket.name
+                            connection = nc_from.outputs[from_s].connect(node=nc_to, socket=to_s)
+            
+        held_nodes = {}; held_links = {} # NO IDEA why I have to do this
+            
+            
+    
+    # return None, grps, all_links, solved_trees, solved_tree_links, all_nc
+    return all_nc
+            
+            
+from itertools import chain
+
+def parse_tree(base_tree):
+    all_nc =  data_from_tree(base_tree, tree_path = [None], held_links = {}, all_nc = {})
+    all_nc = list(all_nc.values()).copy()
+    kept_nc = {}
+    while (all_nc):
+        nc = all_nc.pop()
+        # total_links=0
+        # for sock in chain( nc.inputs.values(), nc.outputs.values()):
+            # total_links+=len(sock.links)
+        # if total_links > 0:
+        nc.fill_parameters()
+        # ugly, but it solves the problem easily:
+        establish_node_connections(nc)
+        kept_nc[nc.signature]=nc
+    return kept_nc
+
+
+from_name_filter = ["Driver", ]
+
+to_name_filter = [
+                   "Custom Object xForm Override",
+                   "Custom Object",
+                   "Deform Bones"
+                 ]
+
+def establish_node_connections(nc):
+    # This is ugly bc it adds parameters to an object
+    #  but it's kinda necesary to do it after the fact; and it
+    #  wouldn't be ugly if I just initialized the parameter elsewhere
+    connections, hierarchy_connections = [], []
+    for socket in nc.outputs.values():
+        for link in socket.links:
+            connections.append(link.to_node)
+            # this may catch custom properties... too bad.
+            if link.from_socket in from_name_filter:
+                continue
+            if link.to_socket in to_name_filter:
+                continue
+            hierarchy_connections.append(link.to_node)
+    nc.connected_to = connections
+    nc.hierarchy_connections = hierarchy_connections
+
+
+def sort_tree_into_layers(nodes, context):
+    from time import time
+    from mantis.node_container_common import (get_depth_lines,
+      node_depth)
+    from mantis.utilities import prGreen, prOrange, prRed, prPurple
+    # All this function needs to do is sort out the hierarchy and
+    #  get things working in order of their dependencies.
+    
+    prPurple ("Number of nodes: ", len(nodes))
+    roots, drivers = [], []
+    start = time()
+    
+    for n in nodes.values():
+        if n.node_type == 'DRIVER': drivers.append(n)
+        # ugly but necesary to ensure that drivers are always connected.
+        if not (hasattr(n, 'inputs')) or ( len(n.inputs) == 0):
+            roots.append(n)
+        elif (hasattr(n, 'inputs')):
+            none_connected = True
+            for inp in n.inputs.values():
+                if inp.is_linked: none_connected = False
+            if none_connected: roots.append(n)
+    
+    layers, nodes_heights = {}, {}
+    
+    
+    
+    for root in roots:
+            nodes_heights[root.signature] = 0
+        
+    #Possible improvement: unify roots if they represent the same data
+    all_sorted_nodes = []
+    for root in roots:
+        
+        # if len(root.hierarchy_connections) == 0:
+            # if (len(root.connected_to) == 0):
+                # prRed("No connections: ", root)
+            # continue
+        
+        depth_lines = get_depth_lines(root)[0]
+        
+        for n in nodes.values():
+            if n.signature not in (depth_lines.keys()):
+                continue #belongs to a different root
+            d = nodes_heights.get(n.signature, 0)
+            if (new_d := node_depth(depth_lines[n.signature])) > d:
+                d = new_d
+            nodes_heights[n.signature] = d
+                
+    for k, v in nodes_heights.items():
+        if (layer := layers.get(v, None)):
+            layer.append(nodes[k]) # add it to the existing layer
+        else: layers[v] = [nodes[k]] # or make a new layer with the node
+        all_sorted_nodes.append(nodes[k]) # add it to the sorted list
+    
+    for n in nodes.values():
+        if n not in all_sorted_nodes:
+            for drv in drivers:
+                if n in drv.connected_to:
+                    depth = nodes_heights[drv.signature] + 1
+                    nodes_heights[n.signature] = depth
+                    # don't try to push downstream deps up bc this
+                    #  is a driver and it will be done in the
+                    #  finalize pass anyway
+                    if (layer := layers.get(depth, None)):
+                        layer.append(n)
+                    else: layers[v] = [n]
+                else:
+                    prRed(n)
+                    for inp in n.inputs.values():
+                        print (len(inp.links))
+                    raise RuntimeError(wrapRed("Failed to depth-sort nodes (because of a driver-combine node?)"))
+    #
+    prGreen("Sorting depth for %d nodes finished in %s seconds" %
+               (len(nodes), time() - start))
+    
+    if (False): # True to print the layers
+        for i in range(len(layers)):
+            try:
+                print(i, layers[i])
+            except KeyError: # empty layer?
+                print (i)
+    return layers
+
+
+def execute_tree(nodes, base_tree, context):
+    import bpy
+    from time import time
+    from mantis.node_container_common import GraphError
+    start_time  = time()
+    
+    # input_from_grp_nodes(parsed_tree, base_tree, nodes)
+    # bpy.ops.wm.quit_blender()
+    
+    layers = sort_tree_into_layers(nodes, context)
+    start_execution_time = time()
+    
+    #             Execute the first pass (xForm, Utility)              #
+    for i in range(len(layers)):
+        for node in layers[i]:
+            if (node.node_type in ['XFORM', 'UTILITY']):
+                try:
+                    node.bExecute(context)
+                except Exception as e:
+                    prRed("Execution failed at %s" % node); raise e
+    #                     Switch to Pose Mode                          #
+    active = None
+    switch_me = []
+    for n in nodes.values():
+        # if it is a armature, switch modes
+        # total hack                   #kinda dumb
+        if ((hasattr(n, "bGetObject")) and (n.__class__.__name__ == "xFormArmature" )):
+            try:
+                ob = n.bGetObject()
+            except KeyError: # for bones
+                ob = None
+            # TODO this will be a problem if and when I add mesh/curve stuff
+            if (hasattr(ob, 'mode') and ob.mode == 'EDIT'):
+                switch_me.append(ob)
+                active = ob # need to have an active ob, not None, to switch modes.
+            # we override selected_objects to prevent anyone else from mode-switching
+    # TODO it's possible but unlikely that the user will try to run a 
+    #    graph with no armature nodes in it.
+    if (active):
+        bpy.ops.object.mode_set({'active_object':active, 'selected_objects':switch_me}, mode='POSE')
+    
+    #               Execute second pass (Link, Driver)                 #
+    for i in range(len(layers)):
+        for n in layers[i]:
+            # Now do the Link & Driver nodes during the second pass.
+            if (n.node_type in ['LINK', 'DRIVER']):
+                try:
+                    n.bExecute(context)   
+                except GraphError:
+                    pass                     
+                except Exception as e:
+                    print (n); raise e
+                    
+    #                          Finalize                                #
+    for i in range(len(layers)):
+        for node in layers[i]:
+            if (hasattr(node, "bFinalize")):
+                node.bFinalize(context)
+    
+    prGreen("Executed Tree in %s seconds" % (time() - start_execution_time))
+    prGreen("Finished executing tree in %f seconds" % (time() - start_time))
+    

+ 1603 - 0
socket_definitions.py

@@ -0,0 +1,1603 @@
+import bpy
+from bpy.types import NodeSocket, NodeSocketStandard
+
+
+from mantis.utilities import (prRed, prGreen, prPurple, prWhite,
+                              prOrange,
+                              wrapRed, wrapGreen, wrapPurple, wrapWhite,
+                              wrapOrange,)
+
+
+transform_spaces = (('WORLD', "World", "World Space"),
+                    ('LOCAL', "Local", "Local Space"),
+                    ('POSE', "Pose", "Pose Space"),
+                    # ('CUSTOM', "Custom", "Custom Space")
+                    ('LOCAL_WITH_PARENT', "Local (With Parent)", "Local Space"),)
+                    
+                    # ('TRANSFORM', "Pose", "Pose Space"),)
+                    
+transform_spaces_bone_object = (('WORLD', "World", "World Space"),
+                                ('LOCAL', "Local", "Local Space"),
+                                ('POSE', "Pose", "Pose Space"),)
+                                # ('CUSTOM', "Custom", "Custom Space")
+transform_spaces_object = (('WORLD', "World", "World Space"),
+                           ('LOCAL', "Local", "Local Space"),)
+                           # ('CUSTOM', "Custom", "Custom Space")
+
+enumRotationOrder =(('AUTO', 'Auto', 'Auto'),
+                    ('XYZ', "XYZ", "XYZ"),
+                    ('XZY', "XZY", "XZY"),
+                    ('ZXY', "ZXY", "ZXY"),
+                    ('ZYX', "ZYX", "ZYX"),
+                    ('YXZ', "YXZ", "YXZ"),
+                    ('YZX', "YZX", "YZX"),
+                    ('QUATERNION', "Quaternion", "Quaternion"),
+                    ('AXIS_ANGLE', "Axis Angle", "Axis Angle"),)
+
+
+
+# node socket colors:
+cFloat          = (0.631373, 0.631373, 0.631373, 1.000000)
+cColor          = (0.780392, 0.780392, 0.160784, 1.000000)
+cVector         = (0.388235, 0.388235, 0.780392, 1.000000)
+cShader         = (0.388235, 0.780392, 0.388235, 1.000000)
+cInt            = (0.058824, 0.521569, 0.149020, 1.000000)
+cString         = (0.388235, 0.388235, 0.388235, 1.000000)
+# cBool           = (0.698039, 0.650980, 0.188235, 1.000000)
+cParameter      = (0.48, 0.24, 0.24, 1.0)
+cDriver         = (0.88, 0.11, 0.88, 1.0)
+cDriverVariable = (0.66, 0.33, 0.04, 1.0)
+cFCurve         = (0.77, 0.77, 0.11, 1.0)
+cKeyframe       = (0.06, 0.22, 0.88, 1.0)
+cEnable         = (0.92, 0.92, 0.92, 1.0)
+cLayerMask      = (0.82, 0.82, 0.82, 1.0)
+cDeformer       = (0.05, 0.08, 0.45, 1.0)
+
+
+# custom colors:
+cIK             = (0.596078, 0.596078, 0.364706, 1.000000) #because it's yellow in Blender
+cRelationship   = (0.352941, 0.584314, 0.431373, 1.000000) #constraint color
+cMatrix         = (0.0, 1.0, 0.75, 1)
+cxForm          = (0.843137, 0.592157, 0.388235, 1.000000) #could even fetch the theme colors...
+cTransformSpace = (1.0, 0.4, 0.216, 1.0)
+cBool           = (0.1, 0.1, 0.1, 1.0)
+cBool3          = (0.35, 0.25, 0.18, 1.0)
+cRotationOrder  = (0.0, 0.8, 0.0, 1.0)
+cQuaternion     = (0.85, 0.25, 0.18, 1.0)
+#
+cGeometry          = (0.000000, 0.672443, 0.366253, 1.000000)
+# think about making colors that are representative of the data's purpose:
+   # location
+   # rotation
+   # scale
+
+# OR make all of it a reference to the type of data within?
+
+# Hybrid approach: Make same-data, similar purpose have similar colors.
+
+
+def TellClasses():
+    return [ #MantisSocket,
+             #DefaultSocket,
+             #InputSocket,
+             MatrixSocket,
+             xFormSocket,
+             xFormMultiSocket,
+             RelationshipSocket,
+             DeformerSocket,
+             GeometrySocket,
+             GenericRotationSocket,
+             EnableSocket,
+             HideSocket,
+            #  InverseKinematicsSocket,
+             DriverSocket,
+             DriverVariableSocket,
+             FCurveSocket,
+             KeyframeSocket,
+             LayerMaskSocket,
+             LayerMaskInputSocket,
+             
+             xFormParameterSocket,
+             ParameterBoolSocket,
+             ParameterIntSocket,
+             ParameterFloatSocket,
+             ParameterVectorSocket,
+             ParameterStringSocket,
+             
+             TransformSpaceSocket,
+             BooleanSocket,
+             BooleanThreeTupleSocket,
+             RotationOrderSocket,
+             QuaternionSocket,
+             QuaternionSocketAA,
+             IntSocket,
+             StringSocket,
+
+             EnumMetaRigSocket,
+             EnumMetaBoneSocket,
+             BoolUpdateParentNode,
+             LabelSocket,
+             IKChainLengthSocket,
+             EnumInheritScale,
+             EnumRotationMix,
+             EnumRotationMixCopyTransforms,
+             EnumMaintainVolumeStretchTo,
+             EnumRotationStretchTo,
+             EnumTrackAxis,
+             EnumUpAxis,
+             EnumLockAxis,
+             EnumLimitMode,
+             EnumYScaleMode,
+             EnumXZScaleMode,
+             EnumTransformationMap,
+             EnumTransformationRotationMode,
+             EnumTransformationRotationOrder,
+             EnumTransformationTranslationMixMode,
+             EnumTransformationRotationMixMode,
+             EnumTransformationScaleMixMode,
+             EnumTransformationAxes,
+             # Deformers
+             EnumSkinning,
+             #
+             FloatSocket,
+             FloatPositiveSocket,
+             FloatFactorSocket,
+             FloatAngleSocket,
+             VectorSocket,
+             VectorEulerSocket,
+             VectorTranslationSocket,
+             VectorScaleSocket,
+             # Drivers             
+             EnumDriverVariableType,
+             EnumDriverVariableEvaluationSpace,
+             EnumDriverRotationMode,
+             EnumDriverType,]
+
+def Tell_bl_idnames():
+    return [cls.bl_idname for cls in TellClasses()]
+
+
+# Was setting color like this:
+# color : bpy.props.FloatVectorProperty(size = 4, default = cFCurve,)
+# but this didn't work when Blender automatically generated interface classes?
+# so changed it to color = cVariable
+# but for color-changing sockets, if I make them, this won' work? Maybe?
+#
+# I actually think I was wrong about all of that lol
+# TODO change it back, dingus
+
+########################################################################
+#  Update Callbacks
+########################################################################
+
+def default_update(socket, context, do_execute=True):
+    context = bpy.context
+    if not context.space_data:
+        return
+    if not hasattr(context.space_data, "path"):
+        return
+    node_tree = context.space_data.path[0].node_tree
+    if node_tree.do_live_update:
+        # I don't know how the tree can be valid at 0 nodes but doesn't hurt
+        #  to force it if this somehow happens.
+        if ((node_tree.tree_valid == False or len(node_tree.parsed_tree) == 0)
+             or socket.node.bl_idname in ["MantisNodeGroup"]):
+            # prGreen("Forcing Update From Socket Change.")
+            node_tree.update_tree(context)
+        elif (node_tree.tree_valid == True):
+            # prGreen("Partial Update From Socket Change.")
+            # We don't have to update the whole thing, just the socket
+            from mantis.utilities import tree_from_nc
+            for nc in node_tree.parsed_tree.values():
+                try:
+                    if (tree_from_nc(nc.signature, nc.base_tree) == socket.node.id_data):
+                        if socket.node.name in nc.signature:
+                            getstring = socket.name
+                            if (getstring not in nc.parameters.keys()):
+                                prRed("Socket update failed for %s" % socket.name)
+                            else:
+                                nc.parameters[getstring] = socket.default_value
+                except AttributeError as e:
+                    prWhite(nc)
+                    prWhite(nc.inputs)
+                    raise e
+            # Now update the tree display:
+            node_tree.display_update(context)
+        if node_tree.do_live_update:
+            try:
+                node_tree.execute_tree(context)
+            except Exception as e:
+                prRed("Automatic Tree Execution failed because of %s" % e)
+
+
+def update_socket(self, context,):
+    default_update(self,context)
+    
+
+                        
+def update_mute_socket(self, context):
+    self.node.mute = not self.default_value
+    default_update(self,context)
+    
+def update_hide_socket(self, context):
+    self.node.mute = self.default_value
+    default_update(self,context)
+
+def update_parent_node(self, context):
+    default_update(self,context)
+    if hasattr(self.node, "display_update"):
+        self.node.display_update(context)
+    
+def ik_chain_length_update_socket(self, context):
+    default_update(self,context)
+    # self.node.update_chain_length(context)
+    
+# Driver Variable:
+def driver_variable_socket_update(self, context):
+    default_update(self,context)
+    self.node.update_on_socket_change(context)
+    
+def driver_socket_update(self, context):
+    default_update(self,context)
+    self.node.update_on_socket_change(context)
+
+def update_metarig_armature(self, context,):
+    if self.search_prop:
+        self.node.armature = self.search_prop.name
+        self.node.inputs["Meta-Bone"].search_prop = self.search_prop
+    default_update(self,context)
+
+def update_metarig_posebone(self, context,):
+    self.node.pose_bone = self.default_value
+    default_update(self,context)
+
+
+
+########################################################################
+#  Sockets
+########################################################################
+
+
+def ChooseDraw(self, context, layout, node, text, icon = "NONE", use_enum=True, nice_bool=True, icon_only=False):
+    # TEXT ONLY
+    if ( (hasattr(self, "text_only")) and (getattr(self, "text_only") ) ):
+        layout.label(text=text)
+    # ENUM VALUES (this is a HACK, fix it later)
+    elif ('Enum' in self.bl_idname) and (use_enum):
+        layout.prop_tabs_enum(self, "default_value",)
+    # for OUTPUT sockets that take INPUT (confusing name!)
+    elif ((hasattr(self, "default_value")) and hasattr(self, "input") and getattr(self, "input")):
+        # for simple input nodes
+        layout.prop(self, "default_value", text=text, toggle=nice_bool, slider=True)
+    # for INPUTS that are NOT CONNECTED
+    elif (hasattr(self, "default_value")) and not (self.is_output or self.is_linked):
+        # DO: expose these values as parameters for this function
+        #   and set them for each socket.
+        if icon == 'NONE': icon_only = False
+        elif icon_only == True : text = "" # "real" icon-only looks bad for strings, need to check other props types.
+        layout.prop(self, "default_value", text=text, toggle=nice_bool, slider=True, icon=icon,)
+    # CONNECTED sockets and outputs without input fields
+    else:
+        layout.label(text=text)
+
+class RelationshipSocket(NodeSocket):
+    # Description string
+    '''Relationship'''
+    # Optional identifier string. If not explicitly defined, the python class name is used.
+    bl_idname = 'RelationshipSocket'
+    bl_label = "Relationship"
+    color = cRelationship
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class DeformerSocket(NodeSocket):
+    # Description string
+    '''Deformer'''
+    # Optional identifier string. If not explicitly defined, the python class name is used.
+    bl_idname = 'DeformerSocket'
+    bl_label = "Deformer"
+    color = cDeformer
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+
+
+class MatrixSocket(NodeSocket):
+    '''Matrix Input Output'''
+    bl_idname = 'MatrixSocket'
+    bl_label = "Matrix"
+    default_value : bpy.props.FloatVectorProperty(
+        default = (1.0, 0.0, 0.0, 0.0, 
+                   0.0, 1.0, 0.0, 0.0, 
+                   0.0, 0.0, 1.0, 0.0, 
+                   0.0, 0.0, 0.0, 1.0),
+        size=16,
+        update = update_socket,)
+    color = cMatrix
+    input : bpy.props.BoolProperty(default =False,)
+
+    # Optional function for drawing the socket input value
+    def draw(self, context, layout, node, text):
+        layout.label(text=text)
+    def draw_color(self, context, node):
+        return self.color
+
+    # Utility functions to make handling the 16 numbers more bearable
+    def SetValue(self, mat):
+        self.default_value =  ( mat[0][0], mat[0][1], mat[0][2], mat[0][3],
+                                mat[1][0], mat[1][1], mat[1][2], mat[1][3],
+                                mat[2][0], mat[2][1], mat[2][2], mat[2][3],
+                                mat[3][0], mat[3][1], mat[3][2], mat[3][3], )
+    def TellValue(self):
+        from mathutils import Matrix
+        v = self.default_value
+        return Matrix( ( ( v[ 0], v[ 1], v[ 2], v[ 3],),
+                         ( v[ 4], v[ 5], v[ 6], v[ 7],),
+                         ( v[ 8], v[ 9], v[10], v[11],),
+                         ( v[12], v[13], v[14], v[15]), ) )
+                         #NOTE, we're not using the last row
+                         # so we're gonna use it to store data
+                         # unused, unused, unused, bone_length
+                         # but we're not going to make it
+                         # available except by accessor functions
+    # would like to make this stuff easier to deal with tho
+    def TellBoneLength(self):
+        return self.default_value[15]
+
+
+class xFormSocket(NodeSocket):
+    '''xFrom Input Output'''
+    bl_idname = 'xFormSocket'
+    bl_label = "xForm"
+    color = cxForm
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class xFormMultiSocket(NodeSocket):
+    '''xFrom Input Output'''
+    bl_idname = 'xFormMultiSocket'
+    bl_label = "xForm"
+    color = cxForm
+    
+    input : bpy.props.BoolProperty(default =False,)
+    def __init__(self):
+        # self.is_multi_input=True
+        self.link_limit=0
+        
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class GeometrySocket(NodeSocket):
+    '''Geometry Input Output'''
+    bl_idname = 'GeometrySocket'
+    bl_label = "Geometry"
+    color = cGeometry
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class GenericRotationSocket(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'GenericRotationSocket'
+    bl_label = "Rotation"
+    color = (0.0,0.0,0.0,0.0)
+    input : bpy.props.BoolProperty(default =False,)
+
+
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+    #TODO reimplement the below
+    # not high priority in the least
+    # def draw(self, context, layout, node, text):
+    #     if ((self.is_linked) and (not ToMathutilsValue(QuerySocket(self)[0]))):
+    #         layout.label(text="Invalid Input")
+    #     else:
+    #         layout.label(text=text)
+    # def draw_color(self, context, node):
+    #     from mathutils import Vector, Euler, Matrix, Quaternion
+    #     sock = QuerySocket(self)[0]
+    #     val = ToMathutilsValue(sock)
+    #     color = (1.0, 0.0, 0.0, 1.0,)
+    #     if (not self.is_linked):
+    #         color = (0.0, 0.0, 0.0, 0.0,)
+    #     if (val):
+    #         if ((isinstance(val, Vector)) or (isinstance(val, Euler))):
+    #             color = cVector
+    #         elif (isinstance(val, Quaternion)):
+    #             color = cQuaternion
+    #         elif (isinstance(val, Matrix)):
+    #             color = cMatrix
+    #     return (color)
+
+###############################
+
+class EnableSocket(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnableSocket'
+    bl_label = "Enable"
+    default_value: bpy.props.BoolProperty(default=True, update = update_mute_socket,)
+    color = cEnable
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text, nice_bool=False)
+    def draw_color(self, context, node):
+        return self.color
+
+class HideSocket(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'HideSocket'
+    bl_label = "Enable"
+    default_value: bpy.props.BoolProperty(default=False, update = update_hide_socket,)
+    color = cEnable
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text, nice_bool=False)
+    def draw_color(self, context, node):
+        return self.color
+
+class FCurveSocket(NodeSocket):
+    '''fCurve'''
+    bl_idname = 'FCurveSocket'
+    bl_label = "fCurve"
+    color = cFCurve
+    input : bpy.props.BoolProperty(default =False, update = update_socket)
+    def __init__(self):
+        self.display_shape = 'DIAMOND'
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class KeyframeSocket(NodeSocket):
+    '''Keyframe'''
+    bl_idname = 'KeyframeSocket'
+    bl_label = "Keyframe"
+    color = cKeyframe
+    input : bpy.props.BoolProperty(default =False, update = update_socket)
+    def __init__(self):
+        self.display_shape = 'DIAMOND'
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class DriverSocket(NodeSocket):
+    '''Driver'''
+    bl_idname = 'DriverSocket'
+    bl_label = "Driver"
+    color = cDriver
+    input : bpy.props.BoolProperty(default =False, update = update_socket)
+    
+    def __init__(self):
+        self.display_shape = 'DIAMOND'
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class DriverVariableSocket(NodeSocket):
+    '''Driver'''
+    bl_idname = 'DriverVariableSocket'
+    bl_label = "Driver"
+    color = cDriverVariable
+    input : bpy.props.BoolProperty(default =False, update = update_socket)
+    
+    def __init__(self):
+        self.display_shape = 'DIAMOND'
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+
+
+# transform_spaces
+# transform_spaces_bone_object
+# transform_spaces_object
+
+# def get_transform_space_enum(self, context):
+    # pass
+
+class TransformSpaceSocket(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'TransformSpaceSocket'
+    bl_label = "Transform Space"
+    default_value: bpy.props.EnumProperty(
+        name="Space Transform",
+        description="Space Transform",
+        items=transform_spaces,
+        default='WORLD',
+        update = update_socket,)
+    color = cTransformSpace
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class BooleanSocket(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'BooleanSocket'
+    bl_label = "Boolean"
+    default_value: bpy.props.BoolProperty(update = update_socket,)
+    color = cBool
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class BooleanThreeTupleSocket(NodeSocket):
+    # Description string
+    '''Custom node socket type'''
+    # Optional identifier string. If not explicitly defined, the python class name is used.
+    bl_idname = 'BooleanThreeTupleSocket'
+    bl_label = "Boolean Vector"
+    default_value: bpy.props.BoolVectorProperty(subtype = "XYZ",update = update_socket,)
+    color = cBool3
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+    def TellValue(self):
+        return (self.default_value[0], self.default_value[1], self.default_value[2])
+
+class RotationOrderSocket(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'RotationOrderSocket'
+    bl_label = "Rotation Order"
+    default_value: bpy.props.EnumProperty(
+        name="Rotation Order",
+        description="Rotation Order",
+        items=enumRotationOrder,
+        default='AUTO',
+        update = update_socket,)
+    color = cRotationOrder
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class QuaternionSocket(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'QuaternionSocket'
+    bl_label = "Quaternion"
+    default_value: bpy.props.FloatVectorProperty(
+        subtype = "QUATERNION",
+        size = 4,
+        default = (1.0, 0.0, 0.0, 0.0,),
+        update = update_socket,)
+    color = cQuaternion
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class QuaternionSocketAA(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'QuaternionSocketAA'
+    bl_label = "Axis Angle"
+    color = cQuaternion
+    input : bpy.props.BoolProperty(default =False,)
+    default_value: bpy.props.FloatVectorProperty(
+        subtype = "AXISANGLE",
+        size = 4,
+        default = (1.0, 0.0, 0.0, 0.0,),
+        update = update_socket,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class IntSocket(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'IntSocket'
+    bl_label = "Boolean"
+    default_value: bpy.props.IntProperty(default=0, update = update_socket,)
+    color = cInt
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class StringSocket(bpy.types.NodeSocketString):
+    """Float Input socket"""
+    bl_idname = 'StringSocket'
+    bl_label = "Float"
+    default_value : bpy.props.StringProperty(default = "", update = update_socket,)
+    # text_only : bpy.props.BoolProperty(default=False)
+    color = cString
+    icon : bpy.props.StringProperty(default = "NONE",)
+    input : bpy.props.BoolProperty(default =False,)
+    # def __init__(self):
+        # if self.node.bl_idname == 'UtilityBoneProperties':
+            # self.display_shape='DIAMOND'
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text, icon=self.icon, icon_only=True)
+    def draw_color(self, context, node):
+        return self.color
+
+class LayerMaskSocket(bpy.types.NodeSocket):
+    """Layer Mask Input socket"""
+    bl_idname = 'LayerMaskSocket'
+    bl_label = "Layer Mask"
+    default_value: bpy.props.BoolVectorProperty(subtype = "LAYER", update = update_socket, size=32)
+    color = cLayerMask
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+        
+class LayerMaskInputSocket(bpy.types.NodeSocket): # I can probably use inheritance somehow lol
+    """Layer Mask Input socket"""
+    bl_idname = 'LayerMaskInputSocket'
+    bl_label = "Layer Mask"
+    default_value: bpy.props.BoolVectorProperty(subtype = "LAYER", update = update_socket, size=32)
+    color = cLayerMask
+    input : bpy.props.BoolProperty(default =True,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+
+
+#####################################################################################
+# Parameters
+#####################################################################################
+
+
+
+class xFormParameterSocket(NodeSocket):
+    '''xFrom Parameter'''
+    bl_idname = 'xFormParameterSocket'
+    bl_label = "Parameter"
+    color = cxForm
+    input : bpy.props.BoolProperty(default =False,)
+    
+    def __init__(self):
+        self.display_shape = 'DIAMOND'
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+# what is this one again?
+
+
+class ParameterBoolSocket(bpy.types.NodeSocket):
+    """Boolean Parameter Socket"""
+    bl_idname = 'ParameterBoolSocket'
+    bl_label = "Bool"
+    color = cBool
+    input : bpy.props.BoolProperty(default =False,)
+    #custom properties:
+    min:bpy.props.FloatProperty(default = 0)
+    max:bpy.props.FloatProperty(default = 1)
+    soft_min:bpy.props.FloatProperty(default = 0)
+    soft_max:bpy.props.FloatProperty(default = 1)
+    description:bpy.props.StringProperty(default = "")
+    default_value : bpy.props.BoolProperty(default = False, update = update_socket,)
+    def __init__(self):
+        self.display_shape = 'DIAMOND'
+        # if True:
+            # print (self.is_property_set("default_value"))
+            # ui_data = self.id_properties_ui("default_value")
+            # ui_data.update(
+                # description=self.description,
+                # default=0,) # for now
+            # ui_data.update(
+                # min = self.min,
+                # max = self.max,
+                # soft_min = self.soft_min,
+                # soft_max = self.soft_max,)
+                        
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+        
+class ParameterIntSocket(bpy.types.NodeSocket):
+    """Integer Parameter socket"""
+    bl_idname = 'ParameterIntSocket'
+    bl_label = "Int"
+    default_value : bpy.props.IntProperty(default = 0, update = update_socket,)
+    color = cInt
+    input : bpy.props.BoolProperty(default =False,)
+    #custom properties:
+    min:bpy.props.FloatProperty(default = 0)
+    max:bpy.props.FloatProperty(default = 1)
+    soft_min:bpy.props.FloatProperty(default = 0)
+    soft_max:bpy.props.FloatProperty(default = 1)
+    description:bpy.props.StringProperty(default = "")
+    def __init__(self):
+        self.display_shape = 'DIAMOND'
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+        
+class ParameterFloatSocket(bpy.types.NodeSocket):
+    """Float Parameter socket"""
+    bl_idname = 'ParameterFloatSocket'
+    bl_label = "Float"
+    default_value : bpy.props.FloatProperty(default = 0.0, update = update_socket,)
+    color = cFloat
+    input : bpy.props.BoolProperty(default =False,)
+    #custom properties:
+    min:bpy.props.FloatProperty(default = 0)
+    max:bpy.props.FloatProperty(default = 1)
+    soft_min:bpy.props.FloatProperty(default = 0)
+    soft_max:bpy.props.FloatProperty(default = 1)
+    description:bpy.props.StringProperty(default = "")
+    def __init__(self):
+        self.display_shape = 'DIAMOND'
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+        
+class ParameterVectorSocket(bpy.types.NodeSocket):
+    """Vector Parameter socket"""
+    bl_idname = 'ParameterVectorSocket'
+    bl_label = "Vector"
+    default_value : bpy.props.FloatVectorProperty(
+        default = (0.0, 0.0, 0.0),
+        update = update_socket,)
+    color = cVector
+    input : bpy.props.BoolProperty(default =False,)
+    #custom properties:
+    description:bpy.props.StringProperty(default = "")
+    def __init__(self):
+        self.display_shape = 'DIAMOND'
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class ParameterStringSocket(bpy.types.NodeSocket):
+    """String Parameter socket"""
+    bl_idname = 'ParameterStringSocket'
+    bl_label = "Float"
+    default_value : bpy.props.StringProperty(default = "", update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    text_only : bpy.props.BoolProperty(default=False)
+    #custom properties:
+    description:bpy.props.StringProperty(default = "")
+    def __init__(self):
+        self.display_shape = 'DIAMOND'
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+
+#####################################################################################
+# Additional socket types, for special cases
+#####################################################################################
+
+from bpy.props import PointerProperty, StringProperty
+
+def poll_is_armature(self, obj):
+    return obj.type == "ARMATURE"
+    
+# def poll_is_armature(self, obj):
+    # return obj.type == "ARMATURE"
+
+class EnumMetaRigSocket(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumMetaRigSocket'
+    bl_label = "Meta Rig"
+    
+    search_prop:PointerProperty(type=bpy.types.Object, poll=poll_is_armature, update=update_metarig_armature)
+    
+    def get_default_value(self):
+        return self.search_prop.name
+    
+    default_value  : StringProperty(name = "", get=get_default_value)
+    
+    color = cString
+    def draw(self, context, layout, node, text):
+        if not (self.is_linked):
+            layout.prop_search(data=self, property="search_prop", search_data=bpy.data, search_property="objects", text="", icon="OUTLINER_OB_ARMATURE", results_are_suggestions=True)
+        else:
+            layout.label(text=self.node.armature)
+        
+    def draw_color(self, context, node):
+        return self.color
+
+
+def SearchPBDraw(self, context, layout, node, text, icon = "NONE", use_enum=True, nice_bool=True, icon_only=False):
+    layout.prop_search(data=self, property="default_value", search_data=self.search_prop.data, search_property="bones", text=text, icon=icon, results_are_suggestions=True)
+    
+class EnumMetaBoneSocket(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumMetaBoneSocket'
+    bl_label = "Meta Bone"
+    
+    search_prop:PointerProperty(type=bpy.types.Object)
+    bone:StringProperty()
+    
+    def populate_bones_list(self, context):
+        # just gonna hardcode the value
+        if (meta_rig := self.search_prop):
+            retList = []
+            armatures = []
+            i = -1
+            retList.append( ('NONE', '', '', 'NONE', i:=i+1 ) )
+            for b in meta_rig.data.bones:
+                retList.append( (b.name, b.name, "Bone to copy matrix from", "BONE_DATA", i:=i+1 ) )
+            return(retList)
+        return None
+
+    # default_value : bpy.props.EnumProperty(
+                 # items = populate_bones_list,
+                 # name = "Meta Rig")
+                 
+    def get_default_value(self):
+        return self.search_prop.name
+    
+    default_value  : StringProperty(name = "", update=update_metarig_posebone)
+                 
+    color = cString
+    def draw(self, context, layout, node, text):
+        if not (self.is_linked):
+            if self.search_prop is None:
+                layout.prop(self, "default_value", text="", icon="BONE_DATA",)
+            else:
+                SearchPBDraw(self, context, layout, node, text="")
+        else:
+            layout.label(text=self.node.pose_bone)
+        
+    def draw_color(self, context, node):
+        return self.color
+    
+    
+    
+    
+    
+
+
+class BoolUpdateParentNode(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'BoolUpdateParentNode'
+    bl_label = "Boolean"
+    default_value: bpy.props.BoolProperty(default=False, update = update_parent_node)
+    color = cBool
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class LabelSocket(bpy.types.NodeSocket):
+    """Float Input socket"""
+    bl_idname = 'LabelSocket'
+    bl_label = "Label"
+    color = (0.000, 0.000, 0.000, 0.000000)
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class IKChainLengthSocket(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'IKChainLengthSocket'
+    bl_label = "Chain Length"
+    default_value: bpy.props.IntProperty(default=0, update = ik_chain_length_update_socket, min = 0, max = 255)
+    color = cInt
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+# Inherit
+
+eInheritScale = (
+        ('FULL', "Full", "Fully inherit scale", 1),
+        ('AVERAGE', "Average", "todo", 2),
+        ('ALIGNED', "Aligned", "todo", 3),
+        ('FIX_SHEAR', "Fix Shear", "todo", 4),
+        ('NONE', "None", "todo", 5),
+    )
+class EnumInheritScale(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumInheritScale'
+    bl_label = "Inherit Scale"
+    default_value: bpy.props.EnumProperty(
+        items=eInheritScale,
+        name="Inherit Scale",
+        description="Inherit Scale",
+        default = 'FULL',
+        #options = set(),
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+# Copy Rotation
+
+eRotationMix =(
+        ('REPLACE', "Replace", "Fully inherit scale", 0),
+        ('BEFORE', "Before", "Fully inherit scale", 1),
+        ('AFTER', "After", "Fully inherit scale", 2),
+        ('ADD', "Add", "Fully inherit scale", 3),
+        #todo, but i don't care much
+    )
+    
+# TODO HACK
+# I am trying to figure out how to do enum_flag as
+#  mutually exclusive options
+# but! I don't think it's possible
+# I just like the UI for it :P
+    
+class EnumRotationMix(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumRotationMix'
+    bl_label = "Rotation Mix"
+    default_value: bpy.props.EnumProperty(
+        items=eRotationMix,
+        name="Rotation Mix",
+        description="Rotation Mix",
+        default = 'REPLACE',#{'REPLACE'},
+        options = set(), # this has to be a set lol
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+eRotationMix_copytransforms =(
+        ('REPLACE', "Replace", "Fully inherit scale"),
+        ('BEFORE', "Before", "Fully inherit scale"),
+        ('AFTER', "After", "Fully inherit scale"),)
+
+class EnumRotationMixCopyTransforms(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumRotationMixCopyTransforms'
+    bl_label = "Rotation Mix"
+    default_value: bpy.props.EnumProperty(
+        items=eRotationMix_copytransforms,
+        name="Rotation Mix",
+        description="Rotation Mix",
+        default = 'REPLACE', #{'REPLACE'},
+        #options = {'ENUM_FLAG'}, # this sux
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+# STRETCH TO
+
+eMaintainVolumeStretchTo = (('VOLUME_XZX', "XZ", "XZ", 1),
+                            ('VOLUME_X', "X", "X", 2),
+                            ('VOLUME_Z', "Z", "Z", 4),
+                            ('NO_VOLUME', "None", "None", 8),)
+class EnumMaintainVolumeStretchTo(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumMaintainVolumeStretchToSocket'
+    bl_label = "Maintain Volume"
+    default_value: bpy.props.EnumProperty(
+        items=eMaintainVolumeStretchTo,
+        name="Maintain Volume",
+        description="Maintain Volume",
+        default = 'VOLUME_XZX',
+        #options = {'ENUM_FLAG'},
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+eRotationStretchTo = (('PLANE_X', "XZ", "XZ", 1),
+                      ('PLANE_Z', "ZX", "ZX", 2),
+                      ('SWING_Y', "Swing", "Swing", 4),)
+
+class EnumRotationStretchTo(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumRotationStretchTo'
+    bl_label = "Rotation"
+    default_value: bpy.props.EnumProperty(
+        items=eRotationStretchTo,
+        name="Rotation",
+        description="Rotation",
+        default = 'PLANE_X',
+        #options = {'ENUM_FLAG'},
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+# Track-To
+
+eTrackAxis = (('TRACK_X', "X", "X", 1),
+               ('TRACK_Y', "Y", "Y", 2),
+               ('TRACK_Z', "Z", "Z", 4),
+               ('TRACK_NEGATIVE_X', "-X", "-X", 8),
+               ('TRACK_NEGATIVE_Y', "-Y", "-Y", 16),
+               ('TRACK_NEGATIVE_Z', "-Z", "-Z", 32,))
+
+eUpAxis = (('UP_X', "X", "X", 1),
+           ('UP_Y', "Y", "Y", 2),
+           ('UP_Z', "Z", "Z", 4),)
+
+class EnumTrackAxis(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumTrackAxis'
+    bl_label = "Track Axis"
+    default_value: bpy.props.EnumProperty(
+        items=eTrackAxis,
+        name="Track Axis",
+        description="Track Axis",
+        default = 'TRACK_X',
+        #options = {'ENUM_FLAG'},
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class EnumUpAxis(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumUpAxis'
+    bl_label = "Up Axis"
+    default_value: bpy.props.EnumProperty(
+        items=eUpAxis,
+        name="Up Axis",
+        description="Up Axis",
+        default = 'UP_X',
+        #options = {'ENUM_FLAG'},
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+# Locked Track
+
+eLockAxis = (('LOCK_X', "X", "X", 1),
+             ('LOCK_Y', "Y", "Y", 2),
+             ('LOCK_Z', "Z", "Z", 4),)
+
+class EnumLockAxis(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumLockAxis'
+    bl_label = "Lock Axis"
+    default_value: bpy.props.EnumProperty(
+        items=eLockAxis,
+        name="Lock Axis",
+        description="Lock Axis",
+        default = 'LOCK_X',
+        #options = {'ENUM_FLAG'},
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+# Limit Distance:
+
+eLimitMode = (('LIMITDIST_INSIDE', "Inside", "Inside",),
+              ('LIMITDIST_OUTSIDE', "Outside", "Outside",),
+              ('LIMITDIST_ONSURFACE', "On Surface", "On Surface",),)
+
+class EnumLimitMode(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumLimitMode'
+    bl_label = "Clamp Region"
+    default_value: bpy.props.EnumProperty(
+        items=eLimitMode,
+        name="Clamp Region",
+        description="Clamp Region",
+        default = 'LIMITDIST_INSIDE',
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+
+# Spline IK
+eYScaleMode = (('NONE', "None", "Don’t scale the X and Z axes.",),
+              ('FIT_CURVE', "Fit Curve", "Scale the bones to fit the entire length of the curve.",),
+              ('BONE_ORIGINAL', "Bone Original", "Use the original scaling of the bones.",),)
+
+eXZScaleMode = (('NONE', "None", "Don’t scale the X and Z axes.",),
+                ('BONE_ORIGINAL', "Bone Original", "Use the original scaling of the bones.",),
+                ('INVERSE_PRESERVE', "Inverse Scale", "Scale of the X and Z axes is the inverse of the Y-Scale.",),
+                ('VOLUME_PRESERVE', "Volume Preservation", "Scale of the X and Z axes are adjusted to preserve the volume of the bones.",),)
+
+class EnumYScaleMode(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumYScaleMode'
+    bl_label = "Y Scale Mode"
+    default_value: bpy.props.EnumProperty(
+        items=eYScaleMode,
+        name="Y Scale Mode",
+        description="Y Scale Mode",
+        default = 'FIT_CURVE',
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class EnumXZScaleMode(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumXZScaleMode'
+    bl_label = "Y Scale Mode"
+    default_value: bpy.props.EnumProperty(
+        items=eXZScaleMode,
+        name="XZ Scale Mode",
+        description="XZ Scale Mode",
+        default = 'NONE',
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+
+eMapxForm = (('LOCATION', "Location", "Location",),
+             ('ROTATION', "Rotation", "Rotation",),
+             ('SCALE', "Scale", "Scale",),)
+
+
+eRotationMode = (('AUTO', 'Auto', 'Euler using the rotation order of the target.', 0),
+                 ('XYZ', "XYZ", "Euler using the XYZ rotation order", 1),
+                 ('XZY', "XZY", "Euler using the XZY rotation order", 2),
+                 ('ZXY', "ZXY", "Euler using the ZXY rotation order", 3),
+                 ('ZYX', "ZYX", "Euler using the ZYX rotation order", 4),
+                 ('YXZ', "YXZ", "Euler using the YXZ rotation order", 5),
+                 ('YZX', "YZX", "Euler using the YZX rotation order", 6),
+                 ('QUATERNION', "Quaternion", "Quaternion", 7),
+                 ('SWING_TWIST_X', 'Swing and X Twist.', 'Decompose into a swing rotation to aim the X axis, followed by twist around it.',  8),
+                 ('SWING_TWIST_Y', 'Swing and Y Twist.', 'Decompose into a swing rotation to aim the Y axis, followed by twist around it.',  9),
+                 ('SWING_TWIST_Z', 'Swing and Z Twist.', 'Decompose into a swing rotation to aim the Z axis, followed by twist around it.', 10),)
+
+enumTransformationRotationOrder = enumRotationOrder[:6]
+
+
+eTranslationMix =(
+        ('ADD', "Add", "", 0),
+        ('REPLACE', "Replace", "", 1),
+    )
+    
+eScaleMix =(
+        ('MULTIPLY', "Multiply", "", 0),
+        ('REPLACE', "Replace", "", 1),
+    )
+    
+
+class EnumTransformationMap(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumTransformationMap'
+    bl_label = "Map"
+    default_value: bpy.props.EnumProperty(
+        items=eMapxForm,
+        name="Map To/From",
+        description="Map To/From",
+        default = 'LOCATION',
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+
+class EnumTransformationRotationMode(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumTransformationRotationMode'
+    bl_label = "Map"
+    default_value: bpy.props.EnumProperty(
+        items=eRotationMode,
+        name="Rotation Mode",
+        description="Rotation Mode",
+        default = 'AUTO',
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text, use_enum=False)
+    def draw_color(self, context, node):
+        return self.color
+        
+class EnumTransformationRotationOrder(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumTransformationRotationOrder'
+    bl_label = "Map"
+    default_value: bpy.props.EnumProperty(
+        items=enumTransformationRotationOrder,
+        name="Rotation Order",
+        description="Rotation Order",
+        default = 'AUTO',
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text, use_enum=False)
+    def draw_color(self, context, node):
+        return self.color
+        
+class EnumTransformationTranslationMixMode(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumTransformationTranslationMixMode'
+    bl_label = "Map"
+    default_value: bpy.props.EnumProperty(
+        items=eTranslationMix,
+        name="Mix Translation",
+        description="Mix Translation",
+        default = 'ADD',
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text, use_enum=False)
+    def draw_color(self, context, node):
+        return self.color
+        
+class EnumTransformationRotationMixMode(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumTransformationRotationMixMode'
+    bl_label = "Map"
+    default_value: bpy.props.EnumProperty(
+        items=eRotationMix,
+        name="Mix Rotation",
+        description="Mix Rotation",
+        default = 'ADD',
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text, use_enum=False)
+    def draw_color(self, context, node):
+        return self.color
+        
+class EnumTransformationScaleMixMode(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumTransformationScaleMixMode'
+    bl_label = "Map"
+    default_value: bpy.props.EnumProperty(
+        items=eScaleMix,
+        name="Mix Scale",
+        description="Mix Scale",
+        default = 'REPLACE',
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text, use_enum=False)
+    def draw_color(self, context, node):
+        return self.color
+
+eAxes = (
+        ('X', "X", "X", 0),
+        ('Y', "Y", "Y", 1),
+        ('Z', "Z", "Z", 1),
+    )
+    
+class EnumTransformationAxes(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumTransformationAxes'
+    bl_label = "Map"
+    default_value: bpy.props.EnumProperty(
+        items=eAxes,
+        # name="",
+        # description="",
+        default = 'X',
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text, use_enum=False)
+    def draw_color(self, context, node):
+        return self.color
+        
+#
+
+eSkinningMethod = (('EXISTING_GROUPS', "Use Existing Groups", "Use the existing vertex groups, or create empty groups if not found.",),
+                   ('AUTOMATIC_HEAT', "Automatic (Heat)", "Use Blender's heatmap automatic skinning",),)
+
+class EnumSkinning(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumSkinning'
+    bl_label = "Skinning Method"
+    default_value: bpy.props.EnumProperty(
+        items=eSkinningMethod,
+        name="Skinning Method",
+        description="Skinning Method",
+        default = 'AUTOMATIC_HEAT',
+        update = update_socket,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+
+eDriverVariableType = ( 
+                        ( 'SINGLE_PROP',
+                          "Property",
+                          "Property",
+                          1),
+                        ( 'LOC_DIFF',
+                          "Distance",
+                          "Distance",
+                          2),
+                       ( 'ROTATION_DIFF',
+                         "Rotational Difference",
+                         "Rotational Difference",
+                         3),
+                         # TRANSFORMS
+                      )
+
+class EnumDriverVariableType(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumDriverVariableType'
+    bl_label = "Variable Type"
+    default_value: bpy.props.EnumProperty(
+        items = eDriverVariableType,
+        name = "Variable Type",
+        description = "Variable Type",
+        default = 'SINGLE_PROP',
+        update = driver_variable_socket_update,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text, use_enum=False)
+    def draw_color(self, context, node):
+        return self.color
+
+
+eDriverVariableEvaluationSpace = ( 
+                        ( 'WORLD_SPACE',
+                          "World",
+                          "World",
+                          1),
+                        
+                        ( 'TRANSFORM_SPACE',
+                          "Transform",
+                          "Transform",
+                          2),
+                        ( 'LOCAL_SPACE',
+                          "Local",
+                          "Local",
+                          3),
+                      )
+
+class EnumDriverVariableEvaluationSpace(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumDriverVariableEvaluationSpace'
+    bl_label = "Evaluation Space"
+    default_value: bpy.props.EnumProperty(
+        items = eDriverVariableEvaluationSpace,
+        name = "Evaluation Space",
+        description = "Evaluation Space",
+        default = 'WORLD_SPACE',
+        update = driver_variable_socket_update,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text, use_enum=False)
+    def draw_color(self, context, node):
+        return self.color
+
+class EnumDriverRotationMode(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumDriverRotationMode'
+    bl_label  = "Rotaton Mode"
+    default_value: bpy.props.EnumProperty(
+        items = eRotationMode,
+        name = "Rotation Mode",
+        description = "Rotation Mode",
+        default = 'AUTO',
+        update = driver_variable_socket_update,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text, use_enum=False)
+    def draw_color(self, context, node):
+        return self.color
+#
+
+
+eDriverType = (('AVERAGE', 'Average', 'Average', 0),
+               ('SUM', "Sum", "Sum", 1),
+               ('SCRIPTED', "Scripted", "Scripted Expression", 2),
+               ('MIN', "Min", "Minimum", 3),
+               ('MAX', "Max", "Maximum", 4),)
+
+class EnumDriverType(NodeSocket):
+    '''Custom node socket type'''
+    bl_idname = 'EnumDriverType'
+    bl_label  = "Driver Type"
+    default_value: bpy.props.EnumProperty(
+        items = eDriverType,
+        name = "Driver Type",
+        description = "Driver Type",
+        default = 'AVERAGE',
+        update = driver_socket_update,)
+    color = cString
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text, use_enum=False)
+    def draw_color(self, context, node):
+        return self.color
+
+
+# Keyframe
+
+# Enum for kf handle type
+# enum for interpolation type
+# eventually gonna make it to the fancy stuff
+
+
+class FloatSocket(bpy.types.NodeSocketFloat):
+    """Float Input socket"""
+    bl_idname = 'FloatSocket'
+    bl_label = "Float"
+    default_value : bpy.props.FloatProperty(default = 0.0, update = update_socket,)
+    color = cFloat
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+        
+class FloatPositiveSocket(bpy.types.NodeSocketFloat):
+    """Float Input socket"""
+    bl_idname = 'FloatPositiveSocket'
+    bl_label = "Float"
+    default_value : bpy.props.FloatProperty(default = 0.0, min=0, update = update_socket,)
+    color = cFloat
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class FloatFactorSocket(bpy.types.NodeSocketFloatFactor):
+    '''xFrom Input Output'''
+    bl_idname = 'FloatFactorSocket'
+    bl_label = "xForm"
+    default_value : bpy.props.FloatProperty(
+        default = 0.0,
+        min = 0.0,
+        max=1.0,
+        update = update_socket,
+        subtype='FACTOR',)
+    color = cFloat
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class FloatAngleSocket(bpy.types.NodeSocketFloatAngle):
+    '''xFrom Input Output'''
+    bl_idname = 'FloatAngleSocket'
+    bl_label = "xForm"
+    default_value : bpy.props.FloatProperty(
+        default = 0.0,
+        min = -180,
+        max=180,
+        update = update_socket,
+        subtype='ANGLE',)
+    color = cFloat
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class VectorSocket(bpy.types.NodeSocketVectorEuler):
+    """Vector Input socket"""
+    bl_idname = 'VectorSocket'
+    bl_label = "Float"
+    default_value : bpy.props.FloatVectorProperty(
+        default = (0.0, 0.0, 0.0),
+        update = update_socket,)
+    color = cVector
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class VectorEulerSocket(bpy.types.NodeSocketVectorEuler):
+    """Vector Input socket"""
+    bl_idname = 'VectorEulerSocket'
+    bl_label = "Float"
+    default_value : bpy.props.FloatVectorProperty(
+        default = (0.0, 0.0, 0.0),
+        update = update_socket,
+        subtype='EULER',)
+    color = cVector
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class VectorTranslationSocket(bpy.types.NodeSocketVectorTranslation):
+    """Vector Input socket"""
+    bl_idname = 'VectorTranslationSocket'
+    bl_label = "Float"
+    default_value : bpy.props.FloatVectorProperty(
+        default = (0.0, 0.0, 0.0),
+        update = update_socket,
+        subtype='TRANSLATION',)
+    color = cVector
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+class VectorScaleSocket(bpy.types.NodeSocketVectorXYZ):
+    """Vector Input socket"""
+    bl_idname = 'VectorScaleSocket'
+    bl_label = "Float"
+    default_value : bpy.props.FloatVectorProperty(
+        default = (1.0, 1.0, 1.0),
+        update = update_socket,
+        subtype='XYZ',)
+    color = cVector
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text)
+    def draw_color(self, context, node):
+        return self.color
+
+

+ 21 - 0
update_grandalf.sh

@@ -0,0 +1,21 @@
+#!/bin/bash
+
+# delete stale grandalf
+rm -rf ./grandalf
+# oddly, github can convert to svn and then I can checkout the folder as an svn repo...?
+# get it
+svn checkout https://github.com/bdcht/grandalf/trunk/grandalf/
+# delete the .svn stuff and the __pycache__ if they exist
+rm -rf ./grandalf/.svn
+if [[ -d "./grandalf/__pycache__" ]]
+then
+    rm -r ./grandalf/__pycache__
+fi
+if [[ -d "./grandalf/utils/__pycache__" ]]
+then
+    rm -r ./grandalf/utils/__pycache__
+fi
+# fix the imports, they need to be made relative to Mantis.
+sed -i 's/grandalf\.utils/.utils/g' ./grandalf/*.py # annoyingly, it isn't consistent in the repo
+sed -i 's/\.utils/mantis.grandalf.utils/g' ./grandalf/*.py
+# this works

+ 415 - 0
utilities.py

@@ -0,0 +1,415 @@
+
+
+#fool: should be wrColor like prColor... dumb
+
+def wrapRed(skk):    return "\033[91m{}\033[00m".format(skk)
+def wrapGreen(skk):  return "\033[92m{}\033[00m".format(skk)
+def wrapPurple(skk): return "\033[95m{}\033[00m".format(skk)
+def wrapWhite(skk):  return "\033[97m{}\033[00m".format(skk)
+def wrapOrange(skk):  return "\033[0;33m{}\033[00m".format(skk)
+
+# these should reimplement the print interface..
+def prRed(*args): print (*[wrapRed(arg) for arg in args])
+def prGreen(*args): print (*[wrapGreen(arg) for arg in args])
+def prPurple(*args): print (*[wrapPurple(arg) for arg in args])
+def prWhite(*args): print (*[wrapWhite(arg) for arg in args])
+def prOrange(*args): print (*[wrapOrange(arg) for arg in args])
+
+# add THIS to the top of a file for easy access:
+# from mantis.utilities import (prRed, prGreen, prPurple, prWhite,
+#                               prOrange,
+#                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
+#                               wrapOrange,)
+
+
+# uncomment to turn them off.
+# def prRed(*args): return; print (*[wrapRed(arg) for arg in args])
+# def prGreen(*args): return; print (*[wrapGreen(arg) for arg in args])
+# def prPurple(*args): return; print (*[wrapPurple(arg) for arg in args])
+# def prWhite(*args): return; print (*[wrapWhite(arg) for arg in args])
+# def prOrange(*args): return; print (*[wrapOrange(arg) for arg in args])
+
+
+
+#DO! Figure out what the hell this does
+# then re-write it in a simpler, cleaner way
+# that ignores groups because it gets lines from a parsed tree
+# ideally I can use the seeking-lines instead of the socket/tree lines
+# since those allow the function to travel through the tree.
+
+# not sure if the above comment still has any place here....
+
+def print_lines(lines): 
+    printstring, string = "", ""
+    cur_g = 0
+    for line in lines:
+        string += wrapRed("%i: " % len(line))
+        for s, g in line:
+            new_g = len(g) -1
+            difference = new_g - cur_g
+            if difference > 0:
+                string = string[:-1] # get rid of leading space
+                for i in range(difference):
+                    string += " [ "
+            elif difference < 0:
+                string = string[:-4]# get rid of arrow
+                for i in range(abs(difference)):
+                    string += " ] "
+                string += "-> "
+            cur_g = new_g
+            wrap=wrapWhite
+            if (s.node.bl_idname in ['UtilitySwitch', 'UtilityDriver', 'UtilityDriverVariable']):
+                wrap = wrapPurple
+            elif (s.node.bl_idname in ['xFormArmatureNode', 'xFormBoneNode']):
+                wrap = wrapOrange
+            elif (s.node.bl_idname in ['LinkStretchTo']):
+                wrap = wrapRed
+            elif ('Link' in s.node.bl_idname):
+                wrap = wrapGreen
+            string += wrap(s.node.name + ":" + s.name) + " -> "
+        string = string[:-4]
+        while cur_g > 0:
+            cur_g -= 1
+            string += " ] "
+        cur_g, difference = 0,0
+        printstring +=string + "\n\n"; string = ""
+    return printstring
+    # why is this not printing groups in brackets?
+
+def print_socket_signature(sig):
+    string = ""
+    for i, e in enumerate(sig):
+        if (e == "NONE"):
+            continue
+        wrap = wrapWhite
+        if (i == len(sig)-2):
+            wrap = wrapRed
+        elif (i == len(sig) - 1):
+            wrap = wrapGreen
+        string+= wrap(e) + ":"
+    return string[:-1]
+    
+def print_node_signature(sig,):
+    string = ""
+    for i, e in enumerate(sig):
+        if (e == "NONE"):
+            continue
+        wrap = wrapWhite
+        if (i == len(sig)-2):
+            wrap = wrapRed
+        elif (i == len(sig) - 1):
+            continue
+        string+= wrap(e) + ":"
+    return string[:-1]
+
+def print_parsed_node(parsed_node):
+    # do: make this consistent with the above
+    string = ""
+    for k, v in parsed_node.items():
+        if isinstance(v, dict):
+            string += "%s:\n" % (k)
+            for k1, v1 in v.items():
+                string += "    %s:                %s\n" % (k1, v1)
+        else:
+            string += "%s:    %s\n" % (k, v )
+    return string
+
+
+def get_socket_signature(line_element):
+    """
+    This function creates a convenient, hashable signature for
+    identifying a node path.
+    """
+    if not line_element:
+        return None
+    signature, socket, tree_path = [], line_element[0], line_element[1]
+    for n in tree_path:
+        if hasattr(n, "name"):
+            signature.append(n.name)
+        else:
+            signature.append("NONE")
+    signature.append(socket.node.name); signature.append(socket.identifier)
+    return tuple(signature)
+
+    
+    
+def tuple_of_line(line):
+    # For creating a set of lines
+    return tuple(tuple_of_line_element(e) for e in line)
+def tuple_of_line_element(line_element):
+    return (line_element[0], tuple(line_element[1]))
+
+
+# This has a lot of branches and is kinda slow.
+def socket_seek(socket, trees):
+    from bpy.types import NodeReroute, NodeGroupOutput
+    if (hasattr( socket.node, "traverse")):
+        if (socket.node.bl_idname == "MantisNodeGroup"):
+            trees.append(socket.node)
+            socket = socket.node.traverse(socket, "IN")
+        else:
+            socket = socket.node.traverse(socket)
+    elif (isinstance(socket.node, NodeReroute)):
+        socket = socket.node.outputs[0]
+    elif (isinstance(socket.node, NodeGroupOutput)):
+        group_node = trees.pop()
+        if group_node:
+            socket = group_node.traverse(socket, "OUT")
+        else:
+            raise RuntimeError("Error parsing Group Nodes")
+    else:
+        raise RuntimeError("Error: node tree cannot be navigated")
+    return socket, trees
+
+#This is a little slow.
+def lines_from_socket(sock, tree_path = [None]):
+    done = False
+    sPath =[0,]
+    lines = []
+    while (not done):
+        seek = sock
+        trees = tree_path.copy() # make sure to copy, lists are not immutable
+        for curheight, ind in enumerate(sPath):
+            if not seek: #this will cause the loop to complete normally
+                continue # which will return an error
+            if (ind <= (len(seek.links) -1)):
+                seek = seek.links[ind].to_socket
+                nextseek, trees = socket_seek(seek, trees.copy())
+                if (not nextseek): # The node has no no traverse function.
+                    # note, kind of duplicated code, TODO,
+                    lines.append(sPath[:curheight+1])
+                    if (curheight > 0):
+                        sPath[curheight] += 1
+                    else:
+                        done = True
+                    break
+                if (nextseek.is_linked): #otherwise this is a leaf
+                    seek = nextseek
+                    if (curheight == len(sPath)-1): #go up
+                        sPath.append(0)
+                elif not (nextseek.is_linked):
+                    lines.append(sPath[:curheight+1])
+                    sPath[curheight]+=1
+                    # this makes sure we're progressing through the tree.
+                    break
+            else:
+                if (curheight > 0):
+                    sPath.pop() #go back...
+                    sPath[curheight-1] += 1
+                    
+                else:
+                    done = True
+                break
+        else:
+            raise RuntimeError("There has been an error parsing the tree")
+    return lines
+
+
+# only using this once, should this even be a function?
+def create_socket_lists(sock, tree_path, lines):
+    out_lines = []
+    for line in lines:
+        s = sock
+        trees = tree_path.copy()
+        out_line = [(s, trees)]
+        for i, ind in enumerate(line):
+            if i < len(line):
+                s_next = s.links[ind].to_socket
+                s_final, trees = socket_seek(s_next, trees.copy())
+                if s_final:
+                    s = s_final
+                else: # The node has no no traverse function.
+                    # this needs special check, if it's the first node,
+                    #  it's already in the tree.
+                    if (i > 0):
+                        out_line.append( (s, trees) )
+                    out_line.append( (s_next, trees) )
+                    break
+            # nodes to skip...
+            if (s.node.bl_idname in [
+                                     "NodeReroute",
+                                     "MantisNodeGroupOutput",
+                                     "NodeGroupOutput",
+                                     "MantisNodeGroupInput",
+                                     "NodeGroupInput"
+                                    ]):
+                continue
+            
+            out_line.append( (s, trees) )
+        out_lines.append(out_line)
+    return out_lines
+
+#NOTE: this may not work at all lol
+# TODO BUG HACK rename and remove this before publishing
+def find_root_nodes(tree, tree_path = [None]):
+    root_nodes = []
+    for node in tree.nodes:
+        addMe = True
+        for s in node.inputs:
+            if (s.is_linked == True):
+                addMe = False
+                # for now, don't try to sovle this, it will need 
+                #  a backwards search
+                for link in s.links:
+                    # we need to check if this is a "false" connection;
+                    #  that is, a Group In from an unconnected Group
+                    if (link.from_socket.node.bl_idname in ["NodeGroupInput", "MantisNodeGroupInput"]):
+                        identifier = link.from_socket.identifier
+                        for grp in tree_path[1:][::-1]:
+                            for inp in grp.inputs:
+                                if inp.identifier == identifier and not inp.is_linked:
+                                    addMe=True
+                                    break
+                            else:
+                                addMe=False
+        if (hasattr(node, "node_tree")):
+            # we use the node itself here for the node path, will use it for node signature later.
+            root_nodes.extend( find_root_nodes(node.node_tree, tree_path+[node]) )
+        
+        if (node.bl_idname in [
+                               "NodeReroute",
+                               "MantisNodeGroupOutput",
+                               "NodeGroupOutput",
+                               "MantisNodeGroupInput",
+                               "NodeGroupInput",
+                               "NodeFrame",
+                              ]):
+            addMe = False
+            continue
+        
+        if (addMe):
+            root_nodes.append( (tree_path, node) )
+    return root_nodes
+
+
+
+def parse_node_tree(tree):
+    root_nodes = find_root_nodes(tree)
+    # this seems to produce garbage results. Check this!
+
+    input_lines = []
+    
+    for path, node in root_nodes:
+        # print (path, node)
+        for s in node.outputs:
+            socket_lists = create_socket_lists(s, path, lines_from_socket(s, path))
+            for line in socket_lists:
+                in_line = line.copy()
+                input_lines.append(in_line)
+    # NOT SURE if any of this stuff below is necesary at all
+    return (input_lines) # let's find out if it is
+    #
+    
+    # I think the unreachable code here is bad. TODO: figure out if there's
+    #       any reason at all to revive this old code.
+    #
+    # I certainly *like* the idea of removing redundant data.
+    #
+    # note: it seems like the Execute Tree function is completing
+    #     in 80% of the previous time after removing the below
+    #     meanign that this was wasting 1/5 of the total time
+    no_dupes_sigs = set()
+    no_dupes_lines = set()
+    for in_line in input_lines:
+        sig = get_socket_signature(in_line[-1])
+        sig = list(sig)
+        sig.append(in_line[-1][0].name) # socket
+        sig = tuple(sig)
+
+        before = len(no_dupes_sigs)
+        no_dupes_sigs.add(sig)
+        after = len(no_dupes_sigs)
+        # make a tuple of the node path, too.
+        # in_line = tuple(in_line[0]), in_line[1]
+        
+        if (before < after): # new item
+            no_dupes_lines.add(tuple_of_line(in_line))
+    #MAYBE
+    # maybe i can get a list of all nodes
+    # including nodes in nodegroups and nested node groups
+    # then I can assign a uuid to each one
+    # and associate the uuids with the node lines
+    # perhaps I should do that before running this function
+    
+    # for line in no_dupes_lines:
+        # print (list(line))
+    
+    return (list(no_dupes_lines))
+
+    # don't deal with lines no mo. Do stuff with line elements
+
+# DO THIS!
+# make lines_from_socket attach a tree path to each node-socket, instead of just a tree
+# that way, if the tree-path is longer than the socket-path, the tree path won't be truncated.
+
+
+
+# for use with node signatures
+
+def tree_from_nc(sig, base_tree):
+    if (sig[0] == 'MANTIS_AUTOGENERATED'):
+        sig = sig[:-2] # cut off the input part of the signature.
+    tree = base_tree
+    for i, path_item in enumerate(sig):
+        if (i == 0) or (i == len(sig) - 1):
+            continue
+        tree = tree.nodes.get(path_item).node_tree
+    return tree
+    
+def get_node_prototype(sig, base_tree):
+    return tree_from_nc(sig, base_tree).nodes.get( sig[-1] )
+
+      
+            
+##################################################################################################
+# misc
+##################################################################################################
+
+# This will not work with float properties. Use them directly.
+# this is an extremely idiotic way to do this
+# it's also slow!
+# TODO fix this
+#using isinstance is the most lizard-brained way to do this, utter idiocy.
+def to_mathutils_value(socket):
+    if (hasattr(socket, "default_value")):
+        val = socket.default_value
+        from mathutils import Matrix, Euler, Quaternion, Vector
+        from bpy.types import (NodeSocketVector, NodeSocketVectorAcceleration,
+                              NodeSocketVectorDirection, NodeSocketVectorEuler,
+                              NodeSocketVectorTranslation, NodeSocketVectorVelocity,
+                              NodeSocketVectorXYZ,)
+        from . import socket_definitions
+        if ((isinstance(socket, NodeSocketVector)) or
+            (isinstance(socket, NodeSocketVectorAcceleration)) or
+            (isinstance(socket, NodeSocketVectorDirection)) or
+            (isinstance(socket, NodeSocketVectorTranslation)) or
+            (isinstance(socket, NodeSocketVectorXYZ)) or
+            (isinstance(socket, NodeSocketVectorVelocity)) or
+            (isinstance(socket, socket_definitions.VectorSocket)) or
+            (isinstance(socket, socket_definitions.VectorEulerSocket)) or
+            (isinstance(socket, socket_definitions.VectorTranslationSocket)) or
+            (isinstance(socket, socket_definitions.VectorScaleSocket)) or
+            (isinstance(socket, socket_definitions.ParameterVectorSocket))):
+            return (Vector(( val[0], val[1], val[2], )))
+        if (isinstance(socket, NodeSocketVectorEuler)):
+            return (Euler(( val[0], val[1], val[2])), 'XYZ',) #TODO make choice
+        if (isinstance(socket, socket_definitions.MatrixSocket)):
+            # return val #Blender makes it a Matrix for me <3
+            # nevermind... BLENDER HAS BETRAYED ME
+            return socket.TellValue()
+        if (isinstance(socket,socket_definitions.QuaternionSocket)):
+            return (Quaternion( (val[0], val[1], val[2], val[3],)) )
+        if (isinstance(socket,socket_definitions.QuaternionSocketAA)):
+            return (Quaternion( (val[1], val[2], val[3],), val[0], ) )
+        if ((isinstance(socket, socket_definitions.FloatSocket)) or
+            (isinstance(socket, socket_definitions.ParameterIntSocket)) or
+            (isinstance(socket, socket_definitions.ParameterFloatSocket))):
+            return val
+        if (isinstance(socket, socket_definitions.BooleanThreeTupleSocket)):
+            return (val[0], val[1], val[2]) # we'll send a tuple out
+        if ((isinstance(socket, socket_definitions.LayerMaskSocket)) or
+            (isinstance(socket, socket_definitions.LayerMaskInputSocket))):
+            return tuple(val) # should werk
+    else:
+        return None
+

+ 677 - 0
xForm_containers.py

@@ -0,0 +1,677 @@
+from mantis.node_container_common import *
+from bpy.types import Node
+from .base_definitions import MantisNode
+
+def TellClasses():
+             
+    return [ 
+             # xForm
+             xFormRoot,
+             xFormArmature,
+             xFormBone,
+             xFormGeometryObject,
+           ]
+
+#*#-------------------------------#++#-------------------------------#*#
+# X - F O R M   N O D E S
+#*#-------------------------------#++#-------------------------------#*#
+
+# class xFormNull:
+    # '''A node representing an Empty object'''
+    # inputs =
+    # {
+     # "Name":None,
+     # "Rotation Order":None,
+     # "Matrix":None,
+     # "Relationship":None,
+    # }
+    # outputs =
+    # {
+     # "xFormOut":None,
+    # }
+    # parameters =
+    # {
+     # "Name":None,
+     # "Rotation Order":None,
+     # "Matrix":None,
+     # "Relationship":None,
+    # }
+    
+    # def evaluate_input(self, input):
+        # pass
+    
+    # def instantiate_blender_object(self):
+        # pass
+# for whatever reason, the above isn't implemented yet in the node-tree
+# so I'm not implementing it here, either
+
+class xFormRoot:
+    '''A node representing the root of the scene.'''
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.signature = signature
+        self.inputs = {}
+        self.outputs = {"World Out":NodeSocket(name="World Out", node = self),}
+        self.parameters = {}
+        self.links = {} # leave this empty for now!
+        self.node_type = 'XFORM'
+    
+    def evaluate_input(self, input_name):
+        return "ROOT"
+    
+    def bExecute(self, bContext = None,):
+        pass
+        
+    def bFinalize(self, bContext = None,):
+        pass
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self,):
+        pass
+
+class xFormArmature:
+    '''A node representing an armature object'''
+    
+    bObject = None
+    
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+         "Name"           : NodeSocket(is_input = True, name = "Name", node = self),
+         "Rotation Order" : NodeSocket(is_input = True, name = "Rotation Order", node = self),
+         "Matrix"         : NodeSocket(is_input = True, name = "Matrix", node = self),
+         "Relationship"   : NodeSocket(is_input = True, name = "Relationship", node = self),
+        }
+        self.outputs = {
+         "xForm Out" : NodeSocket(name="xForm Out", node = self),
+        }
+        self.parameters = {
+         "Name":None,
+         "Rotation Order":None,
+         "Matrix":None,
+         "Relationship":None,
+        }
+        self.links = {} # leave this empty for now!
+        # now set up the traverse target...
+        self.inputs["Relationship"].set_traverse_target(self.outputs["xForm Out"])
+        self.outputs["xForm Out"].set_traverse_target(self.inputs["Relationship"])
+        self.node_type = 'XFORM'
+    
+        
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+    
+    def bExecute(self, bContext = None,):
+        from .utilities import get_node_prototype
+        
+        import bpy
+        if (not isinstance(bContext, bpy.types.Context)):
+            raise RuntimeError("Incorrect context")
+
+        name = self.evaluate_input("Name")
+        matrix = self.evaluate_input('Matrix')
+
+
+        #check if an object by the name exists
+        if (name) and (ob := bpy.data.objects.get(name)):
+            if (ob.animation_data):
+                while (ob.animation_data.drivers):
+                        ob.animation_data.drivers.remove(ob.animation_data.drivers[-1])
+            for pb in ob.pose.bones:
+                # clear it, even after deleting the edit bones, 
+                #  if we create them again the pose bones will be reused
+                while (pb.constraints):
+                    pb.constraints.remove(pb.constraints[-1])
+                pb.location = (0,0,0)
+                pb.rotation_euler = (0,0,0)
+                pb.rotation_quaternion = (1.0,0,0,0)
+                pb.rotation_axis_angle = (0,0,1.0,0)
+                pb.scale = (1.0,1.0,1.0)
+        else:
+            # Create the Object
+            ob = bpy.data.objects.new(name, bpy.data.armatures.new(name)) #create ob
+            if (ob.name != name):
+                raise RuntimeError("Could not create xForm object", name)
+            
+        self.bObject = ob.name
+        ob.matrix_world = matrix
+        
+        # # first, get the parent object
+        # parent_node = get_parent(self)
+        # if hasattr(parent_node, "bObject"):
+            # # this won't work of course, TODO
+            # ob.parent = parent_node.bObject
+            # # print (self.bObject)
+        if True:
+            from bpy.types import EditBone
+            parent_nc = get_parent(self, type='LINK')
+            if parent_nc:
+                parent = parent_nc.inputs['Parent'].links[0].from_node.bGetObject(mode = 'OBJECT')
+                ob.parent = parent
+            
+        
+        # Link to Scene:
+        if (ob.name not in bContext.view_layer.active_layer_collection.collection.objects):
+            bContext.view_layer.active_layer_collection.collection.objects.link(ob)
+        #self.bParent(bContext)
+        
+        print( wrapGreen("Created Armature object: ")+ wrapWhite(ob.name))
+        # Finalize the action
+        # oddly, overriding context doesn't seem to work
+
+        try:
+            bpy.ops.object.select_all(action='DESELECT')
+        except RuntimeError:
+            pass # we're already in edit mode, should be OK to do this.
+        bContext.view_layer.objects.active = ob
+        selected=[]
+        for other_ob in bpy.data.objects:
+            if other_ob.mode == "EDIT":
+                selected.append(other_ob)
+        selected.append(ob)
+        context_override = {"active_object":ob, "selected_objects":selected}
+        print("Changing Armature Mode to " +wrapPurple("EDIT"))
+        bpy.ops.object.mode_set(context_override, mode='EDIT')
+        if ob.mode != "EDIT":
+            prRed("eh?")
+        # clear it
+        while (len(ob.data.edit_bones) > 0):
+            ob.data.edit_bones.remove(ob.data.edit_bones[0])
+        # bContext.view_layer.objects.active = prevAct
+
+        
+        
+        self.executed = True
+    
+    # # not used yet
+    # #
+    # def bFinalize(self, bContext = None):
+        # import bpy
+        # ob = self.bGetObject()
+        # prevAct = bContext.view_layer.objects.active
+        # bContext.view_layer.objects.active = ob
+        # bpy.ops.object.mode_set(mode='OBJECT')
+        # print ("Changing Armature Mode to OBJECT")
+        # bContext.view_layer.objects.active = prevAct
+
+    def bGetObject(self, mode = ''):
+        import bpy; return bpy.data.objects[self.bObject]
+
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self,):
+        fill_parameters(self)
+
+class xFormBone:
+    '''A node representing a bone in an armature'''
+    # DO: make a way to identify which armature this belongs to
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+         "Name"           : NodeSocket(is_input = True, name = "Name", node = self,),
+         "Rotation Order" : NodeSocket(is_input = True, name = "Rotation Order", node = self,),
+         "Matrix"         : NodeSocket(is_input = True, name = "Matrix", node = self,),
+         "Relationship"   : NodeSocket(is_input = True, name = "Relationship", node = self,),
+         # IK settings
+         "IK Stretch"     : NodeSocket(is_input = True, name = "IK Stretch", node = self,),
+         "Lock IK"        : NodeSocket(is_input = True, name = "Lock IK", node = self,),
+         "IK Stiffness"   : NodeSocket(is_input = True, name = "IK Stiffness", node = self,),
+         "Limit IK"       : NodeSocket(is_input = True, name = "Limit IK", node = self,),
+         "X Min"          : NodeSocket(is_input = True, name = "X Min", node = self,),
+         "X Max"          : NodeSocket(is_input = True, name = "X Max", node = self,),
+         "Y Min"          : NodeSocket(is_input = True, name = "Y Min", node = self,),
+         "Y Max"          : NodeSocket(is_input = True, name = "Y Max", node = self,),
+         "Z Min"          : NodeSocket(is_input = True, name = "Z Min", node = self,),
+         "Z Max"          : NodeSocket(is_input = True, name = "Z Max", node = self,),
+         # Visual stuff
+         "Layer Mask"                         : NodeSocket(is_input = True, name = "Layer Mask", node = self,),
+         "Hide"                               : NodeSocket(is_input = True, name = "Hide", node = self,),
+         "Custom Object"                      : NodeSocket(is_input = True, name = "Custom Object", node = self,),
+         "Custom Object xForm Override"       : NodeSocket(is_input = True, name = "Custom Object xForm Override", node = self,),
+         "Custom Object Scale to Bone Length" : NodeSocket(is_input = True, name = "Custom Object Scale to Bone Length", node = self,),
+         "Custom Object Wireframe"            : NodeSocket(is_input = True, name = "Custom Object Wireframe", node = self,),
+         "Custom Object Scale"                : NodeSocket(is_input = True, name = "Custom Object Scale", node = self,),
+         "Custom Object Translation"          : NodeSocket(is_input = True, name = "Custom Object Translation", node = self,),
+         "Custom Object Rotation"             : NodeSocket(is_input = True, name = "Custom Object Rotation", node = self,),
+         "Bone Group"                         : NodeSocket(is_input = True, name = "Bone Group", node = self,),
+         # Deform Stuff
+         "Deform"               : NodeSocket(is_input = True, name = "Deform", node = self,),
+         "Envelope Distance"    : NodeSocket(is_input = True, name = "Envelope Distance", node = self,),
+         "Envelope Weight"      : NodeSocket(is_input = True, name = "Envelope Weight", node = self,),
+         "Envelope Multiply"    : NodeSocket(is_input = True, name = "Envelope Multiply", node = self,),
+         "Envelope Head Radius" : NodeSocket(is_input = True, name = "Envelope Head Radius", node = self,),
+         "Envelope Tail Radius" : NodeSocket(is_input = True, name = "Envelope Tail Radius", node = self,),
+        }
+        
+        self.outputs = {
+         "xForm Out"       : NodeSocket(name = "xForm Out", node = self),
+        }
+        self.parameters = {
+         "Name":None,
+         "Rotation Order":None,
+         "Matrix":None,
+         "Relationship":None,
+         # IK settings
+         "IK Stretch":None,
+         "Lock IK":None,
+         "IK Stiffness":None,
+         "Limit IK":None,
+         "X Min":None,
+         "X Max":None,
+         "Y Min":None,
+         "Y Max":None,
+         "Z Min":None,
+         "Z Max":None,
+         "Layer Mask":None,
+         "Hide":None,
+         "Layer Mask":None,
+         "Hide":None,
+         "Custom Object":None,
+         "Custom Object xForm Override":None,
+         "Custom Object Scale to Bone Length":None,
+         "Custom Object Wireframe":None,
+         "Custom Object Scale":None,
+         "Custom Object Translation":None,
+         "Custom Object Rotation":None,
+         "Bone Group"           : None,
+         "Deform"               : None,
+         "Envelope Distance"    : None,
+         "Envelope Weight"      : None,
+         "Envelope Multiply"    : None,
+         "Envelope Head Radius" : None,
+         "Envelope Tail Radius" : None,
+        }
+        self.links = {} # leave this empty for now!
+        # now set up the traverse target...
+        self.inputs["Relationship"].set_traverse_target(self.outputs["xForm Out"])
+        self.outputs["xForm Out"].set_traverse_target(self.inputs["Relationship"])
+        self.node_type = 'XFORM'
+        setup_custom_props(self)
+             
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+    
+    def __repr__(self):
+        return self.signature.__repr__()
+        
+    def fill_parameters(self):
+        fill_parameters(self)
+    
+    def bGetParentArmature(self):
+        finished = False
+        if (trace := trace_single_line(self, "Relationship")[0] ) :
+            for i in range(len(trace)):
+                # have to look in reverse, actually
+                if ( isinstance(trace[ i ], xFormArmature ) ):
+                    return trace[ i ].bGetObject()
+        return None
+        #should do the trick...
+    
+    def bSetParent(self, eb):
+        # print (self.bObject)
+        from bpy.types import EditBone
+        parent_nc = get_parent(self, type='LINK')
+        # print (self, parent_nc.inputs['Parent'].from_node)
+        parent = parent_nc.inputs['Parent'].links[0].from_node.bGetObject(mode = 'EDIT')
+        if isinstance(parent, EditBone):
+            eb.parent = parent
+        
+        #DUMMY
+        # I NEED TO GET THE LINK NC
+        # IDIOT
+            
+        eb.use_connect = parent_nc.evaluate_input("Connected")
+        eb.use_inherit_rotation = parent_nc.evaluate_input("Inherit Rotation")
+        eb.inherit_scale = parent_nc.evaluate_input("Inherit Scale")
+        # otherwise, no need to do anything.
+        
+         
+    def bExecute(self, bContext = None,): #possibly will need to pass context?
+        import bpy
+        from mathutils import Vector
+        if (not isinstance(bContext, bpy.types.Context)):
+            raise RuntimeError("Incorrect context")
+        xF = self.bGetParentArmature()
+        
+        name = self.evaluate_input("Name")
+        matrix = self.evaluate_input("Matrix")
+        
+        length = matrix[3][3]
+        
+        if (xF):
+            if (xF.mode != "EDIT"):
+                raise RuntimeError("Armature Object Not in Edit Mode, exiting...")
+        else:
+            raise RuntimeError("Could not create edit bone: ", name, " from node:", self.signature, " Reason: No armature object to add bone to.")
+        #
+        # Create the Object
+        d = xF.data
+        eb = d.edit_bones.new(name)
+        
+        if (eb.name != name):
+            raise RuntimeError("Could not create bone ", name, "; Perhaps there is a duplicate bone name in the node tree?")
+        eb.matrix  = matrix.copy()
+        tailoffset = Vector((0,length,0)) #Vector((0,self.tailoffset, 0))
+        tailoffset = matrix.copy().to_3x3() @ tailoffset
+        eb.tail    = eb.head + tailoffset
+        
+        if (eb.name != name):
+            raise RuntimeError("Could not create edit bone: ", name)
+        assert (eb.name), "Bone must have a name."
+        self.bObject = eb.name
+        # The bone should have relationships going in at this point.
+        
+        assert (self.bObject), "eh? %s" % eb.name
+        
+        self.bSetParent(eb)
+        eb.layers = self.evaluate_input("Layer Mask")
+        
+        
+        # Setup Deform attributes...
+        eb.use_deform            = self.evaluate_input("Deform")
+        eb.envelope_distance     = self.evaluate_input("Envelope Distance")
+        eb.envelope_weight       = self.evaluate_input("Envelope Weight")
+        eb.use_envelope_multiply = self.evaluate_input("Envelope Multiply")
+        eb.head_radius           = self.evaluate_input("Envelope Head Radius")
+        eb.tail_radius           = self.evaluate_input("Envelope Tail Radius")
+
+        print( wrapGreen("Created Bone: ") + wrapOrange(eb.name) + wrapGreen(" in ") + wrapWhite(self.bGetParentArmature().name))
+        self.executed = True
+
+    def bFinalize(self, bContext = None):
+        import bpy
+        from mantis.drivers import MantisDriver
+        # prevAct = bContext.view_layer.objects.active
+        # bContext.view_layer.objects.active = ob
+        # bpy.ops.object.mode_set(mode='OBJECT')
+        # bContext.view_layer.objects.active = prevAct
+        #
+        #get relationship
+        # ensure we have a pose bone...
+        # set the ik parameters
+        #
+        #
+        # Don't need to bother about whatever that was
+        
+        pb = self.bGetParentArmature().pose.bones[self.bObject]
+        pb.id_properties_clear()
+        # these are kept around unless explicitly deleted.
+        from .utilities import get_node_prototype
+        np = get_node_prototype(self.signature, self.base_tree)
+        driver = None
+        do_prints=False
+        for i, inp in enumerate(np.inputs):
+            if ('Parameter' in inp.bl_idname):
+                name = inp.name
+                # print(inp.name)
+                try:
+                    value = self.evaluate_input(inp.name)
+                except KeyError as e:
+                    trace = trace_single_line(self, inp.name)
+                    if do_prints: print(trace[0][-1], trace[1])
+                    if do_prints: print (trace[0][-1].parameters)
+                    #prop = trace[0][-1].parameters[trace[1].name]
+                    raise e
+                # This may be driven, so let's do this:
+                if do_prints: print (value)
+                if (isinstance(value, tuple)):
+                    # it's either a CombineThreeBool or a CombineVector.
+                    prRed("COMITTING SUICIDE NOW!!")
+                    bpy.ops.wm.quit_blender()
+                if (isinstance(value, MantisDriver)):
+                    # the value should be the default for its socket...
+                    if do_prints: print (type(inp.default_value))
+                    type_val_map = {
+                        str:"",
+                        bool:False,
+                        int:0,
+                        float:0.0,
+                        bpy.types.bpy_prop_array:(0,0,0),
+                        }
+                    driver = value
+                    value = type_val_map[type(inp.default_value)]
+                if (value is None):
+                    prRed("This is probably not supposed to happen")
+                    value = 0
+                    raise RuntimeError("Could not set value of custom parameter")
+                    # it creates a more confusing error later sometimes, better to catch it here.
+                
+                # IMPORTANT: Is it possible for more than one driver to
+                #   come through here, and for the variable to be
+                #   overwritten?
+                    
+                #TODO important
+                #from rna_prop_ui import rna_idprop_ui_create
+                # use this ^
+                
+                # add the custom properties to the **Pose Bone**
+                pb[name] = value
+                # This is much simpler now.
+                ui_data = pb.id_properties_ui(name)
+                ui_data.update(
+                    description=inp.description,
+                    default=value,)
+                #if a number
+                for num_type in ['Float']:
+                    if num_type in inp.bl_idname:
+                        ui_data.update(
+                            min = inp.min,
+                            max = inp.max,
+                            soft_min = inp.soft_min,
+                            soft_max = inp.soft_max,)
+                            
+                for num_type in ['Int', 'Bool']:
+                    # for some reason the types don't cast implicitly
+                    if num_type in inp.bl_idname:
+                        ui_data.update(
+                            min = int(inp.min),
+                            max = int(inp.max),
+                            soft_min = int(inp.soft_min),
+                            soft_max = int(inp.soft_max),)
+                # Doesn't seem to work?
+                #pb.property_overridable_library_set("["+name+"]", True)
+        # Set up IK settings, these belong to the pose bone.
+        if (pb.is_in_ik_chain): # cool!
+            pb. ik_stretch = self.evaluate_input("IK Stretch")
+            lock           = self.evaluate_input("Lock IK")
+            stiffness      = self.evaluate_input("IK Stiffness")
+            limit          = self.evaluate_input("Limit IK")
+            pb.ik_min_x    = self.evaluate_input("X Min")
+            pb.ik_max_x    = self.evaluate_input("X Max")
+            pb.ik_min_y    = self.evaluate_input("Y Min")
+            pb.ik_max_y    = self.evaluate_input("Y Max")
+            pb.ik_min_z    = self.evaluate_input("Z Min")
+            pb.ik_max_z    = self.evaluate_input("Z Max")
+            pb.ik_stiffness_x  = stiffness[0]
+            pb.ik_stiffness_y  = stiffness[1]
+            pb.ik_stiffness_z  = stiffness[2]
+            pb.lock_ik_x       = lock[0]
+            pb.lock_ik_y       = lock[1]
+            pb.lock_ik_z       = lock[2]
+            pb. use_ik_limit_x = limit[0]
+            pb.use_ik_limit_y  = limit[1]
+            pb.use_ik_limit_z  = limit[2]
+        # time to set up drivers!
+        if (driver):
+            pass
+        #
+        # OK, visual settings
+        #
+        # Get the override xform's bone:
+        
+        
+        if len(self.inputs["Custom Object xForm Override"].links) > 0:
+            trace = trace_single_line(self, "Custom Object xForm Override")
+            try:
+                pb.custom_shape_transform = trace[0][1].bGetObject()
+            except AttributeError:
+                pass
+                    
+        if len(self.inputs["Custom Object"].links) > 0:
+            trace = trace_single_line(self, "Custom Object")
+            try:
+                ob = trace[0][1].bGetObject()
+            except AttributeError:
+                ob=None
+            if type(ob) in [bpy.types.Object]:
+                pb.custom_shape = ob
+        #
+        pb.bone.hide = self.evaluate_input("Hide")
+        pb.custom_shape_scale_xyz = self.evaluate_input("Custom Object Scale")
+        pb.custom_shape_translation = self.evaluate_input("Custom Object Translation")
+        pb.custom_shape_rotation_euler = self.evaluate_input("Custom Object Rotation")
+        pb.use_custom_shape_bone_size = self.evaluate_input("Custom Object Scale to Bone Length")
+        pb.bone.show_wire = self.evaluate_input("Custom Object Wireframe")
+        #
+        # Bone Groups
+        if bg_name := self.evaluate_input("Bone Group"): # this is a string
+            obArm = self.bGetParentArmature()
+            # Temporary! Temporary! HACK
+            color_set_items= [
+                               "DEFAULT",
+                               "THEME01",
+                               "THEME02",
+                               "THEME03",
+                               "THEME04",
+                               "THEME05",
+                               "THEME06",
+                               "THEME07",
+                               "THEME08",
+                               "THEME09",
+                               "THEME10",
+                               "THEME11",
+                               "THEME12",
+                               "THEME13",
+                               "THEME14",
+                               "THEME15",
+                               "THEME16",
+                               "THEME17",
+                               "THEME18",
+                               "THEME19",
+                               "THEME20",
+                               # "CUSTOM",
+                             ]
+            try:
+                bg = obArm.pose.bone_groups.get(bg_name)
+            except SystemError:
+                bg = None
+                pass # no clue why this happens. uninitialzied?
+            if not bg:
+                bg = obArm.pose.bone_groups.new(name=bg_name)
+                #HACK lol
+                from random import randint
+                bg.color_set = color_set_items[randint(0,14)]
+                #15-20 are black by default, gross
+                # this is good enough for now!
+            
+            pb.bone_group = bg
+            
+        
+        
+
+    def bGetObject(self, mode = 'POSE'):
+        try:
+            if   (mode == 'EDIT'):
+                return self.bGetParentArmature().data.edit_bones[self.bObject]
+            elif (mode == 'OBJECT'):
+                return self.bGetParentArmature().data.bones[self.bObject]
+            elif (mode == 'POSE'):
+                return self.bGetParentArmature().pose.bones[self.bObject]
+        except Exception as e:
+            prRed ("Cannot get bone for %s" % self)
+            raise e
+
+
+
+class xFormGeometryObject:
+    '''A node representing an armature object'''
+
+    bObject = None
+
+    def __init__(self, signature, base_tree):
+        self.base_tree=base_tree
+        self.executed = False
+        self.signature = signature
+        self.inputs = {
+          "Name"           : NodeSocket(is_input = True, name = "Name", node = self),
+          "Geometry" : NodeSocket(is_input = True, name = "Geometry", node = self),
+          "Matrix"         : NodeSocket(is_input = True, name = "Matrix", node = self),
+          "Relationship"   : NodeSocket(is_input = True, name = "Relationship", node = self),
+        }
+        self.outputs = {
+          "xForm Out" : NodeSocket(is_input = False, name="xForm Out", node = self), }
+        self.parameters = {
+          "Name":None, 
+          "Geometry":None, 
+          "Matrix":None, 
+          "Relationship":None, 
+        }
+        self.links = {} # leave this empty for now!
+        # now set up the traverse target...
+        self.inputs["Relationship"].set_traverse_target(self.outputs["xForm Out"])
+        self.outputs["xForm Out"].set_traverse_target(self.inputs["Relationship"])
+        self.node_type = "XFORM"
+        self.bObject = None
+
+    def bSetParent(self, ob):
+        from bpy.types import Object, Bone
+        parent_nc = get_parent(self, type='LINK')
+        if (parent_nc):
+            parent = parent_nc.inputs['Parent'].links[0].from_node
+            parent_bOb = parent.bGetObject(mode = 'EDIT')
+            if isinstance(parent_bOb, Bone):
+                armOb= parent.bGetParentArmature()
+                ob.parent = armOb
+                ob.parent_type = 'BONE'
+                ob.parent_bone = parent_bOb.name
+            elif isinstance(parent, Object):
+                ob.parent = parent
+            # blender will do the matrix math for me IF I set the world
+            #   matrix after setting the parent.
+            #
+            # deal with parenting settings here, if necesary
+        
+    def evaluate_input(self, input_name):
+        return evaluate_input(self, input_name)
+
+    def bExecute(self, bContext = None,):
+        import bpy
+        self.bObject = bpy.data.objects.get(self.evaluate_input("Name"))
+        if not self.bObject:
+            trace = trace_single_line(self, "Geometry")
+            if trace[-1]:
+                self.bObject = bpy.data.objects.new(self.evaluate_input("Name"), trace[-1].node.bGetObject())
+        else: # clear it
+            self.bObject.constraints.clear()
+            # Don't clear this, we want to be able to reuse this
+            # self.bObject.vertex_groups.clear()
+            #
+            # This is probably also not what we want - but for now it is OK
+            self.bObject.modifiers.clear()
+        try:
+            bpy.context.collection.objects.link(self.bObject)
+        except RuntimeError: #already in; but a dangerous thing to pass.
+            pass
+        
+        self.bSetParent(self.bObject)
+        self.bObject.matrix_world = self.evaluate_input("Matrix")
+            
+    def bFinalize(self, bContext = None):
+        pass
+        
+    def bGetObject(self, mode = 'POSE'):
+        return self.bObject
+
+    def __repr__(self):
+        return self.signature.__repr__()
+
+    def fill_parameters(self):
+        fill_parameters(self)

+ 268 - 0
xForm_definitions.py

@@ -0,0 +1,268 @@
+import bpy
+from .base_definitions import xFormNode
+from bpy.types import Node
+from mantis.utilities import (prRed, prGreen, prPurple, prWhite,
+                              prOrange,
+                              wrapRed, wrapGreen, wrapPurple, wrapWhite,
+                              wrapOrange,)
+from .base_definitions import get_signature_from_edited_tree
+
+
+def TellClasses():
+    return [
+        # xFormNullNode,
+        xFormBoneNode,
+        xFormRootNode,
+        xFormArmatureNode,
+        xFormGeometryObjectNode,
+        ]
+
+def default_traverse(self, socket):
+    if (socket == self.outputs["xForm Out"]):
+        return self.inputs["Relationship"]
+    if (socket == self.inputs["Relationship"]):
+        return self.outputs["xForm Out"]
+    return None
+
+# Representing an Empty or non-armature-Object
+# class xFormNullNode(Node, xFormNode):
+#     '''A node representing a Null node'''
+#     bl_idname = 'xFormNullNode'
+#     bl_label = "Null"
+#     bl_icon = 'EMPTY_AXIS'
+
+#     # === Optional Functions ===
+#     def init(self, context):
+#         self.inputs.new('StringSocket', "Name")
+#         self.inputs.new('RelationshipSocket', "Relationship")
+#         self.inputs.new('RotationOrderSocket', "Rotation Order")
+#         self.inputs.new('MatrixSocket', "Matrix")
+#         self.outputs.new('xFormSocket', "xForm Out")
+
+
+def check_if_connected(start, end, line):
+    started=False
+    for path_nc in line:
+        prWhite("    ", path_nc.signature)
+        if path_nc.signature == start.signature:
+            started = True
+        elif path_nc.signature == end.signature:
+            break
+        if started:
+            if path_nc.inputs.get("Connected"):
+                if path_nc.evaluate_input("Connected") == False:
+                    return False
+    else:
+        return False
+    return True
+
+class xFormRootNode(Node, xFormNode):
+    '''A node representing the world node'''
+    bl_idname = 'xFormRootNode'
+    bl_label = "World Root"
+    bl_icon = 'WORLD'
+
+    def init(self, context):
+        self.outputs.new('RelationshipSocket', "World Out")
+        
+class xFormBoneNode(Node, xFormNode):
+    '''A node representing a Bone'''
+    bl_idname = 'xFormBoneNode'
+    bl_label = "Bone"
+    bl_icon = 'BONE_DATA'
+    
+    display_ik_settings : bpy.props.BoolProperty(default=False)
+    display_vp_settings : bpy.props.BoolProperty(default=False)
+    display_def_settings : bpy.props.BoolProperty(default=False)
+    socket_count : bpy.props.IntProperty()
+    
+    def init(self, context):
+        self.inputs.new('StringSocket', "Name")
+        self.inputs.new('RotationOrderSocket', "Rotation Order")
+        self.inputs.new('RelationshipSocket', "Relationship")
+        self.inputs.new('MatrixSocket', "Matrix")
+
+        # IK SETTINGS
+        a = []
+        # a.append(self.inputs.new ('LabelSocket', "IK Settings"))
+        a.append(self.inputs.new ('FloatFactorSocket', "IK Stretch"))
+        a.append(self.inputs.new ('BooleanThreeTupleSocket', "Lock IK"))
+        a.append(self.inputs.new ('NodeSocketVector', "IK Stiffness"))
+        a.append(self.inputs.new ('BooleanThreeTupleSocket', "Limit IK"))
+        a.append(self.inputs.new ('NodeSocketFloatAngle', "X Min"))
+        a.append(self.inputs.new ('NodeSocketFloatAngle', "X Max"))
+        a.append(self.inputs.new ('NodeSocketFloatAngle', "Y Min"))
+        a.append(self.inputs.new ('NodeSocketFloatAngle', "Y Max"))
+        a.append(self.inputs.new ('NodeSocketFloatAngle', "Z Min"))
+        a.append(self.inputs.new ('NodeSocketFloatAngle', "Z Max"))
+        #4-14
+        
+        # visual settings:
+        b = []
+        b.append(self.inputs.new ('LayerMaskSocket', "Layer Mask"))
+        b.append(self.inputs.new ('xFormSocket', "Custom Object"))
+        b.append(self.inputs.new ('xFormSocket', "Custom Object xForm Override"))
+        b.append(self.inputs.new ('BooleanSocket', "Custom Object Scale to Bone Length"))
+        b.append(self.inputs.new ('BooleanSocket', "Custom Object Wireframe"))
+        b.append(self.inputs.new ('VectorScaleSocket', "Custom Object Scale"))
+        b.append(self.inputs.new ('VectorSocket', "Custom Object Translation"))
+        b.append(self.inputs.new ('VectorEulerSocket', "Custom Object Rotation"))
+        b.append(self.inputs.new ('StringSocket', "Bone Group"))
+        # 16-22
+        # Deform Settings:
+        c = []
+        c.append(self.inputs.new ('BooleanSocket', "Deform"))
+        c.append(self.inputs.new ('FloatPositiveSocket', "Envelope Distance"))
+        c.append(self.inputs.new ('FloatFactorSocket',   "Envelope Weight"))
+        c.append(self.inputs.new ('BooleanSocket', "Envelope Multiply"))
+        c.append(self.inputs.new ('FloatPositiveSocket', "Envelope Head Radius"))
+        c.append(self.inputs.new ('FloatPositiveSocket', "Envelope Tail Radius"))
+        #24-28
+        
+        # c[0].default_value=False
+        
+        # Hide should be last
+        b.append(self.inputs.new ('HideSocket',   "Hide"))
+    
+        
+        for sock in a:
+            sock.hide = True
+        for sock in b:
+            if sock.name in ['Custom Object', 'Layer Mask']:
+                continue
+            sock.hide = True
+        for sock in c:
+            if sock.name == 'Deform':
+                continue
+            sock.hide = True
+            
+        # Thinking about using colors for nodes, why not?
+        # cxForm          = (0.443137, 0.242157, 0.188235,) #could even fetch the theme colors...
+        # self.color=cxForm
+        # self.use_custom_color=True
+        self.socket_count = len(self.inputs)
+        #
+        self.outputs.new('xFormSocket', "xForm Out")
+    
+    def draw_buttons(self, context, layout):
+        layout.operator("mantis.add_custom_property", text='+Add Custom Parameter')
+        # layout.label(text="Edit Parameter ... not implemented")
+        if (len(self.inputs) > self.socket_count):
+            layout.operator("mantis.remove_custom_property", text='-Remove Custom Parameter')
+        else:
+            layout.label(text="")
+        
+    def display_update(self, parsed_tree, context):
+        if context.space_data:
+            node_tree = context.space_data.path[0].node_tree
+            nc = parsed_tree.get(get_signature_from_edited_tree(self, context))
+            other_nc = None
+            if len(self.inputs.get("Relationship").links)>0:
+                prev_node = self.inputs.get("Relationship").links[0].from_node
+                if prev_node:
+                    other_nc = parsed_tree.get(get_signature_from_edited_tree(prev_node, context))
+            
+            if nc and other_nc:
+                self.display_vp_settings = nc.inputs["Custom Object"].is_connected
+                self.display_def_settings = nc.evaluate_input("Deform")
+                self.display_ik_settings = False
+                #
+                from .node_container_common import ( trace_all_lines_up,
+                                                     trace_single_line)
+                trace = trace_all_lines_up(nc, "xForm Out")
+                
+                for key in trace.keys():
+                    if (ik_nc:= parsed_tree.get(key)):
+                        if ik_nc.__class__.__name__ in ["LinkInverseKinematics"]:
+                            # if the tree is invalid? This shouldn't be necessary.
+                            if ik_nc.inputs["Input Relationship"].is_connected:
+                                chain_count = ik_nc.evaluate_input("Chain Length")
+                                if chain_count == 0:
+                                    self.display_ik_settings = True
+                                else:
+                                    if ik_nc.evaluate_input("Use Tail") == False:
+                                        chain_count+=1
+                                    for line in trace[key]:
+                                        # preprocess it to get rid of non-xForms:
+                                        xForm_line=[]
+                                        for path_nc in line:
+                                            if path_nc == ik_nc:
+                                                if ik_nc.inputs["Input Relationship"].links[0].from_node != prev_path_nc:
+                                                    break # not a constraint connection
+                                            if path_nc.node_type == 'XFORM':
+                                                xForm_line.append(path_nc)
+                                            prev_path_nc = path_nc
+                                            
+                                        else:
+                                            if len(xForm_line) < chain_count:
+                                                self.display_ik_settings = True
+                
+                inp = nc.inputs["Relationship"]
+                link = None
+                if inp.is_connected:
+                    link = inp.links[0]
+                while(link):
+                    if link.from_node.__class__.__name__ in ["LinkInverseKinematics"]:
+                        self.display_ik_settings = link.from_node.evaluate_input("Use Tail")
+                        break
+                    inp = link.from_node.outputs[link.from_socket]
+                    inp = inp.traverse_target
+                    if not inp:
+                        break
+                    if inp.links:
+                        link = inp.links[0]
+                    else:
+                        link = None
+            #
+            if self.display_ik_settings == True:
+                for inp in self.inputs[4:14]:
+                    inp.hide = False
+            else:
+                for inp in self.inputs[4:14]:
+                    inp.hide = True
+            if self.display_vp_settings == True:
+                for inp in self.inputs[16:22]:
+                    inp.hide = False
+            else:
+                for inp in self.inputs[16:22]:
+                    inp.hide = True
+            #
+            if self.display_def_settings == True:
+                for inp in self.inputs[24:29]:
+                    inp.hide = False
+            else:
+                for inp in self.inputs[24:29]:
+                    inp.hide = True
+        
+    
+    # def copy(ectype, archtype):
+        # # TODO: automatically avoid duplicating names
+        # ectype.inputs["Name"].default_value = ""
+
+
+class xFormArmatureNode(Node, xFormNode):
+    '''A node representing an Armature object node'''
+    bl_idname = 'xFormArmatureNode'
+    bl_label = "Armature"
+    bl_icon = 'OUTLINER_OB_ARMATURE'
+
+    def init(self, context):
+        self.inputs.new('StringSocket', "Name")
+        self.inputs.new('RelationshipSocket', "Relationship")
+        self.inputs.new('RotationOrderSocket', "Rotation Order")
+        self.inputs.new('MatrixSocket', "Matrix")
+        self.outputs.new('xFormSocket', "xForm Out")
+
+
+class xFormGeometryObjectNode(Node, xFormNode):
+    """Represents a curve or mesh object."""
+    bl_idname = "xFormGeometryObject"
+    bl_label = "Geometry Object"
+    bl_icon = "EMPTY_AXIS"
+    
+    def init(self, context):
+        self.inputs.new('StringSocket', "Name")
+        self.inputs.new('GeometrySocket', "Geometry")
+        self.inputs.new('MatrixSocket', "Matrix")
+        self.inputs.new('RelationshipSocket', "Relationship")
+        self.outputs.new('xFormSocket', "xForm Out")