Tuesday, July 11, 2017

Using Rotation Parameter/Action in Conjunction with Point Parameter/Stretch Action of Dynamic Block

In my recent development work, I ran into an interesting issue: in a particular type of drawing, a dynamic block as label is used to annotate a long Polyline, which could the center line of pipe, road, ditch...This video clip shows how the label block behaves. As the video shows, the block's insertion point is at its end of leader. and its label portion can be dragged and rotated freely without affecting the label block's pointing location (its insertion point). The reason of its label portion being rotatable is because that the label blocks are placed in ModelSpace, but when drawing contents are presented in PaperSpace layout, the Viewports opened in layout could be twisted in different angles, thus the need to rotate the dynamic block's label portion so that the label appears horizontally in Viewports. See pictures below.

As CAD programmer, we obviously can help CAD users in this situation, so that they do not have to manually rotate the labels in all Viewports with different twisted angles; instead we can write some code to get it done easily.

Here is a short video clip that shows 2 dynamic blocks being used as label: one a block that can be dragged to rotate as whole; the other is a label with a leader (with leader pointer as the block's insertion point), so that the label portion can be dragged to different position and rotated with the leader pointer remains in original insertion point.

This picture shows how the label blocks are inserted into ModelSpace:


And this picture shows how the label blocks look like in a Viewport with twisted angle in paperSpace layout:


It would be ideal that the labels seen via the Viewport are all rotated so that its text information is presented horizontally. This is where the rotation parameter/action in the dynamic block comes into play. Of course, for the label that does not have a pointing leader, we can simply rotate the block reference itself instead of adding a rotation dynamic property. But here I just want to use it in comparison to the dynamic block that has its rotation part at the end of a pointing leader - there is something tricky to handle. Read on.

Firstly, I need to know which blocks in drawing are label blocks, and the dynamic property names I need to set. Here is a class to provide this information:
using Autodesk.AutoCAD.Geometry;
using System.Collections.Generic;
 
namespace LabelRotation
{
    public class LabelBlockConfiguration
    {
        public static Dictionary<stringPoint2d> GetLabelBlockConfigurations()
        {
            var dic = new Dictionary<stringPoint2d>();
 
            dic.Add("MYTAG"Point2d.Origin);
            dic.Add("MYLABEL"new Point2d(0.0, 10.0));
 
            return dic;
        }
 
        public const string LABEL_POSITION_PROP = "LabelPosition";
        public const string LABEL_ANGLE_PROP = "LabelAngle";
        public const string TAG_ANGLE_PROP = "TagAngle";
    }
}

The data in this class tells me:

1. There are 2 blocks ("MYTAG" and "MYLABEL") used as label;
2. Each block has a dynamic property named as "xxxxAngle", used to set the label's rotation;
3. Block "MYLABEL" also has a dynamic property names as "LabelPosition". Since the this dynamic property is a Point parameter/Stretch action, the actually property name should be "LabelPosition X" and LabelPosition Y" respectively.
4. Each block name is associated with a Point2d value, which is the original value of "LabelPosition" property (for the Point parameter/Stretch action). Why do we need to know this value? We'll see it later that how it is critical to rotate label correctly if the label's rotation part is stretch-able as previous video clip shows.

Here is the code with a command defined to ease CAD users from having to manually rotate the labels according to the twist angle of Viewport:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System.Collections.Generic;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assemblyCommandClass(typeof(LabelRotation.Commands))]
 
namespace LabelRotation
{
    public class Commands
    {
        private Dictionary<stringPoint2d> _labelBlockConfigs = 
            LabelBlockConfiguration.GetLabelBlockConfigurations();
 
        [CommandMethod("SetLabel"CommandFlags.NoTileMode)]
        public void SetLabelRotationByViewport()
        {
            var doc = CadApp.DocumentManager.MdiActiveDocument;
            var ed = doc.Editor;
 
            ed.SwitchToPaperSpace();
 
            try
            {
                var lblIds = GetLabelBlocksInModelSpace(doc);
                double twistAngle;
                if (GetTwistAngleOfViewportOnLayout(ed, out twistAngle))
                {
                    RotateLabels(doc, lblIds, twistAngle);
                }
                else
                {
                    ed.WriteMessage("\n*Cancel*");
                }
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage("\nError\n{0}", ex.Message);
                ed.WriteMessage("\n*Cancel*");
            }
            finally
            {
                Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
            }
        }
 
        #region private methods
 
        private IEnumerable<ObjectId> GetLabelBlocksInModelSpace(Document dwg)
        {
            var lblIds = new List<ObjectId>();
 
            var filterVals = new TypedValue[]
            {
                new TypedValue((int)DxfCode.Start, "INSERT"),
                new TypedValue((int)DxfCode.LayoutName, "MODEL")
            };
 
            var res = dwg.Editor.SelectAll(new SelectionFilter(filterVals));
            if (res.Status == PromptStatus.OK)
            {
                using (var tran = dwg.TransactionManager.StartTransaction())
                {
                    foreach (var id in res.Value.GetObjectIds())
                    {
                        var blk = (BlockReference)tran.GetObject(
                            id, OpenMode.ForRead);
                        var blkName = blk.Name;
                        if (blk.IsDynamicBlock)
                        {
                            var br = (BlockTableRecord)tran.GetObject(
                                blk.DynamicBlockTableRecord, OpenMode.ForRead);
                            blkName = br.Name;
                        }
 
                        if (IsTargetLabelBlock(blkName))
                        {
                            lblIds.Add(id);
                        }
                    }
 
                    tran.Commit();
                }
            }
 
            return lblIds;
        }
 
        private bool IsTargetLabelBlock(string blkName)
        {
            foreach (var item in _labelBlockConfigs)
            {
                if (item.Key.ToUpper() == blkName.ToUpper()) return true;
            }
            return false;
        }
 
        private bool GetTwistAngleOfViewportOnLayout(
            Editor ed, out double twistAngle)
        {
            twistAngle = 0.0;
 
            ObjectId vportId = SelectViewport(ed);
 
            if (!vportId.IsNull)
            {
                using (var tran = 
                    vportId.Database.TransactionManager.StartTransaction())
                {
                    var vport = (Viewport)tran.GetObject(
                        vportId, OpenMode.ForRead);
                    twistAngle = vport.TwistAngle;
                    tran.Commit();
                }
 
                return true;
            }
            else
            {
                return false;
            }
        }
 
        private ObjectId SelectViewport(Editor ed)
        {
            var opt = new PromptEntityOptions(
                "\nSelect viewport:");
            opt.SetRejectMessage("\nInvalid: not a Viewport!");
            opt.AddAllowedClass(typeof(Viewport), true);
            var res = ed.GetEntity(opt);
            if (res.Status== PromptStatus.OK)
            {
                return res.ObjectId;
            }
            else
            {
                return ObjectId.Null;
            }
        }
 
        private void RotateLabels(
            Document dwg, IEnumerable<ObjectId> lblIds, double twistAngle)
        {
            double angle = 0.0 - twistAngle;
 
            using (var tran = dwg.TransactionManager.StartTransaction())
            {
                foreach (var labelId in lblIds)
                {
                    var blk = (BlockReference)tran.GetObject(
                        labelId, OpenMode.ForWrite);
                    foreach (DynamicBlockReferenceProperty prop in
                        blk.DynamicBlockReferencePropertyCollection)
                    {
                        if (prop.PropertyName.ToUpper() ==
                            LabelBlockConfiguration.LABEL_ANGLE_PROP.ToUpper() ||
                            prop.PropertyName.ToUpper() ==
                            LabelBlockConfiguration.TAG_ANGLE_PROP.ToUpper())
                        {
                            prop.Value = angle;
                            break;
                        }
                    }
                }
                tran.Commit();
            }
        }
 
        #endregion
    }
}

Now, from this video clip we can see the result of the code execution, which is as expected: all the labels have been rotated into horizontal position according to the twist angle of the Viewport. Well... until this happens: if the stretch-able label portion of the dynamic block has been dragged before the code is executed. See this video clip. If you see the video clip closely, you should be able to see in the "Properties" window that even I do not rotate the label (the "LabelAngle" dynamic property value is not changed initially), simply dragging the label (thus the "LabelPosition X/Y" dynamic property value changes) makes the "LabelAngle" property change its value. That is why the code works only with the label blocks with its original Stretch action position, but not with the labels that were dragged prior to the code execution.

While I now know the Rotation parameter of the dynamic block somehow changes when being used in conjunction with Point parameter/Stretch action, figuring out what the Rotation property value is due to the stretch looks like difficult thing to do. But without knowing the initial rotation angle of a stretched label, my code simple does not work as expected. I was struggling to get my code work in this scenario for a while and a bell suddenly rang in my mind: I could try to save the current Stretch action's position (the property "LabelPosition X/Y", and then reset the property back to its original value (as the dynamic block definition defines); then I rotate the label (setting "LabelAngle" property according to Viewport's twist angle); after the rotation, I restore the label's Stretch action position. So, I modified the code in private method RotateLabels(), as following:
private void RotateLabels(
    Document dwg, IEnumerable<ObjectId> lblIds, double twistAngle)
{
    double angle = 0.0 - twistAngle;
 
    using (var tran = dwg.TransactionManager.StartTransaction())
    {
        foreach (var labelId in lblIds)
        {
            var blk = (BlockReference)tran.GetObject(
                labelId, OpenMode.ForWrite);
 
            var blkName = blk.Name;
            if (blk.IsDynamicBlock)
            {
                var br = (BlockTableRecord)tran.GetObject(
                    blk.DynamicBlockTableRecord, OpenMode.ForRead);
                blkName = br.Name;
            }
 
 
            Point2d originalPosition = Point2d.Origin;
            if (_labelBlockConfigs.ContainsKey(blkName.ToUpper()))
            {
                originalPosition = _labelBlockConfigs[blkName.ToUpper()];
 
                // Since dynamic property value from linear or point 
                // parameteris affected by block insertion scale, 
                // thus we need to multiply the scale.
                // Also, for the dynamic block, the block scale must 
                // be uniform scale
                double scale = blk.ScaleFactors.X;
                originalPosition = new Point2d(
                    originalPosition.X * scale, 
                    originalPosition.Y * scale);
            }
 
            //Save existing label position
            Point2d currentPosition = GetPositionProperty(blk);
 
            //Set label to its original position
            if (originalPosition!=currentPosition)
            {
                SetPositionProperty(blk, originalPosition);
            }
 
            //Set rotation
            SetRotationProperty(blk, angle);
 
            //Restore the label position
            if (originalPosition != currentPosition)
            {
                SetPositionProperty(blk, currentPosition);
            }
        }
        tran.Commit();
    }
}
 
private Point2d GetPositionProperty(BlockReference blk)
{
    double x = 0.0;
    double y = 0.0;
 
    foreach (DynamicBlockReferenceProperty prop in
                blk.DynamicBlockReferencePropertyCollection)
    {
        if (prop.PropertyName.ToUpper() ==
            LabelBlockConfiguration.LABEL_POSITION_PROP.ToUpper() + " X")
        {
            x = Convert.ToDouble(prop.Value);
        }
 
        if (prop.PropertyName.ToUpper() ==
            LabelBlockConfiguration.LABEL_POSITION_PROP.ToUpper() + " Y")
        {
            y = Convert.ToDouble(prop.Value);
        }
    }
 
    return new Point2d(x, y);
}
 
private void SetPositionProperty(BlockReference blk, Point2d position)
{
    foreach (DynamicBlockReferenceProperty prop in
                blk.DynamicBlockReferencePropertyCollection)
    {
        if (prop.PropertyName.ToUpper() ==
            LabelBlockConfiguration.LABEL_POSITION_PROP.ToUpper() + " X")
        {
            prop.Value = position.X;
        }
 
        if (prop.PropertyName.ToUpper() ==
            LabelBlockConfiguration.LABEL_POSITION_PROP.ToUpper() + " Y")
        {
            prop.Value = position.Y;
        }
    }
}
 
private void SetRotationProperty(BlockReference blk, double angle)
{
    foreach (DynamicBlockReferenceProperty prop in
                blk.DynamicBlockReferencePropertyCollection)
    {
        if (prop.PropertyName.ToUpper() ==
            LabelBlockConfiguration.LABEL_ANGLE_PROP.ToUpper() ||
            prop.PropertyName.ToUpper() ==
            LabelBlockConfiguration.TAG_ANGLE_PROP.ToUpper())
        {
            prop.Value = angle;
            break;
        }
    }
}

With this code change, this video clip shows the labels now are all rotated correctly whether they have been dragged from their original position or not prior to being rotated.

Some discussion:

1. Often there could be multiple Viewports to show different portion of ModelSpace content and the Viewports could have different twist angle. In this case, we obviously need to determine which labels are visible in which Viewport and then rotate them accordingly. In this article I omitted this scenario and simplified the case that all labels are seen in single twisted Viewport.

2. Since I somehow found the way to rotate the label correctly, I did not bother to figure out why/how the rotation property value changes due the dragging action. Because I need to rest the stretched position back to the original position (the position when the dynamic block is defined) before doing the rotation, that is why we need to associate a Point2d value with the block name as known label block information in class LabelBlockConfiguration.













Wednesday, May 31, 2017

Swap Layout Order

A question was posted in the popular CAD programming discussion forum here, regarding re-ordering layout tabs (setting Layout.TabOrder). In this discussion thread, one of my posts published here quite a few years ago was mentioned.

While the situation is a bit different from the situation described in my old post, I thought the way to re-order layout should be the same: set TabOrder property to desired number.

However, the TabOrder cannot be duplicated, and cannot be 0 (which is always the TabOrder of "MODEL" layout). So, when re-ordering a Layout tab, one must realise that a layout tab can either not be re-ordered (the first one cannot be moved down, the last one cannot be moved up), or the move will affect the layout before or after it (that is, the affected layout needs to swap its TabOrder with the one you want to re-order).

Another thing to know is, once you have Layout object open in a Transaction, you can set its TabOrder to a number that is currently assigned to other Layout. For example, you have 3 layouts. You open the Layout object in the middle, which has TabOrder=2. Then you can have code

Layout2.TabOrder=1

At this time, the first layout also has TabOrder=1. When the code executes the line Layout2.TabOrder=1, there is no exception raised. Error only raise when the Transaction is committing, because of the duplicated TabOrder.

OK, enough explanation. Here is my sample project showing how easy to re-order layout tabs by swapping TabOrder values of 2 Layout objects.

Firstly, I wrote a class doing the CAD processing, including retrieving Layout information for UI, and the TabOrder swaping code:
using System.Collections.Generic;
using System.Linq;
using Autodesk.AutoCAD.DatabaseServices;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
namespace SwapLayoutTabs
{
    public class CadUtil
    {
        public static IEnumerable&lt;LayoutInfo> GetLayoutInformation()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
 
            var lst = new List&lt;LayoutInfo>();
 
            using (var tran = dwg.TransactionManager.StartTransaction())
            {
                var dic = (DBDictionary)tran.GetObject(
                    dwg.Database.LayoutDictionaryId, OpenMode.ForRead);
                foreach (DBDictionaryEntry entry in dic)
                {
                    var layout = (Layout)tran.GetObject(entry.Value, OpenMode.ForRead);
                    if (layout.LayoutName.ToUpper() != "MODEL")
                    {
                        lst.Add(new LayoutInfo()
                        {
                            LayoutId = entry.Value,
                            TabOrder = layout.TabOrder,
                            LayoutName = layout.LayoutName
                        });
                    }
                }
                tran.Commit();
            }
 
            return from l in lst orderby l.TabOrder ascending select l;
        }
 
        public static void SwapLayoutTabOrder(ObjectId lay1Id, ObjectId lay2Id)
        {
            using (var tran = lay1Id.Database.TransactionManager.StartTransaction())
            {
                var layout1 = (Layout)tran.GetObject(lay1Id, OpenMode.ForWrite);
                var layout2 = (Layout)tran.GetObject(lay2Id, OpenMode.ForWrite);
 
                var order1 = layout1.TabOrder;
                var order2 = layout2.TabOrder;
 
                layout1.TabOrder = order2;
                layout2.TabOrder = order1;
 
                tran.Commit();
            }
        }
 
        public static void UpdateEditorScreen()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            dwg.Editor.Regen();
        }
    }
}


I created a dialog box to allow user to re-order the layout:


To fill up the ListView and then let user to manipulate the layout orders I create a class LayoutInfo to hold layout information:
using Autodesk.AutoCAD.DatabaseServices;
 
namespace SwapLayoutTabs
{
    public class LayoutInfo
    {
        public string LayoutName { setget; }
        public int TabOrder { setget; }
        public ObjectId LayoutId { setget; }
    }
}

Here is the dialog form's code-behind:
using System;
using System.Windows.Forms;
 
using Autodesk.AutoCAD.DatabaseServices;
 
namespace SwapLayoutTabs
{
    public partial class dlgLayoutOrder : Form
    {
        public dlgLayoutOrder()
        {
            InitializeComponent();
        }
 
        private void SetListView()
        {
            var layouts = CadUtil.GetLayoutInformation();
 
            lvLayouts.Items.Clear();
 
            foreach (var layout in layouts)
            {
                var item = new ListViewItem(layout.TabOrder.ToString());
                item.SubItems.Add(layout.LayoutName);
                item.Tag = layout.LayoutId;
                item.Selected = false;
 
                lvLayouts.Items.Add(item);
            }
 
            btnUp.Enabled = false;
            btnDown.Enabled = false;
        }
 
        private void OrderLayoutTab(bool increment)
        {
            var selectedIndex = lvLayouts.SelectedItems[0].Index;
            var otherIndex = increment ? selectedIndex + 1 : selectedIndex - 1;
 
            ObjectId id1 = (ObjectId)lvLayouts.Items[selectedIndex].Tag;
            ObjectId id2 = (ObjectId)lvLayouts.Items[otherIndex].Tag;
 
            try
            {
                Cursor = Cursors.WaitCursor;
 
                CadUtil.SwapLayoutTabOrder(id1, id2);
                CadUtil.UpdateEditorScreen(); //Regen is require to actually see the tab change in AutoCAD editor
                SetListView();
            }
            catch(System.Exception ex)
            {
                MessageBox.Show("Swapping layout tab error:\n\n" + ex.Message);
            }
            finally
            {
                Cursor = Cursors.Default;
            }
        }
 
        private void lvLayouts_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lvLayouts.SelectedItems.Count == 0)
            {
                btnUp.Enabled = false;
                btnDown.Enabled = false;
            }
            else
            {
                var selectedIndex = lvLayouts.SelectedItems[0].Index;
                btnUp.Enabled = selectedIndex > 0;
                btnDown.Enabled = selectedIndex &lt; lvLayouts.Items.Count - 1;
            }
        }
 
        private void btnUp_Click(object sender, EventArgs e)
        {
            OrderLayoutTab(false);
        }
 
        private void btnDown_Click(object sender, EventArgs e)
        {
            OrderLayoutTab(true);
        }
 
        private void btnClose_Click(object sender, EventArgs e)
        {
            this.Close();
        }
 
        private void dlgLayoutOrder_Load(object sender, EventArgs e)
        {
            SetListView();
        }
    }
}

And finally, this the command class:
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assemblyCommandClass(typeof(SwapLayoutTabs.MyCommands))]
 
namespace SwapLayoutTabs
{
    public class MyCommands 
    {
        [CommandMethod("OrderLayouts")]
        public static void SetLayoutOrders()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            try
            {
                using (var dlg = new dlgLayoutOrder())
                {
                    CadApp.ShowModalDialog(dlg);
                }
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage("\nError: {0}", ex.Message);
                ed.WriteMessage("\n*Cancel*");
            }
            finally
            {
                Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
            }
        }
    }
}

Go to this video clip to see how the code works.














Tuesday, April 4, 2017

Asking User To Select Entity In Other Drawing - 2 of 2

This is the second post on the same topic discussed here. In the previous post I used an approach that the source drawing, in which user needs to select something, should not be pre-opened in current AutoCAD session, because when AutoCAD (since 2015, of course) switch active drawing from one opened drawing to another opened drawing, the currently executing command will be suspended (either Document.CommandEnded or DocumentCommandCancelled event then is raised, depending on the situation).

As the previous article demonstrated, with a session command method, we can let the code open a drawing from file in AutoCAD and the newly opened drawing becomes MdiActiveDocument and the command can continue (as opposed to command being suspended when MdiActiveDocument is switched to another already opened drawing).

In this post, I use a different approach to allow user to switch MdiActiveDocument, select something there and then switch back to original drawing and continue the intended workflow. Here, I handle DocumentCollection and Document events to chain the workflow together. The following code is pretty much self-explanatory:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assemblyCommandClass(typeof(SelectFromOtherDwg2.TextCommand))]
 
namespace SelectFromOtherDwg2
{
    public class TextCommand
    {
        private static DocChangeReason _changeReason = DocChangeReason.ForNone;
        private static DocumentCollection _dwgManager = null;
        private static string _sourceDwgName = null;
        private static Document _workDwg = null;
        private static CircleInfo _circleInfo = null;
 
        [CommandMethod("SwitchDwg"CommandFlags.Session)]
        public static void SwitchDwg()
        {
            InitializeData();
            SetSourceDocumentCurrent();
        }
 
        [CommandMethod("Selecting"CommandFlags.Session | CommandFlags.NoHistory)]
        public static void SelectingInOtherDwg()
        {
            if (string.IsNullOrEmpty(_sourceDwgName) ||
                _workDwg == nullreturn;
 
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            if (dwg.Name == _sourceDwgName)
            {
                CircleInfo info;
                bool picked = GetSourceCircle(dwg.Editor, out info);
                if (picked)
                {
                    _changeReason = DocChangeReason.ForDrawing;
                    _circleInfo = info;
                }
                else
                {
                    _changeReason = DocChangeReason.ForNone;
                }
 
                // Return back to original drawing
                CadApp.DocumentManager.MdiActiveDocument = _workDwg;
            }
        }
 
        private static void Document_CommandCancelled(
            object sender, CommandEventArgs e)
        {
            if (e.GlobalCommandName.ToUpper().Contains("SWITCHDWG"))
            {
                //CadApp.ShowAlertDialog("Command is cancelled." +
                //    "\nCurrent Drawing: " +
                //    CadApp.DocumentManager.MdiActiveDocument.Name);
 
                _workDwg.CommandCancelled -= Document_CommandCancelled;
                _workDwg.CommandEnded -= Document_CommandEnded;
 
                if (CadApp.DocumentManager.MdiActiveDocument.Name == _sourceDwgName &&
                    _changeReason == DocChangeReason.ForPicking)
                {
                    CadApp.DocumentManager.MdiActiveDocument.SendStringToExecute(
                        "Selectinig "truefalsefalse);
                }
            }
        }
 
        private static void Document_CommandEnded(object sender, CommandEventArgs e)
        {
            if (e.GlobalCommandName.ToUpper().Contains("SWITCHDWG"))
            {
                //CadApp.ShowAlertDialog("Command is ended." +
                //    "\nCurrent Drawing: " + 
                //    CadApp.DocumentManager.MdiActiveDocument.Name);
 
                _workDwg.CommandCancelled -= Document_CommandCancelled;
                _workDwg.CommandEnded -= Document_CommandEnded;
 
                if (CadApp.DocumentManager.MdiActiveDocument.Name == _sourceDwgName &&
                    _changeReason == DocChangeReason.ForPicking)
                {
                    CadApp.DocumentManager.MdiActiveDocument.SendStringToExecute(
                        "Selecting "truefalsefalse);
                }
            }
        }
 
        private static void DocumentCollection_DocumentBecameCurrent(
            object sender, DocumentCollectionEventArgs e)
        {
            if (e.Document.Name==_workDwg.Name && 
                _changeReason== DocChangeReason.ForDrawing)
            {
                _dwgManager.DocumentBecameCurrent -= 
                    DocumentCollection_DocumentBecameCurrent;
                //CadApp.ShowAlertDialog("Draw circle here...");
                DrawCircle(e.Document, _circleInfo.Center, _circleInfo.Radius);
                _workDwg = null;
            }
        }
 
        private static void InitializeData()
        {
            _dwgManager = CadApp.DocumentManager;
            _dwgManager.DocumentBecameCurrent += 
                DocumentCollection_DocumentBecameCurrent;
            
            _circleInfo = null;
            _changeReason = DocChangeReason.ForNone;
 
            _workDwg = CadApp.DocumentManager.MdiActiveDocument;
            _workDwg.CommandCancelled += Document_CommandCancelled;
            _workDwg.CommandEnded += Document_CommandEnded;
        }
        
        private static void SetSourceDocumentCurrent()
        {
            Document dwg = null;
 
            if (!string.IsNullOrEmpty(_sourceDwgName))
            {
                // if source drawing already open in AutoCAD session?
                foreach (Document d in _dwgManager)
                {
                    if (d.Name.ToUpper() == _sourceDwgName.ToUpper())
                    {
                        dwg = d;
                        break;
                    }
                }
            }
 
            if (dwg == null)
            {
                _sourceDwgName = SelectSourceFile();
                if (!string.IsNullOrEmpty(_sourceDwgName))
                {
                    // Open drawing in AutoCAD, the opened drawing
                    // becomes MdiActiveDocument, then the command ends,
                    // which results in CommandEnded event being raised
                    _changeReason = DocChangeReason.ForPicking;
                    _dwgManager.Open(_sourceDwgName, true);
                }
                else
                {
                    _changeReason = DocChangeReason.ForNone;
                }
            }
            else
            {
                // Switch MdiActiveDocument, which results in 
                // current command ("SwitchDwg") being cancelled
                _changeReason = DocChangeReason.ForPicking;
                _dwgManager.MdiActiveDocument = dwg;
            }
        }
 
        private static string SelectSourceFile()
        {
            var fName = "";
 
            using (var dlg = new System.Windows.Forms.OpenFileDialog())
            {
                dlg.Title = "Select Source Drawing";
                dlg.Filter = "AutoCAD Drawing (*.dwg)|*.dwg";
                dlg.Multiselect = false;
                if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    fName = dlg.FileName;
                }
            }
 
            return fName;
        }
 
        private static bool GetSourceCircle(
            Editor ed, out CircleInfo circleInfo)
        {
            circleInfo = null;
 
            var opt = new PromptEntityOptions("\nSelect a circle:");
            opt.SetRejectMessage("\nInvalid: not a circle.");
            opt.AddAllowedClass(typeof(Circle), true);
 
            var res = ed.GetEntity(opt);
            if (res.Status == PromptStatus.OK)
            {
                using (var tran =
                    ed.Document.TransactionManager.StartTransaction())
                {
                    var c = (Circle)tran.GetObject(
                        res.ObjectId, OpenMode.ForRead);
                    circleInfo = new CircleInfo
                    {
                        Center = c.Center, Radius = c.Radius
                    };
                    tran.Commit();
                }
 
                return true;
            }
            else
            {
                return false;
            }
        }
 
        private static void DrawCircle(Document dwg, Point3d pt, double r)
        {
            using (var lck = dwg.LockDocument())
            {
                using (var tran =
                    dwg.TransactionManager.StartTransaction())
                {
                    var model = (BlockTableRecord)tran.GetObject(
                        SymbolUtilityServices.GetBlockModelSpaceId(
                            dwg.Database), OpenMode.ForWrite);
 
                    var c = new Circle();
                    c.Center = pt;
                    c.Radius = r;
                    c.SetDatabaseDefaults(dwg.Database);
 
                    model.AppendEntity(c);
                    tran.AddNewlyCreatedDBObject(c, true);
 
                    tran.Commit();
                }
            }
        }
    }
}

Watch this video clip to see the result of running this code.

See we can see, with this approach, the source drawing can stay open in current AutoCAD session, so that user can switch to it for selecting as many times as needed, as opposed to in the previous approach where the source drawing has to be opened/closed each time user needs to select something from it.

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.