Monday, March 23, 2015

Centroid ObjectSnap

During drafting process, sometimes an AutoCAD user would wish he.she could easily find out where the centroid of an polygon (assuming it is a closed Polyline), and better yet, he/she could snap the mouse cursor at the centroid.

With Autodesk.AutoCAD.DatabaseServices.OspanOverrule, this is actually a very easy achievable thing to do. 

Here is the code sample of the custom OsnapOverrule:

using System;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
 
namespace CentroidSnap
{
    public class MyCentroidSnap : OsnapOverrule 
    {
        private static MyCentroidSnap _instance = null;
        private bool _overruling = false;
        private bool _started = false;
 
        public static MyCentroidSnap Instance
        {
            get
            {
                if (_instance == null) _instance = new MyCentroidSnap();
                return _instance;
            }
        }
 
        public bool Started
        {
            get { return _started; }
        }
 
        public void StartOSnap()
        {
            _overruling = Overruling;
 
            Overrule.AddOverrule(RXClass.GetClass(typeof(Curve)), thistrue);
            Overruling = true;
            _started = true;
        }
 
        public void StopOSnap()
        {
            Overrule.RemoveOverrule(RXClass.GetClass(typeof(Curve)), this);
            Overruling = _overruling;
            _started = false;
        }
 
        #region overriden methods
 
        public override void GetObjectSnapPoints(
            Entity entity, ObjectSnapModes snapMode, IntPtr gsSelectionMark,
            Point3d pickPoint, Point3d lastPoint, Matrix3d viewTransform,
            Point3dCollection snapPoints, IntegerCollection geometryIds)
        {
            bool hasCentre = true;
            Point3d snapPt = new Point3d();
 
            if (entity is Polyline)
            {
                Polyline pl = (Polyline)entity;
                if (pl.Closed)
                {
                    snapPt = GetPolylineCentre(pl);
                }
                else
                {
                    hasCentre = false;
                }
            }
            else if (entity is Circle)
            {
                snapPt = ((Circle)entity).Center;
            }
            else
            {
                hasCentre = false;
            }
            
            if (hasCentre)
            {
                //Clear all possible existing snap points
                snapPoints.Clear();
 
                //Set OSnap mode to NEA
                snapMode = ObjectSnapModes.ModeNear;
                snapPoints.Add(snapPt);
            }
        }
 
        public override bool IsContentSnappable(Entity entity)
        {
            return false;
        }
 
        #endregion
 
        #region private methods
 
        private Point3d GetPolylineCentre(Polyline pl)
        {
            double x = 0.0, y = 0.0, z = 0.0;
            for (int i=0; i<pl.NumberOfVertices; i++)
            {
                Point3d p = pl.GetPoint3dAt(i);
                x += p.X;
                y += p.Y;
                z += p.Z;
            }
 
            return new Point3d(
                x / pl.NumberOfVertices, 
                y / pl.NumberOfVertices, 
                z / pl.NumberOfVertices);
        }
 
        #endregion
    }
}

Here is the command class to start/stop the custom MyCentroidSnap overrule:

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assemblyCommandClass(typeof(CentroidSnap.MyCadCommands))]
 
namespace CentroidSnap
{
    public class MyCadCommands 
    {
 
        [CommandMethod("CSnap")]
        public static void RunMyCadCommand()
        {
            Document dwg = CadApp.DocumentManager.MdiActiveDocument;
            Editor ed = dwg.Editor;
 
            if (!MyCentroidSnap.Instance.Started)
            {
                MyCentroidSnap.Instance.StartOSnap();
                ed.WriteMessage(
                    "\nMYCentroidOSnap is started.");
            }
            else
            {
                MyCentroidSnap.Instance.StopOSnap();
                ed.WriteMessage(
                    "\nMYCentroidOSnap is stopped.");
            }
 
            Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
        }
    }
}

I also posted on the same topic a couple of years ago herehere and here. Obviously, I could have also create this MyCentroidSnap class by deriving it from Autodesk.AutoCAD.GraphicsInterface.Glyph class. 

As usual, see this video clip for the MyCentroidSnap's action.

Friday, March 6, 2015

COM API Update to Entity And Transaction

When moving from AutoCAD VBA (COM API) programming to AutoCAD ObjectARX .NET API programming, one of the most "dramatic" changes an AutoCAD programmer might find is the concept of using Transaction. That is, the code that updates anything in drawing database (entities, table records...) must be wrapped in a transaction, and the transaction must be explicitly committed in order for the update/change to be materialized.

I believe I was not the only one who forgot to call Transaction.Commit() quite a few times during the early stage of moving from VBA to .NET programming until calling Commit() became basic instinct after writing a lot .NET code.

One good thing of moving from VBA to .NET programming is that one can still use COM API in .NET code, although it is debatable what is a good practice to mix COM API and .NET API, which is not the topic of this article.

One thing I have been wondering occasionally all the years since I moved to use .NET APIs: what would happen if the COM API calls, which change stuff in drawing, are wrapped inside a .NET API's Transaction?

Naturally, my assumption is that the Transaction is not necessary, unless I want to have a chance to undo the changes made with COM API calls (that is, Transaction.Abort() can undo changes wrapped by the Transaction, be it the changes are made via .NET API, or COM API). To verify this assumption, I wrote following code to verify it:

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Interop;
using Autodesk.AutoCAD.Interop.Common;
 
[assemblyCommandClass(typeof(ComApiInTransaction.MyCommands))]
 
namespace ComApiInTransaction
{
    public class MyCommands
    {
        [CommandMethod("PureCom")]
        public static void ComWithoutTransaction()
        {
            Document dwg = Application.DocumentManager.MdiActiveDocument;
            Editor ed = dwg.Editor;
 
            try
            {
                AcadDocument acadDoc = (AcadDocument)dwg.GetAcadDocument();
 
                UpdateEntitiesWithCom(acadDoc);
 
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage("\nCommand \"MyCmd\" failed:");
                ed.WriteMessage("\n{0}\n{1}", ex.Message, ex.StackTrace);
            }
            finally
            {
                Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
            }
        }
 
        [CommandMethod("TransCom")]
        public static void ComWithTransaction()
        {
            Document dwg = Application.DocumentManager.MdiActiveDocument;
            Editor ed = dwg.Editor;
 
            try
            {
                AcadDocument acadDoc = (AcadDocument)dwg.GetAcadDocument();
 
                using (var trans = dwg.TransactionManager.StartTransaction())
                {
                    UpdateEntitiesWithCom(acadDoc);
 
                    dwg.Editor.UpdateScreen();
 
                    PromptKeywordOptions opt = new PromptKeywordOptions(
                        "\nSelect option:");
                    opt.AppendKeywordsToMessage = true;
                    opt.Keywords.Add("Commit");
                    opt.Keywords.Add("Abort");
                    opt.Keywords.Default = "Commit";
 
                    bool commit = false;
                    PromptResult res = ed.GetKeywords(opt);
                    if (res.Status==PromptStatus.OK)
                    {
                        commit = res.StringResult == "Commit";
                    }
 
                    if (commit)
                        trans.Commit();
                    else
                        trans.Abort();
                }
 
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage("\nCommand \"MyCmd\" failed:");
                ed.WriteMessage("\n{0}\n{1}", ex.Message, ex.StackTrace);
            }
            finally
            {
                Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
            }
        }
 
        [CommandMethod("NetChange")]
        public static void NetChange()
        {
            Document dwg = Application.DocumentManager.MdiActiveDocument;
            Editor ed = dwg.Editor;
 
            try
            {
                using (var trans = dwg.TransactionManager.StartTransaction())
                {
                    UpdateEntitiesWithNetApi(dwg, trans);
 
                    //This line of code makes sure the entity color changes
                    //in AutoCAD editor before the Transaction is commited
                    trans.TransactionManager.QueueForGraphicsFlush();
 
                    dwg.Editor.UpdateScreen();
 
                    PromptKeywordOptions opt = new PromptKeywordOptions(
                        "\nSelect option:");
                    opt.AppendKeywordsToMessage = true;
                    opt.Keywords.Add("Commit");
                    opt.Keywords.Add("Abort");
                    opt.Keywords.Default = "Commit";
 
                    bool commit = false;
                    PromptResult res = ed.GetKeywords(opt);
                    if (res.Status == PromptStatus.OK)
                    {
                        commit = res.StringResult == "Commit";
                    }
 
                    if (commit)
                        trans.Commit();
                    else
                        trans.Abort();
                }
 
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage("\nCommand \"MyCmd\" failed:");
                ed.WriteMessage("\n{0}\n{1}", ex.Message, ex.StackTrace);
            }
            finally
            {
                Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
            }
        }
 
        private static void UpdateEntitiesWithCom(AcadDocument dwg)
        {
            foreach (AcadEntity ent in dwg.ModelSpace)
            {
                int index=(int)ent.TrueColor.ColorIndex;
                index++;
                if (index<1 || index>7) index=1;
 
                AcadAcCmColor color=new AcadAcCmColor();
                color.ColorIndex=(AcColor)index;
                ent.TrueColor = color;
                ent.Update();
            }
        }
 
        private static void UpdateEntitiesWithNetApi(
            Document dwg, Transaction tran)
        {
            ObjectId modelId = 
                SymbolUtilityServices.GetBlockModelSpaceId(dwg.Database);
            BlockTableRecord model = (BlockTableRecord)
                tran.GetObject(modelId, OpenMode.ForRead);
            foreach (ObjectId entId in model)
            {
                Entity ent = (Entity)tran.GetObject(entId, OpenMode.ForWrite);
 
                short index = ent.Color.ColorIndex;
                index++;
                if (index < 1 || index > 7) index = 1;
 
                Autodesk.AutoCAD.Colors.Color color =
                    Autodesk.AutoCAD.Colors.Color.FromColorIndex(
                    Autodesk.AutoCAD.Colors.ColorMethod.ByColor, index);
 
                ent.Color = color;
            }
        }
    }
}

The code uses COM API to change all entities' color in ModelSpace. The first Command "PureCom" simply goes ahead to change entities' color with COM API; while the second command "TransCom" wraps the changes made by COM API in a Transaction, and user gets chance to decide whether to commit the changes or abort.

There is also a third command that uses .NET API to to the same with entities in ModelSpace, as a comparison to the COM API counterpart.

See this video clip for the code in action.


Thursday, March 5, 2015

Update Drawing After Async Task Completed

We all know that AutoCAD is not multi-thread-able when dealing with drawing database. But, this does not mean one cannot spin multiple threads from AutoCAD process. With the help of .NET framework, it is quite easy to kick off a few threads from AutoCAD process to do some time-consuming works (as long as the side threads do not deal with the drawing database loaded into AutoCAD), so that the AutoCAD's main thread is not locked waiting for those lengthy works being completed.

With more and more computing power/resources being available in the clouds, it becomes more and more common that a drafting/designing process needs AutoCAD to grab information from remote locations (network share, remote database, cloud services...). Depending on the nature of the remote resources, the time to obtain data could be lengthy enough that doing it in a background thread makes much sense.

Also, in most cases, if not all, after the background thread finishes it work, we'd like AutoCAD responds properly according to the result of the side thread, such as updating the currently opened drawing with the data processed/retrieved by the background thread.

Here is a scenario: with a drawing open in AutoCAD, user wants to check a remote data source for some information, which could takes seconds or minutes to complete; when AutoCAD gets that piece of information back, the drawing or drawings in current AutoCAD session is or are to be updated, such as updating complicated title block, creating or updating a few entities in drawing...; while AutoCAD tries to get data from the remote source, the user does not like AutoCAD UI being locked up, the user wants to continue to work with AutoCAD, at least to be able to zoom/pan..., for example.

There are some discussions can be found online on this topic. One is an article posted by ADN team member Adam Nagy. In his article, Adam shows how to use a System.Windows.Forms.Control to invoke a callback from the side thread to get back to AutoCAD main thread.

I also had an article discussing this topic a few years back, in which I used System.ComponentModel.BackgroundWorker to do work i n side thread. I'd say that using BackgroundWorker is more natural choice in most cases: it allows the side processing to expose its progress, to be cancelled and provides status of completion (being cancelled, completed with or without error).

We, as AutoCAD programmers, are probably more interested in letting AutoCAD doing something accordingly after side thread is done with its work. The code in Adam's article shows how to create a Line entity (or any type of Entity, for that matter). However, one would be wondering: what will happen if AutoCAD's main thread is in middle of something, for example, a command is in progress when the side thread is done and calls back to the main thread? Can the callback still be abler to lock the MdiActiveDocuemnt and update it regardless AutoCAD having a command in progress?

To actually experience what could happen, I decided to give it a try. I started with AutoCAD 2012, which is still my company's working version, and then with AutoCAD 2014 and 2015. It proves AutoCAD 2015 makes things a bit differently, possibly because of the removal of FIBER, which I'll point out later.

Below is the code I used.

This is class SideWorker that does the side work and update drawing when side work finishes:

using System.ComponentModel;
using Autodesk.AutoCAD.ApplicationServices;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
 
namespace BackgroundThreadCallBack
{
    public class SideWorker
    {
        Document _dwg = null;
        BackgroundWorker _worker = null;
        int _runningCount;
        int _currentCount;
 
        public SideWorker(int runningCount)
        {
            _runningCount = runningCount;
        }
 
        public void StartSideWork()
        {
            _dwg = CadApp.DocumentManager.MdiActiveDocument;
 
            _worker = new BackgroundWorker();
            _worker.RunWorkerCompleted += SideWorker_RunWorkerCompleted;
            _worker.DoWork += SideWorker_DoWork;
 
            _currentCount = 0;
 
            _worker.RunWorkerAsync();
        }
 
        private void SideWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            //Simulate a long-running process that need 10 sec to complete
            System.Threading.Thread.Sleep(10000);
        }
 
        private void SideWorker_RunWorkerCompleted(
            object sender, RunWorkerCompletedEventArgs e)
        {
            Document doc = CadApp.DocumentManager.MdiActiveDocument;
            Editor ed = doc.Editor;
 
            //Only if user has not switched current drawing
            if (doc == _dwg)
            {
                _currentCount++;
 
                if (_currentCount &lt;= _runningCount)
                {
                    AddCircle(doc, _currentCount * 10.0);
                    ed.WriteMessage(
                        "\nCircle \"{0}\" added.",
                        _currentCount);
                }
                
                if (_currentCount &lt; _runningCount)
                {
                    _worker.RunWorkerAsync();
                }
                else
                {
                    ed.WriteMessage(
                        "\n{0} circle{1} added!",
                        _currentCount,
                        _currentCount > 1 ? "s" : "");
 
                    _worker.Dispose();
                }
            }
            else
            {
                ed.WriteMessage(
                    "\nCurrent drawing has changed!");
            }
        }
 
        private void AddCircle(Document dwg, double circleRadial)
        {
            using (var dlock=dwg.LockDocument())
            {
                using (var tran=dwg.TransactionManager.StartTransaction())
                {
                    var space = (BlockTableRecord)
                        tran.GetObject(
                        dwg.Database.CurrentSpaceId, 
                        OpenMode.ForWrite);
 
                    Circle c = new Circle();
                    c.Center = new Point3d(0.0,0.0,0.0);
                    c.Radius = circleRadial;
                    c.SetDatabaseDefaults();
 
                    space.AppendEntity(c);
                    tran.AddNewlyCreatedDBObject(c, true);
 
                    tran.Commit();
                }
            }
        }
    }
}

What the code does is to kick off a few times of background thread for a long processing work (I simply have this side thread sleep for 10 second to simulate a long computing process); when each of the processing is completed, the callback to the main AutoCAD thread will update the drawing (adding a Circle).

Here the the command class that uses the class SideWorker:

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
 
[assemblyCommandClass(typeof(BackgroundThreadCallBack.MyCommands))]
 
namespace BackgroundThreadCallBack
{
    public class MyCommands 
    {
        [CommandMethod("SideWork")]
        public static void RunMyCommand()
        {
            Document dwg = Application.DocumentManager.MdiActiveDocument;
            Editor ed = dwg.Editor;
 
            SideWorker worker = new SideWorker(5);
            worker.StartSideWork();
 
            ed.WriteMessage(
                "\nSide worker is started to create new circles. " +
                "You can continue working with current drawing...");
 
            Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
        }
    }
}

I then ran the code in different condition:
  • After executing command "SideWork", let AutoCAD be idle;
  • After executing command "SideWork", start different AutoCAD command, it may or may not be completed before a background thread calls back to the main thread. 
Here is a video clip showing how AutoCAD reacts.

As one can see:
  • When AutoCAD is idle, there is no issue for side thread calling back and updating drawing. This is expected, of course.
  • When command, like "PLine", "Line" is in progress (that is, I kept picking point to draw a polyline and.or a line), the background thread callback has no problem to create circle in drawing.
  • When command "Circle" in progress (that is, I picked center point, and kept dragging the mouse to decide the radius), the background callback executed (as the command line message "Circle # added!" could be seen), but no circle being drawn by the callback, nor AutoCAD reports error.
  • When other command. such as "STRETCH" or "MOVE" on a SelectionSet of PickFirst entity or entities, the callback results are the same as when "CIRCLE" command in progress. That is, callback executed, no circle being added as the callback result, nor error raised.
Based on this experiment, it seems that we can say that if a real command in progress, which locks the document, then the side thread callback cannot lock the document to update it, in this case, AutoCAD somehow ignores the failed document locking/updating and continue. 

As for why when "PLINE" or "LINE" command allows side thread callback to update drawing, I guess is that when one keeps picking next point, the command is actually update the POLYLINE/LINE right at the point is picked, and when user hover the mouse for another point, the drawing is not actually locked. However, this is only true for AutoCAD 2014 or earlier. In AutoCAD 2015, however, with command "PLINE" or "LINE" in progress, the side thread callback could not update the drawing. See this video clip. I'd think AutoCAD 2015's behaviour in this regard makes more sense. That is, if AutoCAD has a command in progress, it should not be interrupted by the completion of a side thread execution.

Therefore, we need find a way to let AutoCAD know that side work has been completed and it is time for AutoCAD to pick up the results and do something accordingly. either automatically, or with user interaction. With some erring and trying, I came to following modified class SideWorker:

using System.ComponentModel;
using Autodesk.AutoCAD.ApplicationServices;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
 
namespace BackgroundThreadCallBack
{
    public class SideWorker
    {
        Document _dwg = null;
        BackgroundWorker _worker = null;
        int _runningCount = 1;
        int _currentCount;
        bool _actionPending = false;
        bool _showDelayedMsg = false//Used for debugging purpose
 
        public SideWorker(int runningCount)
        {
            //Indicate how many times a background task
            //is to run as side thread
            _runningCount = runningCount;
        }
 
        public void StartSideWork()
        {
            _dwg = CadApp.DocumentManager.MdiActiveDocument;
 
            _worker = new BackgroundWorker();
            _worker.RunWorkerCompleted += SideWorker_RunWorkerCompleted;
            _worker.DoWork += SideWorker_DoWork;
 
            _currentCount = 0;
 
            //Start the very first background work
            _actionPending = false;
            _worker.RunWorkerAsync();
        }
 
        private void SideWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            //Simulate a long-running process that need 10 sec to complete
            System.Threading.Thread.Sleep(10000);
        }
 
        private void SideWorker_RunWorkerCompleted(
            object sender, RunWorkerCompletedEventArgs e)
        {
            Document doc = CadApp.DocumentManager.MdiActiveDocument;
            Editor ed = doc.Editor;
 
            if (!_actionPending)
            {
                //listenning Idle event each time when 
                //side thread has done its work
                Application.Idle += Application_Idle;
                _actionPending = true;
            }
        }
 
        private void Application_Idle(object sender, System.EventArgs e)
        {
            if (!_actionPending) return;
 
            //Even Application is idle, it may not be in quiescent state
            if (!Application.IsQuiescent)
            {
                if (!_showDelayedMsg)
                {
                    //Show why AutoCAD is not in quiescent state:
                    //a command is in profress
                    Document d = Application.DocumentManager.MdiActiveDocument;
                    d.Editor.WriteMessage(
                        "\nCommand \"{0}\" is executing, drawing circle is delayed!",
                        d.CommandInProgress);
                    _showDelayedMsg = true; 
                }
 
                return;
            }
 
            _showDelayedMsg = false;
 
            Document doc = Application.DocumentManager.MdiActiveDocument;
            Editor ed = doc.Editor;
 
            //If user switches active document during side work execution
            //the side work thread will be cancelled
            if (doc != _dwg)
            {
                _worker.Dispose();
                _worker = null;
                _actionPending = false;
 
                int remaining = _runningCount - _currentCount;
 
                ed.WriteMessage(
                    "\nSide work is cancelled due to active document change." +
                    "\n{0} circle{1} remaining to be drawn!",
                    remaining, remaining > 1 ? "s" : "");
            }
            else
            {
                _currentCount++;
 
                if (_currentCount <= _runningCount)
                {
                    AddCircle(doc, _currentCount * 10.0);
                    ed.WriteMessage(
                        "\nCircle \"{0}\" added.",
                        _currentCount);
                    if (_currentCount < _runningCount)
                    {
                        _actionPending = false;
                        _worker.RunWorkerAsync();
                    }
                }
                
                if (_currentCount>=_runningCount)
                {
                    _worker.Dispose();
                    _worker = null;
                    _actionPending = false;
 
                    ed.WriteMessage(
                        "\n{0} circle{1} added!",
                        (_currentCount - 1),
                        (_currentCount - 1) > 1 ? "s" : "");
                }
            }
 
            Application.Idle -= Application_Idle;
        }
 
        private void AddCircle(Document dwg, double circleRadial)
        {
            using (var dlock=dwg.LockDocument())
            {
                using (var tran=dwg.TransactionManager.StartTransaction())
                {
                    var space = (BlockTableRecord)
                        tran.GetObject(
                        SymbolUtilityServices.GetBlockModelSpaceId(dwg.Database), 
                        OpenMode.ForWrite);
 
                    Circle c = new Circle();
                    c.Center = new Point3d(0.0,0.0,0.0);
                    c.Radius = circleRadial;
                    c.SetDatabaseDefaults(dwg.Database);
 
                    space.AppendEntity(c);
                    tran.AddNewlyCreatedDBObject(c, true);
 
                    tran.Commit();
                }
 
                dwg.Editor.UpdateScreen();
            }
        }
    }
}

As one can see, the major change is to start handling Application.Idle event when a side thread execution completed; and the actual drawing update occurs in Application.Idle event handler. Also, in Application.Idle event handler, the code needs to test if the application is in quiescent status (e.g. if there is a command in progress), because AutoCAD being idle does not necessarily mean there is no command in progress: AutoCAD may in middle of a command and waiting user an expected interaction. Once user completes or cancels the command in progress, AutoCAD will have chance to fire Idle event again. so that the action expected after a side thread work is done will have its turn when Application.Idle fires again.

This video clip shows the how the updated SideWorker class work after command "SideWork" issues:
  • If user does not do anything with AutoCAD, background work (sleep 10 seconds) is executed 5 times, and each time the side thread execution completes, a circle is drawn in current drawing;
  • If user uses transparent command, such as zoom/pan with mouse, circles are drawn at each end of side thread execution, meaning zooming/panning has not impact;
  • When user start a command to let AutoCAD do something during the background thread execution, AutoCAD UI is not locked; and when the side thread finishes and the AutoCAD's current command is still in progress, no circle is drawn until the current command is ended (then AutoCAD gets chance to fire Idle event).
  • During background work execution, user could change AutoCAD's active document. When the side work completed, no circle will be drawn and remaining side work execution is cancelled, if the current active document is not the one from which the side work is started.
The code showed here is a simplified case. In reality, we usually need the side thread go to remote resources to grab some data and return the data back to let AutoCAD do something accordingly. In code here, I let the drawing being updated  when each side thread execution completes. If the data returned by the side work requires AutoCAD to a series of tasks, it may be better to store the returned data in a queue at each end of side thread execution. When all the expected side thread executions are done, the code then listens the Application.Idle event until AutoCAD is idle. At this moment, the code can grab data one by one from the queue and let AutoCAD do corresponding task. Before that, it might be good to notify user with remote data arrival and user can then decide the next move.



Followers

About Me

My photo
After graduating from university, I worked as civil engineer for more than 10 years. It was AutoCAD use that led me to the path of computer programming. Although I now do more generic business software development, such as enterprise system, timesheet, billing, web services..., AutoCAD related programming is always interesting me and I still get AutoCAD programming tasks assigned to me from time to time. So, AutoCAD goes, I go.