/// <summary> /// Move the specified set of nodes to a new subnetwork, create a new group node that contains this subnet, /// restore inter- and intra-network connections. /// </summary> /// <param name="network">The parent network</param> /// <param name="nodesToGroup">The nodes to group</param> /// <returns>Returns the NodeGroupIOBinding that was constructed for this group using the IOBindingFactory.</returns> public NodeGroupIOBinding MergeIntoGroup(NetworkViewModel network, IEnumerable <NodeViewModel> nodesToGroup) { if (!CheckPropertiesValid()) { throw new InvalidOperationException("All properties must be set before usage"); } else if (network == null) { throw new ArgumentNullException(nameof(network)); } else if (nodesToGroup == null) { throw new ArgumentNullException(nameof(nodesToGroup)); } var groupNodesSet = nodesToGroup is HashSet <NodeViewModel> set ? set : new HashSet <NodeViewModel>(nodesToGroup); // Check if nodesToGroup can be combined into a single group if (groupNodesSet.Count == 0 || !GraphAlgorithms.IsContinuousSubGraphSet(groupNodesSet)) { return(null); } // Create new empty group var subnet = SubNetworkFactory(); var groupNode = GroupNodeFactory(subnet); network.Nodes.Add(groupNode); var groupEntranceNode = EntranceNodeFactory(); var groupExitNode = ExitNodeFactory(); subnet.Nodes.AddRange(new [] { groupEntranceNode, groupExitNode }); // Map from input on a group member node to group node input var groupNodeInputs = new Dictionary <NodeInputViewModel, NodeInputViewModel>(); // Map from output on a group member node to group node output var groupNodeOutputs = new Dictionary <NodeOutputViewModel, NodeOutputViewModel>(); // Move the new nodes to appropriate positions groupNode.Position = new Point( groupNodesSet.Average(n => n.Position.X), groupNodesSet.Average(n => n.Position.Y) ); double yCoord = groupNodesSet.Average(n => n.Position.Y); groupEntranceNode.Position = new Point( groupNodesSet.Min(n => n.Position.X) - 100, yCoord ); groupExitNode.Position = new Point( groupNodesSet.Max(n => n.Position.X) + 100, yCoord ); // Setup binding between entrance/exit inputs and outputs var ioBinding = IOBindingFactory(groupNode, groupEntranceNode, groupExitNode); // Calculate set of connections to replace var subnetConnections = new List <ConnectionViewModel>(); var borderInputConnections = new List <ConnectionViewModel>(); var borderOutputConnections = new List <ConnectionViewModel>(); foreach (var con in network.Connections.Items) { bool inputIsInSubnet = groupNodesSet.Contains(con.Input.Parent); bool outputIsInSubnet = groupNodesSet.Contains(con.Output.Parent); if (inputIsInSubnet && outputIsInSubnet) { subnetConnections.Add(con); } else if (inputIsInSubnet) { borderInputConnections.Add(con); } else if (outputIsInSubnet) { borderOutputConnections.Add(con); } } // Construct inputs/outputs into/out of the group foreach (var borderInCon in borderInputConnections) { if (!groupNodeInputs.ContainsKey(borderInCon.Input)) { groupNodeInputs[borderInCon.Input] = ioBinding.AddNewGroupNodeInput(borderInCon.Output); } } foreach (var borderOutCon in borderOutputConnections) { if (!groupNodeOutputs.ContainsKey(borderOutCon.Output)) { groupNodeOutputs[borderOutCon.Output] = ioBinding.AddNewGroupNodeOutput(borderOutCon.Input); } } // Transfer nodes and inner connections to subnet network.Connections.Edit(l => { l.RemoveMany(subnetConnections); l.RemoveMany(borderInputConnections); l.RemoveMany(borderOutputConnections); }); network.Nodes.RemoveMany(groupNodesSet); subnet.Nodes.AddRange(groupNodesSet); subnet.Connections.AddRange(subnetConnections.Select(con => subnet.ConnectionFactory(con.Input, con.Output))); // Restore connections in/out of group network.Connections.AddRange(Enumerable.Concat( borderInputConnections.Select(con => network.ConnectionFactory(groupNodeInputs[con.Input], con.Output)), borderOutputConnections.Select(con => network.ConnectionFactory(con.Input, groupNodeOutputs[con.Output])) )); subnet.Connections.AddRange(Enumerable.Concat( borderInputConnections.Select(con => subnet.ConnectionFactory(con.Input, ioBinding.GetSubnetInlet(groupNodeInputs[con.Input]))), borderOutputConnections.Select(con => subnet.ConnectionFactory(ioBinding.GetSubnetOutlet(groupNodeOutputs[con.Output]), con.Output)) )); return(ioBinding); }