Prechádzať zdrojové kódy

Improve Selection Sync

This commit improves selection sync with persistent dicts that store the actual
mapping between ui nodes and created objects
it is faster because I don't have to search for the mantis nodes that are spawned
by the selected ui nodes.
This commit also does half of the setup for syncing 3D View selection to the node graph
Joseph Brandenburg 1 mesiac pred
rodič
commit
555c99e212
4 zmenil súbory, kde vykonal 68 pridanie a 57 odobranie
  1. 20 0
      base_definitions.py
  2. 1 0
      misc_nodes.py
  3. 41 53
      timers.py
  4. 6 4
      xForm_nodes.py

+ 20 - 0
base_definitions.py

@@ -16,6 +16,9 @@ from .mantis_dataclasses import MantisSocketTemplate
 FLOAT_EPSILON=0.0001 # used to check against floating point inaccuracy
 FLOAT_EPSILON=0.0001 # used to check against floating point inaccuracy
 links_sort_key= lambda a : (-a.multi_input_sort_id, -a.sub_sort_id)
 links_sort_key= lambda a : (-a.multi_input_sort_id, -a.sub_sort_id)
 
 
+selection_sync_node_to_object = {} # what objects a node spawns
+selection_sync_object_to_node = {} # what node an object comes from
+
 def TellClasses():
 def TellClasses():
     #Why use a function to do this? Because I don't need every class to register.
     #Why use a function to do this? Because I don't need every class to register.
     return [ MantisTree,
     return [ MantisTree,
@@ -984,6 +987,23 @@ class MantisNode:
             if hasattr(constraint_or_deformer, "subtarget"):
             if hasattr(constraint_or_deformer, "subtarget"):
                 constraint_or_deformer.subtarget = subtarget
                 constraint_or_deformer.subtarget = subtarget
 
 
+    # This function is for registering the objects a node creates in the global dict
+    # The goal is to have a global map of node <==> object and object <==> node
+    # so that we can do e.g. selection syncing, etc.
+    # NOTE: I'm not registering with mContext here, that happens elsewhere
+    #       but it is probably a good idea to refactor this together.
+    def bRegisterObject(self, ob_type, ob_value, sub_ob_value = None):
+        ui_signature = self.ui_signature
+        if selection_sync_node_to_object.get(ui_signature) is None:
+            selection_sync_node_to_object[ui_signature] = []
+        ob_name = ob_value.name
+        sub_ob_name = sub_ob_value.name if sub_ob_value else None
+        key = (ob_name, sub_ob_name)
+        if ob_type in ['OBJECT', 'BONE']:
+            selection_sync_object_to_node[key] = ui_signature
+            selection_sync_node_to_object[ui_signature].append( key )
+
+
 # PASSES DEFINED HERE!
 # PASSES DEFINED HERE!
     def bPrepare(self, bContext=None):
     def bPrepare(self, bContext=None):
         return # This one runs BEFORE anything else
         return # This one runs BEFORE anything else

+ 1 - 0
misc_nodes.py

@@ -1236,6 +1236,7 @@ class InputExistingGeometryObject(MantisNode):
         if ob is None and name:
         if ob is None and name:
             prRed(f"No object found with name {name} in {self}")
             prRed(f"No object found with name {name} in {self}")
         self.bObject=ob
         self.bObject=ob
+        self.bRegisterObject('OBJECT', ob)
         self.prepared, self.executed = True, True
         self.prepared, self.executed = True, True
 
 
     def bGetObject(self, mode=''):
     def bGetObject(self, mode=''):

+ 41 - 53
timers.py

@@ -47,20 +47,19 @@ spaces = get_visible_space_data('VIEW_3D')
 for space in spaces:
 for space in spaces:
     print(f"Found 3D Viewport: shading mode = {space.shading.type}")
     print(f"Found 3D Viewport: shading mode = {space.shading.type}")
 
 
-
 # TODO: now that I have this working...
 # TODO: now that I have this working...
 #  - Selection State should be tracked and handled on a per-object basis
 #  - Selection State should be tracked and handled on a per-object basis
 #     so Mantis should update selection state one item at a time
 #     so Mantis should update selection state one item at a time
-#  - every object should be kept in the cache at all times.
-#  - a deselect event should check only the current tree for empty selection
-#  - but deselect should deselect anything that is tracked by Mantis
-#  - Mantis node <==> UI node relationships should be cached instead of using the
-#     tree cache, and ideally, saved in the Scene object or something
-#  - I'll probably do like a JSON or something for that, maybe a pickle
-#  - anyhow the important thing is I need a bi-directional dictionary so I can
-#     make the selection sync happen both ways
 #  - finally, I want to extend this selection sync to show active constraints and modifiers
 #  - finally, I want to extend this selection sync to show active constraints and modifiers
 #     and to link up with viewport gizmo tools.
 #     and to link up with viewport gizmo tools.
+#  - Serialize this so that it sticks around when saving/loading file
+# Eventually:
+#  - sync scene/object selection to node graph. I have the structure in place
+#     but I still have to write the selection tracking code.
+# Potential small BUG?
+#  - I am not ensuring the base/root tree is the same for the space and the nodes
+#  - because I straight up do not store that in the selection_sync dicts
+#  - easy to fix, will do it when clearing TODO's here.
 
 
 def timer_update_selection_sync():
 def timer_update_selection_sync():
     if not ENABLED: return None
     if not ENABLED: return None
@@ -73,7 +72,6 @@ def timer_update_selection_sync():
 
 
     selection_state_start = cached_selection_state.copy()
     selection_state_start = cached_selection_state.copy()
     selection_state = cached_selection_state.copy()
     selection_state = cached_selection_state.copy()
-    ui_signatures = list(cached_selection_state.keys())
     current_tree_selected_total = 0
     current_tree_selected_total = 0
 
 
     # we can't get UI context in this function so we have to checc all of them
     # we can't get UI context in this function so we have to checc all of them
@@ -91,8 +89,7 @@ def timer_update_selection_sync():
             active_ui_signature = (get_signature_from_space(tree_path[-1].nodes.active, space))
             active_ui_signature = (get_signature_from_space(tree_path[-1].nodes.active, space))
             for n in tree_path[-1].nodes:
             for n in tree_path[-1].nodes:
                 if n.bl_idname not in WATCH_FOR_SELECTION: continue
                 if n.bl_idname not in WATCH_FOR_SELECTION: continue
-                ui_signatures.append( ui_signature := get_signature_from_space(n, space))
-                selection_state[ui_signature] = n.select
+                selection_state[ui_signature := get_signature_from_space(n, space)] = n.select
                 current_tree_selected_total += int(n.select)
                 current_tree_selected_total += int(n.select)
     
     
     if parsed_tree is None:
     if parsed_tree is None:
@@ -103,55 +100,46 @@ def timer_update_selection_sync():
         
         
         from bpy import context
         from bpy import context
         view_layer = context.view_layer
         view_layer = context.view_layer
-        deselect = bool(current_tree_selected_total)
+        deselect = not bool(current_tree_selected_total)
         try:
         try:
-            update_scene_selection_from_selection_sync(
-                parsed_tree, ui_signatures, active_ui_signature, view_layer,
+            update_scene_selection_from_node_graph_selection_sync(
+                active_ui_signature, view_layer,
                 selection_state, deselect)
                 selection_state, deselect)
         except Exception as e:
         except Exception as e:
             prRed (e)
             prRed (e)
             return SELECTION_POLL_RATE
             return SELECTION_POLL_RATE
     return SELECTION_POLL_RATE
     return SELECTION_POLL_RATE
 
 
-
-# 1 get the selected nodes
-# 2 find mantis nodes that have the ui_signature of the node
-# 3 get their b-objects and select them
-def update_scene_selection_from_selection_sync(
-        parsed_tree, ui_signatures, active_ui_signature, view_layer,
+def update_scene_selection_from_node_graph_selection_sync(
+        active_ui_signature, view_layer,
         selection_state, deselect ):
         selection_state, deselect ):
-    # first check if this is a deselect event.
-    # if it is, do not change the active object!
-    # that makes the armature switch modes, it's annoying
-    deselect=False
-    if sum(selection_state.values()) == 0:
-        deselect=True
+    from .base_definitions import ( selection_sync_node_to_object,
+                                    selection_sync_object_to_node, )
     prWhite("Updating Scene Selection from Node Graph Selection")
     prWhite("Updating Scene Selection from Node Graph Selection")
-    for mantis_node in parsed_tree.values():
+    deselect_do_once = []
+    for ui_signature, selected in selection_state.items():
         this_one_is_active = False
         this_one_is_active = False
-        if not hasattr(mantis_node, 'bGetObject'): continue
-        if mantis_node.node_type != 'XFORM': continue
-        if mantis_node.ui_signature in ui_signatures:
-            selected = selection_state[mantis_node.ui_signature]
-            if mantis_node.ui_signature == active_ui_signature:
-                this_one_is_active = True
-            ob = mantis_node.bGetObject()
-            if isinstance(ob, PoseBone):
-                if deselect:
-                    ob.select = False
-                    continue
-                ob.select = selected
-                if this_one_is_active: # set the active bone
-                    ob.id_data.data.bones.active = ob.bone
-                # now do its parent object...
-                if selected:
-                    if ob.id_data.select_get(view_layer=view_layer) == False:
-                        ob.id_data.select_set(True, view_layer=view_layer)
-                    if this_one_is_active:
-                        view_layer.objects.active = ob.id_data
-            elif isinstance(ob, bpy_object):
-                if ob.select_get(view_layer=view_layer) != selected:
-                    ob.select_set(selected, view_layer=view_layer)
+        if ui_signature == active_ui_signature: this_one_is_active = True
+        for object_key in selection_sync_node_to_object.get(ui_signature, []):
+            id_data = bpy.data.objects.get(object_key[0])
+            if object_key[1]: # we have a sub_value, it's a bone
+                if deselect and object_key[0] not in deselect_do_once:
+                    for pb in id_data.pose.bones:
+                        pb.select = False
+                    deselect_do_once.append(object_key[0])
+                else:
+                    ob = id_data.pose.bones[object_key[1]]
+                    ob.select = selected
+                    if this_one_is_active: # set the active bone
+                        id_data.data.bones.active = ob.bone
+                    # now do its parent object...
+                    if selected:
+                        id_data.select_set(True, view_layer=view_layer)
+                        if this_one_is_active:
+                            view_layer.objects.active = id_data
+            else: # or the sub_value is None, this is an object
+                ob = id_data
+                if deselect: selected = False
+                ob.select_set(selected, view_layer=view_layer)
                 if this_one_is_active and not deselect:
                 if this_one_is_active and not deselect:
-                    view_layer.objects.active = ob
-
+                    view_layer.objects.active = ob

+ 6 - 4
xForm_nodes.py

@@ -181,6 +181,7 @@ class xFormArmature(xFormNode):
         ob.id_properties_clear()
         ob.id_properties_clear()
 
 
         self.bObject = ob.name
         self.bObject = ob.name
+        self.bRegisterObject('OBJECT', ob)
         ob.matrix_world = matrix.copy()
         ob.matrix_world = matrix.copy()
         ob.data.pose_position = 'REST'
         ob.data.pose_position = 'REST'
         set_object_parent(self)
         set_object_parent(self)
@@ -331,6 +332,7 @@ class xFormBone(xFormNode):
             raise RuntimeError("Could not create edit bone: ", name)
             raise RuntimeError("Could not create edit bone: ", name)
         assert (eb.name), "Bone must have a name."
         assert (eb.name), "Bone must have a name."
         self.bObject = eb.name
         self.bObject = eb.name
+        self.bRegisterObject('BONE', xF, eb)
         # The bone should have relationships going in at this point.
         # The bone should have relationships going in at this point.
 
 
         self.bSetParent(eb)
         self.bSetParent(eb)
@@ -655,7 +657,7 @@ class xFormGeometryObject(xFormNode):
             self_edit_name=self.bObject.name,
             self_edit_name=self.bObject.name,
         )
         )
         self.parameters['xForm Out'] = my_info
         self.parameters['xForm Out'] = my_info
-
+        self.bRegisterObject('OBJECT', self.bObject)
         self.prepared = True
         self.prepared = True
 
 
     def bTransformPass(self, bContext = None,):
     def bTransformPass(self, bContext = None,):
@@ -743,7 +745,7 @@ class xFormObjectInstance(xFormNode):
             self_edit_name=self.bObject.name,
             self_edit_name=self.bObject.name,
         )
         )
         self.parameters['xForm Out'] = my_info
         self.parameters['xForm Out'] = my_info
-
+        self.bRegisterObject('OBJECT', self.bObject)
         self.prepared = True
         self.prepared = True
 
 
     def bTransformPass(self, bContext = None,):
     def bTransformPass(self, bContext = None,):
@@ -832,7 +834,7 @@ class xFormCurvePin(xFormNode):
             ob.empty_display_size = 0.10
             ob.empty_display_size = 0.10
 
 
         self.bObject = ob
         self.bObject = ob
-
+        self.bRegisterObject('OBJECT', self.bObject)
         reset_object_data(ob)
         reset_object_data(ob)
 
 
         node_line = trace_single_line(self, "Parent Curve")[0][1:] # slice excludes self
         node_line = trace_single_line(self, "Parent Curve")[0][1:] # slice excludes self
@@ -922,7 +924,6 @@ class xFormCurvePin(xFormNode):
     def bGetObject(self, mode = 'POSE'):
     def bGetObject(self, mode = 'POSE'):
         return self.bObject
         return self.bObject
 
 
-
 # special thank-you to Natalie Cuthbert, who submitted a patch for this
 # special thank-you to Natalie Cuthbert, who submitted a patch for this
 # it turned out, I already had this patch laying around
 # it turned out, I already had this patch laying around
 # because I was too busy with grad school I didn't succeed in collaborating
 # because I was too busy with grad school I didn't succeed in collaborating
@@ -958,4 +959,5 @@ class xFormGetBone(xFormNode):
                 case 'POSE':
                 case 'POSE':
                     bone = armature.pose.bones.get(self.evaluate_input("Bone"))
                     bone = armature.pose.bones.get(self.evaluate_input("Bone"))
         assert self.bObject is not None, f"{self} failed to get the desired bone. Check if the bone name exists."
         assert self.bObject is not None, f"{self} failed to get the desired bone. Check if the bone name exists."
+        self.bRegisterObject('BONE', armature, bone)
         return bone
         return bone