Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DYN-2763 Add options to change Python Engine in multiple python nodes #15475

Merged
merged 13 commits into from
Sep 11, 2024
20 changes: 20 additions & 0 deletions src/DynamoCore/Models/DynamoModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2946,6 +2946,26 @@ public bool OpenCustomNodeWorkspace(Guid guid)

return false;
}
/// <summary>
/// Opens an existing custom node workspace.
/// </summary>
/// <param name="guid">Identifier of the workspace to open</param>
/// <returns>True if workspace was found and open</returns>
internal bool OpenCustomNodeWorkspaceSilent(Guid guid)
{
CustomNodeWorkspaceModel customNodeWorkspace;
if (CustomNodeManager.TryGetFunctionWorkspace(guid, IsTestMode, out customNodeWorkspace))
{
if (!Workspaces.OfType<CustomNodeWorkspaceModel>().Contains(customNodeWorkspace))
{
AddWorkspace(customNodeWorkspace);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the custom node workspace loaded silently, we should also remove it somehow after Python engine switched?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about that as well, but the workspaces should/are marked dirty IMO, so user will know which custom nodes were updated. One scenario where this would be bad is maybe if a user has too many custom nodes?
Also saving the custom nodes for them and then closing the workspace seemed a bit hacky, would you like that instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer the current option then, maybe when customer has too many dyfs in the canvas, then this could potentially be a problem

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we could consult customers for feedback and then later update this functionality.

}

return true;
}

return false;
}

/// <summary>
/// Adds a node to the current workspace.
Expand Down
11 changes: 11 additions & 0 deletions src/DynamoCoreWpf/Commands/WorkspaceCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,17 @@ public bool HasSelection
get { return DynamoSelection.Instance.Selection.Count > 0; }
}

[JsonIgnore]
public bool CanUpdatePythonEngine
{
get { return DynamoViewModel.CanUpdatePythonNodeEngine(null); }
}
[JsonIgnore]
public bool CanUpdateAllPythonEngine
{
get { return DynamoViewModel.CanUpdateAllPythonEngine(null); }
}

[JsonIgnore]
public bool IsGeometryOperationEnabled
{
Expand Down
35 changes: 34 additions & 1 deletion src/DynamoCoreWpf/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions src/DynamoCoreWpf/Properties/Resources.en-US.resx
Original file line number Diff line number Diff line change
Expand Up @@ -4025,4 +4025,13 @@ To make this file into a new template, save it to a different folder, then move
<data name="NodeHelpIsDumped" xml:space="preserve">
<value>Node Help Data is dumped to \"{0}\".</value>
</data>
<data name="UpdateAllPythonEngineWarning" xml:space="preserve">
<value>Update all {0} python nodes in the current workspace to use {1} engine?</value>
</data>
<data name="UpdateAllPythonEngineWarningTitle" xml:space="preserve">
<value>Update All Python Nodes</value>
</data>
<data name="UpdateAllPythonEngineMainMenuHeader" xml:space="preserve">
<value>Set Python Engine</value>
</data>
</root>
9 changes: 9 additions & 0 deletions src/DynamoCoreWpf/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -4012,4 +4012,13 @@ To make this file into a new template, save it to a different folder, then move
<data name="NodeHelpIsDumped" xml:space="preserve">
<value>Node Help Data is dumped to \"{0}\".</value>
</data>
<data name="UpdateAllPythonEngineWarning" xml:space="preserve">
<value>Update all {0} python nodes in the current workspace to use {1} engine?</value>
</data>
<data name="UpdateAllPythonEngineWarningTitle" xml:space="preserve">
<value>Update All Python Nodes</value>
</data>
<data name="UpdateAllPythonEngineMainMenuHeader" xml:space="preserve">
<value>Set Python Engine</value>
</data>
</root>
7 changes: 7 additions & 0 deletions src/DynamoCoreWpf/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2144,6 +2144,8 @@ Dynamo.ViewModels.DynamoViewModel.UngroupAnnotationCommand.get -> Dynamo.UI.Comm
Dynamo.ViewModels.DynamoViewModel.UngroupAnnotationCommand.set -> void
Dynamo.ViewModels.DynamoViewModel.UngroupModelCommand.get -> Dynamo.UI.Commands.DelegateCommand
Dynamo.ViewModels.DynamoViewModel.UngroupModelCommand.set -> void
Dynamo.ViewModels.DynamoViewModel.UpdateAllPythonEngineCommand.get -> Dynamo.UI.Commands.DelegateCommand
Dynamo.ViewModels.DynamoViewModel.UpdateAllPythonEngineCommand.set -> void
Dynamo.ViewModels.DynamoViewModel.UpdateGraphicHelpersScale(object parameter) -> void
Dynamo.ViewModels.DynamoViewModel.UpdateGraphicHelpersScaleCommand.get -> Dynamo.UI.Commands.DelegateCommand
Dynamo.ViewModels.DynamoViewModel.UpdateGraphicHelpersScaleCommand.set -> void
Expand Down Expand Up @@ -2992,6 +2994,8 @@ Dynamo.ViewModels.WorkspaceViewModel.CanFindNodesFromElements.set -> void
Dynamo.ViewModels.WorkspaceViewModel.CanPaste.get -> bool
Dynamo.ViewModels.WorkspaceViewModel.CanRunNodeToCode.get -> bool
Dynamo.ViewModels.WorkspaceViewModel.CanShowInfoBubble.get -> bool
Dynamo.ViewModels.WorkspaceViewModel.CanUpdateAllPythonEngine.get -> bool
Dynamo.ViewModels.WorkspaceViewModel.CanUpdatePythonEngine.get -> bool
Dynamo.ViewModels.WorkspaceViewModel.CanZoomIn.get -> bool
Dynamo.ViewModels.WorkspaceViewModel.CanZoomOut.get -> bool
Dynamo.ViewModels.WorkspaceViewModel.Checksum.get -> string
Expand Down Expand Up @@ -5492,6 +5496,9 @@ static Dynamo.Wpf.Properties.Resources.UnknowDateFormat.get -> string
static Dynamo.Wpf.Properties.Resources.UnloadFailureMessageBoxTitle.get -> string
static Dynamo.Wpf.Properties.Resources.UnpinNodeTooltip.get -> string
static Dynamo.Wpf.Properties.Resources.UnsavedChangesMessageBoxTitle.get -> string
static Dynamo.Wpf.Properties.Resources.UpdateAllPythonEngineMainMenuHeader.get -> string
static Dynamo.Wpf.Properties.Resources.UpdateAllPythonEngineWarning.get -> string
static Dynamo.Wpf.Properties.Resources.UpdateAllPythonEngineWarningTitle.get -> string
static Dynamo.Wpf.Properties.Resources.UpdateMessage.get -> string
static Dynamo.Wpf.Properties.Resources.UpdateNodeIconsDebugMenu.get -> string
static Dynamo.Wpf.Properties.Resources.UsageReportPromptDialogTitle.get -> string
Expand Down
153 changes: 149 additions & 4 deletions src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
using System.Windows.Media;
using System.Windows.Threading;
using Dynamo.Configuration;
using Dynamo.Controls;
using Dynamo.Core;
using Dynamo.Engine;
using Dynamo.Exceptions;
using Dynamo.Graph;
using Dynamo.Graph.Annotations;
using Dynamo.Graph.Connectors;
using Dynamo.Graph.Nodes;
using Dynamo.Graph.Nodes.CustomNodes;
using Dynamo.Graph.Workspaces;
using Dynamo.Interfaces;
using Dynamo.Logging;
Expand Down Expand Up @@ -1394,6 +1396,148 @@ private void Paste(object parameter)
RaiseCanExecuteUndoRedo();
}

internal bool CanUpdatePythonNodeEngine(object parameter)
{
if (DynamoSelection.Instance.Selection.Count > 0 && SelectionHasPythonNodes())
{
return true;
}
return false;
}
private bool SelectionHasPythonNodes()
{
if (GetSelectedPythonNodes().Any())
{
return true;
}
return false;
}
/// <summary>
/// Updates the engine for the Python nodes,
/// if the nodes belong to another workspace (like custom nodes), they will be opened silently.
/// </summary>
/// <param name="pythonNode"></param>
/// <param name="engine"></param>
internal void UpdatePythonNodeEngine(PythonNodeBase pythonNode, string engine)
{
try
{
var workspaceGUID = Guid.Empty;
var cnWorkspace = GetCustomNodeWorkspace(pythonNode);
if (cnWorkspace != null)
{
workspaceGUID = cnWorkspace.Guid;
FocusCustomNodeWorkspace(cnWorkspace.CustomNodeId, true);
}
this.ExecuteCommand(
new DynamoModel.UpdateModelValueCommand(
workspaceGUID, pythonNode.GUID, nameof(pythonNode.EngineName), engine));
pythonNode.OnNodeModified();
}
catch(Exception ex)
{
Model.Logger.Log("Failed to update Python node engine: " + ex.Message, LogLevel.Console);
}

}
internal void UpdateAllPythonEngine(object param)
{
var pNodes = GetSelectedPythonNodes(Model.CurrentWorkspace.Nodes);
if (pNodes.Count == 0) return;
var result = MessageBoxService.Show(
Owner,
string.Format(Resources.UpdateAllPythonEngineWarning, pNodes.Count, param.ToString()),
Resources.UpdateAllPythonEngineWarningTitle,
MessageBoxButton.YesNo,
MessageBoxImage.Exclamation);
if (result == MessageBoxResult.Yes)
{
pNodes.ForEach(x => UpdatePythonNodeEngine(x, param.ToString()));
}
}
internal bool CanUpdateAllPythonEngine(object param)
{
return true;
}

/// <summary>
/// Adds the python engine to the menu items and subscribes to their click event for updating the engine.
/// </summary>
/// <param name="pythonNodeModel">List of python nodes</param>
/// <param name="pythonEngineVersionMenu">context menu item to which the engines will be added to</param>
/// <param name="updateEngineDelegate">Update event handler, to trigger engine update for the node</param>
/// <param name="engineName">Python engine to be added</param>
/// <param name="isBinding">Should be set to true, if you require to bind the passed
/// NodeModel engine value with the menu item, works only when a single node is passed in the list.</param>
internal void AddPythonEngineToMenuItems(List<PythonNodeBase> pythonNodeModel,
MenuItem pythonEngineVersionMenu,
RoutedEventHandler updateEngineDelegate,
string engineName, bool isBinding = false)
{
//if all nodes in the selection are set to a specific engine, then that engine will be checked in the list.
bool hasCommonEngine = pythonNodeModel.All(x => x.EngineName == engineName);
var currentItem = pythonEngineVersionMenu.Items.Cast<MenuItem>().FirstOrDefault(x => x.Header as string == engineName);
if (currentItem != null)
{
if (pythonNodeModel.Count == 1) return;
currentItem.IsChecked = hasCommonEngine;
return;
}
MenuItem pythonEngineItem = null;
//if single node, then checked property is bound to the engine value, as python node context menu is not recreated
if (pythonNodeModel.Count == 1 && isBinding)
{
var pythonNode = pythonNodeModel.FirstOrDefault(); ;
pythonEngineItem = new MenuItem { Header = engineName, IsCheckable = false };
pythonEngineItem.SetBinding(MenuItem.IsCheckedProperty, new System.Windows.Data.Binding(nameof(pythonNode.EngineName))
{
Source = pythonNode,
Converter = new CompareToParameterConverter(),
ConverterParameter = engineName
});
}
else
{
//when updating multiple nodes checked value is not bound to any specific node,
//rather takes into account all the selected nodes
pythonEngineItem = new MenuItem { Header = engineName, IsCheckable = true };
pythonEngineItem.IsChecked = hasCommonEngine;
}
pythonEngineItem.Click += updateEngineDelegate;
pythonEngineVersionMenu.Items.Add(pythonEngineItem);
}
/// <summary>
/// Gets the Python nodes from the provided list, including python nodes inside custom nodes as well.
/// If no list is provided then the current selection will be considered.
/// </summary>
/// <returns></returns>
internal List<PythonNodeBase> GetSelectedPythonNodes(IEnumerable<NodeModel> nodes = null)
{
if (nodes == null)
{
nodes = DynamoSelection.Instance.Selection.OfType<NodeModel>();
}
var selectedPythonNodes = nodes.OfType<PythonNodeBase>().ToList();
var customNodes = nodes.Where(x => x.IsCustomFunction).ToList();
if (customNodes.Count > 0)
{
foreach (var cNode in customNodes)
{
var customNodeFunction = cNode as Function;
var pythonNodesInCN = customNodeFunction?.Definition.FunctionBody.OfType<PythonNodeBase>().ToList();
if (pythonNodesInCN.Count > 0)
{
selectedPythonNodes.AddRange(pythonNodesInCN);
}
}
}
return selectedPythonNodes;
}
private CustomNodeWorkspaceModel GetCustomNodeWorkspace(NodeModel node)
{
var wg = model.CustomNodeManager.LoadedWorkspaces.Where(x => x.Nodes.Contains(node)).FirstOrDefault();
return wg ?? null;
}
/// <summary>
/// After command framework is implemented, this method should now be only
/// called from a menu item (i.e. Ctrl + W). It should not be used as a way
Expand Down Expand Up @@ -2567,17 +2711,18 @@ internal bool CanShowPackageManager(object parameters)
}

/// <summary>
/// Change the currently visible workspace to a custom node's workspace
/// Change the currently visible workspace to a custom node's workspace, unless the silent flag is set to true.
/// </summary>
/// <param name="symbol">The function definition for the custom node workspace to be viewed</param>
internal void FocusCustomNodeWorkspace(Guid symbol)
/// <param name="silent">When true, the focus will not switch to the workspace, but it will be opened silently.</param>
internal void FocusCustomNodeWorkspace(Guid symbol, bool silent = false)
{
if (symbol == null)
{
throw new Exception(Resources.MessageNodeWithNullFunction);
}

if (model.OpenCustomNodeWorkspace(symbol))
var res = silent ? model.OpenCustomNodeWorkspaceSilent(symbol) : model.OpenCustomNodeWorkspace(symbol);
if (res)
{
//set the zoom and offsets events
CurrentSpace.OnCurrentOffsetChanged(this, new PointEventArgs(new Point2D(CurrentSpace.X, CurrentSpace.Y)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ private void InitializeDelegateCommands()
ToggleFullscreenWatchShowingCommand = new DelegateCommand(ToggleFullscreenWatchShowing, CanToggleFullscreenWatchShowing);
ToggleBackgroundGridVisibilityCommand = new DelegateCommand(ToggleBackgroundGridVisibility, CanToggleBackgroundGridVisibility);
UpdateGraphicHelpersScaleCommand = new DelegateCommand(UpdateGraphicHelpersScale, CanUpdateGraphicHelpersScale);
AlignSelectedCommand = new DelegateCommand(AlignSelected, CanAlignSelected); ;
AlignSelectedCommand = new DelegateCommand(AlignSelected, CanAlignSelected);
UpdateAllPythonEngineCommand = new DelegateCommand(UpdateAllPythonEngine, CanUpdateAllPythonEngine);
UndoCommand = new DelegateCommand(Undo, CanUndo);
RedoCommand = new DelegateCommand(Redo, CanRedo);
CopyCommand = new DelegateCommand(_ => model.Copy(), CanCopy);
Expand Down Expand Up @@ -138,6 +139,7 @@ private void InitializeDelegateCommands()
public DelegateCommand GoToWorkspaceCommand { get; set; }
public DelegateCommand DeleteCommand { get; set; }
public DelegateCommand AlignSelectedCommand { get; set; }
public DelegateCommand UpdateAllPythonEngineCommand { get; set; }
public DelegateCommand PostUIActivationCommand { get; set; }
public DelegateCommand ToggleFullscreenWatchShowingCommand { get; set; }
public DelegateCommand ToggleBackgroundGridVisibilityCommand { get; set; }
Expand Down
3 changes: 2 additions & 1 deletion src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1780,7 +1780,8 @@ private void RefreshViewOnSelectionChange(object sender, NotifyCollectionChanged
RaisePropertyChanged("HasSelection");
RaisePropertyChanged("IsGeometryOperationEnabled");
RaisePropertyChanged("AnyNodeVisible");
RaisePropertyChanged("SelectionArgumentLacing");
RaisePropertyChanged("SelectionArgumentLacing");
RaisePropertyChanged("CanUpdatePythonEngine");
}

/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions src/DynamoCoreWpf/Views/Core/DynamoView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,8 @@
</MenuItem.Header>
</MenuItem>
</MenuItem>
<MenuItem Header="{x:Static p:Resources.UpdateAllPythonEngineMainMenuHeader}"
Name="PythonEngineMenu" />
<Separator />
<MenuItem Header="{x:Static p:Resources.DynamoViewEditMenuCleanupLayout}"
Command="{Binding GraphAutoLayoutCommand}"
Expand Down
Loading
Loading