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.





1 comment:

High Technologies Solutions said...

High Technologies Solutions is the only AutoCAD training Center in Delhi which offers highly proficient and technical training in AutoCAD. Further More Information Here-+91-9311002620 Or Visit Website- https://www.htsindia.com/autocad-training-institute

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.