Browse Source

UI: Bone Custom Colors!

This commit adds:
 - Color socket for Bone
 - Theme Colors node for selecting colors from the theme
 - new socket types and related functions to support new sockets
 - versioning
 - fixes a bug in versioning code that fails to check for readonly props
 - updates the socket templates even though I am not using them yet
Joseph Brandenburg 3 months ago
parent
commit
39d1b14278
9 changed files with 181 additions and 5 deletions
  1. 6 2
      __init__.py
  2. 1 0
      base_definitions.py
  3. 1 1
      blender_manifest.toml
  4. 21 0
      misc_nodes.py
  5. 15 0
      misc_nodes_ui.py
  6. 105 1
      socket_definitions.py
  7. 28 1
      xForm_containers.py
  8. 1 0
      xForm_definitions.py
  9. 3 0
      xForm_socket_templates.py

+ 6 - 2
__init__.py

@@ -17,7 +17,7 @@ from .utilities import prRed
 
 
 MANTIS_VERSION_MAJOR=0
 MANTIS_VERSION_MAJOR=0
 MANTIS_VERSION_MINOR=11
 MANTIS_VERSION_MINOR=11
-MANTIS_VERSION_SUB=18
+MANTIS_VERSION_SUB=19
 
 
 classLists = [module.TellClasses() for module in [
 classLists = [module.TellClasses() for module in [
  link_definitions,
  link_definitions,
@@ -76,6 +76,7 @@ input_category=[
             NodeItem("InputExistingGeometryObject"),
             NodeItem("InputExistingGeometryObject"),
             NodeItem("InputExistingGeometryData"),
             NodeItem("InputExistingGeometryData"),
             NodeItem("UtilityDeclareCollections"),
             NodeItem("UtilityDeclareCollections"),
+            NodeItem("InputThemeBoneColorSets"),
     ]
     ]
 link_transform_category = [
 link_transform_category = [
         NodeItem("LinkCopyLocation"),
         NodeItem("LinkCopyLocation"),
@@ -293,7 +294,10 @@ def do_version_update(node_tree):
             if in_out == 'INPUT' and n.inputs.get(socket_name) is None:
             if in_out == 'INPUT' and n.inputs.get(socket_name) is None:
                 print(f"INFO: adding socket \"{socket_name}\" of type {socket_type} to node {n.name} of type {n.bl_idname}.")
                 print(f"INFO: adding socket \"{socket_name}\" of type {socket_type} to node {n.name} of type {n.bl_idname}.")
                 s = n.inputs.new(socket_type, socket_name, use_multi_input=use_multi_input)
                 s = n.inputs.new(socket_type, socket_name, use_multi_input=use_multi_input)
-                s.default_value = default_val
+                try:
+                    s.default_value = default_val
+                except AttributeError:
+                    pass # the socket is read-only
                 n.inputs.move(len(n.inputs)-1, index)
                 n.inputs.move(len(n.inputs)-1, index)
         socket_map = None
         socket_map = None
         if rename_jobs:
         if rename_jobs:

+ 1 - 0
base_definitions.py

@@ -620,6 +620,7 @@ SOCKETS_ADDED=[("DeformerMorphTargetDeform", 'INPUT', 'BooleanSocket', "Use Shap
                ("UtilityMatricesFromCurve",  'INPUT',  "UnsignedIntSocket", "Spline Index",  1, False,    0),
                ("UtilityMatricesFromCurve",  'INPUT',  "UnsignedIntSocket", "Spline Index",  1, False,    0),
                ("UtilityPointFromCurve",     'INPUT',  "UnsignedIntSocket", "Spline Index",  1, False,    0),
                ("UtilityPointFromCurve",     'INPUT',  "UnsignedIntSocket", "Spline Index",  1, False,    0),
                ("LinkCopyScale",             'INPUT',  "FloatFactorSocket", "Power",    5,      False,    1.0),
                ("LinkCopyScale",             'INPUT',  "FloatFactorSocket", "Power",    5,      False,    1.0),
+               ("xFormBoneNode",             'INPUT',  "ColorSetSocket",    "Color",    22,     False,    None,),
                ]
                ]
 
 
 # replace names with bl_idnames for reading the tree and solving schemas.
 # replace names with bl_idnames for reading the tree and solving schemas.

+ 1 - 1
blender_manifest.toml

@@ -3,7 +3,7 @@ schema_version = "1.0.0"
 # Example of manifest file for a Blender extension
 # Example of manifest file for a Blender extension
 # Change the values according to your extension
 # Change the values according to your extension
 id = "mantis"
 id = "mantis"
-version = "0.11.18"
+version = "0.11.19"
 name = "Mantis"
 name = "Mantis"
 tagline = "Mantis is a rigging nodes toolkit"
 tagline = "Mantis is a rigging nodes toolkit"
 maintainer = "Nodespaghetti <josephbburg@protonmail.com>"
 maintainer = "Nodespaghetti <josephbburg@protonmail.com>"

+ 21 - 0
misc_nodes.py

@@ -18,6 +18,7 @@ def TellClasses():
              InputMatrix,
              InputMatrix,
              InputExistingGeometryObject,
              InputExistingGeometryObject,
              InputExistingGeometryData,
              InputExistingGeometryData,
+             InputThemeBoneColorSets,
              UtilityDeclareCollections,
              UtilityDeclareCollections,
              UtilityCollectionJoin,
              UtilityCollectionJoin,
              UtilityCollectionHierarchy,
              UtilityCollectionHierarchy,
@@ -296,6 +297,26 @@ class InputMatrix(SimpleInputNode):
         self.outputs.init_sockets(outputs)
         self.outputs.init_sockets(outputs)
         self.init_parameters()
         self.init_parameters()
 
 
+class InputThemeBoneColorSets(SimpleInputNode):
+    '''A node representing the theme's colors'''
+        
+    def __init__(self, signature, base_tree):
+        super().__init__(signature, base_tree)
+        from .base_definitions import MantisSocketTemplate
+        outputs = []
+        for i in range(20):
+            outputs.append (MantisSocketTemplate(
+                name = f"Color {str(i).zfill(2)}",
+            ))
+        self.outputs.init_sockets(outputs)
+        self.init_parameters()
+        # we'll go ahead and fill them here
+        from .utilities import get_node_prototype
+        ui_node = get_node_prototype(self.ui_signature, self.base_tree)
+        for i in range(20):
+            self.parameters[f"Color {str(i).zfill(2)}"] = ui_node.outputs[i].default_value
+            
+
 class UtilityMatrixFromCurve(MantisNode):
 class UtilityMatrixFromCurve(MantisNode):
     '''Get a matrix from a curve'''
     '''Get a matrix from a curve'''
 
 

+ 15 - 0
misc_nodes_ui.py

@@ -21,6 +21,7 @@ def TellClasses():
              # InputGeometryNode,
              # InputGeometryNode,
              InputExistingGeometryObjectNode,
              InputExistingGeometryObjectNode,
              InputExistingGeometryDataNode,
              InputExistingGeometryDataNode,
+             InputThemeBoneColorSets,
              UtilityDeclareCollections,
              UtilityDeclareCollections,
              UtilityCollectionJoin,
              UtilityCollectionJoin,
              UtilityCollectionHierarchy,
              UtilityCollectionHierarchy,
@@ -749,6 +750,20 @@ class InputExistingGeometryDataNode(Node, MantisUINode):
         self.outputs.new("GeometrySocket", "Geometry")
         self.outputs.new("GeometrySocket", "Geometry")
         self.initialized = True
         self.initialized = True
 
 
+class InputThemeBoneColorSets(Node, MantisUINode):
+    """Displays the theme's colors."""
+    bl_idname = "InputThemeBoneColorSets"
+    bl_label = "Theme Colors"
+    bl_icon = "NODE"
+    bl_width_min=280
+    initialized : bpy.props.BoolProperty(default = False)
+    mantis_node_class_name=bl_idname
+    
+    def init(self, context):
+        for i in range(20):
+            s = self.outputs.new("ColorSetDisplaySocket", f"Color {str(i).zfill(2)}")
+            s.color_index=i
+        self.initialized = True
 
 
 def socket_data_from_collection_paths(root_data, root_name, path, socket_data):
 def socket_data_from_collection_paths(root_data, root_name, path, socket_data):
     # so we need to 'push' the socket names and their paths in order
     # so we need to 'push' the socket names and their paths in order

+ 105 - 1
socket_definitions.py

@@ -142,6 +142,8 @@ def TellClasses() -> List[MantisSocket]:
              IntSocket,
              IntSocket,
              StringSocket,
              StringSocket,
              CollectionDeclarationSocket,
              CollectionDeclarationSocket,
+             ColorSetDisplaySocket,
+             ColorSetSocket,
 
 
              EnumMetaRigSocket,
              EnumMetaRigSocket,
              EnumMetaBoneSocket,
              EnumMetaBoneSocket,
@@ -880,7 +882,6 @@ class CollectionDeclarationSocket(MantisSocket):
     @classmethod
     @classmethod
     def draw_color_simple(self):
     def draw_color_simple(self):
         return self.color_simple
         return self.color_simple
-
 class BoneCollectionSocket(MantisSocket):
 class BoneCollectionSocket(MantisSocket):
     """Bone Collection socket"""
     """Bone Collection socket"""
     bl_idname = 'BoneCollectionSocket'
     bl_idname = 'BoneCollectionSocket'
@@ -898,6 +899,109 @@ class BoneCollectionSocket(MantisSocket):
     def draw_color_simple(self):
     def draw_color_simple(self):
         return self.color_simple
         return self.color_simple
 
 
+def get_bone_theme_color(socket, prop,):
+    from bpy import context
+    color_index=socket.color_index
+    color_set = context.preferences.themes[0].bone_color_sets[color_index]
+    return getattr(color_set, prop )
+def get_active_color(socket):
+    if socket is None:
+        from bpy import context
+        return context.preferences.themes[0].view_3d.bone_pose_active
+    return get_bone_theme_color(socket, 'active')
+def get_normal_color(socket):
+    if socket is None:
+        from bpy import context
+        return context.preferences.themes[0].view_3d.bone_solid
+    return get_bone_theme_color(socket, 'normal')
+def get_select_color(socket):
+    if socket is None:
+        from bpy import context
+        return context.preferences.themes[0].view_3d.bone_pose
+    return get_bone_theme_color(socket, 'select')
+
+def get_color_set_value(socket,):
+    return   [ socket.active_color[0],
+               socket.active_color[1],
+               socket.active_color[2],
+               socket.normal_color[0],
+               socket.normal_color[1],
+               socket.normal_color[2],
+               socket.selected_color[0],
+               socket.selected_color[1],
+               socket.selected_color[2],]
+
+class ColorSetDisplaySocket(MantisSocket):
+    """Socket for displaying a bone color theme"""
+    bl_idname = 'ColorSetDisplaySocket'
+    bl_label = "Color Set"
+    default_value : bpy.props.FloatVectorProperty(get=get_color_set_value, size=9)
+    color_simple = cColor
+    color : bpy.props.FloatVectorProperty(default=cColor, size=4)
+    icon : bpy.props.StringProperty(default = "NONE",)
+    input : bpy.props.BoolProperty(default =False,)
+    display_text : bpy.props.StringProperty(default="")
+    color_index : bpy.props.IntProperty(default=0)
+    active_color : bpy.props.FloatVectorProperty(
+        name='Active Color', size=3, subtype='COLOR_GAMMA',
+        get=get_active_color )
+    normal_color : bpy.props.FloatVectorProperty(
+        name='Normal Color', size=3, subtype='COLOR_GAMMA',
+        get=get_normal_color )
+    selected_color : bpy.props.FloatVectorProperty(
+        name='Selected Color', size=3, subtype='COLOR_GAMMA',
+        get=get_select_color )
+    is_valid_interface_type=False
+    def draw(self, context, layout, node, text):
+        layout.prop( text='', data=self,
+                    property='active_color', )
+        layout.prop( text='', data=self,
+            property='normal_color',)
+        layout.prop( text='', data=self,
+                    property='selected_color',)
+    def draw_color(self, context, node):
+        return self.color
+    @classmethod
+    def draw_color_simple(self):
+        return self.color_simple
+
+class ColorSetSocket(MantisSocket):
+    """Socket for setting a bone color"""
+    bl_idname = 'ColorSetSocket'
+    bl_label = "Custom Color Set"
+    default_value : bpy.props.FloatVectorProperty(get=get_color_set_value, size=9)
+    color_simple = cColor
+    color : bpy.props.FloatVectorProperty(default=cColor, size=4)
+    icon : bpy.props.StringProperty(default = "NONE",)
+    input : bpy.props.BoolProperty(default = True,)
+    display_text : bpy.props.StringProperty(default="")
+    active_color : bpy.props.FloatVectorProperty(
+        name='Active Color', size=3, subtype='COLOR_GAMMA',
+        default=get_active_color(None),)
+    normal_color : bpy.props.FloatVectorProperty(
+        name='Normal Color', size=3, subtype='COLOR_GAMMA',
+        default=get_normal_color(None),)
+    selected_color : bpy.props.FloatVectorProperty(
+        name='Selected Color', size=3, subtype='COLOR_GAMMA',
+        default=get_select_color(None),)
+    is_valid_interface_type=False
+    def draw(self, context, layout, node, text):
+        if self.is_linked:
+            layout.label(text='Custom Color Set')
+        else:
+            layout.prop( text='Color Set', data=self,
+                        property='active_color', )
+            layout.prop( text='', data=self,
+                property='normal_color',)
+            layout.prop( text='', data=self,
+                        property='selected_color',)
+    def draw_color(self, context, node):
+        return self.color
+    @classmethod
+    def draw_color_simple(self):
+        return self.color_simple
+
+
 eArrayGetOptions =(
 eArrayGetOptions =(
         ('CAP', "Cap", "Fail if the index is out of bounds."),
         ('CAP', "Cap", "Fail if the index is out of bounds."),
         ('WRAP', "Wrap", "Wrap around to the beginning of the array once the idex goes out of bounds."),
         ('WRAP', "Wrap", "Wrap around to the beginning of the array once the idex goes out of bounds."),

+ 28 - 1
xForm_containers.py

@@ -202,6 +202,7 @@ bone_inputs= [
          "Custom Object Scale",
          "Custom Object Scale",
          "Custom Object Translation",
          "Custom Object Translation",
          "Custom Object Rotation",
          "Custom Object Rotation",
+         "Color",
          # Deform Stuff
          # Deform Stuff
          "Deform",
          "Deform",
          "Envelope Distance",
          "Envelope Distance",
@@ -362,8 +363,34 @@ class xFormBone(xFormNode):
         self.executed = True
         self.executed = True
 
 
     def bFinalize(self, bContext = None):
     def bFinalize(self, bContext = None):
-        do_bb=False
         b = self.bGetParentArmature().data.bones[self.bObject]
         b = self.bGetParentArmature().data.bones[self.bObject]
+        # let's do bone colors first
+        color_values = self.evaluate_input('Color')
+        from mathutils import Color
+        color_active = Color(color_values[:3])
+        color_normal = Color(color_values[3:6])
+        color_select = Color(color_values[6:])
+        is_theme_colors = False
+        theme = bContext.preferences.themes[0]
+        for i, color_set in enumerate(theme.bone_color_sets):
+            if  ((color_active == color_set.active) and
+                 (color_normal == color_set.normal) and
+                 (color_select == color_set.select) ):
+                            is_theme_colors=True; break
+        if is_theme_colors:          # add 1, not 0-indexed
+            b.color.palette = 'THEME'+str(i+1).zfill(2)
+        elif    ((color_active == theme.view_3d.bone_pose_active) and
+                 (color_normal == theme.view_3d.bone_solid) and
+                 (color_select == theme.view_3d.bone_pose) ):
+                        b.color.palette = 'DEFAULT'
+        else:
+            b.color.palette = 'CUSTOM'
+            b.color.custom.active=color_active
+            b.color.custom.normal=color_normal
+            b.color.custom.select=color_select
+
+        #
+        do_bb=False
         b.bbone_x = self.evaluate_input("BBone X Size"); b.bbone_x = max(b.bbone_x, 0.0002)
         b.bbone_x = self.evaluate_input("BBone X Size"); b.bbone_x = max(b.bbone_x, 0.0002)
         b.bbone_z = self.evaluate_input("BBone Z Size"); b.bbone_z = max(b.bbone_z, 0.0002)
         b.bbone_z = self.evaluate_input("BBone Z Size"); b.bbone_z = max(b.bbone_z, 0.0002)
         if (segs := self.evaluate_input("BBone Segments")) > 1:
         if (segs := self.evaluate_input("BBone Segments")) > 1:

+ 1 - 0
xForm_definitions.py

@@ -98,6 +98,7 @@ display_names = {
 "Custom Object Scale":'VectorScaleSocket',
 "Custom Object Scale":'VectorScaleSocket',
 "Custom Object Translation":'VectorSocket',
 "Custom Object Translation":'VectorSocket',
 "Custom Object Rotation":'VectorEulerSocket',
 "Custom Object Rotation":'VectorEulerSocket',
+"Color":'ColorSetSocket',
 }
 }
 
 
 # deform_names
 # deform_names

+ 3 - 0
xForm_socket_templates.py

@@ -125,6 +125,9 @@ xFormBoneSockets = [
     CustomObjectScaleTemplate := SockTemplate(name="Custom Object Rotation",
     CustomObjectScaleTemplate := SockTemplate(name="Custom Object Rotation",
             is_input=True, bl_idname='VectorEulerSocket',
             is_input=True, bl_idname='VectorEulerSocket',
             category='Display', default_value=(0.0,0.0,0.0),),
             category='Display', default_value=(0.0,0.0,0.0),),
+    CustomColor := SockTemplate(name="Color",
+            is_input=True, bl_idname='ColorSetSocket',
+            category='Display',),
     # Deform Stuff
     # Deform Stuff
     BoneDeformTemplate := replace(HideRenderTemplate, name='Deform',
     BoneDeformTemplate := replace(HideRenderTemplate, name='Deform',
         category='Deform', blender_property='use_deform', default_value=False,),
         category='Deform', blender_property='use_deform', default_value=False,),