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.