Tuesday, March 5, 2024

Showing Intersecting Polygons/Areas in Jig's Dragging

A recent discussion in the AutoCAD .NET forum raises question about how to show a dynamic area visually during jig's dragging. 


The OP tried to use Hatch to present the dynamic area. However, creating Hatch entity could only be completed by appending necessary HatchLoops, which require database-residing entities playing the role of the loop. So, it is imagine-able that using Hatch to show a dynamic area during Jig's dragging operation, AutoCAD needs to adding HatchLoop entity or entities into database and then erase it repeatedly in high rate (depending how fast the user drags the mouse), therefore the operation would be sticky, as expected, or even not practically usable. This reminds me one of my old article about a custom "AREA" command, in that article I also used Hatch to show dynamic area in a "Jig" style.

In the case of the OP, the Jig would drag a closed curve, or a region, entity against a collection of regions (or simply a collection of closed curves), during the dragging, the dynamically identifiable intersecting areas should be visually presented. Obviously, region entity would be used inside the Jig's code for the boolean-intersecting calculation in order to identify the intersecting areas.

For simplicity, I create a DrawJig with the inputs of a closed polyline (as the moving area) and a collection of regions (as the target areas that could be intersecting the moving area).

Here is the class PolygonIntersectingJig:
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
using CadDb = Autodesk.AutoCAD.DatabaseServices;
using System;
using System.Collections.Generic;
using Autodesk.AutoCAD.GraphicsInterface;
 
namespace AreaIntersectionJig
{
    public class PolygonIntersectingJig : DrawJig, IDisposable
    {
        private CadDb.Polyline _polygon;
        private List<Region> _regions;
 
        private Region _movingRegion;
        private List<Region> _intersectingRegions = new List<Region>();
 
        private Point3d _basePoint;
        private Point3d _currPoint;
 
        public PolygonIntersectingJig(
            CadDb.Polyline polygon, Point3d basePoint, List<Region> regions)
        {
            _polygon = polygon;
            _regions= regions;
            _movingRegion = CreateRegionFromPolyline(polygon);
            _basePoint= basePoint;
            _currPoint= basePoint;
        }
 
        public Point3d JigPoint => _currPoint;
 
        public void Dispose()
        {
            ClearIntersectingAreas();
            _movingRegion.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 (_intersectingRegions.Count > 0)
            {
                foreach (var item in _intersectingRegions)
                {
                    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];
                region.ColorIndex = 2;
                return region;
            }
            else
            {
                throw new ArgumentException(
                    "Selected polyline cannot form a Region object.");
            }
        }
 
        private void ClearIntersectingAreas()
        {
            if (_intersectingRegions.Count > 0)
            {
                foreach (var item in _intersectingRegions) item.Dispose();
            }
        }
 
        private void GenerateIntersectingAreas()
        {
            ClearIntersectingAreas();
 
            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)
                    {
                        intersectingRegion.ColorIndex = 1;
                        _intersectingRegions.Add(intersectingRegion);
                    }
                }
            }
            CadApp.UpdateScreen();
        }
 
        private Curve FindIntersectingArea(Region region1, Region region2)
        {
            Curve areaCurve = null;
            region1.BooleanOperation(BooleanOperationType.BoolIntersect, region2);
            if (!region1.IsNull)
            {
                areaCurve = region1.ToBoundaryPolyline();
            }
            return areaCurve;
        }
    }
}
Here is the CommandMethod that runs the PolygonIntersectingJig:
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))
                {
                    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 the following video clip showing how the dynamic intersecting areas are visually presented during the jig's dragging:


Without using Hatch to show dynamic intersecting areas, the Jig's dragging is responsive quite well. However, the presented dynamic areas might not be as eye-catching "hatched" area, fore sure. But I thought a proper color of the region would be visually satisfying in most cases. One possible solution I could try is to use MPolygon for the visual presentation.

The code here is only meant for showing how to presenting dynamic intersecting areas during Jig's dragging. As for the practical use of it, there could be a few enhancements to make it useful, such as dynamically showing the intersecting areas' area value.

UPDATE
I forgot to include an extension method that returns a Polyline as a region's boundary (Thank you, CubeK, for pointed out in your comment). Here is the code:
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;
        }
    }
}

2 comments:

  1. Hi Norman.
    we have an error on this one:
    areaCurve = region1.ToBoundaryPolyline();

    .ToBoundaryPolyline() is not recognized.

    ReplyDelete
  2. Thank you, CubeK, for letting me know the missing code. I just updated the article with the missing code appended at the end.

    ReplyDelete