Saturday, August 14, 2021

An EntityJig For MLine Creation

 I recently involved a discussion in Autodesk's .NET forum, which is about to create double-lines. The OP chose to create a MLine and then explode it to achieve the goal of drawing line segments continuously with double offsets. While this is an viable way of doing this, with the help of .NET API's Curve.GetOffsetCurves(), the goal of drawing double (or triple) lines is rather easier to achieve than drawing a MLine and then explode it. 

But this post is not about which way is better/easier to draw double lines. The OP somehow ran in to issues that the "Draw MLine -> Explode it" code that generates extra lines after explosion. I did not spent too much time to troubleshoot the code posted. Rather I simply write some code to prove that drawing MLine and then exploding it should be fairly easy (many programmers, including myself, have less patient to fix others' code and choose to rewrite their onw for the same process). So, once my code drew the MLine and exploded it correctly (as expected, I could not see why OP ran into that issue, as posted), I just added a bit extra code to make it fully functional MLine jig, so that I can post a working code sample here for anyone who may be interested in. 

This is the class MLineJig:

using System;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
 
namespace C3DMiscTest
{
    public class MLineJig : EntityJig
    {
        private Point3d _currPoint;
        private Point3d _prevPoint;
 
        private readonly Document _dwg;
        private readonly Editor _ed;
        private readonly ObjectId _styleId;
 
        private readonly TransientManager _tsManager = 
            TransientManager.CurrentTransientManager;
 
        private Mline _mline;
        private Mline _ghost = null;
 
        public MLineJig(string mlineStyleName = null) : base(new Mline())
        {
            _dwg = CadApp.DocumentManager.MdiActiveDocument;
            _ed = _dwg.Editor;
 
            _styleId = GetMLineStyle(_dwg.Database, mlineStyleName);
            if (_styleId.IsNull)
            {
                throw new InvalidCastException("No MLine style available!");
            }
 
            _mline = Entity as Mline;
        }
 
        public void Draw()
        {
            var res = _ed.GetPoint("\nSelect start point:");
            if (res.Status != PromptStatus.OK) return;
            _currPoint = res.Value;
            _prevPoint = res.Value;
            
            var cancelled = false;
            ObjectId explodeId = ObjectId.Null;
 
            using (var tran = _dwg.TransactionManager.StartTransaction())
            {
                _mline.SetDatabaseDefaults(_dwg.Database);
                _mline.Style = _styleId;
                _mline.Justification = MlineJustification.Zero;
                _mline.Normal = new Vector3d(0, 0, 1);
 
                _mline.AppendSegment(_currPoint);
                var space = (BlockTableRecord)tran.GetObject(
                    _dwg.Database.CurrentSpaceId, OpenMode.ForWrite);
                explodeId = space.AppendEntity(_mline);
                tran.AddNewlyCreatedDBObject(_mline, true);
                _dwg.TransactionManager.QueueForGraphicsFlush();
 
                while (true)
                {
                    ClearGhostImage();
 
                    var jigRes = _ed.Drag(this);
                    if (jigRes.Status == PromptStatus.OK)
                    {
                        _mline.AppendSegment(_currPoint);
                        _prevPoint = _currPoint;
                        _dwg.TransactionManager.QueueForGraphicsFlush();
                    }
                    else if (jigRes.Status == PromptStatus.Keyword)
                    {
                        if (jigRes.StringResult=="Cancel")
                        {
                            cancelled = true;
                        }
                        break;
                    }
                    else
                    {
                        cancelled = true;
                        break;
                    }
                }
 
                if (!cancelled)
                {
                    tran.Commit();
                    _ed.Regen();
                }
                else
                {
                    tran.Abort();
                }
 
                ClearGhostImage();
            }
 
            if (cancelled || _mline.NumberOfVertices < 2)
            {
                _mline.Dispose();
            }
            else
            {
                if (PromptForExplosion())
                {
                    ExplodeMLine(explodeId);
                }
            }
        }
 
        protected override SamplerStatus Sampler(JigPrompts prompts)
        {
            var jigOpt = new JigPromptPointOptions(
                "\nSelect next point:");
            jigOpt.UseBasePoint = true;
            jigOpt.BasePoint = _prevPoint;
            jigOpt.Cursor = CursorType.RubberBand;
            if (_mline.NumberOfVertices >= 2)
            {
                jigOpt.Keywords.Add("Done");
                jigOpt.Keywords.Add("Cancel");
                jigOpt.Keywords.Default = "Done";
                jigOpt.AppendKeywordsToMessage = true;
                jigOpt.UserInputControls = UserInputControls.NullResponseAccepted;
            }
 
            var jigRes = prompts.AcquirePoint(jigOpt);
            if (jigRes.Status== PromptStatus.OK)
            {
                if (jigRes.Value==_prevPoint)
                {
                    return SamplerStatus.NoChange;
                }
                else
                {
                    _currPoint = jigRes.Value;
                    return SamplerStatus.OK;
                }
            }
            else if (jigRes.Status== PromptStatus.Keyword)
            {
                return jigRes.StringResult == "Done" ?
                    SamplerStatus.NoChange : SamplerStatus.Cancel;
            }
            else
            {
                return SamplerStatus.Cancel;
            }
        }
 
        protected override bool Update()
        {
            CreateGhostImage();
            return true;
        }
 
        private ObjectId GetMLineStyle(Database dbstring styleName)
        {
            var id = ObjectId.Null;
            
            using (var tran = db.TransactionManager.StartTransaction())
            {
                var dict = (DBDictionary)tran.GetObject(
                    db.MLStyleDictionaryId, OpenMode.ForRead);
                if (!string.IsNullOrEmpty(styleName) &&
                    dict.Contains(styleName))
                {
                    id = dict.GetAt(styleName);
                }
                else
                {
                    foreach (DBDictionaryEntry entry in dict)
                    {
                        id = entry.Value;
                        break;
                    }
                }
                tran.Commit();
            }
 
            return id;
        }
 
        private void CreateGhostImage()
        {
            ClearGhostImage();
 
            _ghost = new Mline();
            _ghost.SetDatabaseDefaults(_dwg.Database);
            _ghost.Style = _styleId;
            _ghost.Justification = MlineJustification.Zero;
            _ghost.Normal = new Vector3d(0, 0, 1);
            _ghost.ColorIndex = 2;
 
            _ghost.AppendSegment(_prevPoint);
            _ghost.AppendSegment(_currPoint);
 
            _tsManager.AddTransient(
                _ghost, 
                TransientDrawingMode.DirectTopmost, 
                128, 
                new IntegerCollection());
        }
 
        private void ClearGhostImage()
        {
            if (_ghost != null)
            {
                _tsManager.EraseTransient(
                    _ghost, new IntegerCollection());
                _ghost.Dispose();
            }
        }
 
        private bool PromptForExplosion()
        {
            var kOpt = new PromptKeywordOptions(
                "\nDo you want to explode the MLine:");
            kOpt.Keywords.Add("Yes");
            kOpt.Keywords.Add("No");
            kOpt.Keywords.Default = "No";
            var res = _ed.GetKeywords(kOpt);
            if (res.Status== PromptStatus.OK &&
                res.StringResult=="Yes")
            {
                return true;
            }
            else
            {
                return false;
            }
        }
 
        private void ExplodeMLine(ObjectId entId)
        {
            using (var tran = entId.Database.TransactionManager.StartTransaction())
            {
                var entity = (Entity)tran.GetObject(entId, OpenMode.ForWrite);
 
                var objects = new DBObjectCollection();
                entity.Explode(objects);
 
                var space = (BlockTableRecord)tran.GetObject(
                    entId.Database.CurrentSpaceId, OpenMode.ForWrite);
                foreach (DBObject obj in objects)
                {
                    space.AppendEntity(obj as Entity);
                    tran.AddNewlyCreatedDBObject(obj, true);
                }
                entity.Erase();
                tran.Commit();
            }             
        }
    }
}
To use MLineJig class:

[CommandMethod("DoubleLine")]
public static void DrawDoubleLine()
{
    var dwg = CadApp.DocumentManager.MdiActiveDocument;
    var ed = dwg.Editor;
 
    try
    {
        var jig = new MLineJig();
        jig.Draw();
    }
    catch(System.Exception ex)
    {
        CadApp.ShowAlertDialog($"Error:\n{ex.Message}");
    }
}

See this video clip for the code action: