Преглед на файлове

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 седмици
родител
ревизия
5762c39ef0
променени са 7 файла, в които са добавени 193 реда и са изтрити 26 реда
  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:
         from .mantis_dataclasses import MantisSocketFiller
         from dataclasses import replace
+        from bpy.app import version as bpy_version
         props_sockets = {}
         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 \
                     s_template.prop_owner_group != prop_owner_group:
                 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
     #  where multiple blender objects own the properties, like e.g. bone/posebone
     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
+    # 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
 class xForm_info():

+ 60 - 9
socket_definitions.py

@@ -85,7 +85,6 @@ enumRotationOrder =(('AUTO', 'Auto', 'Auto'),
                     ('AXIS_ANGLE', "Axis Angle", "Axis Angle"),)
 
 
-
 # node socket colors:
 cFloat          = (0.631373, 0.631373, 0.631373, 1.000000)
 cColor          = (0.780392, 0.780392, 0.160784, 1.000000)
@@ -276,7 +275,9 @@ def TellClasses() -> List[MantisSocket]:
              EnumLatticeInterpolationTypeSocket,
              EnumCorrectiveSmoothTypeSocket,
              eFCrvExtrapolationMode,
-
+             # other enums
+             EnumArmatureDisplayType,
+             EnumDisplayType,
              # Math
              MathFloatOperation,
              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):
                 layout.prop_tabs_enum(self, "default_value",)
             else:
-                layout.label(text=text)
+                layout.prop(text=text)
         # for OUTPUT sockets that take INPUT (confusing name!)
         elif ((hasattr(self, "default_value")) and hasattr(self, "input") and getattr(self, "input")):
             # for simple input nodes
@@ -2986,12 +2987,6 @@ class VectorScaleSocket(MantisSocket):
     def draw_color_simple(self):
         return self.color_simple
 
-
-
-
-
-
-
 class KeyframeSocket(MantisSocket):
     '''Keyframe'''
     bl_idname = 'KeyframeSocket'
@@ -3157,6 +3152,62 @@ class EnumCorrectiveSmoothTypeSocket(MantisSocket):
     def draw_color_simple(self):
         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'),
                       ('SUBTRACT', "Subtract", "Subtract"),
                       ('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)
                     was_updating[link.node.name] = link.node.is_updating
                     link.node.is_updating = True
+    socket_templates_original = node.socket_templates
     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]
     node.init_sockets(add_me_sockets)
+    node.socket_templates = socket_templates_original
     try:
         new_sockets_in = {}
         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
     prPurple(f"Fixing BBone Settings for {tree.name}")
     live_updates = tree.do_live_update
+    tree.do_live_update = False
     bones = []
     for node in tree.nodes:
         if hasattr(node, 'is_updating'):
@@ -452,6 +455,71 @@ def fix_bbone_default_scale_in_and_out(*args, **kwargs):
         for node in tree.nodes:
             if hasattr(node, 'is_updating'):
                 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:
 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'],
            update_fcurve_and_armature_node_to_multi_socket, ['node']),
     (['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)
             pass
 
+# TODO this should use the socket templates / prop_sockets system!
 class xFormArmature(xFormNode):
     '''A node representing an armature object'''
 
@@ -248,6 +249,8 @@ class xFormArmature(xFormNode):
             ob.data.edit_bones.remove(ob.data.edit_bones[0])
         # 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.
         root_armature = ob.name
         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.inputs['Name'].display_text = ""
             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:
-                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:
                 self.display_def_settings = mantis_node.evaluate_input("Deform")
             else:
@@ -163,23 +166,30 @@ class xFormBoneNode(Node, xFormNode):
             if self.inputs['Name'].is_linked and mantis_node_ready:
                 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
         self.inputs['BBone HQ Deformation'].hide = True
-
+        
 def object_displaty_update(self, parsed_tree, context):
     custom_prop_display_update(self)
     if context.space_data:

+ 26 - 0
xForm_socket_templates.py

@@ -15,6 +15,9 @@ xFormArmatureSockets=[
             blender_property='matrix_world' ),
     RelationshipInTemplate := SockTemplate(
         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(
         name="Custom Properties", is_input=True,  bl_idname='CustomPropSocket',
             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
 # 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...
 from math import pi
 xFormBoneSockets = [
@@ -178,6 +198,12 @@ xFormBoneSockets = [
         name='Custom Object Scale to Bone Length', category='Display',
         blender_property='use_custom_shape_bone_size',
         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,
             name='Custom Object Wireframe', category='Display',
             blender_property='show_wire', default_value=True,