Sunday, March 24, 2024

Showing Intersecting Polygons/Areas in Jig's Dragging - Take 2

In my previous article, I used region entities to show the dragged polygon and the intersecting polygons/areas. Since the focus of the topic is to show intersecting areas during jig dragging, presenting the intersecting area in more visually eye-catching manner would be desired. Obviously, if the intersecting areas are filled/hatched in distinguishing color would be much better. 

So, I updated the Jig's code by showing the intersecting area as closed polyline, and then apply a DrawableOverrule to force the polyline to be shown as a color-filled polygon. Following is the code of the DrawableOverrule:

using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using Autodesk.AutoCAD.Runtime;
 
namespace AreaIntersectionJig
{
    public class PolylineToPolygonOverrule : DrawableOverrule
    {
        private static bool _originalOverruling;
        public PolylineToPolygonOverrule()
        {
            _originalOverruling = Overruling;
            AddOverrule(RXObject.GetClass(
                typeof(Autodesk.AutoCAD.DatabaseServices.Polyline)), this, true);
            SetCustomFilter();
            Overruling = true;
        }
 
        public override bool IsApplicable(RXObject overruledSubject)
        {
            Autodesk.AutoCAD.DatabaseServices.Polyline pl = overruledSubject
                as Autodesk.AutoCAD.DatabaseServices.Polyline;
 
            if (pl != null && pl.Closed)
            {
                //Only apply this overrule to the polyline
                //that has not been added to working database
                //e.g. created temp polyline as intersecting area during jigging
                if (pl.Database == null)
                    return true;
                else
                    return false;
            }
            else
            {
                return false;
            }
        }
 
        public override bool WorldDraw(Drawable drawable, WorldDraw wd)
        {
            var poly = drawable as Autodesk.AutoCAD.DatabaseServices.Polyline;
            if (poly != null)
            {
                Point3dCollection pts = new Point3dCollection();
                for (int i = 0; i < poly.NumberOfVertices; i++)
                {
                    pts.Add(poly.GetPoint3dAt(i));
                }
 
                var oldFillType=wd.SubEntityTraits.FillType;
                
                wd.SubEntityTraits.FillType = FillType.FillAlways;
                wd.Geometry.Polygon(pts);
 
                wd.SubEntityTraits.FillType=oldFillType;
            }
 
            return base.WorldDraw(drawable, wd);
        }
 
        protected override void Dispose(bool disposing)
        {
            RemoveOverrule(RXObject.GetClass(
                typeof(Autodesk.AutoCAD.DatabaseServices.Polyline)), this);
            Overruling = _originalOverruling;
 
            base.Dispose(disposing);
        }
    }
}

Pay attention to the overridden IsApplicable() method. Because the target polyline to be overruled is the dynamically created polyline showing the intersecting area, which is not database-residing, thus, only polyline with its Database property is null is targeted.

The Jig itself is not much changed in comparison to the code in the previous article. Here is the Jig class (since it is in the same project as the previous Jig class, so, I give it a new class name "AreaIntersectingJig", the changed code is highlighted in red here):

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using System;
using System.Collections.Generic;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
using CadDb = Autodesk.AutoCAD.DatabaseServices;
 
namespace AreaIntersectionJig
{
    public class AreaIntersectingJig : DrawJig, IDisposable
    {
        private List<Region> _regions;
 
        private Region _movingRegion;
        private List<CadDb.Polyline> _intersectingAreas = new List<CadDb.Polyline>();
 
        private Point3d _basePoint;
        private Point3d _currPoint;
 
        private PolylineToPolygonOverrule _polygonOverrule = null;
 
        public AreaIntersectingJig(
            CadDb.Polyline polygon, Point3d basePoint, List<Region> regions)
        {
            _regions = regions;
            _movingRegion = CreateRegionFromPolyline(polygon);
            _basePoint = basePoint;
            _currPoint = basePoint;
 
            _polygonOverrule = new PolylineToPolygonOverrule();
        }
 
        public Point3d JigPoint => _currPoint;
 
        public void Dispose()
        {
            ClearIntersectingAreas();
            _movingRegion.Dispose();
            _polygonOverrule.Dispose();
        }
 
        protected override SamplerStatus Sampler(JigPrompts prompts)
        {
            var opt = new JigPromptPointOptions("\nMove to:");
            opt.UseBasePoint = true;
            opt.BasePoint = _basePoint;
            opt.Cursor = CursorType.RubberBand;
 
            var res = prompts.AcquirePoint(opt);
            if (res.Status == PromptStatus.OK)
            {
                if (res.Value.Equals(_currPoint))
                {
                    return SamplerStatus.NoChange;
                }
                else
                {
                    var mt = Matrix3d.Displacement(_currPoint.GetVectorTo(res.Value));
                    _movingRegion.TransformBy(mt);
                    _currPoint = res.Value;
 
                    GenerateIntersectingAreas();
 
                    return SamplerStatus.OK;
                }
            }
            else
            {
                return SamplerStatus.Cancel;
            }
        }
 
        protected override bool WorldDraw(WorldDraw draw)
        {
            draw.Geometry.Draw(_movingRegion);
 
            if (_intersectingAreas.Count > 0)
            {
                foreach (var item in _intersectingAreas)
                {
                    draw.Geometry.Draw(item);
                }
            }
            return true;
        }
 
        private Region CreateRegionFromPolyline(CadDb.Polyline poly)
        {
            var dbCol = new DBObjectCollection() { poly };
            var regs = Region.CreateFromCurves(dbCol);
            if (regs.Count > 0)
            {
                var region = (Region)regs[0];
                return region;
            }
            else
            {
                throw new ArgumentException(
                    "Selected polyline cannot form a Region object.");
            }
        }
 
        private void ClearIntersectingAreas()
        {
            if (_intersectingAreas.Count > 0)
            {
                foreach (var item in _intersectingAreas) item.Dispose();
            }
        }
 
        private void GenerateIntersectingAreas()
        {
            ClearIntersectingAreas();
 
            var colorIndex = 1;
            foreach (var r in _regions)
            {
                using (var tempRegion = _movingRegion.Clone() as Region)
                {
                    var intersectingRegion = r.Clone() as Region;
                    intersectingRegion.BooleanOperation(
                        BooleanOperationType.BoolIntersect, tempRegion);
                    if (!intersectingRegion.IsNull)
                    {
                        var polygon = intersectingRegion.ToBoundaryPolyline();
                        if (polygon != null)
                        {
                            polygon.ColorIndex = colorIndex;
                            _intersectingAreas.Add(polygon);
 
                            colorIndex++;
                            if (colorIndex > 6) colorIndex = 1;
                        }
                    }
                }
            }
            CadApp.UpdateScreen();
        }
    }
}

To be complete, here are the codes for the command method and the extension method that get region's boundary as polyline.

Region extension method:

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using System;
using System.Collections.Generic;
 
namespace AreaIntersectionJig
{
    // Following code is mainly based on the code from Mr. Gilles Chanteau,
    // posted in Autodesk's AutoCAD .NET API discussion forum
    // https://forums.autodesk.com/t5/net/create-the-outermost-border-for-the-curves-boundary/td-p/12164598
    // I modified the code slightly to make it an Extension Method to be used in my article
    public static class RegionExtension
    {
        private struct Segment
        {
            public Point2d StartPt { get; set; }
            public Point2d EndPt { get; set; }
            public double Bulge { get; set; }
        }
        public static Polyline ToBoundaryPolyline(this Region reg)
        {
            var segments = new DBObjectCollection();
            reg.Explode(segments);
 
            var segs = new List<Segment>();
            var plane = new Plane(Point3d.Origin, reg.Normal);
            for (int i = 0; i < segments.Count; i++)
            {
                if (segments[i] is Region r)
                {
                    r.Explode(segments);
                    continue;
                }
                Curve crv = (Curve)segments[i];
                Point3d start = crv.StartPoint;
                Point3d end = crv.EndPoint;
                double bulge = 0.0;
                if (crv is Arc arc)
                {
                    double angle = arc.Center.GetVectorTo(start).
                        GetAngleTo(arc.Center.GetVectorTo(end), arc.Normal);
                    bulge = Math.Tan(angle / 4.0);
                }
                segs.Add(
                    new Segment 
                    { 
                        StartPt = start.Convert2d(plane), 
                        EndPt = end.Convert2d(plane), 
                        Bulge = bulge 
                    });
            }
 
            foreach (DBObject o in segments) o.Dispose();
 
            var pline = new Polyline();
            pline.AddVertexAt(0, segs[0].StartPt, segs[0].Bulge, 0.0, 0.0);
            Point2d pt = segs[0].EndPt;
            segs.RemoveAt(0);
            int vtx = 1;
            while (true)
            {
                int i = segs.FindIndex((s) => s.StartPt.IsEqualTo(pt) || s.EndPt.IsEqualTo(pt));
                if (i < 0) break;
                Segment seg = segs[i];
                if (seg.EndPt.IsEqualTo(pt))
                    seg = new Segment { StartPt = seg.EndPt, EndPt = seg.StartPt, Bulge = -seg.Bulge };
                pline.AddVertexAt(vtx, seg.StartPt, seg.Bulge, 0.0, 0.0);
                pt = seg.EndPt;
                segs.RemoveAt(i);
                vtx++;
            }
            pline.Closed = true;
            return pline;
        }
    }
}

CommandMethod:

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System.Collections.Generic;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assembly: CommandClass(typeof(AreaIntersectionJig.MyCommands))]
 
namespace AreaIntersectionJig
{
    public class MyCommands
    {
        [CommandMethod("MyJig")]
        public static void RunMyCommand()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var editor = dwg.Editor;
 
            var poly = SelectPolygon(editor);
            if (poly.entId.IsNull)
            {
                editor.WriteMessage("\n*Cancel*\n");
                return;
            }
 
            var regionIds = SelectRegions(editor);
            if (regionIds==null)
            {
                editor.WriteMessage("\n*Cancel*\n");
                return;
            }
 
            using (var tran = dwg.TransactionManager.StartTransaction())
            {
                var movingPolygon = (Polyline)tran.GetObject(poly.entId, OpenMode.ForWrite);
                var regions = GetRegions(regionIds, tran);
                var jigOk = false;
 
                //using (var jig = new PolygonIntersectingJig(movingPolygon, poly.basePt, regions))
                using (var jig = new AreaIntersectingJig(movingPolygon, poly.basePt, regions))
                {
                    var res = editor.Drag(jig);
                    if (res.Status == PromptStatus.OK)
                    {
                        var mt = Matrix3d.Displacement(poly.basePt.GetVectorTo(jig.JigPoint));
                        movingPolygon.TransformBy(mt);
 
                        jigOk = true;
                    }
                }
                
                if (jigOk)
                {
                    tran.Commit();
                }
                else
                {
                    tran.Abort();
                }
            }
            editor.UpdateScreen();
        }
 
        private static (ObjectId entId, Point3d basePt) SelectPolygon(Editor ed)
        {
            var opt = new PromptEntityOptions("\nSelect a closed polyline:");
            opt.SetRejectMessage("\nInvalid: not a polyline.");
            opt.AddAllowedClass(typeof(Polyline), true);
 
            var res = ed.GetEntity(opt);
            if (res.Status== PromptStatus.OK)
            {
                var pRes = ed.GetPoint("\nSelect base point for moving:");
                if (pRes.Status == PromptStatus.OK)
                {
                    return (res.ObjectId, pRes.Value);
                }
                else
                {
                    return (ObjectId.Null, new Point3d());
                }
            }
            else
            {
                return (ObjectId.Null, new Point3d());
            }
        }
 
        private static IEnumerable<ObjectId> SelectRegions(Editor ed)
        {
            var vals = new TypedValue[] { new TypedValue((int)DxfCode.Start, "REGION") };
            var filter = new SelectionFilter(vals);
 
            var opt = new PromptSelectionOptions();
            opt.MessageForAdding = "\nSelect regions:";
 
            var res = ed.GetSelection(opt, filter);
            if (res.Status== PromptStatus.OK)
            {
                return res.Value.GetObjectIds();
            }
            else
            {
                return null;
            }
        }
 
        private static List<Region> GetRegions(IEnumerable<ObjectId> regionIds, Transaction tran)
        {
            var regions= new List<Region>();
 
            foreach (var regionId in regionIds)
            {
                var region = (Region)tran.GetObject(regionId, OpenMode.ForRead);
                regions.Add(region);
                
            }
 
            return regions;
        }
    }
}

See video clip below:


Regardless of the practical usefulness of this Jig, the visual effect is much better, for sure, which is the focus of the topic of this article.

No comments:

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.