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 = ""; } }
/// <summary> /// Change the currently visible workspace to a custom node's workspace /// </summary> /// <param name="symbol">The function definition for the custom node workspace to be viewed</param> internal void ViewCustomNodeWorkspace(FunctionDefinition symbol) { if (symbol == null) { throw new Exception("There is a null function definition for this node."); } if (_model.CurrentSpace.Name.Equals(symbol.Workspace.Name)) { return; } dynWorkspaceModel newWs = symbol.Workspace; if (!this._model.Workspaces.Contains(newWs)) { this._model.Workspaces.Add(newWs); } CurrentSpaceViewModel.OnStopDragging(this, EventArgs.Empty); _model.CurrentSpace = newWs; _model.CurrentSpace.OnDisplayed(); //set the zoom and offsets events var vm = dynSettings.Controller.DynamoViewModel.Workspaces.First(x => x.Model == newWs); vm.OnCurrentOffsetChanged(this, new PointEventArgs(new Point(newWs.X, newWs.Y))); vm.OnZoomChanged(this, new ZoomEventArgs(newWs.Zoom)); }
/// <summary> /// Save a function. This includes writing to a file and compiling the /// function and saving it to the FSchemeEnvironment /// </summary> /// <param name="definition">The definition to saveo</param> /// <param name="bool">Whether to write the function to file</param> /// <returns>Whether the operation was successful</returns> public string SaveFunctionOnly(FunctionDefinition definition) { if (definition == null) { return(""); } // Get the internal nodes for the function dynWorkspaceModel functionWorkspace = definition.Workspace; string directory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); string pluginsPath = Path.Combine(directory, "definitions"); try { if (!Directory.Exists(pluginsPath)) { Directory.CreateDirectory(pluginsPath); } string path = Path.Combine(pluginsPath, dynSettings.FormatFileName(functionWorkspace.Name) + ".dyf"); dynWorkspaceModel.SaveWorkspace(path, functionWorkspace); return(path); } catch (Exception e) { DynamoLogger.Instance.Log("Error saving:" + e.GetType()); DynamoLogger.Instance.Log(e); return(""); } }
public FileDialog GetSaveDialog(dynWorkspaceModel workspace) { FileDialog fileDialog = new SaveFileDialog { AddExtension = true, }; string ext, fltr; if (workspace == _model.HomeSpace) { ext = ".dyn"; fltr = "Dynamo Workspace (*.dyn)|*.dyn"; } else { ext = ".dyf"; fltr = "Dynamo Custom Node (*.dyf)|*.dyf"; } fltr += "|All files (*.*)|*.*"; fileDialog.FileName = workspace.Name + ext; fileDialog.AddExtension = true; fileDialog.DefaultExt = ext; fileDialog.Filter = fltr; return(fileDialog); }
/// <summary> /// Get a dynFunction from a guid, also stores type internally info for future instantiation. /// And add the compiled node to the enviro /// As a side effect, any of its dependent nodes are also initialized. /// </summary> /// <param name="environment">The environment from which to get the </param> /// <param name="guid">Open a definition from a path, without instantiating the nodes or dependents</param> public bool GetNodeInstance(Guid guid, out dynFunction result) { var controller = dynSettings.Controller; if (!this.Contains(guid)) { result = null; return(false); } FunctionDefinition def = null; if (!this.IsInitialized(guid)) { if (!GetDefinitionFromPath(guid, out def)) { result = null; return(false); } } else { def = this.loadedNodes[guid]; } dynWorkspaceModel ws = def.Workspace; IEnumerable <string> inputs = ws.Nodes.Where(e => e is dynSymbol) .Select(s => (s as dynSymbol).Symbol); IEnumerable <string> outputs = ws.Nodes.Where(e => e is dynOutput) .Select(o => (o as dynOutput).Symbol); if (!outputs.Any()) { var topMost = new List <Tuple <int, dynNodeModel> >(); IEnumerable <dynNodeModel> topMostNodes = ws.GetTopMostNodes(); foreach (dynNodeModel topNode in topMostNodes) { foreach (int output in Enumerable.Range(0, topNode.OutPortData.Count)) { if (!topNode.HasOutput(output)) { topMost.Add(Tuple.Create(output, topNode)); } } } outputs = topMost.Select(x => x.Item2.OutPortData[x.Item1].NickName); } result = controller.DynamoViewModel.CreateFunction(inputs, outputs, def); result.NickName = ws.Name; return(true); }
/// <summary> /// Requests a message box asking the user to save the workspace and allows saving. /// </summary> /// <param name="workspace">The workspace for which to show the dialog</param> /// <returns>False if the user cancels, otherwise true</returns> public bool AskUserToSaveWorkspaceOrCancel(dynWorkspaceModel workspace, bool allowCancel = true) { var args = new WorkspaceSaveEventArgs(workspace, allowCancel); OnRequestUserSaveWorkflow(this, args); if (!args.Success) { return(false); } return(true); }
/// <summary> /// Attempts to save a given workspace. Shows a save as dialog if the /// workspace does not already have a path associated with it /// </summary> /// <param name="workspace">The workspace for which to show the dialog</param> internal void ShowSaveDialogIfNeededAndSave(dynWorkspaceModel workspace) { if (workspace.FilePath != null) { _model.SaveAs(workspace.FilePath, workspace); } else { var fd = this.GetSaveDialog(workspace); if (fd.ShowDialog() == DialogResult.OK) { _model.SaveAs(fd.FileName, workspace); } } }
public dynWorkspaceViewModel(dynWorkspaceModel model, DynamoViewModel vm) { _model = model; //setup the composite collection var nodesColl = new CollectionContainer { Collection = Nodes }; _workspaceElements.Add(nodesColl); var connColl = new CollectionContainer { Collection = Connectors }; _workspaceElements.Add(connColl); var notesColl = new CollectionContainer { Collection = Notes }; _workspaceElements.Add(notesColl); //respond to collection changes on the model by creating new view models //currently, view models are added for notes and nodes //connector view models are added during connection _model.Nodes.CollectionChanged += Nodes_CollectionChanged; _model.Notes.CollectionChanged += Notes_CollectionChanged; _model.Connectors.CollectionChanged += Connectors_CollectionChanged; _model.PropertyChanged += ModelPropertyChanged; //DynamoSelection.Instance.Selection.CollectionChanged += NodeFromSelectionCanExecuteChanged; // sync collections Nodes_CollectionChanged(null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, _model.Nodes)); Connectors_CollectionChanged(null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, _model.Connectors)); Notes_CollectionChanged(null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, _model.Notes)); }
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); // show the dialog if (dynSettings.Controller.DynamoViewModel.ShowNewFunctionDialog(ref newName, ref newCategory, ref newDescription, true)) { if (workspace is FuncWorkspace) { var id = dynSettings.CustomNodeManager.GetGuidFromName(workspace.Name); dynSettings.CustomNodeManager.Refactor(id, newName, newCategory, newDescription); } workspace.Name = newName; workspace.Description = newDescription; workspace.Category = newCategory; // workspace.Author = ""; } }
/// <summary> /// Save to a specific file path, if the path is null or empty, does nothing. /// If successful, the CurrentSpace.FilePath field is updated as a side effect /// </summary> /// <param name="path">The path to save to</param> /// <param name="workspace">The workspace to save</param> internal void SaveAs(string path, dynWorkspaceModel workspace) { if (!String.IsNullOrEmpty(path)) { // if it's a custom node if (workspace is FuncWorkspace) { var def = dynSettings.Controller.CustomNodeManager.GetDefinitionFromWorkspace(workspace); def.Workspace.FilePath = path; if (def != null) { this.SaveFunction(def, true); workspace.FilePath = path; } return; } if (!dynWorkspaceModel.SaveWorkspace(path, workspace)) { Log("Workbench could not be saved."); } else { workspace.FilePath = path; } } }
public WorkspaceSaveEventArgs(dynWorkspaceModel ws, bool allowCancel) { Workspace = ws; AllowCancel = allowCancel; Success = false; }
private FileDialog GetSaveDialog(dynWorkspaceModel workspace) { FileDialog fileDialog = new SaveFileDialog { AddExtension = true, }; string ext, fltr; if ( workspace == _model.HomeSpace ) { ext = ".dyn"; fltr = "Dynamo Workspace (*.dyn)|*.dyn"; } else { ext = ".dyf"; fltr = "Dynamo Custom Node (*.dyf)|*.dyf"; } fltr += "|All files (*.*)|*.*"; fileDialog.FileName = workspace.Name + ext; fileDialog.AddExtension = true; fileDialog.DefaultExt = ext; fileDialog.Filter = fltr; return fileDialog; }
/// <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> internal static void Collapse(IEnumerable<dynNodeModel> selectedNodes, dynWorkspaceModel currentWorkspace) { var selectedNodeSet = new HashSet<dynNodeModel>(selectedNodes); //First, prompt the user to enter a name string newNodeName ="", newNodeCategory =""; if (!dynSettings.Controller.DynamoViewModel.ShowNewFunctionDialog(ref newNodeName, ref newNodeCategory)) { return; } var newNodeWorkspace = new FuncWorkspace(newNodeName, newNodeCategory, 0, 0) { WatchChanges = false }; var newNodeDefinition = new FunctionDefinition(Guid.NewGuid()) { Workspace = 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<dynNodeModel, int, Tuple<int, dynNodeModel>>>( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.InPortData.Count).Where(node.HasInput) .Select(data => Tuple.Create(node, data, node.Inputs[data])) .Where(input => !selectedNodeSet.Contains(input.Item3.Item2)))); var outputs = new HashSet<Tuple<dynNodeModel, int, Tuple<int, dynNodeModel>>>( 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<dynNodeModel>( inputs .Select(x => x.Item3.Item2) .Intersect(outputs.Select(x => x.Item3.Item2))) .Select( outerNode => { var node = new dynApply1(); //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(); //MVVM: don't call update layout here //dynSettings.Bench.WorkBench.UpdateLayout(); 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<dynConnectorModel>(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) { currentWorkspace.Nodes.Remove(ele); } 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 (dynNodeModel 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 (dynConnectorModel connector in removeConnectors) { connector.NotifyConnectedPortsOfDeletion(); currentWorkspace.Connectors.Remove(connector); } #endregion newNodeWorkspace.Nodes.ToList().ForEach(x => x.DisableReporting()); var inConnectors = new List<Tuple<dynNodeModel, int, int>>(); #region Process inputs var uniqueInputSenders = new Dictionary<Tuple<dynNodeModel, int>, dynSymbol>(); //Step 3: insert variables (reference step 1) foreach (var input in Enumerable.Range(0, inputs.Count).Zip(inputs, Tuple.Create)) { int inputIndex = input.Item1; dynNodeModel inputReceiverNode = input.Item2.Item1; int inputReceiverData = input.Item2.Item2; dynNodeModel inputNode = input.Item2.Item3.Item2; int inputData = input.Item2.Item3.Item1; dynSymbol 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 dynSymbol { Symbol = 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(); //MVVM : Do not add view directly to canvas /*dynSettings.Bench.WorkBench.Children.Add(nodeUI); //Place it in an appropriate spot Canvas.SetLeft(nodeUI, 0); Canvas.SetTop(nodeUI, inputIndex * (50 + node.NodeUI.Height)); dynSettings.Bench.WorkBench.UpdateLayout();*/ 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 = dynConnectorModel.Make(node, inputReceiverNode, 0, inputReceiverData, 0 ); if (conn1 != null) newNodeWorkspace.Connectors.Add(conn1); } else { //Connect it to the applier var conn = dynConnectorModel.Make(node, curriedNode.InnerNode, 0, 0, 0); if (conn != null) newNodeWorkspace.Connectors.Add(conn); //Connect applier to the inner input receive var conn2 = dynConnectorModel.Make( curriedNode.InnerNode, inputReceiverNode, 0, inputReceiverData, 0); 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<dynNodeModel, int>>(); var outConnectors = new List<Tuple<dynNodeModel, int, int>>(); int i = 0; foreach (var output in outputs) { if (outportList.All(x => !(x.Item1 == output.Item1 && x.Item2 == output.Item2))) { dynNodeModel outputSenderNode = output.Item1; int outputSenderData = output.Item2; dynNodeModel outputReceiverNode = output.Item3.Item2; if (curriedNodeArgs.Any(x => x.OuterNode == outputReceiverNode)) continue; outportList.Add(Tuple.Create(outputSenderNode, outputSenderData)); //Create Symbol Node var node = new dynOutput { 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(); //MVVM : Do not add view directly to canvas /*dynSettings.Bench.WorkBench.Children.Add(nodeUI); //Place it in an appropriate spot Canvas.SetLeft(nodeUI, rightMost + 75 - leftShift); Canvas.SetTop(nodeUI, i * (50 + node.NodeUI.Height)); dynSettings.Bench.WorkBench.UpdateLayout();*/ node.X = rightMost + 75 - leftShift; node.Y = i*(50 + node.Height); var conn = dynConnectorModel.Make( outputSenderNode, node, outputSenderData, 0, 0 ); if (conn != null) newNodeWorkspace.Connectors.Add(conn); i++; } } //Connect outputs to new node foreach (var output in outputs) { //Node to be connected to in CurrentSpace dynNodeModel outputSenderNode = output.Item1; //Port to be connected to on outPutNode_outer int outputSenderData = output.Item2; int outputReceiverData = output.Item3.Item1; dynNodeModel 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 = dynConnectorModel.Make( outputSenderNode, curriedNode.InnerNode, outputSenderData, targetPortIndex + 1, 0); if (conn != null) newNodeWorkspace.Connectors.Add(conn); } } #endregion //set the name on the node collapsedNode.NickName = newNodeName; currentWorkspace.Nodes.Remove(collapsedNode); // save and load the definition from file dynSettings.Controller.CustomNodeLoader.SetNodeInfo(newNodeName, newNodeCategory, newNodeDefinition.FunctionId, ""); var path = dynSettings.Controller.DynamoViewModel.SaveFunctionOnly(newNodeDefinition); dynSettings.Controller.CustomNodeLoader.SetNodePath(newNodeDefinition.FunctionId, path); dynSettings.Controller.SearchViewModel.Add(newNodeName, newNodeCategory, newNodeDefinition.FunctionId); dynSettings.Controller.DynamoViewModel.CreateNodeCommand.Execute(new Dictionary<string, object>() { {"name", collapsedNode.Definition.FunctionId.ToString() }, {"x", avgX }, {"y", avgY } }); var newlyPlacedCollapsedNode = currentWorkspace.Nodes .Where(node => node is dynFunction) .First(node => ((dynFunction)node).Definition.FunctionId == newNodeDefinition.FunctionId); // place the node as intended, not centered newlyPlacedCollapsedNode.X = avgX; newlyPlacedCollapsedNode.Y = avgY; newlyPlacedCollapsedNode.DisableReporting(); foreach (var nodeTuple in inConnectors) { var conn = dynConnectorModel.Make( nodeTuple.Item1, newlyPlacedCollapsedNode, nodeTuple.Item2, nodeTuple.Item3, 0 ); if (conn != null) currentWorkspace.Connectors.Add(conn); } foreach (var nodeTuple in outConnectors) { var conn = dynConnectorModel.Make( newlyPlacedCollapsedNode, nodeTuple.Item1, nodeTuple.Item2, nodeTuple.Item3, 0 ); if (conn != null) currentWorkspace.Connectors.Add(conn); } newlyPlacedCollapsedNode.EnableReporting(); currentWorkspace.EnableReporting(); newNodeWorkspace.WatchChanges = true; }
/// <summary> /// Get a FunctionDefinition from a workspace. Assumes the FunctionDefinition is already loaded. /// Use IsInitialized to figure out if the FunctionDef is loaded. /// </summary> /// <param name="workspace">The workspace which you'd like to find the Definition for</param> /// <returns>A valid function definition if the FunctionDefinition is already loaded, otherwise null. </returns> public FunctionDefinition GetDefinitionFromWorkspace(dynWorkspaceModel workspace) { return(this.loadedNodes.Values.FirstOrDefault((def) => def.Workspace == workspace)); }
public static FScheme.Expression CompileFunction(FunctionDefinition definition, ref IEnumerable <string> inputNames, ref IEnumerable <string> outputNames) { if (definition == null) { return(null); } // Get the internal nodes for the function dynWorkspaceModel functionWorkspace = definition.Workspace; #region Find outputs // Find output elements for the node List <dynOutput> outputs = functionWorkspace.Nodes.OfType <dynOutput>().ToList(); var topMost = new List <Tuple <int, dynNodeModel> >(); // if we found output nodes, add select their inputs // these will serve as the function output if (outputs.Any()) { topMost.AddRange( outputs.Where(x => x.HasInput(0)).Select(x => x.Inputs[0])); outputNames = outputs.Select(x => x.Symbol); } else { // if there are no explicitly defined output nodes // get the top most nodes and set THEM as the output IEnumerable <dynNodeModel> topMostNodes = functionWorkspace.GetTopMostNodes(); var outNames = new List <string>(); foreach (dynNodeModel topNode in topMostNodes) { foreach (int output in Enumerable.Range(0, topNode.OutPortData.Count)) { if (!topNode.HasOutput(output)) { topMost.Add(Tuple.Create(output, topNode)); outNames.Add(topNode.OutPortData[output].NickName); } } } outputNames = outNames; } #endregion // color the node to define its connectivity foreach (var ele in topMost) { ele.Item2.ValidateConnections(); } //Find function entry point, and then compile the function and add it to our environment IEnumerable <dynNodeModel> variables = functionWorkspace.Nodes.Where(x => x is dynSymbol); inputNames = variables.Select(x => (x as dynSymbol).Symbol); INode top; var buildDict = new Dictionary <dynNodeModel, Dictionary <int, INode> >(); if (topMost.Count > 1) { InputNode node = new ExternalFunctionNode(FScheme.Value.NewList); int i = 0; foreach (var topNode in topMost) { string inputName = i.ToString(); node.AddInput(inputName); node.ConnectInput(inputName, new BeginNode()); try { var exp = topNode.Item2.Build(buildDict, topNode.Item1); node.ConnectInput(inputName, exp); } catch { } i++; } top = node; } else if (topMost.Count == 1) { top = topMost[0].Item2.BuildExpression(buildDict); } else { // if the custom node is empty, it will initially be an empty begin top = new BeginNode(); } // if the node has any outputs, we create a BeginNode in order to evaluate all of them // sequentially (begin evaluates a list of expressions) if (outputs.Any()) { var beginNode = new BeginNode(); List <dynNodeModel> hangingNodes = functionWorkspace.GetHangingNodes().ToList(); foreach (var tNode in hangingNodes.Select((x, index) => new { Index = index, Node = x })) { beginNode.AddInput(tNode.Index.ToString()); beginNode.ConnectInput(tNode.Index.ToString(), tNode.Node.Build(buildDict, 0)); } beginNode.AddInput(hangingNodes.Count.ToString()); beginNode.ConnectInput(hangingNodes.Count.ToString(), top); top = beginNode; } // make the anonymous function FScheme.Expression expression = Utils.MakeAnon(variables.Select(x => x.GUID.ToString()), top.Compile()); return(expression); }
/// <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> internal static void Collapse(IEnumerable <dynNodeModel> selectedNodes, dynWorkspaceModel currentWorkspace) { var selectedNodeSet = new HashSet <dynNodeModel>(selectedNodes); //First, prompt the user to enter a name string newNodeName = "", newNodeCategory = "", newNodeDescription = "A collapsed node"; if (!dynSettings.Controller.DynamoViewModel.ShowNewFunctionDialog(ref newNodeName, ref newNodeCategory, ref newNodeDescription)) { return; } var newNodeWorkspace = new FuncWorkspace(newNodeName, newNodeCategory, newNodeDescription, 0, 0) { WatchChanges = false }; var newNodeDefinition = new FunctionDefinition(Guid.NewGuid()) { Workspace = 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 <dynNodeModel, int, Tuple <int, dynNodeModel> > >( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.InPortData.Count).Where(node.HasInput) .Select(data => Tuple.Create(node, data, node.Inputs[data])) .Where(input => !selectedNodeSet.Contains(input.Item3.Item2)))); var outputs = new HashSet <Tuple <dynNodeModel, int, Tuple <int, dynNodeModel> > >( 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 <dynNodeModel>( inputs .Select(x => x.Item3.Item2) .Intersect(outputs.Select(x => x.Item3.Item2))) .Select( outerNode => { var node = new dynApply1(); //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(); //MVVM: don't call update layout here //dynSettings.Bench.WorkBench.UpdateLayout(); 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 <dynConnectorModel>(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) { currentWorkspace.Nodes.Remove(ele); } 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 (dynNodeModel 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 (dynConnectorModel connector in removeConnectors) { connector.NotifyConnectedPortsOfDeletion(); currentWorkspace.Connectors.Remove(connector); } #endregion newNodeWorkspace.Nodes.ToList().ForEach(x => x.DisableReporting()); var inConnectors = new List <Tuple <dynNodeModel, int, int> >(); #region Process inputs var uniqueInputSenders = new Dictionary <Tuple <dynNodeModel, int>, dynSymbol>(); //Step 3: insert variables (reference step 1) foreach (var input in Enumerable.Range(0, inputs.Count).Zip(inputs, Tuple.Create)) { int inputIndex = input.Item1; dynNodeModel inputReceiverNode = input.Item2.Item1; int inputReceiverData = input.Item2.Item2; dynNodeModel inputNode = input.Item2.Item3.Item2; int inputData = input.Item2.Item3.Item1; dynSymbol 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 dynSymbol { Symbol = 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(); //MVVM : Do not add view directly to canvas /*dynSettings.Bench.WorkBench.Children.Add(nodeUI); * * //Place it in an appropriate spot * Canvas.SetLeft(nodeUI, 0); * Canvas.SetTop(nodeUI, inputIndex * (50 + node.NodeUI.Height)); * * dynSettings.Bench.WorkBench.UpdateLayout();*/ 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 = dynConnectorModel.Make(node, inputReceiverNode, 0, inputReceiverData, 0); if (conn1 != null) { newNodeWorkspace.Connectors.Add(conn1); } } else { //Connect it to the applier var conn = dynConnectorModel.Make(node, curriedNode.InnerNode, 0, 0, 0); if (conn != null) { newNodeWorkspace.Connectors.Add(conn); } //Connect applier to the inner input receive var conn2 = dynConnectorModel.Make( curriedNode.InnerNode, inputReceiverNode, 0, inputReceiverData, 0); 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 <dynNodeModel, int> >(); var outConnectors = new List <Tuple <dynNodeModel, int, int> >(); int i = 0; foreach (var output in outputs) { if (outportList.All(x => !(x.Item1 == output.Item1 && x.Item2 == output.Item2))) { dynNodeModel outputSenderNode = output.Item1; int outputSenderData = output.Item2; dynNodeModel outputReceiverNode = output.Item3.Item2; if (curriedNodeArgs.Any(x => x.OuterNode == outputReceiverNode)) { continue; } outportList.Add(Tuple.Create(outputSenderNode, outputSenderData)); //Create Symbol Node var node = new dynOutput { 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(); //MVVM : Do not add view directly to canvas /*dynSettings.Bench.WorkBench.Children.Add(nodeUI); * * //Place it in an appropriate spot * Canvas.SetLeft(nodeUI, rightMost + 75 - leftShift); * Canvas.SetTop(nodeUI, i * (50 + node.NodeUI.Height)); * * dynSettings.Bench.WorkBench.UpdateLayout();*/ node.X = rightMost + 75 - leftShift; node.Y = i * (50 + node.Height); var conn = dynConnectorModel.Make( outputSenderNode, node, outputSenderData, 0, 0); if (conn != null) { newNodeWorkspace.Connectors.Add(conn); } i++; } } //Connect outputs to new node foreach (var output in outputs) { //Node to be connected to in CurrentSpace dynNodeModel outputSenderNode = output.Item1; //Port to be connected to on outPutNode_outer int outputSenderData = output.Item2; int outputReceiverData = output.Item3.Item1; dynNodeModel 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 = dynConnectorModel.Make( outputSenderNode, curriedNode.InnerNode, outputSenderData, targetPortIndex + 1, 0); if (conn != null) { newNodeWorkspace.Connectors.Add(conn); } } } #endregion //set the name on the node collapsedNode.NickName = newNodeName; currentWorkspace.Nodes.Remove(collapsedNode); // save and load the definition from file var customNodeInfo = new CustomNodeInfo(newNodeDefinition.FunctionId, newNodeName, newNodeCategory, "", ""); dynSettings.Controller.CustomNodeManager.SetNodeInfo(customNodeInfo); var path = dynSettings.Controller.DynamoViewModel.SaveFunctionOnly(newNodeDefinition); dynSettings.Controller.CustomNodeManager.SetNodePath(newNodeDefinition.FunctionId, path); dynSettings.Controller.SearchViewModel.Add(newNodeName, newNodeCategory, "No description provided", newNodeDefinition.FunctionId); dynSettings.Controller.DynamoViewModel.CreateNodeCommand.Execute(new Dictionary <string, object>() { { "name", collapsedNode.Definition.FunctionId.ToString() }, { "x", avgX }, { "y", avgY } }); var newlyPlacedCollapsedNode = currentWorkspace.Nodes .Where(node => node is dynFunction) .First(node => ((dynFunction)node).Definition.FunctionId == newNodeDefinition.FunctionId); // place the node as intended, not centered newlyPlacedCollapsedNode.X = avgX; newlyPlacedCollapsedNode.Y = avgY; newlyPlacedCollapsedNode.DisableReporting(); foreach (var nodeTuple in inConnectors) { var conn = dynConnectorModel.Make( nodeTuple.Item1, newlyPlacedCollapsedNode, nodeTuple.Item2, nodeTuple.Item3, 0); if (conn != null) { currentWorkspace.Connectors.Add(conn); } } foreach (var nodeTuple in outConnectors) { var conn = dynConnectorModel.Make( newlyPlacedCollapsedNode, nodeTuple.Item1, nodeTuple.Item2, nodeTuple.Item3, 0); if (conn != null) { currentWorkspace.Connectors.Add(conn); } } newlyPlacedCollapsedNode.EnableReporting(); currentWorkspace.EnableReporting(); newNodeWorkspace.WatchChanges = true; }
/// <summary> /// Attempts to save a given workspace. Shows a save as dialog if the /// workspace does not already have a path associated with it /// </summary> /// <param name="workspace">The workspace for which to show the dialog</param> private void ShowSaveDialogIfNeededAndSave(dynWorkspaceModel workspace) { if (workspace.FilePath != null) { this.SaveAs(workspace.FilePath, workspace); } else { var fd = this.GetSaveDialog(workspace); if (fd.ShowDialog() == DialogResult.OK) { this.SaveAs(fd.FileName, workspace); } } }
//Visibility vis = Visibility.Visible) /// <summary> /// Create a node from a type object in a given workspace. /// </summary> /// <param name="elementType"> The Type object from which the node can be activated </param> /// <param name="nickName"> A nickname for the node. If null, the nickName is loaded from the NodeNameAttribute of the node </param> /// <param name="guid"> The unique identifier for the node in the workspace. </param> /// <param name="x"> The x coordinate where the dynNodeView will be placed </param> /// <param name="y"> The x coordinate where the dynNodeView will be placed</param> /// <returns> The newly instantiate dynNode</returns> public dynNodeModel CreateInstanceAndAddNodeToWorkspace(Type elementType, string nickName, Guid guid, double x, double y, dynWorkspaceModel ws, bool isVisible = true, bool isUpstreamVisible = true) { try { dynNodeModel node = CreateNodeInstance(elementType, nickName, guid); ws.Nodes.Add(node); node.WorkSpace = ws; node.X = x; node.Y = y; node.IsVisible = isVisible; node.IsUpstreamVisible = isUpstreamVisible; return node; } catch (Exception e) { Log("Could not create an instance of the selected type: " + elementType); Log(e); return null; } }
/// <summary> /// Shows a message box asking the user to save the workspace and allows saving. /// </summary> /// <param name="workspace">The workspace for which to show the dialog</param> /// <returns>False if the user cancels, otherwise true</returns> public bool AskUserToSaveWorkspaceOrCancel(dynWorkspaceModel workspace, bool allowCancel = true) { var dialogText = ""; if (workspace is FuncWorkspace) { dialogText = "You have unsaved changes to custom node workspace " + workspace.Name + "\n\n Would you like to save your changes?"; } else // homeworkspace { if (string.IsNullOrEmpty(workspace.FilePath)) { dialogText = "You haven't saved your changes to the Home workspace. " + "\n\n Would you like to save your changes?"; } else { dialogText = "You have unsaved changes to " + Path.GetFileName( workspace.FilePath ) + "\n\n Would you like to save your changes?"; } } var buttons = allowCancel ? MessageBoxButton.YesNoCancel : MessageBoxButton.YesNo; var result = System.Windows.MessageBox.Show(dialogText, "Confirmation", buttons, MessageBoxImage.Question); if (result == MessageBoxResult.Yes) { this.ShowSaveDialogIfNeededAndSave(workspace); } else if (result == MessageBoxResult.Cancel) { return false; } return true; }
/// <summary> /// Get a FunctionDefinition from a workspace. Assumes the FunctionDefinition is already loaded. /// Use IsInitialized to figure out if the FunctionDef is loaded. /// </summary> /// <param name="workspace">The workspace which you'd like to find the Definition for</param> /// <returns>A valid function definition if the FunctionDefinition is already loaded, otherwise null. </returns> public FunctionDefinition GetDefinitionFromWorkspace(dynWorkspaceModel workspace) { return this.loadedNodes.Values.FirstOrDefault((def) => def.Workspace == workspace); }
/// <summary> /// Shows a message box asking the user to save the workspace and allows saving. /// </summary> /// <param name="workspace">The workspace for which to show the dialog</param> /// <returns>False if the user cancels, otherwise true</returns> public bool AskUserToSaveWorkspaceOrCancel(dynWorkspaceModel workspace) { var result = System.Windows.MessageBox.Show("You have unsaved changes to " + workspace.Name + "\n\n Would you like to save your changes?", "Confirmation", MessageBoxButton.YesNoCancel, MessageBoxImage.Question); if (result == MessageBoxResult.Yes) { this.ShowSaveDialogIfNeededAndSave(workspace); } else if (result == MessageBoxResult.Cancel) { return false; } return true; }