Browse Source

New Display Features for Bone, Armature

this commit adds all the display features Blender added since
4.2 that were not implemented in Mantis yet and also
some that are even older
Joseph Brandenburg 4 weeks ago
parent
commit
5762c39ef0
7 changed files with 193 additions and 26 deletions
  1. 6 0
      base_definitions.py
  2. 3 1
      mantis_dataclasses.py
  3. 60 9
      socket_definitions.py
  4. 69 0
      versioning.py
  5. 3 0
      xForm_nodes.py
  6. 26 16
      xForm_nodes_ui.py
  7. 26 0
      xForm_socket_templates.py

+ 6 - 0
base_definitions.py

@@ -785,8 +785,14 @@ class MantisNode:
     def gen_property_socket_map(self, prop_owner_group=None) -> dict:
     def gen_property_socket_map(self, prop_owner_group=None) -> dict:
         from .mantis_dataclasses import MantisSocketFiller
         from .mantis_dataclasses import MantisSocketFiller
         from dataclasses import replace
         from dataclasses import replace
+        from bpy.app import version as bpy_version
         props_sockets = {}
         props_sockets = {}
         for s_template in self.socket_templates:
         for s_template in self.socket_templates:
+            if s_template.min_bpy_version:
+                if bpy_version < s_template.min_bpy_version:
+                    prPurple(f"Skipping socket {s_template.name} because "
+                             f"it requires Blender {s_template.min_bpy_version} or higher")
+                    continue # We can't set or drive this obviously.
             if prop_owner_group is not None and \
             if prop_owner_group is not None and \
                     s_template.prop_owner_group != prop_owner_group:
                     s_template.prop_owner_group != prop_owner_group:
                 continue # for composite objects like bones
                 continue # for composite objects like bones

+ 3 - 1
mantis_dataclasses.py

@@ -18,8 +18,10 @@ class MantisSocketTemplate():
     # this is a hint for the props_sockets system for complex situations
     # this is a hint for the props_sockets system for complex situations
     #  where multiple blender objects own the properties, like e.g. bone/posebone
     #  where multiple blender objects own the properties, like e.g. bone/posebone
     prop_owner_group : str = field(default="")
     prop_owner_group : str = field(default="")
-    # do? add versioning fields for Mantis, Blender version min/max
     is_animatable    : bool = field(default=True) # controls whether a driver is legal or not
     is_animatable    : bool = field(default=True) # controls whether a driver is legal or not
+    # These attributes are for deprecated or new features in Blender
+    min_bpy_version  : Optional[tuple[int]] = field(default=None)
+    max_bpy_version  : Optional[tuple[int]] = field(default=None)
 
 
 @dataclass
 @dataclass
 class xForm_info():
 class xForm_info():

+ 60 - 9
socket_definitions.py

@@ -85,7 +85,6 @@ enumRotationOrder =(('AUTO', 'Auto', 'Auto'),
                     ('AXIS_ANGLE', "Axis Angle", "Axis Angle"),)
                     ('AXIS_ANGLE', "Axis Angle", "Axis Angle"),)
 
 
 
 
-
 # node socket colors:
 # node socket colors:
 cFloat          = (0.631373, 0.631373, 0.631373, 1.000000)
 cFloat          = (0.631373, 0.631373, 0.631373, 1.000000)
 cColor          = (0.780392, 0.780392, 0.160784, 1.000000)
 cColor          = (0.780392, 0.780392, 0.160784, 1.000000)
@@ -276,7 +275,9 @@ def TellClasses() -> List[MantisSocket]:
              EnumLatticeInterpolationTypeSocket,
              EnumLatticeInterpolationTypeSocket,
              EnumCorrectiveSmoothTypeSocket,
              EnumCorrectiveSmoothTypeSocket,
              eFCrvExtrapolationMode,
              eFCrvExtrapolationMode,
-
+             # other enums
+             EnumArmatureDisplayType,
+             EnumDisplayType,
              # Math
              # Math
              MathFloatOperation,
              MathFloatOperation,
              MathVectorOperation,
              MathVectorOperation,
@@ -594,7 +595,7 @@ def ChooseDraw(self, context, layout, node, text, icon = "NONE", use_enum=True,
             if not (self.is_output or self.is_linked):
             if not (self.is_output or self.is_linked):
                 layout.prop_tabs_enum(self, "default_value",)
                 layout.prop_tabs_enum(self, "default_value",)
             else:
             else:
-                layout.label(text=text)
+                layout.prop(text=text)
         # for OUTPUT sockets that take INPUT (confusing name!)
         # for OUTPUT sockets that take INPUT (confusing name!)
         elif ((hasattr(self, "default_value")) and hasattr(self, "input") and getattr(self, "input")):
         elif ((hasattr(self, "default_value")) and hasattr(self, "input") and getattr(self, "input")):
             # for simple input nodes
             # for simple input nodes
@@ -2986,12 +2987,6 @@ class VectorScaleSocket(MantisSocket):
     def draw_color_simple(self):
     def draw_color_simple(self):
         return self.color_simple
         return self.color_simple
 
 
-
-
-
-
-
-
 class KeyframeSocket(MantisSocket):
 class KeyframeSocket(MantisSocket):
     '''Keyframe'''
     '''Keyframe'''
     bl_idname = 'KeyframeSocket'
     bl_idname = 'KeyframeSocket'
@@ -3157,6 +3152,62 @@ class EnumCorrectiveSmoothTypeSocket(MantisSocket):
     def draw_color_simple(self):
     def draw_color_simple(self):
         return self.color_simple
         return self.color_simple
 
 
+
+enumArmatureDisplayType =( ('OCTAHEDRAL', "Octahedral", "Octahedral"),
+                           ('STICK', "Stick", "Stick"),
+                           ('BBONE', "BBone", "BBone"),
+                           ('ENVELOPE', "Envelope", "Envelope"),
+                           ('WIRE', "Wire", "Wire"),)
+
+class EnumArmatureDisplayType(MantisSocket):
+    """Float Math Operation"""
+    bl_idname = 'EnumArmatureDisplayType'
+    bl_label = "Operation"
+    default_value :bpy.props.EnumProperty(
+        name="",
+        description="Display Type",
+        items=enumArmatureDisplayType,
+        default='OCTAHEDRAL',
+        update = update_socket,)
+    color_simple = cString
+    color : bpy.props.FloatVectorProperty(default=cString, size=4)
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text, use_enum=False)
+    def draw_color(self, context, node):
+        return self.color
+    @classmethod
+    def draw_color_simple(self):
+        return self.color_simple
+
+enumDisplayType =(('ARMATURE_DEFINED', 'Auto', 'Auto'),
+                  ('OCTAHEDRAL', "Octahedral", "Octahedral"),
+                  ('STICK', "Stick", "Stick"),
+                  ('BBONE', "BBone", "BBone"),
+                  ('ENVELOPE', "Envelope", "Envelope"),
+                  ('WIRE', "Wire", "Wire"),)
+
+class EnumDisplayType(MantisSocket):
+    """Float Math Operation"""
+    bl_idname = 'EnumDisplayType'
+    bl_label = "Operation"
+    default_value :bpy.props.EnumProperty(
+        name="",
+        description="Display Type",
+        items=enumDisplayType,
+        default='ARMATURE_DEFINED',
+        update = update_socket,)
+    color_simple = cString
+    color : bpy.props.FloatVectorProperty(default=cString, size=4)
+    input : bpy.props.BoolProperty(default =False,)
+    def draw(self, context, layout, node, text):
+        ChooseDraw(self, context, layout, node, text, use_enum=False)
+    def draw_color(self, context, node):
+        return self.color
+    @classmethod
+    def draw_color_simple(self):
+        return self.color_simple
+
 enumFloatOperations = (('ADD', 'Add', 'Add'),
 enumFloatOperations = (('ADD', 'Add', 'Add'),
                       ('SUBTRACT', "Subtract", "Subtract"),
                       ('SUBTRACT', "Subtract", "Subtract"),
                       ('MULTIPLY', "Multiply", "Multiply"),
                       ('MULTIPLY', "Multiply", "Multiply"),

+ 69 - 0
versioning.py

@@ -393,9 +393,11 @@ def update_fcurve_and_armature_node_to_multi_socket(*args, **kwargs):
                     toggle_updating.append(link.node)
                     toggle_updating.append(link.node)
                     was_updating[link.node.name] = link.node.is_updating
                     was_updating[link.node.name] = link.node.is_updating
                     link.node.is_updating = True
                     link.node.is_updating = True
+    socket_templates_original = node.socket_templates
     node.inputs.clear() # this is VERY DANGEROUS without the above
     node.inputs.clear() # this is VERY DANGEROUS without the above
     add_me_sockets = [st for st in node.socket_templates if st.is_input == True]
     add_me_sockets = [st for st in node.socket_templates if st.is_input == True]
     node.init_sockets(add_me_sockets)
     node.init_sockets(add_me_sockets)
+    node.socket_templates = socket_templates_original
     try:
     try:
         new_sockets_in = {}
         new_sockets_in = {}
         for convert_key in my_convert_keys:
         for convert_key in my_convert_keys:
@@ -438,6 +440,7 @@ def fix_bbone_default_scale_in_and_out(*args, **kwargs):
     if current_minor_version == 13 and current_sub_version >= 5: return# sub version must be 4 or less
     if current_minor_version == 13 and current_sub_version >= 5: return# sub version must be 4 or less
     prPurple(f"Fixing BBone Settings for {tree.name}")
     prPurple(f"Fixing BBone Settings for {tree.name}")
     live_updates = tree.do_live_update
     live_updates = tree.do_live_update
+    tree.do_live_update = False
     bones = []
     bones = []
     for node in tree.nodes:
     for node in tree.nodes:
         if hasattr(node, 'is_updating'):
         if hasattr(node, 'is_updating'):
@@ -452,6 +455,71 @@ def fix_bbone_default_scale_in_and_out(*args, **kwargs):
         for node in tree.nodes:
         for node in tree.nodes:
             if hasattr(node, 'is_updating'):
             if hasattr(node, 'is_updating'):
                 node.is_updating = False
                 node.is_updating = False
+        tree.do_live_update = live_updates
+
+# New display features, Display Type, new Custom Object gizmo/pivot stuff
+def add_new_display_features_v0_13_5(*args, **kwargs):
+    tree = kwargs['tree']
+    current_major_version = tree.mantis_version[0]
+    current_minor_version = tree.mantis_version[1]
+    current_sub_version = tree.mantis_version[2]
+    if  current_major_version > 0: return# major version must be 0
+    if current_minor_version >= 13: return# minor version must be 12 or less
+    if current_minor_version == 13 and current_sub_version >= 5: return# sub version must be 4 or less
+    prPurple(f"Adding new Display Features to {tree.name}")
+    live_updates = tree.do_live_update
+    tree.do_live_update = False
+    live_updates = tree.do_live_update
+    bones = []
+    armatures = []
+    for node in tree.nodes:
+        if hasattr(node, 'is_updating'):
+            node.is_updating = True
+        if node.bl_idname == 'xFormBoneNode':
+            bones.append(node)
+        if node.bl_idname == 'xFormArmatureNode':
+            armatures.append(node)
+    try:
+        for bone_node in bones:
+            # we'll just try this...
+            from .xForm_socket_templates import (BoneDisplayType,
+                                                 CustomObjectWireWidth,
+                                                 UseTransformAtCustomShape,
+                                                 UseTransformAroundCustomShape,
+                                                 xFormBoneSockets)
+            input_templates = ( BoneDisplayType,
+                                CustomObjectWireWidth,
+                                UseTransformAroundCustomShape,
+                                UseTransformAtCustomShape, )
+            bone_node.init_sockets(input_templates)
+            bone_node.socket_templates = xFormBoneSockets
+            for i, inp in enumerate(bone_node.inputs):
+                if inp.name == 'Custom Object xForm Override':
+                    # As confusing as this is, it is the least terrible way to do this
+                    # We add the sockets to the end, then move them from the end to the right place
+                    bone_node.inputs.move(len(bone_node.inputs)-1, i+1)
+                    bone_node.inputs.move(len(bone_node.inputs)-1, i+2)
+                    bone_node.inputs.move(len(bone_node.inputs)-1, i+3)
+                    bone_node.inputs.move(len(bone_node.inputs)-1, i+4)
+                    break
+        
+        for armature_node in armatures:
+            # we'll just try this...
+            from .xForm_socket_templates import (ArmatureDisplayType,
+                                                 xFormArmatureSockets)
+            input_templates = ( ArmatureDisplayType, )
+            armature_node.init_sockets(input_templates)
+            armature_node.socket_templates = xFormArmatureSockets
+            for i, inp in enumerate(armature_node.inputs):
+                if inp.name == 'Relationship':
+                    armature_node.inputs.move(len(armature_node.inputs)-1, i+1)
+                    break
+
+    finally:
+        for node in tree.nodes:
+            if hasattr(node, 'is_updating'):
+                node.is_updating = False
+        tree.do_live_update = live_updates
 
 
 # versioning tasks that involve Blender versions rather than Mantis versions:
 # versioning tasks that involve Blender versions rather than Mantis versions:
 error_description_4_5_0_LTS = 'There is a bug in Blender 4.5.0 LTS, that is why these sockets are blue.'\
 error_description_4_5_0_LTS = 'There is a bug in Blender 4.5.0 LTS, that is why these sockets are blue.'\
@@ -490,6 +558,7 @@ versioning_tasks = [
     (['UtilityFCurve', 'LinkArmature'],
     (['UtilityFCurve', 'LinkArmature'],
            update_fcurve_and_armature_node_to_multi_socket, ['node']),
            update_fcurve_and_armature_node_to_multi_socket, ['node']),
     (['MantisTree', 'SchemaTree'], fix_bbone_default_scale_in_and_out, ['tree']),
     (['MantisTree', 'SchemaTree'], fix_bbone_default_scale_in_and_out, ['tree']),
+    (['MantisTree', 'SchemaTree'], add_new_display_features_v0_13_5, ['tree']),
 ]
 ]
 
 
 
 

+ 3 - 0
xForm_nodes.py

@@ -153,6 +153,7 @@ class xFormNode(MantisNode):
             ob.property_overridable_library_set(f"[\"{name}\"]", True)
             ob.property_overridable_library_set(f"[\"{name}\"]", True)
             pass
             pass
 
 
+# TODO this should use the socket templates / prop_sockets system!
 class xFormArmature(xFormNode):
 class xFormArmature(xFormNode):
     '''A node representing an armature object'''
     '''A node representing an armature object'''
 
 
@@ -248,6 +249,8 @@ class xFormArmature(xFormNode):
             ob.data.edit_bones.remove(ob.data.edit_bones[0])
             ob.data.edit_bones.remove(ob.data.edit_bones[0])
         # bContext.view_layer.objects.active = prevAct
         # bContext.view_layer.objects.active = prevAct
 
 
+        ob.data.display_type = self.evaluate_input('Display Type')
+
         # This will OVERWRITE the root armature since bones have to trace back to this one.
         # This will OVERWRITE the root armature since bones have to trace back to this one.
         root_armature = ob.name
         root_armature = ob.name
         parent_xForm_info = get_parent_xForm_info(self)
         parent_xForm_info = get_parent_xForm_info(self)

+ 26 - 16
xForm_nodes_ui.py

@@ -146,8 +146,11 @@ class xFormBoneNode(Node, xFormNode):
                 self.display_ik_settings = pb.is_in_ik_chain
                 self.display_ik_settings = pb.is_in_ik_chain
             self.inputs['Name'].display_text = ""
             self.inputs['Name'].display_text = ""
             mantis_node_ready = mantis_node and mantis_node.prepared
             mantis_node_ready = mantis_node and mantis_node.prepared
+            self.display_vp_settings = False
+            self.display_def_settings = False
+            self.display_bb_settings = False
             if self.inputs['Custom Object'].is_linked and mantis_node_ready:
             if self.inputs['Custom Object'].is_linked and mantis_node_ready:
-                self.display_vp_settings = mantis_node.evaluate_input("Custom Object")
+                self.display_vp_settings = True
             if self.inputs['Deform'].is_linked and mantis_node_ready:
             if self.inputs['Deform'].is_linked and mantis_node_ready:
                 self.display_def_settings = mantis_node.evaluate_input("Deform")
                 self.display_def_settings = mantis_node.evaluate_input("Deform")
             else:
             else:
@@ -163,23 +166,30 @@ class xFormBoneNode(Node, xFormNode):
             if self.inputs['Name'].is_linked and mantis_node_ready:
             if self.inputs['Name'].is_linked and mantis_node_ready:
                 self.inputs['Name'].display_text = mantis_node.evaluate_input("Name")
                 self.inputs['Name'].display_text = mantis_node.evaluate_input("Name")
 
 
-            for template in self.socket_templates:
-                name = template.name
-                category = template.category
-                if category == "IK":
-                    self.inputs[name].hide = not self.display_ik_settings
-                if category == "Display":
-                    if name in ['Custom Object', 'Bone Collection']: continue
-                    self.inputs[name].hide = not self.display_vp_settings
-                if category == "Deform":
-                    if name in ['Deform']: continue
-                    self.inputs[name].hide = not self.display_def_settings
-                if category == "bbone":
-                    if name in ['BBone Segments']: continue
-                    self.inputs[name].hide = not self.display_bb_settings
+        for template in self.socket_templates:
+            name = template.name
+            category = template.category
+            if category == "IK":
+                self.inputs[name].hide = not self.display_ik_settings
+            if category == "Display":
+                if name in ['Custom Object', 'Bone Collection']: continue
+                self.inputs[name].hide = not self.display_vp_settings
+                if name == 'Display Type': # This one is only meaningful when there is no custom shape.
+                    self.inputs[name].hide = self.display_vp_settings
+            if category == "Deform":
+                if name in ['Deform']: continue
+                self.inputs[name].hide = not self.display_def_settings
+            if category == "bbone":
+                if name in ['BBone Segments']: continue
+                self.inputs[name].hide = not self.display_bb_settings
+            # Now hide the sockets that aren't usable in this Blender
+            if template.min_bpy_version:
+                if bpy_version < template.min_bpy_version:
+                    if template.is_input:
+                        self.inputs[template.name].hide = True # Not usable in this Blender
         # TEMPORARY HACK because this one is basically not implemented
         # TEMPORARY HACK because this one is basically not implemented
         self.inputs['BBone HQ Deformation'].hide = True
         self.inputs['BBone HQ Deformation'].hide = True
-
+        
 def object_displaty_update(self, parsed_tree, context):
 def object_displaty_update(self, parsed_tree, context):
     custom_prop_display_update(self)
     custom_prop_display_update(self)
     if context.space_data:
     if context.space_data:

+ 26 - 0
xForm_socket_templates.py

@@ -15,6 +15,9 @@ xFormArmatureSockets=[
             blender_property='matrix_world' ),
             blender_property='matrix_world' ),
     RelationshipInTemplate := SockTemplate(
     RelationshipInTemplate := SockTemplate(
         name="Relationship", is_input=True,  bl_idname='RelationshipSocket', ),
         name="Relationship", is_input=True,  bl_idname='RelationshipSocket', ),
+    ArmatureDisplayType := SockTemplate(name='Display Type', category='Display',
+        default_value='OCTAHEDRAL', blender_property='display_type',
+        min_bpy_version=(4,5,0), bl_idname='EnumArmatureDisplayType',  is_input=True,),
     CustomPropsTemplate := SockTemplate(
     CustomPropsTemplate := SockTemplate(
         name="Custom Properties", is_input=True,  bl_idname='CustomPropSocket',
         name="Custom Properties", is_input=True,  bl_idname='CustomPropSocket',
             use_multi_input=True, is_animatable=False, ),
             use_multi_input=True, is_animatable=False, ),
@@ -127,6 +130,23 @@ bbone_template = SockTemplate( name="<BBone Template>",
 # When there is a driver, you usually want the Pose Bone to hold it
 # When there is a driver, you usually want the Pose Bone to hold it
 # but when there isn't you probably want it on the Bone since it isn't animated
 # but when there isn't you probably want it on the Bone since it isn't animated
 
 
+
+# Version-dependent for Blender
+
+BoneDisplayType = SockTemplate(name='Display Type', category='Display',
+    default_value='ARMATURE_DEFINED', blender_property='display_type',
+    min_bpy_version=(4,5,0), bl_idname='EnumDisplayType',
+    prop_owner_group='bone', is_input=True,)
+# 5.0 needed for these
+UseTransformAtCustomShape = SockTemplate(name="Affect Gizmo", category='Display',
+    default_value=False, blender_property='use_transform_at_custom_shape', is_input=True,
+    min_bpy_version=(5,0,0), bl_idname='BooleanSocket', prop_owner_group='pose_bone',)
+UseTransformAroundCustomShape = SockTemplate(name="Use as Pivot", category='Display',
+    default_value=False, blender_property='use_transform_around_custom_shape', is_input=True,
+    min_bpy_version=(5,0,0), bl_idname='BooleanSocket', prop_owner_group='pose_bone',)
+
+
+
 # and bones! this one is a bit much...
 # and bones! this one is a bit much...
 from math import pi
 from math import pi
 xFormBoneSockets = [
 xFormBoneSockets = [
@@ -178,6 +198,12 @@ xFormBoneSockets = [
         name='Custom Object Scale to Bone Length', category='Display',
         name='Custom Object Scale to Bone Length', category='Display',
         blender_property='use_custom_shape_bone_size',
         blender_property='use_custom_shape_bone_size',
         default_value=True, prop_owner_group='pose_bone', ),
         default_value=True, prop_owner_group='pose_bone', ),
+    UseTransformAtCustomShape, # requires Blender >= 5.0 to use
+    UseTransformAroundCustomShape, # requires Blender >= 5.0 to use
+    CustomObjectWireWidth := SockTemplate(name='Custom Object Wire Width',
+        category='Display', default_value=1.0, blender_property='custom_shape_wire_width',
+        bl_idname='FloatPositiveSocket', prop_owner_group='pose_bone', is_input=True,),
+    BoneDisplayType, # requires Blender >= 4.5.0 to use
     CustomObjectWireframeTemplate := replace(HideRenderTemplate,
     CustomObjectWireframeTemplate := replace(HideRenderTemplate,
             name='Custom Object Wireframe', category='Display',
             name='Custom Object Wireframe', category='Display',
             blender_property='show_wire', default_value=True,
             blender_property='show_wire', default_value=True,