Browse Source

Update to Blender 4.2 API and Blender Extension format

Joseph Brandenburg 1 year ago
parent
commit
dd451fe156

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+*__pycache__*
+grandalf/*

+ 24 - 35
__init__.py

@@ -1,16 +1,3 @@
-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,
 from . import ( ops_nodegroup,
                 base_definitions,
                 base_definitions,
                 socket_definitions,
                 socket_definitions,
@@ -20,7 +7,7 @@ from . import ( ops_nodegroup,
                 primitives_definitions,
                 primitives_definitions,
                 deformer_definitions,
                 deformer_definitions,
               )
               )
-from mantis.ops_generate_tree import CreateMantisTree
+from .ops_generate_tree import CreateMantisTree
 from bpy.types import NodeSocket
 from bpy.types import NodeSocket
 
 
 
 
@@ -43,27 +30,29 @@ while (classLists):
     classes.extend(classLists.pop())
     classes.extend(classLists.pop())
 
 
 interface_classes = []
 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)
+from bpy import app
+if app.version[0]  == 3:
+    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
 import nodeitems_utils
 from nodeitems_utils import NodeCategory, NodeItem
 from nodeitems_utils import NodeCategory, NodeItem

+ 77 - 50
base_definitions.py

@@ -4,7 +4,7 @@ from bpy.props import BoolProperty, StringProperty, EnumProperty, CollectionProp
 from . import ops_nodegroup
 from . import ops_nodegroup
 from bpy.types import NodeTree, Node, PropertyGroup, Operator, UIList, Panel
 from bpy.types import NodeTree, Node, PropertyGroup, Operator, UIList, Panel
 
 
-from mantis.utilities import (prRed, prGreen, prPurple, prWhite,
+from .utilities import (prRed, prGreen, prPurple, prWhite,
                               prOrange,
                               prOrange,
                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
                               wrapOrange,)
                               wrapOrange,)
@@ -44,14 +44,14 @@ class MantisTree(NodeTree):
         @classmethod
         @classmethod
         def valid_socket_type(cls, socket_type: str):
         def valid_socket_type(cls, socket_type: str):
             # https://docs.blender.org/api/master/bpy.types.NodeTree.html#bpy.types.NodeTree.valid_socket_type
             # https://docs.blender.org/api/master/bpy.types.NodeTree.html#bpy.types.NodeTree.valid_socket_type
-            from mantis.socket_definitions import Tell_bl_idnames
+            from .socket_definitions import Tell_bl_idnames
             return socket_type in Tell_bl_idnames()
             return socket_type in Tell_bl_idnames()
             # thank you, Sverchok
             # thank you, Sverchok
             
             
     def update_tree(self, context):
     def update_tree(self, context):
         if self.do_live_update == False:
         if self.do_live_update == False:
             return
             return
-        from mantis import readtree
+        from . import readtree
         prGreen("Validating Tree: %s" % self.name)
         prGreen("Validating Tree: %s" % self.name)
         parsed_tree = readtree.parse_tree(self)
         parsed_tree = readtree.parse_tree(self)
         self.parsed_tree=parsed_tree
         self.parsed_tree=parsed_tree
@@ -75,7 +75,7 @@ class MantisTree(NodeTree):
     
     
     def execute_tree(self,context):
     def execute_tree(self,context):
         prGreen("Executing Tree: %s" % self.name)
         prGreen("Executing Tree: %s" % self.name)
-        from mantis import readtree
+        from . import readtree
         readtree.execute_tree(self.parsed_tree, self, context)
         readtree.execute_tree(self.parsed_tree, self, context)
 
 
     
     
@@ -92,7 +92,7 @@ def update_handler(scene):
             if prev_links != node_tree.num_links:
             if prev_links != node_tree.num_links:
                 node_tree.tree_valid = False
                 node_tree.tree_valid = False
             if node_tree.tree_valid == False:
             if node_tree.tree_valid == False:
-                from mantis import readtree
+                from . import readtree
                 node_tree.update_tree(context)
                 node_tree.update_tree(context)
 
 
 def execute_handler(scene):
 def execute_handler(scene):
@@ -106,7 +106,7 @@ def execute_handler(scene):
 # bpy.app.handlers.load_post.append(set_tree_invalid)
 # bpy.app.handlers.load_post.append(set_tree_invalid)
 bpy.app.handlers.depsgraph_update_pre.append(update_handler)
 bpy.app.handlers.depsgraph_update_pre.append(update_handler)
 bpy.app.handlers.depsgraph_update_post.append(execute_handler)
 bpy.app.handlers.depsgraph_update_post.append(execute_handler)
-    
+
 
 
 class MantisNode:
 class MantisNode:
     num_links:IntProperty(default=-1)
     num_links:IntProperty(default=-1)
@@ -119,7 +119,7 @@ class MantisNode:
         context = bpy.context
         context = bpy.context
         if context.space_data:
         if context.space_data:
             node_tree = context.space_data.path[0].node_tree
             node_tree = context.space_data.path[0].node_tree
-            from mantis import readtree
+            from . import readtree
             prOrange("Updating from insert_link callback")
             prOrange("Updating from insert_link callback")
             node_tree.update_tree(context)
             node_tree.update_tree(context)
             if (link.to_socket.is_linked == False):
             if (link.to_socket.is_linked == False):
@@ -150,10 +150,13 @@ class DeformerNode(MantisNode):
 
 
 from bpy.types import NodeCustomGroup
 from bpy.types import NodeCustomGroup
 # TODO: make this one's traverse() function actually work
 # TODO: make this one's traverse() function actually work
+def poll_node_tree(self, tree):
+    return True #TODO: prevent circular group ofc
 class MantisNodeGroup(NodeCustomGroup, MantisNode):
 class MantisNodeGroup(NodeCustomGroup, MantisNode):
     bl_idname = "MantisNodeGroup"
     bl_idname = "MantisNodeGroup"
     bl_label = "Node Group"
     bl_label = "Node Group"
     
     
+    node_tree_updater : bpy.props.PointerProperty(type=bpy.types.NodeTree, poll=poll_node_tree)
     # def poll_node_tree(self, object):
     # def poll_node_tree(self, object):
         # if object.bl_idname not in "MantisTree":
         # if object.bl_idname not in "MantisTree":
             # return False
             # return False
@@ -165,55 +168,79 @@ class MantisNodeGroup(NodeCustomGroup, MantisNode):
                 # return False
                 # return False
     # node_tree:bpy.props.PointerProperty(type=bpy.types.NodeTree, poll=poll_node_tree)
     # node_tree:bpy.props.PointerProperty(type=bpy.types.NodeTree, poll=poll_node_tree)
     
     
-    def init(self, context):
-        pass
+    # def init(self, context):
+        # pass
+        
+        
+    def socket_value_update(self, context):
+        prGreen("updating...")
+    
+    
+    # this is a total HACK
+    def update(self):
+        if self.node_tree_updater is not self.node_tree:
+            self.update_node_tree()
+            self.node_tree_updater = self.node_tree
+    
+    
+    def update_node_tree(self):
+        self.inputs.clear()
+        self.outputs.clear()
+        for item in self.node_tree.interface.items_tree:
+            if item.item_type != "SOCKET":
+                continue
+            s = None
+            if item.in_out == 'OUTPUT':
+                s = self.outputs.new(type=item.socket_type, name=item.name, identifier=item.identifier)
+            else:
+                s = self.inputs.new(type=item.socket_type, name=item.name, identifier=item.identifier)
     
     
     def draw_buttons(self, context, layout):
     def draw_buttons(self, context, layout):
         row = layout.row(align=True)
         row = layout.row(align=True)
         row.prop(self, "node_tree", text="")
         row.prop(self, "node_tree", text="")
         row.operator("mantis.edit_group", text="", icon='NODETREE', emboss=True)
         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
+# # 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):
 class CircularDependencyError(Exception):
     pass
     pass

+ 74 - 0
blender_manifest.toml

@@ -0,0 +1,74 @@
+schema_version = "1.0.0"
+
+# Example of manifest file for a Blender extension
+# Change the values according to your extension
+id = "mantis"
+version = "0.0.0"
+name = "Mantis"
+tagline = "Rigging Nodes"
+maintainer = "Nodespaghetti <josephbburg@protonmail.com>"
+# Supported types: "add-on", "theme"
+type = "add-on"
+
+# Optional link to documentation, support, source files, etc
+# website = "https://extensions.blender.org/add-ons/my-example-package/"
+
+# Optional list defined by Blender and server, see:
+# https://docs.blender.org/manual/en/dev/advanced/extensions/tags.html
+tags = ["Rigging", "Node"]
+
+blender_version_min = "4.2.0"
+# # Optional: Blender version that the extension does not support, earlier versions are supported.
+# # This can be omitted and defined later on the extensions platform if an issue is found.
+# blender_version_max = "5.1.0"
+
+# License conforming to https://spdx.org/licenses/ (use "SPDX: prefix)
+# https://docs.blender.org/manual/en/dev/advanced/extensions/licenses.html
+license = [
+  "SPDX:AGPL-3.0-or-later",
+]
+# Optional: required by some licenses.
+# copyright = [
+#   "2002-2024 Developer Name",
+#   "1998 Company Name",
+# ]
+
+# Optional list of supported platforms. If omitted, the extension will be available in all operating systems.
+# platforms = ["windows-x64", "macos-arm64", "linux-x64"]
+platforms = ["linux-x64"]
+# Other supported platforms: "windows-arm64", "macos-x64"
+
+# Optional: bundle 3rd party Python modules.
+# https://docs.blender.org/manual/en/dev/advanced/extensions/python_wheels.html
+# wheels = [
+#   "./wheels/hexdump-3.3-py3-none-any.whl",
+#   "./wheels/jsmin-3.0.1-py3-none-any.whl",
+# ]
+
+# # Optional: add-ons can list which resources they will require:
+# # * files (for access of any filesystem operations)
+# # * network (for internet access)
+# # * clipboard (to read and/or write the system clipboard)
+# # * camera (to capture photos and videos)
+# # * microphone (to capture audio)
+# #
+# # If using network, remember to also check `bpy.app.online_access`
+# # https://docs.blender.org/manual/en/dev/advanced/extensions/addons.html#internet-access
+# #
+# # For each permission it is important to also specify the reason why it is required.
+# # Keep this a single short sentence without a period (.) at the end.
+# # For longer explanations use the documentation or detail page.
+#
+# [permissions]
+# network = "Need to sync motion-capture data to server"
+# files = "Import/export FBX from/to disk"
+# clipboard = "Copy and paste bone transforms"
+
+# Optional: build settings.
+# https://docs.blender.org/manual/en/dev/advanced/extensions/command_line_arguments.html#command-line-args-extension-build
+# [build]
+# paths_exclude_pattern = [
+#   "__pycache__/",
+#   "/.git/",
+#   "/*.zip",
+# ]

+ 14 - 15
deformer_containers.py

@@ -1,5 +1,5 @@
-from mantis.node_container_common import *
-from mantis.xForm_containers import xFormGeometryObject
+from .node_container_common import *
+from .xForm_containers import xFormGeometryObject
 from bpy.types import Node
 from bpy.types import Node
 from .base_definitions import MantisNode
 from .base_definitions import MantisNode
 
 
@@ -127,19 +127,18 @@ class DeformerArmature:
                                   'active_pose_bone':deform_bones[0],
                                   'active_pose_bone':deform_bones[0],
                                   'selected_pose_bones':deform_bones,}
                                   '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')
+            with bContext.temp_override(**{'active_object':armOb}):
+                bpy.ops.object.mode_set(mode='POSE')
+                bpy.ops.pose.select_all(action='SELECT')
+            with bContext.temp_override(**context_override):
+                bpy.ops.paint.weight_paint_toggle()
+                bpy.ops.paint.weight_from_bones(type='AUTOMATIC')
+                bpy.ops.paint.weight_paint_toggle()
+                #
+            with bContext.temp_override(**{'active_object':armOb}):
+                bpy.ops.object.mode_set(mode='POSE')
+                bpy.ops.pose.select_all(action='DESELECT') 
+                bpy.ops.object.mode_set(mode='OBJECT')
             # TODO: modify Blender to make this available as a Python API function.
             # TODO: modify Blender to make this available as a Python API function.
         if skin_method == "EXISTING_GROUPS":
         if skin_method == "EXISTING_GROUPS":
             self.initialize_vgroups(use_existing = True)
             self.initialize_vgroups(use_existing = True)

+ 1 - 1
drivers.py

@@ -1,4 +1,4 @@
-from mantis.utilities import (prRed, prGreen, prPurple, prWhite,
+from .utilities import (prRed, prGreen, prPurple, prWhite,
                               prOrange,
                               prOrange,
                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
                               wrapOrange,)
                               wrapOrange,)

+ 39 - 0
i_o.py

@@ -0,0 +1,39 @@
+# this is the I/O part of mantis. I eventually intend to make this a markup language. not right now tho lol
+
+# https://stackoverflow.com/questions/42033142/is-there-an-easy-way-to-check-if-an-object-is-json-serializable-in-python
+# thanks!
+def is_jsonable(x):
+    import json
+    try:
+        json.dumps(x)
+        return True
+    except (TypeError, OverflowError):
+        return False
+
+
+
+def export_to_json(tree, path):
+    # takes a parsed mantis tree and returns a JSON
+    tree_data = tree.parsed_tree
+
+    # we have to prepare the tree data.
+    #   SO: we can't use tuples as keys in JSON
+    #   but the tree uses a lot of tuple keys in Python dicts
+    #   so we can map the tuples to UUIDs instead
+    #  then we can store the list of nodes mapped to the same UUIDs
+    # the goal is to store the tree in a way that makes reproduction possible
+
+
+    import json
+    with open(path, "w") as file:
+        print("Writing mantis tree data to: ", file.name)
+        file.write( json.dumps(json_data, indent = 4) )
+    
+
+def import_from_json(json_data):
+    #reads a JSON file and makes a tree from it.
+    pass
+
+def tree_to_b_nodes(tree):
+    # creates a node tree in Blender from a mantis tree
+    pass

+ 2 - 2
link_containers.py

@@ -1,4 +1,4 @@
-from mantis.node_container_common import *
+from .node_container_common import *
 from bpy.types import Node
 from bpy.types import Node
 from .base_definitions import MantisNode, GraphError
 from .base_definitions import MantisNode, GraphError
 
 
@@ -1440,7 +1440,7 @@ class LinkDrivenParameter:
     def bFinalize(self, bContext = None):
     def bFinalize(self, bContext = None):
         # TODO HACK BUG
         # TODO HACK BUG
         # This probably no longer works
         # This probably no longer works
-        from mantis.drivers import CreateDrivers
+        from .drivers import CreateDrivers
         CreateDrivers( [ self.parameters["Driver"] ] )
         CreateDrivers( [ self.parameters["Driver"] ] )
         
         
     def __repr__(self):
     def __repr__(self):

+ 1 - 1
link_definitions.py

@@ -1,7 +1,7 @@
 import bpy
 import bpy
 from bpy.types import NodeTree, Node, NodeSocket
 from bpy.types import NodeTree, Node, NodeSocket
 from .base_definitions import MantisNode, LinkNode, GraphError
 from .base_definitions import MantisNode, LinkNode, GraphError
-from mantis.utilities import (prRed, prGreen, prPurple, prWhite,
+from .utilities import (prRed, prGreen, prPurple, prWhite,
                               prOrange,
                               prOrange,
                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
                               wrapOrange,)
                               wrapOrange,)

+ 7 - 7
misc_containers.py

@@ -1,8 +1,8 @@
-from mantis.node_container_common import *
+from .node_container_common import *
 
 
 # The fact that I need this means that some of these classes should
 # The fact that I need this means that some of these classes should
 #  probably be moved to link_containers.py
 #  probably be moved to link_containers.py
-from mantis.xForm_containers import xFormRoot, xFormArmature, xFormBone
+from .xForm_containers import xFormRoot, xFormArmature, xFormBone
 
 
 def TellClasses():
 def TellClasses():
     return [
     return [
@@ -521,7 +521,7 @@ class UtilityFCurve:
 
 
     def bExecute(self, bContext = None,):
     def bExecute(self, bContext = None,):
         prepare_parameters(self)
         prepare_parameters(self)
-        from mantis.utilities import get_node_prototype
+        from .utilities import get_node_prototype
         np = get_node_prototype(self.signature, self.base_tree)
         np = get_node_prototype(self.signature, self.base_tree)
         keys = []
         keys = []
         #['amplitude', 'back', 'bl_rna', 'co', 'co_ui', 'easing', 'handle_left', 'handle_left_type', 'handle_right', 'handle_right_type',
         #['amplitude', 'back', 'bl_rna', 'co', 'co_ui', 'easing', 'handle_left', 'handle_left_type', 'handle_right', 'handle_right_type',
@@ -569,7 +569,7 @@ class UtilityDriver:
         self.outputs = {
         self.outputs = {
           "Driver" : NodeSocket(name = "Driver", node=self),
           "Driver" : NodeSocket(name = "Driver", node=self),
         }
         }
-        from mantis.drivers import MantisDriver
+        from .drivers import MantisDriver
         self.parameters = {
         self.parameters = {
           "Driver Type":None, 
           "Driver Type":None, 
           "Expression":None, 
           "Expression":None, 
@@ -584,7 +584,7 @@ class UtilityDriver:
 
 
     def bExecute(self, bContext = None,):
     def bExecute(self, bContext = None,):
         prepare_parameters(self)
         prepare_parameters(self)
-        from mantis.drivers import MantisDriver
+        from .drivers import MantisDriver
         #prPurple("Executing Driver Node")
         #prPurple("Executing Driver Node")
         my_vars = []
         my_vars = []
         
         
@@ -629,7 +629,7 @@ class UtilitySwitch:
         self.outputs = {
         self.outputs = {
           "Driver" : NodeSocket(name = "Driver", node=self),
           "Driver" : NodeSocket(name = "Driver", node=self),
         }
         }
-        from mantis.drivers import MantisDriver
+        from .drivers import MantisDriver
         self.parameters = {
         self.parameters = {
           "xForm":None, 
           "xForm":None, 
           "Parameter":None,
           "Parameter":None,
@@ -661,7 +661,7 @@ class UtilitySwitch:
         if xForm : xForm = xForm.bGetObject() 
         if xForm : xForm = xForm.bGetObject() 
         if not xForm:
         if not xForm:
             raise RuntimeError("Could not evaluate xForm for %s" % self)
             raise RuntimeError("Could not evaluate xForm for %s" % self)
-        from mantis.drivers import MantisDriver
+        from .drivers import MantisDriver
         my_driver ={ "owner" : None,
         my_driver ={ "owner" : None,
                      "prop"  : None, # will be filled out in the node that uses the driver 
                      "prop"  : None, # will be filled out in the node that uses the driver 
                      "ind"   : -1, # same here
                      "ind"   : -1, # same here

+ 3 - 3
node_container_common.py

@@ -1,8 +1,8 @@
-from mantis.utilities import (prRed, prGreen, prPurple, prWhite,
+from .utilities import (prRed, prGreen, prPurple, prWhite,
                               prOrange,
                               prOrange,
                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
                               wrapOrange,)
                               wrapOrange,)
-from mantis.base_definitions import GraphError, CircularDependencyError
+from .base_definitions import GraphError, CircularDependencyError
 # BE VERY CAREFUL
 # BE VERY CAREFUL
 # the x_containers files import * from this file
 # the x_containers files import * from this file
 # so all the top-level imports are carried over
 # so all the top-level imports are carried over
@@ -396,7 +396,7 @@ def finish_drivers(self):
             drivers.append(driver)
             drivers.append(driver)
         else:
         else:
             prRed("Failed to create driver for %s" % prop)
             prRed("Failed to create driver for %s" % prop)
-    from mantis.drivers import CreateDrivers
+    from .drivers import CreateDrivers
     CreateDrivers(drivers)
     CreateDrivers(drivers)
 
 
 
 

+ 1 - 1
nodes_generic.py

@@ -426,7 +426,7 @@ class UtilityDriverNode(Node, MantisNode):
         except AttributeError:
         except AttributeError:
             proceed = False
             proceed = False
         if proceed:
         if proceed:
-            from mantis.f_nodegraph import (GetDownstreamXFormNodes, get_node_container)
+            from .f_nodegraph import (GetDownstreamXFormNodes, get_node_container)
             if (node_container := get_node_container(self, context)[0]):
             if (node_container := get_node_container(self, context)[0]):
                 dType = node_container.evaluate_input("Driver Type")
                 dType = node_container.evaluate_input("Driver Type")
             else:
             else:

+ 1 - 1
ops_generate_tree.py

@@ -1,6 +1,6 @@
 from bpy.types import Operator
 from bpy.types import Operator
 import bpy
 import bpy
-from mantis.utilities import (prRed, prGreen, prPurple, prWhite,
+from .utilities import (prRed, prGreen, prPurple, prWhite,
                               prOrange,
                               prOrange,
                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
                               wrapOrange,)
                               wrapOrange,)

+ 34 - 3
ops_nodegroup.py

@@ -24,7 +24,8 @@ def TellClasses():
         DriverRemoveDriverVariableInput,
         DriverRemoveDriverVariableInput,
         # Armature Link Node
         # Armature Link Node
         LinkArmatureAddTargetInput,
         LinkArmatureAddTargetInput,
-        LinkArmatureRemoveTargetInput,]
+        LinkArmatureRemoveTargetInput,
+        ExportNodeTreeToJSON,]
 
 
 def mantis_tree_poll_op(context):
 def mantis_tree_poll_op(context):
     # return True
     # return True
@@ -78,6 +79,8 @@ class MantisGroupNodes(Operator):
         return mantis_tree_poll_op(context)
         return mantis_tree_poll_op(context)
 
 
 
 
+# IMPORTANT TODO: re-write this because it no longer work
+
 # source is https://github.com/aachman98/Sorcar/blob/master/operators/ScGroupNodes.py
 # 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
 # checc here: https://github.com/nortikin/sverchok/blob/9002fd4af9ec8603e86f86ed7e567a4ed0d2e07c/core/node_group.py#L568
 
 
@@ -301,7 +304,7 @@ class CleanUpNodeGraph(bpy.types.Operator):
         
         
         base_tree=context.space_data.path[-1].node_tree
         base_tree=context.space_data.path[-1].node_tree
         
         
-        from mantis.grandalf.graphs import Vertex, Edge, Graph, graph_core
+        from .grandalf.graphs import Vertex, Edge, Graph, graph_core
         
         
         class defaultview(object):
         class defaultview(object):
             w,h = 1,1
             w,h = 1,1
@@ -336,7 +339,7 @@ class CleanUpNodeGraph(bpy.types.Operator):
         
         
 
 
         
         
-        from mantis.grandalf.layouts import SugiyamaLayout
+        from .grandalf.layouts import SugiyamaLayout
         
         
         
         
         sug = SugiyamaLayout(graph.C[0]) # no idea what .C[0] is
         sug = SugiyamaLayout(graph.C[0]) # no idea what .C[0] is
@@ -844,3 +847,31 @@ class LinkArmatureRemoveTargetInput(bpy.types.Operator):
         n = context.node
         n = context.node
         n.inputs.remove(n.inputs[-1]); n.inputs.remove(n.inputs[-1])
         n.inputs.remove(n.inputs[-1]); n.inputs.remove(n.inputs[-1])
         return {'FINISHED'}
         return {'FINISHED'}
+
+
+
+class ExportNodeTreeToJSON(Operator):
+    """Export this node tree as a JSON file"""
+    bl_idname = "mantis.export_node_tree_json"
+    bl_label = "Export Mantis Tree to JSON"
+
+    @classmethod
+    def poll(cls, context):
+        return (mantis_tree_poll_op(context))
+
+    def execute(self, context):
+        from .i_o import export_to_json
+        import bpy
+
+        tree = context.space_data.path[0].node_tree
+        tree.update_tree(context)
+
+        def remove_special_characters(stritree):
+            # https://stackoverflow.com/questions/295135/turn-a-stritree-into-a-valid-filename
+            # thank you user "Sophie Gage"
+            import re # regular expressions
+            return re.sub('[^\w_.)( -]', '', stritree)
+
+        path = bpy.path.abspath('//')+remove_special_characters(tree.name)+".json"
+        export_to_json(tree, path)
+        return {"FINISHED"}

+ 1 - 1
primitives_containers.py

@@ -1,4 +1,4 @@
-from mantis.node_container_common import *
+from .node_container_common import *
 from bpy.types import Node
 from bpy.types import Node
 from .base_definitions import MantisNode
 from .base_definitions import MantisNode
 
 

+ 13 - 7
readtree.py

@@ -6,7 +6,7 @@ def class_for_mantis_prototype_node(prototype_node):
     """ This is a class which returns a class to instantiate for
     """ This is a class which returns a class to instantiate for
         the given prototype node."""
         the given prototype node."""
     #from .node_container_classes import TellClasses
     #from .node_container_classes import TellClasses
-    from mantis import xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers
+    from . import xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers
     classes = {}
     classes = {}
     for module in [xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers]:
     for module in [xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers]:
         for cls in module.TellClasses():
         for cls in module.TellClasses():
@@ -121,7 +121,7 @@ def class_for_mantis_prototype_node(prototype_node):
 # This is really, really stupid HACK
 # This is really, really stupid HACK
 def gen_nc_input_for_data(socket):
 def gen_nc_input_for_data(socket):
     # Class List #TODO deduplicate
     # Class List #TODO deduplicate
-    from mantis import xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers
+    from . import xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers
     classes = {}
     classes = {}
     for module in [xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers]:
     for module in [xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers]:
         for cls in module.TellClasses():
         for cls in module.TellClasses():
@@ -288,7 +288,7 @@ def reroute_links_grpout(nc, all_nc):
 
 
 
 
 def data_from_tree(base_tree, tree_path = [None], dummy_nodes = {}, all_nc = {}):
 def data_from_tree(base_tree, tree_path = [None], dummy_nodes = {}, all_nc = {}):
-    from mantis.node_container_common import NodeSocket
+    from .node_container_common import NodeSocket
     from .internal_containers import DummyNode
     from .internal_containers import DummyNode
     nc_dict = {}
     nc_dict = {}
     tree_path_names = [tree.name for tree in tree_path if hasattr(tree, "name")]
     tree_path_names = [tree.name for tree in tree_path if hasattr(tree, "name")]
@@ -358,6 +358,8 @@ def data_from_tree(base_tree, tree_path = [None], dummy_nodes = {}, all_nc = {})
             to_s = inp.identifier
             to_s = inp.identifier
             if not inp.is_linked:
             if not inp.is_linked:
                 nc_cls = gen_nc_input_for_data(inp)
                 nc_cls = gen_nc_input_for_data(inp)
+                print (inp)
+                prRed(nc_cls)
                 # at this point we also need to get the "Dummy Node" for
                 # at this point we also need to get the "Dummy Node" for
                 #  this node group.
                 #  this node group.
                 if (nc_cls):
                 if (nc_cls):
@@ -385,6 +387,8 @@ def data_from_tree(base_tree, tree_path = [None], dummy_nodes = {}, all_nc = {})
                         sig =  tuple( [None] + tree_path_names +[from_socket.node.bl_idname])
                         sig =  tuple( [None] + tree_path_names +[from_socket.node.bl_idname])
                         from_s = from_socket.identifier
                         from_s = from_socket.identifier
                     nc_from = nc_dict.get(sig)
                     nc_from = nc_dict.get(sig)
+            # this can be None. Why?
+            prRed (sig)
             nc_from.outputs[from_s].connect(node=nc_to, socket=to_s)
             nc_from.outputs[from_s].connect(node=nc_to, socket=to_s)
             nc_from = None
             nc_from = None
         nc_to = None
         nc_to = None
@@ -492,7 +496,7 @@ to_name_filter = [
 
 
 def sort_tree_into_layers(nodes, context):
 def sort_tree_into_layers(nodes, context):
     from time import time
     from time import time
-    from mantis.node_container_common import (get_depth_lines,
+    from .node_container_common import (get_depth_lines,
       node_depth)
       node_depth)
     # All this function needs to do is sort out the hierarchy and
     # All this function needs to do is sort out the hierarchy and
     #  get things working in order of their dependencies.
     #  get things working in order of their dependencies.
@@ -565,7 +569,7 @@ def sort_tree_into_layers(nodes, context):
 def execute_tree(nodes, base_tree, context):
 def execute_tree(nodes, base_tree, context):
     import bpy
     import bpy
     from time import time
     from time import time
-    from mantis.node_container_common import GraphError
+    from .node_container_common import GraphError
     start_time  = time()
     start_time  = time()
     original_active = context.view_layer.objects.active
     original_active = context.view_layer.objects.active
     
     
@@ -600,7 +604,8 @@ def execute_tree(nodes, base_tree, context):
     # TODO it's possible but unlikely that the user will try to run a 
     # TODO it's possible but unlikely that the user will try to run a 
     #    graph with no armature nodes in it.
     #    graph with no armature nodes in it.
     if (active):
     if (active):
-        bpy.ops.object.mode_set({'active_object':active, 'selected_objects':switch_me}, mode='POSE')
+        with context.temp_override(**{'active_object':active, 'selected_objects':switch_me}):
+            bpy.ops.object.mode_set(mode='POSE')
     
     
     #               Execute second pass (Link, Driver)                 #
     #               Execute second pass (Link, Driver)                 #
     for i in range(len(layers)):
     for i in range(len(layers)):
@@ -634,7 +639,8 @@ def execute_tree(nodes, base_tree, context):
                 switch_me.append(ob)
                 switch_me.append(ob)
                 active = ob
                 active = ob
     if (active):
     if (active):
-        bpy.ops.object.mode_set({'active_object':active, 'selected_objects':switch_me}, mode='OBJECT')
+        with context.temp_override(**{'active_object':active, 'selected_objects':switch_me}):
+            bpy.ops.object.mode_set(mode='OBJECT')
     
     
     prGreen("Executed Tree in %s seconds" % (time() - start_execution_time))
     prGreen("Executed Tree in %s seconds" % (time() - start_execution_time))
     prGreen("Finished executing tree in %f seconds" % (time() - start_time))
     prGreen("Finished executing tree in %f seconds" % (time() - start_time))

+ 52 - 21
socket_definitions.py

@@ -2,7 +2,7 @@ import bpy
 from bpy.types import NodeSocket, NodeSocketStandard
 from bpy.types import NodeSocket, NodeSocketStandard
 
 
 
 
-from mantis.utilities import (prRed, prGreen, prPurple, prWhite,
+from .utilities import (prRed, prGreen, prPurple, prWhite,
                               prOrange,
                               prOrange,
                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
                               wrapOrange,)
                               wrapOrange,)
@@ -50,7 +50,7 @@ cDriverVariable = (0.66, 0.33, 0.04, 1.0)
 cFCurve         = (0.77, 0.77, 0.11, 1.0)
 cFCurve         = (0.77, 0.77, 0.11, 1.0)
 cKeyframe       = (0.06, 0.22, 0.88, 1.0)
 cKeyframe       = (0.06, 0.22, 0.88, 1.0)
 cEnable         = (0.92, 0.92, 0.92, 1.0)
 cEnable         = (0.92, 0.92, 0.92, 1.0)
-cLayerMask      = (0.82, 0.82, 0.82, 1.0)
+cBoneCollection      = (0.82, 0.82, 0.82, 1.0)
 cDeformer       = (0.05, 0.08, 0.45, 1.0)
 cDeformer       = (0.05, 0.08, 0.45, 1.0)
 
 
 
 
@@ -94,8 +94,10 @@ def TellClasses():
              DriverVariableSocket,
              DriverVariableSocket,
              FCurveSocket,
              FCurveSocket,
              KeyframeSocket,
              KeyframeSocket,
-             LayerMaskSocket,
-             LayerMaskInputSocket,
+            #  LayerMaskSocket,
+            #  LayerMaskInputSocket,
+            BoneCollectionSocket,
+            BoneCollectionInputSocket,
              
              
              xFormParameterSocket,
              xFormParameterSocket,
              ParameterBoolSocket,
              ParameterBoolSocket,
@@ -176,7 +178,11 @@ def default_update(socket, context, do_execute=True):
         return
         return
     if not hasattr(context.space_data, "path"):
     if not hasattr(context.space_data, "path"):
         return
         return
-    node_tree = context.space_data.path[0].node_tree
+    try:
+        node_tree = context.space_data.path[0].node_tree
+    except IndexError: # not in the UI, for example, in a script instead.
+        node_tree = None
+        return
     if node_tree.do_live_update:
     if node_tree.do_live_update:
         # I don't know how the tree can be valid at 0 nodes but doesn't hurt
         # I don't know how the tree can be valid at 0 nodes but doesn't hurt
         #  to force it if this somehow happens.
         #  to force it if this somehow happens.
@@ -187,7 +193,7 @@ def default_update(socket, context, do_execute=True):
         elif (node_tree.tree_valid == True):
         elif (node_tree.tree_valid == True):
             # prGreen("Partial Update From Socket Change.")
             # prGreen("Partial Update From Socket Change.")
             # We don't have to update the whole thing, just the socket
             # We don't have to update the whole thing, just the socket
-            from mantis.utilities import tree_from_nc
+            from .utilities import tree_from_nc
             for nc in node_tree.parsed_tree.values():
             for nc in node_tree.parsed_tree.values():
                 try:
                 try:
                     if (tree_from_nc(nc.signature, nc.base_tree) == socket.node.id_data):
                     if (tree_from_nc(nc.signature, nc.base_tree) == socket.node.id_data):
@@ -637,32 +643,55 @@ class StringSocket(bpy.types.NodeSocketString):
     def draw_color(self, context, node):
     def draw_color(self, context, node):
         return self.color
         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
+# 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 = cBoneCollection
+#     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 = cBoneCollection
+#     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
+
+
+class BoneCollectionSocket(bpy.types.NodeSocket):
+    """Bone Collection socket"""
+    bl_idname = 'BoneCollectionSocket'
+    bl_label = "Bone Collection"
+    default_value: bpy.props.StringProperty(default = "Collection", update = update_socket,)
     input : bpy.props.BoolProperty(default =False,)
     input : bpy.props.BoolProperty(default =False,)
+    color = cBoneCollection
     def draw(self, context, layout, node, text):
     def draw(self, context, layout, node, text):
         ChooseDraw(self, context, layout, node, text)
         ChooseDraw(self, context, layout, node, text)
     def draw_color(self, context, node):
     def draw_color(self, context, node):
         return self.color
         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
+class BoneCollectionInputSocket(bpy.types.NodeSocket):
+    """Bone Collection Input Socket"""
+    bl_idname = 'BoneCollectionInputSocket'
+    bl_label = "Bone Collection"
+    default_value: bpy.props.StringProperty(default = "Collection", update = update_socket,)
     input : bpy.props.BoolProperty(default =True,)
     input : bpy.props.BoolProperty(default =True,)
+    color = cBoneCollection
     def draw(self, context, layout, node, text):
     def draw(self, context, layout, node, text):
         ChooseDraw(self, context, layout, node, text)
         ChooseDraw(self, context, layout, node, text)
     def draw_color(self, context, node):
     def draw_color(self, context, node):
         return self.color
         return self.color
 
 
-
-
 #####################################################################################
 #####################################################################################
 # Parameters
 # Parameters
 #####################################################################################
 #####################################################################################
@@ -813,7 +842,9 @@ class EnumMetaRigSocket(NodeSocket):
     search_prop:PointerProperty(type=bpy.types.Object, poll=poll_is_armature, update=update_metarig_armature)
     search_prop:PointerProperty(type=bpy.types.Object, poll=poll_is_armature, update=update_metarig_armature)
     
     
     def get_default_value(self):
     def get_default_value(self):
-        return self.search_prop.name
+        if self.search_prop:
+            return self.search_prop.name
+        return ""
     
     
     default_value  : StringProperty(name = "", get=get_default_value)
     default_value  : StringProperty(name = "", get=get_default_value)
     
     

+ 3 - 3
utilities.py

@@ -407,9 +407,9 @@ def to_mathutils_value(socket):
             return val
             return val
         if (isinstance(socket, socket_definitions.BooleanThreeTupleSocket)):
         if (isinstance(socket, socket_definitions.BooleanThreeTupleSocket)):
             return (val[0], val[1], val[2]) # we'll send a tuple out
             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
+        # if ((isinstance(socket, socket_definitions.LayerMaskSocket)) or
+        #     (isinstance(socket, socket_definitions.LayerMaskInputSocket))):
+        #     return tuple(val) # should werk
     else:
     else:
         return None
         return None
 
 

+ 78 - 52
xForm_containers.py

@@ -1,4 +1,4 @@
-from mantis.node_container_common import *
+from .node_container_common import *
 from bpy.types import Node
 from bpy.types import Node
 from .base_definitions import MantisNode
 from .base_definitions import MantisNode
 
 
@@ -124,7 +124,7 @@ class xFormArmature:
                         ob.animation_data.drivers.remove(ob.animation_data.drivers[-1])
                         ob.animation_data.drivers.remove(ob.animation_data.drivers[-1])
             for pb in ob.pose.bones:
             for pb in ob.pose.bones:
                 # clear it, even after deleting the edit bones, 
                 # clear it, even after deleting the edit bones, 
-                #  if we create them again the pose bones will be reused
+                #  if we create them again the pose bones will be reused 
                 while (pb.constraints):
                 while (pb.constraints):
                     pb.constraints.remove(pb.constraints[-1])
                     pb.constraints.remove(pb.constraints[-1])
                 pb.location = (0,0,0)
                 pb.location = (0,0,0)
@@ -132,6 +132,16 @@ class xFormArmature:
                 pb.rotation_quaternion = (1.0,0,0,0)
                 pb.rotation_quaternion = (1.0,0,0,0)
                 pb.rotation_axis_angle = (0,0,1.0,0)
                 pb.rotation_axis_angle = (0,0,1.0,0)
                 pb.scale = (1.0,1.0,1.0)
                 pb.scale = (1.0,1.0,1.0)
+            # feels ugly and bad, whatever
+            collections = []
+            for bc in ob.data.collections:
+                collections.append(bc)
+            for bc in collections:
+                ob.data.collections.remove(bc)
+            del collections
+            # end ugly/bad
+
+
         else:
         else:
             # Create the Object
             # Create the Object
             ob = bpy.data.objects.new(name, bpy.data.armatures.new(name)) #create ob
             ob = bpy.data.objects.new(name, bpy.data.armatures.new(name)) #create ob
@@ -176,7 +186,8 @@ class xFormArmature:
         selected.append(ob)
         selected.append(ob)
         context_override = {"active_object":ob, "selected_objects":selected}
         context_override = {"active_object":ob, "selected_objects":selected}
         print("Changing Armature Mode to " +wrapPurple("EDIT"))
         print("Changing Armature Mode to " +wrapPurple("EDIT"))
-        bpy.ops.object.mode_set(context_override, mode='EDIT')
+        with bContext.temp_override(**context_override):
+            bpy.ops.object.mode_set(mode='EDIT')
         if ob.mode != "EDIT":
         if ob.mode != "EDIT":
             prRed("eh?")
             prRed("eh?")
         # clear it
         # clear it
@@ -232,7 +243,7 @@ class xFormBone:
          "Z Min"          : NodeSocket(is_input = True, name = "Z Min", node = self,),
          "Z Min"          : NodeSocket(is_input = True, name = "Z Min", node = self,),
          "Z Max"          : NodeSocket(is_input = True, name = "Z Max", node = self,),
          "Z Max"          : NodeSocket(is_input = True, name = "Z Max", node = self,),
          # Visual stuff
          # Visual stuff
-         "Layer Mask"                         : NodeSocket(is_input = True, name = "Layer Mask", node = self,),
+         "Bone Collection"                         : NodeSocket(is_input = True, name = "Bone Collection", node = self,),
          "Hide"                               : NodeSocket(is_input = True, name = "Hide", node = self,),
          "Hide"                               : NodeSocket(is_input = True, name = "Hide", node = self,),
          "Custom Object"                      : NodeSocket(is_input = True, name = "Custom Object", 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 xForm Override"       : NodeSocket(is_input = True, name = "Custom Object xForm Override", node = self,),
@@ -241,7 +252,6 @@ class xFormBone:
          "Custom Object Scale"                : NodeSocket(is_input = True, name = "Custom Object Scale", 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 Translation"          : NodeSocket(is_input = True, name = "Custom Object Translation", node = self,),
          "Custom Object Rotation"             : NodeSocket(is_input = True, name = "Custom Object Rotation", 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 Stuff
          "Deform"               : NodeSocket(is_input = True, name = "Deform", node = self,),
          "Deform"               : NodeSocket(is_input = True, name = "Deform", node = self,),
          "Envelope Distance"    : NodeSocket(is_input = True, name = "Envelope Distance", node = self,),
          "Envelope Distance"    : NodeSocket(is_input = True, name = "Envelope Distance", node = self,),
@@ -270,9 +280,8 @@ class xFormBone:
          "Y Max":None,
          "Y Max":None,
          "Z Min":None,
          "Z Min":None,
          "Z Max":None,
          "Z Max":None,
-         "Layer Mask":None,
          "Hide":None,
          "Hide":None,
-         "Layer Mask":None,
+         "Bone Collection":None,
          "Hide":None,
          "Hide":None,
          "Custom Object":None,
          "Custom Object":None,
          "Custom Object xForm Override":None,
          "Custom Object xForm Override":None,
@@ -281,7 +290,6 @@ class xFormBone:
          "Custom Object Scale":None,
          "Custom Object Scale":None,
          "Custom Object Translation":None,
          "Custom Object Translation":None,
          "Custom Object Rotation":None,
          "Custom Object Rotation":None,
-         "Bone Group"           : None,
          "Deform"               : None,
          "Deform"               : None,
          "Envelope Distance"    : None,
          "Envelope Distance"    : None,
          "Envelope Weight"      : None,
          "Envelope Weight"      : None,
@@ -355,6 +363,23 @@ class xFormBone:
         # Create the Object
         # Create the Object
         d = xF.data
         d = xF.data
         eb = d.edit_bones.new(name)
         eb = d.edit_bones.new(name)
+
+        # Bone Collections:
+        #    We treat each separate string as a Bone Collection that this object belongs to
+        #    Bone Collections are fully qualified by their hierarchy.
+        #    Separate Strings with "|" and indicate hierarchy with ">". These are special characters.
+        # NOTE: if the user names the collections differently at different times, this will take the FIRST definition and go with it
+        sCols = self.evaluate_input("Bone Collection")
+        bone_collections = sCols.split("|")
+        for collection_list in bone_collections:
+            hierarchy = collection_list.split(">")
+            col_parent = None
+            for i, sCol in enumerate(hierarchy):
+                if ( col := d.collections.get(sCol) ) is None:
+                    col = d.collections.new(sCol)
+                col.parent = col_parent
+                col_parent = col
+            col.assign(eb)
         
         
         if (eb.name != name):
         if (eb.name != name):
             raise RuntimeError("Could not create bone ", name, "; Perhaps there is a duplicate bone name in the node tree?")
             raise RuntimeError("Could not create bone ", name, "; Perhaps there is a duplicate bone name in the node tree?")
@@ -372,7 +397,6 @@ class xFormBone:
         assert (self.bObject), "eh? %s" % eb.name
         assert (self.bObject), "eh? %s" % eb.name
         
         
         self.bSetParent(eb)
         self.bSetParent(eb)
-        eb.layers = self.evaluate_input("Layer Mask")
         
         
         
         
         # Setup Deform attributes...
         # Setup Deform attributes...
@@ -388,7 +412,7 @@ class xFormBone:
 
 
     def bFinalize(self, bContext = None):
     def bFinalize(self, bContext = None):
         import bpy
         import bpy
-        from mantis.drivers import MantisDriver
+        from .drivers import MantisDriver
         # prevAct = bContext.view_layer.objects.active
         # prevAct = bContext.view_layer.objects.active
         # bContext.view_layer.objects.active = ob
         # bContext.view_layer.objects.active = ob
         # bpy.ops.object.mode_set(mode='OBJECT')
         # bpy.ops.object.mode_set(mode='OBJECT')
@@ -530,49 +554,51 @@ class xFormBone:
         pb.custom_shape_rotation_euler = self.evaluate_input("Custom Object Rotation")
         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.use_custom_shape_bone_size = self.evaluate_input("Custom Object Scale to Bone Length")
         pb.bone.show_wire = self.evaluate_input("Custom Object Wireframe")
         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!
+        # #
+        # # D E P R E C A T E D
+        # #
+        # # 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
+        #     pb.bone_group = bg
             
             
         
         
         
         

+ 9 - 10
xForm_definitions.py

@@ -1,7 +1,7 @@
 import bpy
 import bpy
 from .base_definitions import xFormNode
 from .base_definitions import xFormNode
 from bpy.types import Node
 from bpy.types import Node
-from mantis.utilities import (prRed, prGreen, prPurple, prWhite,
+from .utilities import (prRed, prGreen, prPurple, prWhite,
                               prOrange,
                               prOrange,
                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
                               wrapOrange,)
                               wrapOrange,)
@@ -99,7 +99,7 @@ class xFormBoneNode(Node, xFormNode):
         
         
         # visual settings:
         # visual settings:
         b = []
         b = []
-        b.append(self.inputs.new ('LayerMaskSocket', "Layer Mask"))
+        b.append(self.inputs.new ('BoneCollectionInputSocket', "Bone Collection"))
         b.append(self.inputs.new ('xFormSocket', "Custom Object"))
         b.append(self.inputs.new ('xFormSocket', "Custom Object"))
         b.append(self.inputs.new ('xFormSocket', "Custom Object xForm Override"))
         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 Scale to Bone Length"))
@@ -107,8 +107,7 @@ class xFormBoneNode(Node, xFormNode):
         b.append(self.inputs.new ('VectorScaleSocket', "Custom Object Scale"))
         b.append(self.inputs.new ('VectorScaleSocket', "Custom Object Scale"))
         b.append(self.inputs.new ('VectorSocket', "Custom Object Translation"))
         b.append(self.inputs.new ('VectorSocket', "Custom Object Translation"))
         b.append(self.inputs.new ('VectorEulerSocket', "Custom Object Rotation"))
         b.append(self.inputs.new ('VectorEulerSocket', "Custom Object Rotation"))
-        b.append(self.inputs.new ('StringSocket', "Bone Group"))
-        # 16-22
+        # 16-21
         # Deform Settings:
         # Deform Settings:
         c = []
         c = []
         c.append(self.inputs.new ('BooleanSocket', "Deform"))
         c.append(self.inputs.new ('BooleanSocket', "Deform"))
@@ -117,7 +116,7 @@ class xFormBoneNode(Node, xFormNode):
         c.append(self.inputs.new ('BooleanSocket', "Envelope Multiply"))
         c.append(self.inputs.new ('BooleanSocket', "Envelope Multiply"))
         c.append(self.inputs.new ('FloatPositiveSocket', "Envelope Head Radius"))
         c.append(self.inputs.new ('FloatPositiveSocket', "Envelope Head Radius"))
         c.append(self.inputs.new ('FloatPositiveSocket', "Envelope Tail Radius"))
         c.append(self.inputs.new ('FloatPositiveSocket', "Envelope Tail Radius"))
-        #24-28
+        #22-27
         
         
         # c[0].default_value=False
         # c[0].default_value=False
         
         
@@ -128,7 +127,7 @@ class xFormBoneNode(Node, xFormNode):
         for sock in a:
         for sock in a:
             sock.hide = True
             sock.hide = True
         for sock in b:
         for sock in b:
-            if sock.name in ['Custom Object', 'Layer Mask']:
+            if sock.name in ['Custom Object', 'Bone Collection']:
                 continue
                 continue
             sock.hide = True
             sock.hide = True
         for sock in c:
         for sock in c:
@@ -221,17 +220,17 @@ class xFormBoneNode(Node, xFormNode):
                 for inp in self.inputs[4:14]:
                 for inp in self.inputs[4:14]:
                     inp.hide = True
                     inp.hide = True
             if self.display_vp_settings == True:
             if self.display_vp_settings == True:
-                for inp in self.inputs[16:22]:
+                for inp in self.inputs[16:21]:
                     inp.hide = False
                     inp.hide = False
             else:
             else:
-                for inp in self.inputs[16:22]:
+                for inp in self.inputs[16:21]:
                     inp.hide = True
                     inp.hide = True
             #
             #
             if self.display_def_settings == True:
             if self.display_def_settings == True:
-                for inp in self.inputs[24:29]:
+                for inp in self.inputs[23:28]:
                     inp.hide = False
                     inp.hide = False
             else:
             else:
-                for inp in self.inputs[24:29]:
+                for inp in self.inputs[23:28]:
                     inp.hide = True
                     inp.hide = True