.NET
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Apply A Spatial Filter To Improve Processing Time

8 REPLIES 8
SOLVED
Reply
Message 1 of 9
jschierenbeck
509 Views, 8 Replies

Apply A Spatial Filter To Improve Processing Time

I have a program that locates a line at a certain point. Right now the code iterates through the entire BlockTableRecord  until if finds a line where the distance to is 0.

 

Rather than iterating through the entire drawing set, it would be much faster to apply a spatial filter to only search through entities that are a certain distance within a certain distance of the search point. How can I do this?

 

Here is my current code:

public static Line GetLineAtPoint(Point3d point, Transaction tr, Database db)
{
BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForRead);

foreach (ObjectId id in btr)
{
Line line = tr.GetObject(id, OpenMode.ForWrite) as Line;

if (line != null)
{
double dist = point.DistanceTo(line.GetClosestPointTo(point, false));

if (dist == 0) return line;
}
}

return null;
}

8 REPLIES 8
Message 2 of 9

What do you mean by line at a certain point? If it’s the start or end point, you can use a hashmap and a kd-tree.

Maybe something here will give you some ideas

https://www.theswamp.org/index.php?topic=59487.0

Python for AutoCAD, Python wrappers for ARX https://github.com/CEXT-Dan/PyRx
Message 3 of 9

It could any point where the distance to the line is 0.

I was able to implement this, which really improved run time. Maybe there is still a better way? 
Would also be nice to be able to do this in a side database.

 

var min = new Point3d(point.X - 1, point.Y - 1, 0);
var max = new Point3d(point.X + 1, point.Y + 1, 0);

PromptSelectionResult result = AcEnv.ed.SelectCrossingWindow(min, max);

if (result.Status == PromptStatus.OK)
{
SelectionSet selectionSet = result.Value;
foreach (ObjectId id in selectionSet.GetObjectIds())
{
Line line = tr.GetObject(id, OpenMode.ForWrite) as Line;

if (line != null)
{
double dist = point.DistanceTo(line.GetClosestPointTo(point, false));

if (dist <= .002) return line;
}
}
}

 

Message 4 of 9
_gile
in reply to: jschierenbeck

Hi,

You cannot acces to the editor in a side database, so the only way is to iterate through all entities in model space.

This version should improve you first attempt by:

- checking the ObjectId.ObjectClass instead of opening each entity (for write) and trying to cast it into a Line

- SegmentLine3d.IsOn instead of Curve.GetClosestPointTo.

public static Line GetLineAtPoint(Point3d point, Transaction tr, Database db)
{
    BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForRead);

    foreach (ObjectId id in btr)
    {
        if (id.ObjectClass.Name == "AcDbLine")
        {
            var line = (Line)tr.GetObject(id, OpenMode.ForRead);

            var segment = new LineSegment3d(line.StartPoint, line.EndPoint);
            if (segment.IsOn(point))
                return line;
        }
    }

    return null;
}


Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 5 of 9


@jschierenbeck wrote:

 

Would also be nice to be able to do this in a side database.

 

Not possible. Graphical selection requires a display list so it can't be done in a database that's not open in the editor.

 

Depending on how many queries you need to do (e.g., how many different points do you need to find on lines), you could use a 3d party spatial indexing library that can be used to narrow down the number objects that must be examined.

Message 6 of 9
daniel_cadext
in reply to: _gile

you can use threads on LineSegment3ds right?

Maybe build a list of segments and use parallel for IsOn

Python for AutoCAD, Python wrappers for ARX https://github.com/CEXT-Dan/PyRx
Message 7 of 9

Are the types of objects you need to query for limited to Lines?

 

If yes, then I still see a problem with the solutions posted here, which is that they all presume that there can only be a single object passing through the query point. How can that be?  Is there an assumption that no lines in the drawings you're working with intersect one another?

 

In any case, something I've always strived to do, is to avoid writing specific solutions to generic problems like this one. For example, the code below will operate on any type of Curve, but can also be constrained to operate on only a specific subtype using a generic argument. If you pass Line as the generic argument type, it should do exactly what @_gile 's example does (except that it will find all objects passing though the point, rather than just the first one it finds). You can pass any type derived from Curve as the generic argument (or Spline, Polyline, etc.), and it will look at and return those types. Passing Curve will look at and return any type of Curve, as is done in the example.

 

I wrote it to find all objects that pass through the point, because if you ask me, finding only the first object in a block that passes through a point is like playing spin-the-bottle. Try drawing a few intersecting curves, running the example command, and selecting the point where the curves intersect, and it should select both of them

 

public static class MyExtensions
{ 
   /// <summary>
   /// Returns a sequence of Curve-based types that pass
   /// though the given point. The generic argument can
   /// be Curve or any type derived from it, to constrain
   /// the search and the result to only the specified type.
   /// 
   /// This method is not dependent on the drawing editor,
   /// but the example that follows is.
   /// </summary>

   public static IEnumerable<T> GetCurvesAt<T>(
         this IEnumerable<ObjectId> candidates, 
         Point3d point, 
         Transaction tr) where T: Curve
   {
      RXClass rxclass = RXObject.GetClass(typeof(T));
      foreach(ObjectId id in candidates)
      {
         if(id.ObjectClass.IsDerivedFrom(rxclass))
         {
            T curve = (T)tr.GetObject(id, OpenMode.ForRead);
            Curve3d curve3d = curve.TryGetCurve3d();
            if(curve3d != null && curve3d.IsOn(point))
               yield return curve;
         }
      }
   }

   public static Curve3d TryGetCurve3d(this Curve curve)
   {
      try
      {
         return curve.GetGeCurve();
      }
      catch(Autodesk.AutoCAD.Runtime.Exception)
      {
         return null;
      }
   }
}

public static class Example
{
   [CommandMethod("SELECTCURVESATPOINT", CommandFlags.Redraw)]
   public static void SelectCurvesAtPoint()
   {
      Document doc = Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;
      using(var tr = doc.TransactionManager.StartTransaction())
      {
         try
         {
            var ppr = ed.GetPoint("\nPick a point: ");
            if(ppr.Status != PromptStatus.OK)
               return;
            Point3d pickpt = ppr.Value;
            short pickbox = (short)Application.GetSystemVariable("PICKBOX");
            double offset = (PixelSize * pickbox) / 2.0;
            Vector3d delta = new Vector3d(offset, offset, offset);
            PromptSelectionOptions pso = new PromptSelectionOptions();
            pso.RejectObjectsFromNonCurrentSpace = true;

            /// Use a selection filter to limit the selection
            /// to specific type(s) of objects. This example
            /// looks at all objects:

            var psr = ed.SelectCrossingWindow(pickpt - delta, pickpt + delta);
            if(psr.Status == PromptStatus.OK && psr.Value.Count > 0)
            {
               var ids = psr.Value.GetObjectIds().Cast<ObjectId>();
               var curves = ids.GetCurvesAt<Curve>(pickpt, tr);
               if(curves.Any())
               {
                  var array = curves.Select(crv => crv.ObjectId).ToArray();
                  ed.SetImpliedSelection(array);
                  ed.WriteMessage($"\nFound {array.Length} object(s) at selected point.");
                  return;
               }
            }
            ed.WriteMessage("\nNothing found at selected point. ");
         }
         finally
         {
            tr.Commit();
         }
      }
   }

   static double PixelSize
   {
      get
      {
         double vh = (double)Application.GetSystemVariable("VIEWSIZE");
         Point2d pt = (Point2d)Application.GetSystemVariable("SCREENSIZE");
         return vh / pt.Y;
      }
   }
}

 

 

 

 

 

Message 8 of 9

I was under the impression there was going to be more than one search. In this case it would make sense to cache the AcGeCurve3ds. Still a naïve approach though.

 

Woohoo, found a LINE

ArxTemplate::ArxTemplate_doit 0.003700

 

X 1000

Woohoo, found a LINE

ArxTemplate::ArxTemplate_doit 1.684799

 

150,000 lines

 

    static auto makeBlockTableRecordIter(AcDbBlockTableRecord& btr)
    {
        AcDbBlockTableRecordIterator* iter = nullptr;
        auto err = btr.newIterator(iter);
        return std::make_tuple(err, std::unique_ptr<AcDbBlockTableRecordIterator>(iter));
    }

    static AcDbObjectId searchCurveData(const AcGePoint3d& pnt, const CurveDataArray& curves)
    {
        std::atomic<INT_PTR> atomic_id = 0;
        std::for_each(std::execution::par, curves.begin(), curves.end(), [&](const CurveData& cd)
            {
                if (cd._curve->isOn(pnt))
                    atomic_id = (INT_PTR)(AcDbStub*)cd._id;
            });
        return AcDbObjectId{ (AcDbStub*)atomic_id.load() };
    }

    static CurveDataArray createCurveData(AcDbDatabase* pDb)
    {
        CurveDataArray curves;
        AcDbBlockTableRecordPointer pCurSpace(pDb->currentSpaceId());

        for (auto [err, iter] = makeBlockTableRecordIter(*pCurSpace); err == eOk && !iter->done(); iter->step())
        {
            AcDbObjectId id;
            if (auto es = iter->getEntityId(id); es != eOk)
                continue;
            if (!id.objectClass()->isDerivedFrom(AcDbCurve::desc()))
                continue;
            AcDbObjectPointer<AcDbCurve> pDbCurve(id);
            if (auto es = pDbCurve.openStatus();  es != eOk)
                continue;
            AcGeCurve3d* pGeCurve = nullptr;
            if (auto es = pDbCurve->getAcGeCurve(pGeCurve); es != eOk)
                continue;
            curves.emplace_back(CurveData{ pGeCurve, id });
        }
        return curves;
    }

    static void ArxTemplate_doit(void)
    {
        //Cache so we can search more than one time
        CurveDataArray curves = createCurveData(acdbCurDwg());

        AcGePoint3d pnt;
        acedGetPoint(nullptr, L"\nYep: ", asDblArray(pnt));

        PerfTimer timer(__FUNCTIONW__);

        AcDbObjectId id;
        for (int i = 0; i < 1000; i++)
            id = searchCurveData(pnt, curves);

        if (id != 0)
        {
            AcDbEntityPointer pEnt(id);
            acutPrintf(_T("\nWoohoo, found a %ls"), pEnt->isA()->dxfName());
        }
        timer.end();
    }

 

 

Python for AutoCAD, Python wrappers for ARX https://github.com/CEXT-Dan/PyRx
Message 9 of 9

If there' going to be multiple searches against the same set of curves, I would probably go with spatial indexing. 

 


@daniel_cadext wrote:

I was under the impression there was going to be more than one search. In this case it would make sense to cache the AcGeCurve3ds. Still a naïve approach though.

 

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Forma Design Contest


AutoCAD Beta