2 Angajamente 6efb6b2edc ... 7c55b8e72b

Autor SHA1 Permisiunea de a trimite mesaje. Dacă este dezactivată, utilizatorul nu va putea trimite nici un fel de mesaj Data
  Joseph Brandenburg 7c55b8e72b v0.13.2 and add "add active obejct" tool 10 luni în urmă
  Joseph Brandenburg b165def8c8 Fix: object parenting broken 10 luni în urmă
5 a modificat fișierele cu 132 adăugiri și 87 ștergeri
  1. 1 1
      base_definitions.py
  2. 1 1
      blender_manifest.toml
  3. 6 0
      menu_classes.py
  4. 123 84
      ops_generate_tree.py
  5. 1 1
      xForm_nodes.py

+ 1 - 1
base_definitions.py

@@ -83,7 +83,7 @@ def hash_tree(tree):
 
 MANTIS_VERSION_MAJOR=0
 MANTIS_VERSION_MINOR=13
-MANTIS_VERSION_SUB=1
+MANTIS_VERSION_SUB=2
 
 class MantisTree(NodeTree):
     '''A custom node tree type that will show up in the editor type list'''

+ 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.13.1"
+version = "0.13.2"
 name = "Mantis"
 tagline = "Mantis is a rigging nodes toolkit"
 maintainer = "Nodespaghetti <josephbburg@protonmail.com>"

+ 6 - 0
menu_classes.py

@@ -17,6 +17,11 @@ def node_context_menu_draw(self, context):
     layout.operator("mantis.select_nodes_of_type")
     layout.operator("mantis.import_from_component_library")
     # layout.menu('NODE_MT_context_menu_mantis')
+    if context.active_object.type in ["EMPTY", "MESH", "CURVE"]: # this is currently too slow to show to the user otherwise :/
+        operator_settings = layout.operator(
+            "mantis.generate_tree", text="Add active selected object to tree",
+            icon='NODE', emboss=False,)
+        operator_settings.clear_tree = False
 
 # Function to append submenu to node add menu
 def node_add_menu_draw(self, context):
@@ -120,3 +125,4 @@ class MantisActiveTreePanel(Panel):
         layout.operator("mantis.execute_node_tree")
         layout.operator("mantis.force_display_update", text='Force Display Update')
         layout.operator("mantis.import_from_component_library")
+        

+ 123 - 84
ops_generate_tree.py

@@ -15,6 +15,7 @@ def mantis_poll_op(context):
 def create_inheritance_node(pb, parent_name, bone_inherit_node, node_tree):
     from bpy.types import PoseBone
     parent_node = node_tree.nodes.new("linkInherit")
+    parent_node.select = True # for sorting
     parent_bone_node = node_tree.nodes.get(parent_name)
     if not parent_bone_node:
         raise RuntimeError("Can't Find parent node!!")
@@ -83,7 +84,9 @@ constraint_link_map={
 
 def create_relationship_node_for_constraint(node_tree, c):
     if cls_name := constraint_link_map.get(c.type):
-        return node_tree.nodes.new(cls_name)
+        n =  node_tree.nodes.new(cls_name)
+        n.select = True # for sorting
+        return n
     else:
         prRed ("Not yet implemented: %s" % c.type)
         return None
@@ -228,8 +231,10 @@ def fill_parameters(node, c, context):
             node.inputs["XZ Scale Mode"].default_value = c.xz_scale_mode
         case 'TRANSFORM':
             # I can't be arsed to do all this work..
-            from .link_nodes import transformation_props_sockets as props
-            for prop, (sock_name, _unused) in props.items():
+            # from .link_nodes import transformation_props_sockets as props
+            from .link_socket_templates import LinkTransformationSockets
+            for template in LinkTransformationSockets:
+                prop = template.blender_property; socket_name = template.name
                 if "from" in prop:
                     if prop in ["map_from"] or "to" in prop:
                        pass
@@ -258,16 +263,12 @@ def fill_parameters(node, c, context):
                     elif c.map_from == 'SCALE':
                         if "scale" not in prop:
                             continue
-                node.inputs[sock_name].default_value = getattr(c, prop)
+                node.inputs[socket_name].default_value = getattr(c, prop)
                 if prop in "mute":
-                    node.inputs[sock_name].default_value = not getattr(c, prop)
+                    node.inputs[socket_name].default_value = not getattr(c, prop)
         case _:
             print (f"Not yet implemented: {c.type}")
 
-
-
-
-
 def walk_edit_bone(armOb, bone):
     # this is a simplified version of the node-tree walking code
     bonePath, bones, lines, seek = [0,], set(), [], bone
@@ -384,6 +385,7 @@ def setup_vp_settings(bone_node, pb, do_after, node_tree):
                 break
         else: # we make it now
             shape_n = node_tree.nodes.new("InputExistingGeometryObject")
+            shape_n.select = True # for sorting
             shape_n.name = shape_ob.name
             shape_n.label = shape_ob.name
             shape_n.inputs["Name"].default_value = shape_ob.name
@@ -488,6 +490,7 @@ def create_driver(in_node_name, out_node_name, armOb, finished_drivers, switches
             else:
                 # make and connect the switch node
                 switch_node = node_tree.nodes.new("UtilitySwitch"); switches.append(switch_node)
+                switch_node.select = True # for sorting
                 # node_tree.links.new(bone_node.outputs["xForm Out"], switch_node.inputs[0])
                 try:
                     node_tree.links.new(bone_node.outputs[p_string], switch_node.inputs[0])
@@ -598,6 +601,7 @@ def create_driver(in_node_name, out_node_name, armOb, finished_drivers, switches
                             break # found it!
                     else:
                         var_node = node_tree.nodes.new("UtilityDriverVariable"); driver_vars.append(var_node)
+                        var_node.select = True # for sorting
                         prRed("Creating Node: %s" % var_node.name)
                         for key, value in var_data.items():
                             try:
@@ -651,6 +655,7 @@ def create_driver(in_node_name, out_node_name, armOb, finished_drivers, switches
                     break
                 else:
                     fCurve_node = node_tree.nodes.new("UtilityFCurve")
+                    fCurve_node.select = True # for sorting
                     # fc_ob = fCurve_node.fake_fcurve_ob
                     # node_fc = fc_ob.animation_data.action.fcurves[0]
                     # fcurves.append(fCurve_node)
@@ -664,6 +669,7 @@ def create_driver(in_node_name, out_node_name, armOb, finished_drivers, switches
                     for num_keys, (k, v) in enumerate(keys.items()):
                         fCurve_node.inputs.new("KeyframeSocket", "Keyframe."+str(num_keys).zfill(3))
                         kf_node = node_tree.nodes.new("UtilityKeyframe")
+                        kf_node.select = True # for sorting
                         kf_node.inputs[0].default_value = v['co_ui'][0]
                         kf_node.inputs[1].default_value = v['co_ui'][1]
                         node_tree.links.new(kf_node.outputs[0], fCurve_node.inputs[num_keys])
@@ -673,6 +679,7 @@ def create_driver(in_node_name, out_node_name, armOb, finished_drivers, switches
                 driver_node = None
                 # checc for it...
                 driver_node = node_tree.nodes.new("UtilityDriver")
+                driver_node.select = True # for sorting
                 driver_node.inputs["Driver Type"].default_value = fc.driver.type
                 driver_node.inputs["Expression"].default_value = fc.driver.expression.replace ('var', 'a')
                 # HACK, fix the above with a more robust solution
@@ -739,12 +746,28 @@ def set_parent_from_node(pb, bone_inherit_node, node_tree):
                 parent = create_inheritance_node(pb, bone.parent.name, bone_inherit_node, node_tree)
         return parent
 
-def do_generate_geom(ob, node_tree, parent_node=None):
+
+def do_generate_graph( context, node_tree, include_links_and_deforms=True, generate_children=False):
+    active_object = context.active_object
+    for n in node_tree.nodes: n.select=False
+    # clear selection so that the sorting code works, it operates on selection
+    # the other functions are responsible for setting selection properly.
+    if active_object.select_get() == False:
+        return
+    elif active_object.type in ["MESH", "CURVE", "EMPTY"]:
+        do_generate_geom(active_object, node_tree, do_deformers = include_links_and_deforms, generate_children=generate_children)
+    elif active_object.type == 'ARMATURE':
+        do_generate_armature(active_object, context, node_tree, bones_only= not include_links_and_deforms, generate_children=generate_children)
+
+
+def do_generate_geom(ob, node_tree, parent_node=None, do_deformers=False, generate_children= False):
     ob_node = node_tree.nodes.new("xFormGeometryObject")
+    ob_node.select = True # for sorting
     ob_node.name = ob.name; ob_node.label = ob.name
     ob_node.inputs["Name"].default_value=ob.name+"_MANTIS"
     if ob.data:
         geometry_node = node_tree.nodes.new("InputExistingGeometryData")
+        geometry_node.select = True # for sorting
         geometry_node.inputs[0].default_value=ob.data.name
         node_tree.links.new(input=geometry_node.outputs[0], output=ob_node.inputs["Geometry"])
     matrix_of = node_tree.nodes.new("UtilityMatrixFromXForm")
@@ -756,46 +779,48 @@ def do_generate_geom(ob, node_tree, parent_node=None):
 
     # Generate Deformers
     prev_def_node = None
-    for m in ob.modifiers:
-        if m.type == "ARMATURE":
-            def_node = node_tree.nodes.new("DeformerArmature")
-            def_node.inputs["Blend Vertex Group"].default_value = m.vertex_group
-            def_node.inputs["Invert Vertex Group"].default_value = m.invert_vertex_group
-            def_node.inputs["Preserve Volume"].default_value = m.use_deform_preserve_volume
-            def_node.inputs["Use Multi Modifier"].default_value = m.use_multi_modifier
-            def_node.inputs["Use Envelopes"].default_value = m.use_bone_envelopes
-            def_node.inputs["Use Vertex Groups"].default_value = m.use_vertex_groups
-            # def_node.inputs["Copy Skin Weights From"]
-            def_node.inputs["Skinning Method"].default_value="EXISTING_GROUPS"
-            def_ob = node_tree.nodes.get(m.object.name)
-            # get the deformer's target object...
-            if def_ob:
-                node_tree.links.new(input=def_ob.outputs["xForm Out"], output=def_node.inputs["Armature Object"])
-            if prev_def_node:
-                node_tree.links.new(input=prev_def_node.outputs["Deformer"], inputs=def_node.inputs["Deformer"])
-            prev_def_node = def_node
-
-    if prev_def_node:
-        node_tree.links.new(input=prev_def_node.outputs["Deformer"], output=ob_node.inputs["Deformer"])
+    if do_deformers:
+        for m in ob.modifiers:
+            if m.type == "ARMATURE":
+                def_node = node_tree.nodes.new("DeformerArmature")
+                def_node.select = True # for sorting
+                def_node.inputs["Blend Vertex Group"].default_value = m.vertex_group
+                def_node.inputs["Invert Vertex Group"].default_value = m.invert_vertex_group
+                def_node.inputs["Preserve Volume"].default_value = m.use_deform_preserve_volume
+                def_node.inputs["Use Multi Modifier"].default_value = m.use_multi_modifier
+                def_node.inputs["Use Envelopes"].default_value = m.use_bone_envelopes
+                def_node.inputs["Use Vertex Groups"].default_value = m.use_vertex_groups
+                # def_node.inputs["Copy Skin Weights From"]
+                def_node.inputs["Skinning Method"].default_value="EXISTING_GROUPS"
+                def_ob = node_tree.nodes.get(m.object.name)
+                # get the deformer's target object...
+                if def_ob:
+                    node_tree.links.new(input=def_ob.outputs["xForm Out"], output=def_node.inputs["Armature Object"])
+                if prev_def_node:
+                    node_tree.links.new(input=prev_def_node.outputs["Deformer"], inputs=def_node.inputs["Deformer"])
+                prev_def_node = def_node
+
+        if prev_def_node:
+            node_tree.links.new(input=prev_def_node.outputs["Deformer"], output=ob_node.inputs["Deformer"])
 
     if parent_node:
         node_tree.links.new(input=parent_node.outputs["Inheritance"], output=ob_node.inputs["Relationship"])
+    # NOTE: there should be constraints here, too!
 
-
-    # not doing this
-    # matrix_node = node_tree.nodes.new("InputMatrix")
-    # matrix_node.first_row=ob.matrix_world[0:3]
-    # matrix_node.second_row=ob.matrix_world[4:7]
-    # matrix_node.third_row=ob.matrix_world[8:11]
-    # matrix_node.fourth_row=ob.matrix_world[12:15]
-    # node_tree.links.new(input=matrix_node.outputs[0], output=ob_node.inputs["Matrix"])
-
-
+    # copied from below with minor modification # TODO de-dupe me
+    if generate_children:
+        for child in ob.children:
+            its_parent = create_inheritance_node(None, ob.name, {}, node_tree)
+            if child.type in ["MESH", "CURVE", "EMPTY"]:
+                do_generate_geom(child, node_tree, parent_node=its_parent, do_deformers=do_deformers, generate_children=True)
+            if child.type in ["ARMATURE"]:
+                do_generate_armature(armOb, context, node_tree, parent_node=its_parent, bones_only=not do_deformers, generate_children=True)
 
 
 
 
-def do_generate_armature(armOb, context, node_tree, parent_node=None):
+def do_generate_armature(armOb, context, node_tree, parent_node=None,
+                        bones_only=False, generate_children=False):
         from time import time
         start = time()
 
@@ -803,20 +828,18 @@ def do_generate_armature(armOb, context, node_tree, parent_node=None):
         bone_inherit_node = {}
         do_after = set()
 
-
         armature = node_tree.nodes.new("xFormArmatureNode")
+        armature.select = True # for sorting
         mr_node_name = armOb.name
         if not (mr_node:= meta_rig_nodes.get(mr_node_name)):
             mr_node = node_tree.nodes.new("UtilityMetaRig")
+            mr_node.select = True # for sorting
             meta_rig_nodes[mr_node_name] = mr_node
             mr_node.inputs[0].search_prop=armOb
         node_tree.links.new(input=mr_node.outputs[0], output=armature.inputs["Matrix"])
         if parent_node:
             node_tree.links.new()
 
-
-
-
         bones = []
         for root in armOb.data.bones:
             if root.parent is None:
@@ -831,13 +854,12 @@ def do_generate_armature(armOb, context, node_tree, parent_node=None):
         if parent_node:
             node_tree.links.new(input=parent_node.outputs["Inheritance"], output=armature.inputs["Relationship"])
 
-
-
         # for bone_path in lines:
         for bone in bones:
             prGreen(time() - milestone); milestone=time()
             # first go through the bone path and find relevant information
             bone_node = node_tree.nodes.new("xFormBoneNode")
+            bone_node.select = True # for sorting
             bone_node.inputs["Name"].default_value = bone.name
             bone_node.name, bone_node.label = bone.name, bone.name
             matrix = bone.matrix_local.copy()
@@ -865,6 +887,7 @@ def do_generate_armature(armOb, context, node_tree, parent_node=None):
             else: # This is a root
                 if (parent_list := bone_inherit_node.get(armOb.name)) is None:
                     parent = node_tree.nodes.new("linkInherit")
+                    parent.select = True # for sorting
                     bone_inherit_node[armOb.name] = [parent]
                     node_tree.links.new(armature.outputs["xForm Out"], parent.inputs['Parent'])
                     parent.inputs["Inherit Rotation"].default_value = True
@@ -902,12 +925,14 @@ def do_generate_armature(armOb, context, node_tree, parent_node=None):
 
             # prRed("BBone Implementation is not complete, expect errors and missing features for now")
 
-
-            #
             for c in pb.constraints:
                 # prWhite("constraint %s for %s" % (c.name, pb.name), time() - milestone); milestone=time()
                 # make relationship nodes and set up links...
                 if ( c_node := create_relationship_node_for_constraint(node_tree, c)):
+                    # HACK but this code is all ugly hacky crap anyway so this is good enough
+                    if bones_only == True:
+                        continue
+                    
                     c_node.label = c.name
                     # this node definitely has a parent inherit node.
                     c_node.location = parent.location; c_node.location.x += 200
@@ -958,6 +983,7 @@ def do_generate_armature(armOb, context, node_tree, parent_node=None):
             if task in ['Object Target']:
                 in_node  = node_tree.nodes[ in_node_name ]
                 out_node= node_tree.nodes.new("InputExistingGeometryObject")
+                out_node.select = True # for sorting
                 out_node.inputs["Name"].default_value=out_node_name
                 node_tree.links.new(out_node.outputs["Object"], in_node.inputs["Target"])
             if task in ['Target', 'Pole Target']:
@@ -989,36 +1015,30 @@ def do_generate_armature(armOb, context, node_tree, parent_node=None):
             # annoyingly, Rigify uses f-modifiers to setup its fcurves
             # I do not intend to support fcurve modifiers in Mantis at this time
 
-
-        for child in armOb.children:
-            its_parent = None
-            parent_name = armOb.name
-            if child.parent_type == "BONE":
-                parent_name = child.parent_bone
-            if not (possible_parent_nodes := bone_inherit_node.get(parent_name)):
-                its_parent = create_inheritance_node(child, parent_name, bone_inherit_node, node_tree)
-            else:
-                for ppn in possible_parent_nodes: # check if it has the right connected, inherit scale, inherit rotation
-                    if ppn.inputs["Connected"].default_value  != False: continue
-                    if ppn.inputs["Inherit Scale"].default_value != "FULL": continue
-                    if ppn.inputs["Inherit Rotation"].default_value != True: continue
-                    its_parent = ppn; break
+        if generate_children:
+            for child in armOb.children:
+                its_parent = None
+                parent_name = armOb.name
+                if child.parent_type == "BONE":
+                    parent_name = child.parent_bone
+                if not (possible_parent_nodes := bone_inherit_node.get(parent_name)):
+                    its_parent = create_inheritance_node(child, parent_name, bone_inherit_node, node_tree)
                 else:
-                    its_parent = create_inheritance_node(pb, parent_name, bone_inherit_node, node_tree)
-
-            if child.type in ["MESH", "CURVE", "EMPTY"]:
-                do_generate_geom(child, node_tree, its_parent)
-            if child.type in ["ARMATURE"]:
-                do_generate_armature(armOb, context, node_tree, parent_node=its_parent)
+                    for ppn in possible_parent_nodes: # check if it has the right connected, inherit scale, inherit rotation
+                        if ppn.inputs["Connected"].default_value  != False: continue
+                        if ppn.inputs["Inherit Scale"].default_value != "FULL": continue
+                        if ppn.inputs["Inherit Rotation"].default_value != True: continue
+                        its_parent = ppn; break
+                    else:
+                        its_parent = create_inheritance_node(pb, parent_name, bone_inherit_node, node_tree)
 
-        for node in node_tree.nodes:
-            node.select = False
+                if child.type in ["MESH", "CURVE", "EMPTY"]:
+                    do_generate_geom(child, node_tree, its_parent)
+                if child.type in ["ARMATURE"]:
+                    do_generate_armature(armOb, context, node_tree, parent_node=its_parent)
 
         prGreen("Finished generating %d nodes in %f seconds." % (len(node_tree.nodes), time() - start))
 
-
-
-
         return armature
 
 
@@ -1027,11 +1047,21 @@ class GenerateMantisTree(Operator):
     """Generate Mantis Tree From Selected"""
     bl_idname = "mantis.generate_tree"
     bl_label = "Generate Mantis Tree from Selected"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    clear_tree : bpy.props.BoolProperty(default=False, name="Clear Tree?")
+    include_links_and_deforms : bpy.props.BoolProperty(default=False, name="Include Links, Deformers, and Drivers?")
+    generate_children : bpy.props.BoolProperty(default=False, name="Generate Object's Children, too? Warning: This may be slow.")
 
     @classmethod
     def poll(cls, context):
         return (mantis_poll_op(context))
 
+    # show the props dialog so the user can get the info they want
+    def invoke(self, context, event):
+        wm = context.window_manager
+        return wm.invoke_props_dialog(self)
+
     def execute(self, context):
         space = context.space_data
         path = space.path
@@ -1039,8 +1069,8 @@ class GenerateMantisTree(Operator):
 
         do_profile=False
 
-        #This will generate it in the current node tree and OVERWRITE!
-        node_tree.nodes.clear() # is this wise?
+        if self.clear_tree:
+            node_tree.nodes.clear() # is this wise?
 
         import cProfile
         from os import environ
@@ -1051,18 +1081,27 @@ class GenerateMantisTree(Operator):
         node_tree.is_exporting = True
         try:
             if do_profile:
-                cProfile.runctx("do_generate_armature(context.active_object, context, node_tree)", None, locals())
+                import pstats, io
+                from pstats import SortKey
+                with cProfile.Profile() as pr:
+                    do_generate_graph(context, node_tree, include_links_and_deforms =  self.include_links_and_deforms, generate_children=self.generate_children)
+                    s = io.StringIO()
+                    sortby = SortKey.TIME
+                    # sortby = SortKey.CUMULATIVE
+                    ps = pstats.Stats(pr, stream=s).strip_dirs().sort_stats(sortby)
+                    ps.print_stats(20) # print the top 20
+                    print(s.getvalue())
             else:
-                do_generate_armature(context.active_object, context, node_tree)
+                do_generate_graph(context, node_tree, include_links_and_deforms =  self.include_links_and_deforms, generate_children=self.generate_children)
             from .utilities import SugiyamaGraph
-            for n in node_tree.nodes:
-                n.select = True # Sugiyama sorting requires selection.
-            SugiyamaGraph(node_tree, 16)
-        except ImportError: # if for some reason Sugiyama isn't available
-            pass
+            try: # the graph gen code has selected the nodes to sort.
+                SugiyamaGraph(node_tree, 16)
+            except ImportError: # if for some reason Sugiyama isn't available
+                pass
         finally:
             node_tree.do_live_update = True
             node_tree.is_exporting = False
             node_tree.prevent_next_exec = True
 
         return {"FINISHED"}
+

+ 1 - 1
xForm_nodes.py

@@ -55,7 +55,7 @@ def set_object_parent(node):
     if parent_xForm_info.object_type  == '':
         return # no parent
     elif parent_xForm_info.object_type in ['armature', 'object']:
-        parent = data.objects.get(parent_xForm_info.parent_edit_name)
+        parent = data.objects.get(parent_xForm_info.self_edit_name)
         if parent_xForm_info.parent_edit_name and parent is None:
             raise GraphError(f"Could not get parent object for node {node}.")
         node.bGetObject().parent = parent