TheSwamp

Code Red => .NET => Topic started by: waterharbin on February 10, 2012, 02:20:06 AM

Title: How to detect a line near a Point? And then get the distance between them?
Post by: waterharbin on February 10, 2012, 02:20:06 AM
Hello,everyone? Is it possible to "detect" a line near a point? Let's suppose it is the nearest line to be detected.If yes, then return the distance.
If a point is not enough,what about a line? "Detect" the nearest parallel line,and then return the distance?
Any suggestion or code is helpful.
Title: Re: How to detect a line near a Point? And then get the distance between them?
Post by: Kerry on February 10, 2012, 03:42:03 AM

I've done this using a multisided polygon as a fence for a selection set.
loop through, minutely increasing the size of the polygon untill a suitable object gets caught in the selection.

Perhaps not really elegant but seemed to work fine.

Regards

Title: Re: How to detect a line near a Point? And then get the distance between them?
Post by: waterharbin on February 10, 2012, 07:37:25 AM
Hell0,Kerry.Thanks.Can you give me some codes for me to start with?I am totally known nothing about this at the present.
Title: Re: How to detect a line near a Point? And then get the distance between them?
Post by: Kerry on February 10, 2012, 06:20:44 PM

I'll be away for a few hours ... will have a look at a sample when I come back, unless someone else pops up a solution in the meantime.

Something to think about :
Title: Re: How to detect a line near a Point? And then get the distance between them?
Post by: gile on February 11, 2012, 01:27:06 PM
Hi,

Here's another way, certainly slower if there're many entities in the current space, but much more easy to code. :evil:

Code - C#: [Select]
  1. using System.Linq;
  2. using Autodesk.AutoCAD.ApplicationServices;
  3. using Autodesk.AutoCAD.DatabaseServices;
  4. using Autodesk.AutoCAD.EditorInput;
  5. using Autodesk.AutoCAD.Geometry;
  6. using Autodesk.AutoCAD.Runtime;
  7.  
  8. namespace ClosestLineSample
  9. {
  10.     public class Commands
  11.     {
  12.         [CommandMethod("Test")]
  13.         public void Test()
  14.         {
  15.             Document doc = Application.DocumentManager.MdiActiveDocument;
  16.             Database db = doc.Database;
  17.             Editor ed = doc.Editor;
  18.             PromptPointResult ppr = ed.GetPoint("\nSpecify a point: ");
  19.             if (ppr.Status != PromptStatus.OK) return;
  20.             Point3d pt = ppr.Value.TransformBy(ed.CurrentUserCoordinateSystem);
  21.             using (Transaction tr = db.TransactionManager.StartTransaction())
  22.             {
  23.                 BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForRead);
  24.                 var closest = btr
  25.                     .Cast<ObjectId>()
  26.                     .Select(id => tr.GetObject(id, OpenMode.ForRead) as Line)
  27.                     .Where(ent => ent != null)
  28.                     .Select(line => new { Line = line, Dist = pt.DistanceTo(line.GetClosestPointTo(pt, false)) })
  29.                     .Aggregate((l1, l2) => l1.Dist < l2.Dist ? l1 : l2);
  30.                 if (closest != null)
  31.                 {
  32.                     closest.Line.Highlight();
  33.                     ed.WriteMessage("\nDistance = {0}", closest.Dist);
  34.                 }
  35.                 tr.Commit();
  36.             }
  37.         }
  38.     }
  39. }
  40.  
Title: Re: How to detect a line near a Point? And then get the distance between them?
Post by: Kerry on February 11, 2012, 06:00:28 PM

Looks like a fun solution gile.

Querying the complete database may take a little longer, yes, but thankfully doesn't have to deal with the requirement that selections must be in the current view.

The linq makes for a nice solution.

I haven't ananlysed the code, but the distance will be in the WCS, yes ? as will the Point parameter.

Regards,

Title: Re: How to detect a line near a Point? And then get the distance between them?
Post by: Gasty on February 11, 2012, 09:27:51 PM
Hi,

The distance is invariant under CS changes if all the points are in the same CS.

Gaston Nunez
Title: Re: How to detect a line near a Point? And then get the distance between them?
Post by: gile on February 12, 2012, 03:58:47 AM
Thanks Kerry.
Right Gaston.

In the example, the point got with Editor.GetPoint() is defined in current UCS and have to be transformed from current UCS to WCS to get the distance to the point returned by Line.GetClosestPointTo() which is always WCS defined.

Linq allows to write code in a more 'LISPy' way.
Title: Re: How to detect a line near a Point? And then get the distance between them?
Post by: gile on February 12, 2012, 04:30:44 AM
Maybe some aren't very comfortable with the Linq functional style, so, here's a more traditional impertive implementation.

Code - C#: [Select]
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Geometry;
  5. using Autodesk.AutoCAD.Runtime;
  6. using System.Collections.Generic;
  7.  
  8. namespace ClosestLineSample
  9. {
  10.     public class Commands
  11.     {
  12.         [CommandMethod("Test")]
  13.         public void Test()
  14.         {
  15.             Document doc = Application.DocumentManager.MdiActiveDocument;
  16.             Database db = doc.Database;
  17.             Editor ed = doc.Editor;
  18.             PromptPointResult ppr = ed.GetPoint("\nSpecify a point: ");
  19.             if (ppr.Status != PromptStatus.OK) return;
  20.             Point3d pt = ppr.Value.TransformBy(ed.CurrentUserCoordinateSystem);
  21.             using (Transaction tr = db.TransactionManager.StartTransaction())
  22.             {
  23.                 BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForRead);
  24.                 Line closest = null;
  25.                 double shortest = 0.0;
  26.                 foreach (ObjectId id in btr)
  27.                 {
  28.                     Line line = tr.GetObject(id, OpenMode.ForRead) as Line;
  29.                     if (line != null)
  30.                     {
  31.                         double dist = pt.DistanceTo(line.GetClosestPointTo(pt, false));
  32.                         if (closest == null)
  33.                         {
  34.                             closest = line;
  35.                             shortest = dist;
  36.                         }
  37.                         else if (shortest > dist)
  38.                         {
  39.                             closest = line;
  40.                             shortest = dist;
  41.                         }
  42.                     }
  43.                 }
  44.                 if (closest != null)
  45.                 {
  46.                     closest.Highlight();
  47.                     ed.WriteMessage("\nDistance = {0}", shortest);
  48.                 }
  49.                 tr.Commit();
  50.             }
  51.         }
  52.     }
  53. }
  54.  
Title: Re: How to detect a line near a Point? And then get the distance between them?
Post by: waterharbin on February 12, 2012, 06:52:44 AM
Hi,gile.If I want to rule out the case which distance is 0(The point lays in the straight line).I should change your great code liks this?
Code: [Select]
foreach (ObjectId id in btr)
                {
                    Line line = tr.GetObject(id, OpenMode.ForRead) as Line;
                    if (line != null)
                    {
                        double dist = pt.DistanceTo(line.GetClosestPointTo(pt, false));

                      [b]if (dist == 0)
                            continue;[/b]                     
                     if (closest == null)
                        {
                            closest = line;
                            shortest = dist;
                        }
                        else if (shortest > dist)
                        {
                            closest = line;
                            shortest = dist;
                        }
                    }
                }

By the way.The codes in your If...else if ... are the same.Why this?
Title: Re: How to detect a line near a Point? And then get the distance between them?
Post by: gile on February 12, 2012, 07:15:57 AM
Quote
By the way.The codes in your If...else if ... are the same.Why this?
You're right.
The first if will only occur for the first iteration, the else if for the following ones.

It might have bee written:
(if (closest == null || shortest > dist) { ... }

Or, introducing your rule:
(if (closest == null || (dist != 0.0 && shortest > dist)) { ... }
Title: Re: How to detect a line near a Point? And then get the distance between them?
Post by: waterharbin on February 12, 2012, 09:31:55 AM
Thanks to gile and Kerry.Job is done! This is the codes.Using a SelectCrossingWindow to reduse the number of entitier.
Code: [Select]
public static void UseCrossingWindow()
        {
            Document doc = Application.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;

            using (Transaction trans = db.TransactionManager.StartTransaction())
            {
                PromptPointResult ppr = ed.GetPoint("\n Sepecify the first Point:");

                if (ppr.Status != PromptStatus.OK) return;

                Point3d pt1 = ppr.Value.TransformBy(ed.CurrentUserCoordinateSystem);
               
                //Get another point to form a SeltectCrossingWindow
                ppr = ed.GetPoint("\n Sepecify the second Point:");

                if (ppr.Status != PromptStatus.OK) return;

                Point3d pt2 = ppr.Value.TransformBy(ed.CurrentUserCoordinateSystem);
               
                // Create a crossing window based on the two selected points
                PromptSelectionResult acSSPrompt;
               
                acSSPrompt = ed.SelectCrossingWindow(pt1, pt2);
               
                // If the prompt status is not OK, do nothing.
                if (acSSPrompt.Status != PromptStatus.OK)
                    return;

                SelectionSet acSSet = acSSPrompt.Value;

                ObjectId[] ids = acSSet.GetObjectIds();


                Line closest = null;
                double shortest = 0.0;

                foreach (ObjectId id in ids)
                {
                    Line line = trans.GetObject(id, OpenMode.ForRead) as Line;
                    if (line != null)
                    {                       
                        double dist = pt1.DistanceTo(line.GetClosestPointTo(pt1, false));

                        if (dist == 0)
                            continue;

                        if (closest == null)
                        {
                            closest = line;
                            shortest = dist;
                        }
                        else if (shortest > dist)
                        {
                            closest = line;
                            shortest = dist;
                        }
                    }
                }

                if (closest != null)
                {
                    closest.Highlight();
                    ed.WriteMessage("\nDistance = {0}", shortest);
                }


                /*
                double shortest = 0.0;
                foreach (ObjectId id in ids)
                {
                    Entity ent = (Entity)trans.GetObject(id, OpenMode.ForWrite);

                    if (ent.GetType().Name == "Line")
                    {
                        Line myLine = (Line)trans.GetObject(id, OpenMode.ForWrite);

                        double dist = pt1.DistanceTo(myLine.GetClosestPointTo(pt1,false));

                        if (dist!=0 && shortest > dist)
                        {
                            shortest = dist;
                        }

                    }
                }     //End of foreach loop
                ed.WriteMessage("\nDistance = {0}", shortest);
                */
                               
                trans.Commit();
            }     
        }
By the way.The codes which have be commented out don't work. The result is "0".
And another question,how to rule out other kinds of entities when define the SelectCrossingWindow?


Hoping it will be helpful someone else!
Title: Re: How to detect a line near a Point? And then get the distance between them?
Post by: gile on February 12, 2012, 11:53:41 AM
1_ The Editor selection methods require current UCS defined points, so you do not have to use TransformBy(ed.CurrentUserCoordinateSystem); on the points returned by Editor.GetPoint().

2_ The commented code you posted do not work because the 'shortest' variable is assign to 0.0 and will never change because it will always be lower than dist. Look at the code I gave, in the first iteration 'dist' value is assigned to 'shortest'.

3_ You can use SelectionFilter to select only Line objects. One constructor of the SelectionFilter  type take a TypedValue array as parameter.
Code - C#: [Select]
  1. TypedValue[] filter = new TypedValue[1]{ new TypedValue(0, "LINE") };
  2. acSSPrompt = ed.SelectCrossingWindow(pt1, pt2, new SelectionFilter(filter));
Have a look at the AutoCAD .NET Developper's Guide > Create and Edit AutoCAD Entities > Work with Selection Sets > Define Rules for Selection Filters (http://docs.autodesk.com/ACD/2010/ENU/AutoCAD%20.NET%20Developer%27s%20Guide/files/WS1a9193826455f5ff2566ffd511ff6f8c7ca-406f.htm) (and the sub topics).
Title: Re: How to detect a line near a Point? And then get the distance between them?
Post by: CADbloke on April 23, 2015, 07:20:00 PM
This is a variation on Giles' post above (http://www.theswamp.org/index.php?topic=40900.msg461532#msg461532) that is looking for coincident objects with a tolerance in the Y direction.

Code - C#: [Select]
  1. /// <summary> how far apart the X positions can be before declaring them totally related. </summary>
  2. double XoffsetCloseEnough = 5;
  3.  
  4. /// <summary> how far apart the Y positions can be before declaring them unrelated. </summary>
  5. double YoffsetTooFarAway  = 1;
  6.  
  7. /// <summary> Random number that just happens to be 500. </summary>
  8. double shortest = 500;
  9.  
  10. /// <summary> This is the same Type as a thing in things </summary>
  11. TypeOfThing closestThing = null;
  12.  
  13. foreach (TypeOfThing thing in things)
  14. {
  15.   if (Math.Abs(thing.Position.Y - otherThing.Position.Y) > YoffsetTooFarAway) continue;
  16.  
  17.   double distance = Math.Abs(thing.Position.X - otherThing.Position.X);
  18.  
  19.   if (distance < shortest)
  20.   {
  21.     closestThing = thing;
  22.     shortest = distance;
  23.     if (distance < XoffsetCloseEnough) break; // That'll do, Pig, that'll do.
  24.   }
  25. }
  26.  

Notice I omitted the null check in the loop because there is a null check after the loop anyway. Null checks are cheap anyway so it won't kill you.

I also added a break; to bail out of the loop when it gets close enough,  just a premature optimi[zs]ation (hello Regex fans) based on where I expect these objects to be. In my case the objects were AttributeReferences.
Title: Re: How to detect a line near a Point? And then get the distance between them?
Post by: BKSpurgeon on December 21, 2016, 06:20:28 AM
Mr Gilles thank you this code is very nice.  As I understand it, if applied with the GetObjects then you can string things together with to get whittle down to a super filtered set of entities?

e.g.

Code: [Select]
using (Transaction tr = doc.Database.TransactionManager.StartTransaction())
            {
                IEnumerable<Entities> filteredEntities = objectIDs.GetObjects<Entity>(OpenMode.ForRead, false, true).Where( predicate).Select(predicate) etc. etc. ;
            }


Where GetObjects is:

Code: [Select]

        public static IEnumerable<T> GetObjects<T>(
            this IEnumerable<ObjectId> ids,
            OpenMode mode = OpenMode.ForRead,
            bool openErased = false,
            bool forceOpenOnLockedLayers = false) where T : DBObject
        {
            if (ids.Any())
            {
                TransactionManager tm = ids.First().Database.TransactionManager;

                RXClass rxclass = RXClass.GetClass(typeof(T));
                if (typeof(T) == typeof(DBObject))
                {
                    foreach (ObjectId id in ids)
                    {
                        yield return (T)tm.GetObject(id, mode, openErased,
                           forceOpenOnLockedLayers);
                    }
                }

                else if (rxclass.GetHasChildren())
                {
                    foreach (ObjectId id in ids)
                    {
                        if (id.ObjectClass.IsDerivedFrom(rxclass))
                            yield return (T)tm.GetObject(id, mode, openErased,
                               forceOpenOnLockedLayers);
                    }
                }
                else
                {
                    foreach (ObjectId id in ids)
                    {
                        if (id.ObjectClass == rxclass)
                            yield return (T)tm.GetObject(id, mode, openErased,
                               forceOpenOnLockedLayers);
                    }
                }
            }
        }

        // Returns true if at least one RXClass is derived from
        // the given RXClass:

        public static bool GetHasChildren(this RXClass rxclass)
        {
            foreach (DictionaryEntry e in SystemObjects.ClassDictionary)
            {
                if (rxclass == ((RXClass)e.Value).MyParent)
                    return true;
            }
            return false;
        }





Hi,

Here's another way, certainly slower if there're many entities in the current space, but much more easy to code. :evil:

Code - C#: [Select]
  1. using System.Linq;
  2. using Autodesk.AutoCAD.ApplicationServices;
  3. using Autodesk.AutoCAD.DatabaseServices;
  4. using Autodesk.AutoCAD.EditorInput;
  5. using Autodesk.AutoCAD.Geometry;
  6. using Autodesk.AutoCAD.Runtime;
  7.  
  8. namespace ClosestLineSample
  9. {
  10.     public class Commands
  11.     {
  12.         [CommandMethod("Test")]
  13.         public void Test()
  14.         {
  15.             Document doc = Application.DocumentManager.MdiActiveDocument;
  16.             Database db = doc.Database;
  17.             Editor ed = doc.Editor;
  18.             PromptPointResult ppr = ed.GetPoint("\nSpecify a point: ");
  19.             if (ppr.Status != PromptStatus.OK) return;
  20.             Point3d pt = ppr.Value.TransformBy(ed.CurrentUserCoordinateSystem);
  21.             using (Transaction tr = db.TransactionManager.StartTransaction())
  22.             {
  23.                 BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForRead);
  24.                 var closest = btr
  25.                     .Cast<ObjectId>()
  26.                     .Select(id => tr.GetObject(id, OpenMode.ForRead) as Line)
  27.                     .Where(ent => ent != null)
  28.                     .Select(line => new { Line = line, Dist = pt.DistanceTo(line.GetClosestPointTo(pt, false)) })
  29.                     .Aggregate((l1, l2) => l1.Dist < l2.Dist ? l1 : l2);
  30.                 if (closest != null)
  31.                 {
  32.                     closest.Line.Highlight();
  33.                     ed.WriteMessage("\nDistance = {0}", closest.Dist);
  34.                 }
  35.                 tr.Commit();
  36.             }
  37.         }
  38.     }
  39. }
  40.  
Title: Re: How to detect a line near a Point? And then get the distance between them?
Post by: gile on December 21, 2016, 07:06:56 AM
Hi,

keep in mind GetObjects<T> already filters by Entity type (T).

Code - C#: [Select]
  1. var closest = btr
  2.     .GetObjects<Line>(OpenMode.ForRead, false, true)
  3.     .Select(line => new { Line = line, Dist = pt.DistanceTo(line.GetClosestPointTo(pt, false)) })
  4.     .Aggregate((l1, l2) => l1.Dist < l2.Dist ? l1 : l2);