timers.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  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. sig_path.append(item.node_tree.nodes.active.name)
  24. return tuple(sig_path+[node.name])
  25. def get_visible_space_data(space_types={'NODE_EDITOR'}):
  26. visible_spaces = []
  27. for wm in bpy.data.window_managers:
  28. for window in wm.windows:
  29. for area in window.screen.areas:
  30. if area.type in space_types:
  31. if area.spaces.active:
  32. visible_spaces.append(area.spaces.active)
  33. return visible_spaces
  34. # Example usage: Get all active 3D Viewport space_data objects
  35. spaces = get_visible_space_data('VIEW_3D')
  36. for space in spaces:
  37. print(f"Found 3D Viewport: shading mode = {space.shading.type}")
  38. # TODO: now that I have this working...
  39. # - Selection State should be tracked and handled on a per-object basis
  40. # so Mantis should update selection state one item at a time
  41. # - every object should be kept in the cache at all times.
  42. # - a deselect event should check only the current tree for empty selection
  43. # - but deselect should deselect anything that is tracked by Mantis
  44. # - Mantis node <==> UI node relationships should be cached instead of using the
  45. # tree cache, and ideally, saved in the Scene object or something
  46. # - I'll probably do like a JSON or something for that, maybe a pickle
  47. # - anyhow the important thing is I need a bi-directional dictionary so I can
  48. # make the selection sync happen both ways
  49. # - finally, I want to extend this selection sync to show active constraints and modifiers
  50. # and to link up with viewport gizmo tools.
  51. def timer_update_selection_sync():
  52. if not ENABLED: return None
  53. root_tree = None
  54. tree_path = None
  55. parsed_tree = None
  56. spaces = get_visible_space_data()
  57. if not spaces:
  58. return SELECTION_POLL_RATE
  59. selection_state_start = cached_selection_state.copy()
  60. selection_state = cached_selection_state.copy()
  61. ui_signatures = list(cached_selection_state.keys())
  62. current_tree_selected_total = 0
  63. # we can't get UI context in this function so we have to checc all of them
  64. #maybe the tree itself can load the timer when it is opened in the node editor?
  65. for space in spaces:
  66. # if we are in the node editor, manage node selection
  67. if space.type == "NODE_EDITOR":
  68. root_tree = space.path[0].node_tree
  69. parsed_tree = root_tree.parsed_tree
  70. tree_path = [path_item.node_tree for path_item in space.path]
  71. if root_tree is None:
  72. return SELECTION_POLL_RATE
  73. if root_tree.bl_idname != 'MantisTree':
  74. return SELECTION_POLL_RATE
  75. active_ui_signature = (get_signature_from_space(tree_path[-1].nodes.active, space))
  76. for n in tree_path[-1].nodes:
  77. if n.bl_idname not in WATCH_FOR_SELECTION: continue
  78. ui_signatures.append( ui_signature := get_signature_from_space(n, space))
  79. selection_state[ui_signature] = n.select
  80. current_tree_selected_total += int(n.select)
  81. if parsed_tree is None:
  82. return SELECTION_POLL_RATE
  83. cached_selection_state.update(selection_state)
  84. # We only fire the update if things have changed.
  85. if selection_state != selection_state_start:
  86. from bpy import context
  87. view_layer = context.view_layer
  88. deselect = bool(current_tree_selected_total)
  89. try:
  90. update_scene_selection_from_selection_sync(
  91. parsed_tree, ui_signatures, active_ui_signature, view_layer,
  92. selection_state, deselect)
  93. except Exception as e:
  94. prRed (e)
  95. return SELECTION_POLL_RATE
  96. return SELECTION_POLL_RATE
  97. # 1 get the selected nodes
  98. # 2 find mantis nodes that have the ui_signature of the node
  99. # 3 get their b-objects and select them
  100. def update_scene_selection_from_selection_sync(
  101. parsed_tree, ui_signatures, active_ui_signature, view_layer,
  102. selection_state, deselect ):
  103. # first check if this is a deselect event.
  104. # if it is, do not change the active object!
  105. # that makes the armature switch modes, it's annoying
  106. deselect=False
  107. if sum(selection_state.values()) == 0:
  108. deselect=True
  109. prWhite("Updating Scene Selection from Node Graph Selection")
  110. for mantis_node in parsed_tree.values():
  111. this_one_is_active = False
  112. if not hasattr(mantis_node, 'bGetObject'): continue
  113. if mantis_node.node_type != 'XFORM': continue
  114. if mantis_node.ui_signature in ui_signatures:
  115. selected = selection_state[mantis_node.ui_signature]
  116. if mantis_node.ui_signature == active_ui_signature:
  117. this_one_is_active = True
  118. ob = mantis_node.bGetObject()
  119. if isinstance(ob, PoseBone):
  120. if deselect:
  121. ob.select = False
  122. continue
  123. ob.select = selected
  124. if this_one_is_active: # set the active bone
  125. ob.id_data.data.bones.active = ob.bone
  126. # now do its parent object...
  127. if selected:
  128. if ob.id_data.select_get(view_layer=view_layer) == False:
  129. ob.id_data.select_set(True, view_layer=view_layer)
  130. if this_one_is_active:
  131. view_layer.objects.active = ob.id_data
  132. elif isinstance(ob, bpy_object):
  133. if ob.select_get(view_layer=view_layer) != selected:
  134. ob.select_set(selected, view_layer=view_layer)
  135. if this_one_is_active and not deselect:
  136. view_layer.objects.active = ob