overlays.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. # For now, this is not yet implemented. lol
  2. def parser_handle_text(text):
  3. return '$' + text
  4. def get_socket_idname_data_type(socket_idname):
  5. data_type = str
  6. match socket_idname:
  7. case "IntSocket" | "UnsignedIntSocket":
  8. data_type = int
  9. case "FloatSocket" | "FloatPositiveSocket" | "FloatFactorSocket" | "FloatAngleSocket":
  10. data_type = float
  11. return (data_type)
  12. # If a $ character is in a value, it is an overlay request
  13. # in the root tree, supply it immediately, otherwise tag this node
  14. # once we finish pulling data from the tree we handle the overlay.
  15. def handle_overlay_detect(node, socket_id, socket_idname, value) -> str:
  16. mContext = node.mContext
  17. root_tree = mContext.base_tree
  18. root_items_tree = root_tree.interface.items_tree
  19. # detect if it is in the root tree, if so, handle immediately
  20. out_value = value
  21. # what this recieves is determined by its idname
  22. socket_data_type = get_socket_idname_data_type(socket_idname)
  23. # ONLY for root node trees, we can just directly fill this.
  24. if node.signature[0] is None and len(node.signature) == 2:
  25. for key, interface_item in root_items_tree.items():
  26. if interface_item.item_type != "SOCKET": continue
  27. if interface_item.in_out != "INPUT": continue
  28. # then what the interface provides is determined by its idname in turn
  29. # NOTE I'm not 100% sure checking the socket_data_type is the right thing to do here
  30. match interface_item.bl_socket_idname:
  31. case "IntSocket" | "BooleanSocket":
  32. if socket_data_type in {int, float} and '$'+key == out_value:
  33. # TODO this is the wrong way to do this, replace this with parser later
  34. out_value = socket_data_type(interface_item.default_int)
  35. case "FloatSocket":
  36. if socket_data_type in {int, float} and '$'+key == out_value:
  37. out_value = socket_data_type(interface_item.default_float)
  38. case "StringSocket" | 'BoneCollectionSocket':
  39. if socket_data_type == str:
  40. out_value = socket_data_type(out_value.replace("$"+key, interface_item.default_string))
  41. else: # since it is not root, we have to tag it.
  42. mContext.tagged_for_overlay.append((node.signature, socket_id))
  43. return out_value
  44. STRING_PADDING = 4
  45. overlay_valid_socket_types = [
  46. "FloatSocket", "FloatPositiveSocket", "FloatFactorSocket",
  47. "FloatAngleSocket", "UnsignedIntSocket", "IntSocket", "StringSocket",
  48. ]
  49. # Do not use these input names as overlay keys:
  50. forbidden_key_names = ["",]
  51. forbidden_in_key_name = ['$',]
  52. def node_scan_overlays(node, socket_id,):
  53. from ..misc_nodes import SimpleInputNode
  54. from ..internal_nodes import AutoGenNode
  55. from ..utilities.utilities import get_ui_node
  56. from ..utilities.node_common import GraphError
  57. interface_nodes = node.mContext.interface_nodes
  58. if not isinstance(node, AutoGenNode):
  59. ui_node = get_ui_node(node.ui_signature, node.base_tree)
  60. # we'll assume that the overlay tag system checked if this is linked.
  61. # TODO this is probably redundant with the check above this
  62. # First let's check if it is a simple input node, by checking outputs that are marked "input"
  63. for socket in ui_node.outputs:
  64. # weird edge case for simple input nodes with unnamed sockets
  65. # the socket.identifier is not used by Mantis but it defaults to 'socket'\
  66. if socket.identifier == socket_id and socket.input == True: break
  67. else:
  68. for socket in ui_node.inputs:
  69. if socket.identifier == socket_id: break
  70. else:
  71. raise KeyError(f"Socket not found by ID {socket_id} in node {node.ui_signature}\n"
  72. f"Candidates: {[socket.identifier for socket in ui_node.inputs]}")
  73. value = socket.default_value # get the ACTUAL UI value
  74. if hasattr(socket, 'overlay_text'):
  75. value = socket.overlay_text
  76. if get_socket_idname_data_type(socket.bl_idname) != str:
  77. value = parser_handle_text(value)
  78. else: # since it doesn't have a UI node
  79. value = node.parameters[socket_id]
  80. overlay_data_type = str
  81. if isinstance(node, (AutoGenNode) ):
  82. overlay_data_type = type(node.parameters[list(node.outputs.values())[0].name])
  83. else: # we have the UI socket
  84. overlay_data_type = get_socket_idname_data_type(socket.bl_idname)
  85. if not isinstance(value, str): return
  86. # First step: get the interface nodes and the overlay keys
  87. # this should only check interfaces that are ancestors of this node
  88. my_interfaces = []
  89. for index in range(len(node.signature)-1):
  90. if index == 0: continue # root
  91. # we add one here because the node inside the schema references the schema's interface...
  92. current_schema_interface_index = node.generated_at_schema_index[index+1]
  93. if current_schema_interface_index < 0: # this indicates that it is not in a schema
  94. interface = interface_nodes.get(
  95. interface_signature := (None, *node.signature[1:index+1], "InputInterface"))
  96. else: # First we get the interface that enters the schema (index 0)
  97. interface = interface_nodes.get(
  98. interface_signature := (None, *node.signature[1:index+1],
  99. "InputInterface."+str( 0 ).zfill(4)))
  100. if interface and interface not in my_interfaces: my_interfaces.append(interface)
  101. if current_schema_interface_index == 0: continue # just did it
  102. # we need to get the schema of the current index
  103. interface = interface_nodes.get(
  104. interface_signature := (None, *node.signature[1:index+1],
  105. "InputInterface."+str(current_schema_interface_index).zfill(4)))
  106. if interface:
  107. my_interfaces.append( interface )
  108. # the my_interfaces list is sorted in REVERSE order of importance
  109. # Next, get all the overlay providers.
  110. name_to_id_map = {}
  111. name_to_type_map = {} # Just for printing reasonable error messages
  112. overlay_keys = set()
  113. root_overlay_values = {}
  114. # First, we'll get the base tree's default inputs
  115. for interface_item in node.mContext.base_tree.interface.items_tree:
  116. if interface_item.item_type != "SOCKET": continue
  117. if interface_item.in_out != "INPUT": continue
  118. base_value=None
  119. match interface_item.bl_socket_idname:
  120. case "IntSocket" | "BooleanSocket":
  121. # TODO parser for number types
  122. base_value = interface_item.default_int
  123. case "FloatSocket":
  124. base_value = interface_item.default_float
  125. case "StringSocket" | "BoneCollectionSocket":
  126. base_value = interface_item.default_string
  127. if base_value:
  128. root_overlay_values[interface_item.name] = base_value
  129. overlay_keys.add(interface_item.name)
  130. name_to_id_map[interface_item.name] = None # don't worry about it
  131. name_to_type_map[interface_item.name] = interface_item.bl_socket_idname
  132. # BUG? should this be my_interfaces ? why bother with interfaces this may not be a part of?
  133. for interface_node in interface_nodes.values():
  134. # problem: the interface node stores socket identifiers as keys
  135. # so in reality we need to get the group UI node and ask for its input names
  136. # if there is a duplicate name, too bad!
  137. group_ui_node = get_ui_node(interface_node.ui_signature[:-1], node.base_tree)
  138. for interface_item in group_ui_node.node_tree.interface.items_tree:
  139. if interface_item.item_type != 'SOCKET': continue
  140. if interface_item.in_out != 'INPUT': continue
  141. if interface_item.name in forbidden_key_names: continue
  142. for substr in forbidden_in_key_name:
  143. if substr in interface_item.name:
  144. break
  145. else: # if we do not find a bad substr
  146. name_to_id_map[interface_item.name]=interface_item.identifier
  147. name_to_type_map[interface_item.name] = interface_item.bl_socket_idname
  148. overlay_keys.add(interface_item.name)
  149. # For schema interfaces, we also need to get the length and index.
  150. if 'Index' in interface.outputs:
  151. overlay_keys.add('Index'); overlay_keys.add('Length')
  152. if 'Index' in overlay_keys:
  153. name_to_id_map['Index']='Index'
  154. name_to_type_map['Index']= "IntSocket"
  155. name_to_id_map['Length']='Length'
  156. name_to_type_map['Length']= "IntSocket"
  157. # sort by reverse length so we find "CatsAndFrogs" before "Cats"
  158. overlay_keys = sorted(overlay_keys, key= lambda a :-1*len(a))
  159. # First I need to break it into substrings based on quotation marks
  160. from ..utilities.utilities import split_quoted_substrings_to_strings
  161. quoted_substrings = split_quoted_substrings_to_strings(value)
  162. overlay_tries = []
  163. for quoted_substring in quoted_substrings:
  164. overlay_tries.extend(quoted_substring.split("$"))
  165. if overlay_data_type == str:
  166. result = overlay_tries[0] # the 0th element is always before the split character
  167. else: # TODO HACK later use the parser feature
  168. result = 0
  169. # the reason for the difference between overlay_try and key is because
  170. # we don't actually know if the $text part has a key in it
  171. for overlay_try in overlay_tries[1:]:
  172. if not overlay_try: continue # some of these are empty
  173. overlay_resolved_value = "$" + overlay_try
  174. for key in overlay_keys:
  175. if key in overlay_try:
  176. overlay_resolved_value = "$" + key
  177. for interface in reversed(my_interfaces): # since this is reversed when we build it
  178. actual_key = name_to_id_map.get(key)
  179. socket = interface.inputs.get(actual_key)
  180. if key in ['Index', 'Length']:
  181. socket = interface.outputs.get(actual_key)
  182. if socket:
  183. overlay_value = None
  184. try:
  185. overlay_value = interface.evaluate_input(actual_key)
  186. except KeyError:
  187. if (socket_type := name_to_type_map.get(key) or 'NOT FOUND') in overlay_valid_socket_types:
  188. raise GraphError(f"Error: Variable {key} is not supported because it has "
  189. f"an invalid input in the node group: {interface.signature[:-1]}.")
  190. raise GraphError( f"Error: Variable {key} is not supported because it is "
  191. f"type {socket_type}" )
  192. # NOTE that the above can also be caused by any BUG occuring in node group update
  193. # because it causes the interface to be invalid for the UI node that spawns the interface node.
  194. if overlay_data_type == str:
  195. if isinstance(overlay_value, (float, int)):
  196. overlay_value = str(overlay_value).zfill(STRING_PADDING)
  197. assert (isinstance(overlay_value, str)), f"Error: Variable {key} is not supported because it is type {type(overlay_value)}"
  198. overlay_resolved_value = overlay_try.replace(key, overlay_value)
  199. else:
  200. overlay_resolved_value = overlay_value
  201. break
  202. else: # we'll try the base tree
  203. if base_tree_result := root_overlay_values.get(key):
  204. if overlay_data_type == str and not isinstance(base_tree_result, str):
  205. base_tree_result = str(base_tree_result).zfill(STRING_PADDING)
  206. overlay_resolved_value = base_tree_result
  207. else:
  208. prOrange(f"WARN: no overlay value applied for {node.ui_signature} for key {key}")
  209. break # we found the key and are done here. One found Key per overlay-try
  210. result += overlay_resolved_value
  211. # Simple Input sockets with unnamed sockets assign this ID automatically
  212. # But Mantis uses the name instead. Usually the name and ID are the same.
  213. if socket_id == 'socket' and ui_node.outputs[0].name == "":
  214. socket_id = ''
  215. node.parameters[socket_id] = result
  216. return result # because sometimes I need to get the result, too