An updated implementation of a CAD standards plugin for AutoCAD using .NET

Over the last few months I've had a number of people ask me for an update to this 6-year old post on implementing a CAD standards plugin for AutoCAD (which in turn was based on code from an older DevNote created from a much older VB6 sample).

Augusto Gonçalves from the ADN team very kindly made a start on this while I was out on vacation, providing a basic port that I've now just put a few finishing touches on. Very few changes were actually needed from the code in the original post, thankfully. Also, I did do my best to make sure the code still supports Win32 as well as x64, although admittedly I've only tested the code and process on 64-bit Windows. And as you need to reference a platform-specific COM component you won't (easily?) be able to build a single plugin that works on both.

Rather than repeating the bulk of the instructions from the previous post, here's a Screencast in which I go through the process of building and loading the plugin (all it doesn't show is downloading and extracting the plugin's source project):

Here's the updated C# code:

//

//  AutoCAD CAD Standards API Sample

//

//  CircleStandard.cs : CAD Standards Plugin Sample for C#

//

// This sample adds a custom plugin to the CAD Standards

// Drawing Checker.

//

// The sample plugin tests for a match between the color of a

// circle in the current drawing, and any of the colors of

// circles contained in the specified standards (.DWS) files.

// All the colors of the standard circles are considered as

// fix candidates of the circle being checked. The recommended

// fix object will be the standard circle having the nearest

// radius to the circle being checked.

 

using AcStMgr;

using AXDBLib;

using MSXML2;

using System;

using System.Collections.Generic;

using System.Runtime.InteropServices;

 

namespace CircleStandard

{

  [ProgId("CircleStandard.CircleStandard")]

  public class CircleStandard : IAcStPlugin2

  {

    // Declare variables

 

    private ContextList m_contexts =

      new ContextList();

    private AcStManager m_mgr;

    private CircleStandard m_plugin;

    private AcadDatabase m_checkDb;

    private AcadDatabase m_dwsDb;

    private AcStError m_err;

    private object m_fixArray;

    private CircleCache[] m_cirCacheArray;

    private int m_recFixIndex;

    private int m_curIndex;

    private int m_fixCnt;

    private string m_propName;

 

    // Initialize

 

    // Initializes the plugin

 

    public void Initialize(AcStManager mgr)

    {

      // This is the only member function in which

      // the interface is passed an IAcStManager interface.

 

      // Store pointer to Manager object

 

      m_mgr = mgr;

      m_plugin = this;

    }

 

    // GetObjectFilter

 

    // Plugin populates the provided array with class names

    // of objects that it can check

 

    public object GetObjectFilter()

    {

      // In this case we're only interested in circles

 

      return new string[] { "AcDbCircle" };

    }

 

    // SetupForAudit

 

    // Sets the context for a plugin to check a drawing

 

    public void SetupForAudit(

      AcadDatabase db,

      string pathName,

      object objNameArray,

      object objPathArray,

      object objDbArray)

    {

      // This method defines the context in which a plug-in

      // will operate, specifically the drawing to check and

      // the DWS files that should be used to check the drawing.

      // Here we cache our DWS standards definitions and make

      // an initial cache of circles in the DWG to be checked.

 

      // NOTE: AcadDatabase objects contained in objDbArray

      // are ***not*** guaranteed to be valid after this call.

      // They should not be cached!!!

 

      if (db != null)

      {

        // Cache a pointer to the database

 

        m_checkDb = db;

 

        // pDb is the DWG to be checked

        // Store list of circles in drawing in m_ObjIDArray

 

        if (m_checkDb != null)

        {

          // Cache list of all circles in the current drawing

 

          foreach (AcadObject obj in

            m_mgr.get_ModelSpaceProxy(m_checkDb))

          {

            if (obj.ObjectName == "AcDbCircle")

            {

              m_contexts.Add(obj.ObjectID, true);

            }

          }

        }

 

        var dbArray = (object[])objDbArray;

        var nameArray = (string[])objNameArray;

        var pathArray = (string[])objPathArray;

 

        int i = 0;

 

        // Iterate over the DWSes and cache properties (color

        // and radius) of standard circles

 

        for (int iDWS = 0; iDWS < dbArray.Length; iDWS++)

        {

          // Get the DWS database

 

          m_dwsDb = (AcadDatabase)dbArray[iDWS];

          foreach (AcadCircle stdCircle in

            m_mgr.get_ModelSpaceProxy(m_dwsDb))

          {

            CircleCache cirCache = new CircleCache();

 

            // CircleCache is utility object for storing

            // properties

 

            // Cache properties (color and radius) of all

            // circles in the DWS database

 

            cirCache.color = stdCircle.color;

            cirCache.radius = stdCircle.Radius;

            cirCache.standardFileName = nameArray[iDWS];

 

            // pFix contains fix information to be passed back

            // to the manager later

 

            var fix = new AcStFix();

            fix.Description = "Color fix";

            fix.StandardFileName =

              cirCache.standardFileName;

            fix.FixObjectName =

              "Color: " +

              StripAcPrefix(stdCircle.color.ToString());

 

            if (fix.PropertyCount == 0)

            {

              fix.PropertyValuePut(

                "Color",

                stdCircle.color

              );

            }

            cirCache.pFix = fix;

 

            Array.Resize<CircleCache>(

              ref m_cirCacheArray,

              i+1

            );

            m_cirCacheArray[i++] = cirCache;

          }

        }

      }

    }

 

    // SetContext

 

    // Sets the objects to examine when iterating over errors

 

    public void SetContext(object objIdArray, bool useDb)

    {

      // If useDb is set to "true" (default), or if

      // objIdArray is blank, we use the database (we get

      // all ids for the current drawing). Otherwise, we

      // set supplied list of objIdArrays as our list.

 

      m_contexts.SetContext(useDb, objIdArray);

    }

 

    // Start

 

    // Initializes the error iterator mechanism

 

    public void Start(AcStError err)

    {

      // If pStartError is set to an error object, we should

      // only start checking from that error, not from the

      // beginning. Mostly we will just go the Next item at

      // this point...

 

      if (err != null)

      {

        var badId = err.BadObjectId;

 

        // Find the index for BadObjectId in m_objIDArray

 

        for (

          m_curIndex = 0;

          m_curIndex < m_contexts.Count;

          m_curIndex++

        )

        {

          if (m_contexts[m_curIndex] == badId)

          {

            m_curIndex = (m_curIndex - 1);

            Next();

          }

        }

      }

      else

      {

        // No AcStError object was passed in. Start checking

        // from the very begining

 

        m_curIndex = -1;

        Next();

      }

    }

 

    // Next

 

    // Finds the next error in the current context

 

    public void Next()

    {

      m_err = null;

      if (m_contexts.Count > 0)

      {

        // Drawing contains AcDbCircle objects

 

        AcadCircle circle;

        bool foundErr;

 

        if (m_cirCacheArray.Length > 0)

        {

          // If we've not reached end of list, we first

          // increment current list index

 

          if (m_curIndex < m_contexts.Count - 1)

          {

            m_curIndex++;

            foundErr = false;

            while (m_curIndex < m_contexts.Count)

            {

              // Don't iterate beyond end of list

              // Retrieve object using its ObjectId

 

              try

              {

                circle =

                  (AcadCircle)m_checkDb.ObjectIdToObject(

                    m_contexts[m_curIndex]

                  );

 

                // Try to find a circle with the same color from

                // the cached standard circle (Iterate over cached

                // standards)

 

                for (

                  int iCache = 0;

                  iCache < m_cirCacheArray.Length;

                  iCache++

                )

                {

                  if (circle.color.CompareTo(

                        m_cirCacheArray[iCache].color

                      ) != 0)

                  {

                    // If it doesn't match, we've found a potential

                    // error

 

                    foundErr = true;

                  }

                  else

                  {

                    // If it matches any one standard, then we can

                    // stop checking

 

                    foundErr = false;

                    break;

                  }

                }

                // Check for color differences

 

                if (foundErr)

                {

                  // We found an error so create a local error

                  // object

 

                  var err = new AcStError();

                  err.Description = "Color is non-standard";

                  err.BadObjectId = circle.ObjectID;

                  err.BadObjectName =

                    StripAcPrefix(

                      circle.color.ToString()

                    );                 

                  err.Plugin = m_plugin;

                  err.ErrorTypeName = "Color ";

                  err.ResultStatus =

                    AcStResultStatus.acStResFlagsNone;

 

                  if (err.PropertyCount == 0)

                  {

                   
; err.PropertyValuePut(

                      "Color",

                      circle.color

                    );

                  }

                  m_err = err;

                  foundErr = false;

                  break;

                }

              }

              catch

              {

              }             

              m_curIndex = (m_curIndex + 1);

            }

          }

        }

      }

    }

 

    // Done

 

    // Returns true if there are no more errors

 

    public bool Done()

    {

      return (m_err == null);

    }

 

    // GetError -- Returns the current error

 

    public AcStError GetError()

    {

      return m_err;

    }

 

    // GetAllFixes

 

    // Returns an array of IAcStFix objects for the given

    // error (note: The caller is responsible for releasing

    // the objects in this array)

 

    public void GetAllFixes(

      AcStError err,

      ref object fixArray,

      ref int recommendedFixIndex

    )

    {

      if (err != null)

      {

        var arr = new IAcStFix[m_cirCacheArray.Length];

        ACAD_COLOR vErrorVal;

        recommendedFixIndex = -1;

        m_fixCnt = 0;

 

        // If we have a cache of fixes, then use that

 

        if (m_cirCacheArray.Length > 0)

        {

          for (int i = 0; i < m_cirCacheArray.Length; i++)

          {

            vErrorVal =

              (ACAD_COLOR)err.PropertyValueGet("Color");

            if (vErrorVal.CompareTo(

                  m_cirCacheArray[i].color

                ) != 0)

            {

              // If color property of fix matches error, then

              // add to list of fixes.

 

              arr[i] = m_cirCacheArray[i].pFix;

            }

          }

          fixArray = arr;

          m_fixArray = fixArray;

 

          // Find the recommendedFixIndex

          // (we call this function to retrieve the index -

          // we don't need the returned fix object here)

 

          GetRecommendedFix(err);

          recommendedFixIndex = m_recFixIndex;

        }

 

        // Did we find a recommended fix along the way?

 

        if (recommendedFixIndex == -1)

        {

          // No recomended fix, so set the proper flag on the

       
  // error object

 

          err.ResultStatus =

            AcStResultStatus.acStResNoRecommendedFix;

        }

      }

    }

 

    // GetRecommendedFix

 

    // Retrieves a fix object that describes the

    // recommended fix

 

    public AcStFix GetRecommendedFix(AcStError err)

    {

      var recFix = new AcStFix();

 

      if (m_cirCacheArray.Length == 0)

      {

        err.ResultStatus =

          AcStResultStatus.acStResNoRecommendedFix;

      }

      else

      {

        // Get the objectId for this error

 

        var tmpObjID = err.BadObjectId;

 

        // Retrieve the object to fix from the DWG

 

        var tmpCircle =

          (AcadCircle)m_checkDb.ObjectIdToObject(tmpObjID);

        double radiusToBeChecked = tmpCircle.Radius;

 

        CircleCache cirCache = m_cirCacheArray[0];

        double diff =

          Math.Abs(radiusToBeChecked - cirCache.radius);

        m_recFixIndex = 0;

 

        // Attempt to get a fix color from the cached

        // m_CircleCacheArray

 

        // Rule: the color of the standard circle with the

        // nearest radius as the one to be fixed

 

        for (int i = 0; i < m_cirCacheArray.Length; i++)

        {

          if (diff >

              Math.Abs(

                radiusToBeChecked - m_cirCacheArray[i].radius

              )

            )

          {

            cirCache = m_cirCacheArray[i];

            diff =

              Math.Abs(radiusToBeChecked - cirCache.radius);

            m_recFixIndex = i;

          }

        }

 

        // Populate properties of the recommended fix object

 

        recFix.Description = "Color fix";

        recFix.StandardFileName =

          m_cirCacheArray[m_recFixIndex].

          standardFileName;

        recFix.FixObjectName = "Color";

        if (recFix.PropertyCount == 0)

        {

          recFix.PropertyValuePut(

            "Color",

            m_cirCacheArray[m_recFixIndex].color

          );

        }

      }

      return recFix;

    }

 

    // GetPropertyDiffs

 

    // Populates the provided arrays with the names of

    // properties that are present in the provided

    // error and fix objects (used to populate the fix

    // dialog with 'property name, current value, fix value')

 

    public void GetPropertyDiffs(

      AcStError err,

      AcStFix fix,

      ref object objPropNames,

      ref object o
bjErrorValues,

      ref object objFixValues,

      ref object objFixableStatuses)

    {

      if (err != null)

      {

        var propNames = new string[0];

        var propName = "";

        var errorValues = new string[0];

        var objErrorVal = new object();

        var fixValues = new string[0];

        var objFixVal = new object();

        var fixableStatuses = new bool[0];

 

        // Iterate error properties

 

        for (int i = 0; i < err.PropertyCount; i++)

        {

          err.PropertyGetAt(i, ref propName, ref objErrorVal);

          m_propName = propName;

 

          // Retrieve corresponding Fix property value

 

          try

          {

            fix.PropertyValueGet(propName, ref objFixVal);

 

            var errVal = (ACAD_COLOR)objErrorVal;

            var fixVal = (ACAD_COLOR)objFixVal;

 

            // Fix object has the same prop, so see if they match

 

            if (errVal.CompareTo(fixVal) != 0)

            {

              // Store error and fix properties in array ready to

              // pass back to caller

 

              Array.Resize<string>(ref propNames, i+1);

              propNames[i] = propName;

              Array.Resize<string>(ref errorValues, i+1);

              errorValues[i] = StripAcPrefix(errVal.ToString());

              Array.Resize<string>(ref fixValues, i+1);

              fixValues[i] = StripAcPrefix(fixVal.ToString());

              Array.Resize<bool>(ref fixableStatuses, i+1);

              fixableStatuses[i] = true;

            }

          }

          catch

          {

          }

        }

 

        // Initialize the arrays supplied by caller

 

        objPropNames = propNames;

        objErrorValues = errorValues;

        objFixValues = fixValues;

        objFixableStatuses = fixableStatuses;

        m_fixCnt++;

      }

    }

 

    // StripAcPrefix

 

    // Helper function to make color names prettier

 

    private string StripAcPrefix(string p)

    {

      if (p.StartsWith("ac"))

        return p.Substring(2);

      else

        return p;

    }

 

    // FixError

 

    // Takes an error and a fix object and attempts

    // to fix the
error

 

    public void FixError(

      AcStError err,

      AcStFix fix,

      out string failureReason)

    {

      failureReason = "";

      if (err != null)

      {

        var badObjID = err.BadObjectId;

 

        // Retrieve object to fix from DWG

 

        var badObj =

          (AcadCircle)m_checkDb.ObjectIdToObject(badObjID);

        if (fix == null)

        {

          // If the fix object is null then attempt to get

          // the recommended fix

 

          var tmpFix = GetRecommendedFix(err);

 

          if (tmpFix == null)

          {

            // Set the error's result status to failed and

            // noRecommendedFix

 

            err.ResultStatus =

              AcStResultStatus.acStResNoRecommendedFix;

          }

          else

          {

            fix = tmpFix;

          }

        }

 

        if (fix != null)

        {

          // Fix the bad circle

 

          var sFixVal = new object();

          fix.PropertyValueGet(m_propName, ref sFixVal);

          var fixVal = (ACAD_COLOR)sFixVal;

          try

          {

            badObj.color = fixVal;

            err.ResultStatus =

              AcStResultStatus.acStResFixed;

          }

          catch

          {

            err.ResultStatus =

              AcStResultStatus.acStResFixFailed;

          }

        }

      }

    }

 

    // Clear

 

    // Clears the plugin state and releases any cached

    // objects

 

    public void Clear()

    {

      // Called just before a plugin is released.

      // Use this function to tidy up after yourself

 

      m_plugin = null;

      m_curIndex = -1;

      m_recFixIndex = -1;

      m_fixCnt = 0;

      m_propName = "";

      m_mgr = null;

      m_dwsDb = null;

      m_checkDb = null;

 

      if (m_err != null)

      {

          m_err.Reset();

          m_err = null;

      }

      if (m_cirCacheArray != null)

      {

        for (int i = 0; i < m_cirCacheArray.Length; i++)

        {

          if (m_cirCacheArray[i].pFix != null)

          {

            m_cirCacheAr
ray[i].pFix.Reset();

            m_cirCacheArray[i].pFix = null;

          }

        }

      }

 

      m_contexts.Clear();

    }

 

    // CheckSysvar

 

    // Checks a system variable

 

    public void CheckSysvar(

      string sysvarName,

      bool getAllFixes,

      ref bool passFail)

    {

    }

 

    // StampDatabase

 

    // Returns whether the plugin uses information

    // from the database for checking

 

    public void StampDatabase(AcadDatabase db, ref bool stampIt)

    {

      // If the DWS contains circles, we stamp it by

      // returning stampIt as true, otherwise, returning

      // stampIt as false

 

      stampIt = false;

      foreach (

        AcadObject obj in

        m_mgr.get_ModelSpaceProxy(db)

      )

      {

        if (obj.ObjectName == "AcDbCircle")

        {

          stampIt = true;

          break;

        }

      }

    }

 

    // UpdateStatus

 

    // Updates the result status of the provided error

 

    public void UpdateStatus(AcStError err)

    {

    }

 

    // WritePluginInfo

 

    // Takes an AcStPluginInfoSection node and creates a

    // new AcStPluginInfo node below it (note: used by the

    // Batch Standards Checker to get information about the

    // plugin)

 

    public void WritePluginInfo(object objSectionNode)

    {

      var section = (IXMLDOMNode)objSectionNode;

      var xml =

        section.ownerDocument.createElement("AcStPluginInfo");

      var info = (IXMLDOMElement)section.appendChild(xml);

 

      info.setAttribute("PluginName", Name);

      info.setAttribute("Version", Version);

      info.setAttribute("Description", Description);

      info.setAttribute("Author", Author);

      info.setAttribute("HRef", HRef);

      info.setAttribute("DWSName", "");

      info.setAttribute("Status", "1");

    }

 

    // Author

 

    // Returns the name of the plugin's author

 

    public string Author

    {

      get { return "Kean Walmsley, Autodesk, Inc."; }

    }

 

    // Description

 

    // Returns a description of what the plugin checks

 

    public string Description

    {

      get

      {

        return

          "Checks that circles in a drawing have a color " +

          "that matches those of a similar radius in an " +

          "associated standards file.";

      }

    }

 

    // HRef

 

    // Returns a URL where the plugin can be obtained

 

    public string HRef

    {

      get

      {

        return

          "http://blogs.autodesk.com/through-the-interface";

      }

    }

 

    // Icon

 

    // Returns the HICON property Icon

 

    public int Icon

    {

      get { return 1; }

    }

 

    // Name

 

    // Returns the name of the plugin

 

    public string Name

    {

      get { return "Circle color checker"; }

    }

 

    // Version

 

    // Returns the version of the plugin

 

    public string Version

    {

      get { return "2.0"; }

    }

 

    // CircleCache

 

    // Caches "standard" circle properties (Color, Radius)

    // from .DWS files, as well as pointers to the circle's

    // relevant AcStFix object

 

    private class CircleCache

    {

      public double radius;

      public ACAD_COLOR color;

      public string standardFileName;

      public AcStFix pFix;

    }

 

    // ContextList

 

    // Manages list of objects to check - either all in

    // database, or just those recently added or modified

 

    private class ContextList

    {

      // List of objects to use when not in database context

 

      List<long> m_altIdArray = new List<long>();

 

      List<long> m_dbIdArray = new List<long>();

 

      // All objects in database

 

      private bool m_useDb;

 

      // Return item from correct context list

 

      public long this[int index]

      {

        get

        {

          if (m_useDb)

            return m_dbIdArray[index];

          else

            return m_altIdArray[index];

        }

      }

 

      // Number of items in current list

 

      public int Count

      {

        get

        {

          if (m_useDb)

            return m_dbIdArray.Count;

          else

            return m_altIdArray.Count;

        }

      }

 

      // Flag to determine from which context list to return element

      // Select all database or just modified items for checking

      // (but also add any new ids to database array

 

      public void SetContext(bool useDb, object objContextArray)

      {

        if (!useDb && objContextArray != null)

        {

          m_useDb = false;

          long[] idArray = (long[])objContextArray;

          for (int i = 0; i < idArray.Length; i++)

          {

            long val = (long)idArray[i];

            m_altIdArray.Add(val);

 

            // Have to keep database list up to date

 

            m_dbIdArray.Add(val);

          }

        }

        else

        {

          // Clear

 

          m_useDb = true;

          m_altIdArray.Clear();

        }

      }

 

      public void Add(long id, bool useDb)

      {

        if (useDb)

          m_dbIdArray.Add(id);

        else

          m_altIdArray.Add(id);

      }

 

      // Clear both lists

 

      public void Clear()

      {

        m_altIdArray.Clear();

        m_dbIdArray.Clear();

      }

    }

 

    long IAcStPlugin2.Icon

    {

      get { return 0; }

    }

  }

}

Thanks to Augusto for his help getting this ready!

3 responses to “An updated implementation of a CAD standards plugin for AutoCAD using .NET”

  1. Seems like you would have to create a unique DLL for each release of AutoCAD. I was able to get this working for 2013 but 2015 replies that the plugin manager can't get the description. Also the regasm doesn't appear to work without the two additional DLL files and also Autodesk.AutoCAD.Interop.Common.dll all in the same folder for some reason. Not sure how this worked so easily in the demo video.

    1. You shouldn't need a DLL per version... not sure what's going on there.

      I'm also not sure how this worked differently in the demo video: in the video you see regasm gives a few warnings, but still works. How did it fail for you?

      Kean

  2. Hey, if you like AutoCAD - we recently released a new, free plugin for the program, with which the user can correct and optimize their CAD data for 3D printing! The range of features and materials is exceptional and will help the users print their 3D files to the highest standards. Just check it out on 3yourmind.com/3... 🙂

Leave a Reply to Kean Walmsley Cancel reply

Your email address will not be published. Required fields are marked *