Wednesday, June 21, 2023

Auto-Rotating Annotating Entities in Viewport with DrawableOverrule

When annotating entities (Text, MText, or Block with Attributes) are used in ModelSpace as if they are labels, they often are made horizonal in terms of their textual presentation (or all with certain rotation, for that matter). However, CAD users would face an annoying issue, if the drawing contents have to be presented in various layout viewports, which may have different twist angles, depending how the drawing contents in the ModelSpace is to be presented in the viewport. CAD users may have to spent time to rotate the annotating entities that visible in a viewport, so that they appear in the viewport as horizontal (or vertical). 

In the past, I had written custom command at my work to make label-like entities being rotated to horizontal/vertical based on which viewport they appeared. While this worked OK in many cases, but it would obviously not work well, if an annotating entity was visible in multiple viewports, which may have different twist angle.

AutoCAD Civil 3D users know that C3D labels automatically appear horizontally in layout viewports, regardless their twist angles. So, can we achieve the same or similar effect with plain AutoCAD (e.g. making annotating entities - DBText, MText, BlockReference with Attributes - to appear always horizontally in layout viewports)? The answer is "yes". This article demonstrates how to use DrawableOrverule to do it.

A custom DrawableOverrule overrides either its WorldDraw(), or ViewportDraw(), or both, to make the target entity visually different from its built-in appearance. In most cases overridden WorldDraw() is used. For example, we can make a curve (Line, Polyline...) looks like a 3D pipe, a DBPoint looks like a ball. Because of the topic of this article, while the main focus is to override ViewportDraw() method, so that the annotating entities would be appeared horizontally in layout viewports, the WorldDraw() must(!) also be overridden (I placed comments in the code). The code is following.

Class LabelDrawableOverrule:

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using Autodesk.AutoCAD.Runtime;
using CadDb = Autodesk.AutoCAD.DatabaseServices;
 
namespace LabelTextDrawableOverrule
{
    public class LabelDrawableOverrule : DrawableOverrule
    {
        private const string BLOCK_NAME = "TESTLABEL";
        private static LabelDrawableOverrule _instance;
        private bool _isEnabled = false;
        private bool _originalOverruling = false;
 
        #region public properties
        public static LabelDrawableOverrule Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new LabelDrawableOverrule();
                }
                return _instance;
            }
        }
 
        public bool IsEnabled=>_isEnabled;
 
        #endregion
 
        public void RunOverrule()
        {
            if (!_isEnabled)
            {
                EnableOverrule();
            }
            else
            {
                DisableOverrule();
            }
            Application.DocumentManager.MdiActiveDocument.Editor.Regen();
        }
 
        public override bool IsApplicable(RXObject overruledSubject)
        {
            var blk = overruledSubject as BlockReference;
            if (blk!=null && blk.Name.ToUpper()==BLOCK_NAME)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
 
        /// <summary>
        /// It is important to override WorldDraw() method and
        /// bypass calling base.WorldDraw() and return false
        /// </summary>
        /// <param name="drawable"></param>
        /// <param name="wd"></param>
        /// <returns></returns>
        public override bool WorldDraw(Drawable drawable, WorldDraw wd)
        {
            //base.WorldDraw(drawable, wd);
            return false;
        }
 
        public override void ViewportDraw(Drawable drawable, ViewportDraw vd)
        {
            var ent = drawable as BlockReference; // because this Overrule targets BlockReference
            if (ent == nullreturn;
 
            if (vd.ViewportObjectId.IsNull)
            {
                // Rotating overruled entity in ModelSpace to horizontal
                if (ent.Rotation != 0.0)
                {
                    using (var clone = ent.Clone() as BlockReference)
                    {
                        clone.TransformBy(
                            Matrix3d.Rotation(-ent.Rotation, Vector3d.ZAxis, ent.Position));
                        base.ViewportDraw(clone, vd);
                        return;
                    }
                }
            }
            else
            {
                if (IsLabelBlockVisibleFromViewport(vd.ViewportObjectId, ent, out double tweestAngle))
                {
                    using (var clone = ent.Clone() as BlockReference)
                    {
                        clone.TransformBy(Matrix3d.Rotation(
                            -(tweestAngle + ent.Rotation), Vector3d.ZAxis, ent.Position));
                        base.ViewportDraw(clone, vd);
                        return;
                    }
                }
            }
 
            base.ViewportDraw(drawable, vd);
        }
 
        #region private methods
 
        private void EnableOverrule()
        {
            _originalOverruling = Overrule.Overruling;
            AddOverrule(RXClass.GetClass(typeof(BlockReference)), thisfalse);
            SetCustomFilter();
            _isEnabled = true;
        }
 
        private void DisableOverrule()
        {
            RemoveOverrule(RXClass.GetClass(typeof(BlockReference)), this);
            _isEnabled = false;
            Overrule.Overruling=_originalOverruling;
        }
 
        // for simplicity, I consider the label block is visible in the viewport
        // if its insertion point is inside the viewport boundary projected to ModelSpace
        private bool IsLabelBlockVisibleFromViewport(
            ObjectId vportId, BlockReference labelBlockout double angle)
        {
            angle = 0.0;
            var isInside = false;
 
            using (var tran=vportId.Database.TransactionManager.StartOpenCloseTransaction())
            {
                var vport = (CadDb.Viewport)tran.GetObject(vportId, OpenMode.ForRead);
                angle = vport.TwistAngle;
 
                using (var vportBoundaryInModel = GetViewportBoundaryInModelSpace(vport, tran))
                {
                    isInside =
                        CadHelper.IsPointInside(labelBlock.Position, vportBoundaryInModel);
                }
 
                tran.Commit();
            }
 
            return isInside;
        }
 
        private CadDb.Polyline GetViewportBoundaryInModelSpace(
            CadDb.Viewport vport, Transaction tran)
        {
            CadDb.Polyline poly;
            var mt = CadHelper.PaperToModel(vport);
 
            if (!vport.NonRectClipEntityId.IsNull && vport.NonRectClipOn)
            {
                var clipPoly = (CadDb.Polyline)tran.GetObject(
                    vport.NonRectClipEntityId, OpenMode.ForRead);
                poly = clipPoly.Clone() as CadDb.Polyline;
            }
            else
            {
                poly = GetViewportBoundary(vport, mt);
            }
 
            return poly;
        }
 
        private CadDb.Polyline GetViewportBoundary(
            CadDb.Viewport vport, Matrix3d paperToModelTransform)
        {
            CadDb.Polyline poly = new CadDb.Polyline();
    
            Extents3d ext = vport.GeometricExtents;
            Point3d pt;
 
            pt=(new Point3d(ext.MinPoint.X, ext.MinPoint.Y, 0.0)).
                TransformBy(paperToModelTransform);
            poly.AddVertexAt(0, new Point2d(pt.X, pt.Y), 0.0, 0.0, 0.0);
 
            pt = (new Point3d(ext.MinPoint.X, ext.MaxPoint.Y, 0.0)).
                TransformBy(paperToModelTransform);
            poly.AddVertexAt(1, new Point2d(pt.X, pt.Y), 0.0, 0.0, 0.0);
 
            pt = (new Point3d(ext.MaxPoint.X, ext.MaxPoint.Y, 0.0)).
                TransformBy(paperToModelTransform);
            poly.AddVertexAt(2, new Point2d(pt.X, pt.Y), 0.0, 0.0, 0.0);
 
            pt = (new Point3d(ext.MaxPoint.X, ext.MinPoint.Y, 0.0)).
                TransformBy(paperToModelTransform);
            poly.AddVertexAt(3, new Point2d(pt.X, pt.Y), 0.0, 0.0, 0.0);
 
            poly.Closed = true;
            return poly;
        }
        
        private CadDb.Polyline GetNonRectClipBoundary(
            CadDb.Polyline polyline, Matrix3d paperToModelTransform)
        {
            Point3dCollection points = new Point3dCollection();
            for (int i = 0; i<polyline.NumberOfVertices; i++)
            {   
                points.Add(polyline.GetPoint3dAt(i).TransformBy(paperToModelTransform));
            }
 
            var poly = new CadDb.Polyline();
            var n = 0;
            foreach (Point3d p in points)
            {
                poly.AddVertexAt(n, new Point2d(p.X, p.Y), 0.0, 0.0, 0.0);
                n++;
            }
            poly.Closed = true;
            return poly;
        }
 
        #endregion
    }
}

Class CadHelper, in which I referenced AcMPolygonMgd.dll for testing if a point is inside of closed polyline:

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using System;
 
namespace LabelTextDrawableOverrule
{
    public class CadHelper
    {
 
        #region
        //**********************************************************************
        //Create coordinate transform matrix
        //between modelspace and paperspace viewport
        //The code is borrowed from
        //http://www.theswamp.org/index.php?topic=34590.msg398539#msg398539
        //*********************************************************************
        public static Matrix3d PaperToModel(Viewport vp)
        {
            Matrix3d mx = ModelToPaper(vp);
            return mx.Inverse();
        }
   
        public static Matrix3d ModelToPaper(Viewport vp)
        {
            Vector3d vd = vp.ViewDirection;
            Point3d vc = new Point3d(vp.ViewCenter.X, vp.ViewCenter.Y, 0);
            Point3d vt = vp.ViewTarget;
            Point3d cp = vp.CenterPoint;
            double ta = -vp.TwistAngle;
            double vh = vp.ViewHeight;
            double height = vp.Height;
            double width = vp.Width;
            double scale = vh / height;
            double lensLength = vp.LensLength;
            Vector3d zaxis = vd.GetNormal();
            Vector3d xaxis = Vector3d.ZAxis.CrossProduct(vd);
            Vector3d yaxis;
   
            if (!xaxis.IsZeroLength())
            {
                xaxis = xaxis.GetNormal();
                yaxis = zaxis.CrossProduct(xaxis);
            }
            else if (zaxis.Z < 0)
            {
                xaxis = Vector3d.XAxis * -1;
                yaxis = Vector3d.YAxis;
                zaxis = Vector3d.ZAxis * -1;
            }
            else
            {
                xaxis = Vector3d.XAxis;
                yaxis = Vector3d.YAxis;
                zaxis = Vector3d.ZAxis;
            }
            Matrix3d pcsToDCS = Matrix3d.Displacement(Point3d.Origin - cp);
            pcsToDCS = pcsToDCS * Matrix3d.Scaling(scale, cp);
            Matrix3d dcsToWcs = Matrix3d.Displacement(vc - Point3d.Origin);
            Matrix3d mxCoords = Matrix3d.AlignCoordinateSystem(
                Point3d.Origin, Vector3d.XAxis, Vector3d.YAxis,
                Vector3d.ZAxis, Point3d.Origin,
                xaxis, yaxis, zaxis);
            dcsToWcs = mxCoords * dcsToWcs;
            dcsToWcs = Matrix3d.Displacement(vt - Point3d.Origin) * dcsToWcs;
            dcsToWcs = Matrix3d.Rotation(ta, zaxis, vt) * dcsToWcs;
 
            Matrix3d perspectiveMx = Matrix3d.Identity;
            if (vp.PerspectiveOn)
            {
                double vSize = vh;
                double aspectRatio = width / height;
                double adjustFactor = 1.0 / 42.0;
                double adjstLenLgth = vSize * lensLength *
                    Math.Sqrt(1.0 + aspectRatio * aspectRatio) * adjustFactor;
                double iDist = vd.Length;
                double lensDist = iDist - adjstLenLgth;
                double[] dataAry = new double[]
                {
                    1,0,0,0,0,1,0,0,0,0,
                    (adjstLenLgth - lensDist) / adjstLenLgth,
                    lensDist * (iDist - adjstLenLgth) / adjstLenLgth,
                    0,0,-1.0 / adjstLenLgth,iDist / adjstLenLgth
                };
    
                perspectiveMx = new Matrix3d(dataAry);
            }
 
            Matrix3d finalMx =
                pcsToDCS.Inverse() *perspectiveMx * dcsToWcs.Inverse();
 
            return finalMx;
        }
 
        public static bool IsPointInside(Point3d point, Polyline boundarybool onEdgeAsInside=true)
        {
 
            using (MPolygon mPolygon = MakeMPolygon(boundary))
            {
                for (int i = 0; i < mPolygon.NumMPolygonLoops; i++)
                {
                    if (mPolygon.IsPointOnLoopBoundary(point, i, Tolerance.Global.EqualPoint))
                    {
                        if (onEdgeAsInside) return true;
                    }
                }
 
                if (mPolygon.IsPointInsideMPolygon(point, Tolerance.Global.EqualPoint).Count == 1)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }
 
        #endregion
 
        #region private methods
 
        private static MPolygon MakeMPolygon(Polyline pline)
        {
            MPolygon mPolygon = new MPolygon();
            mPolygon.AppendLoopFromBoundary(pline, false, Tolerance.Global.EqualPoint);
            mPolygon.Elevation = pline.Elevation;
            mPolygon.Normal = pline.Normal;
            return mPolygon;
        }
 
        #endregion
    }
}

And finally, the CommandClass:

using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assembly: CommandClass(typeof(LabelTextDrawableOverrule.MyCommands))]
 
namespace LabelTextDrawableOverrule
{
    public class MyCommands
    {
        [CommandMethod("LabelOverrule")]
        public static void RunLabelOverrule()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            LabelDrawableOverrule.Instance.RunOverrule();
            var msg = LabelDrawableOverrule.Instance.IsEnabled ?
                "LabelDrawableOverrule is enabled." : 
                "LabelDrawableOverrule is disabled.";
            ed.WriteMessage($"\n{msg}\n");
        }
    }
}

Points of interest:

  1. The ViewportDraw argument of the ViewportDraw() method provided ObjectId of the layout viewport, so that we can use to find out the Viewport.TwistAngle and also find out if the overruled entity is visible in the viewport. In my code here, for simplicity, I assumed the label block entity is visible in the viewport if its insertion point is inside of the viewport boundary projected in the ModelSpace.
  2. We'll need to make sure WorldDraw() method is overridden and do not call base.WorldDraw() inside. We also must let it return false, so that the ViewportDraw() method is called by the Overrule.
  3. I used a copy of the overruled entity and rotated it as needed and pass it to base.ViewportDraw() method. That is, the overruled entity itself remained unchanged (e.g. the block's Rotation property was unchanged), only its image in the viewport got rotated (to be horizonal). I could have rotated the overruled entity, but if the entities is visible in multiple viewports with different twist angles, it would end up with constant changing rotations.
See the video clip below showing the code running result:



Tuesday, March 21, 2023

Using Custom Jig To Transform A Group of Entities - Again

 In my previous post I used Transient graphics in conjunction with Editor.PointMonitor handling to create a "DrawJig" for transforming a group of entities and mentioned that I might post code for a custom jig that is derived from DrawJig class. 

In the meantime, the original question poster also asked question again, regarding my previous post, and wanted to know how to do change the connected polyline when one of its vertex is at the block's insertion point. While it is slightly out of the topic of the discussion (using Jig to change/transform a group of entities), it is really not that difficult to solve, whether the polyline's start/end point, vertex, or event anywhere, is at the block's insertion point.

Now that I now write another solution to the original question, by using custom DrawJig derived custom Jig, I decide to show the code in action with a polyline connected to the block's insertion point in different way: start/end point connected, a vertex point connected, or any point along the polyline connected. Also, for simplicity, I only do it with Polyline with straight segments.

Again, the code is rather easy to read/follow. So, no need to extra explanation, I think.

The class MyBlockDrawJig:

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using System;
using System.Collections.Generic;
using CadDb = Autodesk.AutoCAD.DatabaseServices;
 
namespace CurveConnectedBlockJig
{
    public class MyBlockDrawJig : DrawJig, IDisposable
    {
        private class EndConnectedPolyline
        {
            public CadDb.Polyline Polyline { getset; }
            public bool AtStart { getset; }
        }
 
        private class MidConnectedPolyline
        {
            public CadDb.Polyline Polyline { getset; }
            public (int index, Point3d vertex) VertexBefore { getset; }
            public (int index, Point3d vertex) VertexAfter { getset; }
        }
 
        private readonly ObjectId _blkId;
        private readonly Document _dwg;
        private readonly Database _db;
        private readonly Editor _ed;
 
        private Transaction _tran = null;
        private Point3d _basePoint;
        private Point3d _prevPoint;
        private Point3d _currPoint;
 
        private BlockReference _block = null;
        private List<EndConnectedPolyline> _endConnectedPolys = new List<EndConnectedPolyline>();
        private List<MidConnectedPolyline> _midConnectedPolys=new List<MidConnectedPolyline>();
 
        private int _addedVertexIndex = -1;
 
        public MyBlockDrawJig(Document dwg, ObjectId blkId)
        {
            _blkId = blkId;
            _dwg = dwg;
            _db = dwg.Database;
            _ed = dwg.Editor;
        }
 
        public void Move()
        {
            _tran = _db.TransactionManager.StartTransaction();
            _block = (BlockReference)_tran.GetObject(_blkId, OpenMode.ForWrite);
            _block.Highlight();
 
            FindConnectedPolylines();
            _basePoint = _block.Position;
            _prevPoint = _block.Position;
            _currPoint = _block.Position;
 
            PromptStatus status = PromptStatus.Cancel;
            try
            {
                var res = _ed.Drag(this);
                status = res.Status;
            }
            finally
            {
                _block.Unhighlight();
                foreach (var item in _endConnectedPolys)
                {
                    item.Polyline.Unhighlight();
                }
                foreach (var item in _midConnectedPolys)
                {
                    item.Polyline.Unhighlight();
                }
 
                if (status == PromptStatus.OK)
                {
                    _tran.Commit();
                }
                else
                {
                    _tran.Abort();
                }
            }
        }
 
        public void Dispose()
        {
            if (_tran != null)
            {
                _tran.Dispose();
            }
        }
 
        #region DrawJig implementing
 
        protected override SamplerStatus Sampler(JigPrompts prompts)
        {
            var opt = new JigPromptPointOptions(
                "\nSelect position to move:");
 
            opt.UseBasePoint = true;
            opt.BasePoint = _basePoint;
            opt.Cursor = CursorType.RubberBand;
 
            var res = prompts.AcquirePoint(opt);
            if (res.Status == PromptStatus.OK)
            {
                if (res.Value!=_currPoint)
                {
                    _block.TransformBy(
                        Matrix3d.Displacement(_prevPoint.GetVectorTo(_currPoint)));
 
                    TransformEndConnected();
                    TransformMidConnected();
 
                    _prevPoint = _currPoint;
                    _currPoint = res.Value;
                    return SamplerStatus.OK;
                }
                else
                {
                    return SamplerStatus.NoChange;
                }
            }
            else
            {
                return SamplerStatus.Cancel;
            }
        }
 
        protected override bool WorldDraw(WorldDraw draw)
        {
            draw.Geometry.Draw(_block);
            foreach (var item in _endConnectedPolys)
            {
                draw.Geometry.Draw(item.Polyline);
            }
            foreach (var item in _midConnectedPolys)
            {
                draw.Geometry.Draw(item.Polyline);
            }
            return true;
        }
 
        #endregion
 
        #region private methods: collect jigging targets
 
        private void FindConnectedPolylines()
        {
            var model = (BlockTableRecord)_tran.GetObject(
                SymbolUtilityServices.GetBlockModelSpaceId(_db), OpenMode.ForRead);
            foreach (ObjectId id in model)
            {
                if (id.ObjectClass.DxfName.ToUpper() != "LWPOLYLINE"continue;
 
                var poly=(CadDb.Polyline)_tran.GetObject(id, OpenMode.ForRead);
 
                var endConnected = IsEndConnected(poly);
                if (endConnected !=null)
                {
                    _endConnectedPolys.Add(endConnected);
                }
                else
                {
                    var midConnected = IsMidConnected(poly);
                    if (midConnected != null)
                    {
                        _midConnectedPolys.Add(midConnected);
                    }
                }
            }
        }
 
        private EndConnectedPolyline IsEndConnected(CadDb.Polyline poly)
        {
            EndConnectedPolyline endConnected = null;
            if (poly.StartPoint.Equals(_block.Position))
            {
                poly.UpgradeOpen();
                endConnected = new EndConnectedPolyline()
                {
                    Polyline = poly,
                    AtStart = true
                };
            }
            else if (poly.EndPoint.Equals(_block.Position))
            {
                poly.UpgradeOpen();
                endConnected = new EndConnectedPolyline()
                {
                    Polyline = poly,
                    AtStart = false
                };
            }
 
            if (endConnected!=null) endConnected.Polyline.Highlight();
            
            return endConnected;
        }
 
        private MidConnectedPolyline IsMidConnected(CadDb.Polyline poly)
        {
            MidConnectedPolyline midConnected = null;
 
            var geCurve = poly.GetGeCurve();
            if (geCurve.IsOn(_block.Position))
            {
                poly.UpgradeOpen();
                midConnected = new MidConnectedPolyline()
                {
                    Polyline = poly
                };
 
                var dist=poly.GetDistAtPoint(_block.Position);
                
                for (int i=1; i<poly.NumberOfVertices; i++)
                {
                    var pt = poly.GetPoint3dAt(i);
                    var l = poly.GetDistAtPoint(pt);
 
                    if (l >= dist)
                    {
                        midConnected.VertexBefore = (i - 1, poly.GetPoint3dAt(i - 1));
                        var diff = Math.Abs(dist - l);
                        if (diff <= Tolerance.Global.EqualPoint)
                        {
                            midConnected.VertexAfter = (i + 1, poly.GetPoint3dAt(i + 1));
                        }
                        else
                        {
                            midConnected.VertexAfter = (i, pt);
                        }
                        break;
                    }
                }
            }
 
            if (midConnected!=null) midConnected.Polyline.Highlight();
            return midConnected;
        }
 
        #endregion
 
        #region private methods: transform jigged entities
 
        private void TransformEndConnected()
        {
            foreach (var item in _endConnectedPolys)
            {
                if (item.AtStart)
                {
                    item.Polyline.SetPointAt(0, new Point2d(_currPoint.X, _currPoint.Y));
                }
                else
                {
                    item.Polyline.SetPointAt(
                        item.Polyline.NumberOfVertices - 1, 
                        new Point2d(_currPoint.X, _currPoint.Y));
                }
            }
        }
 
        private void TransformMidConnected()
        {
            foreach (var item in _midConnectedPolys)
            {
                if (item.VertexAfter.index-item.VertexBefore.index==1)
                {
                    if (_addedVertexIndex<0)
                    {
                        // insert a new vertex on the straight segment
                        _addedVertexIndex = item.VertexAfter.index;
                        item.Polyline.AddVertexAt(
                            _addedVertexIndex, 
                            new Point2d(_currPoint.X, _currPoint.Y), 0.0, 0.0, 0.0);
                    }
                    item.Polyline.SetPointAt(
                        _addedVertexIndex, new Point2d(_currPoint.X, _currPoint.Y));
                }
                else
                {
                    var index = item.VertexAfter.index - 1;
                    item.Polyline.SetPointAt(
                        index, new Point2d(_currPoint.X, _currPoint.Y));
                }
            }
        }
 
        #endregion
    }
}


The CommandMethod is almost the same as before, except for instantiating a different jig class:

[CommandMethod("MoveBlk2")]
public static void RunBlockDrawJig()
{
    var dwg = CadApp.DocumentManager.MdiActiveDocument;
    var ed = dwg.Editor;
 
    var opt = new PromptEntityOptions(
        "\nSelect curve-connected block:");
    opt.SetRejectMessage("Invalid: must be a block:");
    opt.AddAllowedClass(typeof(BlockReference), true);
 
    var res = ed.GetEntity(opt);
    if (res.Status == PromptStatus.OK)
    {
        using (var jig = new MyBlockDrawJig(dwg, res.ObjectId))
        {
            jig.Move();
        }
    }
 
    ed.WriteMessage("\n");
}

See the video clip below:



Monday, March 20, 2023

Using Custom Jig To Transform A Group of Entities

This post is in response to a question asked in Autodesk's AutoCAD .NET API discussion forum. While in my reply I suggested it can be done easily by either creating a DrawJig or doing a jig-like code with the combination of Editor.PointMonitor and Transient graphics, I went ahead to write code with the latter approach. 

The code is rather straightforward and easy to follow, assuming there is a block reference with one or more curves (Line or Polyline) connecting to it (e.g. one of the end point of the Line/Polyline) at at the block's insertion point). The code is as following.

The class MyBlockJig:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using System;
using System.Collections.Generic;
using CadDb = Autodesk.AutoCAD.DatabaseServices;
 
namespace CurveConnectedBlockJig
{
 
    public class MyBlockJig : IDisposable
    {
        private class ConnectedCurve
        {
            public ObjectId ConnectedCurveId { getset; }
            public Drawable Ghost { getset; }
            public bool AtStartPoint { getset; }
        }
 
        private readonly Document _dwg;
        private readonly Database _db;
        private readonly Editor _ed;
        private readonly ObjectId _blkId;
        private readonly TransientManager _tsMng = TransientManager.CurrentTransientManager;
 
        private List<ConnectedCurve> _connectedCurves = new List<ConnectedCurve>();
        private BlockReference _blkGhost = null;
 
        private Point3d _prevPoint;
        private Point3d _currPoint;
 
        public MyBlockJig(Document dwg, ObjectId blkId)
        {
            if (blkId.ObjectClass.DxfName.ToUpper()!="INSERT")
            {
                throw new InvalidOperationException(
                    "Not a block reference!");
            }
            _blkId = blkId;
 
            _dwg=dwg;
            _db = _dwg.Database;
            _ed= _dwg.Editor;
        }
 
        public void Move()
        {
            FindConnectedCurves();
            _prevPoint = _blkGhost.Position;
            _currPoint = _blkGhost.Position;
            try
            {
                AddGhosts();
                _ed.PointMonitor += Editor_PointMonitor;
                var res = _ed.GetPoint("\nMove block to: ");
                if (res.Status == PromptStatus.OK)
                {
                    UpdateEntities(res.Value);
                }
            }
            finally
            {
 
                _ed.PointMonitor -= Editor_PointMonitor;
            }
        }
 
        public void Dispose()
        {
            ClearGhosts();
        }
 
        #region private methods
 
        // Assume any Line/Polyline with one of its end point is at the block's insertion point
        // is considered connected to the block reference, thus the end segment of the 
        // curve would move with the block reference during the drag; Also, if the connected curve is
        // Polyline, assume the connected segment is straight segment
        private void FindConnectedCurves()
        {
            using (var tran = _db.TransactionManager.StartTransaction())
            {
                var model = (BlockTableRecord)tran.GetObject(
                    SymbolUtilityServices.GetBlockModelSpaceId(_db), OpenMode.ForRead);
 
 
                var blkRef = (BlockReference)tran.GetObject(_blkId, OpenMode.ForRead);
                _blkGhost = blkRef.Clone() as BlockReference;
                var position = blkRef.Position;
                foreach (ObjectId id in model)
                {
                    var curve = tran.GetObject(id, OpenMode.ForRead) as Curve;
                    if (curve == nullcontinue;
                    if (!(curve is Line) && !(curve is CadDb.Polyline)) continue;
 
                    if (IsConnected(position, curve.StartPoint, curve.EndPoint))
                    {
                        var connectedCurve = GetConnectedCurve(curve, position);
                        _connectedCurves.Add(connectedCurve);
                    }
                }
 
                tran.Commit();
            }
        }
 
        private bool IsConnected(Point3d position, Point3d point1, Point3d point2)
        {
            var dist = position.DistanceTo(point1);
            if (dist <= Tolerance.Global.EqualPoint) return true;
 
            dist = position.DistanceTo(point2);
            if (dist <= Tolerance.Global.EqualPoint) return true;
 
            return false;
        }
 
        private ConnectedCurve GetConnectedCurve(Curve curve, Point3d position)
        {
            var connected=new ConnectedCurve() { ConnectedCurveId = curve.Id };
 
            Drawable drawable = null;
            Line line = null;
            Point3d pt;
            bool atStart;
                
            if (position.Equals(curve.StartPoint))
            {
                if (curve is Line)
                {
                    pt = ((Line)curve).EndPoint;
                }
                else
                {
                    pt = ((CadDb.Polyline)curve).GetPoint3dAt(1);
                }
                atStart = true;
            }
            else
            {
                if (curve is Line)
                {
                    pt = ((Line)curve).StartPoint;
                }
                else
                {
                    var lastIndex = ((CadDb.Polyline)curve).NumberOfVertices - 1;
                    pt = ((CadDb.Polyline)curve).GetPoint3dAt(lastIndex - 1);
                }
                atStart = false;
            }
 
            line = new Line(position, pt);
            line.ColorIndex = 2;
            drawable = line;
 
            connected.Ghost = drawable;
            connected.AtStartPoint = atStart;
            return connected;
        }
 
        private void Editor_PointMonitor(object sender, PointMonitorEventArgs e)
        {
            var point = e.Context.RawPoint;
            if (point.Equals(_currPoint)) return;
 
            UpdateGhosts(point);
        }
 
        private void AddGhosts()
        {
            _tsMng.AddTransient(_blkGhost, TransientDrawingMode.Highlight, 128, new IntegerCollection());
            if (_connectedCurves.Count > 0 )
            {
                foreach (var connected in _connectedCurves)
                {
                    _tsMng.AddTransient(connected.Ghost, TransientDrawingMode.Highlight, 128, new IntegerCollection());
                }
            }
        }
 
        private void UpdateGhosts(Point3d point)
        {
            _prevPoint = _currPoint;
            _currPoint = point;
 
            _blkGhost.TransformBy(Matrix3d.Displacement(_prevPoint.GetVectorTo(_currPoint)));
            _tsMng.UpdateTransient(_blkGhost, new IntegerCollection());
 
            if ( _connectedCurves.Count > 0 )
            {
                foreach (var connected in _connectedCurves)
                {
                    ((Line)connected.Ghost).StartPoint=point;
                    _tsMng.UpdateTransient(connected.Ghost, new IntegerCollection());
                }
            }
        }
 
        private void ClearGhosts()
        {
            if (_blkGhost!=null)
            {
                _tsMng.EraseTransient(_blkGhost, new IntegerCollection());
                _blkGhost.Dispose();
            }
 
            if (_connectedCurves.Count > 0)
            {
                foreach (var connected in _connectedCurves)
                {
                    if (connected.Ghost != null)
                    {
                        _tsMng.EraseTransient(connected.Ghost, new IntegerCollection());
                        connected.Ghost.Dispose();
                    }
                }
            }
        }
 
        private void UpdateEntities(Point3d point)
        {
            using (var tran = _db.TransactionManager.StartTransaction())
            {
                var blk = (BlockReference)tran.GetObject(_blkId, OpenMode.ForWrite);
                blk.TransformBy(Matrix3d.Displacement(blk.Position.GetVectorTo(point)));
 
                foreach (var connected in _connectedCurves)
                {
                    var curve = (Curve)tran.GetObject(connected.ConnectedCurveId, OpenMode.ForWrite);
                    UpdateConnectedCurve(curve, point, connected.AtStartPoint);
                }
 
                tran.Commit();
            }
        }
 
        private void UpdateConnectedCurve(Curve curve, Point3d pointbool changeStart)
        {
            if (curve is Line)
            {
                var line = (Line)curve;
                if (changeStart)
                {
                    line.StartPoint = point;
                }
                else
                {
                    line.EndPoint = point;
                }
            }
            else
            {
                var poly = (CadDb.Polyline)curve;
                if (changeStart)
                {
                    poly.SetPointAt(0, new Point2d(point.X, point.Y));
                }
                else
                {
                    poly.SetPointAt(poly.NumberOfVertices-1, new Point2d(point.X,point.Y));
                }
            }
        }
 
        #endregion
    }
}

Then the CommandMethod to run the code:
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assembly: CommandClass(typeof(CurveConnectedBlockJig.MyCommands))]
 
namespace CurveConnectedBlockJig
{
    public class MyCommands
    {
        [CommandMethod("MoveBlk")]
        public static void RunCommand()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            var opt = new PromptEntityOptions(
                "\nSelect curve-connected block:");
            opt.SetRejectMessage("Invalid: must be a block:");
            opt.AddAllowedClass(typeof(BlockReference), true);
 
            var res = ed.GetEntity(opt);
            if (res.Status== PromptStatus.OK)
            {
                using (var jig = new MyBlockJig(dwg, res.ObjectId))
                {
                    jig.Move();
                }
            }
 
            ed.WriteMessage("\n");
        }
 
    }
}

See the video clip below for the code in action:


As I mentioned, it would be equally easy to just derive a custom DrawJig for the same visual effect. If I can find extra time, I might put it up (hint: MIGHT, 😃).


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.