Wednesday, December 16, 2020

Obtaining Block's Image

 Showing a block's image is a very often-needed task in AutoCAD programming. AutoCAD drawing file stores a bitmap image in it as thumbnail image of the drawing, if the block drawing is saved properly. There are different way to retrieve the thumbnail image from drawing:

1. In the VBA era, there was a very populate COM control available DwgThumbnail.ocx, which can be placed in VBA's UserForm, or stand-alone app's UI. However, when most AutoCAD being used have moved to 64-bit VBA, this 32-bit COM control is no longer useful.

2. With .NET API, one can use Database.ThumbnailBitmap property to retrieve the block's image from block drawing file by opening it as side database.

However, the 2 approached mentioned above only work with block drawing files. What about getting the image of blocks that are in current opening drawing and show them? Well, there is also an .NET API approach to to it:

Autodesk.AutoCAD.Windows.Data.CMLContentSearchPreviews.GetBlockTRThumbnail(BlockTableRecord)

Because the APIs in namespace Autodesk.AutoCAD.Windows.Data is meant for AutoCAD's UI support, the block image generated with this method is in quite low resolution/small size. So, it is often a not satisfied solution to our custom CAD application needs.

Well, there is actually another much better API method right there available to get better images from blocks in drawing, whether the drawing is opened in AutoCAD, or not (i.e. we can open it in AutoCAD as side database). This method is

Autodesk.AutoCAD.Internal.Utils.GetBlockImage()

The good thing with this method is that one can pass image's width and height to let the method generate an image in desired size while keeping the image in relatively good quality.

With this method, I have written a demo app that retrieve images of all blocks of an opened drawing in AutoCAD and displayed them in a PaletteSet. This is quite similar one can see in "Design Center" PaletteSet window where all blocks' image are shown. The demo app also allows to retrieve block images from a not opened drawing (i.e. the drawing is opened as side database when images are retrieved). See the video clip showing how it works:

Form the video one may notice that it takes quite long time to load all block images from a file. That is because for each block the code retrieves 2 images, one is in smaller size (100 x100) while the other is in larger size (300 x 300), because I'd like to show an image in better quality and in larger size when user clicks each small image on the PaletteSet.

As one can imagine, this PaletteSet can be easily modified to simulate AutoCAD's new block inserting palette.

I am not going to post the code of the demo app itself, because the basic thing that makes it work is Internal.Utils.GetBlockImage()

Here are some code that wraps up that method to generate block image as System.IntPtr, System.Drawing.Image, or System.Windows.Media.ImageSource for different use (in Win Form application, or in WPF application):

using System;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Internal;
 
namespace UseInternalNamespace
{
    public class InternalNamespaceTool
    {
        public static System.IntPtr GetBlockImagePointer(
            string blkName, Database db, int imgWidth, int imgHeight,
            Autodesk.AutoCAD.Colors.Color backColor)
        {
            var blockId = GetBlockTableRecordId(blkName, db);
            if (!blockId.IsNull)
            {
                var imgPtr = Utils.GetBlockImage(blockId, imgWidth, imgHeight, backColor);
                return imgPtr;
            }
            else
            {
                throw new ArgumentException(
                    $"Cannot find block definition in current database: \"{blkName}\".");
            }
        }
 
        public static System.IntPtr GetBlockImagePointer(
            ObjectId blockDefinitionId, int imgWidth, int imgHeight,
            Autodesk.AutoCAD.Colors.Color backColor)
        {
            var imgPtr = Utils.GetBlockImage(blockDefinitionId, imgWidth, imgHeight, backColor);
            return imgPtr;
        }
 
        public static System.Drawing.Image GetBlockImage(
            string blkName, Database db, int imgWidth, int imgHeight, 
            Autodesk.AutoCAD.Colors.Color backColor)
        {
            return System.Drawing.Image.FromHbitmap(
                GetBlockImagePointer(blkName, db, imgWidth, imgHeight, backColor));
 
        }
 
        public static System.Drawing.Image GetBlockImage(
            ObjectId blockDefinitionId, int imgWidth, int imgHeight,
            Autodesk.AutoCAD.Colors.Color backColor)
        {
            return System.Drawing.Image.FromHbitmap(
                GetBlockImagePointer(blockDefinitionId, imgWidth, imgHeight, backColor));
        }
 
        public static System.Windows.Media.ImageSource GetImageSource(
            string blkName, Database db, int imgWidth, int imgHeight,
            Autodesk.AutoCAD.Colors.Color backColor)
        {
            var imgPtr = GetBlockImagePointer(blkName, db, imgWidth, imgHeight, backColor);
            return ConvertBitmapToImageSource(imgPtr);
        }
 
        public static System.Windows.Media.ImageSource GetImageSource(
            ObjectId blockDefinitionId, int imgWidth, int imgHeight,
            Autodesk.AutoCAD.Colors.Color backColor)
        {
            var imgPtr = GetBlockImagePointer(blockDefinitionId, imgWidth, imgHeight, backColor);
            return ConvertBitmapToImageSource(imgPtr);
        }
 
        #region private methods
 
        private static ObjectId GetBlockTableRecordId(string blkName, Database db)
        {
            var blkId = ObjectId.Null;
 
            using (var tran = db.TransactionManager.StartTransaction())
            {
                var bt = (BlockTable)tran.GetObject(db.BlockTableId, OpenMode.ForRead);
                if (bt.Has(blkName))
                {
                    blkId = bt[blkName];
                }
                tran.Commit();
            }
 
            return blkId;
        }
 
        private static System.Windows.Media.ImageSource ConvertBitmapToImageSource(IntPtr imgHandle)
        {
            return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
                imgHandle,
                IntPtr.Zero,
                System.Windows.Int32Rect.Empty,
                System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
 
        }
 
        #endregion
    }
}





Sunday, November 29, 2020

Find Line's Intersection Points With Block (BlockReference)

 I recently replied a question posted in AutoCAD .NET discussion forum, in which I proposed a code workflow. Then another question in similar context came up. So I thought I might as well write some code to demonstrate my idea in the reply to the first question, which would indirectly answer the second question: after all, once the intersection points of line and the block reference are known, trimming the line would be simple next step.

Firstly to simplify the case, I limit the discussion only on Line and BlockReference with nested entities being Curve type only.

As we know all classes derived from Entity have overloaded method IntersectWith(), which can be used to find intersection points between 2 entities. BlockReference, being derived from Entity, inherently also has its IntersectWith() method. However, because BlockReference is a reference of a composite object with many different entities nested, its IntersectWtith() method is implemented in its own way: it uses its bounding box (GeometricExtents) as its boundary to calculate its intersection points with other entity. Following code demonstrate this:

#region Command to find entity's intersecting point with block: block's bounding box
 
[CommandMethod("BlkIntersect1")]
public static void TestBlockIntersection1()
{
    var dwg = CadApp.DocumentManager.MdiActiveDocument;
    var ed = dwg.Editor;
 
    var res = ed.GetEntity("\nSelect block:");
    if (res.Status == PromptStatus.OK)
    {
        if (res.ObjectId.ObjectClass.DxfName.ToUpper() != "INSERT")
        {
            ed.WriteMessage("\nNot a block!");
            return;
        }
 
        dwg.Database.Pdmode = 34;
 
        try
        {
            CadHelper.Highlight(res.ObjectId, true);
 
            using (var tran = dwg.TransactionManager.StartTransaction())
            {
                var blk = (BlockReference)tran.GetObject(
                    res.ObjectId, OpenMode.ForRead);
                var space = (BlockTableRecord)tran.GetObject(
                    dwg.Database.CurrentSpaceId, OpenMode.ForWrite);
 
                // create a rectangle polyline to show the block's GeometricExtents
                var boundBox = CreateBoundBox(blk);
                space.AppendEntity(boundBox);
                tran.AddNewlyCreatedDBObject(boundBox, true);
                tran.TransactionManager.QueueForGraphicsFlush();
 
                while (true)
                {
                    var eRes = ed.GetEntity(
                        "\nSelect an entity intersecting the block:");
                    if (eRes.Status == PromptStatus.OK)
                    {
                        FindBlockIntersectionPoint(eRes.ObjectId, blk, space, tran);
                    }
                    else
                    {
                        break;
                    }
                }
 
                tran.Commit();
            }
        }
        finally
        {
            CadHelper.Highlight(res.ObjectId, false);
        }
    }
}
 
private static void FindBlockIntersectionPoint(
    ObjectId entId, BlockReference blk, BlockTableRecord space, Transaction tran)
{
 
    var ent = (Entity)tran.GetObject(entId, OpenMode.ForRead);
 
    var pts = new Point3dCollection();
    blk.IntersectWith(ent, Intersect.OnBothOperands, pts, IntPtr.Zero, IntPtr.Zero);
 
    if (pts.Count > 0)
    {
        foreach (Point3d pt in pts)
        {
            var dbPt = new DBPoint(pt);
            space.AppendEntity(dbPt);
            tran.AddNewlyCreatedDBObject(dbPt, true);
            tran.TransactionManager.QueueForGraphicsFlush();
        }
    }
 
}
 
private static Polyline CreateBoundBox(BlockReference blk)
{
    var ext = blk.GeometricExtents;
    var poly = new Polyline(4);
    poly.AddVertexAt(
        0, new Point2d(ext.MinPoint.X, ext.MinPoint.Y),
        0.0, 0.0, 0.0);
    poly.AddVertexAt(
        0, new Point2d(ext.MinPoint.X, ext.MaxPoint.Y),
        0.0, 0.0, 0.0);
    poly.AddVertexAt(
        0, new Point2d(ext.MaxPoint.X, ext.MaxPoint.Y),
        0.0, 0.0, 0.0);
    poly.AddVertexAt(
        0, new Point2d(ext.MaxPoint.X, ext.MinPoint.Y),
        0.0, 0.0, 0.0);
    poly.Closed = true;
    poly.ColorIndex = 2;
    return poly;
}
 
#endregion

The code draws a rectangle as the block's bounding box and draws point at intersecting point of a line and the block reference. As following video shows the intersection point is at the location where the line and the bounding box intersect to each other.


Now here is the code I proposed to the first and/or second question: find actual intersection point of a line to the block's "real" boundary (outmost entity nested in the block).

#region command to find entity's intersecting point: block's real boundary
 
[CommandMethod("BlkIntersect2")]
public static void TestBlockIntersection2()
{
    var dwg = CadApp.DocumentManager.MdiActiveDocument;
    var ed = dwg.Editor;
 
    var res = ed.GetEntity("\nSelect block:");
    if (res.Status == PromptStatus.OK)
    {
        if (res.ObjectId.ObjectClass.DxfName.ToUpper() != "INSERT")
        {
            ed.WriteMessage("\nNot a block!");
            return;
        }
 
        dwg.Database.Pdmode = 34;
 
        try
        {
            CadHelper.Highlight(res.ObjectId, true);
 
            using (var tran = dwg.TransactionManager.StartTransaction())
            {
                var blk = (BlockReference)tran.GetObject(
                    res.ObjectId, OpenMode.ForRead);
                var space = (BlockTableRecord)tran.GetObject(
                    dwg.Database.CurrentSpaceId, OpenMode.ForWrite);
 
                while (true)
                {
                    var opt = new PromptEntityOptions(
                        "\nSelect a line intersecting with the block:");
                    opt.SetRejectMessage("\nInvalid: not a line!");
                    opt.AddAllowedClass(typeof(Line), true);
                    var eRes = ed.GetEntity(opt);
                    if (eRes.Status == PromptStatus.OK)
                    {
                        GetBlockIntersectionPoints(
                            eRes.ObjectId, blk, space, tran);
                    }
                    else
                    {
                        break;
                    }
                }
 
                tran.Commit();
            }
        }
        finally
        {
            CadHelper.Highlight(res.ObjectId, false);
        }
    }
}
 
private static void GetBlockIntersectionPoints(
    ObjectId entId, BlockReference blk, BlockTableRecord space, Transaction tran)
{
    var line = (Line)tran.GetObject(entId, OpenMode.ForRead);
    if (FindOutmostIntersectingPoints(line, blk, out Point3d pt1, out Point3d pt2))
    {
        var dbPt = new DBPoint(pt1);
        space.AppendEntity(dbPt);
        tran.AddNewlyCreatedDBObject(dbPt, true);
        tran.TransactionManager.QueueForGraphicsFlush();
 
        dbPt = new DBPoint(pt2);
        space.AppendEntity(dbPt);
        tran.AddNewlyCreatedDBObject(dbPt, true);
        tran.TransactionManager.QueueForGraphicsFlush();
    }
}
 
private static bool FindOutmostIntersectingPoints(
    Line line, BlockReference blk, out Point3d pt1, out Point3d pt2)
{
    pt1 = Point3d.Origin;
    pt2 = Point3d.Origin;
 
    var points = GetAllIntersectingPoints(line, blk);
    if (points.Count>0)
    {
        pt1 = (from p in points
                orderby p.DistanceTo(line.StartPoint)
                select p).First();
 
        pt2 = (from p in points
                orderby p.DistanceTo(line.EndPoint)
                select p).First();
 
        return true;
    }
    else
    {
        return false;
    }
}
 
private static List<Point3d> GetAllIntersectingPoints(
    Line line, BlockReference blk)
{
    var points = new List<Point3d>();
 
    using (var ents = new DBObjectCollection())
    {
        blk.Explode(ents);
        foreach (DBObject o in ents)
        {
            var ent = (Entity)o;
            var pts = new Point3dCollection();
            line.IntersectWith(
                ent, Intersect.OnBothOperands, pts, IntPtr.Zero, IntPtr.Zero);
            foreach(Point3d p in pts)
            {
                points.Add(p);
            }
            o.Dispose();
        }
    }
 
    return points;
}
 
#endregion

The video below show the code is able to find the 2 intersection points of a line that pass through a block reference, which fall on the block's outmost entity or entities. Obviously this indirectly answers the second question: with these 2 points, the line could ne trimmed easily.


As aforementioned, I limit the code to only apply to Line and block with only Curve type as nested entities. Extra considerations are needed in other cases, such as:

1. If a nested entity in block is a BlockReference, Text/MText/AttributeReference, then the intersection point is on their bounding box. Recursive exploding BlockReference might be needed.

2. If start or end point, or both the intersecting Line/Curve locate inside the block, it might be quite difficult to determine the start/end point is only inside the block's bounding box but outside the outmost entities, or not.

3. If the intersecting entity with block is not a Line, it could intersect with the block more than 2 times.

So writing code to cover all possible cases would be quite some work, if it is possible at all.

Update

As the comment pointed out, I forgot to post the code of Highlight(ObjectId, bool). Here is the code:

public static void Highlight(ObjectId entId, bool highlight)
{
    using (var tran = entId.Database.TransactionManager.StartOpenCloseTransaction())
    {
        var ent = (Entity)tran.GetObject(entId, OpenMode.ForRead);
        if (highlight)
        {
            ent.Highlight();
        }
        else
        {
            ent.Unhighlight();
        }
        tran.Commit();
    }
}

Tuesday, November 10, 2020

A User-Friendly Command To Copy Part of Polyline

 I recently replied a question asked in Autodesk's AutoCAD .NET API user forum, about how to copy part of Polyline. Naturally, I also wrote some quick test code to make sure my reply does make sense. Afterwards, I though why don't I polish the code a bit more to make it a user-friendly command, hence this post.

The operation requires 3 user inputs: a polyline, two points on the polyline. The output is a copy of the portion of this polyline between the 2 points.

We can easily split a Polyline (or a Curve in general) with Curve.GetSplitCurves(Point3dCollection). In our case, we just need to pass the 2 points as Point3DCollection to Curve.GetSplitCurves() method and pick out the portion of this polyline between the 2 points from the returned DBObjectCollection. However, there are a couple of things to be handled carefully:

1. The points in the argument Point3DCollection of GetSplitCurves() method must be in correct order: the points should be sorted according to its distance from the polyline's start point; When user selects 2 points on polyline, he/she can do the selection along different direction of the Polyline, thus the need of sorting.

2. User could select one of the Polyline's end (StartPoint or EndPoint). User could even choose to select the Polyline's StartPoint and EndPoint (i.e. selecting entire polyline), 

Here is a class PatialPolylineSelector that guide user to select a Polyline, then select 2 points on it. Once 2 points are selected, a non-database-residing Polyline (a clone of the portion of the selected Polyline between the 2 points) is returned to calling process. To make the point selection very user-friendly, when user moves mouse cursor for selecting second point, a ghost polyline is drawn as Transient Graphics to show the output portion of the Polyline.

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using CadDb = Autodesk.AutoCAD.DatabaseServices;
 
namespace GetPartialPolyline
{
    public class PartialPolylineSelector
    {
        private readonly Document _dwg;
        private readonly Editor _ed;
 
        private Point3d _firstPoint;
        private ObjectId _polylineId = ObjectId.Null;
 
        private TransientManager _tsManager = 
            TransientManager.CurrentTransientManager;
        private CadDb.Polyline _ghostPolyline = null;
 
        public PartialPolylineSelector(Document dwg)
        {
            _dwg = dwg;
            _ed = dwg.Editor;
        }
 
        public CadDb.Polyline SelectPartialPolyline()
        {
            var polyId = SelectPolyline(out Point3d pickPt1);
            if (polyId.IsNull)
            {
                _ed.WriteMessage("\n*Cancel*");
                return null;
            }
 
            if (!SelectTwoPointsOnPolyline(
                polyId, pickPt1, 
                out Point3d startPt, out Point3d endPt))
            {
                _ed.WriteMessage("\n*Cancel*");
                return null;
            }
 
            var poly = GeneratePartialPolyline(polyId, startPt, endPt);
            return poly;
        }
 
        #region private methods
 
        private ObjectId SelectPolyline(out Point3d pickPoint)
        {
            pickPoint = Point3d.Origin;
            var opt = new PromptEntityOptions(
                "\nSelect a polyline:");
            opt.SetRejectMessage("\nInvalid: not a polyline!");
            opt.AddAllowedClass(typeof(CadDb.Polyline), true);
 
            var res = _ed.GetEntity(opt);
            if (res.Status == PromptStatus.OK)
            {
                using (var tran = 
                    res.ObjectId.Database.TransactionManager.StartTransaction())
                {
                    var poly = (CadDb.Polyline)tran.GetObject(
                        res.ObjectId, OpenMode.ForRead);
                    //make sure the output point is on the polyline
                    pickPoint = poly.GetClosestPointTo(res.PickedPoint, false);
                    tran.Commit();
                }
 
                return res.ObjectId;
            }
            else
            {
                return ObjectId.Null;
            }
        }
 
        private bool SelectTwoPointsOnPolyline(
            ObjectId polyId, Point3d prevPoint,
            out Point3d startPt, out Point3d endPt)
        {
            startPt = Point3d.Origin;
            endPt = Point3d.Origin;
 
            _firstPoint = prevPoint;
            var nextPoint = Point3d.Origin;
            _polylineId = polyId;
 
            bool ok = false;
            using (var tran = _dwg.TransactionManager.StartTransaction())
            {
                var poly = (CadDb.Polyline)tran.GetObject(polyId, OpenMode.ForRead);
 
                while (true)
                {
                    ClearGhostPolyline();
                    var opt = new PromptPointOptions(
                        "\nSelect second point on the polyline:")
                    {
                        AppendKeywordsToMessage = true,
                        AllowNone = true
                    };
                    opt.Keywords.Add("First point");
                    opt.Keywords.Default = "First point";
 
                    PromptPointResult res;
                    try
                    {
                        // when selecting another point on polyline
                        // show the part of polyline to be cloned
                        // as Transient Graphics
                        _ed.PointMonitor += Editor_PointMonitor;
 
                        res = _ed.GetPoint(opt);
                    }
                    finally
                    {
                        _ed.PointMonitor -= Editor_PointMonitor;
                        ClearGhostPolyline();
                    }
 
                    if (res.Status == PromptStatus.OK)
                    {
                        nextPoint = poly.GetClosestPointTo(res.Value, false);
                        ok = true;
                        break;
                    }
                    else if (res.Status == PromptStatus.Keyword)
                    {
                        // re-select the first point on polyline
                        var cancel = false;
                        while (true)
                        {
                            var op = new PromptPointOptions(
                                "\nSelect first point on polyline:");
                            var rs = _ed.GetPoint(op);
                            if (rs.Status == PromptStatus.OK)
                            {
                                _firstPoint = poly.GetClosestPointTo(rs.Value, false);
                                break;
                            }
                            else
                            {
                                cancel = true;
                                break;
                            }
                        }
 
                        if (cancel)
                        {
                            ok = false;
                            break;
                        }
                    }
                    else
                    {
                        ok = false;
                        break;
                    }
                }
 
                if (ok)
                {
                    SortPickedPoints(
                        poly, _firstPoint, nextPoint, out startPt, out endPt);
                }
 
                tran.Commit();
            }
 
            return ok;
        }
 
        private void SortPickedPoints(
            CadDb.Polyline poly, Point3d picked1, Point3d picked2,
            out Point3d startPt, out Point3d endPt)
        {
            var dist1 = poly.GetDistAtPoint(picked1);
            var dist2 = poly.GetDistAtPoint(picked2);
            if (dist1 < dist2)
            {
                startPt = picked1;
                endPt = picked2;
            }
            else
            {
                startPt = picked2;
                endPt = picked1;
            }
        }
 
        private CadDb.Polyline GeneratePartialPolyline(
            ObjectId polylineId, Point3d startPt, Point3d endPt)
        {
            CadDb.Polyline poly = null;
            using (var tran = _dwg.TransactionManager.StartTransaction())
            {
                var pline = (CadDb.Polyline)tran.GetObject(polylineId, OpenMode.ForRead);
 
                Point3dCollection points = new Point3dCollection();
                if (startPt.IsEqualTo(pline.StartPoint) &&
                    endPt.IsEqualTo(pline.EndPoint))
                {
                    poly = pline.Clone() as CadDb.Polyline;
                }
                else
                {
                    if (startPt.IsEqualTo(pline.StartPoint))
                    {
                        points.Add(endPt);
 
                        var dbObjects = pline.GetSplitCurves(points);
                        if (dbObjects.Count == 2)
                        {
                            poly = dbObjects[0] as CadDb.Polyline;
                            dbObjects[1].Dispose();
                        }
                        else
                        {
                            foreach (DBObject obj in dbObjects)
                            {
                                obj.Dispose();
                            }
                        }
                    }
                    else if (endPt.IsEqualTo(pline.EndPoint))
                    {
                        points.Add(startPt);
 
                        var dbObjects = pline.GetSplitCurves(points);
                        if (dbObjects.Count == 2)
                        {
                            poly = dbObjects[1] as CadDb.Polyline;
                            dbObjects[0].Dispose();
                        }
                        else
                        {
                            foreach (DBObject obj in dbObjects)
                            {
                                obj.Dispose();
                            }
                        }
                    }
                    else
                    {
                        points.Add(startPt);
                        points.Add(endPt);
 
                        var dbObjects = pline.GetSplitCurves(points);
                        if (dbObjects.Count == 3)
                        {
                            poly = dbObjects[1] as CadDb.Polyline;
                            dbObjects[0].Dispose();
                            dbObjects[2].Dispose();
                        }
                        else
                        {
                            foreach (DBObject obj in dbObjects)
                            {
                                obj.Dispose();
                            }
                        }
                    }
                }
 
                tran.Commit();
            }
 
            return poly;
        }
 
        private void Editor_PointMonitor(object sender, PointMonitorEventArgs e)
        {
            ClearGhostPolyline();
 
            var poly = _polylineId.GetObject(OpenMode.ForRead) as CadDb.Polyline;
            var nextPoint = poly.GetClosestPointTo(e.Context.RawPoint, false);
            var dist = nextPoint.DistanceTo(e.Context.RawPoint);
            if (dist<poly.Length/10.0)
            {
                SortPickedPoints(
                    poly, _firstPoint, nextPoint,
                    out Point3d startPt, out Point3d endPt);
                var ghost = GeneratePartialPolyline(_polylineId, startPt, endPt);
                if (ghost!=null)
                {
                    _ghostPolyline = ghost;
                    _ghostPolyline.ColorIndex = 1;
                    _tsManager.AddTransient(
                        _ghostPolyline, 
                        TransientDrawingMode.DirectTopmost, 
                        128, 
                        new IntegerCollection());
                }
 
                e.AppendToolTipText(
                    $"Selected polyline length = {_ghostPolyline.Length}");
            }
        }
 
        private void ClearGhostPolyline()
        {
            if (_ghostPolyline!=null)
            {
                _tsManager.EraseTransient(
                    _ghostPolyline, new IntegerCollection());
                _ghostPolyline.Dispose();
            }
        }
 
        #endregion
    }
}


This the CommandClass to run the process:

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assemblyCommandClass(typeof(GetPartialPolyline.MyCommands))]
 
namespace GetPartialPolyline
{
    public class MyCommands
    {
        [CommandMethod("PartialPoly")]
        public static void CreatePartialPolyline()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            try
            {
                var selector = new PartialPolylineSelector(dwg);
                var poly = selector.SelectPartialPolyline();
                if (poly!=null)
                {
                    poly.ColorIndex = 2;
                    AddPolylineToDb(dwg.Database, poly);
                }
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage($"\nExecution error:\n{ex.Message}\n");
            }
        }
 
        private static void AddPolylineToDb(Database db, Polyline poly)
        {
            using (var tran = db.TransactionManager.StartTransaction())
            {
                var space = (BlockTableRecord)tran.GetObject(
                    db.CurrentSpaceId, OpenMode.ForWrite);
                space.AppendEntity(poly);
                tran.AddNewlyCreatedDBObject(poly, true);
                tran.Commit();
            }
 
        }
    }
}

This the video clip showing the command execution effect: