Ver Fonte

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 há 3 meses atrás
pai
commit
39d1b14278

+ 6 - 2
__init__.py

@@ -17,7 +17,7 @@ from .utilities import prRed
 
 MANTIS_VERSION_MAJOR=0
 MANTIS_VERSION_MINOR=11
-MANTIS_VERSION_SUB=18
+MANTIS_VERSION_SUB=19
 
 classLists = [module.TellClasses() for module in [
  link_definitions,
@@ -76,6 +76,7 @@ input_category=[
             NodeItem("InputExistingGeometryObject"),
             NodeItem("InputExistingGeometryData"),
             NodeItem("UtilityDeclareCollections"),
+            NodeItem("InputThemeBoneColorSets"),
     ]
 link_transform_category = [
         NodeItem("LinkCopyLocation"),
@@ -293,7 +294,10 @@ def do_version_update(node_tree):
             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}.")
                 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)
         socket_map = None
         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),
                ("UtilityPointFromCurve",     'INPUT',  "UnsignedIntSocket", "Spline Index",  1, False,    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.

+ 1 - 1
blender_manifest.toml

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

+ 21 - 0
misc_nodes.py

@@ -18,6 +18,7 @@ def TellClasses():
              InputMatrix,
              InputExistingGeometryObject,
              InputExistingGeometryData,
+             InputThemeBoneColorSets,
              UtilityDeclareCollections,
              UtilityCollectionJoin,
              UtilityCollectionHierarchy,
@@ -296,6 +297,26 @@ class InputMatrix(SimpleInputNode):
         self.outputs.init_sockets(outputs)
         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):
     '''Get a matrix from a curve'''
 

+ 15 - 0
misc_nodes_ui.py

@@ -21,6 +21,7 @@ def TellClasses():
              # InputGeometryNode,
              InputExistingGeometryObjectNode,
              InputExistingGeometryDataNode,
+             InputThemeBoneColorSets,
              UtilityDeclareCollections,
              UtilityCollectionJoin,
              UtilityCollectionHierarchy,
@@ -749,6 +750,20 @@ class InputExistingGeometryDataNode(Node, MantisUINode):
         self.outputs.new("GeometrySocket", "Geometry")
         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):
     # 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,
              StringSocket,
              CollectionDeclarationSocket,
+             ColorSetDisplaySocket,
+             ColorSetSocket,
 
              EnumMetaRigSocket,
              EnumMetaBoneSocket,
@@ -880,7 +882,6 @@ class CollectionDeclarationSocket(MantisSocket):
     @classmethod
     def draw_color_simple(self):
         return self.color_simple
-
 class BoneCollectionSocket(MantisSocket):
     """Bone Collection socket"""
     bl_idname = 'BoneCollectionSocket'
@@ -898,6 +899,109 @@ class BoneCollectionSocket(MantisSocket):
     def draw_color_simple(self):
         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 =(
         ('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."),

+ 28 - 1
xForm_containers.py

@@ -202,6 +202,7 @@ bone_inputs= [
          "Custom Object Scale",
          "Custom Object Translation",
          "Custom Object Rotation",
+         "Color",
          # Deform Stuff
          "Deform",
          "Envelope Distance",
@@ -362,8 +363,34 @@ class xFormBone(xFormNode):
         self.executed = True
 
     def bFinalize(self, bContext = None):
-        do_bb=False
         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_z = self.evaluate_input("BBone Z Size"); b.bbone_z = max(b.bbone_z, 0.0002)
         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 Translation":'VectorSocket',
 "Custom Object Rotation":'VectorEulerSocket',
+"Color":'ColorSetSocket',
 }
 
 # deform_names

+ 3 - 0
xForm_socket_templates.py

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