Evilham

Evilham.com

AcCoreConsole: Simple plugin for workload simulation

Introduction

Following up on my previous post, I’ll introduce a small AutoCAD .Net plugin that can run on the AcCoreConsole.

This could work as a hands-on, quick and dirty tutorial to writing .Net plugins for AutoCAD, I recommend reading the official documentation though. If you are already comfortable writing .Net plugins for AutoCAD, you can skip to the Customising execution (Command Line Arguments) section.

The idea of this plugin is to perform a computation that takes a while and exit; nothing fancy.

The reason for this, is to test my implementation of something similar to AutocadIO that runs locally and in a much smaller scale; the main motivation for that is that many companies are not (yet) ready to handle over their files to Autodesk and having the files processed in their amazing cloud.

At some point, my implementation should provide for an easy way to choose between using all local resources and sending jobs to AutocadIO; that would give maximum flexibility to both users and developers as well, all while giving end-users (companies) a peek into the potential of massively parallel running processes.

Table of Contents

Previous setup

The officially supported way to write .Net plugins for Autocad is using Visual Studio, something like Mono would probably work as well.

Basically, you need a copy of the ObjectARX SDK which is a set of dlls that define AutoCAD’s API.

There are two ways to get (and license!) the ObjectARX SDK:

  • Downloading it directly from the official website.
  • Using nuget, which sets up everything for you. This currently only supports AutoCAD 2016; the binaries will usually be compatible with AutoCAD 2014 and 2015 though.

I recommend using nuget since it’s the most straightforward way to set up the development environment.

If you do choose the other way, you will get a zip file with some samples, documentation and lots of other files. Most of these files are for ObjectARX developers, which is the underlying C++ API for AutoCAD.

The most important files in the zip are in the inc directory: AcCoreMgd.dll, AcDbMgd.dll, AcTcMgd.dll. You should make sure to add references to these dlls in your Visual Studio project.

Project setup

The Visual Studio project needs to have Class Library as its output type, this means that after compiling we will get a dll file containing the classes that make up the plugin.

This dll gets imported in runtime by AutoCAD or the AcCoreConsole, which will also call the relevant bits of your code.

Code

There are basically two classes that make a .Net plugin for AutoCAD: the Plugin class and the Commands class.

The Plugin class

This class is optional, the basic idea is that this class is instantiated once and should contain data that is static for the plugin.

The Plugin class makes more sense when running a command inside AutoCAD as opposed to the AcCoreConsole; the reason for that is that this class gets instantiated just once for the whole AutoCAD session, allowing you to share data across command executions in different documents and so on.

Since in AcCoreConsole you only open one document, I think its usefulness is rather limited.

A typical Plugin class would look like this:

using Autodesk.AutoCAD.Runtime;

[assembly: ExtensionApplication(typeof(MyPlugin.Plugin))]
namespace MyPlugin 
{
  public class Plugin : IExtensionApplication
  {
    public void Initialize()
    {
      // Initialize some resources to be shared across documents
      // and executions of any commands define by this plugin.
    }

    public void Terminate()
    {
      // Dispose of any resources that need to be manually released.
      // Remember that AutoCAD is already closing at this point!
    }
  }
}

The class name Plugin is totally arbitrary and could easily be something else.

The important part here is that the class implements the IExtensionApplication interface.

Signalising the ExtensionApplication attribute on a class is recommended, but not mandatory.

The Commands class

This doesn’t have to be a class per se but I do like to create a unique class that contains any custom defined commands.

My typical Commands class looks like this:

using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices.Core;

[assembly: CommandClass(typeof(MyPlugin.Commands))]
namespace MyPlugin
{
    public class Commands
    {
        [CommandMethod("MyCommand", "MyCommand", "MyCommand",
            CommandFlags.Modal & CommandFlags.Session)]
        public static void MyCommandMethod()
        {
          // Do something with the current drawing
        }
    }
}

In addition to the class name Commands, the method name MyCommandMethod is arbitrary. The defined commands don’t even have to be all in a single class.

To define a command, we just use the CommandMethod attribute on a static method in a class as shown.

As before, the CommandClass attribute is recommended but not mandatory

To my understanding, the Commands class never gets instantiated since AutoCAD and AcCoreConsole invoke the static method directly.

The class attributes

The main benefit of using the ExtensionApplication and CommandClass attributes is load time. That way AutoCAD (and AcCoreConsole) know exactly where the Plugin’s entry point is and where any commands are defined.

If you use class attributes once, you must use them all the time in the plugin, otherwise any commands defined in a class not signalised with the CommandClass attribute won’t be defined.

More information on the class and method attributes can be found in the official documentation, as well as in Kean‘s incredibly informative blog.

The actual workload simulation plugin

The workload

Because of my Math background, I love the Fibonacci sequence. Turns out, it’s also a very popular programming exercise that shows how badly recursive algorithms can make something easy complicated.

Which is why I picked this as a way to occupy the CPU in the plugin.

/// <summary>
/// Workload simulation. Naïve calculation of the n-th Fibonacci number.
/// A significant workload can be achieved with n >= 42.
/// </summary>
/// <param name="n"></param>
/// <returns></returns>
private static ulong Fib(uint n)
{
    if (n < 2)
        return n;
    return Fib(n-1) + Fib(n-2);
}

First Plugin version

Now, a very quick way to have a plugin that simulates workload would be to calculate Fib(60); that would look something like this:

using System;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices.Core;

[assembly: CLSCompliant(true)]
[assembly: CommandClass(typeof(TestAcCorePlugin.Commands))]
namespace TestAcCorePlugin
{
    public class Commands
    {
        [CommandMethod("TestAcCore", "TestAcCore", "TestAcCore",
            CommandFlags.Modal & CommandFlags.Session)]
        public static void TestAcCoreCommand()
        {
            Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(
        string.Format("\nFib {0}:\t{1}", 60, Fib(60)));
        }
        /// <summary>
        /// Workload simulation. Naïve calculation of the n-th Fibonacci number.
        /// A significant workload can be achieved with n >= 42.
        /// </summary>
        /// <param name="n"></param>
        /// <returns></returns>
        private static ulong Fib(uint n)
        {
            if (n < 2)
                return n;
            return Fib(n-1) + Fib(n-2);
        }
    }
}

But I want this plugin to be a bit more flexible than that and since it will be running in the AcCoreConsole…

Customising execution (Command Line Arguments)

To my knowledge, there is no defined way to pass information to AcCoreConsole plugins; a suggestion Kean made when we met last year in Prague was to pass arguments in the Command Line to the plugin.

This is possible because the AcCoreConsole ignores any arguments it doesn’t recognise. That leaves us with the issue of telling AcCoreConsole’s arguments and those for our plugin apart.

One way to do that is the good ol’ UNIX way of using two consecutive dashes: -- to signalise the beginning of our arguments.

That leads us to this method:

/// <summary>
/// Ignore any command line arguments before the last "--" word.
/// </summary>
/// <returns>A list of the gotten arguments.</returns>
private static List<string> ParseArgs()
{
    var args = new List<string>(Environment.GetCommandLineArgs());
    var parsedArgs = new List<string>();
    var lastIndex = args.FindLastIndex((x) => x == "--");
    if (lastIndex > 0 && lastIndex + 1 < args.Count)
    {
        parsedArgs.AddRange(
          args.GetRange(lastIndex + 1, args.Count - (lastIndex + 1)));
    }
    return parsedArgs;
}

This method reads the arguments from the Environment variable, finds the last appearance of -- and ignores everything behind that. That way we don’t have to worry about parsing and understanding AcCoreConsole’s arguments.

Furthermore, it provides us with a (probably) forward compatible way of passing arguments. If Autodesk decides to add more Command Line arguments to the AcCoreConsole in the future, that’s OK since we just ignore everything before the last --.

Now, to use the gotten arguments we have to modify the TestAcCoreCommand method:

public static void TestAcCoreCommand()
{
    var args = ParseArgs();
    var fibs = new List<uint>();
    if (args.Count > 0)
    {
        foreach (var arg in args)
            fibs.Add(uint.Parse(arg));
    }
    else {
        for (uint i = 0; i < 45; ++i)
            fibs.Add(i);
    }
    foreach (var i in fibs)
    {
        Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(
            string.Format("\nFib {0}:\t{1}", i, Fib(i)));
    }
}

In this case, we treat the arguments as a list of integers that tells us which members of the Fibonacci sequence are to be calculated.

When no argument is passed (or if the command runs inside AutoCAD), we calculate the first 45 Fibonacci numbers.

Using the plugin

After compiling, a TestAcCorePlugin.dll file will be generated. This file can be loaded with netload from both inside AutoCAD and the AcCoreConsole; that will define the testaccore command.

To automate loading and command calling, I created a Script file called ac.scr, with following content:

secureload 0
netload "${PathToDll}\TestAcCorePlugin.dll"
testaccore
secureload 1

The secureload part is because we are not (yet) signing this dll; which we should.

Now with an open Command Prompt, I can invoke my plugin with:

"${PathToAutoCAD}\accoreconsole.exe" /s "%{PathToScript}\ac.scr" -- 30 40 45

Which will produce following output:

AutoCAD Core Engine Console - Copyright Autodesk, Inc 2009-2013.
Regenerating model.

Command: netload Assembly file name: "${PathToDll}\TestAcCorePlugin.dll"
Command: testaccore

Fib 30: 832040
Fib 40: 102334155
Fib 45: 1134903170

Command: _quit

Great! We just used AcCoreConsole to calculate Fibonacci numbers.

Closing

The main point of this post is the usage of Command Line Arguments to customise the execution of a given script running in AcCoreConsole.

This allows for many interesting use-cases: we can, for example, run three different AcCoreConsoles, each with a different number as an argument; that would make the calculations in parallel as opposed to serially.

As a matter of fact, I am doing just that; next post will start introducing what I call OwnACCCL (OwnAcCoreConsoleLauncher). Which takes care of starting the AcCoreConsoles with the proper arguments and not exhausting the system’s resources while at it.