overlays.py 12 KB

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