Calling AutoCAD commands from .NET

through the interface

asynchronously

synchronously

  • SendStringToExecute from the managed document object

  • SendCommand from the COM document object

  • acedPostCommand via P/Invoke

  • ads_queueexpr() via P/Invoke

  • acedCommand via P/Invoke, example,

  • acedCmd via P/Invoke

  • RunCommand (AutoCAD 2009 and later), AutoCAD Command only, not LISP function, example

from this, "... RunCommand() is undocumented and must be invoked via reflection ..."

  • From AutoCAD 2015, Editor.Command() and Editor.CommandAsync(), link,

The Master's Example Classes

Overkill Command Non-reflection-based access to the Editor's non-public RunCommand() method.

TypedValue using DXF Code with multiple values call Lisp function

The “correct” way to use AutoCAD acedCmd() acedCommand() in ObjectARX, .NET, or LISP

Difference between acedCmd and acedPostCommand?

This is a completely different functions:

1) The first asynchronous - the second synchronous

2) The first takes as a parameter only string - the second strings, doubles, array of doubles, etc.

3) The first requires the final " " or "\n" - the second does not.

But both functions require PInvoke in .NET

acedCmd is the same as Editor.Command()

acedPostCommand is similar (but not the same) as Document.SendStringToExecute()

acedPostCommand send command only to active document (Application.DocumentManager.MdiActiveDocument)

Document.SendStringToExecute() send command to any opened document.

Editor.Command() vs Editor.CommandAsync()

Hi,

Since AutoCAD 2015, two instance methods have been added to the Editor class: Editor.Command() and Editor.CommandAsync().

These method replace the need to P/Invoke acedCmd() or call the non-public Editor.RunCommand() to synchronously run AutoCAD commands.

Note that these methods are no longer available.

The docs about these methods are more than laconic: "This is Command, a member of class Editor."

Googling for CommandAsync I only found 3 examples: a thread on Kean Walmsley's blog, another one from AutoCAD DevBlog and a code sample onGitHub.

So, I made some tests to try to deeply understand how work these new methods and share the results here.

About the methods arguments type

Both Editor.Command() and Editor.CommandAsync() accept a params array of object as argument. This means the arguments (the command name and the command inputs) can be passed separated by a comma (no need to build an array as in the examples on the Adn DevBlog and GitHub).

The command inputs can be any strong .NET type (int, double, Point2d, Point3d, ObjectId, SelectionSet, ...).

As with the LISP function command, numbers or points can be passed as string (as on command line).

An empty string ("") stands for Enter and backslash (@"\") for a pause for user input. For the last one, a constant have added to the Editor class : Editor.PauseToken.

Command() vs CommandAsync()

That said, the main question is: what is the difference between Command() and CommandAsync(), in other words, when must we have to use CommandAsync() rather than Command().

This seems to be the purpose of Kean's example, but I do not think it is very relevent: the Editor.Command() also accepts pauses for user input.

Going a little further in the comparison with de command LISP function, I tried to mimic some LISP behaviors:

- call multiple commands in a single expression:

Code - Auto/Visual Lisp: [Select]

- split the the command arguments in multiple expressions:

Code - Auto/Visual Lisp: [Select]

In both cases, it requires to use CommandAsync(). With Command() method, we have to call it for each command (the following ZoomEntLast() method is also used in later examples)

Code - C#: [Select]

    1. public void ZoomEntLast()

    2. {

    3. Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;

    4. ObjectId entlast = Autodesk.AutoCAD.Internal.Utils.EntLast();

    5. if (entlast != ObjectId.Null)

    6. {

    7. ed.Command("_.ZOOM", "_object", entlast, "");

    8. ed.Command("_.ZOOM", ".8x");

    9. }

    10. }

    11. [CommandMethod("CMD1")]

    12. public async void Cmd1()

    13. {

    14. Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;

    15. await ed.CommandAsync(

    16. "_.LINE", new Point3d(-10.0, -10.0, 0.0), new Point3d(10.0, 10.0, 0.0), "",

    17. "_.CIRCLE", Point3d.Origin, 10.0);

    18. ZoomEntLast();

    19. }

    20. [CommandMethod("CMD2")]

    21. public async void Cmd2()

    22. {

    23. Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;

    24. await ed.CommandAsync("_.CIRCLE");

    25. await ed.CommandAsync("0,0");

    26. await ed.CommandAsync("10");

    27. ZoomEntLast();

    28. }

The last shown behavior is interesting, for example, to pass a list of arguments to a command (e.g. a points list for LINE, PLINE, SPLINE).

This also requires the CommandAsync using.

Code - C#: [Select]

    1. [CommandMethod("CMD3")]

    2. public async void Cmd3()

    3. {

    4. Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;

    5. Point2d[] pts =

    6. {

    7. new Point2d(0.0, -10.0),

    8. new Point2d(10.0, 0.0),

    9. new Point2d(0.0, 10.0),

    10. new Point2d(-10.0, 0.0)

    11. };

    12. await ed.CommandAsync("_.PLINE");

    13. foreach (Point2d pt in pts)

    14. {

    15. await ed.CommandAsync(pt);

    16. }

    17. await ed.CommandAsync("_close");

    18. ZoomEntLast();

    19. }

Synchronicity

Another "advanced using of command" in LISP is to launch a command and let the user complete it before continuing the program.

Code - Auto/Visual Lisp: [Select]

With the Editor.Command() method, all the 'while stuff' is implicit, and the program will wait for the command completed:

Code - C#: [Select]

    1. [CommandMethod("CMD4")]

    2. public void Cmd4()

    3. {

    4. Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;

    5. ed.Command("_.PLINE");

    6. ZoomEntLast();

    7. }

To be synchronous, the Editor.CommandAsync() needs to explicitly complete the command (iow do the while stuff as in LISP). This may be interesting if some other task may interact during the user inputs, for example, collecting the newly created lines:

Code - C#: [Select]

    1. [CommandMethod("CMD5")]

    2. public async void Cmd5()

    3. {

    4. Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;

    5. HashSet<ObjectId> ids = new HashSet<ObjectId>();

    6. await ed.CommandAsync("_.LINE", Editor.PauseToken);

    7. while (((string)Application.GetSystemVariable("CMDNAMES")).Contains("LINE"))

    8. {

    9. try

    10. {

    11. await ed.CommandAsync(Editor.PauseToken);

    12. ids.Add(Autodesk.AutoCAD.Internal.Utils.EntLast());

    13. }

    14. catch { break; } // eUserBreak (Cancel) handling

    15. }

    16. Database db = HostApplicationServices.WorkingDatabase;

    17. using (Transaction tr = db.TransactionManager.StartTransaction())

    18. {

    19. foreach (ObjectId id in ids)

    20. {

    21. Entity ent = (Entity)tr.GetObject(id, OpenMode.ForWrite);

    22. ent.ColorIndex = 1;

    23. }

    24. tr.Commit();

    25. }

    26. }

So, just a question: to let the user complete command inputs, is it safe to just call Command() as below (which seems to work fine) ?

Code - C#: [Select]

    1. [CommandMethod("CMD4")]

    2. public void Cmd4()

    3. {

    4. Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;

    5. ed.Command("_.PLINE");

    6. ZoomEntLast();

    7. }

or is it preferable to call CommandAsync() ?

Code - C#: [Select]

    1. [CommandMethod("CMD6")]

    2. public async void Cmd6()

    3. {

    4. Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;

    5. await ed.CommandAsync("_.PLINE");

    6. while (((string)Application.GetSystemVariable("CMDNAMES")).Contains("PLINE"))

    7. {

    8. try { await ed.CommandAsync(Editor.PauseToken); }

    9. catch { break; }

    10. }

    11. ZoomEntLast();

    12. }

Editor.Command() calls through to acedCmdS(). It's a slightly different code path from acedCommandS(), for which the ObjectARX docs say:

"However, if acedCommandS is not suitable if you want to pass in enough tokens to begin a command but not finish it. When the tokens run out, any command in progress is cancelled. Partially completed command side effects are not rolled back."

Clearly that's not the behaviour we're seeing here, but my personal instinct would be to avoid relying on acedCmdS() completing partial commands (rather than cancelling them, as documented for acedCommandS()).

What I meant in previous post is you can Await anything like custom class CommandResult

Code - C#: [Select]

    1. public sealed class CommandResult : INotifyCompletion

    2. {

    3. // Fields

    4. private bool m_completed = false;

    5. private Action m_continuation;

    6. // Methods

    7. internal CommandResult()

    8. {

    9. }

    10. internal int Callback()

    11. {

    12. int num;

    13. try

    14. {

    15. this.m_completed = true;

    16. this.m_continuation();

    17. return 0x13ec;

    18. }

    19. catch (Exception exception1)

    20. {

    21. UnhandledExceptionFilter.CerOrShowExceptionDialog(exception1);

    22. num = -5002;

    23. }

    24. return num;

    25. }

    26. public Editor.CommandResult GetAwaiter()

    27. {

    28. return this;

    29. }

    30. public void GetResult()

    31. {

    32. if (acedCmdCWasCancelled())

    33. {

    34. Interop.ThrowExceptionForErrorStatus(180);

Pretty neat can do it with extension methods also

    1. }

    2. }

http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115642.aspx

    1. public void OnCompleted(Action continuation)

Code - C#: [Select]

    1. {

    2. this.m_continuation = continuation;

    3. }

    4. // Properties

    5. public bool IsCompleted

    6. {

    7. [return: MarshalAs(UnmanagedType.U1)]

    8. get

    9. {

    10. return this.m_completed;

    11. }

    12. }

    13. }

    1. await 3000;

    2. ..

    3. ....

    4. ...

    5. public static class Awaiters

    6. {

    7. public static TaskAwaiter GetAwaiter(this TimeSpan timeSpan)

    8. {

    9. return Task.Delay(timeSpan).GetAwaiter();

    10. }

    11. public static TaskAwaiter GetAwaiter(this Int32 millisecondsDue)

    12. {

    13. return TimeSpan.FromMilliseconds(millisecondsDue).GetAwaiter();

    14. }

    15. }

Just to follow up on this... the docs do indeed need to be fixed. You can rely on Editor.Command() completing commands for you rather than cancelling them: so you should only need a while loop if you need finer-grained control over the command tokens.