NodeOperations.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. using System.Collections.Immutable;
  2. using System.Reflection;
  3. using PixiEditor.ChangeableDocument.Changeables.Graph;
  4. using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
  5. using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
  6. using PixiEditor.ChangeableDocument.Changeables.Interfaces;
  7. using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
  8. using PixiEditor.ChangeableDocument.Changes.Structure;
  9. using Drawie.Backend.Core;
  10. using Drawie.Backend.Core.Shaders.Generation;
  11. using Drawie.Backend.Core.Surfaces;
  12. using Drawie.Numerics;
  13. using PixiEditor.ChangeableDocument.Changeables.Graph.Context;
  14. namespace PixiEditor.ChangeableDocument.Changes.NodeGraph;
  15. public static class NodeOperations
  16. {
  17. private static Dictionary<Type, INodeFactory> allFactories;
  18. private static Dictionary<string, Type> nodeMap;
  19. static NodeOperations()
  20. {
  21. allFactories = new Dictionary<Type, INodeFactory>();
  22. var factoryTypes = typeof(Node).Assembly.GetTypes().Where(x =>
  23. x.IsAssignableTo(typeof(INodeFactory)) && x is { IsAbstract: false, IsInterface: false })
  24. .ToImmutableArray();
  25. foreach (var factoryType in factoryTypes)
  26. {
  27. INodeFactory factory = (INodeFactory)Activator.CreateInstance(factoryType);
  28. allFactories.Add(factory.NodeType, factory);
  29. }
  30. nodeMap = new Dictionary<string, Type>();
  31. var nodeTypes = typeof(Node).Assembly.GetTypes().Where(x =>
  32. x.IsSubclassOf(typeof(Node)) && x is { IsAbstract: false, IsInterface: false })
  33. .ToImmutableArray();
  34. foreach (var nodeType in nodeTypes)
  35. {
  36. NodeInfoAttribute? attribute = nodeType.GetCustomAttribute<NodeInfoAttribute>();
  37. if (attribute != null)
  38. {
  39. nodeMap.Add(attribute.UniqueName, nodeType);
  40. }
  41. else
  42. {
  43. #if DEBUG
  44. throw new InvalidOperationException($"Node {nodeType.Name} does not have NodeInfoAttribute");
  45. #endif
  46. }
  47. }
  48. }
  49. public static bool TryGetNodeType(string nodeUniqueName, out Type nodeType)
  50. {
  51. return nodeMap.TryGetValue(nodeUniqueName, out nodeType);
  52. }
  53. public static Node CreateNode(Type nodeType, IReadOnlyDocument target, params object[] optionalParameters)
  54. {
  55. Node node = null;
  56. if (allFactories.TryGetValue(nodeType, out INodeFactory factory))
  57. {
  58. node = factory.CreateNode(target);
  59. }
  60. else
  61. {
  62. node = (Node)Activator.CreateInstance(nodeType, optionalParameters);
  63. }
  64. return node;
  65. }
  66. public static List<IChangeInfo> AppendMember(Node parent, Node toAppend,
  67. out Dictionary<Guid, VecD> originalPositions)
  68. {
  69. InputProperty<Painter?>? parentInput =
  70. parent.GetInputProperty(OutputNode.InputPropertyName) as InputProperty<Painter?>;
  71. if (parentInput == null)
  72. {
  73. throw new InvalidOperationException("Parent node does not have an input property for appending members.");
  74. }
  75. OutputProperty<Painter>? toAddOutput = toAppend.GetOutputProperty("Output") as OutputProperty<Painter>;
  76. if (toAddOutput == null)
  77. {
  78. throw new InvalidOperationException("Node to append does not have an output property named 'Output'.");
  79. }
  80. InputProperty<Painter>? toAddInput =
  81. toAppend.GetInputProperty(OutputNode.InputPropertyName) as InputProperty<Painter>;
  82. if (toAddInput == null)
  83. {
  84. throw new InvalidOperationException(
  85. "Node to append does not have an input property for appending members.");
  86. }
  87. Guid memberId = toAppend.Id;
  88. List<IChangeInfo> changes = AppendMember(parentInput, toAddOutput, toAddInput, memberId);
  89. var adjustedPositions = AdjustPositionsAfterAppend(toAppend, parent,
  90. parentInput.Connection?.Node as Node ?? null, out originalPositions);
  91. changes.AddRange(adjustedPositions);
  92. return changes;
  93. }
  94. public static List<IChangeInfo> AppendMember(
  95. InputProperty<Painter?> parentInput,
  96. OutputProperty<Painter> toAddOutput,
  97. InputProperty<Painter> toAddInput, Guid memberId)
  98. {
  99. List<IChangeInfo> changes = new();
  100. IOutputProperty? previouslyConnected = null;
  101. if(parentInput == null) return changes;
  102. if (parentInput.Connection != null)
  103. {
  104. previouslyConnected = parentInput.Connection;
  105. }
  106. toAddOutput.ConnectTo(parentInput);
  107. if (previouslyConnected != null)
  108. {
  109. previouslyConnected.ConnectTo(toAddInput);
  110. changes.Add(new ConnectProperty_ChangeInfo(previouslyConnected.Node.Id, memberId,
  111. previouslyConnected.InternalPropertyName, toAddInput.InternalPropertyName));
  112. }
  113. changes.Add(new ConnectProperty_ChangeInfo(memberId, parentInput.Node.Id,
  114. toAddOutput.InternalPropertyName, parentInput.InternalPropertyName));
  115. return changes;
  116. }
  117. public static List<IChangeInfo> DetachStructureNode(StructureNode structureNode)
  118. {
  119. List<IChangeInfo> changes = new();
  120. if (structureNode.Background.Connection != null)
  121. {
  122. // connect connection to next input if possible
  123. var connections = structureNode.Output.Connections.ToArray();
  124. var output = structureNode.Background.Connection;
  125. foreach (var input in connections)
  126. {
  127. output.ConnectTo(input);
  128. changes.Add(new ConnectProperty_ChangeInfo(output.Node.Id, input.Node.Id,
  129. output.InternalPropertyName, input.InternalPropertyName));
  130. }
  131. structureNode.Background.Connection.DisconnectFrom(structureNode.Background);
  132. changes.Add(new ConnectProperty_ChangeInfo(null, structureNode.Id, null,
  133. structureNode.Background.InternalPropertyName));
  134. }
  135. var outputs = structureNode.Output.Connections.ToArray();
  136. foreach (var outputConnection in outputs)
  137. {
  138. structureNode.Output.DisconnectFrom(outputConnection);
  139. changes.Add(new ConnectProperty_ChangeInfo(null, outputConnection.Node.Id, null,
  140. outputConnection.InternalPropertyName));
  141. }
  142. return changes;
  143. }
  144. public static List<IChangeInfo> AdjustPositionsAfterAppend(Node member, Node appendedTo, Node? previouslyConnected,
  145. out Dictionary<Guid, VecD> originalPositions)
  146. {
  147. List<IChangeInfo> changes = new();
  148. Dictionary<Guid, VecD> originalPositionDict = new();
  149. member.Position = new VecD(appendedTo.Position.X - 250, appendedTo.Position.Y);
  150. changes.Add(new NodePosition_ChangeInfo(member.Id, member.Position));
  151. previouslyConnected?.TraverseBackwards((aNode, previousNode, _) =>
  152. {
  153. if (aNode is Node toMove)
  154. {
  155. originalPositionDict.Add(toMove.Id, toMove.Position);
  156. var y = toMove.Position.Y;
  157. toMove.Position = (previousNode?.Position ?? member.Position) - new VecD(250, 0);
  158. toMove.Position = new VecD(toMove.Position.X, y);
  159. changes.Add(new NodePosition_ChangeInfo(toMove.Id, toMove.Position));
  160. }
  161. return true;
  162. });
  163. originalPositions = originalPositionDict;
  164. return changes;
  165. }
  166. public static List<IChangeInfo> AdjustPositionsBeforeAppend(Node member, Node appendedTo,
  167. out Dictionary<Guid, VecD> originalPositions)
  168. {
  169. List<IChangeInfo> changes = new();
  170. Dictionary<Guid, VecD> originalPositionDict = new();
  171. member.TraverseBackwards((aNode, previousNode, _) =>
  172. {
  173. if (aNode is Node toMove)
  174. {
  175. originalPositionDict.Add(toMove.Id, toMove.Position);
  176. var y = toMove.Position.Y;
  177. VecD pos = member.Position + new VecD(250, 0);
  178. if (previousNode != null)
  179. {
  180. pos = previousNode.Position - new VecD(250, 0);
  181. }
  182. toMove.Position = pos;
  183. toMove.Position = new VecD(toMove.Position.X, y);
  184. changes.Add(new NodePosition_ChangeInfo(toMove.Id, toMove.Position));
  185. if (aNode == appendedTo) return false;
  186. }
  187. return true;
  188. });
  189. member.Position = new VecD(appendedTo.Position.X - 250, appendedTo.Position.Y);
  190. changes.Add(new NodePosition_ChangeInfo(member.Id, member.Position));
  191. originalPositions = originalPositionDict;
  192. return changes;
  193. }
  194. public static List<IChangeInfo> RevertPositions(Dictionary<Guid, VecD> positions, IReadOnlyDocument target)
  195. {
  196. List<IChangeInfo> changes = new();
  197. foreach (var (guid, position) in positions)
  198. {
  199. var node = target.FindNode(guid) as Node;
  200. if (node == null) continue;
  201. node.Position = position;
  202. changes.Add(new NodePosition_ChangeInfo(guid, position));
  203. }
  204. return changes;
  205. }
  206. public static ConnectionsData CreateConnectionsData(IReadOnlyNode node)
  207. {
  208. var originalOutputConnections = new Dictionary<PropertyConnection, List<PropertyConnection>>();
  209. foreach (var outputProp in node.OutputProperties)
  210. {
  211. PropertyConnection outputConnection = new(outputProp.Node.Id, outputProp.InternalPropertyName);
  212. originalOutputConnections.Add(outputConnection, new List<PropertyConnection>());
  213. foreach (var conn in outputProp.Connections)
  214. {
  215. originalOutputConnections[outputConnection]
  216. .Add(new PropertyConnection(conn.Node.Id, conn.InternalPropertyName));
  217. }
  218. }
  219. var originalInputConnections = node.InputProperties.Select(x =>
  220. (new PropertyConnection(x.Node.Id, x.InternalPropertyName),
  221. new PropertyConnection(x.Connection?.Node.Id, x.Connection?.InternalPropertyName)))
  222. .ToList();
  223. return new ConnectionsData(originalOutputConnections, originalInputConnections);
  224. }
  225. public static List<IChangeInfo> ConnectStructureNodeProperties(ConnectionsData originalConnections, Node node,
  226. IReadOnlyNodeGraph graph)
  227. {
  228. if (node == null || originalConnections == null || graph == null)
  229. {
  230. return new List<IChangeInfo>();
  231. }
  232. List<IChangeInfo> changes = new();
  233. foreach (var connections in originalConnections.originalOutputConnections)
  234. {
  235. PropertyConnection outputConnection = connections.Key;
  236. if (outputConnection == null)
  237. continue;
  238. IOutputProperty outputProp = node.GetOutputProperty(outputConnection.PropertyName);
  239. if (outputProp == null)
  240. {
  241. continue;
  242. }
  243. foreach (var connection in connections.Value)
  244. {
  245. var inputNode = graph.AllNodes.FirstOrDefault(x => x.Id == connection.NodeId);
  246. if (inputNode is null)
  247. continue;
  248. IInputProperty property = inputNode.GetInputProperty(connection.PropertyName);
  249. if (property is null)
  250. continue;
  251. outputProp.ConnectTo(property);
  252. changes.Add(new ConnectProperty_ChangeInfo(node.Id, property.Node.Id, outputProp.InternalPropertyName,
  253. property.InternalPropertyName));
  254. }
  255. }
  256. foreach (var connection in originalConnections.originalInputConnections)
  257. {
  258. var outputNode = graph.AllNodes.FirstOrDefault(x => x.Id == connection.Item2?.NodeId);
  259. if (outputNode is null)
  260. continue;
  261. IOutputProperty output = outputNode.GetOutputProperty(connection.Item2.PropertyName);
  262. if (output is null)
  263. continue;
  264. IInputProperty? input =
  265. node.GetInputProperty(connection.Item1.PropertyName);
  266. if (input != null)
  267. {
  268. output.ConnectTo(input);
  269. changes.Add(new ConnectProperty_ChangeInfo(output.Node.Id, node.Id,
  270. output.InternalPropertyName,
  271. input.InternalPropertyName));
  272. }
  273. }
  274. return changes;
  275. }
  276. public static List<IChangeInfo> DetachNode(Changeables.Graph.NodeGraph target, Node? node)
  277. {
  278. List<IChangeInfo> changes = new();
  279. if (node == null)
  280. {
  281. return changes;
  282. }
  283. foreach (var input in node.InputProperties)
  284. {
  285. if (input.Connection == null)
  286. {
  287. continue;
  288. }
  289. input.Connection.DisconnectFrom(input);
  290. changes.Add(new ConnectProperty_ChangeInfo(null, node.Id, null, input.InternalPropertyName));
  291. }
  292. foreach (var output in node.OutputProperties)
  293. {
  294. foreach (var input in output.Connections.ToArray())
  295. {
  296. output.DisconnectFrom(input);
  297. changes.Add(new ConnectProperty_ChangeInfo(null, input.Node.Id, null, input.InternalPropertyName));
  298. }
  299. }
  300. return changes;
  301. }
  302. public static List<IChangeInfo> CreateUpdateInputs(Node copy)
  303. {
  304. List<IChangeInfo> changes = new();
  305. foreach (var input in copy.InputProperties)
  306. {
  307. object value = input.NonOverridenValue;
  308. if (value is Delegate del)
  309. {
  310. value = del.DynamicInvoke(FuncContext.NoContext);
  311. if (value is ShaderExpressionVariable expressionVariable)
  312. {
  313. value = expressionVariable.GetConstant();
  314. }
  315. }
  316. changes.Add(new PropertyValueUpdated_ChangeInfo(copy.Id, input.InternalPropertyName, value));
  317. }
  318. return changes;
  319. }
  320. }
  321. public record PropertyConnection(Guid? NodeId, string? PropertyName);