timers.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. from .utilities.utilities import (prRed, prGreen, prPurple, prWhite,
  2. prOrange,
  3. wrapRed, wrapGreen, wrapPurple, wrapWhite,
  4. wrapOrange,)
  5. # the first step is to get selection syncing working
  6. import bpy
  7. from bpy import context
  8. from .base_definitions import get_signature_from_edited_tree
  9. from bpy.types import Bone, PoseBone
  10. # just to prevent confusing with Python object:
  11. from bpy.types import Object as bpy_object
  12. SELECTION_POLL_RATE = 0.01
  13. ENABLED = True
  14. cached_selection_state = {}
  15. import bpy
  16. # The following node types represent objects that can be selected.
  17. WATCH_FOR_SELECTION = [ 'xFormBoneNode', 'xFormArmatureNode',
  18. 'xFormGeometryObject', 'xFormObjectInstance', 'xFormCurvePin',
  19. 'xFormGetBone', 'InputExistingGeometryObject']
  20. def get_signature_from_space(node, space):
  21. sig_path=[None,]
  22. for item in space.path[:-1]:
  23. if item.node_tree.nodes.active:
  24. sig_path.append(item.node_tree.nodes.active.name)
  25. else:
  26. return () # can't be determined, should not happen in normal use.
  27. if node:
  28. return tuple(sig_path+[node.name])
  29. else: # it's also necessary to do this if there is no path at all.
  30. return ()
  31. def get_visible_space_data(space_types={'NODE_EDITOR'}):
  32. visible_spaces = []
  33. for wm in bpy.data.window_managers:
  34. for window in wm.windows:
  35. for area in window.screen.areas:
  36. if area.type in space_types:
  37. if area.spaces.active:
  38. visible_spaces.append(area.spaces.active)
  39. return visible_spaces
  40. # Example usage: Get all active 3D Viewport space_data objects
  41. spaces = get_visible_space_data('VIEW_3D')
  42. for space in spaces:
  43. print(f"Found 3D Viewport: shading mode = {space.shading.type}")
  44. # TODO: now that I have this working...
  45. # - Selection State should be tracked and handled on a per-object basis
  46. # so Mantis should update selection state one item at a time
  47. # - finally, I want to extend this selection sync to show active constraints and modifiers
  48. # and to link up with viewport gizmo tools.
  49. # - Serialize this so that it sticks around when saving/loading file
  50. # Eventually:
  51. # - sync scene/object selection to node graph. I have the structure in place
  52. # but I still have to write the selection tracking code.
  53. # Potential small BUG?
  54. # - I am not ensuring the base/root tree is the same for the space and the nodes
  55. # - because I straight up do not store that in the selection_sync dicts
  56. # - easy to fix, will do it when clearing TODO's here.
  57. def timer_update_selection_sync():
  58. try:
  59. if not ENABLED: return None
  60. root_tree = None
  61. tree_path = None
  62. parsed_tree = None
  63. spaces = get_visible_space_data()
  64. if not spaces:
  65. return SELECTION_POLL_RATE
  66. selection_state_start = cached_selection_state.copy()
  67. selection_state = cached_selection_state.copy()
  68. current_tree_selected_total = 0
  69. # we can't get UI context in this function so we have to checc all of them
  70. #maybe the tree itself can load the timer when it is opened in the node editor?
  71. for space in spaces:
  72. # if we are in the node editor, manage node selection
  73. if space.type == "NODE_EDITOR":
  74. if not space.path:
  75. return SELECTION_POLL_RATE # empty tree
  76. root_tree = space.path[0].node_tree
  77. if root_tree is None:
  78. return SELECTION_POLL_RATE
  79. if root_tree.bl_idname != 'MantisTree':
  80. return SELECTION_POLL_RATE
  81. tree_path = [path_item.node_tree for path_item in space.path]
  82. if len(tree_path[-1].nodes) == 0:
  83. return SELECTION_POLL_RATE # empty graph
  84. parsed_tree = root_tree.parsed_tree
  85. if tree_path[-1].nodes.active is not None:
  86. active_ui_signature = (get_signature_from_space(tree_path[-1].nodes.active, space))
  87. else:
  88. active_ui_signature = None
  89. for n in tree_path[-1].nodes:
  90. if n.bl_idname not in WATCH_FOR_SELECTION: continue
  91. selection_state[ui_signature := get_signature_from_space(n, space)] = n.select
  92. current_tree_selected_total += int(n.select)
  93. if parsed_tree is None:
  94. return SELECTION_POLL_RATE
  95. cached_selection_state.update(selection_state)
  96. # We only fire the update if things have changed.
  97. if selection_state != selection_state_start:
  98. from bpy import context
  99. view_layer = context.view_layer
  100. deselect = not bool(current_tree_selected_total)
  101. try:
  102. update_scene_selection_from_node_graph_selection_sync(
  103. active_ui_signature, view_layer,
  104. selection_state, deselect)
  105. except Exception as e:
  106. prRed (e)
  107. return SELECTION_POLL_RATE
  108. except Exception as e:
  109. from .__init__ import DOERROR
  110. if DOERROR:
  111. print ("Error in Selection Sync")
  112. print (e)
  113. raise e
  114. return SELECTION_POLL_RATE
  115. def set_bone_selection(pose_bone, selection_state):
  116. # in some older blender versions, select was a bone property
  117. if hasattr(pose_bone, 'select'):
  118. pose_bone.select = selection_state
  119. elif hasattr(pose_bone.bone, 'select'):
  120. pose_bone.bone.select = selection_state
  121. def update_scene_selection_from_node_graph_selection_sync(
  122. active_ui_signature, view_layer,
  123. selection_state, deselect ):
  124. from .base_definitions import selection_sync_node_to_object
  125. # prWhite("Updating Scene Selection from Node Graph Selection")
  126. deselect_do_once = []
  127. for ui_signature, selected in selection_state.items():
  128. this_one_is_active = False
  129. if ui_signature == active_ui_signature: this_one_is_active = True
  130. for object_key in selection_sync_node_to_object.get(ui_signature, []):
  131. id_data = bpy.data.objects.get(object_key[0])
  132. if object_key[1]: # we have a sub_value, it's a bone
  133. if deselect:
  134. if object_key[0] not in deselect_do_once:
  135. for pb in id_data.pose.bones:
  136. set_bone_selection(pb, False)
  137. deselect_do_once.append(object_key[0])
  138. else:
  139. pb = id_data.pose.bones[object_key[1]]
  140. set_bone_selection(pb, selected)
  141. if this_one_is_active: # set the active bone
  142. id_data.data.bones.active = pb.bone
  143. # now do its parent object...
  144. if selected:
  145. id_data.select_set(True, view_layer=view_layer)
  146. if this_one_is_active:
  147. view_layer.objects.active = id_data
  148. else: # or the sub_value is None, this is an object
  149. ob = id_data
  150. if deselect: selected = False
  151. ob.select_set(selected, view_layer=view_layer)
  152. if this_one_is_active and not deselect:
  153. view_layer.objects.active = ob