internal_nodes.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. from .utilities.node_common import *
  2. from bpy.types import Node
  3. from .base_definitions import MantisNode, MantisSocketTemplate
  4. from uuid import uuid4
  5. # this function is kind of confusing and is very important,
  6. # so it bears a full explanation: its purpose is to connect
  7. # the links going into a group to the nodes in that group.
  8. # FIRST we connect all the incoming links into the Group Node to
  9. # a Group Interface node that does nothing but mark the entrance.
  10. # Then, we connect all the outgoing links back to the nodes
  11. # that had incoming links, so the nodes OUTSIDE the Node Group
  12. # are connected directly to BOTH the GroupInterface and the
  13. # nodes INSIDE the node group.
  14. # we give the GroupInterface nodes an obscenely high
  15. # multi_input_sort_id so that they are always last.
  16. # but since these links are going IN, they shouldn't cause any problems.
  17. # the sub_sort_id is set here in case there are UI links which represent
  18. # multiple Mantis links - the mantis links are sorted within the UI links
  19. # and the UI links are sorted as normal, so all the links are in the right
  20. # order.... probably. BUG here?
  21. # I want the Group Interface nodes to be part of the hierarchy...
  22. # but I want to cut the links. hmmm what to do? Fix it if it causes problems.
  23. # solution to hypothetical BUG could be to do traversal on the links
  24. # instead of the sockets.
  25. # NOTE: using NodeSocket.links is slow, it should be cached
  26. def grp_node_reroute_common(in_node, out_node, interface):
  27. from .base_definitions import links_sort_key
  28. for in_node_input in in_node.inputs:
  29. i = 0
  30. if len(in_node_input.links)>1: # sort here to ensure correct sub_sort_id
  31. in_node_input.links.sort(key=links_sort_key)
  32. while (in_node_input.links):
  33. in_link = in_node_input.links.pop()
  34. from_node = in_link.from_node; from_socket = in_link.from_socket
  35. link = from_node.outputs[from_socket].connect(
  36. interface,in_node_input.name, sort_id = 2**16, sub_sort_id=i)
  37. i += 1; in_link.die()
  38. for out_node_output in out_node.outputs:
  39. while (out_node_output.links):
  40. out_link = out_node_output.links.pop()
  41. to_node = out_link.to_node; to_socket = out_link.to_socket
  42. for j, l in enumerate(interface.inputs[out_node_output.name].links):
  43. # we are connecting the link from the ORIGINAL output to the FINAL input.
  44. link = l.from_node.outputs[l.from_socket].connect(
  45. to_node, to_socket, sort_id = out_link.multi_input_sort_id)
  46. link.sub_sort_id = j
  47. out_link.die()
  48. def reroute_links_grp(group, all_nodes):
  49. # TODO: find out why I need 2 paths here. shouldn't it always be a group?
  50. if group.ui_node.bl_idname == 'MantisNodeGroup':
  51. items_tree = group.ui_node.node_tree.interface.items_tree
  52. else: # it's an input or output or something. Why?
  53. items_tree = group.ui_node.id_data.interface.items_tree
  54. interface = GroupInterface(
  55. ( *group.signature, "InputInterface"), group.base_tree,
  56. 'INPUT', group.ui_node, items_tree=items_tree )
  57. all_nodes[interface.signature] = interface
  58. if group.inputs:
  59. if group_input := all_nodes.get(( *group.signature, "NodeGroupInput")):
  60. grp_node_reroute_common(group, group_input, interface)
  61. else:
  62. raise RuntimeError("internal error: failed to enter a node group ")
  63. def reroute_links_grpout(group_output, all_nodes):
  64. if (group_output_node := all_nodes.get( ( *group_output.signature[:-1],) )):
  65. # TODO: see above
  66. if group_output_node.ui_node.bl_idname == 'MantisNodeGroup':
  67. items_tree = group_output_node.ui_node.node_tree.interface.items_tree
  68. else:
  69. items_tree = group_output_node.ui_node.id_data.interface.items_tree
  70. interface = GroupInterface(
  71. ( *group_output_node.signature, "OutputInterface"),
  72. group_output_node.base_tree, 'OUTPUT', ui_node=group_output_node.ui_node,
  73. items_tree=items_tree,)
  74. all_nodes[interface.signature] = interface
  75. grp_node_reroute_common(group_output, group_output_node, interface)
  76. else:
  77. prOrange(f"WARN: unconnected outputs from a node group "
  78. "(maybe you are running the tree from inside a node group?)")
  79. # TODO: make node_type a class property like it is for everything else
  80. # I can't do this without making some changes to schema and anything that
  81. # checks for DUMMY SCHEMA
  82. class DummyNode(MantisNode):
  83. __slots__ = ("node_type", "ui_node", "solver", "uuid", "reroute_links_type")
  84. def __init__(self, signature, base_tree, ui_node = None, ui_signature=None):
  85. super().__init__(signature, base_tree)
  86. self.ui_node = ui_node
  87. self.prepared = True
  88. self.uuid = uuid4()
  89. self.solver = None
  90. self.node_type = 'DUMMY'
  91. self.reroute_links_type='group' # group, out, schema
  92. if ui_node:
  93. if ui_node.bl_idname in ["MantisSchemaGroup"]:
  94. self.node_type = 'DUMMY_SCHEMA'
  95. self.prepared = False
  96. for sock in ui_node.inputs:
  97. if sock.identifier == "__extend__" or sock.name == "__extend__":
  98. continue
  99. self.inputs[sock.identifier] = NodeSocket(is_input = True, name = sock.identifier, node = self)
  100. for sock in ui_node.outputs:
  101. if sock.identifier == "__extend__" or sock.name == "__extend__":
  102. continue
  103. self.outputs[sock.identifier] = NodeSocket(is_input = False, name = sock.identifier, node = self)
  104. self.parameters[sock.identifier]=None
  105. # keep track of the "natural signature" of Schema nodes - so that they are unambiguous
  106. self.ui_signature=self.signature
  107. if ui_signature:
  108. self.ui_signature=ui_signature
  109. # This is necessary for Schema to work if there are multiple Schema nodes using the same Schema tree.
  110. # this is ugly and I hate it.
  111. self.execution_prepared=True # in case it gets left behind in the tree as a dependency
  112. def reroute_links(self, all_nodes):
  113. match self.reroute_links_type:
  114. case "group":
  115. reroute_links_grp(self, all_nodes)
  116. case "schema":
  117. pass # gonna implement this later
  118. case "out":
  119. reroute_links_grpout(self, all_nodes)
  120. class NoOpNode(MantisNode):
  121. __slots__ = ()
  122. node_type = 'UTILITY'
  123. def __init__(self, signature, base_tree):
  124. super().__init__(signature, base_tree)
  125. self.inputs['Input'] = NodeSocket(is_input=True, node=self, name='Input')
  126. self.outputs['Output'] = NodeSocket(is_input=False, node=self, name='Output')
  127. self.init_parameters()
  128. self.set_traverse([("Input", "Output")])
  129. self.prepared, self.executed = True, True
  130. self.execution_prepared=True
  131. # this node is useful for me to insert in the tree and use for debugging especially connections.
  132. class AutoGenNode(MantisNode):
  133. __slots__ = ()
  134. node_type = 'UTILITY'
  135. def __init__(self, signature, base_tree):
  136. super().__init__(signature, base_tree)
  137. self.prepared, self.executed = True, True
  138. self.execution_prepared=True
  139. def reset_execution(self):
  140. super().reset_execution()
  141. self.prepared, self.executed = True, True
  142. # The Group Interface node is responsible for gathering node connections
  143. # going in or out of the group and connecting back out the other side
  144. # this is also where caching and overlays live
  145. class GroupInterface(MantisNode):
  146. __slots__ = ("in_out")
  147. node_type = 'INTERFACE'
  148. def __init__(self, signature, base_tree, in_out, ui_node = None, items_tree = None):
  149. # DO: refactor me to use the tree interface
  150. super().__init__(signature, base_tree)
  151. self.prepared, self.executed = True, True
  152. self.in_out = in_out
  153. # init the sockets based on in/out, then set up traversal
  154. if self.in_out == 'SCHEMA_INPUT': # the length is, in reality, a constant input
  155. self.outputs["Length"] = NodeSocket(is_input=False, node=self, name="Length")
  156. if 'SCHEMA' in self.in_out: # the index like a constant in for each iteration
  157. self.outputs["Index"] = NodeSocket(is_input=False, node=self, name="Index")
  158. for i, item in enumerate(items_tree):
  159. if item.item_type == 'PANEL': continue
  160. is_connection = False; is_array = False
  161. if hasattr(item, 'is_connection'): # in older files and for some types this happens
  162. is_connection = item.is_connection # BUG really every socket should be a Mantis interface type
  163. is_array = item.is_array
  164. if (self.in_out in {'INPUT', 'OUTPUT'} and item.in_out == self.in_out):
  165. source = self.inputs[item.identifier] = NodeSocket(is_input=True, node=self, name=item.identifier)
  166. target = self.outputs[item.identifier] = NodeSocket(is_input=False, node=self, name=item.identifier)
  167. self.set_traverse( [(source.name, target.name)] )
  168. elif self.in_out == 'SCHEMA_INPUT':
  169. if is_array: continue # I'll skip these for now
  170. source = self.inputs[item.identifier] = NodeSocket(is_input=True, node=self, name=item.identifier)
  171. target = self.outputs[item.identifier] = NodeSocket(is_input=False, node=self, name=item.identifier)
  172. self.set_traverse( [(source.name, target.name)] )
  173. elif self.in_out == 'SCHEMA_BETWEEN':
  174. if is_connection: # we need these at every in-between because they are modified between
  175. source = self.inputs[item.identifier] = NodeSocket(is_input=True, node=self, name=item.identifier)
  176. target = self.outputs[item.identifier] = NodeSocket(is_input=False, node=self, name=item.identifier)
  177. self.set_traverse( [(source.name, target.name)] )
  178. self.execution_prepared=True