overlays.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. from ..utilities.utilities import (prRed, prGreen, prPurple, prWhite,
  2. prOrange,
  3. wrapRed, wrapGreen, wrapPurple, wrapWhite,
  4. wrapOrange,)
  5. from ..base_definitions import OverlayApplyError, GraphError
  6. # For now, this is not yet implemented. lol
  7. def parser_handle_text(text):
  8. return '$' + text
  9. def get_socket_idname_data_type(socket_idname):
  10. data_type = str
  11. match socket_idname:
  12. case "IntSocket" | "UnsignedIntSocket":
  13. data_type = int
  14. case "FloatSocket" | "FloatPositiveSocket" | "FloatFactorSocket" | "FloatAngleSocket":
  15. data_type = float
  16. return (data_type)
  17. # If a $ character is in a value, it is an overlay request
  18. # in the root tree, supply it immediately, otherwise tag this node
  19. # once we finish pulling data from the tree we handle the overlay.
  20. def handle_overlay_detect(node, socket_id, socket_idname, value) -> str:
  21. mContext = node.mContext
  22. if node.signature[0] is None and len(node.signature) == 2:
  23. value = fill_root_tree_overlays(mContext, socket_idname, value)
  24. else: # since it is not root, we have to tag it.
  25. mContext.tagged_for_overlay.append((node.signature, socket_id))
  26. return value
  27. # The root tree doesn't have any interfaces to look up so we can fill it immediately
  28. def fill_root_tree_overlays(mContext, socket_idname, value):
  29. root_tree = mContext.base_tree
  30. root_items_tree = root_tree.interface.items_tree
  31. socket_data_type = get_socket_idname_data_type(socket_idname)
  32. for key, interface_item in root_items_tree.items():
  33. if interface_item.item_type != "SOCKET": continue
  34. if interface_item.in_out != "INPUT": continue
  35. # then what the interface provides is determined by its idname in turn
  36. match interface_item.bl_socket_idname:
  37. case "IntSocket" | "BooleanSocket":
  38. if socket_data_type in {int, float} and '$'+key == value:
  39. # TODO this is the wrong way to do this, replace this with parser later
  40. value = socket_data_type(interface_item.default_int)
  41. case "FloatSocket":
  42. if socket_data_type in {int, float} and '$'+key == value:
  43. value = socket_data_type(interface_item.default_float)
  44. case "StringSocket" | 'BoneCollectionSocket':
  45. if socket_data_type == str:
  46. value = socket_data_type(value.replace("$"+key, interface_item.default_string))
  47. return value
  48. def get_interfaces_for_node(mantis_node,):
  49. all_interface_nodes = mantis_node.mContext.input_interface_nodes
  50. my_interfaces = []
  51. for index in range(len(mantis_node.signature)-1):
  52. if index == 0: continue # the root isn't relevant unless I refactor to make an interface for it
  53. current_schema_interface_index = mantis_node.generated_at_schema_index[index+1]
  54. interfaces_from_schema = all_interface_nodes[ ( None, *mantis_node.signature[1:index+1] ) ]
  55. # we need to get ONLY the interfaces up to the current index, which is 0 for non-schema
  56. my_interfaces.extend( interfaces_from_schema[:current_schema_interface_index+1] ) # +1 (slice end is not inclusive)
  57. return my_interfaces
  58. def get_base_tree_overlay_values(items_tree, name_to_type_map, overlay_keys, root_overlay_values):
  59. for interface_item in items_tree:
  60. if interface_item.item_type != "SOCKET": continue
  61. if interface_item.in_out != "INPUT": continue
  62. base_value=None
  63. match interface_item.bl_socket_idname:
  64. case "IntSocket" | "BooleanSocket":
  65. # TODO parser for number types
  66. base_value = interface_item.default_int
  67. case "FloatSocket":
  68. base_value = interface_item.default_float
  69. case "StringSocket" | "BoneCollectionSocket":
  70. base_value = interface_item.default_string
  71. if base_value is not None:
  72. root_overlay_values[interface_item.name] = base_value
  73. overlay_keys.add(interface_item.name)
  74. name_to_type_map[interface_item.name] = interface_item.bl_socket_idname
  75. def get_overlay_keys_types_ids(my_interfaces, overlay_keys, name_to_type_map, name_to_id_map):
  76. # We need to get all the relevant keys to search for, all the types for error messages
  77. # and all the IDs because interface nodes store stuff by ID, not by name
  78. # and if there is a duplicate name, it will shadow - too bad!
  79. for interface_node in my_interfaces:
  80. # fill in the overlay keys and name-to-type-map (used for errors)
  81. interface_items_tree = interface_node.get_tree_interface_items()
  82. for interface_item in interface_items_tree:
  83. if interface_item.item_type != 'SOCKET': continue
  84. if interface_item.in_out != 'INPUT': continue
  85. if interface_item.name in forbidden_key_names: continue
  86. for substr in forbidden_in_key_name:
  87. if substr in interface_item.name: break
  88. else: # if we do not find a bad substr
  89. name_to_id_map[interface_item.name]=interface_item.identifier
  90. name_to_type_map[interface_item.name] = interface_item.bl_socket_idname
  91. overlay_keys.add(interface_item.name)
  92. # For schema interfaces, we also need to get the length and index.
  93. if 'Index' in interface_node.outputs:
  94. overlay_keys.add('Index'); overlay_keys.add('Length')
  95. if 'Index' in overlay_keys:
  96. name_to_id_map['Index']='Index'; name_to_type_map['Index']= "IntSocket"
  97. name_to_id_map['Length']='Length'; name_to_type_map['Length']= "IntSocket"
  98. STRING_PADDING = 4
  99. overlay_valid_socket_types = [
  100. "FloatSocket", "FloatPositiveSocket", "FloatFactorSocket",
  101. "FloatAngleSocket", "UnsignedIntSocket", "IntSocket", "StringSocket",
  102. ]
  103. # Do not use these input names as overlay keys:
  104. forbidden_key_names = ["",]
  105. forbidden_in_key_name = ['$',]
  106. def node_scan_overlays(node, socket_id,):
  107. from ..internal_nodes import AutoGenNode
  108. overlay_data_type = str; socket = None
  109. try:
  110. socket = node.inputs[socket_id]
  111. except KeyError:
  112. socket = node.outputs[socket_id]
  113. if socket_id == 'Schema Length':
  114. # this happens in schema solve init, the actual value lives in an autogen node...
  115. # it doesn't matter, we only need the overlay value and the intended type
  116. value = node.evaluate_input('Schema Length')
  117. overlay_data_type = int
  118. else:
  119. value = node.parameters[socket_id]
  120. if overlay_data_type == str: # It is still default, not set to int by the above
  121. if not socket or not socket.socket_type: # can't determine.
  122. prRed(f"WARNING: cannot determine data type for overlay"
  123. f"variable {socket_id} in {node}" )
  124. overlay_data_type = type(node.parameters[list(node.outputs.values())[0].name])
  125. else: # we have the mantis socket which has a type field
  126. overlay_data_type = get_socket_idname_data_type(socket.socket_type)
  127. if not isinstance(value, str): return
  128. # First step: get the interface nodes and the overlay keys
  129. # this should only check interfaces that are ancestors of this node
  130. my_interfaces = get_interfaces_for_node(mantis_node=node)
  131. # the my_interfaces list is sorted in REVERSE order of importance
  132. # Next, get all the overlay providers.
  133. name_to_type_map, root_overlay_values, name_to_id_map = {}, {}, {}
  134. overlay_keys = set()
  135. # First, we'll get the base tree's default inputs
  136. items_tree = node.mContext.base_tree.interface.items_tree
  137. get_base_tree_overlay_values(items_tree, name_to_type_map, overlay_keys, root_overlay_values)
  138. get_overlay_keys_types_ids(my_interfaces, overlay_keys, name_to_type_map, name_to_id_map)
  139. # sort by reverse length so we find "CatsAndFrogs" before "Cats"
  140. overlay_keys = sorted(overlay_keys, key= lambda a :-1*len(a))
  141. # First I need to break it into substrings based on quotation marks
  142. from ..utilities.utilities import split_quoted_substrings_to_strings
  143. quoted_substrings = split_quoted_substrings_to_strings(value)
  144. overlay_tries = []
  145. for quoted_substring in quoted_substrings:
  146. overlay_tries.extend(quoted_substring.split("$"))
  147. if overlay_data_type == str: result = overlay_tries[0] # the 0th element is always before the split character
  148. else: result = 0# TODO HACK later use the parser feature
  149. # the reason for the difference between overlay_try and key is because
  150. # we don't actually know if the $text part has a key in it yet!
  151. for overlay_try in overlay_tries[1:]:
  152. if not overlay_try: continue # some of these are empty
  153. overlay_resolved_value = "$" + overlay_try
  154. for key in overlay_keys:
  155. if key in overlay_try:
  156. overlay_resolved_value = "$" + key
  157. for interface in reversed(my_interfaces): # since this is reversed when we build it
  158. actual_key = name_to_id_map.get(key)
  159. socket = interface.inputs.get(actual_key)
  160. if key in ['Index', 'Length']:
  161. socket = interface.outputs.get(actual_key)
  162. if socket:
  163. overlay_value = None
  164. try:
  165. overlay_value = interface.evaluate_input(actual_key)
  166. except KeyError:
  167. if (socket_type := name_to_type_map.get(key) or 'NOT FOUND') in overlay_valid_socket_types:
  168. raise GraphError(f"Error: Variable {key} is not supported because it has "
  169. f"an invalid input in the node group: {interface.signature[:-1]}.")
  170. raise GraphError( f"Error: Variable {key} is not supported because it is "
  171. f"type {socket_type}" )
  172. # NOTE that the above can also be caused by any BUG occuring in node group update
  173. # because it causes the interface to be invalid for the UI node that spawns the interface node.
  174. if overlay_data_type == str:
  175. if isinstance(overlay_value, (float, int)):
  176. overlay_value = str(overlay_value).zfill(STRING_PADDING)
  177. assert (isinstance(overlay_value, str)), f"Error: Variable {key} is not supported because it is type {type(overlay_value)}"
  178. overlay_resolved_value = overlay_try.replace(key, overlay_value)
  179. else:
  180. overlay_resolved_value = overlay_value
  181. break
  182. else: # we'll try the base tree
  183. if (base_tree_result := root_overlay_values.get(key)) is not None:
  184. if overlay_data_type == str and not isinstance(base_tree_result, str):
  185. base_tree_result = str(base_tree_result).zfill(STRING_PADDING)
  186. overlay_resolved_value = base_tree_result
  187. else:
  188. prOrange(f"WARN: no overlay value applied for {node.ui_signature} for key {key}")
  189. break # we found the key and are done here. One found Key per overlay-try
  190. # this is where we can catch the errors if the type of the resolved value was wrong
  191. if type(overlay_resolved_value) != overlay_data_type:
  192. raise OverlayApplyError( f"Could not apply overlay variable to {node}::{socket_id}.")
  193. result += overlay_resolved_value
  194. node.parameters[socket_id] = result
  195. return result # because sometimes I need to get the result, too