private void VmOnWorkspacePropertyEditRequested(WorkspaceModel workspace) { // copy these strings var newName = workspace.Name.Substring(0); var newCategory = workspace.Category.Substring(0); var newDescription = workspace.Description.Substring(0); var args = new FunctionNamePromptEventArgs { Name = newName, Description = newDescription, Category = newCategory, CanEditName = false }; this.ViewModel.DynamoViewModel.Model.OnRequestsFunctionNamePrompt(this, args); if (args.Success) { if (workspace is CustomNodeWorkspaceModel) { var def = (workspace as CustomNodeWorkspaceModel).CustomNodeDefinition; this.ViewModel.DynamoViewModel.Model.CustomNodeManager.Refactor(def.FunctionId, args.CanEditName ? args.Name : workspace.Name, args.Category, args.Description); } if (args.CanEditName) { workspace.Name = args.Name; } workspace.Description = args.Description; workspace.Category = args.Category; } }
private void VmOnWorkspacePropertyEditRequested(dynWorkspaceModel workspace) { // copy these strings var newName = workspace.Name.Substring(0); var newCategory = workspace.Category.Substring(0); var newDescription = workspace.Description.Substring(0); var args = new FunctionNamePromptEventArgs { Name = newName, Description = newDescription, Category = newCategory }; dynSettings.Controller.DynamoModel.OnRequestsFunctionNamePrompt(this, args); if (args.Success) { if (workspace is FuncWorkspace) { var def = dynSettings.CustomNodeManager.GetDefinitionFromWorkspace(workspace); dynSettings.CustomNodeManager.Refactor(def.FunctionId, args.Name, args.Category, args.Description); } workspace.Name = args.Name; workspace.Description = args.Description; workspace.Category = args.Category; // workspace.Author = ""; } }
private void EditCustomNodeProperties() { CustomNodeInfo info; var model = dynamoViewModel.Model; if (!model.CustomNodeManager.TryGetNodeInfo(functionNodeModel.Definition.FunctionId, out info)) { return; } // copy these strings var newName = info.Name.Substring(0); var newCategory = info.Category.Substring(0); var newDescription = info.Description.Substring(0); var args = new FunctionNamePromptEventArgs { Name = newName, Description = newDescription, Category = newCategory, CanEditName = false }; model.OnRequestsFunctionNamePrompt(functionNodeModel, args); if (args.Success) { SerializeCustomNodeWorkspaceWithNewInfo(args, dynamoViewModel, functionNodeModel); } }
private void VmOnWorkspacePropertyEditRequested(WorkspaceModel workspace) { var customNodeWs = workspace as CustomNodeWorkspaceModel; if (customNodeWs != null) { // copy these strings var newName = customNodeWs.Name.Substring(0); var newCategory = customNodeWs.Category.Substring(0); var newDescription = customNodeWs.Description.Substring(0); var args = new FunctionNamePromptEventArgs { Name = newName, Description = newDescription, Category = newCategory, CanEditName = false }; this.ViewModel.DynamoViewModel.Model.OnRequestsFunctionNamePrompt(this, args); if (args.Success) { customNodeWs.SetInfo( args.CanEditName ? args.Name : workspace.Name, args.Category, args.Description); } } }
/// <summary> /// Presents the function name dialogue. Returns true if the user enters /// a function name and category. /// </summary> /// <param name="name"></param> /// <param name="category"></param> /// <returns></returns> public void ShowNewFunctionDialog(FunctionNamePromptEventArgs e) { string error = ""; do { //var dialog = new FunctionNamePrompt(dynSettings.Controller.SearchViewModel.Categories, error); var dialog = new FunctionNamePrompt(dynSettings.Controller.SearchViewModel.Categories) { nameBox = { Text = e.Name }, categoryBox = { Text = e.Category } }; if (dialog.ShowDialog() != true) { e.Success = false; return; } if (String.IsNullOrEmpty(dialog.Text)) { error = "You must supply a name."; MessageBox.Show(error, "Custom Node Property Error", MessageBoxButton.OK, MessageBoxImage.Error); } else if (e.Name != dialog.Text && dynSettings.Controller.CustomNodeManager.Contains(dialog.Text)) { error = "A custom node with the given name already exists."; MessageBox.Show(error, "Custom Node Property Error", MessageBoxButton.OK, MessageBoxImage.Error); } else if (e.Name != dialog.Text && dynSettings.Controller.BuiltInTypesByNickname.ContainsKey(dialog.Text)) { error = "A built-in node with the given name already exists."; MessageBox.Show(error, "Custom Node Property Error", MessageBoxButton.OK, MessageBoxImage.Error); } else if (e.Category.Equals("")) { error = "You must enter a new category or choose one from the existing categories."; MessageBox.Show(error, "Custom Node Property Error", MessageBoxButton.OK, MessageBoxImage.Error); } else { error = ""; } e.Name = dialog.Text; e.Category = dialog.Category; e.Description = dialog.Description; } while (!error.Equals("")); e.Success = true; }
/// <summary> /// Collapse a set of nodes in this workspace /// </summary> /// <param name="selectedNodes"> The function definition for the user-defined node </param> internal void CollapseNodes(IEnumerable <NodeModel> selectedNodes) { var args = new FunctionNamePromptEventArgs(); DynamoViewModel.Model.OnRequestsFunctionNamePrompt(null, args); if (!args.Success) { return; } DynamoViewModel.Model.CustomNodeManager.Collapse( selectedNodes, Model, DynamoModel.IsTestMode, args); }
public void CustomNodeEditNodeDescriptionKeepingViewBlockInDyf() { // new custom node var dynamoModel = ViewModel.Model; var nodeName = "Cool node"; var catName = "Custom Nodes"; var initialId = Guid.NewGuid(); var def = dynamoModel.CustomNodeManager.CreateCustomNode(nodeName, catName, "", initialId, true); var workspace = (CustomNodeWorkspaceModel)def; // Set file path workspace.FileName = GetNewFileNameOnTempPath("dyf"); // search common base name ViewModel.SearchViewModel.Visible = true; ViewModel.SearchViewModel.SearchAndUpdateResults("Cool"); // results are correct Assert.AreEqual(1, ViewModel.SearchViewModel.FilteredResults.Count()); var newCustNodeInstance = dynamoModel.CustomNodeManager.CreateCustomNodeInstance(initialId); dynamoModel.CurrentWorkspace.AddAndRegisterNode(newCustNodeInstance, false); // run expression Assert.AreEqual(1, dynamoModel.CurrentWorkspace.Nodes.Count()); // Mimic UI behavior so that custom node updates node name FunctionNodeViewCustomization temp = new FunctionNodeViewCustomization(); FunctionNamePromptEventArgs args = new FunctionNamePromptEventArgs { Success = true, Name = "Cool node_v2", Category = "Custom Nodes", CanEditName = false }; temp.SerializeCustomNodeWorkspaceWithNewInfo(args, ViewModel, newCustNodeInstance); // Check if serialized workspace still have view block string fileContents = File.ReadAllText(workspace.FileName); var Jobject = Newtonsoft.Json.Linq.JObject.Parse(fileContents); Assert.IsTrue(Jobject["View"] != null); Assert.IsTrue(Jobject["Name"].ToString() == "Cool node_v2"); }
/// <summary> /// Collapse a set of nodes and notes currently selected in workspace /// </summary> internal void CollapseSelectedNodes() { var args = new FunctionNamePromptEventArgs(); DynamoViewModel.Model.OnRequestsFunctionNamePrompt(null, args); if (!args.Success) { return; } var selectedNodes = DynamoSelection.Instance.Selection.OfType <NodeModel>(); var selectedNotes = DynamoSelection.Instance.Selection.OfType <NoteModel>(); DynamoViewModel.Model.AddCustomNodeWorkspace( DynamoViewModel.Model.CustomNodeManager.Collapse(selectedNodes, selectedNotes, Model, args)); }
/// <summary> /// Collapse a set of nodes and notes currently selected in workspace /// </summary> internal void CollapseSelectedNodes() { var args = new FunctionNamePromptEventArgs(); DynamoViewModel.Model.OnRequestsFunctionNamePrompt(null, args); if (!args.Success) return; var selectedNodes = DynamoSelection.Instance.Selection.OfType<NodeModel>(); var selectedNotes = DynamoSelection.Instance.Selection.OfType<NoteModel>(); DynamoViewModel.Model.AddCustomNodeWorkspace( DynamoViewModel.Model.CustomNodeManager.Collapse(selectedNodes, selectedNotes, Model, DynamoModel.IsTestMode, args)); Dynamo.Logging.Analytics.TrackCommandEvent("NewCustomNode", "NodeCount", selectedNodes.Count()); }
/// <summary> /// Serialize and update dyf based on FunctionNamePromptEventArgs /// </summary> /// <param name="args">FunctionNamePromptEventArgs which contains updated dyf info</param> /// <param name="dynamoViewModel">Dynamo View Model</param> /// <param name="functionNodeModel">Custom Node</param> internal void SerializeCustomNodeWorkspaceWithNewInfo(FunctionNamePromptEventArgs args, DynamoViewModel dynamoViewModel, Function functionNodeModel) { CustomNodeWorkspaceModel ws; dynamoViewModel.Model.CustomNodeManager.TryGetFunctionWorkspace( functionNodeModel.Definition.FunctionId, DynamoModel.IsTestMode, out ws); ws.SetInfo(args.Name, args.Category, args.Description); if (!string.IsNullOrEmpty(ws.FileName)) { // Construct a temp WorkspaceViewModel based on the CustomNodeWorkspaceModel // for serialization. We need to do so because only CustomNodeWorkspaceModel // is accessible at this point, the dyf is not guaranteed to be opened WorkspaceViewModel temp = new WorkspaceViewModel(ws, dynamoViewModel); temp.Save(ws.FileName); temp.Dispose(); } }
public void CollapsedNodeWOrkspaceIsAddedToDynamoWithUnsavedChanges() { NodeModel node; if (!CurrentDynamoModel.NodeFactory.CreateNodeFromTypeName("CoreNodeModels.Input.DoubleInput", out node)) { throw new Exception("Failed to create node!"); } var selectionSet = new[] { node }; DynamoModel.FunctionNamePromptRequestHandler del = (sender, args) => { args.Category = "Testing"; args.Description = ""; args.Name = "__CollapseTest__"; args.Success = true; }; CurrentDynamoModel.RequestsFunctionNamePrompt += del; var arg = new FunctionNamePromptEventArgs(); CurrentDynamoModel.OnRequestsFunctionNamePrompt(null, arg); Assert.IsTrue(arg.Success); CurrentDynamoModel.AddCustomNodeWorkspace( CurrentDynamoModel.CustomNodeManager.Collapse( selectionSet, Enumerable.Empty <NoteModel>(), CurrentDynamoModel.CurrentWorkspace, DynamoModel.IsTestMode, arg)); Assert.IsNotNull(CurrentDynamoModel.CurrentWorkspace.FirstNodeFromWorkspace <Function>()); Assert.AreEqual(1, CurrentDynamoModel.CurrentWorkspace.Nodes.Count()); Assert.AreEqual(2, CurrentDynamoModel.Workspaces.Count()); var customWorkspace = CurrentDynamoModel.Workspaces.ElementAt(1); Assert.AreEqual("__CollapseTest__", customWorkspace.Name); Assert.IsTrue(customWorkspace.HasUnsavedChanges); }
private void EditCustomNodeProperties() { CustomNodeInfo info; var model = dynamoViewModel.Model; if (!model.CustomNodeManager.TryGetNodeInfo(functionNodeModel.Definition.FunctionId, out info)) { return; } // copy these strings var newName = info.Name.Substring(0); var newCategory = info.Category.Substring(0); var newDescription = info.Description.Substring(0); var args = new FunctionNamePromptEventArgs { Name = newName, Description = newDescription, Category = newCategory, CanEditName = false }; model.OnRequestsFunctionNamePrompt(functionNodeModel, args); if (args.Success) { CustomNodeWorkspaceModel ws; model.CustomNodeManager.TryGetFunctionWorkspace( functionNodeModel.Definition.FunctionId, DynamoModel.IsTestMode, out ws); ws.SetInfo(args.Name, args.Category, args.Description); if (!string.IsNullOrEmpty(ws.FileName)) { ws.Save(model.EngineController.LiveRunnerRuntimeCore); } } }
public void CustomNodeEditNodeDescriptionKeepingIsVisibleInDynamoLibraryInDyf() { // Open an existing dyf var dynamoModel = ViewModel.Model; var examplePath = Path.Combine(TestDirectory, @"core\combine", "Sequence_Json.dyf"); ViewModel.OpenCommand.Execute(examplePath); var customNodeWorkspace = dynamoModel.CurrentWorkspace; var initialId = new Guid("6aecda57-7679-4afb-aa02-05a75cc3433e"); var newCustNodeInstance = dynamoModel.CustomNodeManager.CreateCustomNodeInstance(initialId, null, true); // Switch HomeWorkspace and place custom node on it dynamoModel.CurrentWorkspace = dynamoModel.Workspaces.First(); dynamoModel.CurrentWorkspace.AddAndRegisterNode(newCustNodeInstance, false); // run expression Assert.AreEqual(1, dynamoModel.CurrentWorkspace.Nodes.Count()); // Mimic UI behavior so that custom node updates node name FunctionNodeViewCustomization temp = new FunctionNodeViewCustomization(); FunctionNamePromptEventArgs args = new FunctionNamePromptEventArgs { Success = true, Name = "Sequence2", Category = "Misc", CanEditName = false, Description = "test node" }; temp.SerializeCustomNodeWorkspaceWithNewInfo(args, ViewModel, newCustNodeInstance); // Check if serialized workspace still have view block string fileContents = File.ReadAllText(customNodeWorkspace.FileName); var Jobject = Newtonsoft.Json.Linq.JObject.Parse(fileContents); Assert.IsTrue(Jobject["View"] != null); // Expect matching value Assert.IsTrue(Jobject["View"]["Dynamo"]["IsVisibleInDynamoLibrary"].ToString() == "True"); }
private void EditCustomNodeProperties() { var workspace = Definition.WorkspaceModel; // copy these strings var newName = workspace.Name.Substring(0); var newCategory = workspace.Category.Substring(0); var newDescription = workspace.Description.Substring(0); var args = new FunctionNamePromptEventArgs { Name = newName, Description = newDescription, Category = newCategory, CanEditName = false }; Workspace.DynamoModel.OnRequestsFunctionNamePrompt(this, args); if (args.Success) { if (workspace is CustomNodeWorkspaceModel) { var def = (workspace as CustomNodeWorkspaceModel).CustomNodeDefinition; this.Workspace.DynamoModel.CustomNodeManager.Refactor(def.FunctionId, args.CanEditName ? args.Name : workspace.Name, args.Category, args.Description); } if (args.CanEditName) { workspace.Name = args.Name; } workspace.Description = args.Description; workspace.Category = args.Category; if (workspace.FileName != null) { workspace.Save(); } } }
/// <summary> /// Collapse a set of nodes in a given workspace. Has the side effects of prompting the user /// first in order to obtain the name and category for the new node, /// writes the function to a dyf file, adds it to the FunctionDict, adds it to search, and compiles and /// places the newly created symbol (defining a lambda) in the Controller's FScheme Environment. /// </summary> /// <param name="selectedNodes"> The function definition for the user-defined node </param> /// <param name="currentWorkspace"> The workspace where</param> public static void Collapse(IEnumerable <NodeModel> selectedNodes, WorkspaceModel currentWorkspace, FunctionNamePromptEventArgs args = null) { var selectedNodeSet = new HashSet <NodeModel>(selectedNodes); if (args == null || !args.Success) { args = new FunctionNamePromptEventArgs(); dynSettings.Controller.DynamoModel.OnRequestsFunctionNamePrompt(null, args); //if (!dynSettings.Controller.DynamoViewModel.ShowNewFunctionDialog(ref newNodeName, ref newNodeCategory)) if (!args.Success) { return; } } var newNodeWorkspace = new CustomNodeWorkspaceModel(args.Name, args.Category, args.Description, 0, 0) { WatchChanges = false, HasUnsavedChanges = true }; var newNodeDefinition = new FunctionDefinition(Guid.NewGuid()) { WorkspaceModel = newNodeWorkspace }; currentWorkspace.DisableReporting(); #region Determine Inputs and Outputs //Step 1: determine which nodes will be inputs to the new node var inputs = new HashSet <Tuple <NodeModel, int, Tuple <int, NodeModel> > >( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.InPortData.Count).Where(node.HasConnectedInput) .Select(data => Tuple.Create(node, data, node.Inputs[data])) .Where(input => !selectedNodeSet.Contains(input.Item3.Item2)))); var outputs = new HashSet <Tuple <NodeModel, int, Tuple <int, NodeModel> > >( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.OutPortData.Count).Where(node.HasOutput).SelectMany( data => node.Outputs[data] .Where(output => !selectedNodeSet.Contains(output.Item2)) .Select(output => Tuple.Create(node, data, output))))); #endregion #region Detect 1-node holes (higher-order function extraction) var curriedNodeArgs = new HashSet <NodeModel>( inputs .Select(x => x.Item3.Item2) .Intersect(outputs.Select(x => x.Item3.Item2))) .Select( outerNode => { var node = new Apply1(); //MVVM : Don't make direct reference to view here //MVVM: no reference to view here //dynNodeView nodeUI = node.NodeUI; var elNameAttrib = node.GetType().GetCustomAttributes(typeof(NodeNameAttribute), true)[0] as NodeNameAttribute; if (elNameAttrib != null) { node.NickName = elNameAttrib.Name; } node.GUID = Guid.NewGuid(); //store the element in the elements list newNodeWorkspace.Nodes.Add(node); node.WorkSpace = newNodeWorkspace; node.DisableReporting(); //MVVM : Can't set view location here //dynSettings.Bench.WorkBench.Children.Add(nodeUI); //Place it in an appropriate spot //Canvas.SetLeft(nodeUI, Canvas.GetLeft(outerNode.NodeUI)); //Canvas.SetTop(nodeUI, Canvas.GetTop(outerNode.NodeUI)); node.X = outerNode.X; node.Y = outerNode.Y; //Fetch all input ports // in order // that have inputs // and whose input comes from an inner node List <int> inPortsConnected = Enumerable.Range(0, outerNode.InPortData.Count) .Where( x => outerNode.HasInput(x) && selectedNodeSet.Contains( outerNode.Inputs[x].Item2)) .ToList(); var nodeInputs = outputs .Where(output => output.Item3.Item2 == outerNode) .Select( output => new { InnerNodeInputSender = output.Item1, OuterNodeInPortData = output.Item3.Item1 }).ToList(); nodeInputs.ForEach(_ => node.AddInput()); node.RegisterAllPorts(); return(new { OuterNode = outerNode, InnerNode = node, Outputs = inputs.Where(input => input.Item3.Item2 == outerNode) .Select(input => input.Item3.Item1), Inputs = nodeInputs, OuterNodePortDataList = inPortsConnected }); }).ToList(); #endregion #region UI Positioning Calculations double avgX = selectedNodeSet.Average(node => node.X); double avgY = selectedNodeSet.Average(node => node.Y); double leftMost = selectedNodeSet.Min(node => node.X); double topMost = selectedNodeSet.Min(node => node.Y); double rightMost = selectedNodeSet.Max(node => node.X + node.Width); #endregion #region Move selection to new workspace var connectors = new HashSet <ConnectorModel>(currentWorkspace.Connectors.Where( conn => selectedNodeSet.Contains(conn.Start.Owner) && selectedNodeSet.Contains(conn.End.Owner))); //Step 2: move all nodes to new workspace // remove from old foreach (var ele in selectedNodeSet) { ele.SaveResult = false; currentWorkspace.Nodes.Remove(ele); ele.WorkSpace = newNodeWorkspace; } foreach (var ele in connectors) { currentWorkspace.Connectors.Remove(ele); } // add to new newNodeWorkspace.Nodes.AddRange(selectedNodeSet); newNodeWorkspace.Connectors.AddRange(connectors); double leftShift = leftMost - 250; foreach (NodeModel node in newNodeWorkspace.Nodes) { node.X = node.X - leftShift; node.Y = node.Y - topMost; } #endregion #region Insert new node into the current workspace //Step 5: insert new node into original workspace //var collapsedNode = dynSettings.Controller.DynamoViewModel.CreateFunction( // inputs.Select(x => x.Item1.InPortData[x.Item2].NickName), // outputs // .Where(x => !curriedNodeArgs.Any(y => y.OuterNode == x.Item3.Item2)) // .Select(x => x.Item1.OutPortData[x.Item2].NickName), // newNodeDefinition); //collapsedNode.GUID = Guid.NewGuid(); //currentWorkspace.Nodes.Add(collapsedNode); //collapsedNode.WorkSpace = currentWorkspace; //collapsedNode.X = avgX; //collapsedNode.Y = avgY; #endregion #region Destroy all hanging connectors //Step 6: connect inputs and outputs var removeConnectors = currentWorkspace.Connectors.Where(c => selectedNodeSet.Contains(c.Start.Owner) || selectedNodeSet.Contains(c.End.Owner)) .ToList(); foreach (ConnectorModel connector in removeConnectors) { connector.NotifyConnectedPortsOfDeletion(); currentWorkspace.Connectors.Remove(connector); } #endregion newNodeWorkspace.Nodes.ToList().ForEach(x => x.DisableReporting()); var inConnectors = new List <Tuple <NodeModel, int, int> >(); #region Process inputs var uniqueInputSenders = new Dictionary <Tuple <NodeModel, int>, Symbol>(); //Step 3: insert variables (reference step 1) foreach (var input in Enumerable.Range(0, inputs.Count).Zip(inputs, Tuple.Create)) { int inputIndex = input.Item1; NodeModel inputReceiverNode = input.Item2.Item1; int inputReceiverData = input.Item2.Item2; NodeModel inputNode = input.Item2.Item3.Item2; int inputData = input.Item2.Item3.Item1; Symbol node; var key = Tuple.Create(inputNode, inputData); if (uniqueInputSenders.ContainsKey(key)) { node = uniqueInputSenders[key]; } else { //MVVM : replace NodeUI reference with node inConnectors.Add(Tuple.Create(inputNode, inputData, inputIndex)); //Create Symbol Node node = new Symbol { InputSymbol = inputReceiverNode.InPortData[inputReceiverData].NickName }; //MVVM : Don't make direct reference to view here //dynNodeView nodeUI = node.NodeUI; var elNameAttrib = node.GetType().GetCustomAttributes(typeof(NodeNameAttribute), true)[0] as NodeNameAttribute; if (elNameAttrib != null) { node.NickName = elNameAttrib.Name; } node.GUID = Guid.NewGuid(); //store the element in the elements list newNodeWorkspace.Nodes.Add(node); node.WorkSpace = newNodeWorkspace; node.DisableReporting(); node.X = 0; node.Y = inputIndex * (50 + node.Height); uniqueInputSenders[key] = node; } var curriedNode = curriedNodeArgs.FirstOrDefault(x => x.OuterNode == inputNode); if (curriedNode == null) { var conn1 = ConnectorModel.Make(node, inputReceiverNode, 0, inputReceiverData, PortType.INPUT); if (conn1 != null) { newNodeWorkspace.Connectors.Add(conn1); } } else { //Connect it to the applier var conn = ConnectorModel.Make(node, curriedNode.InnerNode, 0, 0, PortType.INPUT); if (conn != null) { newNodeWorkspace.Connectors.Add(conn); } //Connect applier to the inner input receive var conn2 = ConnectorModel.Make( curriedNode.InnerNode, inputReceiverNode, 0, inputReceiverData, PortType.INPUT); if (conn2 != null) { newNodeWorkspace.Connectors.Add(conn2); } } } #endregion #region Process outputs //List of all inner nodes to connect an output. Unique. var outportList = new List <Tuple <NodeModel, int> >(); var outConnectors = new List <Tuple <NodeModel, int, int> >(); int i = 0; foreach (var output in outputs) { if (outportList.All(x => !(x.Item1 == output.Item1 && x.Item2 == output.Item2))) { NodeModel outputSenderNode = output.Item1; int outputSenderData = output.Item2; NodeModel outputReceiverNode = output.Item3.Item2; if (curriedNodeArgs.Any(x => x.OuterNode == outputReceiverNode)) { continue; } outportList.Add(Tuple.Create(outputSenderNode, outputSenderData)); //Create Symbol Node var node = new Output { Symbol = outputSenderNode.OutPortData[outputSenderData].NickName }; //dynNodeView nodeUI = node.NodeUI; var elNameAttrib = node.GetType().GetCustomAttributes(typeof(NodeNameAttribute), false)[0] as NodeNameAttribute; if (elNameAttrib != null) { node.NickName = elNameAttrib.Name; } node.GUID = Guid.NewGuid(); //store the element in the elements list newNodeWorkspace.Nodes.Add(node); node.WorkSpace = newNodeWorkspace; node.DisableReporting(); node.X = rightMost + 75 - leftShift; node.Y = i * (50 + node.Height); var conn = ConnectorModel.Make( outputSenderNode, node, outputSenderData, 0, PortType.INPUT); if (conn != null) { newNodeWorkspace.Connectors.Add(conn); } i++; } } //Connect outputs to new node foreach (var output in outputs) { //Node to be connected to in CurrentWorkspace NodeModel outputSenderNode = output.Item1; //Port to be connected to on outPutNode_outer int outputSenderData = output.Item2; int outputReceiverData = output.Item3.Item1; NodeModel outputReceiverNode = output.Item3.Item2; var curriedNode = curriedNodeArgs.FirstOrDefault( x => x.OuterNode == outputReceiverNode); if (curriedNode == null) { // we create the connectors in the current space later //MVVM : replaced multiple dynNodeView refrences with dynNode outConnectors.Add( Tuple.Create( outputReceiverNode, outportList.FindIndex( x => x.Item1 == outputSenderNode && x.Item2 == outputSenderData), outputReceiverData)); } else { int targetPort = curriedNode.Inputs .First( x => x.InnerNodeInputSender == outputSenderNode) .OuterNodeInPortData; int targetPortIndex = curriedNode.OuterNodePortDataList.IndexOf(targetPort); //Connect it (new dynConnector) var conn = ConnectorModel.Make( outputSenderNode, curriedNode.InnerNode, outputSenderData, targetPortIndex + 1, PortType.INPUT); if (conn != null) { newNodeWorkspace.Connectors.Add(conn); } } } #endregion // save and load the definition from file newNodeDefinition.SyncWithWorkspace(true, true); dynSettings.Controller.DynamoModel.Workspaces.Add(newNodeWorkspace); string name = newNodeDefinition.FunctionId.ToString(); var collapsedNode = dynSettings.Controller.DynamoModel.CreateNode(avgX, avgY, name); // place the node as intended, not centered collapsedNode.X = avgX; collapsedNode.Y = avgY; collapsedNode.DisableReporting(); foreach (var nodeTuple in inConnectors) { var conn = ConnectorModel.Make( nodeTuple.Item1, collapsedNode, nodeTuple.Item2, nodeTuple.Item3, PortType.INPUT); if (conn != null) { currentWorkspace.Connectors.Add(conn); } } foreach (var nodeTuple in outConnectors) { var conn = ConnectorModel.Make( collapsedNode, nodeTuple.Item1, nodeTuple.Item2, nodeTuple.Item3, PortType.INPUT); if (conn != null) { currentWorkspace.Connectors.Add(conn); } } collapsedNode.EnableReporting(); currentWorkspace.EnableReporting(); newNodeWorkspace.WatchChanges = true; }
/// <summary> /// Collapse a set of nodes in a given workspace. /// </summary> /// <param name="selectedNodes"> The function definition for the user-defined node </param> /// <param name="selectedNotes"> The note models in current selection </param> /// <param name="currentWorkspace"> The workspace where</param> /// <param name="args"></param> internal CustomNodeWorkspaceModel Collapse( IEnumerable <NodeModel> selectedNodes, IEnumerable <NoteModel> selectedNotes, WorkspaceModel currentWorkspace, FunctionNamePromptEventArgs args) { var selectedNodeSet = new HashSet <NodeModel>(selectedNodes); // Note that undoable actions are only recorded for the "currentWorkspace", // the nodes which get moved into "newNodeWorkspace" are not recorded for undo, // even in the new workspace. Their creations will simply be treated as part of // the opening of that new workspace (i.e. when a user opens a file, she will // not expect the nodes that show up to be undoable). // // After local nodes are moved into "newNodeWorkspace" as the result of // conversion, if user performs an undo, new set of nodes will be created in // "currentWorkspace" (not moving those nodes in the "newNodeWorkspace" back // into "currentWorkspace"). In another word, undo recording is on a per- // workspace basis, it does not work across different workspaces. // UndoRedoRecorder undoRecorder = currentWorkspace.UndoRecorder; CustomNodeWorkspaceModel newWorkspace; Debug.WriteLine("Current workspace has {0} nodes and {1} connectors", currentWorkspace.Nodes.Count(), currentWorkspace.Connectors.Count()); using (undoRecorder.BeginActionGroup()) { #region Determine Inputs and Outputs //Step 1: determine which nodes will be inputs to the new node var inputs = new HashSet <Tuple <NodeModel, int, Tuple <int, NodeModel> > >( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.InPorts.Count) .Where(index => node.InPorts[index].Connectors.Any()) .Select(data => Tuple.Create(node, data, node.InputNodes[data])) .Where(input => !selectedNodeSet.Contains(input.Item3.Item2)))); var outputs = new HashSet <Tuple <NodeModel, int, Tuple <int, NodeModel> > >( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.OutPorts.Count) .Where(index => node.OutPorts[index].Connectors.Any()) .SelectMany( data => node.OutputNodes[data].Where( output => !selectedNodeSet.Contains(output.Item2)) .Select(output => Tuple.Create(node, data, output))))); #endregion #region UI Positioning Calculations double avgX = selectedNodeSet.Average(node => node.X); double avgY = selectedNodeSet.Average(node => node.Y); double leftMost = selectedNodeSet.Min(node => node.X); double topMost = selectedNodeSet.Min(node => node.Y); double rightMost = selectedNodeSet.Max(node => node.X + node.Width); double leftShift = leftMost - 250; #endregion #region Handle full selected connectors // Step 2: Determine all the connectors whose start/end owners are // both in the selection set, and then move them from the current // workspace into the new workspace. var fullySelectedConns = new HashSet <ConnectorModel>( currentWorkspace.Connectors.Where( conn => { bool startSelected = selectedNodeSet.Contains(conn.Start.Owner); bool endSelected = selectedNodeSet.Contains(conn.End.Owner); return(startSelected && endSelected); })); foreach (var connector in fullySelectedConns) { undoRecorder.RecordDeletionForUndo(connector); connector.Delete(); } #endregion #region Handle partially selected connectors // Step 3: Partially selected connectors (either one of its start // and end owners is in the selection) are to be destroyed. var partiallySelectedConns = currentWorkspace.Connectors.Where( conn => selectedNodeSet.Contains(conn.Start.Owner) || selectedNodeSet.Contains(conn.End.Owner)).ToList(); foreach (var connector in partiallySelectedConns) { undoRecorder.RecordDeletionForUndo(connector); connector.Delete(); } #endregion #region Transfer nodes and connectors to new workspace var newNodes = new List <NodeModel>(); var newNotes = new List <NoteModel>(); var newAnnotations = new List <AnnotationModel>(); // Step 4: move all nodes and notes to new workspace remove from old // PB: This could be more efficiently handled by a copy paste, but we // are preservering the node foreach (var node in selectedNodeSet) { undoRecorder.RecordDeletionForUndo(node); currentWorkspace.RemoveAndDisposeNode(node); // Assign a new guid to this node, otherwise when node is // compiled to AST, literally it is still in global scope // instead of in function scope. node.GUID = Guid.NewGuid(); // shift nodes node.X = node.X - leftShift; node.Y = node.Y - topMost; newNodes.Add(node); } foreach (var note in selectedNotes) { undoRecorder.RecordDeletionForUndo(note); currentWorkspace.RemoveNote(note); note.GUID = Guid.NewGuid(); note.X = note.X - leftShift; note.Y = note.Y - topMost; newNotes.Add(note); } //Copy the group from newNodes foreach (var group in DynamoSelection.Instance.Selection.OfType <AnnotationModel>()) { undoRecorder.RecordDeletionForUndo(group); currentWorkspace.RemoveGroup(group); group.GUID = Guid.NewGuid(); group.Nodes = group.DeletedModelBases; newAnnotations.Add(group); } // Now all selected nodes already moved to custom workspace, // clear the selection. DynamoSelection.Instance.ClearSelection(); foreach (var conn in fullySelectedConns) { ConnectorModel.Make(conn.Start.Owner, conn.End.Owner, conn.Start.Index, conn.End.Index); } #endregion #region Process inputs var inConnectors = new List <Tuple <NodeModel, int> >(); var uniqueInputSenders = new Dictionary <Tuple <NodeModel, int>, Symbol>(); //Step 3: insert variables (reference step 1) foreach (var input in Enumerable.Range(0, inputs.Count).Zip(inputs, Tuple.Create)) { int inputIndex = input.Item1; NodeModel inputReceiverNode = input.Item2.Item1; int inputReceiverData = input.Item2.Item2; NodeModel inputNode = input.Item2.Item3.Item2; int inputData = input.Item2.Item3.Item1; Symbol node; var key = Tuple.Create(inputNode, inputData); if (uniqueInputSenders.ContainsKey(key)) { node = uniqueInputSenders[key]; } else { inConnectors.Add(Tuple.Create(inputNode, inputData)); node = new Symbol { InputSymbol = inputReceiverNode.InPorts[inputReceiverData].Name, X = 0 }; // Try to figure out the type of input of custom node // from the type of input of selected node. There are // two kinds of nodes whose input type are available: // function node and custom node. List <Library.TypedParameter> parameters = null; if (inputReceiverNode is EFunction) { var func = inputReceiverNode as EFunction; parameters = func.Controller.Definition.Parameters.ToList(); } else if (inputReceiverNode is DSFunctionBase) { var dsFunc = inputReceiverNode as DSFunctionBase; var funcDesc = dsFunc.Controller.Definition; parameters = funcDesc.Parameters.ToList(); if (funcDesc.Type == Engine.FunctionType.InstanceMethod || funcDesc.Type == Engine.FunctionType.InstanceProperty) { var dummyType = new ProtoCore.Type() { Name = funcDesc.ClassName }; var instanceParam = new TypedParameter(funcDesc.ClassName, dummyType); parameters.Insert(0, instanceParam); } } // so the input of custom node has format // input_var_name : type if (parameters != null && parameters.Count() > inputReceiverData) { var typeName = parameters[inputReceiverData].DisplayTypeName; if (!string.IsNullOrEmpty(typeName)) { node.InputSymbol += " : " + typeName; } } node.SetNameFromNodeNameAttribute(); node.Y = inputIndex * (50 + node.Height); uniqueInputSenders[key] = node; newNodes.Add(node); } ConnectorModel.Make(node, inputReceiverNode, 0, inputReceiverData); } #endregion #region Process outputs //List of all inner nodes to connect an output. Unique. var outportList = new List <Tuple <NodeModel, int> >(); var outConnectors = new List <Tuple <NodeModel, int, int> >(); int i = 0; if (outputs.Any()) { foreach (var output in outputs) { if (outportList.All(x => !(x.Item1 == output.Item1 && x.Item2 == output.Item2))) { NodeModel outputSenderNode = output.Item1; int outputSenderData = output.Item2; outportList.Add(Tuple.Create(outputSenderNode, outputSenderData)); //Create Symbol Node var node = new Output { Symbol = outputSenderNode.OutPorts[outputSenderData].Name, X = rightMost + 75 - leftShift }; node.Y = i * (50 + node.Height); node.SetNameFromNodeNameAttribute(); newNodes.Add(node); ConnectorModel.Make(outputSenderNode, node, outputSenderData, 0); i++; } } //Connect outputs to new node outConnectors.AddRange( from output in outputs let outputSenderNode = output.Item1 let outputSenderData = output.Item2 let outputReceiverData = output.Item3.Item1 let outputReceiverNode = output.Item3.Item2 select Tuple.Create( outputReceiverNode, outportList.FindIndex( x => x.Item1 == outputSenderNode && x.Item2 == outputSenderData), outputReceiverData)); } else { foreach (var hanging in selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.OutPorts.Count) .Where(index => !node.OutPorts[index].IsConnected) .Select(port => new { node, port })).Distinct()) { //Create Symbol Node var node = new Output { Symbol = hanging.node.OutPorts[hanging.port].Name, X = rightMost + 75 - leftShift }; node.Y = i * (50 + node.Height); node.SetNameFromNodeNameAttribute(); newNodes.Add(node); ConnectorModel.Make(hanging.node, node, hanging.port, 0); i++; } } #endregion var newId = Guid.NewGuid(); newWorkspace = new CustomNodeWorkspaceModel( nodeFactory, newNodes, newNotes, newAnnotations, Enumerable.Empty <PresetModel>(), currentWorkspace.ElementResolver, new WorkspaceInfo() { X = 0, Y = 0, Name = args.Name, Category = args.Category, Description = args.Description, ID = newId.ToString(), FileName = string.Empty, IsVisibleInDynamoLibrary = true }); newWorkspace.HasUnsavedChanges = true; RegisterCustomNodeWorkspace(newWorkspace); Debug.WriteLine("Collapsed workspace has {0} nodes and {1} connectors", newWorkspace.Nodes.Count(), newWorkspace.Connectors.Count()); var collapsedNode = CreateCustomNodeInstance(newId); collapsedNode.X = avgX; collapsedNode.Y = avgY; currentWorkspace.AddAndRegisterNode(collapsedNode, centered: false); undoRecorder.RecordCreationForUndo(collapsedNode); foreach (var connector in inConnectors.Select((x, idx) => new { node = x.Item1, from = x.Item2, to = idx }) .Select( nodeTuple => ConnectorModel.Make( nodeTuple.node, collapsedNode, nodeTuple.@from, nodeTuple.to)) .Where(connector => connector != null)) { undoRecorder.RecordCreationForUndo(connector); } foreach (var connector in outConnectors.Select( nodeTuple => ConnectorModel.Make( collapsedNode, nodeTuple.Item1, nodeTuple.Item2, nodeTuple.Item3)).Where(connector => connector != null)) { undoRecorder.RecordCreationForUndo(connector); } } return(newWorkspace); }
/// <summary> /// Handles the request for the presentation of the function name prompt /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void _vm_RequestsFunctionNamePrompt(object sender, FunctionNamePromptEventArgs e) { ShowNewFunctionDialog(e); }
/// <summary> /// Presents the function name dialogue. Returns true if the user enters /// a function name and category. /// </summary> /// <param name="name"></param> /// <param name="category"></param> /// <returns></returns> public void ShowNewFunctionDialog(FunctionNamePromptEventArgs e) { string error = ""; do { var categorized = SearchCategory.CategorizeSearchEntries( dynamoViewModel.Model.SearchModel.SearchEntries, entry => entry.Categories); var allCategories = categorized.SubCategories.SelectMany(sub => sub.GetAllCategoryNames()); var dialog = new FunctionNamePrompt(allCategories) { categoryBox = { Text = e.Category }, DescriptionInput = { Text = e.Description }, nameView = { Text = e.Name }, nameBox = { Text = e.Name }, // center the prompt Owner = this, WindowStartupLocation = WindowStartupLocation.CenterOwner }; if (e.CanEditName) { dialog.nameBox.Visibility = Visibility.Visible; dialog.nameView.Visibility = Visibility.Collapsed; } else { dialog.nameView.Visibility = Visibility.Visible; dialog.nameBox.Visibility = Visibility.Collapsed; } if (dialog.ShowDialog() != true) { e.Success = false; return; } if (String.IsNullOrEmpty(dialog.Text)) { error = "You must supply a name."; MessageBox.Show(error, "Custom Node Property Error", MessageBoxButton.OK, MessageBoxImage.Error); } //else if (e.Name != dialog.Text && dynamoViewModel.Model.BuiltInTypesByNickname.ContainsKey(dialog.Text)) //{ // error = "A built-in node with the given name already exists."; // MessageBox.Show(error, "Custom Node Property Error", MessageBoxButton.OK, // MessageBoxImage.Error); //} else if (dialog.Category.Equals("")) { error = "You must enter a new category or choose one from the existing categories."; MessageBox.Show(error, "Custom Node Property Error", MessageBoxButton.OK, MessageBoxImage.Error); } else { error = ""; } e.Name = dialog.Text; e.Category = dialog.Category; e.Description = dialog.Description; } while (!error.Equals("")); e.Success = true; }
/// <summary> /// Handles the request for the presentation of the function name prompt /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void DynamoViewModelRequestsFunctionNamePrompt(object sender, FunctionNamePromptEventArgs e) { ShowNewFunctionDialog(e); }
/// <summary> /// Presents the function name dialogue. Returns true if the user enters /// a function name and category. /// </summary> /// <param name="name"></param> /// <param name="category"></param> /// <returns></returns> public void ShowNewFunctionDialog(FunctionNamePromptEventArgs e) { string error = ""; do { var dialog = new FunctionNamePrompt(dynamoViewModel.Model.SearchModel.Categories) { categoryBox = { Text = e.Category }, DescriptionInput = { Text = e.Description }, nameView = { Text = e.Name }, nameBox = { Text = e.Name } }; if (e.CanEditName) { dialog.nameBox.Visibility = Visibility.Visible; dialog.nameView.Visibility = Visibility.Collapsed; } else { dialog.nameView.Visibility = Visibility.Visible; dialog.nameBox.Visibility = Visibility.Collapsed; } if (dialog.ShowDialog() != true) { e.Success = false; return; } if (String.IsNullOrEmpty(dialog.Text)) { error = "You must supply a name."; MessageBox.Show(error, "Custom Node Property Error", MessageBoxButton.OK, MessageBoxImage.Error); } else if (e.Name != dialog.Text && dynamoViewModel.Model.BuiltInTypesByNickname.ContainsKey(dialog.Text)) { error = "A built-in node with the given name already exists."; MessageBox.Show(error, "Custom Node Property Error", MessageBoxButton.OK, MessageBoxImage.Error); } else if (dialog.Category.Equals("")) { error = "You must enter a new category or choose one from the existing categories."; MessageBox.Show(error, "Custom Node Property Error", MessageBoxButton.OK, MessageBoxImage.Error); } else { error = ""; } e.Name = dialog.Text; e.Category = dialog.Category; e.Description = dialog.Description; } while (!error.Equals("")); e.Success = true; }
/// <summary> /// Collapse a set of nodes in a given workspace. /// </summary> /// <param name="selectedNodes"> The function definition for the user-defined node </param> /// <param name="currentWorkspace"> The workspace where</param> /// <param name="isTestMode"></param> /// <param name="args"></param> public CustomNodeWorkspaceModel Collapse( IEnumerable <NodeModel> selectedNodes, WorkspaceModel currentWorkspace, bool isTestMode, FunctionNamePromptEventArgs args) { var selectedNodeSet = new HashSet <NodeModel>(selectedNodes); // Note that undoable actions are only recorded for the "currentWorkspace", // the nodes which get moved into "newNodeWorkspace" are not recorded for undo, // even in the new workspace. Their creations will simply be treated as part of // the opening of that new workspace (i.e. when a user opens a file, she will // not expect the nodes that show up to be undoable). // // After local nodes are moved into "newNodeWorkspace" as the result of // conversion, if user performs an undo, new set of nodes will be created in // "currentWorkspace" (not moving those nodes in the "newNodeWorkspace" back // into "currentWorkspace"). In another word, undo recording is on a per- // workspace basis, it does not work across different workspaces. // UndoRedoRecorder undoRecorder = currentWorkspace.UndoRecorder; CustomNodeWorkspaceModel newWorkspace; using (undoRecorder.BeginActionGroup()) { #region Determine Inputs and Outputs //Step 1: determine which nodes will be inputs to the new node var inputs = new HashSet <Tuple <NodeModel, int, Tuple <int, NodeModel> > >( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.InPortData.Count) .Where(node.HasConnectedInput) .Select(data => Tuple.Create(node, data, node.Inputs[data])) .Where(input => !selectedNodeSet.Contains(input.Item3.Item2)))); var outputs = new HashSet <Tuple <NodeModel, int, Tuple <int, NodeModel> > >( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.OutPortData.Count) .Where(node.HasOutput) .SelectMany( data => node.Outputs[data].Where( output => !selectedNodeSet.Contains(output.Item2)) .Select(output => Tuple.Create(node, data, output))))); #endregion #region Detect 1-node holes (higher-order function extraction) Log(Properties.Resources.CouldNotRepairOneNodeHoles, WarningLevel.Mild); // http://adsk-oss.myjetbrains.com/youtrack/issue/MAGN-5603 //var curriedNodeArgs = // new HashSet<NodeModel>( // inputs.Select(x => x.Item3.Item2) // .Intersect(outputs.Select(x => x.Item3.Item2))).Select( // outerNode => // { // //var node = new Apply1(); // var node = newNodeWorkspace.AddNode<Apply1>(); // node.SetNickNameFromAttribute(); // node.DisableReporting(); // node.X = outerNode.X; // node.Y = outerNode.Y; // //Fetch all input ports // // in order // // that have inputs // // and whose input comes from an inner node // List<int> inPortsConnected = // Enumerable.Range(0, outerNode.InPortData.Count) // .Where( // x => // outerNode.HasInput(x) // && selectedNodeSet.Contains( // outerNode.Inputs[x].Item2)) // .ToList(); // var nodeInputs = // outputs.Where(output => output.Item3.Item2 == outerNode) // .Select( // output => // new // { // InnerNodeInputSender = output.Item1, // OuterNodeInPortData = output.Item3.Item1 // }) // .ToList(); // nodeInputs.ForEach(_ => node.AddInput()); // node.RegisterAllPorts(); // return // new // { // OuterNode = outerNode, // InnerNode = node, // Outputs = // inputs.Where( // input => input.Item3.Item2 == outerNode) // .Select(input => input.Item3.Item1), // Inputs = nodeInputs, // OuterNodePortDataList = inPortsConnected // }; // }).ToList(); #endregion #region UI Positioning Calculations double avgX = selectedNodeSet.Average(node => node.X); double avgY = selectedNodeSet.Average(node => node.Y); double leftMost = selectedNodeSet.Min(node => node.X); double topMost = selectedNodeSet.Min(node => node.Y); double rightMost = selectedNodeSet.Max(node => node.X + node.Width); double leftShift = leftMost - 250; #endregion #region Handle full selected connectors // Step 2: Determine all the connectors whose start/end owners are // both in the selection set, and then move them from the current // workspace into the new workspace. var fullySelectedConns = new HashSet <ConnectorModel>( currentWorkspace.Connectors.Where( conn => { bool startSelected = selectedNodeSet.Contains(conn.Start.Owner); bool endSelected = selectedNodeSet.Contains(conn.End.Owner); return(startSelected && endSelected); })); foreach (var connector in fullySelectedConns) { undoRecorder.RecordDeletionForUndo(connector); connector.Delete(); } #endregion #region Handle partially selected connectors // Step 3: Partially selected connectors (either one of its start // and end owners is in the selection) are to be destroyed. var partiallySelectedConns = currentWorkspace.Connectors.Where( conn => selectedNodeSet.Contains(conn.Start.Owner) || selectedNodeSet.Contains(conn.End.Owner)).ToList(); foreach (var connector in partiallySelectedConns) { undoRecorder.RecordDeletionForUndo(connector); connector.Delete(); } #endregion #region Transfer nodes and connectors to new workspace var newNodes = new List <NodeModel>(); // Step 4: move all nodes to new workspace remove from old // PB: This could be more efficiently handled by a copy paste, but we // are preservering the node foreach (var node in selectedNodeSet) { undoRecorder.RecordDeletionForUndo(node); currentWorkspace.RemoveNode(node); // Assign a new guid to this node, otherwise when node is // compiled to AST, literally it is still in global scope // instead of in function scope. node.GUID = Guid.NewGuid(); node.RenderPackages.Clear(); // shit nodes node.X = node.X - leftShift; node.Y = node.Y - topMost; newNodes.Add(node); } foreach (var conn in fullySelectedConns) { ConnectorModel.Make(conn.Start.Owner, conn.End.Owner, conn.Start.Index, conn.End.Index); } #endregion #region Process inputs var inConnectors = new List <Tuple <NodeModel, int> >(); var uniqueInputSenders = new Dictionary <Tuple <NodeModel, int>, Symbol>(); //Step 3: insert variables (reference step 1) foreach (var input in Enumerable.Range(0, inputs.Count).Zip(inputs, Tuple.Create)) { int inputIndex = input.Item1; NodeModel inputReceiverNode = input.Item2.Item1; int inputReceiverData = input.Item2.Item2; NodeModel inputNode = input.Item2.Item3.Item2; int inputData = input.Item2.Item3.Item1; Symbol node; var key = Tuple.Create(inputNode, inputData); if (uniqueInputSenders.ContainsKey(key)) { node = uniqueInputSenders[key]; } else { inConnectors.Add(Tuple.Create(inputNode, inputData)); node = new Symbol { InputSymbol = inputReceiverNode.InPortData[inputReceiverData].NickName, X = 0 }; // Try to figure out the type of input of custom node // from the type of input of selected node. There are // two kinds of nodes whose input type are available: // function node and custom node. List <Library.TypedParameter> parameters = null; if (inputReceiverNode is Function) { var func = inputReceiverNode as Function; parameters = func.Controller.Definition.Parameters.ToList(); } else if (inputReceiverNode is DSFunctionBase) { var dsFunc = inputReceiverNode as DSFunctionBase; parameters = dsFunc.Controller.Definition.Parameters.ToList(); } // so the input of custom node has format // input_var_name : type if (parameters != null && parameters.Count() > inputReceiverData) { var typeName = parameters[inputReceiverData].DisplayTypeName; if (!string.IsNullOrEmpty(typeName)) { node.InputSymbol += " : " + typeName; } } node.SetNickNameFromAttribute(); node.Y = inputIndex * (50 + node.Height); uniqueInputSenders[key] = node; newNodes.Add(node); } //var curriedNode = curriedNodeArgs.FirstOrDefault(x => x.OuterNode == inputNode); //if (curriedNode == null) //{ ConnectorModel.Make(node, inputReceiverNode, 0, inputReceiverData); //} //else //{ // //Connect it to the applier // newNodeWorkspace.AddConnection(node, curriedNode.InnerNode, 0, 0); // //Connect applier to the inner input receive // newNodeWorkspace.AddConnection( // curriedNode.InnerNode, // inputReceiverNode, // 0, // inputReceiverData); //} } #endregion #region Process outputs //List of all inner nodes to connect an output. Unique. var outportList = new List <Tuple <NodeModel, int> >(); var outConnectors = new List <Tuple <NodeModel, int, int> >(); int i = 0; if (outputs.Any()) { foreach (var output in outputs) { if (outportList.All(x => !(x.Item1 == output.Item1 && x.Item2 == output.Item2))) { NodeModel outputSenderNode = output.Item1; int outputSenderData = output.Item2; //NodeModel outputReceiverNode = output.Item3.Item2; //if (curriedNodeArgs.Any(x => x.OuterNode == outputReceiverNode)) // continue; outportList.Add(Tuple.Create(outputSenderNode, outputSenderData)); //Create Symbol Node var node = new Output { Symbol = outputSenderNode.OutPortData[outputSenderData].NickName, X = rightMost + 75 - leftShift }; node.Y = i * (50 + node.Height); node.SetNickNameFromAttribute(); newNodes.Add(node); ConnectorModel.Make(outputSenderNode, node, outputSenderData, 0); i++; } } //Connect outputs to new node outConnectors.AddRange( from output in outputs let outputSenderNode = output.Item1 let outputSenderData = output.Item2 let outputReceiverData = output.Item3.Item1 let outputReceiverNode = output.Item3.Item2 select Tuple.Create( outputReceiverNode, outportList.FindIndex( x => x.Item1 == outputSenderNode && x.Item2 == outputSenderData), outputReceiverData)); } else { foreach (var hanging in selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.OutPortData.Count) .Where(port => !node.HasOutput(port)) .Select(port => new { node, port })).Distinct()) { //Create Symbol Node var node = new Output { Symbol = hanging.node.OutPortData[hanging.port].NickName, X = rightMost + 75 - leftShift }; node.Y = i * (50 + node.Height); node.SetNickNameFromAttribute(); newNodes.Add(node); ConnectorModel.Make(hanging.node, node, hanging.port, 0); i++; } } #endregion var newId = Guid.NewGuid(); newWorkspace = new CustomNodeWorkspaceModel( args.Name, args.Category, args.Description, nodeFactory, newNodes, Enumerable.Empty <NoteModel>(), 0, 0, newId, currentWorkspace.ElementResolver, string.Empty); newWorkspace.HasUnsavedChanges = true; RegisterCustomNodeWorkspace(newWorkspace); var collapsedNode = CreateCustomNodeInstance(newId, isTestMode: isTestMode); collapsedNode.X = avgX; collapsedNode.Y = avgY; currentWorkspace.AddNode(collapsedNode, centered: false); undoRecorder.RecordCreationForUndo(collapsedNode); foreach (var connector in inConnectors.Select((x, idx) => new { node = x.Item1, from = x.Item2, to = idx }) .Select( nodeTuple => ConnectorModel.Make( nodeTuple.node, collapsedNode, nodeTuple.@from, nodeTuple.to)) .Where(connector => connector != null)) { undoRecorder.RecordCreationForUndo(connector); } foreach (var connector in outConnectors.Select( nodeTuple => ConnectorModel.Make( collapsedNode, nodeTuple.Item1, nodeTuple.Item2, nodeTuple.Item3)).Where(connector => connector != null)) { undoRecorder.RecordCreationForUndo(connector); } } return(newWorkspace); }
/// <summary> /// Collapse a set of nodes in a given workspace. Has the side effects of prompting the user /// first in order to obtain the name and category for the new node, /// writes the function to a dyf file, adds it to the FunctionDict, adds it to search, and compiles and /// places the newly created symbol (defining a lambda) in the Controller's FScheme Environment. /// </summary> /// <param name="selectedNodes"> The function definition for the user-defined node </param> /// <param name="currentWorkspace"> The workspace where</param> /// <param name="args"></param> public static void Collapse(IEnumerable <NodeModel> selectedNodes, WorkspaceModel currentWorkspace, FunctionNamePromptEventArgs args = null) { var selectedNodeSet = new HashSet <NodeModel>(selectedNodes); if (args == null || !args.Success) { args = new FunctionNamePromptEventArgs(); dynSettings.Controller.DynamoViewModel.OnRequestsFunctionNamePrompt(null, args); //if (!dynSettings.Controller.DynamoViewModel.ShowNewFunctionDialog(ref newNodeName, ref newNodeCategory)) if (!args.Success) { return; } } // Note that undoable actions are only recorded for the "currentWorkspace", // the nodes which get moved into "newNodeWorkspace" are not recorded for undo, // even in the new workspace. Their creations will simply be treated as part of // the opening of that new workspace (i.e. when a user opens a file, she will // not expect the nodes that show up to be undoable). // // After local nodes are moved into "newNodeWorkspace" as the result of // conversion, if user performs an undo, new set of nodes will be created in // "currentWorkspace" (not moving those nodes in the "newNodeWorkspace" back // into "currentWorkspace"). In another word, undo recording is on a per- // workspace basis, it does not work across different workspaces. // UndoRedoRecorder undoRecorder = currentWorkspace.UndoRecorder; undoRecorder.BeginActionGroup(); var newNodeWorkspace = new CustomNodeWorkspaceModel(args.Name, args.Category, args.Description, 0, 0) { WatchChanges = false, HasUnsavedChanges = true }; var newNodeDefinition = new CustomNodeDefinition(Guid.NewGuid()) { WorkspaceModel = newNodeWorkspace }; currentWorkspace.DisableReporting(); #region Determine Inputs and Outputs //Step 1: determine which nodes will be inputs to the new node var inputs = new HashSet <Tuple <NodeModel, int, Tuple <int, NodeModel> > >( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.InPortData.Count).Where(node.HasConnectedInput) .Select(data => Tuple.Create(node, data, node.Inputs[data])) .Where(input => !selectedNodeSet.Contains(input.Item3.Item2)))); var outputs = new HashSet <Tuple <NodeModel, int, Tuple <int, NodeModel> > >( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.OutPortData.Count).Where(node.HasOutput).SelectMany( data => node.Outputs[data] .Where(output => !selectedNodeSet.Contains(output.Item2)) .Select(output => Tuple.Create(node, data, output))))); #endregion #region Detect 1-node holes (higher-order function extraction) var curriedNodeArgs = new HashSet <NodeModel>( inputs .Select(x => x.Item3.Item2) .Intersect(outputs.Select(x => x.Item3.Item2))) .Select( outerNode => { var node = new Apply1(); //MVVM : Don't make direct reference to view here //MVVM: no reference to view here //dynNodeView nodeUI = node.NodeUI; var elNameAttrib = node.GetType().GetCustomAttributes(typeof(NodeNameAttribute), true)[0] as NodeNameAttribute; if (elNameAttrib != null) { node.NickName = elNameAttrib.Name; } node.GUID = Guid.NewGuid(); //store the element in the elements list newNodeWorkspace.Nodes.Add(node); node.WorkSpace = newNodeWorkspace; node.DisableReporting(); //MVVM : Can't set view location here //dynSettings.Bench.WorkBench.Children.Add(nodeUI); //Place it in an appropriate spot //Canvas.SetLeft(nodeUI, Canvas.GetLeft(outerNode.NodeUI)); //Canvas.SetTop(nodeUI, Canvas.GetTop(outerNode.NodeUI)); node.X = outerNode.X; node.Y = outerNode.Y; //Fetch all input ports // in order // that have inputs // and whose input comes from an inner node List <int> inPortsConnected = Enumerable.Range(0, outerNode.InPortData.Count) .Where( x => outerNode.HasInput(x) && selectedNodeSet.Contains( outerNode.Inputs[x].Item2)) .ToList(); var nodeInputs = outputs .Where(output => output.Item3.Item2 == outerNode) .Select( output => new { InnerNodeInputSender = output.Item1, OuterNodeInPortData = output.Item3.Item1 }).ToList(); nodeInputs.ForEach(_ => node.AddInput()); node.RegisterAllPorts(); return(new { OuterNode = outerNode, InnerNode = node, Outputs = inputs.Where(input => input.Item3.Item2 == outerNode) .Select(input => input.Item3.Item1), Inputs = nodeInputs, OuterNodePortDataList = inPortsConnected }); }).ToList(); #endregion #region UI Positioning Calculations double avgX = selectedNodeSet.Average(node => node.X); double avgY = selectedNodeSet.Average(node => node.Y); double leftMost = selectedNodeSet.Min(node => node.X); double topMost = selectedNodeSet.Min(node => node.Y); double rightMost = selectedNodeSet.Max(node => node.X + node.Width); #endregion #region Handle full selected connectors // Step 2: Determine all the connectors whose start/end owners are // both in the selection set, and then move them from the current // workspace into the new workspace. var fullySelectedConns = new HashSet <ConnectorModel>( currentWorkspace.Connectors.Where(conn => { bool startSelected = selectedNodeSet.Contains(conn.Start.Owner); bool endSelected = selectedNodeSet.Contains(conn.End.Owner); return(startSelected && endSelected); })); foreach (var ele in fullySelectedConns) { undoRecorder.RecordDeletionForUndo(ele); currentWorkspace.Connectors.Remove(ele); } #endregion #region Handle partially selected connectors // Step 3: Partially selected connectors (either one of its start // and end owners is in the selection) are to be destroyed. var partiallySelectedConns = currentWorkspace.Connectors.Where( conn => selectedNodeSet.Contains(conn.Start.Owner) || selectedNodeSet.Contains(conn.End.Owner)).ToList(); foreach (ConnectorModel connector in partiallySelectedConns) { undoRecorder.RecordDeletionForUndo(connector); connector.NotifyConnectedPortsOfDeletion(); currentWorkspace.Connectors.Remove(connector); } #endregion #region Transfer nodes and connectors to new workspace // Step 4: move all nodes to new workspace remove from old foreach (var ele in selectedNodeSet) { undoRecorder.RecordDeletionForUndo(ele); ele.SaveResult = false; currentWorkspace.Nodes.Remove(ele); ele.WorkSpace = newNodeWorkspace; } // add to new newNodeWorkspace.Nodes.AddRange(selectedNodeSet); newNodeWorkspace.Connectors.AddRange(fullySelectedConns); double leftShift = leftMost - 250; foreach (NodeModel node in newNodeWorkspace.Nodes) { node.X = node.X - leftShift; node.Y = node.Y - topMost; } #endregion foreach (var node in newNodeWorkspace.Nodes) { node.DisableReporting(); } #region Process inputs var inConnectors = new List <Tuple <NodeModel, int> >(); var uniqueInputSenders = new Dictionary <Tuple <NodeModel, int>, Symbol>(); //Step 3: insert variables (reference step 1) foreach (var input in Enumerable.Range(0, inputs.Count).Zip(inputs, Tuple.Create)) { int inputIndex = input.Item1; NodeModel inputReceiverNode = input.Item2.Item1; int inputReceiverData = input.Item2.Item2; NodeModel inputNode = input.Item2.Item3.Item2; int inputData = input.Item2.Item3.Item1; Symbol node; var key = Tuple.Create(inputNode, inputData); if (uniqueInputSenders.ContainsKey(key)) { node = uniqueInputSenders[key]; } else { //MVVM : replace NodeUI reference with node inConnectors.Add(Tuple.Create(inputNode, inputData)); //Create Symbol Node node = new Symbol { InputSymbol = inputReceiverNode.InPortData[inputReceiverData].NickName }; //MVVM : Don't make direct reference to view here //dynNodeView nodeUI = node.NodeUI; var elNameAttrib = node.GetType().GetCustomAttributes(typeof(NodeNameAttribute), true)[0] as NodeNameAttribute; if (elNameAttrib != null) { node.NickName = elNameAttrib.Name; } node.GUID = Guid.NewGuid(); //store the element in the elements list newNodeWorkspace.Nodes.Add(node); node.WorkSpace = newNodeWorkspace; node.DisableReporting(); node.X = 0; node.Y = inputIndex * (50 + node.Height); uniqueInputSenders[key] = node; } var curriedNode = curriedNodeArgs.FirstOrDefault(x => x.OuterNode == inputNode); if (curriedNode == null) { var conn1 = ConnectorModel.Make(node, inputReceiverNode, 0, inputReceiverData, PortType.INPUT); if (conn1 != null) { newNodeWorkspace.Connectors.Add(conn1); } } else { //Connect it to the applier var conn = ConnectorModel.Make(node, curriedNode.InnerNode, 0, 0, PortType.INPUT); if (conn != null) { newNodeWorkspace.Connectors.Add(conn); } //Connect applier to the inner input receive var conn2 = ConnectorModel.Make( curriedNode.InnerNode, inputReceiverNode, 0, inputReceiverData, PortType.INPUT); if (conn2 != null) { newNodeWorkspace.Connectors.Add(conn2); } } } #endregion #region Process outputs //List of all inner nodes to connect an output. Unique. var outportList = new List <Tuple <NodeModel, int> >(); var outConnectors = new List <Tuple <NodeModel, int, int> >(); int i = 0; if (outputs.Any()) { foreach (var output in outputs) { if (outportList.All(x => !(x.Item1 == output.Item1 && x.Item2 == output.Item2))) { NodeModel outputSenderNode = output.Item1; int outputSenderData = output.Item2; NodeModel outputReceiverNode = output.Item3.Item2; if (curriedNodeArgs.Any(x => x.OuterNode == outputReceiverNode)) { continue; } outportList.Add(Tuple.Create(outputSenderNode, outputSenderData)); //Create Symbol Node var node = new Output { Symbol = outputSenderNode.OutPortData[outputSenderData].NickName }; //dynNodeView nodeUI = node.NodeUI; var elNameAttrib = node.GetType().GetCustomAttributes(typeof(NodeNameAttribute), false)[0] as NodeNameAttribute; if (elNameAttrib != null) { node.NickName = elNameAttrib.Name; } node.GUID = Guid.NewGuid(); //store the element in the elements list newNodeWorkspace.Nodes.Add(node); node.WorkSpace = newNodeWorkspace; node.DisableReporting(); node.X = rightMost + 75 - leftShift; node.Y = i * (50 + node.Height); var conn = ConnectorModel.Make( outputSenderNode, node, outputSenderData, 0, PortType.INPUT); if (conn != null) { newNodeWorkspace.Connectors.Add(conn); } i++; } } //Connect outputs to new node foreach (var output in outputs) { //Node to be connected to in CurrentWorkspace NodeModel outputSenderNode = output.Item1; //Port to be connected to on outPutNode_outer int outputSenderData = output.Item2; int outputReceiverData = output.Item3.Item1; NodeModel outputReceiverNode = output.Item3.Item2; var curriedNode = curriedNodeArgs.FirstOrDefault(x => x.OuterNode == outputReceiverNode); if (curriedNode == null) { // we create the connectors in the current space later //MVVM : replaced multiple dynNodeView refrences with dynNode outConnectors.Add( Tuple.Create( outputReceiverNode, outportList.FindIndex( x => x.Item1 == outputSenderNode && x.Item2 == outputSenderData), outputReceiverData)); } else { int targetPort = curriedNode.Inputs.First(x => x.InnerNodeInputSender == outputSenderNode) .OuterNodeInPortData; int targetPortIndex = curriedNode.OuterNodePortDataList.IndexOf(targetPort); //Connect it (new dynConnector) var conn = ConnectorModel.Make( outputSenderNode, curriedNode.InnerNode, outputSenderData, targetPortIndex + 1, PortType.INPUT); if (conn != null) { newNodeWorkspace.Connectors.Add(conn); } } } } else { foreach (var hanging in selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.OutPortData.Count) .Where(port => !node.HasOutput(port)) .Select(port => new { node, port })).Distinct()) { //Create Symbol Node var node = new Output { Symbol = hanging.node.OutPortData[hanging.port].NickName }; //dynNodeView nodeUI = node.NodeUI; var elNameAttrib = node.GetType().GetCustomAttributes(typeof(NodeNameAttribute), false)[0] as NodeNameAttribute; if (elNameAttrib != null) { node.NickName = elNameAttrib.Name; } node.GUID = Guid.NewGuid(); //store the element in the elements list newNodeWorkspace.Nodes.Add(node); node.WorkSpace = newNodeWorkspace; node.DisableReporting(); node.X = rightMost + 75 - leftShift; node.Y = i * (50 + node.Height); var conn = ConnectorModel.Make( hanging.node, node, hanging.port, 0, PortType.INPUT); if (conn != null) { newNodeWorkspace.Connectors.Add(conn); } i++; } } #endregion // save and load the definition from file newNodeDefinition.SyncWithWorkspace(true, true); dynSettings.Controller.DynamoModel.Workspaces.Add(newNodeWorkspace); string name = newNodeDefinition.FunctionId.ToString(); var collapsedNode = dynSettings.Controller.DynamoModel.CreateNode(avgX, avgY, name); undoRecorder.RecordCreationForUndo(collapsedNode); // place the node as intended, not centered collapsedNode.X = avgX; collapsedNode.Y = avgY; collapsedNode.DisableReporting(); foreach (var nodeTuple in inConnectors.Select((x, idx) => new { node = x.Item1, from = x.Item2, to = idx })) { var conn = ConnectorModel.Make( nodeTuple.node, collapsedNode, nodeTuple.from, nodeTuple.to, PortType.INPUT); if (conn != null) { currentWorkspace.Connectors.Add(conn); undoRecorder.RecordCreationForUndo(conn); } } foreach (var nodeTuple in outConnectors) { var conn = ConnectorModel.Make( collapsedNode, nodeTuple.Item1, nodeTuple.Item2, nodeTuple.Item3, PortType.INPUT); if (conn != null) { currentWorkspace.Connectors.Add(conn); undoRecorder.RecordCreationForUndo(conn); } } undoRecorder.EndActionGroup(); collapsedNode.EnableReporting(); currentWorkspace.EnableReporting(); foreach (var node in newNodeWorkspace.Nodes) { node.EnableReporting(); } newNodeWorkspace.WatchChanges = true; }
/// <summary> /// Collapse a set of nodes in a given workspace. /// </summary> /// <param name="dynamoModel">The current DynamoModel</param> /// <param name="selectedNodes"> The function definition for the user-defined node </param> /// <param name="currentWorkspace"> The workspace where</param> /// <param name="args"></param> public static void Collapse(DynamoModel dynamoModel, IEnumerable <NodeModel> selectedNodes, WorkspaceModel currentWorkspace, FunctionNamePromptEventArgs args = null) { if (args == null || !args.Success) { args = new FunctionNamePromptEventArgs(); dynamoModel.OnRequestsFunctionNamePrompt(null, args); if (!args.Success) { return; } } var selectedNodeSet = new HashSet <NodeModel>(selectedNodes); // Note that undoable actions are only recorded for the "currentWorkspace", // the nodes which get moved into "newNodeWorkspace" are not recorded for undo, // even in the new workspace. Their creations will simply be treated as part of // the opening of that new workspace (i.e. when a user opens a file, she will // not expect the nodes that show up to be undoable). // // After local nodes are moved into "newNodeWorkspace" as the result of // conversion, if user performs an undo, new set of nodes will be created in // "currentWorkspace" (not moving those nodes in the "newNodeWorkspace" back // into "currentWorkspace"). In another word, undo recording is on a per- // workspace basis, it does not work across different workspaces. // UndoRedoRecorder undoRecorder = currentWorkspace.UndoRecorder; using (undoRecorder.BeginActionGroup()) { var newNodeWorkspace = new CustomNodeWorkspaceModel( dynamoModel, args.Name, args.Category, args.Description, 0, 0) { WatchChanges = false, HasUnsavedChanges = true }; var newNodeDefinition = new CustomNodeDefinition(Guid.NewGuid()) { WorkspaceModel = newNodeWorkspace }; currentWorkspace.DisableReporting(); #region Determine Inputs and Outputs //Step 1: determine which nodes will be inputs to the new node var inputs = new HashSet <Tuple <NodeModel, int, Tuple <int, NodeModel> > >( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.InPortData.Count) .Where(node.HasConnectedInput) .Select(data => Tuple.Create(node, data, node.Inputs[data])) .Where(input => !selectedNodeSet.Contains(input.Item3.Item2)))); var outputs = new HashSet <Tuple <NodeModel, int, Tuple <int, NodeModel> > >( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.OutPortData.Count) .Where(node.HasOutput) .SelectMany( data => node.Outputs[data].Where( output => !selectedNodeSet.Contains(output.Item2)) .Select(output => Tuple.Create(node, data, output))))); #endregion #region Detect 1-node holes (higher-order function extraction) dynamoModel.Logger.LogWarning("Could not repair 1-node holes", WarningLevel.Mild); // http://adsk-oss.myjetbrains.com/youtrack/issue/MAGN-5603 //var curriedNodeArgs = // new HashSet<NodeModel>( // inputs.Select(x => x.Item3.Item2) // .Intersect(outputs.Select(x => x.Item3.Item2))).Select( // outerNode => // { // //var node = new Apply1(); // var node = newNodeWorkspace.AddNode<Apply1>(); // node.SetNickNameFromAttribute(); // node.DisableReporting(); // node.X = outerNode.X; // node.Y = outerNode.Y; // //Fetch all input ports // // in order // // that have inputs // // and whose input comes from an inner node // List<int> inPortsConnected = // Enumerable.Range(0, outerNode.InPortData.Count) // .Where( // x => // outerNode.HasInput(x) // && selectedNodeSet.Contains( // outerNode.Inputs[x].Item2)) // .ToList(); // var nodeInputs = // outputs.Where(output => output.Item3.Item2 == outerNode) // .Select( // output => // new // { // InnerNodeInputSender = output.Item1, // OuterNodeInPortData = output.Item3.Item1 // }) // .ToList(); // nodeInputs.ForEach(_ => node.AddInput()); // node.RegisterAllPorts(); // return // new // { // OuterNode = outerNode, // InnerNode = node, // Outputs = // inputs.Where( // input => input.Item3.Item2 == outerNode) // .Select(input => input.Item3.Item1), // Inputs = nodeInputs, // OuterNodePortDataList = inPortsConnected // }; // }).ToList(); #endregion #region UI Positioning Calculations double avgX = selectedNodeSet.Average(node => node.X); double avgY = selectedNodeSet.Average(node => node.Y); double leftMost = selectedNodeSet.Min(node => node.X); double topMost = selectedNodeSet.Min(node => node.Y); double rightMost = selectedNodeSet.Max(node => node.X + node.Width); #endregion #region Handle full selected connectors // Step 2: Determine all the connectors whose start/end owners are // both in the selection set, and then move them from the current // workspace into the new workspace. var fullySelectedConns = new HashSet <ConnectorModel>( currentWorkspace.Connectors.Where( conn => { bool startSelected = selectedNodeSet.Contains(conn.Start.Owner); bool endSelected = selectedNodeSet.Contains(conn.End.Owner); return(startSelected && endSelected); })); foreach (var ele in fullySelectedConns) { undoRecorder.RecordDeletionForUndo(ele); currentWorkspace.Connectors.Remove(ele); } #endregion #region Handle partially selected connectors // Step 3: Partially selected connectors (either one of its start // and end owners is in the selection) are to be destroyed. var partiallySelectedConns = currentWorkspace.Connectors.Where( conn => selectedNodeSet.Contains(conn.Start.Owner) || selectedNodeSet.Contains(conn.End.Owner)).ToList(); foreach (ConnectorModel connector in partiallySelectedConns) { undoRecorder.RecordDeletionForUndo(connector); connector.NotifyConnectedPortsOfDeletion(); currentWorkspace.Connectors.Remove(connector); } #endregion #region Transfer nodes and connectors to new workspace // Step 4: move all nodes to new workspace remove from old // PB: This could be more efficiently handled by a copy paste, but we // are preservering the node foreach (var ele in selectedNodeSet) { undoRecorder.RecordDeletionForUndo(ele); ele.SaveResult = false; currentWorkspace.Nodes.Remove(ele); // VisualizationManager and EngineController subscribe // NodeDeleted event, so notify them that this node has // been deleted. dynamoModel.OnNodeDeleted(ele); // Assign a new guid to this node, otherwise when node is // compiled to AST, literally it is still in global scope // instead of in function scope. ele.GUID = Guid.NewGuid(); ele.RenderPackages.Clear(); ele.Workspace = newNodeWorkspace; } // add to new newNodeWorkspace.Nodes.AddRange(selectedNodeSet); newNodeWorkspace.Connectors.AddRange(fullySelectedConns); foreach (var node in newNodeWorkspace.Nodes) { node.DisableReporting(); } double leftShift = leftMost - 250; foreach (NodeModel node in newNodeWorkspace.Nodes) { node.X = node.X - leftShift; node.Y = node.Y - topMost; } #endregion #region Process inputs var inConnectors = new List <Tuple <NodeModel, int> >(); var uniqueInputSenders = new Dictionary <Tuple <NodeModel, int>, Symbol>(); //Step 3: insert variables (reference step 1) foreach (var input in Enumerable.Range(0, inputs.Count).Zip(inputs, Tuple.Create)) { int inputIndex = input.Item1; NodeModel inputReceiverNode = input.Item2.Item1; int inputReceiverData = input.Item2.Item2; NodeModel inputNode = input.Item2.Item3.Item2; int inputData = input.Item2.Item3.Item1; Symbol node; var key = Tuple.Create(inputNode, inputData); if (uniqueInputSenders.ContainsKey(key)) { node = uniqueInputSenders[key]; } else { inConnectors.Add(Tuple.Create(inputNode, inputData)); node = newNodeWorkspace.AddNode <Symbol>(); node.InputSymbol = inputReceiverNode.InPortData[inputReceiverData].NickName; node.SetNickNameFromAttribute(); node.DisableReporting(); node.X = 0; node.Y = inputIndex * (50 + node.Height); uniqueInputSenders[key] = node; } //var curriedNode = curriedNodeArgs.FirstOrDefault(x => x.OuterNode == inputNode); //if (curriedNode == null) //{ newNodeWorkspace.AddConnection( node, inputReceiverNode, 0, inputReceiverData); //} //else //{ // //Connect it to the applier // newNodeWorkspace.AddConnection(node, curriedNode.InnerNode, 0, 0); // //Connect applier to the inner input receive // newNodeWorkspace.AddConnection( // curriedNode.InnerNode, // inputReceiverNode, // 0, // inputReceiverData); //} } #endregion #region Process outputs //List of all inner nodes to connect an output. Unique. var outportList = new List <Tuple <NodeModel, int> >(); var outConnectors = new List <Tuple <NodeModel, int, int> >(); int i = 0; if (outputs.Any()) { foreach (var output in outputs) { if ( outportList.All( x => !(x.Item1 == output.Item1 && x.Item2 == output.Item2))) { NodeModel outputSenderNode = output.Item1; int outputSenderData = output.Item2; NodeModel outputReceiverNode = output.Item3.Item2; //if (curriedNodeArgs.Any(x => x.OuterNode == outputReceiverNode)) // continue; outportList.Add(Tuple.Create(outputSenderNode, outputSenderData)); //Create Symbol Node var node = newNodeWorkspace.AddNode <Output>(); node.Symbol = outputSenderNode.OutPortData[outputSenderData].NickName; node.SetNickNameFromAttribute(); node.DisableReporting(); node.X = rightMost + 75 - leftShift; node.Y = i * (50 + node.Height); newNodeWorkspace.AddConnection( outputSenderNode, node, outputSenderData, 0); i++; } } //Connect outputs to new node foreach (var output in outputs) { //Node to be connected to in CurrentWorkspace NodeModel outputSenderNode = output.Item1; //Port to be connected to on outPutNode_outer int outputSenderData = output.Item2; int outputReceiverData = output.Item3.Item1; NodeModel outputReceiverNode = output.Item3.Item2; //var curriedNode = // curriedNodeArgs.FirstOrDefault(x => x.OuterNode == outputReceiverNode); //if (curriedNode == null) //{ // we create the connectors in the current space later outConnectors.Add( Tuple.Create( outputReceiverNode, outportList.FindIndex( x => x.Item1 == outputSenderNode && x.Item2 == outputSenderData), outputReceiverData)); //} //else //{ // int targetPort = // curriedNode.Inputs.First( // x => x.InnerNodeInputSender == outputSenderNode) // .OuterNodeInPortData; // int targetPortIndex = // curriedNode.OuterNodePortDataList.IndexOf(targetPort); // //Connect it (new dynConnector) // newNodeWorkspace.AddConnection( // outputSenderNode, // curriedNode.InnerNode, // outputSenderData, // targetPortIndex + 1); //} } } else { foreach (var hanging in selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.OutPortData.Count) .Where(port => !node.HasOutput(port)) .Select(port => new { node, port })).Distinct()) { //Create Symbol Node var node = newNodeWorkspace.AddNode <Output>(); node.Symbol = hanging.node.OutPortData[hanging.port].NickName; node.SetNickNameFromAttribute(); //store the element in the elements list node.DisableReporting(); node.X = rightMost + 75 - leftShift; node.Y = i * (50 + node.Height); newNodeWorkspace.AddConnection(hanging.node, node, hanging.port, 0); i++; } } #endregion // save and load the definition from file newNodeDefinition.SyncWithWorkspace(dynamoModel, true, true); dynamoModel.Workspaces.Add(newNodeWorkspace); string name = newNodeDefinition.FunctionId.ToString(); var collapsedNode = currentWorkspace.AddNode(avgX, avgY, name); undoRecorder.RecordCreationForUndo(collapsedNode); // place the node as intended, not centered collapsedNode.X = avgX; collapsedNode.Y = avgY; collapsedNode.DisableReporting(); foreach ( var nodeTuple in inConnectors.Select( (x, idx) => new { node = x.Item1, from = x.Item2, to = idx })) { var conn = currentWorkspace.AddConnection( nodeTuple.node, collapsedNode, nodeTuple.from, nodeTuple.to); if (conn != null) { undoRecorder.RecordCreationForUndo(conn); } } foreach (var nodeTuple in outConnectors) { var conn = currentWorkspace.AddConnection( collapsedNode, nodeTuple.Item1, nodeTuple.Item2, nodeTuple.Item3); if (conn != null) { undoRecorder.RecordCreationForUndo(conn); } } collapsedNode.EnableReporting(); currentWorkspace.EnableReporting(); foreach (var node in newNodeWorkspace.Nodes) { node.EnableReporting(); } newNodeWorkspace.WatchChanges = true; } }