diff --git a/src/DynamoCoreWpf/Properties/Resources.Designer.cs b/src/DynamoCoreWpf/Properties/Resources.Designer.cs index db8e4d9ea0f..54ba4ec8cfc 100644 --- a/src/DynamoCoreWpf/Properties/Resources.Designer.cs +++ b/src/DynamoCoreWpf/Properties/Resources.Designer.cs @@ -2683,6 +2683,33 @@ public static string EditWindowTitle { } } + /// + /// Looks up a localized string similar to Element Binding allows a two-way interaction between Dynamo and the host application like Revit or Civil3D where a user can select an element in the host document and have Dynamo "bind" that element to a node in the workspace.. + /// + public static string ElementBindingDesc { + get { + return ResourceManager.GetString("ElementBindingDesc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A Save As command will create a workspace which is treated as a completely new file by Dynamo and existing element binding data will be lost. New element binding data will be created as normal as you run this file. Use the Save command instead if you wish to preserve element binding with the host document.. + /// + public static string ElementBindingWarningMessage { + get { + return ResourceManager.GetString("ElementBindingWarningMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Element Binding Warning. + /// + public static string ElementBindingWarningTitle { + get { + return ResourceManager.GetString("ElementBindingWarningTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to An error occurred when loading the application icon: {0}. /// diff --git a/src/DynamoCoreWpf/Properties/Resources.en-US.resx b/src/DynamoCoreWpf/Properties/Resources.en-US.resx index caac9cd1ebf..b6b9a256805 100644 --- a/src/DynamoCoreWpf/Properties/Resources.en-US.resx +++ b/src/DynamoCoreWpf/Properties/Resources.en-US.resx @@ -3651,4 +3651,13 @@ In certain complex graphs or host program scenarios, Automatic mode may cause in When toggled on, file names of exported images include date and time of export. - \ No newline at end of file + + A Save As command will create a workspace which is treated as a completely new file by Dynamo and existing element binding data will be lost. New element binding data will be created as normal as you run this file. Use the Save command instead if you wish to preserve element binding with the host document. + + + Element Binding Warning + + + Element Binding allows a two-way interaction between Dynamo and the host application like Revit or Civil3D where a user can select an element in the host document and have Dynamo "bind" that element to a node in the workspace. + + diff --git a/src/DynamoCoreWpf/Properties/Resources.resx b/src/DynamoCoreWpf/Properties/Resources.resx index e094990be3f..20166f73c35 100644 --- a/src/DynamoCoreWpf/Properties/Resources.resx +++ b/src/DynamoCoreWpf/Properties/Resources.resx @@ -3638,4 +3638,13 @@ In certain complex graphs or host program scenarios, Automatic mode may cause in When toggled on, file names of exported images include date and time of export. - \ No newline at end of file + + A Save As command will create a workspace which is treated as a completely new file by Dynamo and existing element binding data will be lost. New element binding data will be created as normal as you run this file. Use the Save command instead if you wish to preserve element binding with the host document. + + + Element Binding Warning + + + Element Binding allows a two-way interaction between Dynamo and the host application like Revit or Civil3D where a user can select an element in the host document and have Dynamo "bind" that element to a node in the workspace. + + diff --git a/src/DynamoCoreWpf/UI/Prompts/DynamoMessageBox.xaml b/src/DynamoCoreWpf/UI/Prompts/DynamoMessageBox.xaml index 3bce7cdcf4d..cd7ba9d79b8 100644 --- a/src/DynamoCoreWpf/UI/Prompts/DynamoMessageBox.xaml +++ b/src/DynamoCoreWpf/UI/Prompts/DynamoMessageBox.xaml @@ -4,7 +4,7 @@ xmlns:p="clr-namespace:Dynamo.Wpf.Properties;assembly=DynamoCoreWpf" xmlns:ui="clr-namespace:Dynamo.UI" xmlns:localui="clr-namespace:Dynamo.Wpf.UI.GuidedTour" - xmlns:w="clr-namespace:System.Windows;assembly=PresentationCore" + xmlns:fa="http://schemas.fontawesome.io/icons/" Title="{x:Static p:Resources.GenericTaskDialogTitle}" MinWidth="400" MaxWidth="500" @@ -98,6 +98,35 @@ Foreground="#3C3C3C" Text="{Binding TitleText, UpdateSourceTrigger=PropertyChanged}" TextWrapping="Wrap" /> + + /// A tooltip is shown on the message box when this is set to true and if + /// Tooltip is non-null and non-empty. + /// + public bool ShowTooltip { get; private set; } + + /// + /// A tooltip is shown on the message box when this is set to a non-empty string + /// and ShowTooltip is true. + /// + public string Tooltip { get; private set; } + #endregion /// @@ -97,6 +109,8 @@ public DynamoMessageBox() { InitializeComponent(); DataContext = this; + ShowTooltip = false; + ToolTip = ""; } /// @@ -106,16 +120,19 @@ public DynamoMessageBox() /// /// /// + /// /// public static MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, - MessageBoxImage icon) + MessageBoxImage icon, string tooltip = "") { var dynamoMessageBox = new DynamoMessageBox { BodyText = messageBoxText, TitleText = caption, MessageBoxButton = button, - MessageBoxImage = icon + MessageBoxImage = icon, + ShowTooltip = !string.IsNullOrEmpty(tooltip), + Tooltip = tooltip }; dynamoMessageBox.ConfigureButtons(button); diff --git a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs index d412584d59a..307cd71115b 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs @@ -2087,10 +2087,13 @@ private void InternalSaveAs(string path, SaveContext saveContext, bool isBackup { try { - Model.Logger.Log(String.Format(Properties.Resources.SavingInProgress, path)); - CurrentSpaceViewModel.Save(path, isBackup, Model.EngineController, saveContext); + Model.Logger.Log(string.Format(Properties.Resources.SavingInProgress, path)); + var hasSaved = CurrentSpaceViewModel.Save(path, isBackup, Model.EngineController, saveContext); - if (!isBackup) AddToRecentFiles(path); + if (!isBackup && hasSaved) + { + AddToRecentFiles(path); + } } catch (Exception ex) { @@ -2151,8 +2154,10 @@ internal void SaveAs(Guid id, string path, bool isBackup = false, SaveContext sa try { Model.Logger.Log(String.Format(Properties.Resources.SavingInProgress, path)); - Workspaces.Where(w => w.Model.Guid == id).FirstOrDefault().Save(path, isBackup, Model.EngineController, saveContext); - if (!isBackup) AddToRecentFiles(path); + var hasSaved = Workspaces.FirstOrDefault(w => w.Model.Guid == id).Save( + path, isBackup, Model.EngineController, saveContext); + + if (!isBackup && hasSaved) AddToRecentFiles(path); } catch (Exception ex) { diff --git a/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs index 4c63be9050b..2d95e6378ec 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs @@ -20,6 +20,7 @@ using Dynamo.Graph.Workspaces; using Dynamo.Models; using Dynamo.Selection; +using Dynamo.UI.Prompts; using Dynamo.Utilities; using Dynamo.Wpf.ViewModels; using Dynamo.Wpf.ViewModels.Core; @@ -598,7 +599,7 @@ internal void ZoomOutInternal() /// /// /// Thrown when the file path is null. - internal void Save(string filePath, bool isBackup = false, EngineController engine = null, SaveContext saveContext = SaveContext.None) + internal bool Save(string filePath, bool isBackup = false, EngineController engine = null, SaveContext saveContext = SaveContext.None) { if (String.IsNullOrEmpty(filePath)) { @@ -627,6 +628,23 @@ internal void Save(string filePath, bool isBackup = false, EngineController engi { // For intentional SaveAs either through UI or API calls, replace workspace elements' Guids and workspace Id jo["Uuid"] = Guid.NewGuid().ToString(); + if (jo["Bindings"] != null && jo["Bindings"].Any()) + { + jo["Bindings"] = JToken.Parse("[]"); + + if (!DynamoModel.IsTestMode) + { + var result = DynamoMessageBox.Show(Wpf.Properties.Resources.ElementBindingWarningMessage, + Wpf.Properties.Resources.ElementBindingWarningTitle, MessageBoxButton.OKCancel, + MessageBoxImage.Warning, Wpf.Properties.Resources.ElementBindingDesc); + + if (result == MessageBoxResult.Cancel) + { + return false; + } + } + } + saveContent = GuidUtility.UpdateWorkspaceGUIDs(jo.ToString()); } else @@ -656,6 +674,8 @@ internal void Save(string filePath, bool isBackup = false, EngineController engi Debug.WriteLine(ex.Message + " : " + ex.StackTrace); throw (ex); } + + return true; } /// /// This function appends view block to the model json diff --git a/test/DynamoCoreWpfTests/DynamoViewTests.cs b/test/DynamoCoreWpfTests/DynamoViewTests.cs index 706448ba3d9..3ca828479a6 100644 --- a/test/DynamoCoreWpfTests/DynamoViewTests.cs +++ b/test/DynamoCoreWpfTests/DynamoViewTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -21,6 +22,7 @@ using Dynamo.Wpf.ViewModels.Core; using Dynamo.Wpf.Views; using DynamoCoreWpfTests.Utility; +using Newtonsoft.Json.Linq; using NUnit.Framework; using SharpDX.DXGI; @@ -126,6 +128,49 @@ public void OpeningWorkspaceWithTclsrustWarning() DynamoModel.IsTestMode = true; } + [Test] + public void ElementBinding_SaveAs() + { + var prebindingPathInTestDir = @"core\callsite\trace_test-prebinding.dyn"; + var prebindingPath = Path.Combine(GetTestDirectory(ExecutingDirectory), prebindingPathInTestDir); + + var pathInTestsDir = @"core\callsite\trace_test.dyn"; + var filePath = Path.Combine(GetTestDirectory(ExecutingDirectory), pathInTestsDir); + + // Always start with a fresh workspace with no binding data for this test. + File.Copy(prebindingPath, filePath); + OpenAndRun(pathInTestsDir); + + // Assert that the node doesn't have trace data the first time it's run. + var hasTraceData = Model.CurrentWorkspace.Nodes.FirstOrDefault(x => + x.Name == "IncrementerTracedClass.WasCreatedWithTrace"); + Assert.AreEqual(false, hasTraceData.CachedValue.Data); + + // Saving the workspace after a run serializes trace data to the DYN. + ViewModel.SaveCommand.Execute(null); + + DynamoUtilities.PathHelper.isValidJson(filePath, out string fileContents, out Exception ex); + var obj = DSCore.Data.ParseJSON(fileContents) as Dictionary; + Assert.AreEqual(1, (obj["Bindings"] as IEnumerable).Count()); + + var saveAsPathInTestDir = @"core\callsite\trace_test2.dyn"; + var saveAsPath = Path.Combine(GetTestDirectory(ExecutingDirectory), saveAsPathInTestDir); + + // SaveAs current workspace, close workspace. + ViewModel.SaveAsCommand.Execute(saveAsPath); + ViewModel.CloseHomeWorkspaceCommand.Execute(null); + + Open(saveAsPathInTestDir); + + // Assert saved as file doesn't have binding data after open. + DynamoUtilities.PathHelper.isValidJson(saveAsPath, out fileContents, out ex); + obj = DSCore.Data.ParseJSON(fileContents) as Dictionary; + Assert.AreEqual(0, (obj["Bindings"] as IEnumerable).Count()); + + File.Delete(filePath); + File.Delete(saveAsPath); + } + [Test] public void TestToastNotificationClosingBehavior() { diff --git a/test/core/callsite/trace_test-prebinding.dyn b/test/core/callsite/trace_test-prebinding.dyn new file mode 100644 index 00000000000..7d85def8728 --- /dev/null +++ b/test/core/callsite/trace_test-prebinding.dyn @@ -0,0 +1,186 @@ +{ + "Uuid": "92697b36-63f4-4e89-8814-4b1681342cf0", + "IsCustomNode": false, + "Description": "", + "Name": "trace_test", + "ElementResolver": { + "ResolutionMap": {} + }, + "Inputs": [], + "Outputs": [], + "Nodes": [ + { + "ConcreteType": "Dynamo.Graph.Nodes.ZeroTouch.DSFunction, DynamoCore", + "Id": "963b1305492e4741a2639848b37ba25f", + "NodeType": "FunctionNode", + "Inputs": [ + { + "Id": "7d54983d59b2475486dbea82e4cd8234", + "Name": "x", + "Description": "int", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "Outputs": [ + { + "Id": "5fa2598e19d6478fb9134f42c2e4eb49", + "Name": "IncrementerTracedClass", + "Description": "IncrementerTracedClass", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "FunctionSignature": "FFITarget.IncrementerTracedClass.IncrementerTracedClass@int", + "Replication": "Auto", + "Description": "Note that x is a dummy var here that is intended to force replicated dispatch it's not actually used\n\nIncrementerTracedClass.IncrementerTracedClass (x: int): IncrementerTracedClass" + }, + { + "ConcreteType": "Dynamo.Graph.Nodes.CodeBlockNodeModel, DynamoCore", + "Id": "72fa79780443493faff475150a26c935", + "NodeType": "CodeBlockNode", + "Inputs": [], + "Outputs": [ + { + "Id": "e63d799074504deb914e70ebb0d492d6", + "Name": "", + "Description": "Value of expression at line 1", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "Replication": "Disabled", + "Description": "Allows for DesignScript code to be authored directly", + "Code": "12;" + }, + { + "ConcreteType": "Dynamo.Graph.Nodes.ZeroTouch.DSFunction, DynamoCore", + "Id": "fca7f7004dec4c1aa7383ca0a722d5fa", + "NodeType": "FunctionNode", + "Inputs": [ + { + "Id": "a91e903ed65e44b7b3f8b733d814e918", + "Name": "incrementerTracedClass", + "Description": "FFITarget.IncrementerTracedClass", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "Outputs": [ + { + "Id": "4f7a39b495454181a927247cfc2d045e", + "Name": "bool", + "Description": "bool", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "FunctionSignature": "FFITarget.IncrementerTracedClass.WasCreatedWithTrace", + "Replication": "Auto", + "Description": "IncrementerTracedClass.WasCreatedWithTrace ( ): bool" + } + ], + "Connectors": [ + { + "Start": "5fa2598e19d6478fb9134f42c2e4eb49", + "End": "a91e903ed65e44b7b3f8b733d814e918", + "Id": "d68582a233564324926bd398c01905df", + "IsHidden": "False" + }, + { + "Start": "e63d799074504deb914e70ebb0d492d6", + "End": "7d54983d59b2475486dbea82e4cd8234", + "Id": "6884d21efb704658b6902b11fdf87358", + "IsHidden": "False" + } + ], + "Dependencies": [], + "NodeLibraryDependencies": [], + "Thumbnail": "", + "GraphDocumentationURL": null, + "ExtensionWorkspaceData": [ + { + "ExtensionGuid": "28992e1d-abb9-417f-8b1b-05e053bee670", + "Name": "Properties", + "Version": "2.19", + "Data": {} + } + ], + "Author": "", + "Linting": { + "activeLinter": "None", + "activeLinterId": "7b75fb44-43fd-4631-a878-29f4d5d8399a", + "warningCount": 0, + "errorCount": 0 + }, + "Bindings": [], + "View": { + "Dynamo": { + "ScaleFactor": 1.0, + "HasRunWithoutCrash": true, + "IsVisibleInDynamoLibrary": true, + "Version": "3.0.0.6191", + "RunType": "Automatic", + "RunPeriod": "1000" + }, + "Camera": { + "Name": "_Background Preview", + "EyeX": -17.0, + "EyeY": 24.0, + "EyeZ": 50.0, + "LookX": 12.0, + "LookY": -13.0, + "LookZ": -58.0, + "UpX": 0.0, + "UpY": 1.0, + "UpZ": 0.0 + }, + "ConnectorPins": [], + "NodeViews": [ + { + "Id": "963b1305492e4741a2639848b37ba25f", + "Name": "IncrementerTracedClass.IncrementerTracedClass", + "IsSetAsInput": false, + "IsSetAsOutput": false, + "Excluded": false, + "ShowGeometry": true, + "X": 440.79999999999995, + "Y": 204.80000000000007 + }, + { + "Id": "72fa79780443493faff475150a26c935", + "Name": "Code Block", + "IsSetAsInput": false, + "IsSetAsOutput": false, + "Excluded": false, + "ShowGeometry": true, + "X": 196.0, + "Y": 255.0 + }, + { + "Id": "fca7f7004dec4c1aa7383ca0a722d5fa", + "Name": "IncrementerTracedClass.WasCreatedWithTrace", + "IsSetAsInput": false, + "IsSetAsOutput": false, + "Excluded": false, + "ShowGeometry": true, + "X": 560.0000000000002, + "Y": 438.80000000000007 + } + ], + "Annotations": [], + "X": 0.0, + "Y": 0.0, + "Zoom": 1.0 + } +} \ No newline at end of file