Motivation #
You might want to chain two or more AutoCAD/Civil 3D commands that involve user interaction — for example, generating tables where the user needs to click “OK” or “Cancel” in a dialog box.
Editor.Command #
According to this post on TheSwamp, the Editor.Command()
method should do the job. In theory, it should wait for user input before proceeding to the next command.
[CommandMethod(nameof(ConsecutiveCommand))]
public void ConsecutiveCommand()
{
Autodesk.AutoCAD.ApplicationServices.Core.Application.DocumentManager.MdiActiveDocument
.Editor.Command("AeccAddNetworkPipeTable");
Autodesk.AutoCAD.ApplicationServices.Core.Application.DocumentManager.MdiActiveDocument
.Editor.Command("AeccAddNetworkStructTable");
}
In other words, the above ConsecutiveCommand
should first present the user with the dialog box for adding a Network Pipe Table (_AeccAddNetworkPipeTable
).
The user can configure the settings and click OK, and only after that should the next dialog box (_AeccAddNetworkStructTable
) appear.
What actually happens #
In practice, the first command (_AeccAddNetworkPipeTable
) runs correctly, but the second command (_AeccAddNetworkStructTable
) is ignored — as if it never existed.
The reason for this behavior isn’t immediately obvious.
Solution: Editor.CommandAsync #
The solution is to use Editor.CommandAsync
, as explained in this Autodesk forum post.
You’ll need to call CommandAsync
within a loop to ensure that each command completes before the next one runs.
Here’s a wrapper class that encapsulates the mechanism:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.ApplicationServices.Core;
using Autodesk.AutoCAD.EditorInput;
using System;
using System.Threading.Tasks;
using Application = Autodesk.AutoCAD.ApplicationServices.Core.Application;
public class CommandSequenceRunner
{
private readonly Editor _editor;
public CommandSequenceRunner(Editor editor)
{
_editor = editor ?? throw new ArgumentNullException(nameof(editor));
}
/// <summary>
/// Runs AutoCAD commands sequentially, waiting only for
/// interactive commands that remain active in CMDNAMES.
/// </summary>
public async Task RunAsync(params string[] commands)
{
for (int i = 0; i < commands.Length; i++)
{
string cmd = commands[i];
if (string.IsNullOrWhiteSpace(cmd))
continue;
await _editor.CommandAsync(cmd);
// Wait only if the command remains active (interactive)
string baseName = GetCommandBaseName(cmd);
if (IsCommandStillRunning(baseName))
{
try
{
while (IsCommandStillRunning(baseName))
await _editor.CommandAsync(Editor.PauseToken);
}
catch
{
break;
}
}
}
}
private static bool IsCommandStillRunning(string name)
=> GetActiveCmdNames().Contains(name, StringComparison.OrdinalIgnoreCase);
private static string GetActiveCmdNames()
=> (string)Application.GetSystemVariable("CMDNAMES");
private static string GetCommandBaseName(string cmd)
{
var first = cmd.Split(' ', '\t')[0];
return first.TrimStart('.', '_');
}
}
And here’s how to use it:
var ed = Application.DocumentManager.MdiActiveDocument.Editor;
var runner = new CommandSequenceRunner(ed);
await runner.RunAsync(
"AeccAddNetworkPipeTable",
"AeccAddNetworkStructTable");