Monday, April 27, 2020

Jig With Dimension

There is a question posted in Autodesk's .NET API discussion forum for a while without being answered for a while. Today I happened to have a bit spare time, so I put together a bit of code to see what I can do.

Since the goal is showing a dimension during jig dragging, obviously, the dimension is the distance and/or angle of measurement of the current location and original location of the dragged entity. We all know that AutoCAD's built-in tooltip has already done this and done it pretty good. So, personally, I do not see why I need to reinvent the wheel. But since someone asked, I would give it a try.

I decide to use AlignedDimension in the custom jig. With an AlignedDimention, be it is associated to an entity or not, if user uses mouse to drag one of its extension line (straight or rotating), its dimension text updated automatically. If I use it in the jig, the first extension line of the AlignedDimension would be fixed at the original entity location, and the other extension line would be moved with the mouse.

I create a custom DrawJig that could be used for move existing entity: DimJig class:

using System;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
namespace DimJig
{
    public class DimMoveJig : DrawJig
    {
        private ObjectId _entityId;
        private Point3d _basePoint = Point3d.Origin;
        private Point3d _prevPoint = Point3d.Origin;
        private Point3d _movingPoint = Point3d.Origin;
 
        private Document _dwg;
        private Editor _ed;
 
        private Entity _entity;
        private AlignedDimension _dim = null;
 
        public DimMoveJig() : base()
        {
            _dwg = CadApp.DocumentManager.MdiActiveDocument;
            _ed = _dwg.Editor;
        }
 
        public void Move()
        {
            if (!SelectEntity(out ObjectId entIdout Point3d basePt)) return;
 
            _entityId = entId;
            _basePoint = basePt;
            _prevPoint = basePt;
            _movingPoint = basePt;
 
            var offset = GetDimLineOffset(_entityId);
            var dimTextPt = GetMidOffsetPoint(_basePoint, _movingPoint, offset);
 
            try
            {
                _dim = new AlignedDimension(
                    _basePoint, 
                    _movingPoint, 
                    dimTextPt, 
                    "0000", 
                    _dwg.Database.DimStyleTableId);
 
                using (var tran = _dwg.TransactionManager.StartTransaction())
                {
                    _entity = (Entity)tran.GetObject(_entityId, OpenMode.ForWrite);
 
                    var res = _ed.Drag(this);
                    if (res.Status == PromptStatus.OK)
                    {
                        tran.Commit();
                    }
                    else
                    {
                        tran.Abort();
                    }
                }
            }
            finally
            {
                if (_dim != null) _dim.Dispose();
            }
 
            _ed.WriteMessage("\n");
        }
 
        protected override SamplerStatus Sampler(JigPrompts prompts)
        {
            var status = SamplerStatus.NoChange;
 
            var opt = new JigPromptPointOptions(
                "\nMove to:");
            opt.UseBasePoint = true;
            opt.BasePoint = _basePoint;
            opt.Cursor = CursorType.RubberBand;
 
            var res = prompts.AcquirePoint(opt);
            if (res.Status== PromptStatus.OK)
            {
                if (!res.Value.Equals(_movingPoint))
                {
                    var mt = Matrix3d.Displacement(_movingPoint.GetVectorTo(res.Value));
                    _entity.TransformBy(mt);
 
                    _dim.XLine2Point = res.Value;
                    var dist = _basePoint.DistanceTo(res.Value).ToString("######0.000");
                    _dim.DimensionText = dist.ToString();
 
                    _movingPoint = res.Value;
 
                    status = SamplerStatus.OK;
                }
            }
            else
            {
                status = SamplerStatus.Cancel;
            }
 
            return status;
        }
 
        protected override bool WorldDraw(WorldDraw draw)
        {
            _dim.RecomputeDimensionBlock(true);
            draw.Geometry.Draw(_dim);
            
            return draw.Geometry.Draw(_entity);
        }
 
        #region private methods
 
        private bool SelectEntity(out ObjectId entIdout Point3d basePoint)
        {
            entId = ObjectId.Null;
            basePoint = Point3d.Origin;
 
            var ok = true;
            var eRes = _ed.GetEntity("\nSelect entity to move:");
            if (eRes.Status== PromptStatus.OK)
            {
                entId = eRes.ObjectId;
 
                var pRes = _ed.GetPoint("\nSelect base point:");
                if (pRes.Status== PromptStatus.OK)
                {
                    basePoint=pRes.Value;
                }
                else
                {
                    ok = false;
                }
            }
            else
            {
                ok = false;
            }
 
            if (!ok) _ed.WriteMessage("\n*Cancel*\n");
 
            return ok;
        }
 
        private double GetDimLineOffset(ObjectId entId)
        {
            Extents3d ext;
 
            using (var tran = entId.Database.TransactionManager.StartOpenCloseTransaction())
            {
                var ent = (Entity)tran.GetObject(entIdOpenMode.ForRead);
                ext = ent.GeometricExtents;
            }
 
            var w = ext.MaxPoint.X - ext.MinPoint.X;
            var h = ext.MaxPoint.Y - ext.MinPoint.Y;
 
            return (w > h ? w : h) / 5.0;
        }
 
        private Point3d GetMidOffsetPoint(Point3d startPtPoint3d endPtdouble offset)
        {
            double ang;
            using (var line = new Line(startPtendPt))
            {
                ang = line.Angle;
            }
 
            if (startPt.X < endPt.X)
                ang = ang + Math.PI / 2.0;
            else
                ang = ang - Math.PI / 2.0;
 
            var x = Math.Cos(ang) * offset;
            var y = Math.Sin(ang) * offset;
 
            var midX = (startPt.X + endPt.X) / 2.0;
            var midY = (startPt.Y + endPt.Y) / 2.0;
            var midZ = (startPt.Z + endPt.Z) / 2.0;
            var midPt = new Point3d(midXmidYmidZ);
 
            return new Point3d(midPt.X + xmidPt.Y + ymidPt.Z);
        }
 
        #endregion
    }
}

As the code shows, I simply create a non-database-residing AlignedDimension, and during jig dragging, I update the dimension's second extension line (to the current mouse point), calculate the distance as dimension text; then the overridden WorldDraw() method to redrawing the dimension.

This is the command class to use this DimJig:

using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assemblyCommandClass(typeof(DimJig.MyCommands))]
 
namespace DimJig
{
    public class MyCommands 
    {
        [CommandMethod("MyMoveJig")]
        public static void RunMyCommand()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            try
            {
                DimMoveJig jig = new DimMoveJig();
                jig.Move();
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage($"\nInitializing error:\n{ex.Message}\n");
            }
        }
    }
}

See this video clip for DimJig's effect:



I have omitted many things here: the dimension's dimension line position (offset of the dimension line) would need to be calculated/changed with mouse move; the dimension text height would need to be changed based on the entity's size, the view's zooing...These would make the code quite complicated. With good AutoCAD tool tip already in place (we can see in this video clip), I would not pains-takingly reinvent the wheel. So, the code here is just for a concept-proving.

Tuesday, April 21, 2020

Show Temporary Entity/Shape During User Selecting - 2

This is the continued discussion of my previous post. In this article I tried to use DrawableOverrule to achieve the same or similar visual effect during user selecting process.

Here comes the code:

First, the MySelectionDrawableOverrule class, which is responsible for the visual effect (creating an extra entity image at a selected entity)

using System;
using System.Collections.Generic;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.GraphicsInterface;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
 
namespace TempEntityInSelection
{
    public class MySelectionDrawableOverrule : DrawableOverrule
    {
        private bool _originalOverruling = Overrule.Overruling;
        private Func<ObjectIdEntity> _createTempEntityFunction = null;
        private Editor _ed;
 
        public MySelectionDrawableOverrule(Editor ed)
        {
            _ed = ed;
        }
 
        public List<ObjectId> Entities { private setget; } = new List<ObjectId>();
 
        public void Start(Func<ObjectIdEntitycreateTempEntFunction)
        {
            if (createTempEntFunction == nullreturn;
 
            _createTempEntityFunction = createTempEntFunction;
 
            Overrule.AddOverrule(RXClass.GetClass(typeof(Curve)), thistrue);
            this.SetCustomFilter();
            Overruling = true;
 
            _ed.SelectionAdded += Editor_SelectionAdded;
            _ed.SelectionRemoved += Editor_SelectionRemoved;
        }
 
        public void Stop()
        {
            Overrule.RemoveOverrule(RXClass.GetClass(typeof(Curve)), this);
            Overruling = _originalOverruling;
 
            _ed.SelectionAdded -= Editor_SelectionAdded;
            _ed.SelectionRemoved -= Editor_SelectionRemoved;
        }
 
        public override bool IsApplicable(RXObject overruledSubject)
        {
            var ent = (Entity)overruledSubject;
            return Entities.Contains(ent.ObjectId);
        }
 
        public override bool WorldDraw(Drawable drawableWorldDraw wd)
        {
            var ent = drawable as Entity;
            if (ent!=null)
            {
                using (var tempEnt = _createTempEntityFunction(ent.ObjectId))
                {
                    wd.Geometry.Draw(tempEnt);
                }
            }
 
            return base.WorldDraw(drawablewd);
        }
 
        #region private methods
 
        private void Editor_SelectionAdded(
            object senderSelectionAddedEventArgs e)
        {
            var added = e.AddedObjects;
            if (added.Count == 0) return;
 
            if (_createTempEntityFunction == nullreturn;
 
            var add = false;
            foreach (SelectedObject s in added)
            {
                if (!Entities.Contains(s.ObjectId))
                {
                    Entities.Add(s.ObjectId);
                    if (!addadd = true;
                }
            }
 
            if (add) _ed.Regen();
        }
 
        private void Editor_SelectionRemoved(
            object senderSelectionRemovedEventArgs e)
        {
            var removed = e.RemovedObjects;
            if (removed.Count == 0) return;
 
            var remove = false;
            foreach (SelectedObject s in removed)
            {
                if (Entities.Contains(s.ObjectId))
                {
                    Entities.Remove(s.ObjectId);
                    if (!removeremove = true;
                }
            }
 
            if (remove) _ed.Regen();
        }
 
        #endregion
    }
}

Since I only want this Overrule to take effect when entities are added/removed from SelectionSet during user selecting, thus, the Editor.SelectionAdded/Removed event is handled in this class.

Now the class MyOverruleSelector that uses MySelectionDrawableOverrule for the entity selection:

using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
namespace TempEntityInSelection
{
    public class MyOverruleSelector
    {
        private readonly Document _dwg;
        private readonly Editor _ed;
        private List<string> _layerFilters = new List<string>();
        private Func<ObjectIdEntity> _createTempEntityFunction = null;
        private MySelectionDrawableOverrule _overrule;
 
        public MyOverruleSelector(IEnumerable<stringlayerFilter = null)
        {
            _dwg = CadApp.DocumentManager.MdiActiveDocument;
            _ed = _dwg.Editor;
            if (layerFilter != null &&
                layerFilter.Count() > 0)
            {
                _layerFilters.AddRange(layerFilter);
            }
            _overrule = new MySelectionDrawableOverrule(_ed);
        }
 
        public ObjectId SelectTempEntity(
            Func<ObjectIdEntitycreateTempGraphicAction)
        {
            ObjectId selectedEnt = ObjectId.Null;
 
            _createTempEntityFunction = createTempGraphicAction;
            ObjectId[] selectedEntities = null;
 
            try
            {
                _overrule.Start(_createTempEntityFunction);
 
                PromptSelectionResult res;
                if (_layerFilters.Count == 0)
                {
                    res = _ed.GetSelection();
                }
                else
                {
                    var vals = new TypedValue[]
                    {
                        new TypedValue(
                            (int)DxfCode.LayerName,
                            string.Join(",", _layerFilters.ToArray()))
                    };
                    res = _ed.GetSelection(new SelectionFilter(vals));
                }
 
                if (res.Status == PromptStatus.OK)
                {
                    selectedEntities = res.Value.GetObjectIds();
                    HighlightEntities(selectedEntitiestrue);
 
                    selectedEnt = SelectTargetEntity(selectedEntities);
                }
            }
            finally
            {
                _overrule.Stop();
                _ed.Regen();
            }
 
            return selectedEnt;
        }
 
        #region private methods
 
        private void HighlightEntities(
            IEnumerable<ObjectIdentIdsbool highlight)
        {
            foreach (var id in entIds)
                HighlightEntity(idhighlight);
        }
 
        private void HighlightEntity(ObjectId entIdbool highlight)
        {
            using (var tran = entId.Database.TransactionManager.
                StartOpenCloseTransaction())
            {
                var ent = (Entity)tran.GetObject(entIdOpenMode.ForRead);
                if (highlight)
                    ent.Highlight();
                else
                    ent.Unhighlight();
            }
        }
 
        private ObjectId SelectTargetEntity(IEnumerable<ObjectIdentIds)
        {
            while (true)
            {
                var res = _ed.GetEntity(
                    "\nSelect one of the highlighted entity to draw new circle:");
                if (res.Status == PromptStatus.OK)
                {
                    if (entIds.Contains(res.ObjectId))
                    {
                        return res.ObjectId;
                    }
                    else
                    {
                        _ed.WriteMessage(
                            "\nInvalid: selected wrong entity!");
                    }
                }
                else
                {
                    break;
                }
            }
 
            return ObjectId.Null; ;
        }
 
        #endregion
    }
}

As the code shows, in comparison to the code in previous article, the custom DrawableOverrule replaced code that uses Transient Graphics to draw temporary entities. One of differences worth pointing out is that when using Transient Graphics, the code creates non-database-residing entities and keeps them in memory until the Transient Graphics are cleared; while in the custom DrawableOverrule, non-database-residing entity is also created for Overrule.WorldDraw() method to generate the graphic image of the entity, and then the temporary entity is disposed immediately.

Finally the CommandClass that put everything into work, which is basically the same as in previous article:

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assemblyCommandClass(typeof(TempEntityInSelection.MyCommands))]
 
namespace TempEntityInSelection
{
    public class MyCommands
    {
        [CommandMethod("MySelecting")]
        public static void SelectWithTempEntShowing()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            ObjectId selectedEntId = ObjectId.Null;
            try
            {
                var selector = new MyOverruleSelector();
                selectedEntId = selector.SelectTempEntity(
                    TempEntityGenerator.Create);
 
                if (!selectedEntId.IsNull)
                {
                    AddNewEntity(selectedEntId);
                }
                else
                {
                    ed.WriteMessage("\n*Cancel*");
                }
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage($"\nInitializing error:\n{ex.Message}\n");
                ed.WriteMessage("\n*Cancel*");
            }
 
            ed.WriteMessage("\n");
        }
 
        private static void AddNewEntity(ObjectId sourceEntId)
        {
            Database db = sourceEntId.Database;
            using (var tran = db.TransactionManager.StartTransaction())
            {
                var ent = TempEntityGenerator.Create(sourceEntId);
                if (ent != null)
                {
                    ent.SetDatabaseDefaults(db);
 
                    var space = (BlockTableRecord)tran.GetObject(
                        db.CurrentSpaceId, OpenMode.ForWrite);
 
                    space.AppendEntity(ent);
                    tran.AddNewlyCreatedDBObject(enttrue);
                }
 
                tran.Commit();
            }
        }
    }
}

See video bellow showing how the code works:



If you watched the video, you would notice that adding entities to selection after the first selecting, the previous selected entities (the custom Overrule applicable entities) turn red. That is because I did not save and restore the color used to draw selected entities after drawing the temporary entity image in red. That is because I think this Overrule solution, while it works, has serious drawback: the Overrule has to call Editor.Regen() whenever selection is added or removed to make sure the DrawableOverrule-applicable entities' image updated. If the drawing has high volume of entities, Regen() would be problematic (taking time). I really wish the DrawableOverrule API could provide a way to only update the display of applicable entity, instead of the need of calling Editor.Regen().

So, I'd settle with the solution in previous article, in general. But this article just shows that a different approach is possible.





Monday, April 20, 2020

Show Temporary Entity/Shape During User Selecting

This post is one of the solutions I offered to an discussion topic in Autodesk's discussion forum, where I proposed 2 possible solutions - using Transient Graphics in Editor.SelectionAdded[Removed] event handing to show temporary entity as visual hint to help user to decide which temporary entity would be eventually changed to permanent on in the drawing database. I may later try to code the other proposed solution of using DrawableOverrule, if I can manage a bit time later.

The idea of the process is something like:

1. User want to create a new entity, based on existing entities in the drawing; but the new entity could be different (entity type, entity geometry) when different existing entity is chosen.
2. So, user would select a few existing entities; for each selected entity, a possible new entity associated to it would be shown as temporary entity. Thus, looking at these temporary entities, user would be easily decide which temporary entity is the one he/she needed.
3. Once user made the choice, chosen temporary entity would be added to database and other temporary entities would be gone.

The solution design:

1. Creating a class that handles Editor's SelectionAdded/Removed event, so that during Editor.GetSelection() calling, Transient Graphics can be drawn as temporary entity images.
2. Since the temporary entity is only dependent to an existing entity, the actual temporary entity generating process is coded outside the said class and only to be "injected" in to the said class' selecting process. This way, the code of generating temporary entity can be easily expended to create different type of entity or entity with different geometric data without affect the process of selecting with temporary entity image.

Here is the code of class MySelector:

using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
using Autodesk.AutoCAD.GraphicsInterface;
 
namespace TempEntityInSelection
{
    public class MySelector : IDisposable 
    {
        private readonly Document _dwg;
        private readonly Database _db;
        private readonly Editor _ed;
        private Dictionary<ObjectIdEntity> _tempEntities = 
            new Dictionary<ObjectIdEntity>();
        private Func<ObjectIdEntity> _createTempEntityFunction = null;
        private List<string> _layerFilters = new List<string>();
        private TransientManager _tsManager = 
            TransientManager.CurrentTransientManager;
 
        public MySelector(IEnumerable<stringlayerFilter = null)
        {
            _dwg = CadApp.DocumentManager.MdiActiveDocument;
            _db = _dwg.Database;
            _ed = _dwg.Editor;
            if (layerFilter!=null &&
                layerFilter.Count()>0)
            {
                _layerFilters.AddRange(layerFilter);
            }
        }
 
        #region publuc methods
 
        public void Dispose()
        {
            ClearTransients();
 
            if (_tempEntities.Count>0)
            {
                foreach (var item in _tempEntities)
                {
                    if (item.Value == nullcontinue;
 
                    if (item.Value.ObjectId.IsNull &&
                        !item.Value.IsDisposed) item.Value.Dispose();
                }
 
                _tempEntities.Clear();
            }
        }
 
        public ObjectId SelectTempEntity(
            Func<ObjectIdEntitycreateTempGraphicAction)
        {
            ObjectId selectedEnt = ObjectId.Null;
 
            _createTempEntityFunction = createTempGraphicAction;
 
            ObjectId[] selectedEntities = null;
            try
            {
                _ed.SelectionAdded += Editor_SelectionAdded;
                _ed.SelectionRemoved += Editor_SelectionRemoved;
 
                PromptSelectionResult res;
                if (_layerFilters.Count==0)
                {
                    res = _ed.GetSelection();
                }
                else
                {
                    var vals = new TypedValue[]
                    {
                        new TypedValue(
                            (int)DxfCode.LayerName, 
                            string.Join(",", _layerFilters.ToArray()))
                    };
                    res = _ed.GetSelection(new SelectionFilter(vals));
                }
 
                if (res.Status== PromptStatus.OK)
                {
                    selectedEntities = res.Value.GetObjectIds();
                    HighlightEntities(selectedEntitiestrue);
 
                    selectedEnt = SelectTargetEntity(selectedEntities);
                }
 
                ClearTransients();
            }
            finally
            {
                _ed.SelectionAdded -= Editor_SelectionAdded;
                _ed.SelectionRemoved -= Editor_SelectionRemoved;
 
                if (selectedEntities!=null)
                {
                    HighlightEntities(selectedEntitiesfalse);
                }
            }
 
            return selectedEnt;
        }
 
        #endregion
 
        #region private methods
 
        private void Editor_SelectionAdded(
            object senderSelectionAddedEventArgs e)
        {
            var added = e.AddedObjects;
            if (added.Count == 0) return;
 
            if (_createTempEntityFunction == nullreturn;
 
            ClearTransients();
 
            foreach (SelectedObject s in added)
            {
                if (!_tempEntities.ContainsKey(s.ObjectId))
                {
                    Entity tempEnt = _createTempEntityFunction(s.ObjectId);
                    _tempEntities.Add(s.ObjectId, tempEnt);
                }
                else
                {
                    Entity tempEnt = _tempEntities[s.ObjectId];
                    if (tempEnt != nulltempEnt.Dispose();
 
                    tempEnt = _createTempEntityFunction(s.ObjectId);
                    _tempEntities[s.ObjectId] = tempEnt;
                }
            }
 
 
            if (_tempEntities.Count>0)
            {
                DrawTransients();
            }
        }
 
        private void Editor_SelectionRemoved(
            object senderSelectionRemovedEventArgs e)
        {
            var removed = e.RemovedObjects;
            if (removed.Count == 0) return;
 
            ClearTransients();
 
            foreach (SelectedObject s in removed)
            {
                if (_tempEntities.ContainsKey(s.ObjectId))
                {
                    _tempEntities[s.ObjectId].Dispose();
                    _tempEntities.Remove(s.ObjectId);
                }
            }
 
            if (_tempEntities.Count > 0)
            {
                DrawTransients();
            }
        }
 
        private void ClearTransients()
        {
            _tsManager.EraseTransients(
                TransientDrawingMode.DirectTopmost, 
                128, 
                new IntegerCollection());
        }
 
        private void DrawTransients()
        {
            foreach (var item in _tempEntities)
            {
                _tsManager.AddTransient(
                    item.Value, 
                    TransientDrawingMode.DirectTopmost, 
                    128, 
                    new IntegerCollection());
            }
        }
 
        private void HighlightEntities(
            IEnumerable<ObjectIdentIdsbool highlight)
        {
            foreach (var id in entIds)
                HighlightEntity(idhighlight);
        }
 
        private void HighlightEntity(ObjectId entIdbool highlight)
        {
            using (var tran = entId.Database.TransactionManager.
                StartOpenCloseTransaction())
            {
                var ent = (Entity)tran.GetObject(entIdOpenMode.ForRead);
                if (highlight)
                    ent.Highlight();
                else
                    ent.Unhighlight();
            }
        }
 
        private ObjectId SelectTargetEntity(IEnumerable<ObjectIdentIds)
        {
            while(true)
            {
                var res = _ed.GetEntity(
                    "\nSelect one of the highlighted entity to draw new circle:");
                if (res.Status== PromptStatus.OK)
                {
                    if (entIds.Contains(res.ObjectId))
                    {
                        return res.ObjectId;
                    }
                    else
                    {
                        _ed.WriteMessage(
                            "\nInvalid: selected wrong entity!");
                    }
                }
                else
                {
                    break;
                }
            }
 
            return ObjectId.Null; ;
        }
 
        #endregion
    }
}

Because MySelector class is responsible to create Transient Graphics with non-database-residing entities (temporary entities), it should also be responsible to dispose these temporary entities when the process is done. To make the code of using MySelector simpler, I have it implement IDisposable interface, so that the instance of MySelector can be wrapped with using (...){...} block and its Dispose() method is called automatically.

Also, the only public method SelectTempEntity() accepts a Func<ObjectId, Entity> as argument. This function is responsible to create a new entity based on an existing entity (i.e. input an entity's ObjectId, a non-database-residing entity will be returned). This allows the programmer of using MySelector class to freely implement the Func<ObjectId, Entity> to produce whatever entity he/she wants without affecting how MySelector works.

Following is a set of static methods that implement Func<ObjectId, Entity>:

using System;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
 
namespace TempEntityInSelection
{
    public class TempEntityGenerator
    {
        /// <summary>
        /// This method create a new circle at
        /// 1. If the source entity is a Circle: @its center
        /// 2. If the source is line, @its middle poiny
        /// 3. If it is neither circle, nor line, no new entity is created
        /// </summary>
        /// <param name="entId"></param>
        /// <returns></returns>
        public static Entity Create(ObjectId entId)
        {
            Circle circle = null;
 
            using (var tran = entId.Database.
                TransactionManager.StartOpenCloseTransaction())
            {
                var ent = (Entity)tran.GetObject(entIdOpenMode.ForRead);
 
                if (ent is Line || ent is Circle)
                {
                    circle = new Circle();
                    circle.ColorIndex = 1;
                    circle.Radius = 200;
 
                    if (ent is Circle)
                    {
                        circle.Center = ((Circle)ent).Center;
                    }
                    else if (ent is Line)
                    {
                        var line = (Line)ent;
                        var x = (line.EndPoint.X + line.StartPoint.X) / 2.0;
                        var y = (line.EndPoint.Y + line.StartPoint.Y) / 2.0;
                        circle.Center = new Point3d(xy, 0.0);
                    }
                }
 
                tran.Commit();
            }
 
            return circle;
        }
 
        public static Entity CreateDifferentEntity(ObjectId entId)
        {
            throw new NotImplementedException();
        }
    }
}

Now, here is the CommandClass that uses MySelector to select an target entity and actual create a new entity based selected target entity.

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assemblyCommandClass(typeof(TempEntityInSelection.MyCommands))]
 
namespace TempEntityInSelection
{
    public class MyCommands
    {
        [CommandMethod("TestDraw")]
        public static void RunMyCommand()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            ObjectId selectedEntId = ObjectId.Null;
            try
            {
                using (var selector = new MySelector())
                {
                    selectedEntId = selector.SelectTempEntity(
                        TempEntityGenerator.Create);
                }
 
                if (!selectedEntId.IsNull)
                {
                    AddNewEntity(selectedEntId);
                }
                else
                {
                    ed.WriteMessage("\n*Cancel*");
                }
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage($"\nInitializing error:\n{ex.Message}\n");
                ed.WriteMessage("\n*Cancel*");
            }
 
            ed.WriteMessage("\n");
        }
 
        private static void AddNewEntity(ObjectId sourceEntId)
        {
            Database db = sourceEntId.Database;
            using (var tran = db.TransactionManager.StartTransaction())
            {
                var ent = TempEntityGenerator.Create(sourceEntId);
                if (ent != null)
                {
                    ent.SetDatabaseDefaults(db);
 
                    var space = (BlockTableRecord)tran.GetObject(
                        db.CurrentSpaceId, OpenMode.ForWrite);
 
                    space.AppendEntity(ent);
                    tran.AddNewlyCreatedDBObject(enttrue);
                }
 
                tran.Commit();
            }
        }
    }
}


Following video shows the effect of running the code:







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.