Tuesday, November 3, 2020

Using a C# EXE to interact with AutoCAD

I'm updating this because I've found out that the method explained below has problems, and that you'd do better making a C# DLL to run inside AutoCAD, or use C# with ObjectDBX to run as an EXE which talks to AutoCAD.

The problem is that Microsoft changed the way .NET works, details here, which means that Documents.Open  can sometimes fail. The error code is HRESULT: 0x80010001 (RPC_E_CALL_REJECTED). It is to do with the fact that AutoCAD is still initializing when the call is made, so you need to add some sort of delay, or catch an exception, or maybe wait till it is visible. This is my solution, though I don't like having to do this:

            AcadDocument AcadDoc = null;

            int iNumTries = 0;
            const int ikMaxTries = 400;
            while (iNumTries < ikMaxTries)
            {
                bool bError = false ;
                try
                {
                    AcadDoc = AcadApp.Documents.Open(@"C:\DisegniSviluppati\04.DWG");
                    AcadDoc.Activate();
                }

                catch (Exception e1)
                {
                    Debug.WriteLine("Catch on try " + iNumTries.ToString() + ", exception: " + e1.Message + "\n");
                    bError = true;
                }

                if (!bError)
                {
                    break;
                }

                if (iNumTries == ikMaxTries)
                {
                    break;
                }

                iNumTries++;
            }

            if (iNumTries == ikMaxTries)
            {
                // Failure
                return;
            }

            // Now we will loop over all the entities in the drawing we have just saved and re-opened...
            var ModelSpace = AcadDoc.ModelSpace;
            int iNumEntities = ModelSpace.Count;

Essentially I stay in a loop trying to open the drawing until I don't get an exception. The source below will work generally, but be ready to strange and random exceptions, depending on the timing of your program.

Here's the old article:

It took me quite a time to find out enough to understand how to do this, so here's a summary to anybody looking for the same answers.

I made my project with a single dialog and a single button. Then I added in the reference required to talk to AutoCAD. Right click on References and follow the 1 2 3 4 steps shown below:

Now you can add "using AutoCAD" in your C# sources.

Add a button to your dialog and double click on it to get a button click handler, I've called mine Button_Click.

The main part of the program is inside the code which reacts to the button click. Here is the whole source of the MainWindow. The comments should help you follow the logic.

using System.Diagnostics; // so I can write to the output window of the IDE
using System.Windows;
using AutoCAD; // So I can access autocad types


namespace DwgCSharp
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // Open AutoCAD and get it as a variable in C#...
            AcadApplication AcadApp;
            AcadApp = new AcadApplication();

            // Make sure the AutoCAD windows are visible
            // (But note that making it invisible probably speeds up processing.)
            AcadApp.Application.Visible = true;

            // Get the active document, which in this case is the default empty document which
            // AutoCAD starts with...
            AcadDocument AcadDoc;
            AcadDoc = AcadApp.ActiveDocument;

            // Model space is actually where most of the entities (LINE,CIRCLE,POINT etc) in an AutoCAD DWG file live.
            var ModelSpace = AcadDoc.ModelSpace;

            // Create a new layer in the current drawing...
            AcadLayer CircleLayer = AcadDoc.Layers.Add("Circle");
            CircleLayer.color = AutoCAD.AcColor.acGreen;
            AcadDoc.ActiveLayer = CircleLayer;

            // Coordinates are arrays of doubles, 3 doubles...
            double[] centerPoint = new double[3];
            centerPoint[0] = 3.0; // x
            centerPoint[1] = 4.0; // y
            centerPoint[2] = 0.0; // z

            // Add a circle...
            AcadCircle Circ = ModelSpace.AddCircle(centerPoint, 99.0);
            Debug.WriteLine("Circle entity type is" + Circ.EntityType.ToString()) ;

            // Make another layer for text...
            AcadLayer TextLayer = AcadDoc.Layers.Add("Text");
            TextLayer.color = AutoCAD.AcColor.acRed;
            AcadDoc.ActiveLayer = TextLayer;

            // Add some text on that layer...
            AcadText TestText = ModelSpace.AddText("Hello there", centerPoint, 5.0);

            // Close the drawing file...
            AcadDoc.Close(true, @"D:\Temp\CSHARP.DWG");


            // Reopen the drawing file in read only
            AcadDoc = AcadApp.Documents.Open(@"D:\Temp\CSHARP.DWG", // name
                                             true,                  // read-only
                                             string.Empty);         // no password

            // Now we will loop over all the entities in the drawing we have just saved and re-opened...
            ModelSpace = AcadDoc.ModelSpace;
            int iNumEntities = ModelSpace.Count;

            for (int i = 0; i < iNumEntities; i++)
            {
                AcadEntity entity = ModelSpace.Item(i);
                Debug.WriteLine("entity = " + entity.ToString() + entity.EntityType.ToString());

                if (entity.EntityType == 32)
                {
                    AcadText text = (AcadText)entity;
                    Debug.WriteLine("text found: " + text.TextString);
                }
                else if (entity.EntityType == 8)
                {
                    AcadCircle Circle = (AcadCircle)entity;
                    double x = Circle.Center[0];
                    double y = Circle.Center[1];
                    Debug.WriteLine("circle found: " + Circle.Diameter.ToString() + " x=" + x.ToString() + " y=" + y.ToString());
                }
            }

            AcadDoc.Close();
        }
    }
}

The resulting drawing file looks like this:

 


The actual application looks like this:


The second half of the code shows how to interrogate an AutoCAD drawing using C#. I print out the results using Debug.WriteLine

I often used Debug.WriteLine in my C# programs to follow what is happening, it outputs to the IDE's Output window:

I hope you find this little article useful!

(There is another method to drive AutoCAD using C#, and that is to create a C# DLL which is loaded into AutoCAD as a plugin. But there are many articles on method, so I'll say no more here.)

No comments:

Post a Comment