Skip to content

Commit 6fd8000

Browse files
authored
DYN-2763 Add options to change Python Engine in multiple python nodes (#15475)
1 parent ff3d733 commit 6fd8000

15 files changed

+318
-46
lines changed

src/DynamoCore/Models/DynamoModel.cs

+20
Original file line numberDiff line numberDiff line change
@@ -2950,6 +2950,26 @@ public bool OpenCustomNodeWorkspace(Guid guid)
29502950

29512951
return false;
29522952
}
2953+
/// <summary>
2954+
/// Opens an existing custom node workspace.
2955+
/// </summary>
2956+
/// <param name="guid">Identifier of the workspace to open</param>
2957+
/// <returns>True if workspace was found and open</returns>
2958+
internal bool OpenCustomNodeWorkspaceSilent(Guid guid)
2959+
{
2960+
CustomNodeWorkspaceModel customNodeWorkspace;
2961+
if (CustomNodeManager.TryGetFunctionWorkspace(guid, IsTestMode, out customNodeWorkspace))
2962+
{
2963+
if (!Workspaces.OfType<CustomNodeWorkspaceModel>().Contains(customNodeWorkspace))
2964+
{
2965+
AddWorkspace(customNodeWorkspace);
2966+
}
2967+
2968+
return true;
2969+
}
2970+
2971+
return false;
2972+
}
29532973

29542974
/// <summary>
29552975
/// Adds a node to the current workspace.

src/DynamoCoreWpf/Commands/WorkspaceCommands.cs

+11
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,17 @@ public bool HasSelection
328328
get { return DynamoSelection.Instance.Selection.Count > 0; }
329329
}
330330

331+
[JsonIgnore]
332+
public bool CanUpdatePythonEngine
333+
{
334+
get { return DynamoViewModel.CanUpdatePythonNodeEngine(null); }
335+
}
336+
[JsonIgnore]
337+
public bool CanUpdateAllPythonEngine
338+
{
339+
get { return DynamoViewModel.CanUpdateAllPythonEngine(null); }
340+
}
341+
331342
[JsonIgnore]
332343
public bool IsGeometryOperationEnabled
333344
{

src/DynamoCoreWpf/Properties/Resources.Designer.cs

+34-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/DynamoCoreWpf/Properties/Resources.en-US.resx

+9
Original file line numberDiff line numberDiff line change
@@ -4025,4 +4025,13 @@ To make this file into a new template, save it to a different folder, then move
40254025
<data name="NodeHelpIsDumped" xml:space="preserve">
40264026
<value>Node Help Data is dumped to \"{0}\".</value>
40274027
</data>
4028+
<data name="UpdateAllPythonEngineWarning" xml:space="preserve">
4029+
<value>Update all {0} python nodes in the current workspace to use {1} engine?</value>
4030+
</data>
4031+
<data name="UpdateAllPythonEngineWarningTitle" xml:space="preserve">
4032+
<value>Update All Python Nodes</value>
4033+
</data>
4034+
<data name="UpdateAllPythonEngineMainMenuHeader" xml:space="preserve">
4035+
<value>Set Python Engine</value>
4036+
</data>
40284037
</root>

src/DynamoCoreWpf/Properties/Resources.resx

+9
Original file line numberDiff line numberDiff line change
@@ -4012,4 +4012,13 @@ To make this file into a new template, save it to a different folder, then move
40124012
<data name="NodeHelpIsDumped" xml:space="preserve">
40134013
<value>Node Help Data is dumped to \"{0}\".</value>
40144014
</data>
4015+
<data name="UpdateAllPythonEngineWarning" xml:space="preserve">
4016+
<value>Update all {0} python nodes in the current workspace to use {1} engine?</value>
4017+
</data>
4018+
<data name="UpdateAllPythonEngineWarningTitle" xml:space="preserve">
4019+
<value>Update All Python Nodes</value>
4020+
</data>
4021+
<data name="UpdateAllPythonEngineMainMenuHeader" xml:space="preserve">
4022+
<value>Set Python Engine</value>
4023+
</data>
40154024
</root>

src/DynamoCoreWpf/PublicAPI.Unshipped.txt

+7
Original file line numberDiff line numberDiff line change
@@ -2144,6 +2144,8 @@ Dynamo.ViewModels.DynamoViewModel.UngroupAnnotationCommand.get -> Dynamo.UI.Comm
21442144
Dynamo.ViewModels.DynamoViewModel.UngroupAnnotationCommand.set -> void
21452145
Dynamo.ViewModels.DynamoViewModel.UngroupModelCommand.get -> Dynamo.UI.Commands.DelegateCommand
21462146
Dynamo.ViewModels.DynamoViewModel.UngroupModelCommand.set -> void
2147+
Dynamo.ViewModels.DynamoViewModel.UpdateAllPythonEngineCommand.get -> Dynamo.UI.Commands.DelegateCommand
2148+
Dynamo.ViewModels.DynamoViewModel.UpdateAllPythonEngineCommand.set -> void
21472149
Dynamo.ViewModels.DynamoViewModel.UpdateGraphicHelpersScale(object parameter) -> void
21482150
Dynamo.ViewModels.DynamoViewModel.UpdateGraphicHelpersScaleCommand.get -> Dynamo.UI.Commands.DelegateCommand
21492151
Dynamo.ViewModels.DynamoViewModel.UpdateGraphicHelpersScaleCommand.set -> void
@@ -2992,6 +2994,8 @@ Dynamo.ViewModels.WorkspaceViewModel.CanFindNodesFromElements.set -> void
29922994
Dynamo.ViewModels.WorkspaceViewModel.CanPaste.get -> bool
29932995
Dynamo.ViewModels.WorkspaceViewModel.CanRunNodeToCode.get -> bool
29942996
Dynamo.ViewModels.WorkspaceViewModel.CanShowInfoBubble.get -> bool
2997+
Dynamo.ViewModels.WorkspaceViewModel.CanUpdateAllPythonEngine.get -> bool
2998+
Dynamo.ViewModels.WorkspaceViewModel.CanUpdatePythonEngine.get -> bool
29952999
Dynamo.ViewModels.WorkspaceViewModel.CanZoomIn.get -> bool
29963000
Dynamo.ViewModels.WorkspaceViewModel.CanZoomOut.get -> bool
29973001
Dynamo.ViewModels.WorkspaceViewModel.Checksum.get -> string
@@ -5492,6 +5496,9 @@ static Dynamo.Wpf.Properties.Resources.UnknowDateFormat.get -> string
54925496
static Dynamo.Wpf.Properties.Resources.UnloadFailureMessageBoxTitle.get -> string
54935497
static Dynamo.Wpf.Properties.Resources.UnpinNodeTooltip.get -> string
54945498
static Dynamo.Wpf.Properties.Resources.UnsavedChangesMessageBoxTitle.get -> string
5499+
static Dynamo.Wpf.Properties.Resources.UpdateAllPythonEngineMainMenuHeader.get -> string
5500+
static Dynamo.Wpf.Properties.Resources.UpdateAllPythonEngineWarning.get -> string
5501+
static Dynamo.Wpf.Properties.Resources.UpdateAllPythonEngineWarningTitle.get -> string
54955502
static Dynamo.Wpf.Properties.Resources.UpdateMessage.get -> string
54965503
static Dynamo.Wpf.Properties.Resources.UpdateNodeIconsDebugMenu.get -> string
54975504
static Dynamo.Wpf.Properties.Resources.UsageReportPromptDialogTitle.get -> string

src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs

+149-4
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515
using System.Windows.Media;
1616
using System.Windows.Threading;
1717
using Dynamo.Configuration;
18+
using Dynamo.Controls;
1819
using Dynamo.Core;
1920
using Dynamo.Engine;
2021
using Dynamo.Exceptions;
2122
using Dynamo.Graph;
2223
using Dynamo.Graph.Annotations;
2324
using Dynamo.Graph.Connectors;
2425
using Dynamo.Graph.Nodes;
26+
using Dynamo.Graph.Nodes.CustomNodes;
2527
using Dynamo.Graph.Workspaces;
2628
using Dynamo.Interfaces;
2729
using Dynamo.Logging;
@@ -1394,6 +1396,148 @@ private void Paste(object parameter)
13941396
RaiseCanExecuteUndoRedo();
13951397
}
13961398

1399+
internal bool CanUpdatePythonNodeEngine(object parameter)
1400+
{
1401+
if (DynamoSelection.Instance.Selection.Count > 0 && SelectionHasPythonNodes())
1402+
{
1403+
return true;
1404+
}
1405+
return false;
1406+
}
1407+
private bool SelectionHasPythonNodes()
1408+
{
1409+
if (GetSelectedPythonNodes().Any())
1410+
{
1411+
return true;
1412+
}
1413+
return false;
1414+
}
1415+
/// <summary>
1416+
/// Updates the engine for the Python nodes,
1417+
/// if the nodes belong to another workspace (like custom nodes), they will be opened silently.
1418+
/// </summary>
1419+
/// <param name="pythonNode"></param>
1420+
/// <param name="engine"></param>
1421+
internal void UpdatePythonNodeEngine(PythonNodeBase pythonNode, string engine)
1422+
{
1423+
try
1424+
{
1425+
var workspaceGUID = Guid.Empty;
1426+
var cnWorkspace = GetCustomNodeWorkspace(pythonNode);
1427+
if (cnWorkspace != null)
1428+
{
1429+
workspaceGUID = cnWorkspace.Guid;
1430+
FocusCustomNodeWorkspace(cnWorkspace.CustomNodeId, true);
1431+
}
1432+
this.ExecuteCommand(
1433+
new DynamoModel.UpdateModelValueCommand(
1434+
workspaceGUID, pythonNode.GUID, nameof(pythonNode.EngineName), engine));
1435+
pythonNode.OnNodeModified();
1436+
}
1437+
catch(Exception ex)
1438+
{
1439+
Model.Logger.Log("Failed to update Python node engine: " + ex.Message, LogLevel.Console);
1440+
}
1441+
1442+
}
1443+
internal void UpdateAllPythonEngine(object param)
1444+
{
1445+
var pNodes = GetSelectedPythonNodes(Model.CurrentWorkspace.Nodes);
1446+
if (pNodes.Count == 0) return;
1447+
var result = MessageBoxService.Show(
1448+
Owner,
1449+
string.Format(Resources.UpdateAllPythonEngineWarning, pNodes.Count, param.ToString()),
1450+
Resources.UpdateAllPythonEngineWarningTitle,
1451+
MessageBoxButton.YesNo,
1452+
MessageBoxImage.Exclamation);
1453+
if (result == MessageBoxResult.Yes)
1454+
{
1455+
pNodes.ForEach(x => UpdatePythonNodeEngine(x, param.ToString()));
1456+
}
1457+
}
1458+
internal bool CanUpdateAllPythonEngine(object param)
1459+
{
1460+
return true;
1461+
}
1462+
1463+
/// <summary>
1464+
/// Adds the python engine to the menu items and subscribes to their click event for updating the engine.
1465+
/// </summary>
1466+
/// <param name="pythonNodeModel">List of python nodes</param>
1467+
/// <param name="pythonEngineVersionMenu">context menu item to which the engines will be added to</param>
1468+
/// <param name="updateEngineDelegate">Update event handler, to trigger engine update for the node</param>
1469+
/// <param name="engineName">Python engine to be added</param>
1470+
/// <param name="isBinding">Should be set to true, if you require to bind the passed
1471+
/// NodeModel engine value with the menu item, works only when a single node is passed in the list.</param>
1472+
internal void AddPythonEngineToMenuItems(List<PythonNodeBase> pythonNodeModel,
1473+
MenuItem pythonEngineVersionMenu,
1474+
RoutedEventHandler updateEngineDelegate,
1475+
string engineName, bool isBinding = false)
1476+
{
1477+
//if all nodes in the selection are set to a specific engine, then that engine will be checked in the list.
1478+
bool hasCommonEngine = pythonNodeModel.All(x => x.EngineName == engineName);
1479+
var currentItem = pythonEngineVersionMenu.Items.Cast<MenuItem>().FirstOrDefault(x => x.Header as string == engineName);
1480+
if (currentItem != null)
1481+
{
1482+
if (pythonNodeModel.Count == 1) return;
1483+
currentItem.IsChecked = hasCommonEngine;
1484+
return;
1485+
}
1486+
MenuItem pythonEngineItem = null;
1487+
//if single node, then checked property is bound to the engine value, as python node context menu is not recreated
1488+
if (pythonNodeModel.Count == 1 && isBinding)
1489+
{
1490+
var pythonNode = pythonNodeModel.FirstOrDefault(); ;
1491+
pythonEngineItem = new MenuItem { Header = engineName, IsCheckable = false };
1492+
pythonEngineItem.SetBinding(MenuItem.IsCheckedProperty, new System.Windows.Data.Binding(nameof(pythonNode.EngineName))
1493+
{
1494+
Source = pythonNode,
1495+
Converter = new CompareToParameterConverter(),
1496+
ConverterParameter = engineName
1497+
});
1498+
}
1499+
else
1500+
{
1501+
//when updating multiple nodes checked value is not bound to any specific node,
1502+
//rather takes into account all the selected nodes
1503+
pythonEngineItem = new MenuItem { Header = engineName, IsCheckable = true };
1504+
pythonEngineItem.IsChecked = hasCommonEngine;
1505+
}
1506+
pythonEngineItem.Click += updateEngineDelegate;
1507+
pythonEngineVersionMenu.Items.Add(pythonEngineItem);
1508+
}
1509+
/// <summary>
1510+
/// Gets the Python nodes from the provided list, including python nodes inside custom nodes as well.
1511+
/// If no list is provided then the current selection will be considered.
1512+
/// </summary>
1513+
/// <returns></returns>
1514+
internal List<PythonNodeBase> GetSelectedPythonNodes(IEnumerable<NodeModel> nodes = null)
1515+
{
1516+
if (nodes == null)
1517+
{
1518+
nodes = DynamoSelection.Instance.Selection.OfType<NodeModel>();
1519+
}
1520+
var selectedPythonNodes = nodes.OfType<PythonNodeBase>().ToList();
1521+
var customNodes = nodes.Where(x => x.IsCustomFunction).ToList();
1522+
if (customNodes.Count > 0)
1523+
{
1524+
foreach (var cNode in customNodes)
1525+
{
1526+
var customNodeFunction = cNode as Function;
1527+
var pythonNodesInCN = customNodeFunction?.Definition.FunctionBody.OfType<PythonNodeBase>().ToList();
1528+
if (pythonNodesInCN.Count > 0)
1529+
{
1530+
selectedPythonNodes.AddRange(pythonNodesInCN);
1531+
}
1532+
}
1533+
}
1534+
return selectedPythonNodes;
1535+
}
1536+
private CustomNodeWorkspaceModel GetCustomNodeWorkspace(NodeModel node)
1537+
{
1538+
var wg = model.CustomNodeManager.LoadedWorkspaces.Where(x => x.Nodes.Contains(node)).FirstOrDefault();
1539+
return wg ?? null;
1540+
}
13971541
/// <summary>
13981542
/// After command framework is implemented, this method should now be only
13991543
/// called from a menu item (i.e. Ctrl + W). It should not be used as a way
@@ -2567,17 +2711,18 @@ internal bool CanShowPackageManager(object parameters)
25672711
}
25682712

25692713
/// <summary>
2570-
/// Change the currently visible workspace to a custom node's workspace
2714+
/// Change the currently visible workspace to a custom node's workspace, unless the silent flag is set to true.
25712715
/// </summary>
25722716
/// <param name="symbol">The function definition for the custom node workspace to be viewed</param>
2573-
internal void FocusCustomNodeWorkspace(Guid symbol)
2717+
/// <param name="silent">When true, the focus will not switch to the workspace, but it will be opened silently.</param>
2718+
internal void FocusCustomNodeWorkspace(Guid symbol, bool silent = false)
25742719
{
25752720
if (symbol == null)
25762721
{
25772722
throw new Exception(Resources.MessageNodeWithNullFunction);
25782723
}
2579-
2580-
if (model.OpenCustomNodeWorkspace(symbol))
2724+
var res = silent ? model.OpenCustomNodeWorkspaceSilent(symbol) : model.OpenCustomNodeWorkspace(symbol);
2725+
if (res)
25812726
{
25822727
//set the zoom and offsets events
25832728
CurrentSpace.OnCurrentOffsetChanged(this, new PointEventArgs(new Point2D(CurrentSpace.X, CurrentSpace.Y)));

src/DynamoCoreWpf/ViewModels/Core/DynamoViewModelDelegateCommands.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ private void InitializeDelegateCommands()
5151
ToggleFullscreenWatchShowingCommand = new DelegateCommand(ToggleFullscreenWatchShowing, CanToggleFullscreenWatchShowing);
5252
ToggleBackgroundGridVisibilityCommand = new DelegateCommand(ToggleBackgroundGridVisibility, CanToggleBackgroundGridVisibility);
5353
UpdateGraphicHelpersScaleCommand = new DelegateCommand(UpdateGraphicHelpersScale, CanUpdateGraphicHelpersScale);
54-
AlignSelectedCommand = new DelegateCommand(AlignSelected, CanAlignSelected); ;
54+
AlignSelectedCommand = new DelegateCommand(AlignSelected, CanAlignSelected);
55+
UpdateAllPythonEngineCommand = new DelegateCommand(UpdateAllPythonEngine, CanUpdateAllPythonEngine);
5556
UndoCommand = new DelegateCommand(Undo, CanUndo);
5657
RedoCommand = new DelegateCommand(Redo, CanRedo);
5758
CopyCommand = new DelegateCommand(_ => model.Copy(), CanCopy);
@@ -138,6 +139,7 @@ private void InitializeDelegateCommands()
138139
public DelegateCommand GoToWorkspaceCommand { get; set; }
139140
public DelegateCommand DeleteCommand { get; set; }
140141
public DelegateCommand AlignSelectedCommand { get; set; }
142+
public DelegateCommand UpdateAllPythonEngineCommand { get; set; }
141143
public DelegateCommand PostUIActivationCommand { get; set; }
142144
public DelegateCommand ToggleFullscreenWatchShowingCommand { get; set; }
143145
public DelegateCommand ToggleBackgroundGridVisibilityCommand { get; set; }

src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1780,7 +1780,8 @@ private void RefreshViewOnSelectionChange(object sender, NotifyCollectionChanged
17801780
RaisePropertyChanged("HasSelection");
17811781
RaisePropertyChanged("IsGeometryOperationEnabled");
17821782
RaisePropertyChanged("AnyNodeVisible");
1783-
RaisePropertyChanged("SelectionArgumentLacing");
1783+
RaisePropertyChanged("SelectionArgumentLacing");
1784+
RaisePropertyChanged("CanUpdatePythonEngine");
17841785
}
17851786

17861787
/// <summary>

src/DynamoCoreWpf/Views/Core/DynamoView.xaml

+2
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,8 @@
534534
</MenuItem.Header>
535535
</MenuItem>
536536
</MenuItem>
537+
<MenuItem Header="{x:Static p:Resources.UpdateAllPythonEngineMainMenuHeader}"
538+
Name="PythonEngineMenu" />
537539
<Separator />
538540
<MenuItem Header="{x:Static p:Resources.DynamoViewEditMenuCleanupLayout}"
539541
Command="{Binding GraphAutoLayoutCommand}"

0 commit comments

Comments
 (0)