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, 😃).


1 comment:

MinhNguyen said...

Thank you for your tutorial. Could you please expand on the topic of splines? Can you provide a tutorial on moving blocks with spline objects? Thank you for sharing

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.